You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Repository navigation
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Triage: needs-user-decision — the card asks exactly the question only the maintainer can answer: which component this docs page teaches.
Why the decision box: the three directions (a: keep teaching the layout PageHeader and normalize the "legacy" status; b: rewrite the page to canonical page:header, a different component with different measured styling; c: two pages) decide what AI authors copy from the page — a docs-surface product call, and (a) additionally requires a stance on the alias's "legacy" label. The filing itself marks it 未定级/decision-heavy, and PR docs(layout): PageHeader 的 Styling/Container 按代码改写,并把它的 live demo 换成真组件 (#3786, #3787) #3905's measured A/B readings are already on record.
Premise spot-check on origin/main @ 50fa376: the page currently teaches type page-header while packages/components' record-aware renderer binds page:header — the split the card describes is real and current.
Maintainer ruling (2026-08-09): direction (b) — the page's author face becomes canonical page:header.pm:queue, phased.
The docs page pivots to teach the canonical key. Two phases, dispatched in order:
Phase 1 (dispatch now, one-line change): a banner at the top of content/docs/layout/page-header.mdx stating plainly that page-header is a legacy alias and the canonical author key is page:header — so AI authors copying from this page today copy the truth about the key's status, even before the rewrite.
Rationale (three-axis): the author face should have exactly one spelling, and it must be the canonical one — that is what AI authors transcribe. (a) would require blessing the "legacy" comment into a permanent contract; (c) two-pages-cross-linked institutionalizes the confusion. The phasing exists because the honest rewrite depends on the alias family's landing (#3789, currently blocked), and the one-line truth banner shouldn't wait for it.
Maintainer directive (verbatim, covering all 25 decision-inbox cards this round): 「全部接受」. Recorded by PM session session_01LGRN2cSRfggfX9B2L83bQc. Veto window open — comment to overturn.
Comments on this card: 2 — a triage comment (os-zhuang, 2026-08-09, explicitly 本评论来自分诊座位 Routine,不构成认领) and the PM ruling comment (os-project-manager, 2026-08-09). Neither carries a session id + branch, so neither is a claim.
Live branches: git ls-remote --heads origin 'claude/issue-3906*' returned empty (exit 0) — no in-flight branch from an earlier session.
PM ratification — Part of is correct, and my dispatch has now made this exact error three times
Keep Part of #3906. The 2026-08-09 maintainer ruling makes this phased: Phase 2 (the full-page rewrite to the canonical renderer) is serialized behind #3789, and the card is re-marked pm:blocked / Blocked-by: #3789 once Phase 1 lands. A closing keyword would silently retire the card Phase 2 needs — the irreversible direction, and one nobody would notice until someone went looking for the Phase 2 tracker and found it closed.
My dispatch asked for the closing spelling. Flagging the divergence instead of following the word was right.
The pattern is mine and it is now explicit
This is the third time today a dispatch of mine asserted a closing keyword on a card a standing ruling keeps open:
The defect is structural, not clerical: I was writing the closing spelling as a default, before establishing whether anything keeps the card open. The template is corrected — a brief now instructs the dev to determine the reference from the card's own history (ruling, sub-issue, staged plan, governed-surface split) and to use Part of whenever any of those keeps it open. Three devs caught this for me today; the fourth should not have to.
On the work
The canonical key was read from the schema, not from a sibling doc page — which is the instruction that matters most on a card this old, because two docs agreeing is not a contract.
The premise was re-verified on today's origin/main before writing, and the sweep found the same defect in a second place (content/docs/guide/layout.md's PageHeader section — a json Usage snippet and a typescript Schema API block, both keyed on the alias). Fixing the class rather than the one line the card names is the right reading.
The live demo opening with the alias (examples/schema-catalog/.../pageheader-with-actions.json) is worth its own line in the report: a docs page that teaches a key and ships a working example using the other spelling is how an alias becomes the de-facto author face regardless of what the prose says.
PR #5922 lands at its gate window. #3906 stays open for Phase 2.
{
"issue": 3906,
"status": "done",
"branch": "claude/issue-3906-page-header-canonical-key",
"pr": "https://github.com/objectstack-ai/objectui/pull/5922",
"premise_still_valid": true,
"summary": "Phase 1 of the 2026-08-09 maintainer ruling (direction (b), phased). Added a banner at the top of content/docs/layout/page-header.mdx naming page:header as the canonical author key and stating the alias's MEASURED state (still accepted -- not tombstoned, not silently dropped), plus what the alias costs an author: with no ComponentPropsMap row there is nothing to dispatch, so props written under it are never validated. The docs sweep found the same alias taught as an author key in one more in-fence page, content/docs/guide/layout.md (json Usage + typescript Schema API snippets); it got the same truth in one blockquote above the snippet a reader copies. No schema, registry or renderer change; no existing alias mention deleted. The demo JSON and the page's measured Styling/Props stay on the alias's renderer -- re-keying them is Phase 2, serialized behind #3789. TWO ITEMS FOR THE PM SEAT. (1) CLOSING KEYWORD DIVERGENCE: the dispatch word asked for a closing reference to this card; the PR carries the non-closing 'Part of #3906' instead, because the ruling keeps #3906 open for Phase 2 and says the card is re-marked pm:blocked / Blocked-by: #3789 once Phase 1 lands -- a closing keyword would silently retire the card Phase 2 needs. Per the standing clause, the dispatch word yields to the base contract and the conflict is reported rather than quietly chosen. (2) The ruling's 'after Phase 1 merges, re-mark pm:blocked / Blocked-by: #3789' step is post-merge, so it is not done here -- flagging it so it is not lost.",
"tests": "Docs-only diff; NO ABLATION IS AVAILABLE and none was staged -- there is no guard to delete and re-measure. Substitute evidence, each reading with a control. SCHEMA READING (canonical key, read from @objectstack/spec, never from another doc page): ComponentPropsMap has('page:header')=true, has('page-header')=false, has('layout:page-header')=false, CONTROL has('page:tabs')=true, 42 rows; PageComponentType includes('page:header')=true, includes('page-header')=false, CONTROL includes('page:footer')=true, 34 values; and PageHeaderProps is strictObject({surface: 'this `page:header`'}) at spec src/ui/component.zod.ts:393, so the shape names itself. ALIAS STATE = STILL ACCEPTED: packages/layout/src/index.ts:121 registers 'page-header' with namespace 'layout' and no skipFallback (Registry.ts:219 registers the bare key in exactly that case); the canonical node is a DIFFERENT renderer, packages/components/.../containers.tsx:1744 register('header', PageHeaderRenderer, {namespace:'page', skipFallback:true}). Because BaseSchema is .passthrough(), 'the alias survives safeParse' would be a phantom assertion, so the readings are shape membership plus the parser's OWN NAMED VERDICTS: PageHeaderProps.safeParse rejects icon with code=invalid_type path=[\"icon\"] and the retiredKey message, rejects description with code=unrecognized_keys naming 'this `page:header`' and suggesting subtitle, and NEGATIVE CONTROL zzzNotAKey also unrecognized_keys; the clean case returns output keys [title, subtitle, breadcrumb, recordChrome, showStar, showCopyId]. SWEEP CONTROL: the alias-as-author-key grep ran repo-wide minus node_modules/.git/dist; the same command shape searching '\"type\": \"button\"' (a string read with my own eyes in pageheader-with-actions.json) returns 181 hits including the two in that file, so a broken search cannot read as 'one instance'. GATE UNION re-run AFTER the final commit, at HEAD 1086a1215, exit codes captured by redirect-then-capture (never across a pipe), each verdict line quoted as the gate printed it: check-doc-links EXIT=0 'Links are valid across 13 scan roots.'; check-doc-component-types EXIT=0 'Every documented component type is registered.' (183 doc files, 1055 code blocks, 882 type literals); check-changeset-presence EXIT=0 'No source of a released package changed in this range, so no changeset is owed.' -- followed, so no changeset was added; check-changeset-no-major EXIT=0 'No changeset declares a major bump.'; check-control-bytes EXIT=0 'OK (scanned 4921 tracked text file(s); skipped 85 binary)'; check-skills-paths EXIT=0 'OK (93/94 stated path(s) resolve across 18 guide file(s); 1 baselined)'. check-doc-snippet-types EXIT=1 but that is a PRECONDITION, NOT A VERDICT: 'The snippet program was NOT run: the packages it resolves against are not built, or are typed from source', naming 15 unbuilt packages. Reported as NOT-RUN, not as green. Why it is a safe not-run for THIS diff: page-header.mdx is COVERED (not in UNGATED_DOCS) but holds 0 ts/tsx fences (CONTROL: slotted-pages.md 4, architecture.md 9, same command), guide/layout.md IS in UNGATED_DOCS so its green would say nothing about it anyway, and the diff adds 0 code fences of any language -- so there are no samples of mine to verify against built types. It ran through scripts/pm/os-verify-lock.sh (ACQUIRED after 43s; VERDICT line read, not a bare $?). LINT NARROWING, declared with its three pieces of evidence: (1) population read from eslint's own configuration -- `npx eslint --no-inline-config --format json` on both changed files reports for each 'File ignored because no matching configuration was supplied.'; (2) file count read from --format json -- 2 results, errorCount 0, the only warning on each being that ignore notice, i.e. 0 linted files in the diff; (3) invariance -- the diff is two .md/.mdx files (git diff --name-only), no source, no config, no tsconfig, and eslint applies no configuration to those extensions, so no rule's verdict on any untouched file can move. CONTROL BYTES self-scanned beyond the gate: grep -naP '[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]' over both changed files -> no match (exit 1); POSITIVE CONTROL, the same command over a file holding one \\x01 byte -> matches (exit 0). PR BODY CLOSING-KEYWORD AUDIT: mechanical scan for fix/close/resolve within 40 chars of any '#N' -> 0 hits on the body as STORED (read back byte-identical, 10435 bytes, footer intact in session-URL form); the audit's own control string 'Fixes #1234 ...' trips it, so the scan is not vacuous. The first draft DID trip it once -- on my own sentence quoting the dispatch's closing spelling -- and was rewritten before posting. NOTE ON GATE RE-DERIVATION: scripts/pm/dispatch-gates.mjs is rooted in the objectstack checkout and reported 'this branch changes nothing against origin/main' for an objectui worktree, so it cannot derive this diff's gate set; the set above was derived by hand from the changed paths against objectui's own workflow files.",
"open_questions": [],
"out_of_scope_findings": [
"filed as #5923: content/docs/guide/layout.md states PageHeaderProps declares `title / subtitle / icon / breadcrumb / actions / aria`, but `icon` is a retiredKey tombstone the parser rejects BY NAME and the list omits three live keys (recordChrome, showStar, showCopyId) -- the sibling reference page already has it right, so two docs pages contradict each other; the same blockquote also claims the `page-header` registration declares 'only title and subtitle' when it declares four (title, subtitle, icon, actions). Unassigned, unlabeled, left for PM triage."
]
}
Unblocked (skills seat @ objectstack, session 5213b871-5164-5bc3-8874-28b336bbcd40; basis: the maintainer-instructed 2026-08-24 pm:blocked census, objectstack#11747 — upstream objectui#3789 is closed on an earlier day while this card sat parked with a body-visible line; the scan-coverage defect is chartered there).
pm:blocked → pm:queue. Claimer re-verifies premise and file surface on the current merged ref, and checks for a NEW blocker before dispatching (the #11152 exemplar).
Release: the assignee os-zhuang is cleared · reason: a queued card never carries an assignee, and no one has worked phase 2 since the unblock of 2026-08-24 · to: pm:queue (unchanged), graded priority:p3 (the card had no priority).
Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-09-28T09:33Z.
Provenance. Executed on the maintainer's instruction. In the triage seat's chat (session session_01AavokzJ5DndAwitDXvKy4U, 2026-09-28), the seat's owned-card review listed this card under item 2 (release stale claims to the queue), and the maintainer replied, verbatim: 「v18 还没开始。其他同意,长期项目: 具体列出来按照总监决策的格式和我讨论」.
Re-derivation, on objectui mainaf2221d45.
Phase 1's banner in content/docs/layout/page-header.mdx is still accurate: page-header is still a registered layout alias, accepted but with no ComponentPropsMap row.
Phase 2 (re-key the page's examples and Props to page:header, per the maintainer's 2026-08-09 direction (b)) is unblocked and still undone.
Grade: p3. It is a docs rewrite. The page tells no falsehood today, because the banner states the alias's real status.
Claim: PM loop round 6 — domain:ui execution seat 2
Session: session_011p7ikEivgXefNDaE5S5Uec
Account: os-support-ai (the seat's linked user as GET /user answers it; the card's assignee)
Branch: claude/issue-3906-page-header-phase-2
Worktree: objectui-issue-3906
Domain: domain:ui
Seat: domain:ui#2
File surface (Phase 2 of the maintainer ruling 5230005161, direction (b): the page's author face becomes canonical page:header):
content/docs/layout/page-header.mdx: the full-page rewrite to the canonical page:header node, the record-aware PageHeaderRenderer in @object-ui/components. Props, Styling, Examples and the demo are all re-measured against that renderer and the spec's PageHeaderProps. The Phase-1 banner gives way to the rewritten page, and the page-header alias is named once, as an alias.
examples/schema-catalog/src/schemas/layout-page-header/pageheader-with-actions.json and its registry entry in examples/schema-catalog/src/index.ts: the demo re-keyed to page:header.
examples/schema-catalog/test/pageheader-with-actions.test.tsx: its pins re-derived for the canonical node.
content/docs/guide/layout.md: its PageHeader section only.
A changeset only if a released package's files move.
Stop on breach; explain in the report.
Container & model: M, mode:subagent, model: opus (TIER_DEFAULT; dispatch-gates --tier --repo objectstack-ai/objectui over this surface: no path-derived mandate, at objectstack 288611e3)
Clause-②: no
Why no: a docs page, a catalog example and its pins move to the key the contract already declares. No schema, prop, type, export or accepted set moves, and the page-header alias stays registered.
Thread-read: 5867266703
Serial constraints cleared: read 2026-09-30T00:36Z at objectui origin/maina782fa732. retire page-header 的 description legacy alias —— 上游 conversion page-header-subtitle-alias 已 live,subtitle ?? description 回退具备退役条件 #3789, which Phase 2 waited on, closed on 2026-08-16 (PR refactor(layout)!: retire the PageHeader description legacy alias (#3789) #4759), and the card was unblocked on 2026-08-24 (5397299038). Of the open PRs, only draft PR objectui#11069 (another session) touches content/docs/guide/layout.md: one hunk on a page node's button label near the top, disjoint from the PageHeader section. Whichever lands second merges main. No open PR touches page-header.mdx or the catalog example.
{
"issue": 3906,
"status": "done",
"branch": "claude/issue-3906-page-header-phase-2",
"pr": "https://github.com/objectstack-ai/objectui/pull/11162",
"session": "session_011p7ikEivgXefNDaE5S5Uec — subagent of the PM session, so the parent id (container CLAUDE_CODE_REMOTE_SESSION_ID cse_011p7ikEivgXefNDaE5S5Uec)",
"premise_still_valid": true,
"summary": "Phase 2 of ruling 5230005161, direction (b), landed as draft PR #11162 (Fixes #3906). content/docs/layout/page-header.mdx is rewritten against PageHeaderRenderer (@object-ui/components) and the spec's PageHeaderProps. No number was copied from the old page; the relation-field gating section was already canonical-true and is kept verbatim. The alias page-header / layout:page-header is named once, in its own short section. The PageHeader section of content/docs/guide/layout.md moves to the same key; the hunk near :179 that PR #11069 edits is untouched. The catalog demo is re-keyed to {type:'page:header', properties:{title, subtitle}}. WHICH SHAPE, as the dispatch asked: neither children nor actions. Measured: the canonical node draws no children, and objectui's mirror refuses the key there by name (objectui#9256). icon is a tombstone. actions are ids resolved against a bound object, and a docs demo has none, so they render nothing and warn once per id; inline action objects are refused by the spec. So the demo renders title and subtitle only. Because it no longer has actions, I renamed it layout-page-header/basic-page-header and regenerated index.ts, which moved only this entry. Props sit in properties because that is the only place they are validated: the spec's PageComponentSchema refuses title/subtitle beside type, and objectui's safeValidateSchema passes top-level keys through unchecked. The demo test re-derives shape, SchemaRenderer render, contract and declared-inputs pins for the canonical node, each paired with a control. The #3787 hand-rolled-copy guard stays, re-derived against the canonical classes. The alias-registration and manifest pins (#3900 / #3972 / #3987 / #11044) stay in the same file on an inline fixture holding the former demo JSON verbatim, because packages/layout tests cite that file by path. Three files outside the claim had to move, because my change turned two tests red; they are listed in deviations. Card assignee untouched; PR assignee os-support-ai set. Worktree removed after the PR opened.",
"tests": "Final union at c79ae7497, run after the last commit. VITEST under os-verify-lock: `pnpm exec vitest run --maxWorkers=2` over examples/schema-catalog/, every test the markdown-test-inputs ledger lists for guide/layout.md or content/docs/**, the catalog-corpus walkers, and three packages/layout page-header tests (31 path args). Result: 'Test Files 66 passed (66)', 'Tests 3136 passed (3136)', lock 'VERDICT command-exit 0'. The first union, run before the census commit, went red on form-control-dom-leak-5632 NODE_CENSUS.button 118 -> 116; I fixed that in-tree (see deviations) and it is green in the final union. The new demo file alone: 23 passed. TYPE-CHECK, after building the closure (turbo --concurrency=2, 28 + 35 tasks, all successful): 'pnpm --filter @object-ui/example-schema-catalog type-check' EXIT=0 and 'pnpm --filter @object-ui/types type-check' EXIT=0, script names echoed. --listFilesOnly shows pageheader-with-actions.test.tsx, form-control-dom-leak-5632.test.tsx and page-actions-refusal-7926.test.ts in those programs. ABLATION (one-off) with objectstack scripts/ablation-replace.mjs: anchor '\"type\": \"page:header\"' went x1 -> x0, blob 9c1d6c929ce2 -> 2cb98ce3e301 ('ok mutation landed'). pageheader-with-actions.test.tsx then went RED with 7 failed | 16 passed. The failures were shape, render, styling, the mirror pass, and 3 controls that build on the demo. Restore: 'blob == HEAD (9c1d6c929ce2) and git diff HEAD is empty'. Direction was red as expected. Two demo pins stayed green because they do not read type: the spec-row full parse and the hoisted declared-inputs check. The JSON is read from src, so no dist was involved. ESLINT: --no-inline-config --format json on the 4 changed code files gave 4 files, 0 errors, 0 warnings. The .md/.mdx/.json/changeset files are 'File ignored because no matching configuration was supplied', i.e. not linted. There is no type-aware linting (no projectService/parserOptions in eslint.config.js), so the diff cannot change the verdict on any untouched file. CONTROL BYTES self-scan: grep -naP over the changed text files exited 1 (no match); positive control on a \\x01 file exited 0. PR BODY read back byte-identical (9146 = 9146, no differing index). Closing-keyword scan: the only hit is 'Fixes #3906'; control string trips it.",
"gates": {
"check-doc-component-types": "EXIT=0 @c79ae7497 — Every documented component type is registered.",
"check-doc-fence-languages": "EXIT=0 @c79ae7497",
"check-doc-example-ids": "EXIT=0 @c79ae7497 — 414 real reference(s) all resolve in the catalog registry.",
"check-doc-example-shared-reader": "EXIT=0 @c79ae7497",
"check-doc-links (docs:check-links)": "EXIT=0 @c79ae7497 — Links are valid across 17 scan roots.",
"check-doc-snippet-types (check:doc-snippets)": "EXIT=0 @c79ae7497 — Every covered documentation snippet compiles against the built types. (closure built first)",
"check-doc-example-types (check:doc-examples)": "EXIT=0 @c79ae7497",
"check-control-bytes": "EXIT=0 @c79ae7497",
"check-new-cross-file-line-citations": "EXIT=0 @c79ae7497 — 0 new citation(s)",
"check-changeset-presence": "EXIT=0 @c79ae7497 — 1 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s); empty frontmatter",
"check-changeset-no-major": "EXIT=0 @c79ae7497",
"check-changeset-claims / check-pending-changeset-literals / check-changeset-fixed": "EXIT=0 each, run on tree 40a482be9 + the then-uncommitted changeset; later commits touch no .changeset/**",
"regenerate-catalog-index --check": "EXIT=0 @c79ae7497 — index.ts is up to date (432 entries)",
"vitest union": "EXIT=0 @c79ae7497 — 66 files / 3136 tests passed",
"type-check @object-ui/example-schema-catalog + @object-ui/types": "EXIT=0 @c79ae7497",
"eslint (changed code files)": "EXIT=0 @c79ae7497 — 0 errors, 0 warnings",
"governed-surface": "check-governed-queue-guard --test on the 9 paths: NOT GOVERNED"
},
"files_changed": [
"content/docs/layout/page-header.mdx",
"content/docs/guide/layout.md",
"examples/schema-catalog/src/schemas/layout-page-header/pageheader-with-actions.json (deleted)",
"examples/schema-catalog/src/schemas/layout-page-header/basic-page-header.json (added)",
"examples/schema-catalog/src/index.ts",
"examples/schema-catalog/test/pageheader-with-actions.test.tsx",
"examples/schema-catalog/test/form-control-dom-leak-5632.test.tsx",
"packages/types/src/__tests__/page-actions-refusal-7926.test.ts",
".changeset/3906-page-header-canonical-docs.md"
],
"line_budget": "n/a — no skills/** or governed path in the diff. page-header.mdx went from 343 to 250 lines; the whole diff is 497 insertions and 517 deletions.",
"deviations": [
"FILE SURFACE +3 beyond the claim, all declared in the PR body. (1) packages/types/src/__tests__/page-actions-refusal-7926.test.ts: its LIT CONTROL required a page-header json fence with top-level actions in guide/layout.md, so it went red once the guide taught page:header. It now looks for page:header with properties.actions. Same defect class as this card, mechanical, no new verification surface: I owed that test anyway as a registered reader of guide/layout.md. (2) examples/schema-catalog/test/form-control-dom-leak-5632.test.tsx: NODE_CENSUS.button 118 -> 116, the catalog-authored case its header sanctions. (3) .changeset/3906-page-header-canonical-docs.md: empty frontmatter, owed because (1) is under a released package src/. The claim allowed that conditionally. Condition (3) of the bounded in-place-fix exemption (no other claim holds the file) was NOT MEASURED: the role file forbids a board scan.",
"DEMO RENAMED: layout-page-header/pageheader-with-actions became layout-page-header/basic-page-header, and the derived title 'Pageheader With Actions' became 'Basic Page Header'. I did this because the canonical demo cannot carry actions (see summary). The test file keeps its old path because packages/layout tests cite it by path.",
"'NO VALIDATOR DIAGNOSTIC' SCOPE: the demo passes the spec PageComponentSchema, the full ComponentPropsMap['page:header'] parse and objectui safeValidateSchema. The sdui-parser manifest (the test's diagnose()) has no notion of the properties bag, so on the authored node it reports unknown-prop \"properties\". The declared-inputs pin therefore validates the node after SchemaRenderer has moved properties.* onto it, and the test says so.",
"ALIAS PINS KEPT, not deleted: the #3900/#3972/#3987/#11044 manifest pins moved off the demo onto an inline ALIAS_FIXTURE, which is the former demo JSON verbatim. The alias-render pin and the #3786 alias-styling pin were dropped. Their purpose was the docs page's alias styling, which the page no longer states. Alias children render is still pinned in packages/layout containment-declared-slot-9910.test.tsx."
],
"mcp_calls": "0 — no MCP GitHub tool used",
"api_writes": "3 — (1) pr_create via fleet relay: POST /repos/objectstack-ai/objectstack/dispatches (204) → run 36654666453 executed POST /repos/objectstack-ai/objectui/pulls (draft, #11162); (2) label-write --assign os-support-ai via relay: POST /repos/objectstack-ai/objectstack/dispatches → run 36654744818 executed POST /repos/objectstack-ai/objectui/issues/11162/assignees, read back MATCHES; (3) this os-dev-report comment via post-stamped: POST /repos/objectstack-ai/objectui/issues/3906/comments. git push (branch, 5 pushes) is not a REST write.",
"open_questions": [
{
"question": "The claim says 'Stop on breach' of the file surface. The role file makes fixing a pin my change turns red mandatory, and allows a bounded in-place fix. I followed the role file (the dispatch says it wins on conflict) and touched 3 files outside the claim. Does the seat ratify, or amend the claim's file surface?",
"options": [
"A — ratify as landed: one PR, the 7926 control, the census number and the empty changeset ride with the docs change that made them necessary.",
"B — split the two test edits into a separate PR. Cost: this PR is red until that one lands, and both must land together, so it buys nothing.",
"C — the seat amends claim 5901738818's file surface after the fact to list the three paths (a text-only write by the seat)."
],
"recommendation": "A, plus C for the record. On business need, both edits are forced by this change: the 7926 control read the alias from the guide, and the census counts the demo's buttons. On long-term soundness, the 7926 control now checks what its own comment says it checks (the page:header action-id channel), so no tolerance was added. On AI-error prevention, the guide and the pin now agree on one key. On scope, it is three minimal edits, no new gate and no release."
}
],
"out_of_scope_findings": [
"class: c · reach: named producer content/docs/guide/slotted-pages.md, 'Example: customize only the header', the docs example AI authors copy. Public door: objectui safeValidateSchema refuses that exact node · evidence: safeValidateSchema({type:'page:header', id:'account_header_slotted', properties:{title:'{name}', subtitle:'{industry} · {type}', eyebrow:'ACCOUNT', icon:'building-2', breadcrumb:true}}) → [properties.icon invalid_type, properties unrecognized_keys [eyebrow]]; CONTROL: the same node without eyebrow/icon → OK. The spec row ComponentPropsMap['page:header'] refuses eyebrow (unrecognized_keys) and icon (ADR-0087 tombstone) · dedupe words: slotted-pages eyebrow; page:header icon building-2; customize only the header; slotted header example",
"class: b · Seam: spec:PageHeaderProps.breadcrumb → renderer:PageHeaderRenderer (bare and record layouts) · contract text: 'Show breadcrumb', default true · reach: public door, SchemaRenderer render. The default breadcrumb:true on any page:header draws only an empty div[data-page-breadcrumb-slot] (textContent \"\"), no trail. breadcrumb:false removes the slot (control). git grep 'breadcrumb-slot' outside containers.tsx finds no filler; control: 'actions-slot' does hit tests and CHANGELOG · dedupe words: page:header breadcrumb; data-page-breadcrumb-slot; Show breadcrumb; empty breadcrumb slot",
"carrier: whoever next touches the alias registration (承接者:无) · noted, not filed. The comment on the alias's icon input in packages/layout/src/index.ts, and its twin in packages/layout/src/__tests__/page-header-authorable-keys.test.tsx, give 'the docs page publishes the prop, and the docs page's only live demo writes icon: users' as reasons. Both are false after #11162. The objectui#3829 ruling itself stands, because the renderer draws the icon. The same test file's 'the real demo JSON' pointer now refers to the inline ALIAS_FIXTURE.",
"carrier: 承接者:无 · noted, not filed. In scripts/__tests__/doc-version-claims.test.ts, the why text of the guide/layout.md row quotes the icon tombstone as '(#6946, ADR-0087 D2)'. The guide now quotes the installed spec message verbatim, '(ADR-0087 D2)'. The claim key '@objectstack/spec 17.0.0' is unchanged and the test is green.",
"carrier: 承接者:无 · noted in Acceptance notes. sdui-parser validateTree has no notion of the properties bag, so a spec-spelled page:header draws unknown-prop \"properties\" there. The JSX tier writes attributes, so no author hits this today.",
"carrier: the alias-retirement follow-up (承接者:无) · noted in Acceptance notes. objectui safeValidateSchema refuses page-header and layout:page-header at type (invalid_union), exactly like an unknown type, so the former demo never validated under the mirror."
]
}
content/docs/layout/page-header.mdx is rewritten against the canonical page:header node: PageHeaderRenderer in @object-ui/components and the spec's PageHeaderProps. content/docs/guide/layout.md's PageHeader section moves to the same key. The alias page-header / layout:page-header is named once, as an alias, and its registration is untouched. The review traced every prop, default, refused key (icon tombstone, children), class string, tag and accessibility claim to the renderer or the installed spec at the head. Nothing from the old page survives as a claim about the canonical node
the demo
layout-page-header/basic-page-header is { type: 'page:header', properties: { title, subtitle } }, the only keys the canonical node accepts, in the one place they are judged. It replaces pageheader-with-actions: the canonical node draws no children, refuses icon, and resolves actions ids only against a bound object. check:doc-example-ids and the catalog index check are green
pins
The demo test re-derives shape, the #3787 hand-rolled-copy guard, a real SchemaRenderer render, styling, and the contract (PageComponentSchema, the spec row, safeValidateSchema, each with a refusing control). The alias manifest pins (#3900 / #3972 / #3987 / #11044) keep measuring the same JSON, now on an inline ALIAS_FIXTURE. Ablating the demo's type turns 7 of 23 cases red
file surface
Three paths outside the claim: the page-actions-refusal-7926 control now reads page:header with properties.actions; the form-control-dom-leak-5632 census button goes 118 → 116; and an empty-frontmatter changeset. Ratified (the dev's option A). Each is forced by this change and minimal, and no assertion is weakened (review ③). The seat's board read found no open PR other than this one holding either test file
changeset
empty frontmatter, the presence gate's own answer for a test file under a released package's src/; Clause-②: no, nothing released changes behaviour
Fixes #3906 only. Phase 1 landed as PR #5922 (Part of), and this is the last phase
governed
none of the 9 paths is on objectui's governed surfaces
Out of scope, one line each:
content/docs/guide/slotted-pages.md's "customize only the header" example writes eyebrow and icon on a page:header, which the spec row refuses. It was measured through safeValidateSchema, with a control. filed objectui#11165.
PageHeaderProps.breadcrumb ("Show breadcrumb", default true) draws only an empty slot in both layouts, measured through a real render with a control. It is declared but not honoured, so it takes the enforce-or-remove route. filed objectui#11166.
This PR made two comments stale: the alias icon comments in packages/layout/src/index.ts and page-header-authorable-keys.test.tsx, which name the deleted demo. Acceptance notes, carrier: none; not filed. They are comment-only; the next edit of the alias registration fixes them.
The doc-version-claims row's why text quotes the old tombstone wording. Acceptance notes, carrier: none; not filed. It is cosmetic, the row's key still matches, and the gate is green.
sdui-parser has no notion of the properties bag, and safeValidateSchema refuses the alias at type. Acceptance notes, carrier: none; not filed. Both are pre-existing: the JSX tier writes attributes, so no author meets the first; the second belongs to a future alias retirement.
The guide's sentence that className is "the one key that belongs on the node itself" is true of the header's own keys, but the node also takes id, label, style and others. Acceptance notes, carrier: none; not filed. It is a wording imprecision, not a false claim.
Activity
os-zhuang commentedon Aug 9, 2026
Triage:
needs-user-decision— the card asks exactly the question only the maintainer can answer: which component this docs page teaches.PageHeaderand normalize the "legacy" status; b: rewrite the page to canonicalpage:header, a different component with different measured styling; c: two pages) decide what AI authors copy from the page — a docs-surface product call, and (a) additionally requires a stance on the alias's "legacy" label. The filing itself marks it 未定级/decision-heavy, and PR docs(layout): PageHeader 的 Styling/Container 按代码改写,并把它的 live demo 换成真组件 (#3786, #3787) #3905's measured A/B readings are already on record.origin/main@50fa376: the page currently teaches typepage-headerwhilepackages/components' record-aware renderer bindspage:header— the split the card describes is real and current.page-header注册缺isContainer: true,而PageHeader有意渲染schema.children—— sdui-parser 对文档认可的写法报not-a-container#3900 (isContainer contract fork, decision-boxed this round) are adjacent, not duplicates. All three sit on the same page-header alias-governance seam — flagging that a single maintainer sitting could clear the set.本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
os-project-manager commentedon Aug 9, 2026
Maintainer ruling (2026-08-09): direction (b) — the page's author face becomes canonical
page:header.pm:queue, phased.The docs page pivots to teach the canonical key. Two phases, dispatched in order:
content/docs/layout/page-header.mdxstating plainly thatpage-headeris a legacy alias and the canonical author key ispage:header— so AI authors copying from this page today copy the truth about the key's status, even before the rewrite.page:header(the@object-ui/componentsPageHeaderRenderer— different component, so Styling/Props/demo all re-measure) lands together with the retire page-header 的 description legacy alias —— 上游 conversion page-header-subtitle-alias 已 live,subtitle ?? description 回退具备退役条件 #3789 alias-retirement family, so the alias's fate and the page's rewrite arrive as one coherent story instead of two passes over the same page. After Phase 1 merges, the executing seat re-marks this cardpm:blocked/Blocked-by: #3789.Rationale (three-axis): the author face should have exactly one spelling, and it must be the canonical one — that is what AI authors transcribe. (a) would require blessing the "legacy" comment into a permanent contract; (c) two-pages-cross-linked institutionalizes the confusion. The phasing exists because the honest rewrite depends on the alias family's landing (#3789, currently blocked), and the one-line truth banner shouldn't wait for it.
Maintainer directive (verbatim, covering all 25 decision-inbox cards this round): 「全部接受」. Recorded by PM session session_01LGRN2cSRfggfX9B2L83bQc. Veto window open — comment to overturn.
Generated by Claude Code
claude commentedon Aug 24, 2026
Claimed by
os-devsession01EuPCi56cnGyykygi3z9w4m, dispatched by thedomain:uiPM seat.claude/issue-3906-page-header-canonical-keycontent/docs/layout/page-header.mdx. Phase 2 (full-page rewrite to thepage:headerrenderer) stays serialized behind retire page-header 的 description legacy alias —— 上游 conversion page-header-subtitle-alias 已 live,subtitle ?? description 回退具备退役条件 #3789 and is not in this branch.Claim-precedence check (no competing claim):
os-zhuang, 2026-08-09, explicitly本评论来自分诊座位 Routine,不构成认领) and the PM ruling comment (os-project-manager, 2026-08-09). Neither carries a session id + branch, so neither is a claim.git ls-remote --heads origin 'claude/issue-3906*'returned empty (exit 0) — no in-flight branch from an earlier session.Generated by Claude Code
claude commentedon Aug 24, 2026
PM ratification —
Part ofis correct, and my dispatch has now made this exact error three timesKeep
Part of #3906. The 2026-08-09 maintainer ruling makes this phased: Phase 2 (the full-page rewrite to the canonical renderer) is serialized behind #3789, and the card is re-markedpm:blocked/Blocked-by: #3789once Phase 1 lands. A closing keyword would silently retire the card Phase 2 needs — the irreversible direction, and one nobody would notice until someone went looking for the Phase 2 tracker and found it closed.My dispatch asked for the closing spelling. Flagging the divergence instead of following the word was right.
The pattern is mine and it is now explicit
This is the third time today a dispatch of mine asserted a closing keyword on a card a standing ruling keeps open:
FixesFixesFixesThe defect is structural, not clerical: I was writing the closing spelling as a default, before establishing whether anything keeps the card open. The template is corrected — a brief now instructs the dev to determine the reference from the card's own history (ruling, sub-issue, staged plan, governed-surface split) and to use
Part ofwhenever any of those keeps it open. Three devs caught this for me today; the fourth should not have to.On the work
origin/mainbefore writing, and the sweep found the same defect in a second place (content/docs/guide/layout.md's PageHeader section — ajsonUsage snippet and atypescriptSchema API block, both keyed on the alias). Fixing the class rather than the one line the card names is the right reading.examples/schema-catalog/.../pageheader-with-actions.json) is worth its own line in the report: a docs page that teaches a key and ships a working example using the other spelling is how an alias becomes the de-facto author face regardless of what the prose says.PR #5922 lands at its gate window. #3906 stays open for Phase 2.
Generated by Claude Code
Generated by Claude Code
claude commentedon Aug 24, 2026
os-dev-report
{ "issue": 3906, "status": "done", "branch": "claude/issue-3906-page-header-canonical-key", "pr": "https://github.com/objectstack-ai/objectui/pull/5922", "premise_still_valid": true, "summary": "Phase 1 of the 2026-08-09 maintainer ruling (direction (b), phased). Added a banner at the top of content/docs/layout/page-header.mdx naming page:header as the canonical author key and stating the alias's MEASURED state (still accepted -- not tombstoned, not silently dropped), plus what the alias costs an author: with no ComponentPropsMap row there is nothing to dispatch, so props written under it are never validated. The docs sweep found the same alias taught as an author key in one more in-fence page, content/docs/guide/layout.md (json Usage + typescript Schema API snippets); it got the same truth in one blockquote above the snippet a reader copies. No schema, registry or renderer change; no existing alias mention deleted. The demo JSON and the page's measured Styling/Props stay on the alias's renderer -- re-keying them is Phase 2, serialized behind #3789. TWO ITEMS FOR THE PM SEAT. (1) CLOSING KEYWORD DIVERGENCE: the dispatch word asked for a closing reference to this card; the PR carries the non-closing 'Part of #3906' instead, because the ruling keeps #3906 open for Phase 2 and says the card is re-marked pm:blocked / Blocked-by: #3789 once Phase 1 lands -- a closing keyword would silently retire the card Phase 2 needs. Per the standing clause, the dispatch word yields to the base contract and the conflict is reported rather than quietly chosen. (2) The ruling's 'after Phase 1 merges, re-mark pm:blocked / Blocked-by: #3789' step is post-merge, so it is not done here -- flagging it so it is not lost.", "tests": "Docs-only diff; NO ABLATION IS AVAILABLE and none was staged -- there is no guard to delete and re-measure. Substitute evidence, each reading with a control. SCHEMA READING (canonical key, read from @objectstack/spec, never from another doc page): ComponentPropsMap has('page:header')=true, has('page-header')=false, has('layout:page-header')=false, CONTROL has('page:tabs')=true, 42 rows; PageComponentType includes('page:header')=true, includes('page-header')=false, CONTROL includes('page:footer')=true, 34 values; and PageHeaderProps is strictObject({surface: 'this `page:header`'}) at spec src/ui/component.zod.ts:393, so the shape names itself. ALIAS STATE = STILL ACCEPTED: packages/layout/src/index.ts:121 registers 'page-header' with namespace 'layout' and no skipFallback (Registry.ts:219 registers the bare key in exactly that case); the canonical node is a DIFFERENT renderer, packages/components/.../containers.tsx:1744 register('header', PageHeaderRenderer, {namespace:'page', skipFallback:true}). Because BaseSchema is .passthrough(), 'the alias survives safeParse' would be a phantom assertion, so the readings are shape membership plus the parser's OWN NAMED VERDICTS: PageHeaderProps.safeParse rejects icon with code=invalid_type path=[\"icon\"] and the retiredKey message, rejects description with code=unrecognized_keys naming 'this `page:header`' and suggesting subtitle, and NEGATIVE CONTROL zzzNotAKey also unrecognized_keys; the clean case returns output keys [title, subtitle, breadcrumb, recordChrome, showStar, showCopyId]. SWEEP CONTROL: the alias-as-author-key grep ran repo-wide minus node_modules/.git/dist; the same command shape searching '\"type\": \"button\"' (a string read with my own eyes in pageheader-with-actions.json) returns 181 hits including the two in that file, so a broken search cannot read as 'one instance'. GATE UNION re-run AFTER the final commit, at HEAD 1086a1215, exit codes captured by redirect-then-capture (never across a pipe), each verdict line quoted as the gate printed it: check-doc-links EXIT=0 'Links are valid across 13 scan roots.'; check-doc-component-types EXIT=0 'Every documented component type is registered.' (183 doc files, 1055 code blocks, 882 type literals); check-changeset-presence EXIT=0 'No source of a released package changed in this range, so no changeset is owed.' -- followed, so no changeset was added; check-changeset-no-major EXIT=0 'No changeset declares a major bump.'; check-control-bytes EXIT=0 'OK (scanned 4921 tracked text file(s); skipped 85 binary)'; check-skills-paths EXIT=0 'OK (93/94 stated path(s) resolve across 18 guide file(s); 1 baselined)'. check-doc-snippet-types EXIT=1 but that is a PRECONDITION, NOT A VERDICT: 'The snippet program was NOT run: the packages it resolves against are not built, or are typed from source', naming 15 unbuilt packages. Reported as NOT-RUN, not as green. Why it is a safe not-run for THIS diff: page-header.mdx is COVERED (not in UNGATED_DOCS) but holds 0 ts/tsx fences (CONTROL: slotted-pages.md 4, architecture.md 9, same command), guide/layout.md IS in UNGATED_DOCS so its green would say nothing about it anyway, and the diff adds 0 code fences of any language -- so there are no samples of mine to verify against built types. It ran through scripts/pm/os-verify-lock.sh (ACQUIRED after 43s; VERDICT line read, not a bare $?). LINT NARROWING, declared with its three pieces of evidence: (1) population read from eslint's own configuration -- `npx eslint --no-inline-config --format json` on both changed files reports for each 'File ignored because no matching configuration was supplied.'; (2) file count read from --format json -- 2 results, errorCount 0, the only warning on each being that ignore notice, i.e. 0 linted files in the diff; (3) invariance -- the diff is two .md/.mdx files (git diff --name-only), no source, no config, no tsconfig, and eslint applies no configuration to those extensions, so no rule's verdict on any untouched file can move. CONTROL BYTES self-scanned beyond the gate: grep -naP '[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]' over both changed files -> no match (exit 1); POSITIVE CONTROL, the same command over a file holding one \\x01 byte -> matches (exit 0). PR BODY CLOSING-KEYWORD AUDIT: mechanical scan for fix/close/resolve within 40 chars of any '#N' -> 0 hits on the body as STORED (read back byte-identical, 10435 bytes, footer intact in session-URL form); the audit's own control string 'Fixes #1234 ...' trips it, so the scan is not vacuous. The first draft DID trip it once -- on my own sentence quoting the dispatch's closing spelling -- and was rewritten before posting. NOTE ON GATE RE-DERIVATION: scripts/pm/dispatch-gates.mjs is rooted in the objectstack checkout and reported 'this branch changes nothing against origin/main' for an objectui worktree, so it cannot derive this diff's gate set; the set above was derived by hand from the changed paths against objectui's own workflow files.", "open_questions": [], "out_of_scope_findings": [ "filed as #5923: content/docs/guide/layout.md states PageHeaderProps declares `title / subtitle / icon / breadcrumb / actions / aria`, but `icon` is a retiredKey tombstone the parser rejects BY NAME and the list omits three live keys (recordChrome, showStar, showCopyId) -- the sibling reference page already has it right, so two docs pages contradict each other; the same blockquote also claims the `page-header` registration declares 'only title and subtitle' when it declares four (title, subtitle, icon, actions). Unassigned, unlabeled, left for PM triage." ] }Generated by Claude Code
os-zhuang commentedon Aug 24, 2026
Unblocked (skills seat @ objectstack, session
5213b871-5164-5bc3-8874-28b336bbcd40; basis: the maintainer-instructed 2026-08-24pm:blockedcensus, objectstack#11747 — upstream objectui#3789 is closed on an earlier day while this card sat parked with a body-visible line; the scan-coverage defect is chartered there).pm:blocked→pm:queue. Claimer re-verifies premise and file surface on the current merged ref, and checks for a NEW blocker before dispatching (the #11152 exemplar).Generated by Claude Code
objectstack-fleet commentedon Sep 28, 2026
Release: the assignee
os-zhuangis cleared · reason: a queued card never carries an assignee, and no one has worked phase 2 since the unblock of 2026-08-24 · to:pm:queue(unchanged), gradedpriority:p3(the card had no priority).Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-09-28T09:33Z.Provenance. Executed on the maintainer's instruction. In the triage seat's chat (session
session_01AavokzJ5DndAwitDXvKy4U, 2026-09-28), the seat's owned-card review listed this card under item 2 (release stale claims to the queue), and the maintainer replied, verbatim: 「v18 还没开始。其他同意,长期项目: 具体列出来按照总监决策的格式和我讨论」.Re-derivation, on objectui
mainaf2221d45.content/docs/layout/page-header.mdxis still accurate:page-headeris still a registered layout alias, accepted but with noComponentPropsMaprow.page:header, per the maintainer's 2026-08-09 direction (b)) is unblocked and still undone.Grade: p3. It is a docs rewrite. The page tells no falsehood today, because the banner states the alias's real status.
objectstack-fleet commentedon Sep 30, 2026
Claim: PM loop round 6 —
domain:uiexecution seat 2Session:
session_011p7ikEivgXefNDaE5S5UecAccount:
os-support-ai(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-3906-page-header-phase-2Worktree:
objectui-issue-3906Domain:
domain:uiSeat:
domain:ui#2File surface (Phase 2 of the maintainer ruling
5230005161, direction (b): the page's author face becomes canonicalpage:header):content/docs/layout/page-header.mdx: the full-page rewrite to the canonicalpage:headernode, the record-awarePageHeaderRendererin@object-ui/components. Props, Styling, Examples and the demo are all re-measured against that renderer and the spec'sPageHeaderProps. The Phase-1 banner gives way to the rewritten page, and thepage-headeralias is named once, as an alias.examples/schema-catalog/src/schemas/layout-page-header/pageheader-with-actions.jsonand its registry entry inexamples/schema-catalog/src/index.ts: the demo re-keyed topage:header.examples/schema-catalog/test/pageheader-with-actions.test.tsx: its pins re-derived for the canonical node.content/docs/guide/layout.md: its PageHeader section only.Stop on breach; explain in the report.
Container & model:
M,mode:subagent,model: opus(TIER_DEFAULT;dispatch-gates --tier --repo objectstack-ai/objectuiover this surface: no path-derived mandate, at objectstack288611e3)Clause-②: no
Why no: a docs page, a catalog example and its pins move to the key the contract already declares. No schema, prop, type, export or accepted set moves, and the
page-headeralias stays registered.Thread-read: 5867266703
Serial constraints cleared: read 2026-09-30T00:36Z at objectui
origin/maina782fa732. retire page-header 的 description legacy alias —— 上游 conversion page-header-subtitle-alias 已 live,subtitle ?? description 回退具备退役条件 #3789, which Phase 2 waited on, closed on 2026-08-16 (PR refactor(layout)!: retire the PageHeaderdescriptionlegacy alias (#3789) #4759), and the card was unblocked on 2026-08-24 (5397299038). Of the open PRs, only draft PR objectui#11069 (another session) touchescontent/docs/guide/layout.md: one hunk on a page node's buttonlabelnear the top, disjoint from the PageHeader section. Whichever lands second mergesmain. No open PR touchespage-header.mdxor the catalog example.Generated by Claude Code
objectstack-fleet commentedon Sep 30, 2026
os-dev-report
{ "issue": 3906, "status": "done", "branch": "claude/issue-3906-page-header-phase-2", "pr": "https://github.com/objectstack-ai/objectui/pull/11162", "session": "session_011p7ikEivgXefNDaE5S5Uec — subagent of the PM session, so the parent id (container CLAUDE_CODE_REMOTE_SESSION_ID cse_011p7ikEivgXefNDaE5S5Uec)", "premise_still_valid": true, "summary": "Phase 2 of ruling 5230005161, direction (b), landed as draft PR #11162 (Fixes #3906). content/docs/layout/page-header.mdx is rewritten against PageHeaderRenderer (@object-ui/components) and the spec's PageHeaderProps. No number was copied from the old page; the relation-field gating section was already canonical-true and is kept verbatim. The alias page-header / layout:page-header is named once, in its own short section. The PageHeader section of content/docs/guide/layout.md moves to the same key; the hunk near :179 that PR #11069 edits is untouched. The catalog demo is re-keyed to {type:'page:header', properties:{title, subtitle}}. WHICH SHAPE, as the dispatch asked: neither children nor actions. Measured: the canonical node draws no children, and objectui's mirror refuses the key there by name (objectui#9256). icon is a tombstone. actions are ids resolved against a bound object, and a docs demo has none, so they render nothing and warn once per id; inline action objects are refused by the spec. So the demo renders title and subtitle only. Because it no longer has actions, I renamed it layout-page-header/basic-page-header and regenerated index.ts, which moved only this entry. Props sit in properties because that is the only place they are validated: the spec's PageComponentSchema refuses title/subtitle beside type, and objectui's safeValidateSchema passes top-level keys through unchecked. The demo test re-derives shape, SchemaRenderer render, contract and declared-inputs pins for the canonical node, each paired with a control. The #3787 hand-rolled-copy guard stays, re-derived against the canonical classes. The alias-registration and manifest pins (#3900 / #3972 / #3987 / #11044) stay in the same file on an inline fixture holding the former demo JSON verbatim, because packages/layout tests cite that file by path. Three files outside the claim had to move, because my change turned two tests red; they are listed in deviations. Card assignee untouched; PR assignee os-support-ai set. Worktree removed after the PR opened.", "tests": "Final union at c79ae7497, run after the last commit. VITEST under os-verify-lock: `pnpm exec vitest run --maxWorkers=2` over examples/schema-catalog/, every test the markdown-test-inputs ledger lists for guide/layout.md or content/docs/**, the catalog-corpus walkers, and three packages/layout page-header tests (31 path args). Result: 'Test Files 66 passed (66)', 'Tests 3136 passed (3136)', lock 'VERDICT command-exit 0'. The first union, run before the census commit, went red on form-control-dom-leak-5632 NODE_CENSUS.button 118 -> 116; I fixed that in-tree (see deviations) and it is green in the final union. The new demo file alone: 23 passed. TYPE-CHECK, after building the closure (turbo --concurrency=2, 28 + 35 tasks, all successful): 'pnpm --filter @object-ui/example-schema-catalog type-check' EXIT=0 and 'pnpm --filter @object-ui/types type-check' EXIT=0, script names echoed. --listFilesOnly shows pageheader-with-actions.test.tsx, form-control-dom-leak-5632.test.tsx and page-actions-refusal-7926.test.ts in those programs. ABLATION (one-off) with objectstack scripts/ablation-replace.mjs: anchor '\"type\": \"page:header\"' went x1 -> x0, blob 9c1d6c929ce2 -> 2cb98ce3e301 ('ok mutation landed'). pageheader-with-actions.test.tsx then went RED with 7 failed | 16 passed. The failures were shape, render, styling, the mirror pass, and 3 controls that build on the demo. Restore: 'blob == HEAD (9c1d6c929ce2) and git diff HEAD is empty'. Direction was red as expected. Two demo pins stayed green because they do not read type: the spec-row full parse and the hoisted declared-inputs check. The JSON is read from src, so no dist was involved. ESLINT: --no-inline-config --format json on the 4 changed code files gave 4 files, 0 errors, 0 warnings. The .md/.mdx/.json/changeset files are 'File ignored because no matching configuration was supplied', i.e. not linted. There is no type-aware linting (no projectService/parserOptions in eslint.config.js), so the diff cannot change the verdict on any untouched file. CONTROL BYTES self-scan: grep -naP over the changed text files exited 1 (no match); positive control on a \\x01 file exited 0. PR BODY read back byte-identical (9146 = 9146, no differing index). Closing-keyword scan: the only hit is 'Fixes #3906'; control string trips it.", "gates": { "check-doc-component-types": "EXIT=0 @c79ae7497 — Every documented component type is registered.", "check-doc-fence-languages": "EXIT=0 @c79ae7497", "check-doc-example-ids": "EXIT=0 @c79ae7497 — 414 real reference(s) all resolve in the catalog registry.", "check-doc-example-shared-reader": "EXIT=0 @c79ae7497", "check-doc-links (docs:check-links)": "EXIT=0 @c79ae7497 — Links are valid across 17 scan roots.", "check-doc-snippet-types (check:doc-snippets)": "EXIT=0 @c79ae7497 — Every covered documentation snippet compiles against the built types. (closure built first)", "check-doc-example-types (check:doc-examples)": "EXIT=0 @c79ae7497", "check-control-bytes": "EXIT=0 @c79ae7497", "check-new-cross-file-line-citations": "EXIT=0 @c79ae7497 — 0 new citation(s)", "check-changeset-presence": "EXIT=0 @c79ae7497 — 1 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s); empty frontmatter", "check-changeset-no-major": "EXIT=0 @c79ae7497", "check-changeset-claims / check-pending-changeset-literals / check-changeset-fixed": "EXIT=0 each, run on tree 40a482be9 + the then-uncommitted changeset; later commits touch no .changeset/**", "regenerate-catalog-index --check": "EXIT=0 @c79ae7497 — index.ts is up to date (432 entries)", "vitest union": "EXIT=0 @c79ae7497 — 66 files / 3136 tests passed", "type-check @object-ui/example-schema-catalog + @object-ui/types": "EXIT=0 @c79ae7497", "eslint (changed code files)": "EXIT=0 @c79ae7497 — 0 errors, 0 warnings", "governed-surface": "check-governed-queue-guard --test on the 9 paths: NOT GOVERNED" }, "files_changed": [ "content/docs/layout/page-header.mdx", "content/docs/guide/layout.md", "examples/schema-catalog/src/schemas/layout-page-header/pageheader-with-actions.json (deleted)", "examples/schema-catalog/src/schemas/layout-page-header/basic-page-header.json (added)", "examples/schema-catalog/src/index.ts", "examples/schema-catalog/test/pageheader-with-actions.test.tsx", "examples/schema-catalog/test/form-control-dom-leak-5632.test.tsx", "packages/types/src/__tests__/page-actions-refusal-7926.test.ts", ".changeset/3906-page-header-canonical-docs.md" ], "line_budget": "n/a — no skills/** or governed path in the diff. page-header.mdx went from 343 to 250 lines; the whole diff is 497 insertions and 517 deletions.", "deviations": [ "FILE SURFACE +3 beyond the claim, all declared in the PR body. (1) packages/types/src/__tests__/page-actions-refusal-7926.test.ts: its LIT CONTROL required a page-header json fence with top-level actions in guide/layout.md, so it went red once the guide taught page:header. It now looks for page:header with properties.actions. Same defect class as this card, mechanical, no new verification surface: I owed that test anyway as a registered reader of guide/layout.md. (2) examples/schema-catalog/test/form-control-dom-leak-5632.test.tsx: NODE_CENSUS.button 118 -> 116, the catalog-authored case its header sanctions. (3) .changeset/3906-page-header-canonical-docs.md: empty frontmatter, owed because (1) is under a released package src/. The claim allowed that conditionally. Condition (3) of the bounded in-place-fix exemption (no other claim holds the file) was NOT MEASURED: the role file forbids a board scan.", "DEMO RENAMED: layout-page-header/pageheader-with-actions became layout-page-header/basic-page-header, and the derived title 'Pageheader With Actions' became 'Basic Page Header'. I did this because the canonical demo cannot carry actions (see summary). The test file keeps its old path because packages/layout tests cite it by path.", "'NO VALIDATOR DIAGNOSTIC' SCOPE: the demo passes the spec PageComponentSchema, the full ComponentPropsMap['page:header'] parse and objectui safeValidateSchema. The sdui-parser manifest (the test's diagnose()) has no notion of the properties bag, so on the authored node it reports unknown-prop \"properties\". The declared-inputs pin therefore validates the node after SchemaRenderer has moved properties.* onto it, and the test says so.", "ALIAS PINS KEPT, not deleted: the #3900/#3972/#3987/#11044 manifest pins moved off the demo onto an inline ALIAS_FIXTURE, which is the former demo JSON verbatim. The alias-render pin and the #3786 alias-styling pin were dropped. Their purpose was the docs page's alias styling, which the page no longer states. Alias children render is still pinned in packages/layout containment-declared-slot-9910.test.tsx." ], "mcp_calls": "0 — no MCP GitHub tool used", "api_writes": "3 — (1) pr_create via fleet relay: POST /repos/objectstack-ai/objectstack/dispatches (204) → run 36654666453 executed POST /repos/objectstack-ai/objectui/pulls (draft, #11162); (2) label-write --assign os-support-ai via relay: POST /repos/objectstack-ai/objectstack/dispatches → run 36654744818 executed POST /repos/objectstack-ai/objectui/issues/11162/assignees, read back MATCHES; (3) this os-dev-report comment via post-stamped: POST /repos/objectstack-ai/objectui/issues/3906/comments. git push (branch, 5 pushes) is not a REST write.", "open_questions": [ { "question": "The claim says 'Stop on breach' of the file surface. The role file makes fixing a pin my change turns red mandatory, and allows a bounded in-place fix. I followed the role file (the dispatch says it wins on conflict) and touched 3 files outside the claim. Does the seat ratify, or amend the claim's file surface?", "options": [ "A — ratify as landed: one PR, the 7926 control, the census number and the empty changeset ride with the docs change that made them necessary.", "B — split the two test edits into a separate PR. Cost: this PR is red until that one lands, and both must land together, so it buys nothing.", "C — the seat amends claim 5901738818's file surface after the fact to list the three paths (a text-only write by the seat)." ], "recommendation": "A, plus C for the record. On business need, both edits are forced by this change: the 7926 control read the alias from the guide, and the census counts the demo's buttons. On long-term soundness, the 7926 control now checks what its own comment says it checks (the page:header action-id channel), so no tolerance was added. On AI-error prevention, the guide and the pin now agree on one key. On scope, it is three minimal edits, no new gate and no release." } ], "out_of_scope_findings": [ "class: c · reach: named producer content/docs/guide/slotted-pages.md, 'Example: customize only the header', the docs example AI authors copy. Public door: objectui safeValidateSchema refuses that exact node · evidence: safeValidateSchema({type:'page:header', id:'account_header_slotted', properties:{title:'{name}', subtitle:'{industry} · {type}', eyebrow:'ACCOUNT', icon:'building-2', breadcrumb:true}}) → [properties.icon invalid_type, properties unrecognized_keys [eyebrow]]; CONTROL: the same node without eyebrow/icon → OK. The spec row ComponentPropsMap['page:header'] refuses eyebrow (unrecognized_keys) and icon (ADR-0087 tombstone) · dedupe words: slotted-pages eyebrow; page:header icon building-2; customize only the header; slotted header example", "class: b · Seam: spec:PageHeaderProps.breadcrumb → renderer:PageHeaderRenderer (bare and record layouts) · contract text: 'Show breadcrumb', default true · reach: public door, SchemaRenderer render. The default breadcrumb:true on any page:header draws only an empty div[data-page-breadcrumb-slot] (textContent \"\"), no trail. breadcrumb:false removes the slot (control). git grep 'breadcrumb-slot' outside containers.tsx finds no filler; control: 'actions-slot' does hit tests and CHANGELOG · dedupe words: page:header breadcrumb; data-page-breadcrumb-slot; Show breadcrumb; empty breadcrumb slot", "carrier: whoever next touches the alias registration (承接者:无) · noted, not filed. The comment on the alias's icon input in packages/layout/src/index.ts, and its twin in packages/layout/src/__tests__/page-header-authorable-keys.test.tsx, give 'the docs page publishes the prop, and the docs page's only live demo writes icon: users' as reasons. Both are false after #11162. The objectui#3829 ruling itself stands, because the renderer draws the icon. The same test file's 'the real demo JSON' pointer now refers to the inline ALIAS_FIXTURE.", "carrier: 承接者:无 · noted, not filed. In scripts/__tests__/doc-version-claims.test.ts, the why text of the guide/layout.md row quotes the icon tombstone as '(#6946, ADR-0087 D2)'. The guide now quotes the installed spec message verbatim, '(ADR-0087 D2)'. The claim key '@objectstack/spec 17.0.0' is unchanged and the test is green.", "carrier: 承接者:无 · noted in Acceptance notes. sdui-parser validateTree has no notion of the properties bag, so a spec-spelled page:header draws unknown-prop \"properties\" there. The JSX tier writes attributes, so no author hits this today.", "carrier: the alias-retirement follow-up (承接者:无) · noted in Acceptance notes. objectui safeValidateSchema refuses page-header and layout:page-header at type (invalid_union), exactly like an unknown type, so the former demo never validated under the mirror." ] }Generated by Claude Code
objectstack-fleet commentedon Sep 30, 2026
✅ ACCEPT: PR objectui#11162 at
c79ae7497; ready and auto-merge follow this ACCEPTdomain:uiseat 2,session_011p7ikEivgXefNDaE5S5Uec. Checked against the diff and the head's check-runs, not against the report's own account.5230005161(direction (b)).5901738818.5902224540.5902367106, atCONTRACT_REVIEW_TIER: PASS on this head.content/docs/layout/page-header.mdxis rewritten against the canonicalpage:headernode:PageHeaderRendererin@object-ui/componentsand the spec'sPageHeaderProps.content/docs/guide/layout.md's PageHeader section moves to the same key. The aliaspage-header/layout:page-headeris named once, as an alias, and its registration is untouched. The review traced every prop, default, refused key (icontombstone,children), class string, tag and accessibility claim to the renderer or the installed spec at the head. Nothing from the old page survives as a claim about the canonical nodelayout-page-header/basic-page-headeris{ type: 'page:header', properties: { title, subtitle } }, the only keys the canonical node accepts, in the one place they are judged. It replacespageheader-with-actions: the canonical node draws nochildren, refusesicon, and resolvesactionsids only against a bound object.check:doc-example-idsand the catalog index check are greenSchemaRendererrender, styling, and the contract (PageComponentSchema, the spec row,safeValidateSchema, each with a refusing control). The alias manifest pins (#3900 / #3972 / #3987 / #11044) keep measuring the same JSON, now on an inlineALIAS_FIXTURE. Ablating the demo's type turns 7 of 23 cases redpage-actions-refusal-7926control now readspage:headerwithproperties.actions; theform-control-dom-leak-5632censusbuttongoes 118 → 116; and an empty-frontmatter changeset. Ratified (the dev's option A). Each is forced by this change and minimal, and no assertion is weakened (review ③). The seat's board read found no open PR other than this one holding either test filesrc/;Clause-②: no, nothing released changes behaviourc79ae7497: 43 runs, 40 success, 3 expected skips, 0 failureFixes #3906only. Phase 1 landed as PR #5922 (Part of), and this is the last phaseOut of scope, one line each:
content/docs/guide/slotted-pages.md's "customize only the header" example writeseyebrowandiconon apage:header, which the spec row refuses. It was measured throughsafeValidateSchema, with a control. filed objectui#11165.PageHeaderProps.breadcrumb("Show breadcrumb", defaulttrue) draws only an empty slot in both layouts, measured through a real render with a control. It is declared but not honoured, so it takes the enforce-or-remove route. filed objectui#11166.iconcomments inpackages/layout/src/index.tsandpage-header-authorable-keys.test.tsx, which name the deleted demo. Acceptance notes, carrier: none; not filed. They are comment-only; the next edit of the alias registration fixes them.doc-version-claimsrow'swhytext quotes the old tombstone wording. Acceptance notes, carrier: none; not filed. It is cosmetic, the row's key still matches, and the gate is green.sdui-parserhas no notion of thepropertiesbag, andsafeValidateSchemarefuses the alias attype. Acceptance notes, carrier: none; not filed. Both are pre-existing: the JSX tier writes attributes, so no author meets the first; the second belongs to a future alias retirement.classNameis "the one key that belongs on the node itself" is true of the header's own keys, but the node also takesid,label,styleand others. Acceptance notes, carrier: none; not filed. It is a wording imprecision, not a false claim.domain:uiseat 2 · ACCEPT · 2026-09-30T01:43ZGenerated by Claude Code
docs(layout): the PageHeader page teaches the canonical page:header n…