Repository navigation
docs(layout): the PageHeader page teaches the canonical page:header node (objectui#3906) - #11162
Conversation
…ode (objectui#3906) Phase 2 of the maintainer ruling on objectui#3906, direction (b): the page's author face becomes the canonical `page:header` node, rendered by `PageHeaderRenderer` in `@object-ui/components` and contracted by the spec's `PageHeaderProps`. - content/docs/layout/page-header.mdx: rewritten against the canonical renderer and PageHeaderProps; props, layouts, styling, accessibility and responsive behaviour re-measured. The `page-header` alias is named once, as an alias. - content/docs/guide/layout.md: the PageHeader section moves to the same key. - The catalog demo is re-keyed to `page:header` with its props in `properties`. It carries no actions or children: the canonical node draws no children, and its action ids resolve against a bound object that a docs demo does not have. Renamed to layout-page-header/basic-page-header accordingly; index regenerated. - The demo's test re-derives shape, render, contract and declaration pins for the canonical node; the alias-registration pins stay, on an inline fixture holding the former demo JSON. - page-actions-refusal-7926 LIT CONTROL now looks for the action-id channel where the guide authors it (page:header, properties.actions). Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec
The only file under a released package the change touches is a test in @object-ui/types; an empty-frontmatter changeset declares it. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec
…er demo (objectui#3906) The PageHeader docs demo no longer authors two `button` children: the canonical `page:header` node draws none and the contract refuses the key on it. The dom-leak census counts catalog button nodes, so it moves 118 -> 116, the catalog-authored case its header sanctions. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec
…(objectui#3906) A `page` node defaults to `pageType: record`, whose header always owns the heading, so the example could not show the delegation it describes. With `pageType: app` the titled `page:header` is what drops the page's own `<h1>` (measured: one `<h1>` with the header, the page's own without it). Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec
|
changeset-claim-re-read
|
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract reviewServed-tier: Inputs: card #3906 (body and all 9 comments, including ruling ① Derived judgmentsAccept set and public surface: unchanged — right. No schema, prop, type, export or registration moves. The The rewritten page and the guide's section, claim by claim against the head. Every number and claim is borne out:
Demo shape and rename. Re-derived pins. SHAPE (root Nothing copied. The old page's numbers ( ② Semver level
③ Boundary flags
Deviations, each:
Out-of-scope notes, each checked at the head:
Also for the seat, not a finding: the PR is still Implemented-by: VERDICT: PASS Generated by Claude Code |
Fixes #3906
Clause-②: no
Phase 2 of the maintainer ruling on objectui#3906 (comment 5230005161, direction (b)): the PageHeader docs page's author face becomes the canonical
page:headernode. Phase 1 (the truth banner) landed as PR #5922; this is the last phase.What changed
content/docs/layout/page-header.mdx, rewritten. Every example, the property table, both layouts, the styling numbers, accessibility and responsive behaviour are measured againstPageHeaderRendererin@object-ui/componentsand the spec'sPageHeaderProps(theComponentPropsMap['page:header']row). No number was carried over from the old page or from@object-ui/layout'sPageHeader. The section "Gating a header action on a relation field" was already canonical-true and is kept as it was. The Phase-1 banner gives way to the rewritten page, and thepage-header/layout:page-headeralias is named once, as an alias, in its own short section.content/docs/guide/layout.md, the PageHeader section only, moved to the same key. The hunk near the page node's buttonlabelthat draft PR feat(cli):objectui validateandobjectui checkjudge through the strict authoring face (objectui#5250, slice A) #11069 edits is untouched.page:header, with its props inproperties, and renamedlayout-page-header/pageheader-with-actionstolayout-page-header/basic-page-header(index regenerated withscripts/regenerate-catalog-index.py, which moved only this entry).SchemaRendererrender, the contract, and the registration's declared inputs. The objectui#3787 hand-rolled-copy guard stays, now checked against the canonical renderer's classes..changeset/3906-page-header-canonical-docs.md, declares that nothing is released.What the demo became, and why (the dispatch asked me to say which)
The old demo was
page-headerwithiconand twobuttonchildren. I measured the canonical node, and neither shape carries over:children. A real render leaves the buttons out. objectui's mirror refuses the key on this node (children: invalid_type, objectui#9256), and the manifest reportsnot-a-container.iconis refused by name. It is an ADR-0087 D2 tombstone onPageHeaderProps.actionscan't stand in for the buttons. It holds action IDS, and those resolve against the object bound to the page. A docs demo has no bound object, so ids render nothing and the renderer warns once per id. Inline action objects are refused by the contract (actions.0: expected string).So the demo shows only title and subtitle, which is the shape the canonical node accepts and can render here. That is why it no longer carries "with actions" in its id. The page explains why the live demo has no actions and links to Slotted pages for the header action row.
Why
properties, measuredtitle/subtitlewritten besidetype(unrecognized_keys).safeValidateSchemajudgespage:headerprops only insideproperties. Written besidetype, evensubTitleoricongoes through unchecked.SchemaRenderermovesproperties.*up onto the node before the renderer reads it, so both spellings render. Only thepropertiesspelling is validated, so it is the one the page teaches.Beyond the claim's file surface (declared, not silent)
The claim lists four paths. Three more files had to move, because my change turned two tests outside that list red:
packages/types/src/__tests__/page-actions-refusal-7926.test.ts: its LIT CONTROL scansguide/layout.mdfor apage-headerJSON fence withactions. Its own comment says it is really looking for the action-id channel ofpage:header. It now looks forpage:headerwithproperties.actions. This is the same defect class as this card (the alias taught as the header's key), in a test that reads the guide.examples/schema-catalog/test/form-control-dom-leak-5632.test.tsx:NODE_CENSUS.buttongoes from 118 to 116, because the demo's two buttons left the catalog. This is the catalog-authored case the table's header permits, recorded in the table's own format..changeset/3906-page-header-canonical-docs.md:check-changeset-presencecounts any file under a released package'ssrc/, and the types test is one.Tests
Final union at
c79ae7497, run after the last commit:pnpm exec vitest runoverexamples/schema-catalog/, every test themarkdown-test-inputsledger lists as readingcontent/docs/guide/layout.mdorcontent/docs/**, the catalog-corpus walkers, and the threepackages/layoutpage-header tests:Test Files 66 passed (66),Tests 3136 passed (3136).pnpm --filter @object-ui/example-schema-catalog type-checkandpnpm --filter @object-ui/types type-checkboth exit 0.--listFilesOnlyconfirms all three changed tests are in those programs.check-doc-snippet-types: exit 0, "Every covered documentation snippet compiles against the built types."check-doc-example-types: exit 0.check-doc-component-types: exit 0, "Every documented component type is registered."check-doc-fence-languages,check-doc-example-ids("414 real reference(s) all resolve"),check-doc-example-shared-readerandcheck-doc-links("Links are valid across 17 scan roots."): all exit 0.check-control-bytesandcheck-new-cross-file-line-citations("0 new citation(s)"): exit 0.check-changeset-presence(empty frontmatter, "a complete answer to this gate"),check-changeset-no-major,check-changeset-claims,check-pending-changeset-literals,check-changeset-fixed: all exit 0.regenerate-catalog-index.py --check: exit 0.eslint --no-inline-configon the four changed code files: 0 errors, 0 warnings. The.md,.mdx,.jsonand changeset files match no eslint configuration, so they are not linted. The config has no type-aware linting (noprojectServiceorparserOptions), so this diff cannot change the verdict on any untouched file.ablation-replace.mjs:"type": "page:header"back to"type": "page-header". The anchor went from 1 hit to 0 and the blob changed.pageheader-with-actions.test.tsxturned red: 7 failed, 16 passed. The failures were the shape, render, styling and mirror pins, plus three controls built on the demo.git diff HEADis empty.typeand stayed green: the spec row's full parse and the declared-inputs check.Acceptance notes (observations, not filed)
iconinput. Two comments give "the docs page publishes the prop, and the docs page's only live demo writesicon: users" as reasons to keep that input: one inpackages/layout/src/index.ts, one inpackages/layout/src/__tests__/page-header-authorable-keys.test.tsx. After this PR both clauses are false. The objectui#3829 ruling itself still holds, because the alias renderer really draws the icon. The same test file also says the manifest pins run on "the real demo JSON"; they now run on an inline fixture that is that former demo JSON word for word. Nothing reads these comments. Carrier: whoever next touches the alias registration.doc-version-claimsrow forguide/layout.mdquotes the icon tombstone as "(feat(detail): derived related lists consume the FK's relatedListFilter — AND-composed query, badge counts the same set #6946, ADR-0087 D2)". The guide now quotes the installed spec's message exactly, "(ADR-0087 D2)". The ledger's claim key,@objectstack/spec 17.0.0, still matches, so the test is green; only the row's reason text is out of date.sdui-parser'svalidateTreehas no concept of thepropertiesbag, so apage:headernode spelled the spec's way getsunknown-prop "properties"there. The JSX tier writes attributes, so no author hits this today. The demo test validates the node in its post-move shape and says why.safeValidateSchemarefusespage-headerandlayout:page-headerattype(invalid_union), exactly as it refuses an unknown type, so the old demo never validated there. Retiring the alias is not this card.Out-of-scope findings (for the seat to file; not filed here)
content/docs/guide/slotted-pages.md, "Example: customize only the header", writeseyebrow: 'ACCOUNT'andicon: 'building-2'underpage:headerproperties. objectui'ssafeValidateSchemarejects that exact node:properties.icon: invalid_typeandproperties: unrecognized_keys [eyebrow]. The same node without those two keys validates.PageHeaderProps.breadcrumbis described as "Show breadcrumb" and defaults totrue.PageHeaderRendererrenders only an empty element markeddata-page-breadcrumb-slotand draws no trail. Measured through a realSchemaRendererrender, in both the bare and record layouts.Session:
https://claude.ai/code/session_011p7ikEivgXefNDaE5S5UecGenerated by Claude Code