docs(spec): ui/component.zod.ts 的 29 个站点判定为 no gate 而非收紧(#4001 批 17) - #5071
Merged
Conversation
SDUI 组件 props 是 ui/ 目录最大的待收紧面,批 17 的测量结果是它根本没有 parse:.strict() 在这里不会强制任何东西,只会花掉一次 v17 破坏性变更换来 #4583 所说的「一个被精确校验的死槽位」。 三条独立测量,控制组同轮为真: - 承载键 PageComponentSchema.properties 是 z.record(z.string(), z.unknown()); PageComponentSchema 自 ADR-0089 D3a 起是 .strict(),但严格性不递归。 - 从 24 个 metadata-type root + ObjectStackSchema 做 BFS(复用 build-schemas.ts 的 #4650 闭包,6899 节点),52 个目标全部 UNREACHABLE;六个正控制组全部 root-graph;批 13 的 no-door 形状保持 unreachable。 - 三个仓库里对本文件的 parse/safeParse 全部落在本文件自己的单测内。 经验证据(definePage() 即 PageSchema.parse()):example 语料 10/10 页面上, 写进 components[].properties 的未声明键原样通过并被保留;同一个键放到 properties 的兄弟位上 10/10 被拒(负控制组)。 判定是 no gate 而非 no door —— 词汇是活的(objectui SchemaRenderer 把 properties 里每个键 spread 成 React prop),因此不能按 ADR-0049 退役。修法是 把 parse 接到承载键的闸门上,已立为 #5068。 判定写在三处:文件头、component.test.ts 的钉子(含一条 properties 一旦获得 类型化分派就变红的断言)、账本 ui/ 两张表。账本 authorable strip 76 → 47, no gate 2 → 31,均从存活行重算。 Part of #4001 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9
build-docs.ts 会把文件第一个 JSDoc 块当作模块描述,原样发布到 content/docs/references/ui/component.mdx 和 skills/objectstack-ui/references/ _index.md。内部战役判定不该出现在协议参考文档里(而且生成器会把段落打散, 渲染也是坏的)。 改为 imports 之后的 // 注释块,与 批 13 在 touch.zod.ts 的做法一致 —— touch.mdx 至今仍显示 "Touch Target Configuration Schema",证明这个位置对 生成器不可见。check:generated 8/8 恢复全绿。 WIP checkpoint:实现 + 测试钉子 + 账本 + changeset 已完成并已验证; 待办 = 全量 typecheck/test、三个 example app 的 validate、os-regen 四步、 开 draft PR。 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
|
批 16 发现 derived-clone 桥(共享 .describe() 克隆)会把死形状误报为 REACHABLE。其误差方向与本判定相反——只可能掩盖 no-gate 发现,不可能制造 一个。且本判定不依赖该桥:六个正控制组全部 root-graph(自身实例在闭包内), 52 个目标 root-graph 与 derived-clone 双双未命中。两条非 BFS 测量(开放 承载键 + 三仓零 parse 站点)独立成立。 Part of #4001 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9
冲突两处,都在账本的 ui/ 节,与 #5042(批 14)预期内的重叠: 1. 分类定义行 —— 保留 main 对 `no door` 的措辞改进(补上「除自身单测外 无人 .parse()」),并把 批 17 的 `no gate` 补充语句嫁接到 main 的行上。 两边内容都保留,不偏袒任一侧。 2. authorable 小计段 —— 采用 main 的叙述段(它记录了第五/六/七次 「两边都算错」),然后从存活行重算。 重算(批 14 关掉 dataset/dashboard/report/action 四行,并把 sharing/ notification 重分类为 no door,总数 100 → 91): authorable = view 20 + widget 9 + i18n 6 + app 1 = 36 no door = touch 7 + animation 4 + dnd 4 + keyboard 4 + offline 3 + sharing 1 + notification 1 = 24 no gate = component 29 + chart 2 = 31 合计 = 91 ✓ 批 17 自己就是第八次实例:它在自己分支上算的 47 of 100 对本分支正确、对 合并结果错误(正确值 36 of 91)。表格干净合并、散文两边都错——正是本节 反复记录的那种失败,已按此如实写下。 os-regen 四步已执行:merge(未 rebase)→ 从 origin/main 取回生成物 → 重装 + 重建 spec + 整体重生成 → check:generated 8/8 全绿,并断言兄弟批次 条目存活(批 13 responsive 8 / 批 14 sharing 13 + notification 3 / 批 15 theme 12 + chart 44 键均在)。 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:
|
合并 #5042(批 14)后总数 100 → 91,authorable 65 → 36,两个 no-parse 类 合计 55 of 91。changeset 正文会作为 CHANGELOG 发给使用者,不能停留在 合并前的分支局部数值。 Part of #4001 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9
冲突一处:账本 ui/ 小计段(批 16 与 批 17 都重写了它)。保留 main 的
批 16 叙述与其发现段(AriaProps 收紧 + widget/i18n 重分类),把 批 17 作为
第九次实例追加,再合成单一小计并从存活行重算。
批 16 关掉 AriaProps(i18n 6 → 5 strip)并把 widget(9)与 i18n 其余 5 项
移出 authorable,总数 91 → 90。批 17 再把 component 的 29 从 authorable
移入 no gate:
存活行 = 29+20+9+2+7+5+4+4+4+3+1+1+1 = 90
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 20 + app 1 = 21
21 + 38 + 2 + 29 = 90 ✓
由此:ui/ 剩余 90 个 strip 站点里有 69 个(超过四分之三)不是本 ratchet
的工作。批 17 自身在同一分支上把这行算错了两次(47 of 100 → 36 of 91 →
21 of 90),三次对各自的树都正确、对合并都错误,已如实写下——九次全中。
os-regen 四步:merge(未 rebase)→ 从 origin/main 取回生成物 →
install --frozen-lockfile + 重建 spec + 整体重生成 → check:generated 8/8
全绿,并断言九组兄弟条目共存(批 13 responsive 8 / 批 14 sharing 13 +
notification 3 / 批 15 theme 12 + chart 44 / 批 16 aria 3 + widget 40 +
i18n 25 / 批 17 component 26 键)。
Part of #4001
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9
#5069(批 16)关掉 AriaProps 并把 widget/i18n 移出 authorable,总数 91 → 90。批 17 的 changeset 正文会作为 CHANGELOG 发给使用者,不能停留在 上一次合并时的数值:authorable 50 → 21 of 90,两个 no-parse 类合计 69 of 90(38 no door + 31 no gate)。 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:40
github-merge-queue
Bot
removed this pull request from the merge queue due to a conflict with the base branch
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
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
批 17 的任务是收紧
ui/目录最大的一块——packages/spec/src/ui/component.zod.ts的 29 个 strip 站点。测量结果是不该收紧:这些 schema 根本没有 parse,.strict()在这里不会强制任何东西。component.zod.ts的 diff 是纯注释(+81 行注释,0 行删除,0 行代码改动)。交付物是一次判定 + 把判定钉住,并把真正该做的事(接上闸门)立成 #5068。为什么不收紧
.strict()是 parse 的属性。三条独立测量,控制组同轮为真:1. 承载键活着,但它是个开放口袋。
packages/spec/src/ui/page.zod.tsPageComponentSchema自 ADR-0089 D3a 起确实是.strict(),但严格性不递归——它守住 component 节点自己的键,properties里面完全不校验。没有任何地方按type分派ComponentPropsMap。这与 批 13 修 responsive 的嵌套洞是同一片地形,只是规模大得多。2. BFS 不可达(合并 批 16 后重跑,闭包 6910 节点):
52 个目标 = 21 个导出 schema +
ComponentPropsMap全部 31 个条目。BFS 正好停在properties。遍历直接复用build-schemas.ts的zodChildSchemas/zodShapeOf(#4650 闭包),身份与删除检查一致。3. 三个仓库无生产 parse 点。
objectstack/objectui/cloud中对本文件任何 schema 的.parse()/.safeParse()全部落在本文件自己的单测内。objectui 只引用推断出的类型、手写平行的 React interface;cloud 引用为 0;react-blocks.ts只用Object.keys(ComponentPropsMap)取类型名,其REACT_BLOCKS[].schema全部指向 view/chart schema。ADR-0087 直解探针(批 14 方法,负对照证红)
validate从不解析页面(#5000),所以对本批而言它是空证据——因此走真正的门:definePage()就是PageSchema.parse()。对 example 语料 21 个真实页面,按承载键槽位路径page.regions[].components[].properties追踪:未声明的键写进
properties原样通过并被保留;写到外面一层立刻被拒。负对照才让前一个数字有意义。强制前置步骤:objectui 的 React-prop 开放槽验证结论
任务要求先验证 objectui 的 React-prop 开放槽位,预期得到「部分真门 →
strictObject,部分活开放槽 →.passthrough()」的分裂。实际答案是这个问题在上一层就失效了,所以没有分裂可报——29 个站点是齐一的no gate。槽位确实是开的:
objectui/packages/react/src/SchemaRenderer.tsx先把properties整个 hoist 到节点上,再把不在固定 deny-list 上的每一个作者键 spread 成 React prop(约 457 行)。这就是 #4909 的形状。但在一个没人 parse 的 schema 上,
.passthrough()和.strict()一样空洞,所以没有改任何 posture——记录事实,不做无效动作。check:react-declaration-parity在这里也帮不上忙,原因正是它文档里写的:它比较的是两个声明,证明不了渲染器读什么。为什么是
no gate而不是no door(不要退役)词汇是活的:作者在真实页面上写这些键,渲染器真的读(
PageHeader.tsx读title/subtitle/icon…)。拼错的键既不被拒也不被丢,而是流到渲染器再被忽略——正是 ADR-0078 要消灭的形状,只是比本 ratchet 能触及的层低一层。按no door处理去退役会删掉在用的功能。contract-first 的修法是把 parse 接到承载键自己的闸门上,属于
packages/lint/ 载体那一侧,已立为 #5068,并在那里记录了两条使它不能顺手做的约束:type是开放 union(z.union([PageComponentType, z.string()])),record:line_items这类未注册类型在现实中被使用;record:details的sections[].fields[]/hideFields[]、record picker 的labelField)——packages/lint/src/validate-page-field-bindings.ts的文件头一直写着 "they pass only becausepropertiesis unvalidated"。改了什么
判定按 批 12 的三处标准写下(改要一起改):
packages/spec/src/ui/component.zod.ts—— imports 之后的注释块承载完整判定。//而不是 JSDoc,与 批 13 在touch.zod.ts的做法一致:build-docs.ts会把文件第一个 JSDoc 当模块描述发布到content/docs/references/,内部战役判定不该进协议参考文档(第一版就是这么写的,check:docs/check:skill-refs立刻变红,已修正)。packages/spec/src/ui/component.test.ts—— 3 条钉子,都按「世界变了就变红」设计:properties一旦获得类型化分派就红 → 该重分类回authorable);ComponentPropsMap全部 31 项仍非严格(有人不接 SDUI 组件 props 没有解析闸门:PageComponent.properties是开放 record,ComponentPropsMap的 29 个站点从不被 parse(#4001 批 17 的 no gate 判定) #5068 就来 sweep.strict()会在这里红)。ui/两张表 ——authorable (p)→no gate,含完整测量。仪器先证红(每类断言各一次,已回滚)
properties值类型改成z.string()AssertionError: expected 'string' to be 'unknown'PageComponentSchema去掉.strict()AssertionError: expected true to be falsePageHeaderProps改z.strictObjectAssertionError: expected 30 to be 31账本算术(合并 #5042 批 14 + #5069 批 16 后,从存活行重算)
47 of 100,合并 批 14 后是36 of 91,合并 批 16(关掉 AriaProps、widget/i18n 移出 authorable)后是21 of 90。三次对各自的树都正确、对合并都错误。两次都是表格干净合并、散文冲突——批 17 只改了自己那行的 Class 列。由此得到这个目录现在最大的一个事实:
ui/剩余 90 个 strip 站点里有 69 个(超过四分之三)根本不是本 ratchet 的工作(38no door+ 31no gate)。排后续ui/收紧批次前请先读这个数。验证(合并 批 16 后完整重跑)
check:*逐条strictness-ledger/liveness/empty-state/variant-docs/react-declaration-parity/skill-examples/exported-any/dual-source-exports/docs/api-surface—— 10/10 PASScheck:generatedpnpm --filter @objectstack/spec testpnpm typecheck(全仓)objectstack validateinstall --frozen-lockfile+ 重建 spec + 整体重生成 → 断言九组兄弟条目共存(批 13 responsive 8 / 批 14 sharing 13 + notification 3 / 批 15 theme 12 + chart 44 / 批 16 aria 3 + widget 40 + i18n 25 / 批 17 component 26 键)