Repository navigation
fix(spec): a page label does not fill the untitled header of a record page - #22598
Conversation
… page On a `type: 'record'` page bound to an `object`, a `page:header` that authors no title and keeps its record chrome derives its heading from the record. `translatePage` no longer fills that absent title with the pack's `pages.NAME.label`; everywhere else the label still fills, and an authored title follows the existing order. Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude <noreply@anthropic.com>
…cord-page-header-title
📓 Docs Drift CheckThis PR changes 1 package(s): ⛔ 1 release-owned page(s) name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 139 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 9e413215bac8a5abf058e6791cfa51cc8e821803 && git checkout 9e413215bac8a5abf058e6791cfa51cc8e821803
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 86f53a4b8d818706b367b3b89de0e33d92395f34 d841d2686fd54047b5ce72724c97726a029f7c9a && git checkout -B drift-repro 86f53a4b8d818706b367b3b89de0e33d92395f34 && git merge --no-ff d841d2686fd54047b5ce72724c97726a029f7c9a
node scripts/docs-audit/affected-docs.mjs --json 86f53a4b8d818706b367b3b89de0e33d92395f34
|
Contract reviewServed-tier: Read-only review of PR #22598 for card #22536, at the branch head above (the head the brief named; the branch has not moved). Inputs: the card body and all nine comments (triage ① Derived judgments
② Semver level
③ Boundary flagsEvery dev flag from
Implemented-by: VERDICT: PASS Generated by Claude Code |
Landing pre-checks at
|
Fixes #22536
Clause-②: no
On a record page, a
page:headerwith notitleis the sanctioned spelling for "the renderer derives the heading from the record" (PageHeaderProps.title, the maintainer ruling recorded incomponent.zod.ts). Studio's record-page seed writes exactly that header.translatePagefilled the absent title with the pack'spages.NAME.label, so the record's name was replaced by the page's static label. This happened in every locale whose pack carries the label, including theenskeleton thatos i18n extractwrites. Nothing warned.The rule (triage ruling A, comment 6092658777)
resolvePageHeaderTitle(packages/spec/src/system/i18n-resolver.ts) no longer lets the bundle label fill an untitled rootpage:headerwhen all three of these hold (headerDerivesFromRecord):type === 'record', read as served: a document with notypeis not a record page;objectis a non-empty string;properties.recordChrome !== false.Everywhere else the label still fills an absent title, as before. That includes the existing
#22508control fixture, a page with notypeand noobject, whose pin name now says "non-record page". An authored title, including one that restates the pagelabel, follows the existing order unchanged. The docblock's order list carries the new condition in its step 3.Where the predicate comes from. objectui is read at the
.objectui-shapin47b1f0bb71:PageHeaderRendererdraws the record-derived heading only when a record context is mounted and the header keeps its record chrome (containers.tsx:2331-:2332,:1573-:1574).usePageAssignmentpicks (usePageAssignment.ts:139-:140:type === 'record'andobjectnaming the route's object), and the Studio preview (PagePreview.tsx:186-:187,:311).type: 'modal'action that renders its target page inside the invoking record's context (useActionModal.tsx,RecordDetailView.tsx:3064). Such a page keeps today's fill. Triage accepted this gap, and its in-repo population is 0.Docs:
content/docs/ui/translations.mdxstates the exception beside the label-fallback sentence.Measured at the door
These are one-shot runs and were never committed.
GET /api/v1/meta/:type/:namegoes throughRestServer, using themeta-plural-i18n.test.tsharness shape, on both the cached and uncached branches. The page is the Studio-seed shape:account_record,type: 'record',object: 'account', and a rootpage:headerwith properties{recordChrome: true}.maind6c3791)pages.account_record.label(客户记录页), zh-CNtitle: 客户记录页{recordChrome: true}, no titleenextract skeleton (label: Account Record), entitle: Account Record{recordChrome: true}, no titleenpack, entype: 'app'page, untitled header withrecordChrome: false, zh-CNtitle: 落地页title: 落地页(unchanged)The resolver, its tests,
component.zod.tsandpage.zod.tsare byte-unchanged between d6c3791 and this branch's merge base.Pins (
packages/spec/src/system/i18n-resolver.test.ts)Each pin runs at a region root and at a slot root.
enextract skeleton, and the page's ownlabelstill translates.subtitlestill applies beside the untitled header.translateMetadataDocument, the function the REST page doors call.typeabsent,objectabsent or'', andrecordChrome: false. A further control covers an object-bound page of anothertype(list,app,home,utility).titlereplaces it.#22508control is kept as written: a header with no title on a non-record page shows the bundlelabel.Ablation. This ran at cdfc9ef with the fix committed, using
node scripts/ablation-replace.mjs. It turnedif (own === undefined) return derivesFromRecord ? undefined : bundleLabel;intoif (own === undefined) return bundleLabel;. The anchor went from 1 to 0 hits, and the blob moved from c0682db53c44 to 69817db5e283. Result:Tests 9 failed | 349 passed (358). The 9 failures are exactly the record-page pins (4 per root plus thetranslateMetadataDocumentpin), and every control stayed green. The file was restored with blob c0682db53c44 equal to HEAD, andgit diff HEADis empty.Verification
All runs went through
scripts/pm/os-verify-lock.shwith slotissue-22536. Each result is theVERDICT command-exitline of its run.@objectstack/spec:i18n-resolver.test.tsplustranslation.test.tsat d841d26 (after themainmerge): 505 passed.pnpm testat cdfc9ef: 638 files, 19037 passed, 1 todo.pnpm typecheckat cdfc9ef: green. The test-typecheck ledger held at 52 files, 246 errors and 135 signatures.build && check:generatedat d841d26: all 15 generated artifacts up to date.@objectstack/rest(meta-item-read-gate.ts), viatranslateMetadataDocument. Its consumer runs, all at cdfc9ef:pnpm test: 269 files, 4979 passed, 326 skipped.pnpm typecheck: green, with 0 test-layer errors.pnpm test:repo: 191 passed, 1 skipped.@objectstack/cliunit tier,test/platform-page-i18n-parity.test.tsandtest/i18n-extract.test.ts: 51 passed. These calltranslatePageon the shipped platform pages.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackat d841d26 derived 107 commands. 106 were run and all exited 0.check:skill-examplesfirst exited 3 (PREREQUISITE NOT MET:client-reactwas unbuilt) and exited 0 afterturbo run build --filter='@objectstack/client-react...'.pnpm check:dual-build-cjs-loads: NOT MEASURED, because the dispatch rules out a whole-workspace build.--ranreconciliation: 107 derived, 106 run, 1 unrun (that one). Artifact rosters block: 48 of 51 run, all exit 0. The 3 PR-context guards (check-closing-target-claim,check-partof-closing-keyword,check-single-claim-paths) are run against this PR after it opens, and their results are reported on the card.Acceptance notes
PageSchema's parse default againsttyperead as served. This is reported, not fixed here.PageSchemadefaults an absenttypeto'record'(page.zod.ts:1177).typeis stored withtype: 'record', and a stored row without it is served with it (withDeclaredPageTypeDefault). Its 9 existing pins pass here.SchemaRegistrywithouttypeand without a parse is served with notype. In zh-CN its untitled header gains the label (title: 打包无类型页). The same page as a stored row is servedtype: "record", and its header stays untitled.defineStackapplies the default, and the plugin pages in this tree declaretype. Reach is therefore not established.recordChromespelling. Onlyproperties.recordChromeis read. Measured:PageSchemarefuses a bare top-levelrecordChromeon a component (unrecognized_keysatregions.0.components.0), so a valid served document never carries it. Reading that spelling would be consumer tolerance for a key the contract refuses.pages.NAME.titleandsubtitlestill apply to an untitled record-page header (step 1). Triage ruled on the label alone.showcase_task_detailis the one in-repo record page with an untitled root header. No in-repo pack carries its label today, so nothing in-repo renders differently. The plugin-approvals record page that landed onmainduring this work iskind: 'slotted'with no header slot, so the server sees no root header on it.Generated by Claude Code