Repository navigation
PageTranslation.components has no key for element:text's content — the one string that element renders cannot be addressed by a bundle #14412
Description
Activity
- addedpriority:p2Medium: important, M3Medium: important, M3
on Sep 2, 2026 os-project-manager commented
on Sep 2, 2026 CollaboratorMore actionsMaintainer ruling recorded — A: no
contentkey on the bundle face; the exclusion is written besidehelpandsubtitle; the extraction question becomes its own card (#14749)Director seat (objectstack #12708), summon #10, session
session_01ShyhexkB2d1AeRZ85tgAAe, 2026-09-02.Provenance (who / verbatim / where): maintainer, live PM chat with the director seat, 2026-09-02, replying to decision batch #13 in which this card was item 3 with the recommendation A (fallback B), matching the triage facets on the body. Verbatim reply: 「同意」.
Ruled: A.
element:text'scontentdoes not joinPageTranslation.components. Its localisation route is the inlineI18nLabelmap at the authoring site, which #5728 (the maintainer namedcontentexplicitly) and #10926 (submitLabelretired from the face for the identical shape, "localizable at its own authoring site; adding it here is a face widening") already ruled. The schema comment inpackages/spec/src/system/translation.zod.tsgains the sentence that was missing:contentis excluded on purpose, besidehelpandsubtitle, citing #10926. Option B (add the key and a precedence rule for the two routes) is not taken.The card's new argument, that inline maps are invisible to
os i18n extract,check:i18n-coverageand translators working from the bundle, is real and unweighed by the earlier rulings; it applies to every inlineI18nLabelfield, not only this component, so it is carried as its own card: #14749 (domain:cli,pm:queue). duly's 16-stringuntranslatableledger is the measurement that card starts from.Execution:
domain:speclane, S (one comment sentence; no zod change; no changeset beyond what the changeset-presence gate demands). Clause-② no.State transition, same stroke:
needs-user-decision→pm:queue;priority:p2,domain:spec,i18nretained. Ledger: objectstack director seat post #12708, summon #10.
Generated by Claude Code
Claim: PM loop round R1 —
domain:specseat (seat post #6017). Executing the maintainer ruling recorded by the director seat (comment 5518061415, 2026-09-02, verbatim reply 「同意」 to batch #13 item 3): A — nocontentkey onPageTranslation.components; the exclusion is written besidehelpandsubtitlein the schema comment, citing thesubmitLabelprecedent; the extraction question is its own card (#14749,domain:cli).
Session:session_01H2oQebDDxYKfWZusyd8GXk(GitHubos-justin)
Branch:claude/issue-14412-content-exclusion-sentence
Worktree:objectstack-issue-14412
Domain:domain:spec
File surface:packages/spec/src/system/translation.zod.ts— the comment block that records thehelp/subtitleexclusions on the per-component face (one added sentence, comment only; ⛔ no zod change, no describe change). Changeset only if the changeset-presence gate demands one. Stop on breach; explain in the report.
Container & model: S,mode:subagent,model: opus—--tier packages/spec/src/system/translation.zod.tsatc351a84(05:32Z): no path-derived mandate; content: a source comment changes no accept/reject behaviour and widens nothing (the ruling itself says Clause ② no).
Clause-②: no
Serial constraints cleared:translation.zod.tslast touchedd261cef(2026-09-03); 0 of 18 open PRs at 05:31Z have a head on it (branch-name read; dev re-checks at worktree time); not on the seat post's hot-file queue; H17 index: no hit. No in-flight sibling shares the file.
Generated by Claude Code
Dispatch (R1, 2026-09-04T05:40Z) —
domain:specseat,session_01H2oQebDDxYKfWZusyd8GXk(os-justin), seat post #6017.mode:subagent,model: opus, size S, Clause ② no. The dev leaves its ownClaim:comment below before its first edit.Rulings (not re-decidable). Maintainer ruling recorded 5518061415 (2026-09-02, 「同意」), operative sentences verbatim: "
element:text'scontentdoes not joinPageTranslation.components. … The schema comment inpackages/spec/src/system/translation.zod.tsgains the sentence that was missing:contentis excluded on purpose, besidehelpandsubtitle, citing #10926. Option B … is not taken." "Execution:domain:speclane, S (one comment sentence; no zod change; no changeset beyond what the changeset-presence gate demands). Clause-② no." The extraction/coverage visibility of inline maps is #14749's (domain:cli) — ⛔ not touched here.PM mechanism assumptions (verify). (a) The exclusions live in the comment above the per-component face (the block the triage read at
translation.zod.ts:927-931, near thesubmitLabelretirement note at:867-876) — locate by content, not line number. (b) A/// JSDoc comment change regenerates nothing; if the block is a JSDoc thatgen:docscopies intocontent/docs/references/system/translation.mdx, the regenerated page is a legitimate follower — runcheck:generated --fixand report. (c) Changeset: the presence gate decides — if it demands one, apatchchangeset for@objectstack/spec; if a comment-only change is exempt, the PR carries theskip-changesetlabel (this repo's spelling, not an empty changeset). Report which and why (the gate's own line). (d) An issue id in a source comment is allowed where the file already does it (verify: the file's existing comments cite prior rulings);check:doc-authoringguards customer-facing strings only — do not put the citation into adescribe().PM suggested route (optional). One sentence, in the same register as the
help/subtitleexclusions:element:text'scontentis deliberately not on this face — it is declaredI18nLabelSchema(ui/component.zod.ts, thecontent: I18nLabelSchemadeclaration), so it is localizable at its own authoring site, and adding it here would be the face widening thesubmitLabelretirement declined for the identical shape; extraction/coverage visibility of inline maps is a separate question (#14749).Surface, gates, report. File surface = the claim comment 5536194441 (stop on breach). Derive gates from your actual paths after the last commit (
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands <paths>); at minimumpnpm --filter @objectstack/spec typecheck,check:generated,check:doc-authoring,check:nul-bytes,check-system-context-census, eslint on the edited file. Report:os-dev-reportfirst as a comment here, then as the return; machine-checkable fields only.
Generated by Claude Code
Claim:
Session:session_01H2oQebDDxYKfWZusyd8GXk(GitHubos-justin), dev subagent dispatched by thedomain:specseat (seat post #6017), R1.
Branch:claude/issue-14412-content-exclusion-sentence(pushed empty as a write-route probe before the first edit; worktreeobjectstack-issue-14412, base919beca—origin/mainadvanced from the dispatch'sc351a84between 05:32Z and worktree time).
File surface:packages/spec/src/system/translation.zod.ts— comment only, the JSDoc block above the per-component face where thehelp/subtitleexclusions are recorded. No zod change, nodescribe()change. A.changeset/*.mdonly if the changeset-presence gate demands one; otherwise theskip-changesetlabel.
Assignee: untouched — it is the PM's (os-justin).
Clause-②: noPremise re-verified at base
919becabefore editing (all four faces hold, one line number drifted):- Per-component face is five keys —
titledescriptionlabelplaceholderemptyText— attranslation.zod.ts:927-931. - The
submitLabelretirement note is at:867-876; the two deliberate exclusions (help,subtitle) with their reasons are at:878-888. contentis neither named nor excluded anywhere intranslation.zod.ts(the fourcontenthits in that file are at:486,:511,:519,:550, all prose in the retired-key conversion region, none on this face).element:text'scontent: I18nLabelSchemais declared inpackages/spec/src/ui/component.zod.tsat:1819(insideElementTextPropsSchema, opened at:1802) — the dispatch's:1784had drifted; located by content, not line number.
premise_still_valid: true. Proceeding with ruling A.
Generated by Claude Code
- Per-component face is five keys —
os-dev-report
{ "issue": 14412, "status": "done", "branch": "claude/issue-14412-content-exclusion-sentence", "pr": "https://github.com/objectstack-ai/objectstack/pull/15216", "premise_still_valid": true, "summary": "Ruling A executed as written: no key added to PageTranslation.components; the deliberate exclusion of element:text's content is now recorded in the schema comment beside help and subtitle, citing the #10926 submitLabel precedent, and pointing the extraction/coverage question at #14749 without touching it. One bullet added to the exclusion list plus its lead-in updated from 'Two deliberate exclusions' to 'Three' so the count stays true. Comment only: no zod key, no describe() change, no resolver change. The changeset-presence gate has no path filter and skip-changeset is reserved for diffs that publish nothing from a released package, so a patch changeset for @objectstack/spec was added on evidence that this diff does publish: packages/spec is not private and its npm files array includes src/**/*.zod.ts, so npm pack --dry-run --json lists src/system/translation.zod.ts among the 2087 tarball files, and the comment text also lands in 36 dist files (0 of them .d.ts, so the type surface is unchanged). Worktree base was 919beca, not the dispatch's c351a84 — origin/main advanced between the PM's read at 05:32Z and worktree time; PR base sha is 50d6c924.", "premise_evidence": { "face_is_five_keys": "translation.zod.ts:927-931 — title, description, label, placeholder, emptyText; submitLabel absent as the triage said", "existing_exclusions": "translation.zod.ts:878-888 name help and subtitle with reasons; the submitLabel retirement note is at :867-876", "content_absent_from_face": "the only four 'content' hits in translation.zod.ts are :486 :511 :519 :550, all prose in the retired-key conversion region — content is neither named nor excluded on this face", "content_declared_as_i18n_label": "ui/component.zod.ts:1819 declares content: I18nLabelSchema inside ElementTextPropsSchema (opened :1802). The dispatch cited :1784, which had drifted; located by content, not line number", "citation_precedent_holds": "translation.zod.ts already cites issue numbers in comments 15 times, so the #10926 / #14749 citations follow the file's own convention; the citation is in the JSDoc, not in any describe()", "issue_body_intact": "no sanitizer truncation — the body ends with its signature footer and the os-decision-facets marker is HTML-escaped, not eaten" }, "files_changed": [ "packages/spec/src/system/translation.zod.ts (comment only, +11/-2)", ".changeset/spec-text-content-exclusion-recorded.md (new, patch for @objectstack/spec)" ], "deviations_from_claim_surface": "none — git diff --name-only against the merge base is exactly the two paths above; the changeset was the branch the claim comment pre-authorised ('a changeset only if the gate demands one')", "gates": { "head_sha": "3bf408ba", "derivation": "node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, run from the worktree after the last commit with no path arguments — it read the change set itself (2 paths; committed 2, working tree 0, untracked 0; three-dot semantics) and returned 47 families across 30 workflow files. --repo assertion checked against this checkout's origin and holds. Exit 0.", "green": { "spec build": "exit 0 — 'check-dts-emitted: @objectstack/spec - 34/34 declared declaration file(s) present.'", "spec check:generated": "exit 0 — 'All 15 generated artifacts are up to date.' Nothing regenerated, so PM assumption (b) resolves negative: this JSDoc block is NOT copied into content/docs/references/, check:docs needed no --fix, and the PR carries no generated follower", "spec typecheck": "exit 0 — includes check:scripts-typecheck and check:test-typecheck ('54 file(s) / 261 error(s) / 145 pinned signature(s) held in test-typecheck-debt.json', unchanged)", "spec test (full package)": "exit 0 — 'Test Files 469 passed (469)', 'Tests 12542 passed (12542)', duration 411.94s", "spec test (targeted)": "exit 0 — src/system/translation.test.ts, 'Tests 119 passed (119)'; it is the only test file in the repo referencing PageTranslation / translationDataShape, and it pins none of the comment text", "spec source audits check:generated does not run (8)": "all exit 0 — check:browser-reachable-entries, check:dual-source-exports, check:empty-state, check:entry-nameability, check:exported-any, check:llms-txt, check:variant-docs, check:yaml-examples", "repo check gates (14)": "all exit 0 — check:doc-authoring, check:nul-bytes, check:changeset-gate-self-tests, check:cross-package-test-inputs, check:logger-receiver-detach, check:merge-driver, check:objectui-changeset, check:page-declaration-shape, check:pm-half-states, check:published-files, check:slot-lookup, check:spec-parsed-alias, check:test-source-alias, check:type-source-resolution", "lint check:doc-formula-expressions": "exit 0 after its prerequisites were built (see not_measured_then_measured)", "node script gates and self-tests (13)": "all exit 0 — check-adr-0087-registration --self-test, check-changeset-no-major --self-test, check-ci-filter-parity, check-comment-mask-adoption (+ --self-test), check-dev-prereqs --self-test, check-empty-changeset --self-test, check-keyed-text-bounds (+ --self-test), check-plugin-teardown-shape (+ --self-test, '47 cases pass'), check-system-context-census (+ --self-test), check-undeclared-dep-imports (+ --self-test), docs-audit/check-affected-docs, docs-audit/check-drift-comment, pm/release-rehearsal-clone --self-test", "pnpm lint (repo-wide)": "exit 0 — eslint . --no-inline-config run WHOLE, not narrowed, so no narrowing argument is owed; eslint on the edited file alone also exit 0" }, "not_measured": { "check:dual-build-cjs-loads": "exit 3, NOT a failure and NOT a pass — its own verdict line reads 'Run pnpm build first. This is NOT a pass: nothing was measured.' It needs a built tree for 76 packages; a comment-only diff cannot affect CJS load behaviour and CI builds it anyway. Declared, not counted as green." }, "not_measured_then_measured": { "check:doc-formula-expressions": "first two runs exit 3, 'PREREQUISITE NOT MET' — @objectstack/formula unbuilt, then @objectstack/lint unbuilt. Both built under the shared lock; third run exit 0. The first two readings are reported as unrun, not as reds.", "check-plugin-teardown-shape --self-test": "first run exit 1, but its own text was a refusal, not a finding: 'cannot read the positive control at 621a487607881c66b2899b7e3477115229a156b4 ... Deepen the clone'. This checkout is shallow (git rev-parse --is-shallow-repository = true). A targeted 'git fetch origin 621a4876...' made the pinned fixture reachable; re-run exit 0, '47 cases pass'. The non-self-test gate was exit 0 in both readings." }, "exit_capture": "every gate was run as 'cmd > logfile 2>&1; EXIT=$?' — redirect first, capture second, never through a pipe. Heavy runs (build, typecheck, tests) went through scripts/pm/os-verify-lock.sh and are read from its VERDICT line, not a bare $?; each printed 'VERDICT command-exit 0'.", "ci_status_at_report_time": "in_progress — reported at draft-PR time per the dispatch contract; CI convergence is the PM's" }, "tests": "pnpm --filter @objectstack/spec test at head 3bf408ba: 'Test Files 469 passed (469)', 'Tests 12542 passed (12542)'. Targeted first: src/system/translation.test.ts, 'Tests 119 passed (119)'. pnpm --filter @objectstack/spec typecheck exit 0. No ablation was run and none is claimed: the diff adds no guard, no test and no gate, so there is nothing whose failure could be demonstrated by mutation — a comment carries no accept/reject behaviour. The reverse-verification that IS meaningful here was done instead and is stated above: check:generated proves the comment reaches no generated artifact, and the npm-pack / dist greps prove it does reach the published tarball, which is what settled the changeset question.", "mcp_calls": "7 — issue_read get, issue_read get_comments, add_issue_comment (claim), create_pull_request, pull_request_read (body readback), add_issue_comment (this report), issue_read get_comments (report readback). REST was probed first per the channel rule and is 403 for this seat on repo-scoped endpoints, authenticated or not; gh is absent. No list_issues, no search_issues, no wide scans.", "open_questions": [], "out_of_scope_findings": [] }
Generated by Claude Code
ACCEPT — PR #15216 (head
3bf408ba), reviewed 2026-09-04T06:18Z by thedomain:specseat,session_01H2oQebDDxYKfWZusyd8GXk(os-justin), seat post #6017. Clause ② no (comment-only; no accept/reject behaviour moves) — no contract review owed.Checklist readings, taken on GitHub, not from the report: draft ✓ · base
main✓ · first body lineFixes #14412✓ (closing on merge is right — the card's whole deliverable is this sentence) ·get_files= exactly the claimed surface (packages/spec/src/system/translation.zod.ts+11/−2, comment block only;.changeset/spec-text-content-exclusion-recorded.mdnew) · nocontent/docs/releases/**·@objectstack/specis published and itsfilesarray shipssrc/**/*.zod.ts, so thepatchchangeset is the correct form overskip-changeset(the dev'snpm pack --dry-runreading; accepted) · governed-surface predicate--teston the two paths: 0 of 2 hit ·origin/mainchurn on the file since dispatch: none (last touch PR #14381, 2026-09-02) · diff matches ruling 5518061415 verbatim —contentrecorded as a deliberate exclusion besidehelpandsubtitle, citing thesubmitLabelprecedent, the extractor question pointed at #14749 and not addressed · no model identifiers in PR title/body/changeset/comment · test evidence is real commands with quoted verdict lines at the head sha. Deviations: none (the dispatch'scomponent.zod.ts:1784line reference had drifted to:1819; the dev relocated by content — a dispatch-side slip, not a dev deviation). Report notes the container'sGITHUB_TOKENanswers 403 on repo-scoped REST — MCP channel used, recorded on the seat post.Landing: CI on
3bf408bawas still converging at 06:17Z (Test Core shards, Lint & Repo Gates, Type Check jobs in progress; 12 completed checks allsuccess/skipped). On all-green: ready → squash auto-merge → queue;pm:dispatchedstrips on MERGED with the landing note here.
Generated by Claude Code
Landing provenance (2026-09-04T06:41Z,
domain:specseat,session_01H2oQebDDxYKfWZusyd8GXk): PR #15216 head3bf408ba— all 34 check runssuccessorskipped(Test Core rollup 06:39:25Z, Lint & Repo Gates 06:27:49Z, Type Check · workspace 06:28:32Z),mergeable_state: clean, governed-surface predicate 0 of 2. ACCEPT 5536542172 (Clause ② n/a) is the review of record. Flipped ready → squash auto-merge → merge queue; on MERGED:pm:dispatchedstripped, landing note here with thetranslation.zod.tsprobe onorigin/main.
Generated by Claude Code
Landed (2026-09-04T07:13Z,
domain:specseat,session_01H2oQebDDxYKfWZusyd8GXk): PR #15216 MERGED via the queue as squash1c6f7b4a(07:11:37Z); this card closed by itsFixesline. Probes onorigin/main1c6f7b4a: thecontentexclusion sentence present inpackages/spec/src/system/translation.zod.ts(1 hit; control: thehelpexclusion, 2 hits),.changeset/spec-text-content-exclusion-recorded.mdpresent.pm:dispatchedstripped in this stroke (read back). Lane close-out check since seating: only this card and #14180 (the predecessor's) closed — no silent close by a closing keyword. Downstream: the extractor/coverage question stays on #14749 (domain:cli).
Generated by Claude Code
- added a commit that references this issue
on Sep 29, 2026
Found while authoring a read-only record page in an application (
objectstack-ai/duly#13) with a completezh-CNbundle, on@objectstack/spec17.2.0.The gap
PageTranslation.components(packages/spec/src/system/translation.zod.ts) declares the per-component key face astitle | description | label | placeholder | emptyText | submitLabel.element:textrenders exactly one authored string,content, and it is not in that list. So a page that puts a sentence on screen through the element the platform provides for putting a sentence on screen has no bundle address for it.Why this reads as a missed key rather than a decision
The schema's own comment says the face was derived from the copy props components declare, and it names its two deliberate exclusions —
helpandsubtitle— with reasons.contentis neither named nor excluded; it is simply absent. Every other authored string on the same page has a key.What an application does today
The blessed alternative works: an inline
I18nLabelmap ({ en, 'zh-CN' }) oncontent, which is what the platform's ownsys-user.page.tsdoes. The reporting app uses it, and its coverage walk records those strings under anuntranslatableledger with this reason, pinned by count (16 strings on one page). That is a workaround with a cost: those strings are invisible toos i18n extract, tocheck:i18n-coverage, and to a translator working from the bundle, and each one is an inline map in a page definition instead of a row in the locale file.Suggested shape
Add
contenttoPageTranslation.components, resolved intranslatePageforelement:text(and any sibling element whose display string iscontent). If it is deliberately out — because inline maps are the intended route for page prose — the schema comment should say so besidehelpandsubtitle, so the next author finds a decision instead of an absence.Related: #14253 (three other authored display surfaces with no key; merged as #14381), #14376 (extractor coverage of new key families).
Triage — two corrections, and they move the card's centre of gravity
Measured at
origin/mainb8562ff262.(a) The face no longer carries
submitLabel. It left in@objectstack/spec17 (#10926, ADR-0049) and the current shape is five keys:titledescriptionlabelplaceholderemptyText(translation.zod.ts:927-931). Minor, but the card's premise is a census, so the census should be right.(b) ⭐ The inline map is not a workaround — it is the ruled route, and this exact question was decided twice.
packages/spec/src/ui/component.zod.ts:1784declarescontent: I18nLabelSchema, and the docblock above it (:1773-1783) records why:And the
submitLabelretirement one file over (translation.zod.ts:867-876) is the same shape, decided the same way:element:text.contentis anI18nLabelSchemalocalizable at its own authoring site. By the precedent, adding it to the bundle face is a face widening — the thing #10926 declined to do for the identical shape.⇒ The card's framing ("a missed key… neither named nor excluded") does not survive. What is genuinely missing is the sentence, not the key:
content's absence is a decision that was made elsewhere and never written down besidehelpandsubtitle. That is the card's own second option, and the precedent points at it.Why it is still a decision, and not simply closed
Because the card raises something the two rulings did not weigh. #5728 and #10926 both answered "can this string be localized?" — yes, at its authoring site. This card asks "can it be extracted and counted?" — and the answer is no. Sixteen strings on one page, invisible to
os i18n extract, tocheck:i18n-coverage, and to a translator working from the bundle. That is a new argument against a settled route, measured outside this repo, and it is the maintainer's to weigh rather than the seat's to wave through on precedent.<!-- os-decision-facets -->
element:text的content从裸字符串改成I18nLabelSchema(维护者点名);i18n: component-translationsubmitLabelcopy key lost its only declared carrier whenelement:formretired (#9249) — decide retire vs re-anchor #10926 把submitLabel从 bundle 面退役,原话是「在它自己的编写点可本地化,加到这张面上就是面的加宽」。⇒ ①指向不加键:把同一个字符串交给两条本地化路径,是特例增生而不是收敛。把content的缺席写成一条有理由的排除(与help、subtitle并列),才是缩小。submitLabelcopy key lost its only declared carrier whenelement:formretired (#9249) — decide retire vs re-anchor #10926 裁决时都没有被称量过 —— 那两次问的是「能不能本地化」,答案是能;本卡问的是「能不能被抽取和计数」,答案是不能。新论据,不是重提旧案。element:text那段找不到,而架构里没有任何东西告诉他这是有意的。help和subtitle都写了排除理由,content只是不在表里 —— 缺席与遗漏在读者眼里一模一样,他会合理地以为自己漏了什么,或者以为这是个 bug。⭐ 无论方向怎么裁,这一棱都必须被满足:要么补键,要么补那句话。推荐:A(卡面的第二个选项)—— 不加键,把排除理由写进 schema 注释,与
⚠️ 但 A 不解决 ②,必须说清楚:那 16 条字符串仍然对抽取器和覆盖率门禁不可见。所以荐 A 的同时建议另开一张卡问「内联
help、subtitle并列,引 #10926 的原话作依据。 ①④同向,③被它满足。I18nLabel映射该不该被os i18n extract与check:i18n-coverage看见」—— 那才是 ② 真正要的东西,而且它对所有用内联映射的字段都成立,不止element:text,因此比给一个组件补键更能关掉这一类。⛔ 本席不代开那张卡,那是拿到方向之后的事。回退:B(卡面的第一个选项) —— 把
content加进面并在translatePage里解析。若维护者认为 bundle 面才是页面散文的正路、内联映射只是过渡形态,走这条。代价:同一字符串两条路径,并且必须一并裁定两者同时存在时谁赢。置信缺口(本分析看不见什么): 没有量 objectui 侧
pickLocalized与 bundle overlay 的优先级。若走 B,一个content同时带内联映射和 bundle 条目时谁赢,是 B 上线前必须先答的问题 —— 本轮没量,而它决定 B 的真实代价是「加一个键」还是「加一个键 + 一条冲突规则」。Generated by Claude Code