Skip to content

docs(layout): PageHeader 的 Styling/Container 按代码改写,并把它的 live demo 换成真组件 (#3786, #3787) - #3905

Merged
yinlianghui merged 1 commit into
mainfrom
claude/issue-3786-3787-pageheader-docs-demo
Aug 9, 2026
Merged

docs(layout): PageHeader 的 Styling/Container 按代码改写,并把它的 live demo 换成真组件 (#3786, #3787)#3905
yinlianghui merged 1 commit into
mainfrom
claude/issue-3786-3787-pageheader-docs-demo

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Fixes #3786
Fixes #3787

两单结对一 PR(PR #3826 先例):它们是同一组样式数值的两份漂移副本。demo 手抄的正是 #3786 那三条已漂的数值,分开修会互相踩;demo 换成组件节点后,那组数值的第三份副本自然消灭(下面「第三份副本」一节)。

origin/main 显式 sha a0693d531094dc7f23b8ccd992539c796b9af6e7 切出。


#3786 面 —— Styling → Container 三条

packages/layout/src/PageHeader.tsx:210 的根 class 现状(动笔前在 origin/main 复核过,与单里一致):

cn('flex flex-col gap-3 pb-4 border-b', className)

content/docs/layout/page-header.mdx 的 Container 小节按此改写,三条对三条:

原文 现状 改后
pb-4 on mobile, pb-8 on desktop pb-4,无任何断点变体 pb-4 at every breakpoint — there is no responsive variant
Gap: gap-4 between elements 外层 gap-3;标题行 gap-x-4 gap-y-2(:211) gap-3 on the outer column;标题行的 gap-x-4 gap-y-2 一并写明
(未提) 根节点还有 border-b Border: border-b along the bottom edge

内层 gap-x-4 gap-y-2 选择如实展开而不是略过:这一节的读者是拿公开 className 口子做覆盖的作者,只知道外层 gap-3 仍会算错标题与右槽之间的横向间距。

同小节的 Title(:231)与 Subtitle(:233)两块复核后与代码一致,未动。「把样式数值文档改成指向代码」的根治方向按单里的裁定未搭车

#3787 面 —— 先量再写

单里写明这是要看运行环境的活,所以第一步是两点实测。两点的结论都和单里的猜测不同,记在这里:

实测 1:children 能进右槽,isContainer 与渲染无关

单里担心 page-header 注册没有 isContainer: true(packages/layout/src/index.ts:50-58)会让右槽空掉。实测:不会。isContainer渲染路径上没有任何消费者 —— 全仓消费者只有 packages/sdui-parser/src/validate.ts:89not-a-container 诊断、Studio 调色板元数据(page.tsx:464 / react-page.tsx:66)和 codegen 的文档表格。真正的通路是另外两处配合:SchemaRendererchildren 从 React props 里剥掉,但始终把整个节点作为 schema 传下去(packages/react/src/SchemaRenderer.tsx:396:456),PageHeader 再自己把 schema.children 渲染回右槽(PageHeader.tsx:182-190:207)。

浏览器读数(page-header 节点 + 两个 button children):

objTypes: ["page-header", "button", "button"]
h1: ["Users"]
headerRootClass: "flex flex-col gap-3 pb-4 border-b"
unknownComponent: false   exportBtn: true   addUserBtn: true

这条通路横跨两个文件、靠注释维系,所以配了 pin(见下)。

实测 2:actions 形态在无后端 gallery 渲染不出 —— 但卡点不是 record 上下文

单里推测第一等 actions 委派 record:quick_actions 需要 record 上下文。实测卡在更前一层:record:quick_actions 住在 @object-ui/plugin-detail,而文档站不依赖它。把 demo 写成 actions 形态的实际读数是右槽里套一块红面板:

demoText: "... Users / Manage your team members and permissions /
           Unknown component type: record:quick_actions ... (OBJUI-001)"
objTypes: ["page-header"]      exportBtn: false   addUserBtn: false

(record:quick_actions 本身对内联 ActionDef 数组并不需要 record 上下文 —— needsLookup 只在 actions 是字符串 id 时才要 objectName。但这里够不到那一步。)

按单里的规则(两条都能用才二选一,拿不到就选 children):选 children 形态,标题「With Actions」的语义由右槽两个按钮保留。

实测 3(单里没要求,但决定了这个 PR 的面):demo 换成组件节点后根本渲染不出来

单里提议的那份 JSON 直接落上去,文档页上是一块红面板:

demoText: "Unknown component type: page-header ... (OBJUI-001)"
objTypes: []

原因:@object-ui/layout 既不是 apps/site 的依赖,也没有任何代码 import 它(apps/site/node_modules/@object-ui/ 下没有 symlink;next.config.mjstranspilePackages 倒是列着它 —— 一条不生效的配置,已另立 #3904)。page-header 的占位符也接不住:它在 placeholders.tsx 里属于 opt-in 名单,只有 apps/consoleregisterPlaceholders()

也就是说 #3787 无法只靠改一份 JSON 关掉。本 PR 补上最小的注册面:

  • apps/site/package.json@object-ui/layout 依赖(+ lockfile)
  • 新增 apps/site/app/components/registerLayoutBlocks.ts,由三个渲染 catalog 示例的宿主 import:InteractiveDemoSchemaThumbnailLiveSplitDemo

三个而不是一个,是扫过消费半径后的结论:SchemaThumbnail/docs/guide/schema-catalog 上渲染整个 catalog,只在组件页包一层 loader 会把索引页留在红面板状态。两个页面都实测过:

页面 pageHeaderCount headerRootClass unknownComponent
/docs/layout/page-header 1 flex flex-col gap-3 pb-4 border-b false
/docs/guide/schema-catalog 1 flex flex-col gap-3 pb-4 border-b false

registrar 是显式调用的,尽管 @object-ui/layout 的模块体自己也会调:该包声明了 sideEffects: false,纯副作用 import 可以被打包器整体丢掉。这是绕行不是修复,原因写在模块头,并另立了 #3899。模块作用域调用(不是放进 effect)也保证 server render 时注册已完成,demo 留在预渲染 HTML 里。

顺带被消灭的:第三份副本 + 两个附带修正

demo 原来手抄了 text-2xl font-bold tracking-tight / text-sm text-muted-foreground / gap-4 pb-4 —— 与组件、文档 Styling 小节并列的第三份副本,而且已经漂了(抄的正是 #3786 修掉的 gap-4)。换成组件节点后这份副本没有了,同一组数值回到一处。另外两条附带:

  • 老 demo 每个 div 节点都在触发 The "div" component is deprecated 弃用警告,现在没有了。
  • 老 demo 的标题是 span,整个 demo 一个 h1 都没有 —— 这一页的 Accessibility 小节却承诺「Semantic HTML (h1 for title)」。现在真的渲染出 h1: ["Users"]

测试

新增 examples/schema-catalog/test/pageheader-with-actions.test.tsx(4 条)。分两类事实钉:形状(根节点是 page-header;JSON 里不再出现只属于 PageHeader.tsx 的 class 串 —— 有人再手搓就红)与渲染(走真 SchemaRenderer,断言 h1 标题、副标题、两个 children 都到右槽)。第 4 条钉住 #3786 那三条数值的代码侧:gap-3 / pb-4 / border-b 都在,且不含 sm|md|lg|xl:pb- 断点变体 —— class 一改就红,提示回来同步文档。钉代码侧不是单里推迟的那个「文档改成指向代码」的根治方案,只是普通组件 pin。

反向验证(方向事前判定为红):把手搓 div 版 JSON 放回去,4 条全红,且第 4 条是在正向断言上失败而不是空过 —— 这条尤其要看清,因为 page-header 根节点不存在时 className 取到空串,末尾那句 not.toMatch 本身是会空过的:

× is rooted at a `page-header` node
× declares title/subtitle on the component instead of restating its classes
× renders the real header: h1 title, subtitle, and both children in the action slot
× renders the container spacing the docs Styling section states (#3786)
AssertionError: expected 'div' to be 'page-header'
AssertionError: expected undefined to be 'Users'
AssertionError: expected '' to contain 'gap-3'
Test Files  1 failed (1)      Tests  4 failed (4)

examples/schema-catalog 的 tsconfig excludetest/(既有两个测试同此),所以仓库门禁不做这个文件的类型检查;另跑了一次定向 tsc --noEmit 通过。

门禁

  • pnpm exec vitest run examples/schema-catalog packages/layout --maxWorkers=29 files / 944 tests passed
  • pnpm exec turbo run type-check --concurrency=278/78 successful(apps/site 没有 type-check task,单独跑了 pnpm --filter @object-ui/site types:check,通过)
  • pnpm exec eslint(全部改动文件)→ 0 errors(LiveSplitDemo 的 2 条 set-state-in-effect warning 在 :153-156,既有代码,与本次 import 无关)
  • node scripts/check-doc-links.mjs → Links are valid across 7 scan roots
  • node scripts/check-control-bytes.mjs → OK;改动文件另做 grep -naP 控制字节自扫,零命中
  • node scripts/check-changeset-presence.mjsNo source of a released package changed in this range, so no changeset is owed(改动全部落在 content/docsexamples/schema-catalogapps/site,无 released 包的 src/)
  • 起过的 dev server(:5241)已按记下的 PID 收净,端口已释放;临时 probe 脚本已删

浏览器实证用仓库自带 Playwright + Next dev,截图与读数见上;完整 demo 外观:图标块 + 加粗 h1 标题 + 副标题 + 右对齐 Export / Add User + 底部 border-b

越界发现(均已另立单,本 PR 不碰)

协调

不碰 PageHeader.tsx 本体(两单都是文档/示例面);与在飞 #3852(cli)、#3838(plugin-detail)、#3837(app-shell/ai)零文件相交;#3789(description 退役,pm:blocked)未碰。


Generated by Claude Code

…ke its demo use the component (#3786, #3787)

#3786 — `content/docs/layout/page-header.mdx` Styling -> Container restated three
values the component does not have. Rewritten against
`packages/layout/src/PageHeader.tsx:210-211`: `pb-4` with no responsive variant
(the section promised `pb-8` on desktop), `gap-3` on the outer column with the
title row's own `gap-x-4 gap-y-2` spelled out (it said `gap-4`), and the
undocumented `border-b` added. Readers of this section are override authors using
the public `className` seam, so the old numbers produced wrong layouts.

#3787 — the page's only runnable example hand-rolled a header out of
`div`/`text`/`button` with Tailwind classes copied from `PageHeader.tsx`. It
taught authors to bypass the component, exercised none of `page-header`'s
rendering, and held a third (already drifted) copy of the spacing numbers. It is
now a real `page-header` node with `title`/`subtitle`/`icon` and the two buttons
as schema children, which deletes that third copy.

Rendering the node needed a registration the docs site never had: nothing pulled
in `@object-ui/layout`, so `page-header` produced the red "Unknown component
type" panel (OBJUI-001). Added as a site dependency plus
`registerLayoutBlocks.ts`, imported by all three hosts that render catalog
examples — `SchemaThumbnail` renders the whole catalog on
`/docs/guide/schema-catalog`, so a page-local loader would have left that index
broken for the same example. The registrar is called explicitly because
`@object-ui/layout` declares `sideEffects: false`.

Measured, not assumed: `children` do reach the right-hand slot even though the
registration has no `isContainer: true` (that flag is registry metadata the
render path never consults), while the first-class `actions` form renders
"Unknown component type: record:quick_actions" here because `record:quick_actions`
lives in `@object-ui/plugin-detail`, which the site also does not depend on.
Hence the children form.
@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 1:09am

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-DkbNpKCb.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) 482.39KB 106.34KB
core (index.js) 2.96KB 1.13KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 139.61KB 35.99KB
fields (index.js) 230.82KB 56.70KB
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.53KB 10.71KB
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) 44.98KB 12.37KB
plugin-charts (index.js) 61.04KB 17.31KB
plugin-chatbot (index.js) 180.33KB 42.79KB
plugin-dashboard (index.js) 117.21KB 30.27KB
plugin-designer (index.js) 210.51KB 42.51KB
plugin-detail (index.js) 236.49KB 58.96KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 112.10KB 27.10KB
plugin-gantt (index.js) 162.55KB 39.57KB
plugin-grid (index.js) 187.63KB 49.66KB
plugin-kanban (index.js) 48.30KB 13.28KB
plugin-list (index.js) 105.12KB 25.48KB
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) 19.28KB 6.38KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 1.02KB 0.55KB
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 session_01GTRjn8xBqp75dk7kFupVRt)—— #3786 + #3787 结对

实物核验:头 b70fbef0a,10 文件 +185/−45;trailer 0;与在飞 #3831 零相交。
CI 终态(独立复核):19 检查全部 completed,17 success + 2 skipped,零失败 —— Build Docs 绿是本单承重项(next build 实证新增 site 依赖可预渲染)。

裁定要点:

转 ready 并挂 auto-merge。越界 #3899(sideEffects vs 自动注册,两个语义相反修向归维护者)/#3900(isContainer 契约)/#3902(假断言第四份副本,<2 行)/#3903(缩略图嵌套 button hydration,先于本 PR)/#3904(Playground 第四宿主 + transpilePackages 漂移)归分诊席。


Generated by Claude Code

@yinlianghui
yinlianghui marked this pull request as ready for review August 9, 2026 01:20
@yinlianghui
yinlianghui added this pull request to the merge queue Aug 9, 2026
Merged via the queue into main with commit 50fa376 Aug 9, 2026
20 checks passed
@yinlianghui
yinlianghui deleted the claude/issue-3786-3787-pageheader-docs-demo branch August 9, 2026 01:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants