Skip to content

fix(layout): registerLayout 的 inputs 声明面对齐渲染器读点 —— page-header 补 icon/actions,navigation-renderer 的 items 改为 array (#3972) - #3984

Merged
yinlianghui merged 1 commit into
mainfrom
claude/issue-3972-layout-inputs-declaration
Aug 9, 2026
Merged

fix(layout): registerLayout 的 inputs 声明面对齐渲染器读点 —— page-header 补 icon/actions,navigation-renderer 的 items 改为 array (#3972)#3984
yinlianghui merged 1 commit into
mainfrom
claude/issue-3972-layout-inputs-declaration

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Fixes #3972

前提复核(基 cdc0e44c8,git merge-base --is-ancestor 82f8dfffd HEAD 通过 —— PR #3973 在基内)

正文两处锚点与"实测"全部成立,逐条复核:

  • packages/layout/src/index.tspage-header 只声明 title / subtitle;PageHeader.tsx:117 取参、:224-226 渲染 icon(字符串走 LazyIcon,也接受 React node);@objectstack/spec/uiPageHeaderProps.shape 实测为 title, subtitle, icon, breadcrumb, actions, aria,icon 是 optional string。
  • 文档 demo examples/schema-catalog/src/schemas/layout-page-header/pageheader-with-actions.json 已经"icon": "users"(无需为造钉子改 JSON),而它是 content/docs/layout/page-header.mdx 唯一的 live demo。
  • 注册里 itemstype: 'object',NavigationRendererProps.itemsNavigationItem[](NavigationRenderer.tsx:108);checkType 对两者互斥判定(sdui-parser/src/validate.ts:124-129),ManifestInputType'array'(types.ts:51-62)—— 确实不是 ComponentInput.type 无法表达 spec 的联合类型,于是发布面永远比契约窄一个 arm —— page:header.title 的内联翻译映射今天就会被 manifest 门报 type-mismatch #3832 的表达力问题。

改了什么(packages/layout/src/index.ts,三个键)

正文要求的是"一次按渲染器实际读点的审计",所以 page-header 这一块是整块过的,结果比派发单摘要多出一个键:

渲染器读点 spec 动作
icon :117 / :224-226 有(string) type: 'string'
actions :119 / :192-196(委派 record:quick_actions) 有(array) type: 'array'
items(navigation-renderer) NavigationItem[],:108 无对应 spec 节点 'object''array'

actionsicon 是同一个判据下的同一类缺陷(渲染器真读 × spec 声明 × 类型可表达),content/docs/layout/page-header.mdx 的 Component Props 段落把两者一起写成公开契约,而它今天同样吃 unknown-prop。只修 icon 会让这次审计留下一个已知未修的同族实例,所以一并修了 —— 类型与 canonical page:headeractions 逐字一致(containers.tsx:1585,type: 'array'),不新开方言。若维护者认为该键应单开一单,删掉那一条 input 与对应的两条断言即可,其余不受影响。

刻意不声明的也一并钉住(照抄 spec shape 是这条修复最容易滑进的反向缺陷):breadcrumb(spec 有、这个渲染器零读点 → 声明它就是 #3829 的方向)、showBack / action / description(渲染器读、spec 无 → 声明即第二套方言,#3226 收窄要防的正是这个)、aria(全仓一致的可访问性逃逸口)。

顺带一处值得记下的对称:page:header.iconapps/console/src/__tests__/registry-inputs-spec-parity.test.tsUNPUBLISHED_EXEMPTIONS 里(canonical 渲染器零读点,#3829),而同名键在这里被声明 —— 两个不同渲染器、相反的读点事实,不矛盾。该门只判 ComponentPropsMap 里的键(page:header),kebab 的 legacy page-header 不在其覆盖内,所以本 PR 不动它的任何期望值。

渲染输出逐字节不变:渲染路径从不读 inputs

钉子(两方向,沿 PR #3973 的形态)

注册级(packages/layout/src/__tests__/):

  • page-header-authorable-keys.test.tsx 新增一个 describe:icon/actions 在裸键与 namespace 两处都声明且类型正确、且"合法的理由"(spec 拥有该键)一起断言;反面钉 breadcrumb 未声明(spec 有、零读点)与 showBack/action/description 未声明(spec 无)。
  • navigation-renderer-items-declaration.test.tsx(新):运行期钉 items 声明为 array;另有一半是编译期的 —— NavigationRendererPropsitems 必须仍可赋给数组,由 pnpm type-check 兜住(tsconfig.test.json 覆盖测试文件),这样 prop 被改形状时是 type-check 红,而不是声明面悄悄漂回去。

端到端(examples/schema-catalog/test/pageheader-with-actions.test.tsx,复用 PR #3973 建的 diagnose(),即按 getJsxManifest() 同法构建 manifest):

PR #3973 留在该文件里的两处注释(说 iconunknown-prop 尚在、说 items: [] 会另报 type-mismatch)已随之更新 —— 它们描述的是修复前的状态。

反向验证(先预判,后跑;变异未提交)

预判写在跑之前:把 icon/actions 删掉、items 改回 'object'(即 #3972 前的声明状态),预计 7 条红、两条必须仍绿。实测 Tests 7 failed | 21 passed (28),与预判逐条一致:

审计的其余两个 inputs 块:界内无改动,界外三条已另开单

responsive-grid / app-schema-renderer 逐键过了,没有发现"对正确写法报假诊断"这一类缺陷,故本 PR 不动它们。审计顺带看到的三件事都落在本单完成范围之外,已按 Prime Directive #10 另开(未认领),不夹带:

  • app-schema-renderer.mobileNavMode 声明成自由文本 string,实现是三值联合,且 'hamburger' 在渲染器里零读点 —— 改成 enum 前得先定"词表里还有几个值是真的",属契约决定,不猜。
  • navigation-renderer.items 是 TS 必填、声明面可选,省略它时渲染器直接抛(collectPinnedItems 的 for-of 与 sorted.slice() 都吃 undefined),而校验器一言不发。加 required: true 会新增 error 级诊断,是收紧决定,不在本单"三面对齐"的范围里。
  • responsive-grid.gap 的 TS 类型收 number | string,但 ResponsiveGrid.tsx:111 只对 number 算 class,字符串静默无间距(连默认的 4 也丢)。

验证

  • pnpm exec vitest run packages/layout packages/sdui-parser examples/schema-catalog --maxWorkers=2(仓根,shared lock 内):16 files / 1216 tests 全绿
  • 三个改动文件 --reporter=verbose:28 条全绿,新增钉子逐条可见(反可达性:确认它们真的跑了)。
  • pnpm exec turbo run type-check --concurrency=2(仓根):78/78 successful
  • node scripts/check-control-bytes.mjs:OK(3882 文件);另对本 PR 五个文件跑了 grep -naP 的越界自查,无控制字节。

changeset

.changeset/layout-inputs-declaration-3972.md,@object-ui/layout patch(声明面修正,渲染输出不变)。


Generated by Claude Code

page-header 补声明 icon 与 actions(渲染器真读 + spec 声明 + ManifestInputType
表达得了),navigation-renderer 的 items 由 type:'object' 改为 'array'
(NavigationRendererProps.items 是 NavigationItem[])。

声明面即作者面:sdui-parser 拿节点顶层属性比对 comp.inputs,漏声明的键报
unknown-prop、类型写错的报 type-mismatch —— 两处都是对着正确写法报假诊断,
其中 icon 那条今天就落在 content/docs/layout/page-header.mdx 唯一的 live demo 上。
渲染输出不变(渲染路径不读 inputs)。

两方向都钉住:文档 demo 过 manifest 门无 unknown-prop、数组 items 无
type-mismatch;对照侧 description(#3226 刻意不声明)仍报 unknown-prop、
items 写成对象仍报 type-mismatch 且消息已变为 expected an array。
breadcrumb / showBack / action / aria 的刻意不声明也各自钉了理由。

Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt

Co-authored-by: Claude <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 9, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectui Ignored Ignored Aug 9, 2026 8:54pm

Request Review

@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Main entry (gzip) 28.1 KB 350 KB
Entry file index-C0xIwglU.js
Status PASS

📦 Bundle Size Report

Package Size Gzipped
app-shell (index.js) 8.66KB 3.13KB
app-shell (runtime-config.js) 7.42KB 2.32KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 7.57KB 2.97KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 1.17KB 0.53KB
auth (AuthProvider.js) 22.10KB 4.37KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.13KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.64KB 2.21KB
auth (SocialSignInButtons.js) 9.60KB 3.89KB
auth (UserMenu.js) 3.40KB 1.22KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 35.76KB 9.11KB
auth (createAuthenticatedFetch.js) 4.37KB 1.69KB
auth (index.js) 2.35KB 1.07KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 4.91KB 0.87KB
auth (useIsWorkspaceAdmin.js) 1.61KB 0.85KB
collaboration (CommentThread.js) 26.07KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.65KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 483.34KB 106.60KB
core (index.js) 3.00KB 1.14KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 139.61KB 35.99KB
fields (index.js) 227.92KB 56.45KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (currency.js) 1.22KB 0.64KB
i18n (i18n.js) 4.32KB 1.77KB
i18n (index.js) 2.65KB 1.06KB
i18n (pickLocalized.js) 1.70KB 0.83KB
i18n (provider.js) 9.48KB 3.27KB
i18n (useObjectLabel.js) 27.59KB 6.63KB
i18n (useSafeTranslation.js) 4.52KB 1.96KB
layout (index.js) 38.82KB 10.79KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.74KB
mobile (index.js) 1.50KB 0.62KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.71KB 0.42KB
mobile (useResponsiveConfig.js) 1.36KB 0.63KB
mobile (useSpecGesture.js) 4.32KB 1.64KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 8.75KB 3.06KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 3.67KB 1.12KB
permissions (evaluator.js) 4.41KB 1.44KB
permissions (index.js) 0.91KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.52KB
permissions (usePermissions.js) 1.55KB 0.71KB
plugin-ai (index.js) 15.71KB 3.79KB
plugin-calendar (index.js) 45.23KB 12.45KB
plugin-charts (index.js) 61.49KB 17.48KB
plugin-chatbot (index.js) 180.33KB 42.79KB
plugin-dashboard (index.js) 118.39KB 30.67KB
plugin-designer (index.js) 210.51KB 42.51KB
plugin-detail (index.js) 237.80KB 59.48KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 112.29KB 27.16KB
plugin-gantt (index.js) 162.55KB 39.57KB
plugin-grid (index.js) 187.90KB 49.79KB
plugin-kanban (index.js) 48.53KB 13.38KB
plugin-list (index.js) 109.73KB 26.55KB
plugin-map (index.js) 16.81KB 5.24KB
plugin-markdown (index.js) 13.72KB 4.69KB
plugin-report (index.js) 40.58KB 10.58KB
plugin-timeline (index.js) 25.76KB 7.33KB
plugin-tree (index.js) 8.50KB 2.88KB
plugin-view (index.js) 84.03KB 20.55KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.71KB 3.53KB
providers (index.js) 0.44KB 0.22KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.67KB 2.37KB
react (LazyPluginLoader.js) 3.77KB 1.33KB
react (SchemaRenderer.js) 23.71KB 7.95KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 1.23KB 0.66KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 4.09KB 1.74KB
sdui-parser (index.js) 4.47KB 2.03KB
sdui-parser (parse.js) 10.04KB 2.82KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 4.69KB 1.48KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 0.99KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 0.20KB 0.18KB
types (crud.js) 0.20KB 0.18KB
types (data-display.js) 0.20KB 0.18KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.87KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-retry.js) 4.32KB 2.02KB
types (index.js) 2.71KB 1.34KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 2.59KB 1.31KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (spec-report.js) 5.05KB 1.93KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 0.20KB 0.18KB
types (ui-action.js) 3.40KB 1.71KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

Copy link
Copy Markdown
Collaborator Author

PM 验收(session_01GTRjn8xBqp75dk7kFupVRt):通过,转 ready 并挂 auto-merge。#3972 落地。

核验记录(head b9f8e9c71,基 cdc0e44c8,实物核验 + CI 亲读):

  1. 三键对齐,actions 扩围有据:icon/actions 补声明、items 改数组(实物 grep 齐);actions 超派发词两条但完全满足 issue 正文的审计判据(渲染器读 × spec 声明 × 文档写成公开契约 × 今天吃 unknown-prop),留着必生孪生单 —— PM 采纳,且 PR 写明可单独回退。
  2. 反面钉防「照抄 spec shape」:breadcrumb(spec 有零读点)/showBack/action/description(渲染器读 spec 无)逐个钉住不声明的理由 —— 这正是把「审计」而非「同步」作为本单本质的证据;page-headerpage:header 对外声明了两套 authorable 键:description vs subtitle,消费端用 ?? 兜底 #3226 的 description 收窄对照仍绿。
  3. 反向验证:三键还原变异 7 红 21 绿逐条吻合;两条「设计上不红」的方向(TS prop 钉只看形状、items 判定互换非消失)诚实标注 —— 后者对「toContain 前后都成立」的辨析尤其干净。
  4. PR3973 注释维护:两处描述修复前状态的注释同步更新 —— 正确的接力。
  5. 门与规程:1216 测试绿 + 28 条新钉 verbose 逐条确认真跑;type-check 78/78;控制字节门 + 自扫;changeset patch 三门绿;fable 0;README 无需同步的核查在案;⛔ releases/ 未触碰。
  6. CI 亲读终态:20/20 全 completed、0 失败(Test shard×4 至 21:01:04Z)。

out-of-scope 三条:#3985(mobileNavMode 词表未定,finding 持有)、#3987(items required 缺口 —— 收紧决定有 blast radius,PM 另评分诊)、#3988(gap 字符串死支,finding 持有)—— 划界全部正确,#3987 刻意不夹带(新增 error 级诊断属收紧决定)的判断尤其对。


Generated by Claude Code

@yinlianghui
yinlianghui marked this pull request as ready for review August 9, 2026 21:08
@yinlianghui
yinlianghui added this pull request to the merge queue Aug 9, 2026
Merged via the queue into main with commit 6bd6a4d Aug 9, 2026
21 checks passed
@yinlianghui
yinlianghui deleted the claude/issue-3972-layout-inputs-declaration branch August 9, 2026 21:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants