Skip to content

docs(layout): page-header 两处文档按组件实读收敛到 subtitle,删掉不存在的 breadcrumbs - #3785

Merged
yinlianghui merged 2 commits into
mainfrom
claude/issue-3267-page-header-docs
Aug 8, 2026
Merged

docs(layout): page-header 两处文档按组件实读收敛到 subtitle,删掉不存在的 breadcrumbs#3785
yinlianghui merged 2 commits into
mainfrom
claude/issue-3267-page-header-docs

Conversation

@yinlianghui

@yinlianghui yinlianghui commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator

Fixes #3267

背景

#3226 / PR #3265 只收窄了机器读的声明面(packages/layout/src/index.tspage-header registration 的 inputs)。人和 AI 作者读的声明面 —— 文档 —— 还在教同一个非 spec 的 description,而且教得比 inputs 直接得多:一份是复制粘贴即用的 authored JSON 示例。本 PR 让文档面跟上,纯文档单,不触 packages/**,不触 content/docs/releases/**

文档的每一句都从 origin/main 的代码读出,不从 issue 正文抄 —— 下面是逐条对照表。

改动

1. content/docs/guide/layout.md(「PageHeader Component」小节)

  • authored JSON 示例:descriptionsubtitle;删掉 6 行 breadcrumbs 数组(组件一处都不读);actions 从 ComponentSchema 节点列表(type: "button")改成 spec 形状的 action id 列表 ["edit", "delete"]
  • 「Schema API」块:改 subtitle,删 breadcrumbs,actions 类型改为 action id 或 ActionDef 列表,补 showBack / children,description 降级为标注「legacy alias,不要 author」的一行。
  • 新增两段 blockquote:「写 subtitle 不写 description」与「没有 breadcrumbs 数组」(spec 的 breadcrumb单数 boolean,canonical page:header 的显示开关,不是链接列表)。
  • {field.path} 插值与「未解析折叠为空串」的行为说明。

2. content/docs/layout/page-header.mdx(「Component Props」块)

  • Component Props 块补齐组件真实的 prop 全集:subtitle(标为规范键)、iconactionsshowBackschema,并补上 extends React.HTMLAttributes< HTMLDivElement >
  • description 保留不删(见下「退役状态」),但标为 legacy alias、指向 subtitle
  • 块后新增说明:subtitle 的 spec 依据、description 的退役状态与门控、actions 的委派与右侧槽优先级、showBack 的默认值推断。

同文件里 5 处 PageHeader 示例的 prop 拼写一并改成 subtitle(含 Layout ASCII 图与 Styling 小节的 "Description" 标题)。这一步超出 issue 字面列的 L25-34,但不做会让新加的「description 是 legacy,写 subtitle」注解被紧随其后的 5 个 description= 示例当场推翻 —— 半个修法比不修更误导。(PM 复核已采纳。)

唯一不动的 description:Usage with Page Component 里 Page 组件的 description= 示例。那是 page renderer 自己的真 prop(页面自身的正文,不是 header 的副标题),已就地加注说明,防止后来者顺手「修」错。

description 的退役状态(第二个 commit 修正了这一段)

初版把退役写成「等上游 conversion 落地」。PM 复核代查后指出该前提已过期,独立复核确认(objectstack origin/main = d42a92fc6):

事实 依据
conversion page-header-subtitle-aliaslive docs/protocol-upgrade-guide.md:273 —— "live — protocol 17 loader accepts the old shape"
语义:加载期把 header 节点 properties.description 改写为 properties.subtitle packages/spec/src/conversions/registry.ts:4616-4633(toMajor: 17,renameKey + emit ConversionNotice)
canonical 优先:subtitle 在场则不改写,被遮蔽的 description 原样留下 registry.ts:4607-4612
只动 header 节点(element:text_inputdescription 是它自己的活属性) registry.ts:4610-4612,fixture :4652
加载期 = defineStack / validate / lint + applyConversionsToStoredItemsys_metadata 存量行 packages/spec/CHANGELOG.md:3499(089767f)
lint 侧已按该改写实现 packages/lint/src/authoring-rules.ts:550

