Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

[finding] app-shell 注册后在 schema 路径上不可用:四个 ReactNode 插槽 JSON 一个都填不了,inputs 又为空,节点解析得到却永远渲染不出壳 #4841

Closed

Description

@yinlianghui

发现于 #4827 的实施(PR 见该单),回传立单。未认领,交 PM 三角。观察类(#4827 的 PR 落地后,文档已不再引导任何人写这个节点 —— 今天没有用户路径会踩到;记录的是这个注册面本身)。

事实(基线 378dc920b 实测)

packages/layout/src/index.ts 把 AppShell 注册成组件键 app-shell,不声明 inputs:

ComponentRegistry.register('app-shell', AppShell, {
  namespace: 'layout', label: 'App Shell', category: 'Layout',
});

于是 { "type": "app-shell" } 解析得到这个组件。但 AppShellProps(packages/layout/src/AppShell.tsx:25-40)七个键里有四个是 React.ReactNode 插槽 —— sidebar / navbar / children / rightRail —— 而 JSON 一个都填不了。用一个探针组件量过 SchemaRenderer 递给组件的 props(schema 里八个键全给):

sidebar:     present=true  value={"type":"text","value":"side"}   ← 纯对象,不是节点
navbar:      present=true  value={"type":"text","value":"nav"}
rightRail:   present=true  value={"type":"text","value":"rail"}
className:   present=true  value="custom-app"
defaultOpen: present=true  value=false
branding:    present=true  value={"primaryColor":"#3B82F6","title":"T"}
children:    present=false (absent)
body:        present=false (absent)
schema.children (经 `schema` prop) = [{"type":"text","value":"kid"}]

两种结局,都不是壳:

  1. children 被剥掉,静默。 SchemaRenderer.tsx:550-579 的元数据解构把 children(和 body)摘出去再 spread 其余键,而 AppShell 读的是 children prop、从不读 schema.children。实测渲染 { "type": "app-shell", "className": "custom-app", "children": [...] }:<main class="… custom-app"> 是空的,console.error 计数 0。
  2. 三个节点插槽直接崩。 给 sidebar/navbar/rightRail 一个 schema 对象,AppShell 把它当 ReactNode 渲染,React 拒绝:节点被换成渲染器错误框 Component "app-shell" failed to render — Objects are not valid as a React child (found: object with keys {type, value})。

只有三个纯数据键(className / defaultOpen / branding)真的通过 JSON 路径生效 —— 实测 branding 经 JSON 设到了 document.title 与 --primary。也就是说 JSON 侧能拿到的最好结果,是一个没有导航、没有顶栏、内容区空着的壳。

