Repository navigation
Description
发现于 #4001 批 18。这是 finding 16(union .extend() 陷阱)的一个更深的落点 —— 不在被 .extend() 的基类上,在 union 的成员本身。
事实(端到端追踪,非推断)
ViewItemSchema(packages/spec/src/ui/view.zod.ts)有两副身份:
授权门(想要 strict):
defineViewItem(config)直接.parse()- objectui 的 view 创建表单把
createBuildBody的产物按真 specViewItemSchema校验(app-shell/src/views/metadata-admin/view-create-body.test.ts)
wire 门(必须留开):它是 ViewMetadataSchema 的成员 1 —— saveMetaItem 校验每一份持久化 view body 的那个 union。
追踪链:
- objectui 的 pin 控件 →
dataSource.updateView(objectName, vid, { isPinned: pinned })(app-shell/src/views/ObjectView.tsx:882) updateView(data-objectstack/src/index.ts:2801)先 GET 存储项,再 PUT{ ...current, ...partial, name, object }- 独立 ViewItem 记录的
current带viewKind且带config→ 命中成员 1(扁平 overlay 成员被自己的config: z.undefined()守卫排除) - 于是 body 带着
ViewItemSchema未声明的isPinned到达
今天:strip 掉(且 parsed.data 被丢弃、原始 body 原样落库),保存成功,pin 生效。
收紧后:给一个已保存视图打 pin 会 422。
一句已经过时且承重的散文(批 18 已就地修正)
view.zod.ts 的块注释写:
Auxiliary Studio round-trip keys (
isPinned,sortOrder, …) ride along on every shape: all four members strip-parse (no.strict()) …
写的时候是对的,现在一半错一半承重:成员 2(容器 ViewSchema)自前几批收紧起就是 strict 的(实测 { list: …, isPinned: true } 会 422)。那不是回归 —— updateView 会先 if (current?.list) current = current.list 把容器拆到内层 list config 再合并,所以容器 body 到不了这个 union。但照着这句话推理会得出错误结论,而它正是下一个人决定「成员 1 能不能关」时会读的那句。批 18 已把它换成实测描述。
同时 metadata-type-schemas.test.ts 的 STILL_STRIP 收尾注释把「刻意开放的那个」说成扁平 overlay(成员 3/4,它们确实显式 .strip() 并写了理由)。成员 1 从来没被这样点名过 —— 它只是碰巧还是 strip。
需要裁定
- A. 拆:
ViewItemSchema收紧给授权门用;ViewMetadataSchema里换成一个.strip()重新开放的 wire 成员 —— 与成员 3/4 已有的做法完全一致(ListViewSchema.extend(flattenedViewOverlayFields()).strip())。代价:ViewItemSchema是z.discriminatedUnion,不能直接.extend(),需要两个变体(每个 arm 一份 strict + 一份 strip),或把 base shape 抽出来复用。 - B. 保持宽松,在台账/JSDoc/测试三处把它记成
wire(批 18 当前做法),授权门的严格性靠config内层(ListViewSchema/FormViewSchema都已 strict)与defineViewItem的类型层兜。
两轴分析
长期健全性:A 更对。这个文件里已经有一条被验证过的处理这种双身份的模式 —— 成员 3/4 就是「授权 schema .extend(wire 字段).strip()」。B 让同一个 schema 名同时承担两种契约,而这正是本战役反复付代价的形状(一份散文、两种姿态)。A 也让 isPinned/sortOrder 这些辅助键有个明确的声明位置,而不是靠「没人关这个成员」隐式活着。
让 AI 写的元数据难写错:强烈偏 A。B 的失败模式很具体:defineViewItem({ name, object, viewKind, confg: {…} }) —— config 打错一个字母,今天 strip 掉,得到一个没有任何视图配置的 ViewItem,parse 成功。这就是 #4001 正文里 workflows: [...] 那条(#1535)在 view 面的重演。A 之后作者会拿到点名的拒绝;B 之后这个洞永远留着,而且台账会把它记成「已决定」,再也不会有人来看。
代价要说实话:A 是这两个选项里唯一需要真正设计的 —— discriminatedUnion 的两套变体不是一行改动,而且要确认 z.toJSONSchema() 对新结构仍能出 anyOf(/api/v1/meta/types/view 喂给 Studio 的 SchemaForm 靠它),以及 lazySchema Proxy 在新结构下不复发 ADR-0089 D3a 那次 Cannot set properties of undefined (setting 'ref')。
我推荐 A,两轴同向;但它是架构选择(注册 view 门到底严不严),不是姿态翻转,所以批 18 立案不猜。若排期上 A 进不了 v17 窗口,B 是可接受的中间态 —— 批 18 已按 B 落地并三处留痕,A 落地时把留痕换掉即可。
Activity
xuyushun441-sys commentedon Aug 4, 2026
维护者裁决(2026-08-04):A —— 拆分:授权门 strict 变体 + union 内
.strip()wire 成员(照成员 3/4 已验证模式)ViewItemSchema收紧供defineViewItem/ view 创建表单;ViewMetadataSchema成员 1 换成重新开放的 wire 变体,isPinned/sortOrder等辅助键获得明确声明位置。confg一字之差 → 空配置 ViewItem parse 成功——Object-levelworkflows: [...](and any unknown ObjectSchema key) is silently stripped at build — no error/warning (ADR-0032 'no silent failure', metadata layer) #1535/动作参数弹窗:内联 lookup 参数无法声明引用目标(配置被静默剥离 + 文案谎报「即将上线」) #3405 事故类在作者密度最高的 view 面原样重演;同文件已有双身份处理先例;三轴同向。z.toJSONSchema()对新结构必须继续出anyOf(/api/v1/meta/types/view喂 Studio SchemaForm);② lazySchema Proxy 的 ADR-0089 D3a 旧坑(Cannot set properties of undefined (setting 'ref'))。discriminatedUnion 两套变体 vs 抽 base 复用由实施 dev 按代价定。needs-user-decision摘除,入队(target:v17);排期:台账分家手术之后、批 20 之前或并行(文件面不相交);与 #5073(同文件不同站点)串行。Generated by Claude Code
fix(spec): revert ListView.sort strictness — the console stamps a UI …
xuyushun441-sys commentedon Aug 4, 2026
执行范围追加(2026-08-04,批 18 终报后的 PM 裁定,属既有裁决 A 的自然延伸)
批 18 实测确立:
.strip()与.strict()一样不递归——wire 成员顶层重开保护不了嵌套已关块,控制台盖进嵌套块的键(sort[].id、filter[].id,均crypto.randomUUID())照样 422。因此本单的拆分方案追加硬要求:stripReadDecorations的镜像),覆盖已知两处:ListView.sort[].id(批 18 已回退留开)与ViewFilterRule.id(控制台保存筛选条件会 422:ViewFilterRuleSchema拒绝 filter-builder 盖的id,而 wire 成员的.strip()救不到嵌套块 #5114,热修先行重开,本单落地后按正确结构重关)。id」解决(批 18 的 Q1 两轴否决在案:React list key 上授权面 = 教 AI 作者发 UUID)。ViewFilterRuleSchema拒绝 filter-builder 盖的id,而 wire 成员的.strip()救不到嵌套块 #5114 的三路径探针可复用),理想加 dogfood 浏览器端到端。Generated by Claude Code
feat(spec)!: view.zod.ts 子块收紧 —— 20 站点收 15 留 5(objectstack-ai#4001 批 …
fix(spec): 重开 ViewFilterRuleSchema —— 控制台盖的 UI 行 id 让保存筛选条件 422(objec…
os-zhuang commentedon Aug 4, 2026
认领:PM 循环第 3 批补位(spec 车道,batch:5;v17 链,附 #4959 前提实测 rider)
会话:
session_01ErbEDVAg1No9gdg1pgDAGB分支:
claude/issue-5074-viewitem-authoring-wire-splitWorktree:
objectstack-issue-5074域:
domain:spec文件面:
packages/spec/src/ui/view.zod.ts(ViewItemSchema 拆分 + wire 变体)、配套测试(view-metadata-schema pin、#5114 三路径探针复用)、spec 生成基线(os-regen 四步互保,注意本单落地时 #5289 链可能已进 main)、.changeset/*.md。⛔ 尊重 #5154 的三处 wire 豁免与 #5237 的 allowAddTab 收紧(同文件,先核当前 main 现状)。执行依据:裁决 A(2026-08-04 03:02Z)+ 执行范围追加(05:24Z,递归开放硬要求);两个具名地雷(
z.toJSONSchema出anyOf、lazySchema Proxy ADR-0089 D3a)写进派发词。rider:#4959 前提实测(只验证不修 —— objectui 写侧产 ViewItem、根 schemastack.zod.ts:217收z.array(ViewSchema)的分歧是否成立,以本仓端到端实测为准,结果写进报告供 PM 决定 #4959 走向)。Generated by Claude Code
os-zhuang commentedon Aug 4, 2026
验收:ACCEPT(PM 复核毕,PR #5319)
复核依据(GitHub 实据):
ViewItemWireSchemawire 变体(isPinned/sortOrder显式声明);抽 base(viewItemArmShape())按代价选定,两姿态差异只剩两处且在调用点可见;递归开放走裁定的第二路线(VIEW_CONSOLE_ROW_DECORATIONS词表 + 校验前剥离,stripReadDecorations写路径镜像),两个被挡站点(控制台保存筛选条件会 422:ViewFilterRuleSchema拒绝 filter-builder 盖的id,而 wire 成员的.strip()救不到嵌套块 #5114 临时重开的ViewFilterRuleSchema、批 18 回退的sort[])按正确结构重关,id未被声明(Q1 否决遵守);z.toJSONSchema四成员anyOf双 io 方向、lazySchema Proxy 逐 schema 直接转换 + 重入用例);view—— 被 walker 自己的覆盖断言抓住,按 活性账本覆盖 worklist:9 个已注册 metadata type 仍未治理(#4487 建立闸门后的剩余债务) #4488 写法修复;第三处(build-schemas.ts)因移动生成基线刻意不动,立build-schemas.ts的zodShapeOf对z.preprocess走错管道方向(#4488 已在 check-liveness 修过的同一个盲点) #5317;#4959 rider 处置(另评论于该单):前提端到端证伪,关闭;两条残余观察归档新单。#5316(objectui 客户端校验用错门)已入队候派。
Generated by Claude Code
feat(spec)!: ViewItemSchema 拆成授权门 + wire 变体,并让 wire 的开放递归生效 (objectst…
spec: declaresCollection reads a pipe's authorable side, so a preproc…
chore(objectui): bump the console pin to 2e818d0 (carries objectui#11…
test(spec): the third ui/ file group's test titles state each cited d…
test(spec): the last ui/ file group's test titles state each cited de…