因此文档改为如实三段式:(a) 上游 conversion 已 live 且具体做什么;(b) 本仓 description prop 的退役由 objectui#3789 门控,门槛是核实每条 page-header 作者路径都经过执行改写的 loader;(c) #3789 落地前 prop 保留、新页面写 subtitle。不预告时间,不替 #3789 下结论。

同时修掉 layout.md 的一处同类失真(PM 点 2 的一致性自查抓到):原 blockquote 断言「写 description 的 metadata 在 canonical page:header什么都不出」。conversion 落地后这个结论变成路径相关 —— 经 loader 的元数据会被改写成 subtitle 并正常渲染(registry fixture :4645-4647 明确把 type: 'page:header' 也一并转换),只有绕过 loader 的裸 JSON 才仍然不出。已改写为「两处仍接受 description(加载期改写 + 本渲染器直读),但 subtitle 是唯一在每条路径上都渲染的拼写」,并指向 #3789

文档声明 ↔ 代码行号对照表

文档声明 代码依据(objectui origin/main = 00b9451d8)
title: string 必填 packages/layout/src/PageHeader.tsx:29
subtitle?: string 是规范键 PageHeader.tsx:35;packages/layout/src/index.ts:56(收窄后的 inputs)
spec PageHeaderProps = title/subtitle/icon/breadcrumb/actions/aria,无 description objectstack packages/spec/src/ui/component.zod.ts:224-232
description? 是 legacy alias,subtitle 优先 PageHeader.tsx:36 + :143(const secondaryRaw = subtitle ?? description;)
icon? 接 Lucide 名或 React node,渲染在标题左侧 chip PageHeader.tsx:42:224-228
action?: React.ReactNode 落右侧槽 PageHeader.tsx:43:207
actions? 交给 record:quick_actions,location: 'record_header' PageHeader.tsx:58:192-206
actions 条目是 action id 或 ActionDef,不是 ComponentSchema packages/plugin-detail/src/renderers/record-quick-actions.tsx:60-93(全字符串走对象 metadata 查名,否则当 ActionDef)
showBack? 省略时按 record 上下文推断(有 recordId 且非 embedded 才为 true) PageHeader.tsx:50:159-161
schema? 的形状 PageHeader.tsx:65
children 落右侧槽,actions 优先 PageHeader.tsx:182-190:207(actionchildrenactionsSlotschemaChildren)
className 透传 PageHeader.tsx:210
{field.path} 插值;未解析折叠为空串而非漏出原模板 PageHeader.tsx:89-110:136-140:147-151
组件不读任何 breadcrumb 属性(单复数均无) PageHeader.tsx 全文 breadcrumb 仅出现在 L16 的 doc 注释里,零处读取
spec 的 breadcrumb 是单数 boolean,canonical 节点的显示开关 objectstack component.zod.ts:228;渲染器 packages/components/src/renderers/layout/containers.tsx:979(!== false)、:1495-1498:1530-1531:1560
canonical page:header 渲染器subtitle containers.tsx:962(schema?.subtitle ?? schema?.properties?.subtitle);该文件 description 读取数 = 0(grep -c 实测)
page-headerinputs 只声明 title/subtitle packages/layout/src/index.ts:50-58
Page 的 descriptionpage renderer 自己的真 prop packages/components/src/renderers/layout/page.tsx:605:635:672;注释 :603-604 明说「the page's own prose, not a duplicate of the header's subtitle」

验证(返工后复跑)

$ node scripts/check-doc-links.mjs
Links are valid across 7 scan roots.

$ node scripts/check-control-bytes.mjs
check-control-bytes: OK (scanned 3718 tracked text file(s); skipped 85 binary).

$ grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f]' content/docs/guide/layout.md content/docs/layout/page-header.mdx
(无输出,exit 1 —— 零控制字节)

$ pnpm vitest run --maxWorkers=2 scripts/__tests__/doc-version-claims.test.ts \
    scripts/__tests__/check-doc-links.test.ts scripts/__tests__/check-changeset-presence.test.ts
Test Files  3 passed (3)
     Tests  127 passed (127)

$ npx fumadocs-mdx        # apps/site,确认 MDX 仍可解析
[MDX] generated files in 27.6ms

Changeset

不需要。 依据是仓里的判定脚本本身,而非推断:

$ node scripts/check-changeset-presence.mjs
Compared the working tree with 00b9451d8 (merge-base with origin/main): 2 file(s) changed,
0 of them under the src/ of a package the release covers, 0 under a package changesets ignores,
0 changeset(s) added.
No source of a released package changed in this range, so no changeset is owed.

与先例一致:近 15 个 docs 提交(含多个改 content/docs/** 的)全部零 changeset。

顺手发现(未在本 PR 修,另开 issue)

均属另一类断言,故意不搭车


🤖 Generated with Claude Code

https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt

`content/docs/guide/layout.md` 的 authored JSON 示例与 Schema API 教的是非 spec 的
`description`,还声明了 `breadcrumbs?: Array<{label,href?,icon?}>` 与
`actions?: ComponentSchema[]` —— 前者 PageHeader.tsx 一处都不读,后者类型是错的。
`content/docs/layout/page-header.mdx` 的 Component Props 块只列 `description`、
一字未提 `subtitle`,并漏了 icon/actions/showBack/schema 四个真实 prop。

#3226 / PR #3265 只收窄了机器读的 registration `inputs`;这一单让人和 AI 作者读的
文档面跟上,每条声明都从 origin/main 的代码读出:

- layout.md:示例与 Schema API 改 `subtitle`;`actions` 改为 action id / ActionDef
  列表(交给 `record:quick_actions`,不是 ComponentSchema);补 `showBack`/`children`;
  两段 blockquote 写清「写 subtitle 不写 description」与「没有 breadcrumbs 数组」
  (spec 的 `breadcrumb` 是单数 boolean,canonical `page:header` 的开关)。
- page-header.mdx:Component Props 块补齐真实 prop 全集;`description` 保留但标为
  legacy alias,注明随上游 ADR-0087 D2 conversion `page-header-subtitle-alias`
  一并移除(现在删会让仓外页面静默丢副标题)。同文件 PageHeader 示例的 prop 拼写
  一并改 `subtitle`,否则新加的注解会被下面 5 个 `description=` 示例当场推翻;
  `<Page description=…>` 那一处**不动** —— 它是 `page` renderer 自己的真 prop,
  并就地加注防止后来者误改。

无 changeset:`scripts/check-changeset-presence.mjs` 判定「0 of them under the src/
of a package the release covers … no changeset is owed」,与近 15 个 docs-only PR 的
先例一致。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt
@vercel

vercel Bot commented Aug 8, 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 8, 2026 3:45pm

Request Review

PM 复核代查后指出:conversion `page-header-subtitle-alias` 已在 objectstack main
落地并标 live,因此上一版 page-header.mdx 里「It is scheduled for removal … Until
that conversion ships」是落地即陈旧的事实断言 —— 正是本单要消灭的缺陷类。

独立复核(objectstack origin/main = d42a92fc6):

- docs/protocol-upgrade-guide.md:273 —— `page-header-subtitle-alias` |
  `page.component.page-header.description` | 'description' → 'subtitle' |
  **live — protocol 17 loader accepts the old shape**
- packages/spec/src/conversions/registry.ts:4616-4633 —— toMajor: 17,
  apply() 对 PAGE_HEADER_COMPONENT_TYPES 的节点 renameKey(properties,
  'description', 'subtitle') 并 emit ConversionNotice
- registry.ts:4607-4612 —— canonical 优先:`subtitle` 在场时不改写,被遮蔽的
  `description` 原样留下;只动 header 节点(element:text_input 的 description
  是它自己的活属性)
- packages/spec/CHANGELOG.md:3499(089767f)—— 加载期为 defineStack /
  validate / lint,以及 applyConversionsToStoredItem 覆盖的 sys_metadata 存量行
- packages/lint/src/authoring-rules.ts:550 已按该改写实现

page-header.mdx:退役段改为如实三段式 —— (a) 上游 conversion 已 live 且做什么;
(b) 本仓 prop 退役由 objectui#3789 门控,门槛是核实每条作者路径都经过执行改写的
loader;(c) #3789 落地前 prop 保留、新页面写 subtitle。不预告时间,不替 #3789
下结论。

layout.md:PM 说该文件未做「未落地」断言、不用动 —— 确认无landing-state 断言,
但按 PM 要求自查两文件一致性时发现另一处同类失真:原 blockquote 断言「写
description 的 metadata 在 canonical page:header 下**什么都不出**」。conversion
落地后该结论变成路径相关 —— 经 loader 的元数据会被改写成 subtitle 而正常渲染
(registry fixture:4645-4647 明确把 type: 'page:header' 也一并转换),只有绕过
loader 的裸 JSON 才仍然不出。故按真实情况改写为「两处仍接受 description(加载期
改写 + 本渲染器直读),但 subtitle 是唯一在每条路径上都渲染的拼写」,并指向
#3789。这是 PM 点 2 自查要抓的东西,不是扩范围。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt

Copy link
Copy Markdown
Collaborator Author

✅ 验收通过(objectui 分片 PM,session_01GTRjn8xBqp75dk7kFupVRt)—— undraft + auto-merge。

round 1 核验(d2fbb8ee1):issue 三条断言全部从代码复核为真;19 行「文档声明 ↔ 代码行号」对照表齐备;description#3226 裁定保留并标 legacy;同文件 5 处示例统一改 subtitle 的扩围有理(否则新注解被紧随的示例当场推翻),PM 采纳;Page description= 是 page renderer 自己的真 prop,就地加注防误改 —— 这一条是超出 issue 文本的正确判断。

round 2 返工(e00a5fd19,PM 要求):原稿「Until that conversion ships」在 conversion page-header-subtitle-alias 已 live(objectstack upgrade-guide:273、registry.ts:4616-4633)后是落地即陈旧的断言。返工后:(a) mdx 如实三段式 —— 上游已 live 及其确切语义(canonical subtitle 优先、被遮蔽的 description 不由 loader 裁决)、退役由 #3789 测量门控、落地前 prop 保留;(b) dev 一致性自查抓到 layout.md 第二处同类失真(「什么都不出」在 conversion 落地后变为路径相关)并一并修正为「subtitle 是唯一在每条路径上都渲染的拼写」—— 自查条款正是为此而设,处置正确;(c) PR body 同步重写,推送产物内无陈旧断言残留。

两轮远端 CI 均 0 失败;changeset 按判定脚本与先例确不需要。#3789 落地时应把本 PR 两处措辞列进 done-when(其正文已互相锚定)。


Generated by Claude Code

@yinlianghui
yinlianghui marked this pull request as ready for review August 8, 2026 15:48
@yinlianghui
yinlianghui added this pull request to the merge queue Aug 8, 2026
Merged via the queue into main with commit f9d70a7 Aug 8, 2026
17 checks passed
@yinlianghui
yinlianghui deleted the claude/issue-3267-page-header-docs branch August 8, 2026 15:48
yinlianghui pushed a commit that referenced this pull request Aug 8, 2026
CI 的 Changeset Declaration 门(scripts/check-changeset-presence.mjs)守的是
「fixed 组内任一包的 `<pkg>/src/**` 被改动 -> 必须有 `.changeset/*.md` 声明」,
本次三个改动文件全部落在 packages/app-shell/src 下,所以门要求声明。

先前照 #3666(bd04651)与 #3785(f9d70a7)判断「注释-only 不带 changeset」是
读错了先例:那两个 PR 早于这道门(#3387 引入),且 #3785 只动 content/docs/**,
根本不在守护面内。门自己写明了正确出口 —— 「If this change really should release
nothing, say so — that is a pass, not a workaround」,即空 frontmatter,照
.changeset/registry-inputs-spec-parity-gate-3797.md 的先例写法。

空 frontmatter 而非 patch:AppContent.tsx 只改 JSX 注释,两条
LegacyMetadataRedirect 路由声明与其余每一行代码未动;两个测试文件断言与用例数
逐字节未变(前后同为 43 passed)。确无可发布的行为改动。

本地验证:
  node scripts/check-changeset-presence.mjs  -> exit 0(识别为空 frontmatter 豁免)
  node scripts/check-changeset-no-major.mjs   -> exit 0
github-merge-queue Bot pushed a commit that referenced this pull request Aug 8, 2026
* docs(app-shell): 两个 AppContent 路由测试的生产端叙述改写为「历史 + 现状」两段 (#3749)

三处注释以现在时枚举「侧边栏 / QuickActions 现在发哪些 URL」,而这些 URL 已被
#3660(`sys-datasources`)与 #3739(`sys-objects` + 首页 Manage Objects 卡片)改指
metadata-admin 引擎的规范路由。断言全部正确且全绿 —— 陈旧的只有叙述,以及两个以
生产端命名的 `it` 标题:它们实际量的是别名路由仍能解析且只跳一次。

手法照 #3666:每处拆成「#3610 当时如此」(过去时)与「#3660/#3739 之后如此」
(现在时)两段,现状段正面陈述两条别名今天的身份 —— 不再是任何导航的目标,而是
书签与外部链接的到达路径,这正是重定向必须继续工作的理由。按 #3656,现状段不靠
「不再指向 X」的否认句复述别名 URL。

- `AppContent.noAppComponentRoutes.test.tsx`:头注拆段;两个 `it` 标题从
  `sys-datasources:` / `sys-objects:` 改为 `shell alias:` / `host alias:`,即两条
  别名各自的改写者(shell 自己的 `LegacyMetadataRedirect` vs 宿主的
  `MetadataRedirect`)—— 文件正文本来就画了这条区分。用例体内两处重申同一陈旧
  断言的行内注释同步(`sys-datasources` item still points straight at this
  spelling / this case now measures what production actually does),否则改完标题
  的文件会自相矛盾。顺带把同一段落里 `isMetadataRoute` 的现在时「substring test」
  收敛为过去时 —— #3638 起它是 `pathSegments.includes('metadata')` 段测试。
- `AppContent.pseudoRouteSegments.test.tsx`:生产端表格按实读重写(逐条读自
  `AppSidebar` / `UnifiedSidebar` / `QuickActions` / `HomePage` / `InboxPopover`),
  并补一段说明两条旧拼写并未消失,只是从 `navigation` 行移到了上面两行的到达面。
  `metadata` 两种拼写下都是完整路径段,所以本文件要证伪的论断不受影响。
- `AppContent.tsx`(#3610 那段):收尾句补上 #3739 也已重指,并注明 `sys-objects`
  的链路早在 #3658 就离开了这两条路由。该段前半的过去时叙述保留。

零行为改动:两个测试文件跑前跑后同为 43 passed,断言与用例数未动。
无 changeset —— 注释-only,照 #3666(bd04651)与 #3785(f9d70a7)先例。

Fixes #3749

* chore(changeset): 为 #3749 注释改写声明「不发布任何东西」(空 frontmatter)

CI 的 Changeset Declaration 门(scripts/check-changeset-presence.mjs)守的是
「fixed 组内任一包的 `<pkg>/src/**` 被改动 -> 必须有 `.changeset/*.md` 声明」,
本次三个改动文件全部落在 packages/app-shell/src 下,所以门要求声明。

先前照 #3666(bd04651)与 #3785(f9d70a7)判断「注释-only 不带 changeset」是
读错了先例:那两个 PR 早于这道门(#3387 引入),且 #3785 只动 content/docs/**,
根本不在守护面内。门自己写明了正确出口 —— 「If this change really should release
nothing, say so — that is a pass, not a workaround」,即空 frontmatter,照
.changeset/registry-inputs-spec-parity-gate-3797.md 的先例写法。

空 frontmatter 而非 patch:AppContent.tsx 只改 JSX 注释,两条
LegacyMetadataRedirect 路由声明与其余每一行代码未动;两个测试文件断言与用例数
逐字节未变(前后同为 43 passed)。确无可发布的行为改动。

本地验证:
  node scripts/check-changeset-presence.mjs  -> exit 0(识别为空 frontmatter 豁免)
  node scripts/check-changeset-no-major.mjs   -> exit 0

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

docs: page-header 的两处文档仍在教非 spec 的 description(并声明了组件根本不读的 breadcrumbs)

2 participants