inputs 为空是第二层:sdui-parser 的 unknown-prop 检查没有可比对的声明面(同族 #3972),所以上面任何一种写法都不会被校验器说一句话。

为什么记成观察类

#4827 的 PR 落地后,content/docs/guide/layout.md 已改成教 React 组合,并加了钉子禁止本页再出现 app-shell 的 JSON 节点;packages/layout/README.md(#4817)与 content/docs/layout/app-shell.mdx(#4793/#4808)本来就只教 React。所以今天没有教学面把人引到这个节点上,这条记录的是注册面自身的状态,不是在途用户故障。

可能的处置(交维护者定,本单不预判)

  • A. 撤掉注册(ADR-0049 enforce-or-remove 的"移除"侧):AppShell 是 React 组合原语,app-schema-renderer 才是"从一份 JSON 渲染整壳"的入口(AppSchemaRenderer.tsx:9-19 自陈如此,且已声明 inputs)。撤掉后 { "type": "app-shell" } 会得到明确的"未注册"诊断,而不是今天这个"解析得到但永远渲染不出东西"的中间态。
  • B. 让它真的可授权:声明 inputs,并在渲染适配层把 sidebar/navbar/rightRail 的 schema 值渲染成节点、把 schema.children 接到 children 上。这是新增能力,代价明显更大,而且要先回答"它与 app-schema-renderer 的分工"。

倾向 A —— 契约优先:两个键各自一个渲染器,而不是留一个解析得到却填不满的壳。但这是公共注册面的取舍,不该由实施单自行决定。

参考

Activity

  1. os-zhuang commented on Aug 16, 2026

    @os-zhuang
    Contributor

    Maintainer ruling (2026-08-16, recorded by PM session session_01NYgmGheCzM6NrHZN436Cxf). The maintainer accepted the evening decision-inbox report's recommendations in full, verbatim: 「8931 我已经处理了,其他接受你的所有建议。」 For this card:

    Ruled: Option A — deregister the app-shell component key (ADR-0049 enforce-or-remove, the remove side).

    First-touch grading completes: label flipped needs-user-decision → pm:queue in the same stroke.


    Generated by Claude Code

  2. self-assigned this
    on Aug 16, 2026
  3. yinlianghui commented on Aug 16, 2026

    @yinlianghui
    ContributorAuthor

    【认领】objectui 分片 PM(session_01GTRjn8xBqp75dk7kFupVRt)批次 12 派发,os-dev 实施。分支:claude/issue-4841-deregister-app-shell。

    按 2026-08-16 维护者裁定(评论 5307574602)执行:方案 A 撤销 app-shell 组件键注册(ADR-0049 remove 侧)。AppShell 保持 React 组合原语;app-schema-renderer 仍是 JSON 整壳唯一入口。前置:扫 examples/catalog 的 "type": "app-shell" 用法,命中 ⇒ 停手回报,⛔ 静默迁移。撤除后 { "type": "app-shell" } 得到明确的未注册诊断。

    文件面:packages/layout/src/index.ts + 测试 + changeset。与在飞四单零交集。


    Generated by Claude Code

  4. yinlianghui commented on Aug 16, 2026

    @yinlianghui
    ContributorAuthor

    【实施完成】draft PR: #4859(分支 claude/issue-4841-deregister-app-shell)。

    按裁定(评论 5307574602)撤销 app-shell 组件键注册;AppShell 仍作为 React 组合原语导出,app-schema-renderer 不动。

    前置扫描(裁定要求的"标准退役动作")结果:0 命中。 objectui@1ef236e18 与 objectstack@d491625c1 两个仓的 origin/main 上,"type": "app-shell"(含紧凑、JS 单引号、YAML 三种写法)只出现在 content/docs/guide/layout.md 的说明性散文与 #4827 钉子的失败文案里,没有任何真实节点;examples / catalog / apps / e2e / skills 全零。负控制:同一套命令扫 "type": "page" 在 examples/schema-catalog 命中 5 个文件,证明扫描面确实覆盖 catalog 目录。所以不存在需要 fork report 的用法。

    撤除后的诊断读数(实测,新钉子逐字断言):SchemaRenderer 渲染出 role="alert" 红框,首行 Unknown component type: app-shell,开发态附错误码 OBJUI-001。

    一处与派发预期不同,说明如下:裁定说"文档已干净、无需改教学面",但文档实际必须改,且其中一处是机械强制的。

    1. content/docs/guide/layout.md 的 AppShell 小节有一整块「In a JSON node」,逐条陈述"app-shell 已注册、节点确实解析到本组件、三个纯数据键生效"——这些是 content/docs/guide/layout.md 的 app-shell「Schema API」块教了 7 个 AppShellProps 从未声明的键(header/body/headerClassName/... ),照抄的 JSON 全部静默丢弃 #4827 实测写下的事实陈述,被本次改动直接证伪。content/docs/guide/layout.md 的 app-shell「Schema API」块教了 7 个 AppShellProps 从未声明的键(header/body/headerClassName/... ),照抄的 JSON 全部静默丢弃 #4827 让这一页不再引导任何人写这个节点(这点裁定说得对),但它同时留下了一段描述注册面现状的文字。
    2. 同页 SidebarNav 小节的"registers six keys — …"句列举注册键表并含 app-shell,而 guide-layout-sidebar-nav-doc.test.ts 会把这句与 index.ts 的 register 调用机械比对——不改文档,这条钉子必红(实测确实红了)。
    3. packages/layout/README.md 的 Registration 小节同样列举注册键表(已发布包的 README),一并改。

    三处都只是"把被本次改动证伪的事实改对",没有新增教学内容。

    顺带记录(已另立单,不在本 PR 修):README 的这份键表是三处同源键表里唯一没有机械钉子的一处——guide 那份有 #4840 的钉子、index.ts 有本单新增的钉子,README 只能靠人读到。这正是它这次漂掉的原因。


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions