Repository navigation
fix(spec): a locale bundle's page label no longer replaces a page:header title that says something else - #22534
Conversation
…ader title that says something else
translatePage resolved the header title as the bundle's page title, else the
bundle's page label, and spread it over the authored title at a region root
and a slot root alike. A pack that translated the label and omitted the title
turned a dynamic '{name}' heading into the page's static name.
The order is now: bundle title, then the authored title, then the bundle
label only where the header authors no title or its title restates the
page's own label (the case the CLI extractor dedupes, offering label alone).
Claude-Session: https://claude.ai/code/session_01VZqqwTj2wsihZEbfT6yyYN
Co-authored-by: Claude <noreply@anthropic.com>
…title restates Claude-Session: https://claude.ai/code/session_01VZqqwTj2wsihZEbfT6yyYN Co-authored-by: Claude <noreply@anthropic.com>
…escribe Claude-Session: https://claude.ai/code/session_01VZqqwTj2wsihZEbfT6yyYN Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZqqwTj2wsihZEbfT6yyYN Co-authored-by: Claude <noreply@anthropic.com>
📓 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 c572bb798073004731b40ea36ee88e3b9ef487f6 && git checkout c572bb798073004731b40ea36ee88e3b9ef487f6
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin faf6348508519197c6047b46fb30b6ae8910f6b2 194157f5e16b955ece5f95ad2a518d1d5679139e && git checkout -B drift-repro faf6348508519197c6047b46fb30b6ae8910f6b2 && git merge --no-ff 194157f5e16b955ece5f95ad2a518d1d5679139e
node scripts/docs-audit/affected-docs.mjs --json faf6348508519197c6047b46fb30b6ae8910f6b2
|
Contract reviewServed-tier: Read-only, all read at 2026-10-09T21:01Z: card #22508's body and every comment (triage ① Derived judgmentsAccept set: unchanged. Right. Public surface: unchanged. Right. Served behaviour at
The refinement (label also stands in where the header title restates the page's own
Docs and ledger: right. Test fixture ② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
|
Standing down on a failure that is not this PR's.
|
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 37992289961 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 分类: 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
Fixes #22508
Clause-②: no
What changes
translatePage(packages/spec/src/system/i18n-resolver.ts) used to resolve a root-levelpage:headertitle as the bundle'spages.NAME.title, else the bundle'spages.NAME.label, and write it over whatever the header authored. A locale pack that translated the page label and left out the title therefore replaced a record page's dynamic'{name}'heading with the page's static name in that locale, with nothing warning at build, lint or serve.The header title now resolves through ONE function,
resolvePageHeaderTitle, called from the one visitor both roots pass through (a region's entry and aslots.SLOTentry both arrive withnested: false), so the two roots cannot disagree:pages.NAME.title, across the locale chain;pages.NAME.label, only where the header authors no title, or where its title restates the page's own authoredlabel.The declared contract is restated with that limit in the same PR: the
pagesJSDoc and thepages.NAME.titledescribe intranslation.zod.ts(and the regeneratedcontent/docs/references/system/translation.mdx), thetranslatePagedocblock, the hand-writtencontent/docs/ui/translations.mdxcallout, and thepagesrow ofpackages/spec/liveness/translation.json.One deviation from the triage text, measured, and why
Triage
6085431021reads: "The bundle's pagelabelstands in only where the authored header has no title of its own." Step 3 above adds one case to that: a header title that restates the page's ownlabel. The triage expected nothing to rely on the label replacing an authored title. The measurement says something does:os i18n extract(packages/cli/src/utils/i18n-extract.ts, the page-header pass) deliberately offers NOpages.NAME.titlekey when the header title restates the page label: "titleduplicatinglabelis the common case and resolves via the label fallback, only emit it when the two genuinely differ" (sameAuthored). Every skeleton it has written relies on the label reaching such a header.marketplace_installed,cloud_connection_settings(@objectstack/cloud-connection) andconnect_agent(@objectstack/mcp). Each authors a header title equal to its page label, and@objectstack/platform-objects' bundles carrylabel+subtitleand notitlefor them in all four locales (en, zh-CN, ja-JP, es-ES).packages/cli/test/platform-page-i18n-parity.test.ts("rewrites the page:header copy end-to-end for a non-English locale") goes red for all three.Step 3's second case keeps the triage's own principle ("A bundle supplies a translation of an attribute; it does not swap one attribute for another over authored text"): when the authored title IS the page label's text, the label's translation is a translation of that very text. It also keeps the resolver and the extractor a matched pair: the extractor omits
titleexactly when the resolver will read the label for it. The comparison (sameAuthoredText) mirrors the extractor'ssameAuthored(strings by text, inline maps as sets of non-empty entries). The seat may rule otherwise; the alternative is in the report'sopen_questions.What still changes for an author (named in the changeset): a header whose title says something else than the page label, in a locale whose pack carries
labelbut notitle, now shows the authored title. In this repo that population is zero (H3 below); the reporting app (hotcrm) already writestitlein every pack.Measurements (dispatch hypotheses)
translatePageis reached only throughtranslateMetadataDocument('page', …)(theMETADATA_DOCUMENT_TRANSLATORStable). Its only runtime callers aretranslateMetaListandtranslateMetaDocumentinpackages/rest/src/meta-item-read-gate.ts, which serveGET /api/v1/meta/pageandGET /api/v1/meta/page/NAME. The console reads those doors: objectui'suseObjectLabelresolves onlypages.NAME.labelclient-side (the page's nav label) and nothing readspages.NAME.titlethere. One fix covers both reach points the card names.translatePageas written inproperties.title(the REST door serves the stored or packaged document;PageComponentSchemais strict, sopropertiesis the only place a title can be authored). Pinned at both roots: a template ('{name}'), a plain string, an inline locale map, absent ({},''), and a title restating the label (string and map forms).pagesgroup:@objectstack/platform-objects(4 locales, 6 page entries each) andexamples/app-showcase(zh-CN, 4 entries,componentsonly). Pages whose bundle carrieslabeland notitlewhile a root-level header authors a title: 3 (the three Setup pages above), all of them title-restates-label, all kept translated by step 3. Pages of the defect shape (header title differs from the label, pack haslabeland notitle): 0. Thesys_*_detailpages carry a label-only entry but nopage:header. app-crm, app-todo and app-multi-package carry no page translations.Clause-② measured: no accept set or public surface widens. No schema, key, accepted value, default or export changes; the helpers are module-private. What changes is what a translated page RENDERS, which is resolver behaviour. The one schema-adjacent change is the
pages.NAME.titledescribe string.Tests
All readings below are at head
194157f5e1(the last commit; no commit followed any of them). Wall-clock figures are shared-box seconds.pnpm --filter @objectstack/spec exec vitest run --project local: 631 files, 18842 passed, 1 todo.--project repo: 54 files, 915 passed.src/system/i18n-resolver.test.tsalone: 327 passed (the new#22508block is 13 cases: six per root plus the REST-door case).pnpm --filter @objectstack/spec typecheck(tsc, scripts typecheck,check:test-typecheck): exit 0.@objectstack/cliunit layer, the two files that feedtranslatePageand the extractor:test/platform-page-i18n-parity.test.ts,test/i18n-extract.test.ts, 51 passed (CLI resolves@objectstack/specthroughdist, built at this head).@objectstack/rest(the one runtime caller):pnpm --filter @objectstack/rest exec vitest run: 273 files, 5166 passed, 327 skipped (rest resolves@objectstack/specthroughdist, rebuilt clean after the ablation).regionHeaderWithId()in the batch Query Protocol: Add comprehensive test coverage and documentation for aggregations, joins, and window functions #58 block omittedPageSchema's requiredlabelwhile its header restatedhomePage()'s label. Disposition: add the declaration (label: 'Sales Home'), so the fixture is the shape it was cut from. No other fixture in the rule's consumer radius changed (platform-objects, cli, lint, rest fixtures read; only the CLI parity test exercises the header title, and it stays green).eslint --no-inline-config --format jsonon the three changed TS files reports 3 files, 0 errors, 0 warnings. Population:eslint.config.mjslints**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}, so the.md,.mdxand.jsonfiles in this diff are outside it. Invariance: the config never enables type-aware linting (noparserOptions.project, no typed rules, stated in the config itself), so this diff cannot move a verdict on an untouched file.Ablations (one-shot, both legs restored and proven)
resolvePageHeaderTitleanswering bundle title, else bundle label, whatever the header authors), throughscripts/ablation-replace.mjs(anchor hit 1, blob changed):i18n-resolver.test.ts7 failed / 320 passed. The 7 are exactly the predicted ones: template, plain string and inline map at each root, plus the REST-door case. The controls (pack withtitlereplaces; no authored title shows the label; title restating the label) stayed green. Restored: blob equals HEAD,git diff HEADempty.ablation-dist-preflightfound the marker indist: spec 6 failed / 321 passed (the two restates-label pins, the originalconnect_agentpins and the batch Query Protocol: Add comprehensive test coverage and documentation for aggregations, joins, and window functions #58 pins), andplatform-page-i18n-parity.test.ts"rewrites the page:header copy end-to-end for a non-English locale" went red at its first page,marketplace_installed(titleChanged: false: the zh-CN header stayed "Installed Apps"). Restore leg: source blob equals HEAD, spec rebuilt, preflight--absent: marker absent from all 232 built files, tree clean.Gates
node scripts/pm/dispatch-gates.mjs --commandsderived 111 families from this diff (the 85 the dispatch named plus 26 the docs and changeset paths add). All 111 ran and recorded an exit code;--ranreconciles 111 derived, 111 run, 0 NOT MEASURED. 110 exit 0. One exits 1:pnpm check:platform-checklist, ondocs/qa/platform-checklist/areas/attachments-storage.json, whose anchorattachment-access-hooks.ts#canEditlost its declaration ince3d0ad419(#22513). That is red onmainat this branch's base and atorigin/mainee8751d41etoo; this diff touches neither file. Seven gates first refused with PREREQUISITE NOT MET (exit 3, unbuiltlint/client-react); they were re-run after the build and pass, as were the dist-reading gates after the ablation's restore rebuild.Acceptance notes
page:headerwith NO authored title still receives the bundle's pagelabel(the triage's third pin). On a record page that is not neutral:PageHeaderProps.titledocuments "omit to let the renderer derive the heading from the record", and objectui'sPageHeaderRendererdrawsproperties.titlewhen one is present, so a pack that translates the page label replaces a record-derived heading with the static label, the same failure as this card in another shape. In this repo the population is zero: no root-levelpage:headerwithout a title sits on a page with a translated label (thesys_*_detailpages carry no header). A Studio-seeded record page stores its synthesized header without a title, so it can be reached outside this repo; measured only by reading, not at a door. Raised as an open question in the report, not changed here.fallbackChain) before step 2. So a request whose own pack has onlylabel, while a pack on its fallback chain hastitle, shows that fallback title over the authored one. That is still the bundle's title, which the triage put first.pnpm check:platform-checklist(see Gates). Its carrier is whoever re-anchors theattachments-storagechecklist item afterce3d0ad419; not this PR's file surface.content/docs/references/system/translation.mdxwas regenerated bygen:docsfrom the new describe (check:generatednamed exactly that one artifact stale). No other generated artifact moved;authorable-surface.base.jsonuntouched.Generated by Claude Code