feat(spec)!: view.zod.ts 子块收紧 —— 20 站点收 15 留 5(#4001 批 18) - #5070
Merged
Conversation
…批 18) ui/ wave batch 18 — the long tail of `ui/view.zod.ts`. The top level and the form/page shapes closed in earlier waves; these are the sub-blocks that kept the posture those were rescued from. Closed (16 sites): ViewDataSchema's four provider arms, UserFilterField.options, GanttQuickFilter.options, GanttConfig.tooltipFields, ListView.sort / .conditionalFormatting / .emptyState, FormFieldBase.keyField, FormView.subforms, and submitBehavior's four arms. Reachability measured, not assumed: a BFS from all 24 metadata-type roots plus ObjectStackSchema resolves every one `root-graph`, with ViewSchema/FormViewSchema/ ViewItemSchema/PageSchema positive and batch 13's no-door shapes UNREACHABLE in the same run. The instrument needed fixing first — lazySchema returns a Proxy but a carrier writes `X.optional()`, which resolves it, so comparing the Proxy alone false-negatived ViewDataSchema. submitBehavior became a discriminatedUnion on the `kind` literal it already required: as a plain union of strict members the rejection is an invalid_union whose prescription #5014 measured the renderers flattening away. Left open, each measured and recorded in three places: - UserFiltersSchema — closing would 422 `allowAddTab`, live in objectui's renderer and never declared in spec; saveMetaItem persists the ORIGINAL body, so the stripped key reaches the renderer and the capability works today. - ViewItemSchema (x2) — wire: objectui's pin control PUTs {...storedItem, isPinned} onto this union member; closing would 422 pinning a saved view. - FormFieldBaseSchema — a private base whose sole consumer already .strict()s it and carries the ADR-0089 strictVisibilityError map. Ledger: ui/ 100 -> 84 strip, authorable 76 -> 60, both recomputed from surviving rows. Also corrects a stale block comment claiming all four members of ViewMetadataSchema strip-parse — member 2 has been strict since ViewSchema closed. Part of #4001 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
…dicts Batch 16 found that the door-reachability BFS's `derived-clone` bridge can mark a dead shape reachable off a few shared leaf properties, and that the error runs one way — toward closing dead shapes. Re-checked batch 18's verdicts against it: all sixteen closed shapes resolve `root-graph` by IDENTITY (the schema's own instance, or the instance its lazySchema Proxy resolves to, is literally in the closure), so none rests on bridge evidence. The one `derived-clone` verdict in the run is `ListViewSchema`, a positive control rather than a target, independently identity-reachable via `ObjectListViewSchema`. Part of #4001 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9
Ledger `ui/` section conflicted as expected — batch 16 (#5069) landed while batch 18 was in flight and both edit it. Resolved by taking main's section wholesale (so batch 16's `widget` no-door reclass, the `i18n` split row and the AriaProps closure survive verbatim) and re-applying batch 18's three edits on top. Header and subtotal recomputed FROM THE SURVIVING ROWS, not carried from either side: 29+4+9+2+7+5+4+4+4+3+1+1+1 = 74 strip, authorable 34 of 74. Batch 18 is the ninth wrong-on-both-sides instance — it computed 84 against a tree where batch 16's rows still existed, batch 16 computed 90 against one where view was still 20, and the merge is neither. Recorded in the section's own prose, since the two batches moved the same numbers for OPPOSITE reasons (16 reclassified 14 sites it never touched; 18 closed 16 it did). check:strictness-ledger green on the merged tree: 36 open file(s) / 233 strip site(s). Part of #4001 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9
…row id
The full suite caught this: `view-metadata-schema.test.ts` pins
`sort: [{ id, field, order }]` as "the exact shape normalizeViewMetadata
persists on a console column-sort PUT", and objectui stamps that `id` per row
(components/src/custom/sort-builder.tsx:68/:94, crypto.randomUUID()). Closing
the sort entry made a console column-sort a 422.
The mechanism is the finding, and it governs every nested block in this file:
`.strip()` does NOT recurse, any more than `.strict()` does. ViewMetadataSchema
rescues Studio's round-trip keys by making its flattened members `.strip()`, but
that re-opens the TOP level only — a nested block closed inside ListViewSchema is
still reached through that member, so a console-stamped key inside it 422s
regardless of the member's own posture. The union's block comment read as though
the rescue reached all the way down; it does not, and it now says so.
`id` was deliberately NOT declared to silence the rejection. It is a React list
key, not protocol: declaring it would put a UI artifact on the authorable surface
and teach an AI author to emit a UUID. The end state is #5074's authoring/wire
split applied one level down.
Also adds the variant-docs entry my discriminatedUnion conversion made visible.
`FormView.submitBehavior`'s four variants were always authorable; as a plain
z.union the gate could not see them. GOVERNED (ui/forms.mdx names all four), not
exempt.
Ledger: ui/ 74 -> 75 strip, authorable 34 -> 35, recomputed from surviving rows.
Filed from this: #5114 — ViewFilterRuleSchema (closed by an EARLIER batch, live
on main) rejects the same class of console-stamped `id` on filter rows.
Part of #4001
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 107 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
`ListView.sort` was reverted (the console stamps a UI row `id` into it), so the changeset must not tell upgraders that `direction` now raises — it does not, and a migration table that over-promises is the same "declared != enforced" defect this campaign exists to remove, pointed at consumers. 15 closed, not 16. The `direction -> order` row is removed and `sort` is named among the shapes deliberately left open, with the `.strip()`-does-not-recurse reason. Part of #4001 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9
xuyushun441-sys
marked this pull request as ready for review
August 4, 2026 05:23
xuyushun441-sys
enabled auto-merge
August 4, 2026 05:24
xuyushun441-sys
pushed a commit
that referenced
this pull request
Aug 4, 2026
合并队列以 MERGE_CONFLICT 踢出后重做。冲突两处,双边全保留:
1. remaining-strip 表的相邻两行 —— 保留 批 17 的 `component.zod.ts`
no gate 改判行,同时保留 批 18 的 `view.zod.ts` 5/50 行(20 收 15 留 5)。
git 之所以冲突是两行相邻,不是同一行两改。
2. 小计段 —— 保留 批 18 的第九次实例叙述(它记录 批 16 与 批 18 为相反
原因移动同两个数),把 批 17 作为第十次实例追加,再合成单一小计并从
合并后存活行重算(不套用任何一方的 delta)。
从存活行重算(批 18 把 view 20 → 5,总数 90 → 75):
存活行 = 29+5+9+2+7+5+4+4+4+3+1+1+1 = 75
no door = touch 7 + animation 4 + dnd 4 + keyboard 4
+ offline 3 + sharing 1 + notification 1
+ widget 9 + i18n 5 = 38
no gate = component 29 + chart 2 = 31
authorable = view 5 + app 1 = 6
6 + 38 + 31 = 75 ✓
批 18 关掉 15 个真门、批 17 测出 29 个根本没有门,叠加后 ui/ 只剩 6 个
authorable strip 站点,69/75(92%)不是本 ratchet 的工作。批 17 自身在同
一分支上把这行算错了三次(47/100 → 36/91 → 21/90 → 6/75),四次对各自的
树都正确、对合并都错误,已如实写下——十次全中。
os-regen 四步:merge(未 rebase)→ 从 origin/main 取回生成物(含
批 18 的 variant-docs.json,NOT_DRIVER_MANAGED 棘轮)→ install
--frozen-lockfile + 重建 spec + 整体重生成 → check:generated 8/8 全绿,
并断言十组兄弟条目共存(批 18 view 133 键 + submitBehavior variant-docs
条目 + view 参考页均存活)。
Part of #4001
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Aug 4, 2026
…tstack-ai#5114 热修) (objectstack-ai#5154) * fix(spec): reopen ViewFilterRuleSchema — the console stamps a UI row id (objectstack-ai#5114) Saving a filter from the console 422'd on `main`. An earlier strictness wave closed `ViewFilterRuleSchema` with `strictObject`, and objectui's filter builder stamps `id: crypto.randomUUID()` on every row it writes (`components/src/custom/filter-builder.tsx:228`, re-stamped on read-back at `plugin-view/src/config/view-config-utils.ts:146`/`:160`). `saveMetaItem` validates the PUT body and then persists the AUTHORED body verbatim, so the `id` is on the wire — and the closed shape rejected it. Measured on all three paths before the change, on `origin/main`: ViewFilterRuleSchema -> unrecognized_keys @ [] `id` ListViewSchema.filter -> unrecognized_keys @ ["filter",0] `id` ViewMetadataSchema (overlay) -> invalid_union @ [] "Invalid input" The third is the body the console actually PUTs, and its message is the objectstack-ai#5014 flattening: the key that caused the rejection is not in what the author sees, which is how this sat on `main` unnoticed. The mechanism governs every nested block in this file and is the opposite of what the union's comment implies: `.strip()` does NOT recurse, any more than `.strict()` does. `ViewMetadataSchema` re-opens its flattened members so Studio's round-trip aux keys ride along — but that re-opens the TOP level only, so a nested block closed here is still reached through that member and a console-stamped key inside it 422s regardless of the member's posture. Same finding 批 18 reached one block over on `ListView.sort` (objectstack-ai#5070). `id` is deliberately NOT declared. It is a React list key, not protocol: declaring it would put a UI artifact on the authorable surface and tell an AI author to generate a UUID for a filter rule — a `??` fallback wearing a schema. Reopening drops it from `parsed.data` instead, and `saveMetaItem` stores the original body, so the renderer still reads it. The real close is objectstack-ai#5074's authoring/wire split applied to this block, whose scope addendum names this site. Verified in both directions: re-close the schema and 7 assertions in the new pin file go red, while its two mechanism CONTROLS (top-level aux key rides, nested `emptyState` still rejects) stay green either way. Ledger gate proven red on both the row (6→5) and the header (76→75). Ledger: ui/ 75 -> 76 strip, authorable 35 -> 36, recomputed from surviving rows. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9 * docs(changeset): stop claiming the console filter save is restored (objectstack-ai#5114) Browser verification against the real vendored console found a second, independent defect stacked on the same request: the list toolbar persists the filter builder's whole `FilterGroup` object into `filter`, where the spec declares `ViewFilterRule[]`. That type mismatch rejects before the `id` is ever reached, so reopening `ViewFilterRuleSchema` does not by itself make "save a filter from the console" work — it is necessary, not sufficient. Replaying the captured console body against two running servers separates them: variant pre-fix post-fix as sent (FilterGroup object) 422 422 <- producer bug unwrapped to rule[] WITH the UI ids 422 ACCEPTED <- this change unwrapped to rule[] without ids ACCEPTED ACCEPTED <- control The changeset headline said the console save no longer 422s. It does. Release notes are the one place that claim would have reached users unqualified, so it now states what actually changed and names the remaining blocker. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9 --------- Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #4001 —— ui/ 波次批 18(
packages/spec/src/ui/view.zod.ts,T1 必保)。账面 20 个 strip 站点,收紧 15,留开 5。留开的每一个都是实测判定,不是没做完 —— 三处落锚(schema JSDoc +
view-strictness-batch18.test.ts+ 台账 ui 行)。ListView.sortsort一度收紧成功,还带着本批最好的一条策展 ——direction → order(#4721 给SortNodeSchema同一个二键元组定的别名;那边实测{field, direction:'desc'}解成{field, order:'asc'},静默按相反方向排序)。全量套件把它抓了回来:
view-metadata-schema.test.ts钉着sort: [{ id, field, order }],注释写明那是「控制台按列排序 PUT 时normalizeViewMetadata落盘的确切形状」,而那个id是 objectui 逐行盖的 UI 行标识(components/src/custom/sort-builder.tsx:68/:94,crypto.randomUUID())。收紧 = 控制台点一次排序就 422。机理(比这个站点本身值钱,且与本文件块注释所暗示的相反)
.strip()不递归,和.strict()一样不递归。ViewMetadataSchema靠扁平成员上的.strip()放行 Studio 往返辅助键 —— 但那只重新打开顶层。任何在ListViewSchema内部被收紧的嵌套块仍经由该成员被解析,所以嵌套块里一个控制台盖的键无论成员姿态如何都会 422。块注释读起来像是救援能一路到底;它不能,现已就地改成实测描述,并在测试里钉住这条递归性质:为什么没有顺手声明
id它是 React 列表键,不是协议。声明它等于把 UI 造物放进可授权面,并教 AI 作者去生成一个 UUID —— 正是本战役要杀的「声明即鼓励」。这是
??兜底换了身 schema 外衣。故回退单个站点,终态是 #5074 的授权/wire 拆分下沉一层。同一机理向外追,抓到 main 上的现存 bug → #5114(已立案)
ViewFilterRuleSchema由更早的批次收紧,而filter-builder.tsx:228盖同款id。实测三条路径(含扁平 overlay)全被拒 —— 今天在控制台保存筛选条件就会 422。已如实标注「未跑真实 app 端到端复现」。门测量(批 13-15 口径)
BFS 从 24 个 metadata-type root 加
ObjectStackSchema出发,本文件每个目标 schema 解出root-graph;阳性对照ViewSchema/FormViewSchema/ViewItemSchema/PageSchema可达,批 13 的 no-door 形状(touch/dnd)同一次运行里 UNREACHABLE。lazySchema返回 Proxy,承载点写X.optional()会解掉 Proxy,闭包里存的是真实例 —— 只比 Proxy 会把ViewDataSchema误判成不可达。靠它两个字面承载键交叉核对抓到的。root-graph,无一依赖derived-clone共享属性桥。运行里唯一的derived-clone是ListViewSchema—— 阳性对照而非目标,且经ObjectListViewSchema独立 identity 可达。收紧的 15 个
ViewDataSchema四个 provider 臂、UserFilterField.options、GanttQuickFilter.options、GanttConfig.tooltipFields、ListView.conditionalFormatting/.emptyState、FormFieldBase.keyField、FormView.subforms、submitBehavior四个臂。策展锚定点名兄弟契约:option 上的
count给跨层处方(objectui 每次渲染从快照算它)指向showCount;objectdata source 上裸name刻意不设别名(它是 view ITEM 上的真键,按 finding 7 宁可不指路)。submitBehavior改成discriminatedUnion('kind')(输入形状零变化)。朴素 union 的报错是invalid_union带四份子错误,#5014 实测渲染器压平成 "Invalid input";判别后顶层直接是unrecognized_keys、路径就在submitBehavior上。副作用:check:variant-docs因此首次看见这个一直可授权的词表 —— 判 governed(ui/forms.mdx点名四个变体),非 exempt。留开的 5 个(⛔ 别顺手收掉)
UserFiltersSchemaallowAddTab(#5073)ViewItemSchema×2isPinned往返落在这个成员(#5074)ListView.sortid(见上,#5114 同族)FormFieldBaseSchema.strict()+ ADR-0089 错误映射批 6e 点名的验证做了:依赖真实存在 ——
ObjectUserFiltersSchema是UserFiltersSchema.omit(...),.omit()继承姿态,关基类会把钉从「drop」翻成「reject」。那个翻转是想要的(CLI lint 早就在报),只卡在allowAddTab:saveMetaItem校验后原样存原始 body,所以被 strip 的只是被丢弃的解析结果,渲染器仍读得到 —— 能力今天是工作的,收紧是删能力,不是把静默变响亮。仪器纪律:六类断言先证红
ListView.emptyStatedirection→order别名discriminatedUnion退回unionUserFiltersSchemaViewItemSchemaname→object第 3 条:首版断言查「消息里有没有
delayMs」,而 union 子错误里也有,破坏后依然全绿;改成断言顶层 issue 的 code 与 path 后立刻红。断言查错误的载荷而非错误的形状,看起来在测、实际什么都没锁住。台账闸门两向各证红(行内 4→5;表头 84→85),恢复绿。
台账(合并后重算)
与 #5069(批 16)冲突如期发生。取 main 整段 + 重贴批 18 三处编辑,批 16 条目逐条断言存活(
widgetno-door 行、i18n5/6 split 行、AriaProps 散文、#5055/#5056、「批 16 is the eighth instance」)。表头 75 strip of 198、可授权 35 of 75,从存活行重算:
29+5+9+2+7+5+4+4+4+3+1+1+1 = 75。批 18 是第 9 例「两边都对、合并都错」—— 我的分支算 84、main 算 90,合并是 75,两边都没写过这个数。机理已记进散文:批 16 与批 18 为相反原因动了同两个数(16 改判 14 个它没碰的站点,18 关掉 15 个它碰的),合并值不可由任一 delta 推得。验证(合并后 HEAD
4d7d39d,完整跑).changeset/pre.json仍mode=pre/tag=rc;⛔content/docs/releases/零触碰;diff 面 5 文件。🤖 Generated with Claude Code
https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9