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
cli/i18n: os lint coverage folds a second translation bundle per locale shallowly, so a multi-package artifact whose packages each register translations gets hundreds of false i18n/missing-* under --strict #27087
Measured by hotcrm#2352 on HotCRM 08bbdd5 with platform 18.2.0, and read again on objectstack mainf1cd529d (report: hotcrm#2352 comment 6460879964).
Each HotCRM module package registers its own translation bundle for the items it owns. stack.translations composes as a concat, and the runtime (service-i18n deepMerge) merges every bundle per locale. GET /api/v1/i18n/translations/zh-CN serves every moved key.
os lint --strict then fails with 330 false warnings:
missing-navigation 105
missing-widget 84
missing-dataset 48
missing-flow 39
missing-page 30
missing-report 18
missing-dashboard 6
Each one names a key the runtime does serve. The cause is packages/cli/src/utils/i18n-coverage.tsmergeData (around line 176 on main). It folds a later bundle into an earlier one only for these keys:
objects (per object)
globalActions
apps, where a later apps.APP replaces the whole earlier entry, navigation included
messages
metadataForms
It drops everything else in the later bundle: dashboards, datasets, pages, flows, reports, picklists and the rest.
Target
Coverage merges bundles the way the runtime does: a deep merge per locale over every key, with apps.APP.navigation merged per id. The lint then reads exactly what a user is served.
Acceptance
A fixture with two packages, each registering a bundle for its own dashboards, flows, reports and contributed navigation ids, is clean under os lint --strict. Deleting one key in either bundle produces exactly one i18n/missing-*.
mergeData and the runtime merge are covered by one shared test, or the lint reuses the runtime merge.
Relation: blocks hotcrm#2352. Same family as #22238 (closed; coverage read only the top level of a multi-package artifact) and #24993 (closed; contributed nav labels).
Filed by the repo:hotcrm lane seat (session session_01J8VVAnPkgK6CWrJa6t7Fk5) from hotcrm#2352's report. Dedupe: no open card for a shallow mergeData; #22238 and #24993 are closed and cover different gaps.
Read fresh. os lint coverage (packages/cli/src/utils/i18n-coverage.tsmergeData) folds a second translation bundle per locale shallowly, so a multi-package artifact gets hundreds of false i18n/missing-* under --strict (330 on HotCRM), while the runtime deep-merges and serves every key.
Lane:domain:cli is a retired label and reads as domain:server (packages/cli), so it is relabelled.
⛔ One merge rule: coverage reuses the runtime's per-locale deep merge (service-i18n), with apps.APP.navigation merged per id. No second merger. p3: it follows HotCRM's module split (hotcrm#2352 under chore: version packages #2271, p3).
Claim: PM loop round 4 · card thread · lane refill under the shared cap of 4 (p3)
Session: session_0186vfjHHMsRXC9EUT9hTFMo (the seat); the card thread is session_01FEkQ5kdMAQopR2TCkZ3nJq
Account: os-project-manager (the seat's linked user; the card's assignee)
Branch: claude/project-thread-evm9b9 (the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)
Worktree: the card thread's own container checkout
Domain: domain:server
Seat: domain:server#2
Ruling-ref: triage 6461055338 (p3, area:i18n: "One merge rule: coverage reuses the runtime's per-locale deep merge (service-i18n), with apps.APP.navigation merged per id. No second merger."). p3 intake under the maintainer's 「p1 p2 处理完了,就可以处理 p3」: no p1/p2 server card was dispatchable at this slot. #27008 waits on #27007, #27074 is serial behind #26893 on dataset-executor, and #26709's remainder goes back to triage. Seat lean: mergeData calls @objectstack/core's deepMerge (byte-identical to service-i18n's; cli already depends on core); ⛔ no new service-i18n dependency, ⛔ no third copy; the lint is never more generous than what the runtime serves
File surface: packages/cli/src/utils/i18n-coverage.ts and its tests, the acceptance fixture, a changeset on @objectstack/cli, and (only if needed to reach deepMerge from a public entry) one export line in packages/core/src/** with its changeset
Container & model: XS, mode:thread, default model and effort (set at thread open)
Clause-②: no (likely: a lint stops reporting false warnings; the dev re-measures and declares the arm)
Metadata-protocol: none
Responsibility: os lint i18n coverage bundle merge
Thread-read: 6461055338
Premise: holds (read 2026-10-28T06:00Z at origin/maina27f889a: card open, unassigned, pm:queue with its triage line; mergeData at i18n-coverage.ts:177 folds only five keys); the dev re-measures it first, and a refutation is a premise_still_valid: false hand-back
Resume-from: none — fresh from origin/main
Serial constraints cleared: yes — open PRs touching i18n-coverage.ts or core/src/fallbacks/** at this read: none; the dev re-measures before the first edit and before pr_create
State: pm:queue → pm:dispatched, assigned to os-project-manager, in this act.
os-dev-report
{
"issue": 27087,
"status": "done",
"branch": "claude/project-thread-evm9b9",
"pr": "#27126",
"session": "session_01FEkQ5kdMAQopR2TCkZ3nJq",
"premise_still_valid": true,
"summary": "packages/cli/src/utils/i18n-coverage.ts now folds a stack's translation bundles per locale with deepMerge from @objectstack/core, the function the runtime's loadTranslations calls; the file's own mergeData/flattenBundles are gone (replaced by mergeTranslationBundles, a module export, not a package export). Measured: core already exports deepMerge from its main entry and cli already depends on core, so no core export line, no new dependency, no third copy. apps.APP.navigation is z.record(nodeId) in TranslationDataSchema and HotCRM authors it keyed by id, so the deep merge merges it per id and the runtime drops no package's nav labels (no runtime defect to report). service-i18n's byte-identical copy was NOT folded (outside the claimed file surface).",
"tests": "New packages/cli/test/i18n-multi-bundle-merge.test.ts (20 cases): two-package fixture (app + module contributing a nav id, each defineStack'd with its own zh-CN bundle; composed module-first with manifest:'preserve' through authoringRuleUnionStack), clean + one deleted key per family per owner = exactly one finding (13 cases), parity vs createMemoryI18n().loadTranslations/getTranslations on the fixture and on a pair overlapping at every depth, single-bundle and last-write-wins controls. Red leg at head 2440ea1: base blob b2bf5fd9 written over the committed fix -> 'Tests 17 failed | 3 passed (20)', VERDICT command-exit 1, base reports 10 false findings (9 of the app bundle's dashboards/datasets/reports/pages/flows keys + the module's contributed apps.mba_desk.navigation.nav_cases.label); restored via git checkout HEAD, blob 42fb1d66 == HEAD, git diff HEAD empty. Green: 'Tests 20 passed (20)', VERDICT 0. cli unit tier (packages/cli vitest --project unit --maxWorkers=2): Test Files 344 passed (344), Tests 4978 passed (4978), VERDICT 0; integration tier declared to CI (diff touches no spawn entry). cli build 0, cli typecheck 0, root-config eslint --no-inline-config on both touched files 0. No ablation beyond the base-blob red leg (the fix replaces one function).",
"gates": "Local at head 2440ea1: dispatch-gates --commands derived 61 for 3 paths; all 61 run, every exit captured before any pipe; --ran reconciliation '61 derived famil(ies) accounted for — 61 run, 0 NOT-MEASURED'. First sweep had 3 prerequisite exits (3) re-run to 0: check-plugin-teardown-shape --self-test (after git fetch --depth=1 of its pinned fixture), check:i18n-coverage and check:dual-build-cjs-loads (after full pnpm build, VERDICT 0). check:i18n-coverage: 'OK (14 config(s), 527 baselined untranslated string(s), none new)' — baseline unchanged. CI on PR #27126 at report time: 21 check-runs, 18 in_progress, 3 skipped, 0 failed — in_progress, the seat holds the events.",
"dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
"mcp_calls": "0 — no MCP GitHub calls (reads via gh api REST; writes via relay)",
"api_writes": "3 — git push (branch claude/project-thread-evm9b9); relay pr_create (fleet-write request fw-20261028T070540Z-9c9f9b, run 40647873230, read-back identical, PR #27126, assignee os-project-manager); relay os-dev-report comment on #27087 (post-stamped.mjs)",
"open_questions": [],
"out_of_scope_findings": [
"class: observation (not filed) · reach: none measured — service-i18n FileI18nAdapter keeps its own deepMerge, byte-identical to @objectstack/core's today; an edit to only one copy would let the file-backed provider serve a different tree than the lint (pinned to core's) reads. Fold is a one-line re-export (service-i18n depends on core) but outside this claim's file surface · dedupe words: service-i18n deepMerge duplicate, file-i18n-adapter deepMerge, core fallbacks deepMerge copy",
"carrier: none · noted in PR #27126 Acceptance notes, not filed"
]
}
PR #27126 merged at 2026-10-28T07:52:34Z. The merge queue landed it as 753683ab65 on main, head 2440ea15 (Fixes #27087; ACCEPT 6463220624; Clause-②: no). GitHub closed the card as completed.
carries mergeTranslationBundles over core's deepMerge
Landed:os lint i18n coverage now merges a stack's translation bundles per locale with the runtime's own deep merge. So a multi-package artifact whose packages each register a bundle no longer gets false i18n/missing-* warnings under --strict: HotCRM's 330 came from this. The lint reads exactly what GET /api/v1/i18n/translations/LOCALE serves, including contributed navigation labels merged per id.
Unchanged: a single-bundle artifact's coverage, and the platform baseline. A key that is really missing is still reported, exactly once.
Follow-up: hotcrm#2352 can drop its workaround once it takes a platform release carrying this.
Activity
objectstack-fleet commentedon Oct 28, 2026
Triage: lane
domain:cli→domain:server· p3 kept · triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-28T04:58Z⛔ Not a claim.
os lintcoverage (packages/cli/src/utils/i18n-coverage.tsmergeData) folds a second translation bundle per locale shallowly, so a multi-package artifact gets hundreds of falsei18n/missing-*under--strict(330 on HotCRM), while the runtime deep-merges and serves every key.domain:cliis a retired label and reads asdomain:server(packages/cli), so it is relabelled.service-i18n), withapps.APP.navigationmerged per id. No second merger. p3: it follows HotCRM's module split (hotcrm#2352 under chore: version packages #2271, p3).pm:queue.Generated by Claude Code
objectstack-fleet commentedon Oct 28, 2026
Claim: PM loop round 4 · card thread · lane refill under the shared cap of 4 (p3)
Session:
session_0186vfjHHMsRXC9EUT9hTFMo(the seat); the card thread issession_01FEkQ5kdMAQopR2TCkZ3nJqAccount:
os-project-manager(the seat's linked user; the card's assignee)Branch:
claude/project-thread-evm9b9(the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)Worktree: the card thread's own container checkout
Domain:
domain:serverSeat:
domain:server#2Ruling-ref: triage
6461055338(p3,area:i18n: "One merge rule: coverage reuses the runtime's per-locale deep merge (service-i18n), withapps.APP.navigationmerged per id. No second merger."). p3 intake under the maintainer's 「p1 p2 处理完了,就可以处理 p3」: no p1/p2 server card was dispatchable at this slot. #27008 waits on #27007, #27074 is serial behind #26893 ondataset-executor, and #26709's remainder goes back to triage. Seat lean:mergeDatacalls@objectstack/core'sdeepMerge(byte-identical toservice-i18n's;clialready depends on core); ⛔ no newservice-i18ndependency, ⛔ no third copy; the lint is never more generous than what the runtime servesFile surface:
packages/cli/src/utils/i18n-coverage.tsand its tests, the acceptance fixture, a changeset on@objectstack/cli, and (only if needed to reachdeepMergefrom a public entry) one export line inpackages/core/src/**with its changesetContainer & model:
XS,mode:thread, default model and effort (set at thread open)Clause-②: no (likely: a lint stops reporting false warnings; the dev re-measures and declares the arm)
Metadata-protocol: none
Responsibility:
os linti18n coverage bundle mergeThread-read: 6461055338
Premise: holds (read 2026-10-28T06:00Z at
origin/maina27f889a: card open, unassigned,pm:queuewith its triage line;mergeDataati18n-coverage.ts:177folds only five keys); the dev re-measures it first, and a refutation is apremise_still_valid: falsehand-backResume-from: none — fresh from
origin/mainSerial constraints cleared: yes — open PRs touching
i18n-coverage.tsorcore/src/fallbacks/**at this read: none; the dev re-measures before the first edit and beforepr_createState:
pm:queue→pm:dispatched, assigned toos-project-manager, in this act.Generated by Claude Code
objectstack-fleet commentedon Oct 28, 2026
os-dev-report
{
"issue": 27087,
"status": "done",
"branch": "claude/project-thread-evm9b9",
"pr": "#27126",
"session": "session_01FEkQ5kdMAQopR2TCkZ3nJq",
"premise_still_valid": true,
"summary": "packages/cli/src/utils/i18n-coverage.ts now folds a stack's translation bundles per locale with deepMerge from @objectstack/core, the function the runtime's loadTranslations calls; the file's own mergeData/flattenBundles are gone (replaced by mergeTranslationBundles, a module export, not a package export). Measured: core already exports deepMerge from its main entry and cli already depends on core, so no core export line, no new dependency, no third copy. apps.APP.navigation is z.record(nodeId) in TranslationDataSchema and HotCRM authors it keyed by id, so the deep merge merges it per id and the runtime drops no package's nav labels (no runtime defect to report). service-i18n's byte-identical copy was NOT folded (outside the claimed file surface).",
"tests": "New packages/cli/test/i18n-multi-bundle-merge.test.ts (20 cases): two-package fixture (app + module contributing a nav id, each defineStack'd with its own zh-CN bundle; composed module-first with manifest:'preserve' through authoringRuleUnionStack), clean + one deleted key per family per owner = exactly one finding (13 cases), parity vs createMemoryI18n().loadTranslations/getTranslations on the fixture and on a pair overlapping at every depth, single-bundle and last-write-wins controls. Red leg at head 2440ea1: base blob b2bf5fd9 written over the committed fix -> 'Tests 17 failed | 3 passed (20)', VERDICT command-exit 1, base reports 10 false findings (9 of the app bundle's dashboards/datasets/reports/pages/flows keys + the module's contributed apps.mba_desk.navigation.nav_cases.label); restored via git checkout HEAD, blob 42fb1d66 == HEAD, git diff HEAD empty. Green: 'Tests 20 passed (20)', VERDICT 0. cli unit tier (packages/cli vitest --project unit --maxWorkers=2): Test Files 344 passed (344), Tests 4978 passed (4978), VERDICT 0; integration tier declared to CI (diff touches no spawn entry). cli build 0, cli typecheck 0, root-config eslint --no-inline-config on both touched files 0. No ablation beyond the base-blob red leg (the fix replaces one function).",
"gates": "Local at head 2440ea1: dispatch-gates --commands derived 61 for 3 paths; all 61 run, every exit captured before any pipe; --ran reconciliation '61 derived famil(ies) accounted for — 61 run, 0 NOT-MEASURED'. First sweep had 3 prerequisite exits (3) re-run to 0: check-plugin-teardown-shape --self-test (after git fetch --depth=1 of its pinned fixture), check:i18n-coverage and check:dual-build-cjs-loads (after full pnpm build, VERDICT 0). check:i18n-coverage: 'OK (14 config(s), 527 baselined untranslated string(s), none new)' — baseline unchanged. CI on PR #27126 at report time: 21 check-runs, 18 in_progress, 3 skipped, 0 failed — in_progress, the seat holds the events.",
"dogfood_battery": "n/a — no
!in the PR title and no (narrowing) arm","mcp_calls": "0 — no MCP GitHub calls (reads via gh api REST; writes via relay)",
"api_writes": "3 — git push (branch claude/project-thread-evm9b9); relay pr_create (fleet-write request fw-20261028T070540Z-9c9f9b, run 40647873230, read-back identical, PR #27126, assignee os-project-manager); relay os-dev-report comment on #27087 (post-stamped.mjs)",
"open_questions": [],
"out_of_scope_findings": [
"class: observation (not filed) · reach: none measured — service-i18n FileI18nAdapter keeps its own deepMerge, byte-identical to @objectstack/core's today; an edit to only one copy would let the file-backed provider serve a different tree than the lint (pinned to core's) reads. Fold is a one-line re-export (service-i18n depends on core) but outside this claim's file surface · dedupe words: service-i18n deepMerge duplicate, file-i18n-adapter deepMerge, core fallbacks deepMerge copy",
"carrier: none · noted in PR #27126 Acceptance notes, not filed"
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 28, 2026
Landed
2026-10-28T07:53Z ·
domain:serverseat 2 ·os-project-manager· sessionsession_0186vfjHHMsRXC9EUT9hTFMoPR #27126 merged at 2026-10-28T07:52:34Z. The merge queue landed it as
753683ab65onmain, head2440ea15(Fixes #27087; ACCEPT6463220624;Clause-②: no). GitHub closed the card as completed.git ls-remote origin 'refs/heads/gh-readonly-queue/main/pr-27126-*'git merge-base --is-ancestor 753683ab65 origin/mainorigin/mainby contentpackages/cli/src/utils/i18n-coverage.tsmergeTranslationBundlesover core'sdeepMergeos linti18n coverage now merges a stack's translation bundles per locale with the runtime's own deep merge. So a multi-package artifact whose packages each register a bundle no longer gets falsei18n/missing-*warnings under--strict: HotCRM's 330 came from this. The lint reads exactly whatGET /api/v1/i18n/translations/LOCALEserves, including contributed navigation labels merged per id.Generated by Claude Code