发现于 #3900 的实现(PR 见下方引用),不在该 PR 处理 —— #3900 的完成范围被维护者明确框成「page-header 一行注册 flag + 钉子」,这里是同一个注册文件里另外两个键的声明面缺陷,属另一类动作。两条都有实测诊断输出,不是推断。
现象一:page-header 的 inputs 漏了组件真实渲染的 icon
注册(packages/layout/src/index.ts)只声明 title / subtitle。但 icon 三面齐全:
- 渲染器读它:
packages/layout/src/PageHeader.tsx:117 取参,:224-226 渲染(字符串走 LazyIcon name={icon},也接受 React node)。
- spec 声明它:
@objectstack/spec/ui 的 PageHeaderProps 有 icon。
- 文档 demo 写它:
examples/schema-catalog/src/schemas/layout-page-header/pageheader-with-actions.json 里 "icon": "users",而这是 content/docs/layout/page-header.mdx 唯一的 live demo。
于是 manifest 门今天就对这份 demo 报一条假警告。实测(把该 demo 喂给按 page.tsx 的 getJsxManifest() 同样方式构造的 manifest,再走 validateTree):
{
"severity": "warning",
"code": "unknown-prop",
"message": "page-header has no prop \"icon\"",
"tag": "page-header"
}
这条与 #3900 无关、在 #3900 修复前后都存在(两次实测输出一致),所以 #3900 的钉子刻意只按 not-a-container 这个 code 过滤,没有把它一起钉住 —— 就是为了这一条被修时那个钉子不会翻红。
注意方向,不要无脑抄全 spec 的键。 breadcrumb spec 有、这个 legacy 渲染器不读(全文件只在一句注释和一个 aria-label 里出现过字样),声明它就变成 #3829 那种「声明了却零读点」的反向缺陷。要的是一次按渲染器实际读点的审计:读了且 spec 有 → 声明;读了但 spec 没有 → 按 #3226 定下的调子处理(不要在声明面开第二套方言);没读 → 不声明。
同文件既有的 packages/layout/src/__tests__/page-header-authorable-keys.test.tsx 那条「declares nothing @objectstack/spec does not」是单向的:补 icon 不会让它翻红(spec 有),但它从来管不到「漏声明」这一侧 —— 和 #3900 是同一个不对称的另一处体现。
现象二:navigation-renderer 把 items 声明成 type: 'object',而 prop 是数组
注册里 { name: 'items', type: 'object' },而 NavigationRendererProps.items 是 NavigationItem[](packages/layout/src/NavigationRenderer.tsx:108)。packages/sdui-parser/src/validate.ts 的 checkType 对 'object' 判 typeof value === 'object' && value !== null && !Array.isArray(value),对 'array' 判 Array.isArray —— 所以正确的写法 items: [...] 会拿到 type-mismatch。实测:
{
"severity": "warning",
"code": "type-mismatch",
"message": "navigation-renderer prop \"items\" expected an object",
"tag": "navigation-renderer"
}
ManifestInputType 里有 'array',所以这不是 #3832 那个「ComponentInput.type 表达不了 spec 联合类型」的表达力问题,单纯是声明写错了一个可表达的类型。
可达性诚实标注: 本仓没有任何 JSON 元数据把 navigation-renderer 当 schema 节点写(全仓 grep 只命中注册本身,以及 #3900 加的那条阳性对照测试)。所以这条今天的实际受害者是仓外按 packages/layout/README.md 列出的键作 schema 驱动的消费者,不是仓内 demo —— 与现象一「文档 demo 今天就中」不同级,分诊时请分别判。之所以放在同一条:两者是同一文件、同一族(声明面与实现不符 → manifest 门对正确写法报假诊断)、同一次审计就能扫完,分成两个孪生单没有收益。
建议
一次改动扫完 registerLayout() 的四个 inputs 块(page-header / responsive-grid / navigation-renderer / app-schema-renderer),逐键对齐三面:渲染器读点 × spec 声明 × ManifestInputType 表达。并按 #3900 的做法补双向钉子:声明的键过 manifest 门放行,未声明/写错类型的键仍报 unknown-prop / type-mismatch —— 只钉前者的话,把诊断弄哑一样是绿的。
关联:#3900(发现于此实现,只处理 isContainer)、#3226(同文件 inputs 收窄的前情,建立了「声明面即作者面」的调子)、#3829(page:header.icon:spec 声明、渲染器零读点,同一个键的反向缺陷)、#3832(ComponentInput.type 表达力,不同根因)
发现于 #3900 的实现(PR 见下方引用),不在该 PR 处理 —— #3900 的完成范围被维护者明确框成「
page-header一行注册 flag + 钉子」,这里是同一个注册文件里另外两个键的声明面缺陷,属另一类动作。两条都有实测诊断输出,不是推断。现象一:
page-header的inputs漏了组件真实渲染的icon注册(
packages/layout/src/index.ts)只声明title/subtitle。但icon三面齐全:packages/layout/src/PageHeader.tsx:117取参,:224-226渲染(字符串走LazyIcon name={icon},也接受 React node)。@objectstack/spec/ui的PageHeaderProps有icon。examples/schema-catalog/src/schemas/layout-page-header/pageheader-with-actions.json里"icon": "users",而这是content/docs/layout/page-header.mdx唯一的 live demo。于是 manifest 门今天就对这份 demo 报一条假警告。实测(把该 demo 喂给按
page.tsx的getJsxManifest()同样方式构造的 manifest,再走validateTree):{ "severity": "warning", "code": "unknown-prop", "message": "page-header has no prop \"icon\"", "tag": "page-header" }这条与 #3900 无关、在 #3900 修复前后都存在(两次实测输出一致),所以 #3900 的钉子刻意只按
not-a-container这个 code 过滤,没有把它一起钉住 —— 就是为了这一条被修时那个钉子不会翻红。注意方向,不要无脑抄全 spec 的键。
breadcrumbspec 有、这个 legacy 渲染器不读(全文件只在一句注释和一个aria-label里出现过字样),声明它就变成 #3829 那种「声明了却零读点」的反向缺陷。要的是一次按渲染器实际读点的审计:读了且 spec 有 → 声明;读了但 spec 没有 → 按 #3226 定下的调子处理(不要在声明面开第二套方言);没读 → 不声明。同文件既有的
packages/layout/src/__tests__/page-header-authorable-keys.test.tsx那条「declares nothing@objectstack/specdoes not」是单向的:补icon不会让它翻红(spec 有),但它从来管不到「漏声明」这一侧 —— 和 #3900 是同一个不对称的另一处体现。现象二:
navigation-renderer把items声明成type: 'object',而 prop 是数组注册里
{ name: 'items', type: 'object' },而NavigationRendererProps.items是NavigationItem[](packages/layout/src/NavigationRenderer.tsx:108)。packages/sdui-parser/src/validate.ts的checkType对'object'判typeof value === 'object' && value !== null && !Array.isArray(value),对'array'判Array.isArray—— 所以正确的写法items: [...]会拿到type-mismatch。实测:{ "severity": "warning", "code": "type-mismatch", "message": "navigation-renderer prop \"items\" expected an object", "tag": "navigation-renderer" }ManifestInputType里有'array',所以这不是 #3832 那个「ComponentInput.type表达不了 spec 联合类型」的表达力问题,单纯是声明写错了一个可表达的类型。可达性诚实标注: 本仓没有任何 JSON 元数据把
navigation-renderer当 schema 节点写(全仓 grep 只命中注册本身,以及 #3900 加的那条阳性对照测试)。所以这条今天的实际受害者是仓外按packages/layout/README.md列出的键作 schema 驱动的消费者,不是仓内 demo —— 与现象一「文档 demo 今天就中」不同级,分诊时请分别判。之所以放在同一条:两者是同一文件、同一族(声明面与实现不符 → manifest 门对正确写法报假诊断)、同一次审计就能扫完,分成两个孪生单没有收益。建议
一次改动扫完
registerLayout()的四个inputs块(page-header/responsive-grid/navigation-renderer/app-schema-renderer),逐键对齐三面:渲染器读点 × spec 声明 ×ManifestInputType表达。并按 #3900 的做法补双向钉子:声明的键过 manifest 门放行,未声明/写错类型的键仍报unknown-prop/type-mismatch—— 只钉前者的话,把诊断弄哑一样是绿的。关联:#3900(发现于此实现,只处理
isContainer)、#3226(同文件inputs收窄的前情,建立了「声明面即作者面」的调子)、#3829(page:header.icon:spec 声明、渲染器零读点,同一个键的反向缺陷)、#3832(ComponentInput.type表达力,不同根因)