Uh oh!
There was an error while loading. Please reload this page.
fix(app-shell): expand the nested config.columns union into per-column diagnostics - #3677
Merged
Merged
Conversation
…umn diagnostics `config.columns` is `string[] | ColumnDef[]` with no discriminant, so Zod collapsed every rejection into one issue on the field itself — `config.columns` / `Invalid input` — on both the create and the edit gate. #3606 expanded the ROOT union only and recorded this as a known residual. Select the member the value's own first element elects (field names vs column objects) and report that member's issues at their draft-absolute path, composing the union node's path as the prefix. Narrowed to unions that really are "array of A or array of B": a member that rejected the node's TYPE outright never looked at the contents, so it is not a candidate — which keeps `config.sort` (`string | ColumnSort[]`) and the other neighbours untouched. Presentation only; the verdict is still the one gate's. Fixes#3626 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt
The latest updates on your projects. Learn more about Vercel for GitHub. |
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
…-union comment The comment named `ColumnsSchema` as the thing a spec reorder would touch; no such export exists. Refer to the union's members instead of inventing a name a future reader would grep for and not find. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
yinlianghui
marked this pull request as ready for review
August 7, 2026 20:21
Uh oh!
There was an error while loading. Please reload this page.
This was referenced Aug 7, 2026
akarma-synetal pushed a commit
to akarma-synetal/objectui
that referenced
this pull request
Aug 10, 2026
… the value (objectstack-ai#3693) PR objectstack-ai#3677 narrowed its content rule so it could not speak for `config.sort`, which left every union where some member rejected the value's type outright collapsed to `Invalid input`. When exactly one member accepted the type, naming it is a fact rather than a preference: it is the only member that read the value, so it is the only one whose complaint can be about what the author wrote. `sort: [{field: 'n', order: 'bogus'}]` now reports `config.sort.0.order` with the spec's own `expected one of "asc"|"desc"` instead of `config.sort` / `Invalid input`. The same holds for a `columns[].summary` written as a bad enum string, a form `sections[].fields[]` entry missing its `field`, and an array `filter[].value` whose offending element is now addressed directly. The two nested rules are disjoint rather than layered: censusing the members by objectstack-ai#3677's categorical test, k=1 is this rule, k=every-member is the content rule, and everything else is nobody's. Order is unobservable while both guards hold, and is written content-first so objectstack-ai#3677's narrowing line stays the thing that fails when removed. Verdicts are untouched — this runs inside the issue mapping, downstream of `ok`. Two of objectstack-ai#3677's narrowing pins are superseded and re-pinned: they were asserting that no rule spoke there, which is exactly what this changes. Fixesobjectstack-ai#3678 Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt 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 freeto 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.
Fixes#3626
问题
config.columns是string[] | ColumnDef[]—— 一个没有判别式的 union。Zod 对 union 失败只产出一条 issue(Invalid input),成员的真实诊断埋在issue.errors里,于是任何 columns 错误都塌成config.columns/Invalid input,创建路和编辑路都是。PR #3624 修的是根级 union,并把这条嵌套残留写进了「已知残留」。它比 #3606 轻:path 是真的,
SchemaForm的内联高亮和 Monaco 定位都还能工作 —— 用户被带到出错的字段,但不知道哪一列、哪个键、期望什么。测量(先测量,后设计)
把整个
view家族的 Zod 树走了一遍(@objectstack/spec17.0.0-rc.5):16 个无判别式的嵌套 union,其中columns是唯一一个两个成员都是数组的。这决定了规则能安全地写成什么样。判别式取值自身的首元素:一份
columns要么是字段名列表、要么是列对象列表,首元素说了算。这与根级的viewKind是同一类规则(关于作者写了什么的事实),不是被 #3606 实测否决的「按 issue 数 / 深度」排序启发式 —— 后者是把各成员的错误组互相比较。边界,全部实测而非假设
columns: []columns: [42]/[null]columns: 'nope'['name', {field: 1}]报config.columns.1—— 作者正在写的那张列表里真正坏掉的那个元素收窄:必须不能替
config.sort说话config.sort是string | ColumnSort[],同样两个成员、同样可以是数组值。对sort: ['name'],只看首元素会选中纯string成员并报「expected string, received array」—— 字面为真,却是对一个正确写了数组的人说的最没用的话。所以加了一条逐组独立的范畴判定:在 union 节点上直接拒绝了值的类型(自身相对根 path 为空的
invalid_type)的成员,压根没看过内容,内容就不能作为它的证据,因此不是候选。这条把sort、filter[].value、gantt.tooltipFields[]等全部挡在外面。它是筛选适用范围,不是挑选赢家 —— 赢家只由内容决定。修前 / 修后(两条路逐一实测)
columns: [{ field: 123 }]config.columns/Invalid inputconfig.columns.0.field/expected string, received numbercolumns: [{}]config.columns/Invalid inputconfig.columns.0.field/expected string, received undefinedcolumns: ['a','b',42]config.columns/Invalid inputconfig.columns.2/expected string, received numbercolumns: [{field:'a'},'b']config.columns/Invalid inputconfig.columns.1/expected object, received stringcolumns: [{field:'a',summary:{type:'bogus'}}]config.columns/Invalid inputconfig.columns.0.summary/Invalid inputlist.columns: [{field:123}]list.columns/Invalid inputlist.columns.0.field/expected string, received numbercolumns: [42]config.columns/Invalid inputsort: ['name']config.sort/Invalid input创建路 / 编辑路收敛一致:修前两路都塌在
config.columns,修后两路逐字节相同 —— 这是测试里的bothGates断言(expect(edited.issues).toEqual(created.issues)),不是论证。两路走的是不同路线:创建路上这个 union 是顶层 issue,编辑路上它在根成员选中之后再下一层;path 前缀拼接正是被测的东西。倒数第二行值得单说:
columns[0].summary是enum | {type, field},值是对象,数组规则对它无话可说,于是保留 Zod 自己的消息 —— 但已经被正确定位到config.columns.0.summary,而不再是config.columns。这也是下降的真实边界:不是深度计数器,而是「规则在这里没有话说」。判定奇偶:只有呈现变了
展开逻辑仍在 issue →
SchemaFormIssue的映射内部,位置在ok判定之后、所有既有过滤器之后 —— 驱动ok的那组 raw issues 一个都没动。判定不是「测出来没变」,而是结构上不可能变。仍按 #3624 的手法钉成 pin(8 个 columns 形状 ×ok),它修前修后都绿,这正是它的意义。逆向验证(先预测,后运行)
预测写在跑之前,两个方向都命中:
扰动 A —— 还原实现到
origin/main:预测 7 红(5 CANARY + 1 噪音控制 + 1summary深度锚点),其余绿。实际:恰好这 7 条红,一条不多一条不少。扰动 B —— 只删掉收窄条款那一行(
if (groups.some(memberRejectedNodeType)) return null;):config.sort那条钉子在扰动 A 下两个方向都是绿的(它钉的是「不变」),所以 A 证明不了它有效 —— 需要专门的扰动。预测:只有它一条红。实际:即被预测的那句误选原样出现。如实注明:边界类与收窄类钉子(
[42]、'nope'、空数组、filter value、判定奇偶)在扰动 A 下修前修后都绿 —— 它们是防回归的那一半,不是「修前红修后绿」的那一半。测试
clientValidation.viewDiagnostics.test.ts只加不改(#3624 的 18 条断言一行未动),新增 20 条:5 CANARY + 1 噪音控制 + 2 收窄 + 4 边界 + 8 判定奇偶。clientValidation.viewShapes.test.ts与clientValidation.viewDiagnostics.test.ts的现有断言原样绿。已知残留(未修,不在本单范围)
sort: [{field:'n', order:'bogus'}]这类:只剩一个成员接受了值的类型,理论上可以无歧义地选中它,但那是「唯一候选」规则,不是本单授权的内容判别。仍保留config.sort/Invalid input,已钉住。Generated by Claude Code