fix(layout): navigation-renderer 的 items 声明为 required —— 校验器不再放过必崩的节点 (#3987) - #3995
Merged
Merged
Conversation
`NavigationRendererProps.items` 是非可选的 `NavigationItem[]`,渲染器不给默认值,
而注册声明一直没写 `required`。`sdui-parser` 只在 `input.required` 为真时报
`missing-required-prop`(validate.ts:55-64),于是 `{ "type": "navigation-renderer" }`
校验零诊断、渲染直接抛。
运行期实测(非静态读出):第一处无守卫的读点是 `pinnedItems` memo 里的
`collectPinnedItems(filteredItems)`(NavigationRenderer.tsx:1242 → :1410 的
`for (const item of items)`),抛 `TypeError: items is not iterable`。
`resolveActiveNavItem` memo 挡得住(`visit` 首行 `if (!nodes) return`),
:1247 的 `filteredItems.slice()` 同样会抛但走不到。
这是 #3972(键的存在 × 类型)的第四面:可选性。与 #3900/#3972 相反,它是收紧 ——
新增一条 error 级诊断,而被它拦下的形状恰好等于渲染必然崩溃的形状。
钉子两方向 + 对照:
- 注册级(packages/layout):`items.required === true`(两个 namespace);
`basePath.required` 保持 falsy(渲染器真给了 `basePath = ''` 默认值);
编译期镜像 `undefined extends items` 为假;运行期崩溃见证。
- 端到端(examples/schema-catalog,与 #3972 的 manifest 门钉同处):省略 items 报
error 级 `missing-required-prop`;提供数组零诊断;可选属性省略不报。
- #3900 的 `not-a-container` 对照节点补 `items: []`,恢复单一事实(注释同步)。
Fixes #3987
Co-authored-by: Claude <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
|
上一版写成"两个节点都省略了 basePath",而上一条测试的第二个节点其实供了它 (两者都零诊断)。注释真实性是这条改动本身在管的事,顺手改准。 Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
Collaborator
Author
|
PM 验收(session_01GTRjn8xBqp75dk7kFupVRt):通过,转 ready 并挂 auto-merge。#3987 落地 —— navigation-renderer 声明面四连修(#3900 isContainer / #3972 icon+actions+items 类型 / 本单 required)收口。 核验记录(head
out-of-scope #3999(layout README 的 SidebarNav 示例三键全错 + side-effects 注释旁证)由 PM 分诊(另评)。 Generated by Claude Code |
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.
Fixes #3987
这一条改的是什么
navigation-renderer的itemsinput 补required: true(packages/layout/src/index.ts,一行)。NavigationRendererProps.items是非可选的NavigationItem[],渲染器也不给默认值;而sdui-parser只在input.required为真时报missing-required-prop(
packages/sdui-parser/src/validate.ts:55-64)。所以在这一改动之前,{ "type": "navigation-renderer" }这个节点是校验零诊断 + 渲染必崩:唯一保证崩溃的形状,恰好也是校验器唯一无话可说的形状。
这是 #3972(键的存在 × 类型 ×
ManifestInputType表达力)的第四面:可选性。方向上与 #3900 / #3972 相反 —— 那两条是删假诊断,这条是收紧(新增一条 error 级诊断),
按 issue 里的 PM 分诊裁定实施,未重开讨论。
运行期复现(issue 正文是静态读出,这里是实测)
issue 按 file:line 推断"两处都吃 undefined"。实测一次(省略
items渲染真实组件,MemoryRouter+SidebarProvider,react 19.2.8 / happy-dom),结果确认崩溃,并修正了读点顺序:
pinnedItemsmemo(:1242)调用的collectPinnedItems(
:1410的for (const item of items))—— 抛TypeError: items is not iterable,白屏而非降级。
activeNavIdmemo(resolveActiveNavItem,:1229)挡得住:内部visit首行就是
if (!nodes) return。filteredItems.slice()(:1247)静态上同样会抛,但永远走不到 ——:1242先崩。这次复现已经作为常驻见证进了 PR(见下),不是一次性探针。
钉子(两方向 + 对照)
注册级 ——
packages/layout/src/__tests__/navigation-renderer-items-declaration.test.tsx(与 #3972 同一把钉子的同一个文件,按键归拢):
items.required === true,两个注册键(bare 与layoutnamespace)都钉;basePath.required必须保持 falsy —— 渲染器真的给了它默认值(basePath = '')。required是逐个属性从组件读出的事实,不是一刀切;这条对照一旦翻红,就说明收紧从"缺了会崩的那个属性"扩散成了"所有声明的属性";
undefined extends NavigationRendererProps['items']为假。items哪天变成items?:,这个文件当场 type-check 失败,声明不会悄悄漂回去;items渲染toThrow(TypeError)+toThrow(/items is not iterable/);配对的另一半是"给了
items、省略可选的basePath,渲染正常"。端到端 ——
examples/schema-catalog/test/pageheader-with-actions.test.tsx(#3972 的 manifest 门钉就在这里,紧邻按活注册表建 manifest 的
diagnose()助手):items的节点报missing-required-prop,并断言severity === 'error'与 message 里的"items"(先断unknown-component为空 —— 不然 tag 掉出 manifest 时这个断言会因为根本没跑到required 循环而假绿);
basePath)零诊断;missing-required-prop(navigation-renderer的basePath,以及同文件注册的
page-header只写title)。同批对照测试的同步(#3900 的
not-a-container对照)examples/schema-catalog里 #3900 的对照节点原本故意省略items,注释写着"它不是 required,所以省略不画任何东西" —— 这句被本改动作废。按 fixture triage 的
"补声明"处置:节点补上
items: [],让它只剩那一处故意植入的缺陷(childless 组件带children),对照恢复单一事实;注释同步重写,并记下它两次是从相反方向得到同一个结论
(先前省略是因为写
items: []会撞 #3972 的type-mismatch)。断言toContain(CONTAINMENT)本来按 code 过滤、两种写法都绿 —— 所以这里改的是注释的真实性与 fixture 的单一事实性,
不是为了让它变绿。文件头的"Four things are pinned here"同步为五条。
反向验证(先预判后跑,变异未提交)
预判:撤掉
required: true→ 注册级 2 条 + 端到端 1 条翻红;三组对照(
basePath可选、给了items零诊断、page-header只写 title)保持绿;运行期崩溃见证也保持绿 —— 崩溃与声明无关,它是这条收紧的理由,不是它的后果,
所以它在变异下不该动。实测与预判逐条一致:
blast radius(这是收紧,必须写清)
今天省略
items写navigation-renderer的 schema,会新增一条 error 级missing-required-prop(message 形如< navigation-renderer > is missing required prop "items")。navigation-renderer当 schema 节点写(全仓 grep 只命中注册本身、packages/layout:
page-header注册缺isContainer: true,而PageHeader有意渲染schema.children—— sdui-parser 对文档认可的写法报not-a-container#3900 的对照测试与这批钉子);React 调用侧的必填由 TS 兜住。validateTree的仓内消费者只有上面那个测试文件。inputs/packages/layout/README.md做 schema 驱动的消费者会收到这条新诊断—— 与 packages/layout: registerLayout 的 inputs 声明面与组件实现不符 —— page-header 漏 icon(文档 demo 今天就吃假 unknown-prop),navigation-renderer 的 items 声明成 object 而 prop 是数组 #3972 现象二同级、同一句可达性标注。而它新拦下的形状恰好等于渲染必然崩溃的形状,
这正是
missing-required-prop存在的理由:让作者(尤其 AI 作者)在发布期就听到运行期注定发生的失败。
items一个= []默认值,缺 items 就渲染空导航)与本改动不互斥、但未采用:静默空导航对"作者忘了写 items"这件事比 error 级诊断更没用。若将来采用,
测试文件头已写明处置方式 —— 替换运行期见证为"渲染空导航"的断言(而不是删掉它),并
保留
required: true。验证
pnpm --workspace-concurrency=2 --filter '@object-ui/layout^...' build(新树先建依赖)pnpm exec vitest run packages/layout packages/sdui-parser examples/schema-catalog --maxWorkers=2→ 16 files / 1226 tests passed
pnpm exec turbo run type-check --concurrency=2→ 78 successful, 78 totalnode scripts/check-control-bytes.mjs→ OK(3903 tracked text files);改动文件另做了一次
grep -naP控制字节自扫,干净pnpm exec eslint(改动的三个文件)→ 0 errors(index.ts 上两条 warning 是既有的,与本改动无关)
第二个 commit 只改注释:上一版把对照写成"两个节点都省略了
basePath",而上一条测试的第二个节点其实供了它(两者都零诊断)—— 注释真实性正是这条改动在管的事,顺手改准;
examples/schema-catalog重跑 4 files / 1093 tests passed。边界
只动
packages/layout/src/index.ts(一行 + 注释)、两个测试文件、一个 changeset(
@object-ui/layoutpatch,正文写明 blast radius)。未动NavigationRenderer.tsx本体,未碰
content/docs/releases/,未碰同批在飞的 schemas/scripts/fields/app-shell 面。