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 }}
Repository navigation
Inline I18nLabel maps are invisible to os i18n extract and check:i18n-coverage — should the extractor and the coverage gate see them? (objectstack#14412 ruling A follow-up) #14749
Filed by the director seat (objectstack #12708, summon #10, session session_01ShyhexkB2d1AeRZ85tgAAe) as the follow-up the maintainer's ruling on #14412 named. Filed unassigned; domain:cli (the extractor and the coverage gate), pm:queue.
Ruling this follows
Provenance: maintainer, live PM chat with the director seat, 2026-09-02, decision batch #13 item 3 on #14412, recommendation A, verbatim 「同意」. Ruled: element:text's content does not join PageTranslation.components; the inline I18nLabel map is the one localisation route for it (as #5728 and #10926 already ruled for the same shape), and the exclusion is written into the schema comment beside help and subtitle. The new argument #14412 carried, which neither earlier ruling weighed, is this card's question.
The gap
An authored string localised through an inline I18nLabel map ({ en, 'zh-CN', … } on the field) is real, ruled, and renders. It is also invisible to:
os i18n extract (the strings never reach the locale bundle as rows);
check:i18n-coverage (they are neither counted as covered nor as missing);
a translator working from the bundle (the strings live in page definitions, not in the locale file).
Measured outside this repo on objectstack-ai/duly#13: one read-only record page, 16 such strings, recorded by that app under an untranslatable ledger with this reason and pinned by count. The same applies to every field typed I18nLabelSchema that is authored inline, not only element:text.content, which is why this is one card for the class rather than a per-component key.
What is being asked
Whether the extractor and the coverage gate should walk inline maps, and if so in which direction:
A — extract inline maps into the bundle as rows keyed by their authoring site (page id + node path + prop), and let the coverage gate count locales present in the map as covered and missing ones as gaps. The map stays the source of truth; the bundle row is a projection for translators and counting. Needs a stable key scheme for node paths.
B — coverage only: the gate counts inline maps (present locales covered, absent ones missing) without extracting them; translators keep editing the page definition.
C — leave both as they are and document that inline maps are outside the extraction and coverage story.
Options are for the maintainer's decision inbox after the cli seat has measured the cost of a node-path key scheme; the seat files the four-facet block when it grades this card. Not a Clause-② change on its own unless A adds a documented bundle key family.
Related: #14412 (the ruling), #5728, #10926 (the two rulings that made inline maps the route), #14376 (extractor coverage of new key families), objectstack #12708 (director seat ledger).
Maintainer ruling recorded — A2 · B1 · C3: the key family stays as #14412 ruled; the gate stops reporting a written inline map as unauthored; no node-path key scheme
Director seat, summon #11, session session_01ShyhexkB2d1AeRZ85tgAAe, 2026-09-03.
Provenance (who / verbatim / where): maintainer, live PM chat with the director seat, 2026-09-03, replying to decision batch #18 in which this card was item 3, presented as the three questions the cli seat's measurement derived (5519940610 / 5519964231 / 5520017846) rather than the card's original A/B/C, with the recommendation A2 · B1 · C3. Verbatim reply: 「同意」.
Q2 — B1.check:i18n-coverage / the extractor stop recording a fully-written inline map under the same diagnostic as a key the author never wrote: a map present at an I18nLabel-typed prop is recorded as authored, with its locales counted as covered and absent locales as gaps — without extracting it, without a bundle row, without a new key family. The one-diagnostic-for-two-opposite-facts reading (inlineText() → undefined → pushOptional files it as unauthored) is the defect; it goes.
Q3 — C3. No node-path key scheme is built. The exclusion — inline maps are outside extraction and the bundle, by design — is written where an author reads it (i18n.zod.ts and the extractor's header). Recorded direction if extraction of inline maps is ever wanted: identity first (C2 — component.id / section.name / tabs item.value made mandatory and gate-enforced, then the existing pages.PAGE.components.ID.KEY family reused); ⛔ never array-index keys (C1), which turn a sibling reorder into a silent, all-green translation swap.
Execution: domain:cli lane, S — B1 plus the C3 prose; changeset level by measurement (a CLI diagnostic changes). ⛔ No key family, no extraction, no packages/spec schema change. Related, not folded: #14817 (the three platform record pages are in no extract config — bare, for triage's first grading) and #14816 (the stale "31" in i18n.zod.ts).
State transition, same stroke: needs-user-decision → pm:queue. Ledger: director seat post #12708, summon #11.
Claimed and dispatched — pm:queue → pm:dispatched.
Claim: domain:cli execution seat, session session_01ARYe3yQTQCUFm5qPYNgKaJ
Branch: claude/issue-14749-inline-i18nlabel-not-unauthored
Clause-②: no
⚠️ This seat's reading, ⛔ not a ruling; the dev re-declares from the diff. A gate stopping a false report changes no published contract face.
The ruling — settled, ⛔ not reopenable
Maintainer, director summon #11, recorded at 5522885154: A2 · B1 · C3 — the key family stays as #14412 ruled; the gate stops reporting a written inline map as unauthored; ⛔ no node-path key scheme.
That third clause is a refusal, and it is the one a round is most likely to drift into: the obvious way to make the gate see an inline map is to invent a key derived from the node's path. ⛔ Ruled out by name.
Surface
packages/cli/src/utils/i18n-extract.ts · packages/cli/src/commands/i18n/check.ts · packages/cli/src/commands/lint.ts (the extractor and the coverage gate — the dev confirms the real set).
⚠️ In-flight check, narrowed after a broad one misled
A lane-wide regex over filenames matching i18n flagged two PRs (#15888, #15334). Read per file, neither touches any of the three declared files — #15888 is spec/contracts/i18n-service.ts + a rest test + docs; #15334 is changesets. ⇒ free. A regex scoped to a lane is not a reading about a file — this is the third false hold it produced today.
{
"issue": 14749,
"status": "done",
"branch": "claude/issue-14749-inline-i18nlabel-not-unauthored",
"pr": "https://github.com/objectstack-ai/objectstack/pull/15980",
"premise_still_valid": true,
"summary": "B1 + C3 implemented. An expected entry now carries a third axis beside sourceValue and inline — inlineLocales, the inline I18nLabel map the author wrote, verbatim — and the three push* helpers are the one place that reads it, so every call site inherits the behaviour. Coverage answers per locale from the bundle first and then the map, counting locales the map carries as covered and locales it omits as gaps; the map is still never extracted (no seed, no bundle row, no key family) and no key is synthesised from a node path. C3 prose landed in packages/spec/src/ui/i18n.zod.ts and the extractor header, including the refusal WITH its reason and the recorded identity-first direction. A1 differs: packages/cli/src/utils/i18n-coverage.ts is the file that computes coverage and is NOT in A1, while commands/i18n/check.ts and commands/lint.ts needed no edit — both only call computeI18nCoverage. A2 confirmed free: neither #15888 nor #15334 touches the real surface. Clause-2 re-declared no.",
"tests": "ALL exit codes captured before any pipe (redirect-then-capture). Union re-run at head 0691987f3 after merging origin/main: vitest over test/i18n + platform-page-i18n-parity + lint-platform-fold + unbuilt-workspace-lead = 0 (20 files, 230 tests); pnpm --filter @objectstack/cli typecheck = 0; --filter @objectstack/cli build = 0; --filter @objectstack/spec build && check:generated = 0 (all 15 generated artifacts up to date); check:i18n-coverage = 0 verdict line \"check-i18n-coverage: OK (13 config(s), 621 baselined untranslated string(s), none new)\" — THE RATCHET DID NOT MOVE; check:i18n = 0 \"OK (9 package(s) — all bundles in sync)\"; check:i18n-walk-parity = 0; check:i18n-stale-fill = 0; check:app-nav-i18n = 0; check:cross-package-test-inputs = 0; check:test-source-alias = 0; check:cli-test-child-env = 0; check:changeset-gate-self-tests = 0; check:nul-bytes = 0; check:engine-double-contract = 0; check:where-matcher = 0; pnpm lint (WHOLE REPO, eslint . --no-inline-config) = 0 — no narrowing claimed. ABLATION (red leg): the fix was COMMITTED first; both changed source files were then restored from the merge base d4c2cb196 with git checkout, and the mutation was CONFIRMED ON DISK before any measurement — git hash-object equalled the origin/main blob for each file (d3c457d8/9892ed95, both non-empty and both different from the HEAD blobs), plus a grep control showing 0 occurrences of the new symbols. NO REBUILD WAS NEEDED and that is measured, not assumed: the pins import the CLI package by relative ../src path, so vitest reads source — positive control being that the pins exercised the new code while only the CLI dependency closure had ever been built, never @objectstack/cli itself; the only dist consumed is @objectstack/spec, which the ablation does not touch. Restore leg: git checkout HEAD -- both absolute paths from an EXIT/INT/TERM trap using an absolute REPO_ROOT, then proved by an empty git status --porcelain and an empty git diff HEAD, twice. RED: vitest exit 1, 10 failed | 5 passed (15). GREEN: vitest exit 0, 15 passed (15). Three verbatim red diagnostics: (a) false report silenced — objects.member.fields.email.help, map holding en/zh-CN/ja-JP with an en bundle row, \"expected [ ja-JP, zh-CN ] to deeply equal []\"; (b) TRUE report still fires and in fact starts firing — a partial map holding only en/zh-CN asked about ja-JP, \"expected [] to deeply equal [ ja-JP ]\", i.e. today gate says NOTHING about a genuinely absent locale; (c) a map leaking into a bundle seed, \"seed was object\". The 5 pins that pass on BOTH legs are the controls: an untranslated plain string still missing in every other locale, an empty map still not authoring, the retired { key, defaultValue } dialect still refused, no bundle row scaffolded for a map in any locale, and every emitted key still free of a numeric path segment.",
"mcp_calls": "5 — create_pull_request, search_issues (one targeted duplicate check after repo REST search returned 403), issue_write (filed #15984), update_pull_request, add_issue_comment (this report). All issue and comment READS went through unauthenticated repo-scoped REST (HTTP 200), not MCP.",
"open_questions": [],
"out_of_scope_findings": [
"filed as #15984: content/docs/ui/translations.mdx never mentions inline I18nLabel locale maps at all — a gap, not a falsehood, and newly consequential because an author writing a partial map now gets a real lint finding the guide gives them no vocabulary for. Read per file with a positive control (see summary of the docs reading in the PR body); same class as the accepted #15435.",
"NOT filed, reported instead (no measured instance): walkMetadataForms hands pushEntry inlineText(field.label) ?? humanizeFieldPath(path), so a map on a metadata-form field label would be recorded as authored with the HUMANIZED MACHINE PATH as its text. Different defect from this card (mis-recorded text, not mis-recorded authorship), and METADATA_FORM_REGISTRY carries no maps today, so filing it would manufacture work against a shape nobody writes. Left untouched deliberately: changing it would move the platform metadataForms baseline that check:i18n-coverage folds away.",
"PLATFORM READING, not a repo defect — a third measured shape for the AGENTS.md PR-body footer rule: update_pull_request/PATCH REMOVED the trailing attribution block entirely (rule line and footer both), rather than normalising the session-URL form DOWN to bare as the currently recorded measurement says. Read back and confirmed: 0 footers after the edit, body otherwise byte-intact. Not re-appended (the standing rule bans a re-append loop); durable attribution is carried in the PR body prose instead, which is exactly the remedy that rule prescribes."
]
}
Activity
os-project-manager commentedon Sep 3, 2026
Maintainer ruling recorded — A2 · B1 · C3: the key family stays as #14412 ruled; the gate stops reporting a written inline map as
unauthored; no node-path key schemeDirector seat, summon #11, session
session_01ShyhexkB2d1AeRZ85tgAAe, 2026-09-03.Provenance (who / verbatim / where): maintainer, live PM chat with the director seat, 2026-09-03, replying to decision batch #18 in which this card was item 3, presented as the three questions the cli seat's measurement derived (5519940610 / 5519964231 / 5520017846) rather than the card's original A/B/C, with the recommendation A2 · B1 · C3. Verbatim reply: 「同意」.
Ruled, per question:
contentandbodydo not joinPAGE_COMPONENT_COPY_KEYS. ThePageTranslation.componentshas no key forelement:text'scontent— the one string that element renders cannot be addressed by a bundle #14412 ruling of 2026-09-02 stands: the inlineI18nLabelmap is the localisation route forelement:text.content; one external application's twountranslatableledger rows are not pull enough to reverse a ruling one day old.check:i18n-coverage/ the extractor stop recording a fully-written inline map under the same diagnostic as a key the author never wrote: a map present at anI18nLabel-typed prop is recorded as authored, with its locales counted as covered and absent locales as gaps — without extracting it, without a bundle row, without a new key family. The one-diagnostic-for-two-opposite-facts reading (inlineText()→undefined→pushOptionalfiles it as unauthored) is the defect; it goes.i18n.zod.tsand the extractor's header). Recorded direction if extraction of inline maps is ever wanted: identity first (C2 —component.id/section.name/tabs item.valuemade mandatory and gate-enforced, then the existingpages.PAGE.components.ID.KEYfamily reused); ⛔ never array-index keys (C1), which turn a sibling reorder into a silent, all-green translation swap.Execution:
domain:clilane, S — B1 plus the C3 prose; changeset level by measurement (a CLI diagnostic changes). ⛔ No key family, no extraction, nopackages/specschema change. Related, not folded: #14817 (the three platform record pages are in no extract config — bare, for triage's first grading) and #14816 (the stale "31" ini18n.zod.ts).State transition, same stroke:
needs-user-decision→pm:queue. Ledger: director seat post #12708, summon #11.Generated by Claude Code
docs(spec): record why `element:text`'s `content` is off the page-com…
test(cli): put the three shipped platform record pages under an i18n …
claude commentedon Sep 5, 2026
Claimed and dispatched —
pm:queue→pm:dispatched.Claim:
domain:cliexecution seat, sessionsession_01ARYe3yQTQCUFm5qPYNgKaJBranch:
claude/issue-14749-inline-i18nlabel-not-unauthoredClause-②: no
The ruling — settled, ⛔ not reopenable
Maintainer, director summon #11, recorded at
5522885154: A2 · B1 · C3 — the key family stays as #14412 ruled; the gate stops reporting a written inline map asunauthored; ⛔ no node-path key scheme.That third clause is a refusal, and it is the one a round is most likely to drift into: the obvious way to make the gate see an inline map is to invent a key derived from the node's path. ⛔ Ruled out by name.
Surface
packages/cli/src/utils/i18n-extract.ts·packages/cli/src/commands/i18n/check.ts·packages/cli/src/commands/lint.ts(the extractor and the coverage gate — the dev confirms the real set).A lane-wide regex over filenames matching
i18nflagged two PRs (#15888, #15334). Read per file, neither touches any of the three declared files — #15888 isspec/contracts/i18n-service.ts+ a rest test + docs; #15334 is changesets. ⇒ free. A regex scoped to a lane is not a reading about a file — this is the third false hold it produced today.Generated by Claude Code
zhuangjianguo commentedon Sep 5, 2026
os-dev-report
{ "issue": 14749, "status": "done", "branch": "claude/issue-14749-inline-i18nlabel-not-unauthored", "pr": "https://github.com/objectstack-ai/objectstack/pull/15980", "premise_still_valid": true, "summary": "B1 + C3 implemented. An expected entry now carries a third axis beside sourceValue and inline — inlineLocales, the inline I18nLabel map the author wrote, verbatim — and the three push* helpers are the one place that reads it, so every call site inherits the behaviour. Coverage answers per locale from the bundle first and then the map, counting locales the map carries as covered and locales it omits as gaps; the map is still never extracted (no seed, no bundle row, no key family) and no key is synthesised from a node path. C3 prose landed in packages/spec/src/ui/i18n.zod.ts and the extractor header, including the refusal WITH its reason and the recorded identity-first direction. A1 differs: packages/cli/src/utils/i18n-coverage.ts is the file that computes coverage and is NOT in A1, while commands/i18n/check.ts and commands/lint.ts needed no edit — both only call computeI18nCoverage. A2 confirmed free: neither #15888 nor #15334 touches the real surface. Clause-2 re-declared no.", "tests": "ALL exit codes captured before any pipe (redirect-then-capture). Union re-run at head 0691987f3 after merging origin/main: vitest over test/i18n + platform-page-i18n-parity + lint-platform-fold + unbuilt-workspace-lead = 0 (20 files, 230 tests); pnpm --filter @objectstack/cli typecheck = 0; --filter @objectstack/cli build = 0; --filter @objectstack/spec build && check:generated = 0 (all 15 generated artifacts up to date); check:i18n-coverage = 0 verdict line \"check-i18n-coverage: OK (13 config(s), 621 baselined untranslated string(s), none new)\" — THE RATCHET DID NOT MOVE; check:i18n = 0 \"OK (9 package(s) — all bundles in sync)\"; check:i18n-walk-parity = 0; check:i18n-stale-fill = 0; check:app-nav-i18n = 0; check:cross-package-test-inputs = 0; check:test-source-alias = 0; check:cli-test-child-env = 0; check:changeset-gate-self-tests = 0; check:nul-bytes = 0; check:engine-double-contract = 0; check:where-matcher = 0; pnpm lint (WHOLE REPO, eslint . --no-inline-config) = 0 — no narrowing claimed. ABLATION (red leg): the fix was COMMITTED first; both changed source files were then restored from the merge base d4c2cb196 with git checkout, and the mutation was CONFIRMED ON DISK before any measurement — git hash-object equalled the origin/main blob for each file (d3c457d8/9892ed95, both non-empty and both different from the HEAD blobs), plus a grep control showing 0 occurrences of the new symbols. NO REBUILD WAS NEEDED and that is measured, not assumed: the pins import the CLI package by relative ../src path, so vitest reads source — positive control being that the pins exercised the new code while only the CLI dependency closure had ever been built, never @objectstack/cli itself; the only dist consumed is @objectstack/spec, which the ablation does not touch. Restore leg: git checkout HEAD -- both absolute paths from an EXIT/INT/TERM trap using an absolute REPO_ROOT, then proved by an empty git status --porcelain and an empty git diff HEAD, twice. RED: vitest exit 1, 10 failed | 5 passed (15). GREEN: vitest exit 0, 15 passed (15). Three verbatim red diagnostics: (a) false report silenced — objects.member.fields.email.help, map holding en/zh-CN/ja-JP with an en bundle row, \"expected [ ja-JP, zh-CN ] to deeply equal []\"; (b) TRUE report still fires and in fact starts firing — a partial map holding only en/zh-CN asked about ja-JP, \"expected [] to deeply equal [ ja-JP ]\", i.e. today gate says NOTHING about a genuinely absent locale; (c) a map leaking into a bundle seed, \"seed was object\". The 5 pins that pass on BOTH legs are the controls: an untranslated plain string still missing in every other locale, an empty map still not authoring, the retired { key, defaultValue } dialect still refused, no bundle row scaffolded for a map in any locale, and every emitted key still free of a numeric path segment.", "mcp_calls": "5 — create_pull_request, search_issues (one targeted duplicate check after repo REST search returned 403), issue_write (filed #15984), update_pull_request, add_issue_comment (this report). All issue and comment READS went through unauthenticated repo-scoped REST (HTTP 200), not MCP.", "open_questions": [], "out_of_scope_findings": [ "filed as #15984: content/docs/ui/translations.mdx never mentions inline I18nLabel locale maps at all — a gap, not a falsehood, and newly consequential because an author writing a partial map now gets a real lint finding the guide gives them no vocabulary for. Read per file with a positive control (see summary of the docs reading in the PR body); same class as the accepted #15435.", "NOT filed, reported instead (no measured instance): walkMetadataForms hands pushEntry inlineText(field.label) ?? humanizeFieldPath(path), so a map on a metadata-form field label would be recorded as authored with the HUMANIZED MACHINE PATH as its text. Different defect from this card (mis-recorded text, not mis-recorded authorship), and METADATA_FORM_REGISTRY carries no maps today, so filing it would manufacture work against a shape nobody writes. Left untouched deliberately: changing it would move the platform metadataForms baseline that check:i18n-coverage folds away.", "PLATFORM READING, not a repo defect — a third measured shape for the AGENTS.md PR-body footer rule: update_pull_request/PATCH REMOVED the trailing attribution block entirely (rule line and footer both), rather than normalising the session-URL form DOWN to bare as the currently recorded measurement says. Read back and confirmed: 0 footers after the edit, body otherwise byte-intact. Not re-appended (the standing rule bans a re-append loop); durable attribution is carried in the PR body prose instead, which is exactly the remedy that rule prescribes." ] }Generated by Claude Code
docs(spec): record why `element:text`'s `content` is not a `PageTrans…
test(cli): put the three shipped platform record pages under an i18n …