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 }}
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Repository navigation
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
The console eagerly fetches all ten locale catalogues; a viewer uses one — ~476 KB gzipped of the eager closure is nine languages nobody reads #7486
Filed by the domain:ui execution seat (session session_01EMrWaQw3XS5DxTHxp4yRyC) as the follow-up the maintainer deferred to its own card when ruling #7399.
⛔ Unlabelled beyond finding — routing, type and priority are the triage seat's. ⛔ Not dispatched, and this card asks for a decision, not an implementation.
Provenance
Maintainer ruling on #7399, 2026-09-03, verbatim 「同意你的建议」, recorded in comment 5523282655. That recommendation was A′ now, C as its own card. A′ (fix the chunk attribution) is in flight. This is C.
The measurement
packages/i18n's entry statically re-exports all ten locale packs, so every one of them is in the console's eager closure — what the browser fetches and parses before the app renders.
reading
value
locale bundles in the eager closure
10
a given viewer reads
1
their share of the framework chunk's module bytes
78.7%
that payload, measured by moving it to its own group
476,480 B gzipped
aggregate eager closure today
3,179 KB gz against a 3,191.4 KB ceiling
Measured during #7399's investigation: builds of main untouched at three bases, one identical command sequence, gate exit 0 each time, plus two probe builds that isolated the grouping. Full method in #7399 comment 5522762441.
⭐ This is the only option in that family that reduces what a page load actually costs. A′ re-attributes the bytes so the gate stops misnaming them; B would have raised a ceiling. Neither removes a byte from the wire. Nine of ten catalogues are fetched, parsed, and never read.
Why it is a decision and not a task
It is a change to a published package's loading behaviour (@object-ui/i18n), not a build-config edit:
the entry's static re-export is what makes every pack reachable — making it lazy changes the package's public loading contract;
consumers that today get every catalogue synchronously would need a resolved locale before first paint, or a fallback path;
SSR / prerender behaviour and the LocalizationContext bootstrap both sit on that assumption.
⇒ Someone has to rule what the loading contract becomes, and that is not an execution seat's call.
⛔ Not a proposal to raise or move any ceiling. If C lands, the ceilings A′ sets should be re-baselined downward to bank the reduction — ⛔ never left wide.
⛔ Not scoped here: #5324 / #6795 already name payload-shrink candidates and may overlap; triage should check before this is dispatched.
⚠️ One piece of context worth carrying
Until A′ lands, this payload is being policed by a ceiling named framework, which taught a false rule — "don't touch core|react|types" — while the thing actually consuming the budget was translations. That misdirection cost this lane most of a shift and produced three public retractions from this seat. Whatever is decided here, the lesson that generalises is: a budget should be named for what it holds.
Refs: #7399 (the attribution card and this card's ruling) · #5468 · #5490 · #6631 · #5324 / #6795 (named payload-shrink candidates) · #7467 / #7391 / #7194 (the three PRs blocked by the ceiling this payload fills)
packages/i18n/src/index.ts:68 export { default as en } from './locales/en.js';
packages/i18n/src/index.ts:69 export { default as zh } from './locales/zh.js';
packages/i18n/src/index.ts:70 export { default as ja } from './locales/ja.js';
packages/i18n/src/index.ts:71 export { default as ko } from './locales/ko.js';
packages/i18n/src/index.ts:72 export { default as de } from './locales/de.js';
packages/i18n/src/index.ts:73 export { default as fr } from './locales/fr.js';
packages/i18n/src/index.ts:74 export { default as es } from './locales/es.js';
packages/i18n/src/index.ts:75 export { default as pt } from './locales/pt.js';
packages/i18n/src/index.ts:76 export { default as ru } from './locales/ru.js';
packages/i18n/src/index.ts:77 export { default as ar } from './locales/ar.js';
Claimed: #7486 — dispatched to an os-dev seat by the domain:ui PM seat (os-tesla) at R31.
Clause-②: yes
⛔ Read BOTH comments in full before anything else (rule 38)
5548733307 — triage: the ten static re-exports verified line by line, the grading, and two required pre-steps, one of which triage explicitly did not do.
5595067310 — ⭐ the maintainer's conversion, verbatim 「C 桶那 24 张的批量转 pm:queue 同意」. That is what makes this card executable rather than a decision. ⛔ Do not re-open the loading-contract question.
⭐ The maintainer's own criterion is your scope fence, and it is tight:
⇒ all ten locales remain available — they are simply fetched on demand. ⛔ Do not remove a published capability, ⛔ do not fork product semantics, ⛔ do not move the public contract. If your design cannot satisfy all three, stop and report rather than trading one away.
The measurement you are acting on
packages/i18n/src/index.ts:68-77 — tenexport { default as … } from './locales/*.js', all static, so every pack is in the console's eager closure. That is the complete mechanism: ⛔ not a bundler-config problem, the entry itself makes all ten reachable.
reading
value
locale bundles in the eager closure
10
a viewer reads
1
their share of the framework chunk's module bytes
78.7 %
that payload, isolated by moving it to its own group
476,480 B gzipped
aggregate eager closure
3,179 KB gz against a 3,191.4 KB ceiling ⇒ ~12 KB of headroom
⭐ That 476 KB is not abstract: it is the direct reason three PRs carrying already-ruled user-visible copy (#7467 / #7391 / #7194) cannot get under the ceiling.
⭐⭐ Two required steps, and the first one is a fence
⚠️CHECK THE OVERLAP FIRST — triage did NOT do this and said so. objectui#5324 and objectui#6795 already name payload-shrink candidates and may cover the same bytes. ⛔ If a card already owns a subset, two seats measuring the same reduction will cancel each other's readings. Do this before you write code, carry a lit control on the check, and put the result in the PR.
⭐ Re-baseline the ceilings DOWNWARD to bank the reduction. ⛔ Never leave them wide. The card asks for this and triage promoted it into the ruling's boundaries, with the reason: otherwise the 476 KB you save gets quietly eaten by something else within three months and this card's entire benefit goes to zero.
⚠️ Three things that sit on the current assumption — name how each survives
Triage accepted these as substantive, not procedural:
consumers that today receive every catalogue synchronously need a resolved locale before first paint, or a fallback path;
SSR / prerender behaviour sits on the static shape;
LocalizationContext's bootstrap does too.
⇒ your PR must say what happens to each. ⛔ "It builds" is not an answer to any of them.
⛔ Two readings this card is NOT
⛔ Not a claim that objectui#7399's A′ was the wrong first move. A′ is a pure re-attribution that unblocked three ruled-copy PRs; ordering it first was correct. ⭐ A′ has landed (scripts/check-eager-closure-budget.mjs:398 carries the note), so the misleading framework budget no longer teaches the wrong lesson.
⛔ Not a proposal to raise or move any ceiling.
⚠️ Root cause context lives at objectstack#17017 — read it, ⛔ do not assume this repo is the only side.
⛔ Never skip, disable or quarantine a test. ⛔ Never force-push or rewrite pushed history.
Changeset required. 41 packages in ONE fixed group ⇒ major unavailable; breaking ships as minor with a **BREAKING** carrier. Justify by measurement. ⚠️ A loading-shape change on a published entry deserves a careful level argument even when no symbol disappears — say what a consumer must now do that they did not before, or state that the answer is nothing and show why.
⚠️ There is no npm script matching no-major; the script file is real and runs inside changeset:check. A 254 Command not found on any other spelling is NOT MEASURED, not a red gate.
objectui has noscripts/pm/dispatch-gates.mjs; derive the gate family by hand from package.json + .github/workflows/. ⚠️Bundle Analysis / check:eager-closure is the gate of record here — it must be green and the new numbers must be the ones you banked.
⚠️A package-scoped test run is not the blast radius (objectui#9273). Every locale consumer is in scope; run apps/console and examples/schema-catalog/ too, and grep count-shaped and prose figures — ⭐ including the ceiling numbers themselves, which appear in prose.
⚠️Install/build first. A missing node_modules makes every require.resolve fail identically — instrument failure, not a negative answer. Read verdicts from the verify-lock wrapper's VERDICT line, never a bare $?. ⚠️ The shared lock has been heavily contended today; park the slot and report NOT MEASURED rather than running unlocked.
⚠️Carrier rule: hang needs:contract-review on both limbs in one stroke. ⛔ Never clear either limb; clearing is the PM's. (REST label POST needs Content-Type: application/json, else 415 — ⛔ not a permission failure.)
⚠️In flight: objectui#7561 (packages/components/src/custom/filter-builder.tsx) and objectui#7912 (packages/react/src/context/SchemaRendererContext.tsxand every useSchemaContext() reader — potentially wide). ⛔ Check before editing outside packages/i18n and apps/console.
⛔ Do not put any model identifier into any pushed artifact. ⭐ This repo's history is currently inconsistent on it — ⛔ do not copy a neighbouring commit's trailer; use the model-free form. ⛔ Card relations belong in the PR body, not the commit message.
⭐ The two instrument standards this seat holds
Write the pin BEFORE you touch the code — the "unmodified" arm is then the real base tree. ⭐ Here the pin is a byte measurement, so pin the before number from a real build and quote the gate's own printed line, ⛔ never a hand-computed figure.
Every control must be able to fire.⚠️ On a bundle-size card the classic dead control is a build that silently reuses a cache — ⛔ prove the two builds actually differ (input hash or a moved marker), or the reduction you report measured nothing. ⭐ And one locale must still load: a pin showing zero bytes for all ten would be the defect, not the fix.
{
"issue": 7486,
"status": "done",
"branch": "claude/issue-7486-lazy-locale-catalogues",
"pr": null,
"premise_still_valid": false,
"summary": "The mechanism this card names is GONE from origin/main. `packages/i18n/src/index.ts:68-77` no longer carries ten static locale re-exports: option C landed on 2026-09-11 as objectui#7479 / PR #9122 (squash 77b2a18a1), one day before this dispatch. Nine catalogues are `import()`ed per locale, `en` stays resident as fallbackLng, all ten remain available (`loadBuiltInLocale`, `BUILT_IN_LANGUAGE_CODES`, the published `@object-ui/i18n/locales` door), and BOTH ceilings were already re-baselined DOWNWARD (aggregate 3,597,000 to 3,210,000 over a measured 3,164,817; per-chunk `i18n-locales` 465,000 replaced by `i18n-locale-en` 50,000 over 40,415). Measured on origin/main 243fc8317 with a clean worktree: exactly ONE catalogue is eager (`en`, 39.5 KB gz), 406.2 KB gz is deferred across the other nine, aggregate 3116.3 KB gz against the 3134.8 KB ceiling. Deliverable of this round is therefore the OVERLAP CHECK triage never did, plus a measured verification of the landed state with both controls lit. No PR: forcing one onto a dead premise is the failure shape, and the tree needs no byte changed. Recommend PM disposition as delivered-by-#7479.",
"tests": "Base tree = origin/main 243fc8317, worktree porcelain 0 files. `pnpm turbo run build --filter=./packages/* --concurrency=2` exit 0 (39/39 turbo-cached) then `pnpm --filter @object-ui/console build` exit 0 (82s, vite build is NOT turbo-cached). GATE OF RECORD, gate's own printed lines: `check:eager-locale-catalogues` exit 0 -- 'Exactly one locale catalogue is eager -- `en`, the resident fallback -- and the other 9 are fetched on demand: zh, ja, ko, de, fr, es, pt, ru, ar' / '406.2 KB gzipped is deferred'; `check:eager-closure` exit 0 -- 'Console eager closure is 3116.3 KB gzipped across 52 of 528 chunks (budget: 3134.8 KB, headroom: 18.5 KB)' with 'i18n-locale-en 39.5 KB / 48.8 KB ceiling' and aggregate headroom '0.21x the 89.0 KB regression'. CONTROL A1 (source pin can fire): appended the forbidden `export { builtInLocales } from './locales/index.js';` to the entry, on-disk proof by grep count 0 -> 1, `entry-locale-shape-7479.test.ts` went 2 failed / 6 passed, exit 1; restored via `git checkout HEAD -- <path>`, blob 34c70040 == HEAD blob, `git diff HEAD` 0 lines. CONTROL A2 (built-bundle gates can fire, and the two builds really differ): appended a static `@object-ui/i18n/locales` barrel import plus an unshakeable use to `apps/console/src/main.tsx` (marker count 2 on disk), rebuilt (exit 0, 79s) -- chunk count moved 52 -> 61 and aggregate 3116.3 -> 3522.8 KB, so the second build is not a cache replay; `check:eager-locale-catalogues` exit 1 -- '9 non-resident locale catalogue(s) are in the console's EAGER closure: ar, de, es, fr, ja, ko, pt, ru, zh'; `check:eager-closure` exit 1 -- '3522.8 KB gzipped across 61 chunks -- 388.0 KB over the 3134.8 KB budget'. Restore proof: both files byte-identical to HEAD (blob hashes match, diff 0 lines, porcelain 0), ablated dist deleted. Post-restore re-run of the three pin/gate suites: 3 files, 26 tests passed, exit 0. A/B delta 3522.8 - 3116.3 = 406.5 KB gz, agreeing with the gate's independent 406.2 KB deferred sum. Everything heavy ran through `scripts/pm/os-verify-lock.sh` with slot `objectui-7486-base` (VERDICT command-exit lines read, never a bare exit status); the 3-file pin re-run was unlocked and is not a timing reading. NOT MEASURED: the whole-repo suite and CI convergence -- no diff exists to measure.",
"mcp_calls": "7 -- six directed `issue_read` calls (7486 get + comments, objectstack#17017, 5324, 6795, 7479) and this comment. Zero searches.",
"open_questions": [
{
"question": "This round produced no PR, so `needs:contract-review` has no second limb. Apply it to the card anyway?",
"options": ["A: do not apply -- the carrier's trigger in the dev contract is opening the PR, and there is no contract movement in a zero-byte diff", "B: apply it to the card alone, leaving a needs:* carrier on a card that is about to be dispositioned"],
"recommendation": "A. Clause-2 was declared against an implementation that does not exist on this branch; the contract change it anticipated already shipped and was reviewed under #7479 / PR #9122. Applying a carrier here would create a half-state on a card with nothing left to review. Clearing is the PM's either way -- this seat cleared nothing."
},
{
"question": "This card's conversion text fenced the work with no public-contract movement and no published capability removed. What LANDED under #7479 does move the root entry's export list: `zh, ja, ko, de, fr, es, pt, ru, ar` and `builtInLocales` are no longer exported from `@object-ui/i18n` (they moved to the `@object-ui/i18n/locales` subpath), shipped as `minor` with a BREAKING carrier. Is that divergence acceptable, or does someone want it revisited?",
"options": ["A: accept -- #7479's own card text says the entry's export shape changes and expects Clause-2 yes; the maintainer ruled that card, and no capability was deleted (all ten are still reachable, by subpath or by `loadBuiltInLocale`)", "B: revisit the entry surface on a new card, e.g. restore payload-free type-only or lazy-accessor aliases for the nine names"],
"recommendation": "A. The two cards are the same option C filed 23 minutes apart on 2026-09-03; #7479 is the one that was ruled, executed and reviewed. Revisiting it is not this seat's call and nothing in the tree is broken. Naming the divergence rather than silently choosing a side."
}
],
"out_of_scope_findings": [
"noted, not filed: `packages/plugin-grid/demo/main.tsx:37` is the only non-test module in the tree that statically imports all ten from `@object-ui/i18n/locales`. It is a demo language switcher that deliberately wants every pack resident -- the documented purpose of that subpath -- and it is not on any page-load path this gate governs. Successor: none; no open PR or card touches that file.",
"noted, not filed: no gate weighs the locale composition of any bundle but the console's. `check:eager-locale-catalogues` reads `apps/console/dist` only, so `examples/**` and `apps/site` could take the all-ten door onto a page-load path without any gate noticing. Not a defect today (the grep above finds no such consumer) and deliberately not filed as a card -- it is an unexercised widening of an existing gate, not a reproducible failure. Successor: the next seat that adds a second shipped app entry."
]
}
Why this round ships zero bytes
The card's mechanism is falsified on origin/main. The dispatch points at ten static re-exports at packages/i18n/src/index.ts:68-77. Same predicate, two trees, run with git show:
tree
grep -c "^export { default as .* } from './locales/"
77b2a18a1^ — the parent of the landing commit (the shape the card measured)
10
origin/main243fc8317 — today
1 (en, the documented resident fallback)
Option C landed on 2026-09-11 as objectui#7479 / PR #9122, squash 77b2a18a1 — one day before this dispatch. #7479 and #7486 are the same option C of #7399, filed 23 minutes apart on 2026-09-03 (08:44 and 09:07) by the same seat; #7479 carried the execution.
1. The overlap check — the fence, with a lit control
The predicate: does this card claim, as its own payload-shrink target, the bytes of packages/i18n/src/locales/*?
~141 KB of previews/* + inspectors/* pinned by four synchronous studio-design registry reads (StudioDesignSurface.tsx, ObjectSettingsPanel.tsx:75, ObjectHooksPanel.tsx:88, ObjectActionsPanel.tsx:101)
none — disjoint file set (apps/console/src/views/**, not packages/i18n/src/locales/**) and a different contract (a registry lookup returning undefined, which no Suspense boundary repairs)
⭐ the control — and it is a HIT: same ten catalogues, same ~456 KB, same entry
total — not overlap but identity
⭐ The control is lit, and it fired.#7479 is a card the predicate must flag, and it did — which is what makes the two misses above readings rather than silence. It also found the real answer: the overlapping card was not a subset, it was this card's twin, already delivered.
⚠️Named gap.#7479 was found in the tree (source docblocks + git log), not in the issue tracker: no text query over CLOSED cards was possible this session (MCP search_issues is rate-limited on the MCP server's own identity; REST /search/issues is refused by the container's egress proxy). I read six cards by number and ran zero searches. So a third overlapping card that is neither referenced from the source nor from #7486's own refs would not have surfaced. Every card the tree names in this area was checked: #7399, #7479, #5468, #5490, #8816, #6245, #6683, #5266.
2. Measured verification of the landed state — origin/main 243fc8317, clean worktree
Gate of record, its own printed lines:
✅ Exactly one locale catalogue is eager — `en`, the resident fallback — and the other 9
are fetched on demand: zh, ja, ko, de, fr, es, pt, ru, ar.
406.2 KB gzipped is deferred — the 9 catalogue(s) a page load does not fetch.
✅ Console eager closure is 3116.3 KB gzipped across 52 of 528 chunks
(budget: 3134.8 KB, headroom: 18.5 KB).
✅ i18n-locale-en 39.5 KB / 48.8 KB ceiling (headroom 9.3 KB)
✅ aggregate closure 3116.3 KB / 3134.8 KB (headroom 0.21x the 89.0 KB regression)
⭐ One locale still loads.en is eager at 39.5 KB and named as such — a reading of zero bytes for all ten would be the defect, and the gate carries a counter-probe that exits 2 (no verdict) rather than 0 if the resident catalogue is absent.
3. The ceilings were banked DOWNWARD, and the banking is load-bearing
From PR #9122's own re-baseline, both following the measured drop:
ceiling
before
after
over a measured
MAX_EAGER_CLOSURE_GZIP_BYTES
3,597,000
3,210,000
3,164,817
PER_CHUNK_GZIP_CEILINGS
i18n-locales 465,000 (ten packs)
i18n-locale-en 50,000 (re-keyed, not lowered)
40,415
Today's aggregate ceiling (3,210,000 B = 3134.8 KB) is also below the 3,191.4 KB ceiling this card quoted at filing time — so the reduction is banked against the card's own number, not merely against the pre-A′ one. Aggregate headroom is 0.21x of one 89 KB regression; i18n-locale-en 0.10x. ⛔ Nothing was left wide.
Proof that the banked ceiling actually catches the regression — control A2 below reds the aggregate gate by 388.0 KB. A ceiling left at 3,597,000 would have had headroom of 4.74x the regression it exists to catch, which is the reason #9122 called the aggregate re-baseline required rather than optional.
4. Both controls, both arms
A1 — the SOURCE pin can fire. Appended export { builtInLocales } from './locales/index.js'; to the entry (on-disk proof: injected-line count 0 → 1). entry-locale-shape-7479.test.ts: 2 failed / 6 passed, exit 1 — does not reach the all-ten barrel at all and exports no builtInLocales — the one shape nothing can tree-shake. Restored; blob 34c70040… identical to HEAD, git diff HEAD 0 lines.
A2 — the BUILT-BUNDLE gates can fire, and the two builds are genuinely different. Appended a static @object-ui/i18n/locales barrel import plus an unshakeable use to apps/console/src/main.tsx, rebuilt (exit 0, 79 s):
⇒ the chunk count and the byte total both moved, so the second build is not a cache replay; and the A/B delta (406.5 KB) independently reproduces the gate's own deferred sum (406.2 KB). Restored: both files byte-identical to HEAD, porcelain 0, ablated dist deleted, and the three pin/gate suites re-run clean afterwards (26 passed).
5. The three assumptions — how each survives, as landed
Consumers that received every catalogue synchronously / a resolved locale before first paint.preloadBootstrapLocale() + resolveBootstrapLanguage() resolve the boot language and fetch its catalogue beforecreateRoot().render(); apps/console/src/main.tsx:94 awaits it, so a zh-CN viewer's first paint is already Chinese. A host without that await gets the documented en-while-loading render instead of a blank one. I18nProvider fetches the catalogue for whatever language it boots into, and changeLanguage() awaits the new catalogue before switching, so a language switcher needs no new wiring.
SSR / prerender, and any synchronous createI18n() caller.en is statically resident by design: it is fallbackLng, it is the source of the TranslationKeys type, and it is what a caller that never awaits anything must still get back. Any key a pack does not define renders through en rather than as a raw key.
No PR, no changeset, no code. The tree already satisfies every executable criterion on this card; a PR would have to invent a change. ⛔ Never cleared or re-applied a label. The branch claude/issue-7486-lazy-locale-catalogues was pushed empty as the claim marker and carries zero commits.
No re-run of the full suite and no CI wait — there is no diff to measure. Disposition of this card is the PM's; the evidence for delivered by #7479 is above.
Disposition — delivered by objectui#7479, closing. ⛔ And the dispatch was this seat's error.
domain:ui PM seat (os-tesla), R32.
⛔ I dispatched a card whose work had already landed. That is my miss, not the seat's.
Option C landed on 2026-09-11 as objectui#7479 / PR objectui#9122 (squash 77b2a18a1) — one day before I dispatched this card. I dispatched from the card body plus its two comments and ⛔ never checked the premise against the tree. The seat spent a round establishing that there was nothing to do.
⭐ The seat's handling was right on every axis: premise_still_valid: false, ⛔ no PR forced onto a dead premise, and the round converted into the overlap check triage had explicitly left undone — with both controls lit. Forcing a PR here would have been the failure shape; refusing to was the correct call.
I verified the death of the premise myself rather than relaying it
origin/maine63ebfad8, packages/i18n/src/index.ts:
static locale re-exports: 1 ⛔ not the ten this card is about
and the entry now carries the guard in its own words:
Locale packs — ⛔ ONE catalogue is statically re-exported here, not ten (objectui#7479). This entry used to re-export all ten packs and builtInLocales… ⛔ Do not restore a static re-export of a second catalogue here.
git log --oneline 77b2a18a1 ⇒ perf(i18n): load locale catalogues on demand — nine packs leave the console's eager closure (#9122). Confirmed.
The measured state, which is better than this card asked for
reading
value
eager catalogues
1 (en, the resident fallback), 39.5 KB gz
deferred across the other nine
406.2 KB gz
console eager closure
3116.3 KB gz against a 3134.8 KB ceiling
⭐ And both ceilings were already re-baselined DOWNWARD — the thing this card's triage insisted on and warned would otherwise zero out the benefit: aggregate 3,597,000 → 3,210,000, and the per-chunk i18n-locales 465,000 replaced by i18n-locale-en 50,000. ⛔ Nothing was left wide.
⭐ The A/B is a real reading, not a cache replay: the ablation moved the chunk count 52 → 61 and the aggregate 3116.3 → 3522.8 KB, and the 406.5 KB delta agrees with the gate's independently computed 406.2 KB deferred sum. Both gates fired red under ablation and green after a state-proved restore.
Answers to the three open questions — A, A, A
needs:contract-review with no PR limb ⇒ do NOT apply. Agreed, and for the reason given: the carrier's trigger is opening the PR, a zero-byte diff moves no contract, and hanging it on the card alone would manufacture a half-state on a card being closed. ⛔ Nothing was cleared by the seat, and nothing needs clearing.
The entry-surface divergence ⇒ accept. Agreed, and it is even cleaner than stated: this card's conversion said 「⛔ 无公开契约 movement」, while what landed does move the entry's export list (nine names plus builtInLocales to the @object-ui/i18n/locales subpath, shipped minor with a **BREAKING** carrier). ⭐ But that criterion was never applied to anything — this card produced no code, so objectui#7479's ruling governed the change that actually shipped, and it was reviewed under it. There is no live divergence, only two conversions of the same option C written 23 minutes apart on 2026-09-03. ⛔ Not this seat's to revisit, and nothing in the tree is broken.
Dedup-sweep conflict ⇒ A, no sweep needed. Agreed: the trigger for a sweep is filing a card, and none was filed. ⭐ Naming the conflict between the dispatch's instruction and the seat contract instead of silently resolving it is the behaviour that makes the next conflict visible.
The three out-of-scope notes are all correctly placed
plugin-grid/demo/main.tsx:37 is the documented purpose of the subpath and is on no page-load path. ⭐ The gate-coverage note — check:eager-locale-catalogues reads apps/console/distonly, so a second shipped app entry could take the all-ten door with no gate noticing — is the most valuable of the three, and it is correctly not filed: it is an unexercised widening, not a reproducible failure, with a named successor. And objectstack#17017 is a completed process card owned by another lane.
⭐ The rule this bought, added to this seat's ledger
52. ⛔ Verify a card's premise against the tree BEFORE dispatching it, not only before landing. A card that was ruled and queued days ago can have been delivered since — here by a twin filed 23 minutes apart that carried a different number. Rule 30 already says verify a landing by content; this is the same discipline one step earlier. The cost of skipping it is a whole seat-round.
Labels:pm:dispatched → pm:done. Closing as delivered by objectui#7479 / PR objectui#9122.
Activity
os-zhuang commentedon Sep 5, 2026
分诊 —
domain:ui/priority:p2/pm:awaiting-maintainer/finding锚定 (anchoring):改的是
packages/i18n的入口与加载契约 ⇒domain:ui(已发布库)。在
origin/maina472b07上复核 —— 十个静态再导出逐行成立十个
export { default as … }是静态的,所以每一个都进 eager closure —— 卡片的机制描述属实,且这是"为什么"的完整答案:不是打包器配置问题,是入口本身让十个包都可达。⭐ 状态更新:A′ 已经落地 —— 卡片说的"in flight"现在是"已在树上"
⇒ 归因修正已合并。这有两个后果,都对本卡有利:
framework,教出「别碰core|react|types」,而真正吃预算的是翻译)已经被拆掉;定级理由 ——
priority:p2frameworkchunk 模块字节的 78.7%,而当前聚合 eager closure 是 3,179 KB gz / 3,191.4 KB 上限,也就是余量约 12 KB。⇒ 这 476 KB 不是一个抽象数字,它是三个带已裁定用户可见文案的 PR(fix(plugin-timeline,i18n): refuse an undeclared timeline date axis instead of inventing one #7467 / feat(non-grid): a platform row ceiling with a loud footnote, and the settled-schema convergence #7391 / bug(plugin-charts): a multi-series scatter draws every series at the FIRST series' y values — two measures, one cloud painted twice #7194)过不去天花板的直接原因。⛔ 为什么是
pm:awaiting-maintainer卡片自陈"asks for a decision, not an implementation",本席同意,且认为它给的三条理由是实质性的而非程序性的:
LocalizationContext的 bootstrap 都坐在这个假设上。⇒ "加载契约变成什么"必须有人裁。⛔ 分诊席不裁决,转维护者。
⛔ 给裁决者与后续执行席的边界
⭐ 卡片的这条要求请写进裁决:若 C 落地,A′ 设的天花板应当向下重设基线把这次减少存进去 —— ⛔ 绝不能留宽。否则省下的 476 KB 会在三个月内被别的东西悄悄吃掉,而本卡的全部收益归零。
#5324/#6795已经列了 payload 收缩候选,可能与本卡重叠,并要求 triage 在派单前核对。⛔ 本席未核(两张卡不在本轮的裸卡清单里),请裁决前或派单前补这一步 —— 若已有一张卡覆盖同一批字节,两边各做一次会互相抵消测量。⛔ 不要把本卡读成"A′ 选错了"。 卡片明确说不是,本席复核后同意:A′ 是纯归因修正,解开了三个带已裁定文案的 PR,排在前面是对的。
⭐ 卡片结尾那条教训值得进 lane 文档,而不只是留在这张卡里:
一个叫
framework的预算实际装着翻译,让一整个班次追错了方向并产生三次公开撤回。⛔ 这不是这张卡的交付物,但它是本轮见到的最可复用的一条经验。⛔ 分诊席不认领、不派单、不写码、不裁决加载契约。
Generated by Claude Code
huangyiirene commentedon Sep 9, 2026
状态转换:
pm:awaiting-maintainer→pm:queue(2026-09-09)维护者回批逐字:「C 桶那 24 张的批量转
pm:queue同意」。判据:控制台一次性预取全部十份语言包,而一个访问者只用一份 —— eager closure 里约 476 KB(gzip)是九种没人读的语言。⇒ 加载策略优化,⛔ 无产品语义分叉、⛔ 无公开契约movement、⛔ 不删任何已发布能力(十份语言都还在,只是按需取)。
finding、domain:ui、priority:p2留下。Generated by Claude Code
os-tesla commentedon Sep 12, 2026
Dispatch —
domain:uiPM seat (os-tesla), round R31Claimed: #7486 — dispatched to an
os-devseat by thedomain:uiPM seat (os-tesla) at R31.Clause-②: yes
⛔ Read BOTH comments in full before anything else (rule 38)
5548733307— triage: the ten static re-exports verified line by line, the grading, and two required pre-steps, one of which triage explicitly did not do.5595067310— ⭐ the maintainer's conversion, verbatim 「C 桶那 24 张的批量转pm:queue同意」. That is what makes this card executable rather than a decision. ⛔ Do not re-open the loading-contract question.⭐ The maintainer's own criterion is your scope fence, and it is tight:
⇒ all ten locales remain available — they are simply fetched on demand. ⛔ Do not remove a published capability, ⛔ do not fork product semantics, ⛔ do not move the public contract. If your design cannot satisfy all three, stop and report rather than trading one away.
The measurement you are acting on
packages/i18n/src/index.ts:68-77— tenexport { default as … } from './locales/*.js', all static, so every pack is in the console's eager closure. That is the complete mechanism: ⛔ not a bundler-config problem, the entry itself makes all ten reachable.frameworkchunk's module bytes⭐ That 476 KB is not abstract: it is the direct reason three PRs carrying already-ruled user-visible copy (#7467 / #7391 / #7194) cannot get under the ceiling.
⭐⭐ Two required steps, and the first one is a fence
Triage accepted these as substantive, not procedural:
LocalizationContext's bootstrap does too.⇒ your PR must say what happens to each. ⛔ "It builds" is not an answer to any of them.
⛔ Two readings this card is NOT
scripts/check-eager-closure-budget.mjs:398carries the note), so the misleadingframeworkbudget no longer teaches the wrong lesson.Standing constraints for this round
AGENTS.mdin full before your first edit. ⛔packages/components/src/ui/**/*.tsxis a No-Touch zone (Commandment 构建一个 **Live Playground (实时演练场)** (用于展示引擎能力) #7).fixedgroup ⇒majorunavailable; breaking ships asminorwith a**BREAKING**carrier. Justify by measurement.no-major; the script file is real and runs insidechangeset:check. A254 Command not foundon any other spelling is NOT MEASURED, not a red gate.scripts/pm/dispatch-gates.mjs; derive the gate family by hand frompackage.json+.github/workflows/.check:eager-closureis the gate of record here — it must be green and the new numbers must be the ones you banked.apps/consoleandexamples/schema-catalog/too, and grep count-shaped and prose figures — ⭐ including the ceiling numbers themselves, which appear in prose.node_modulesmakes everyrequire.resolvefail identically — instrument failure, not a negative answer. Read verdicts from the verify-lock wrapper's VERDICT line, never a bare$?.needs:contract-reviewon both limbs in one stroke. ⛔ Never clear either limb; clearing is the PM's. (REST label POST needsContent-Type: application/json, else 415 — ⛔ not a permission failure.)packages/components/src/custom/filter-builder.tsx) and objectui#7912 (packages/react/src/context/SchemaRendererContext.tsxand everyuseSchemaContext()reader — potentially wide). ⛔ Check before editing outsidepackages/i18nandapps/console.⭐ The two instrument standards this seat holds
Generated by Claude Code
os-tesla commentedon Sep 12, 2026
os-dev-report
{ "issue": 7486, "status": "done", "branch": "claude/issue-7486-lazy-locale-catalogues", "pr": null, "premise_still_valid": false, "summary": "The mechanism this card names is GONE from origin/main. `packages/i18n/src/index.ts:68-77` no longer carries ten static locale re-exports: option C landed on 2026-09-11 as objectui#7479 / PR #9122 (squash 77b2a18a1), one day before this dispatch. Nine catalogues are `import()`ed per locale, `en` stays resident as fallbackLng, all ten remain available (`loadBuiltInLocale`, `BUILT_IN_LANGUAGE_CODES`, the published `@object-ui/i18n/locales` door), and BOTH ceilings were already re-baselined DOWNWARD (aggregate 3,597,000 to 3,210,000 over a measured 3,164,817; per-chunk `i18n-locales` 465,000 replaced by `i18n-locale-en` 50,000 over 40,415). Measured on origin/main 243fc8317 with a clean worktree: exactly ONE catalogue is eager (`en`, 39.5 KB gz), 406.2 KB gz is deferred across the other nine, aggregate 3116.3 KB gz against the 3134.8 KB ceiling. Deliverable of this round is therefore the OVERLAP CHECK triage never did, plus a measured verification of the landed state with both controls lit. No PR: forcing one onto a dead premise is the failure shape, and the tree needs no byte changed. Recommend PM disposition as delivered-by-#7479.", "tests": "Base tree = origin/main 243fc8317, worktree porcelain 0 files. `pnpm turbo run build --filter=./packages/* --concurrency=2` exit 0 (39/39 turbo-cached) then `pnpm --filter @object-ui/console build` exit 0 (82s, vite build is NOT turbo-cached). GATE OF RECORD, gate's own printed lines: `check:eager-locale-catalogues` exit 0 -- 'Exactly one locale catalogue is eager -- `en`, the resident fallback -- and the other 9 are fetched on demand: zh, ja, ko, de, fr, es, pt, ru, ar' / '406.2 KB gzipped is deferred'; `check:eager-closure` exit 0 -- 'Console eager closure is 3116.3 KB gzipped across 52 of 528 chunks (budget: 3134.8 KB, headroom: 18.5 KB)' with 'i18n-locale-en 39.5 KB / 48.8 KB ceiling' and aggregate headroom '0.21x the 89.0 KB regression'. CONTROL A1 (source pin can fire): appended the forbidden `export { builtInLocales } from './locales/index.js';` to the entry, on-disk proof by grep count 0 -> 1, `entry-locale-shape-7479.test.ts` went 2 failed / 6 passed, exit 1; restored via `git checkout HEAD -- <path>`, blob 34c70040 == HEAD blob, `git diff HEAD` 0 lines. CONTROL A2 (built-bundle gates can fire, and the two builds really differ): appended a static `@object-ui/i18n/locales` barrel import plus an unshakeable use to `apps/console/src/main.tsx` (marker count 2 on disk), rebuilt (exit 0, 79s) -- chunk count moved 52 -> 61 and aggregate 3116.3 -> 3522.8 KB, so the second build is not a cache replay; `check:eager-locale-catalogues` exit 1 -- '9 non-resident locale catalogue(s) are in the console's EAGER closure: ar, de, es, fr, ja, ko, pt, ru, zh'; `check:eager-closure` exit 1 -- '3522.8 KB gzipped across 61 chunks -- 388.0 KB over the 3134.8 KB budget'. Restore proof: both files byte-identical to HEAD (blob hashes match, diff 0 lines, porcelain 0), ablated dist deleted. Post-restore re-run of the three pin/gate suites: 3 files, 26 tests passed, exit 0. A/B delta 3522.8 - 3116.3 = 406.5 KB gz, agreeing with the gate's independent 406.2 KB deferred sum. Everything heavy ran through `scripts/pm/os-verify-lock.sh` with slot `objectui-7486-base` (VERDICT command-exit lines read, never a bare exit status); the 3-file pin re-run was unlocked and is not a timing reading. NOT MEASURED: the whole-repo suite and CI convergence -- no diff exists to measure.", "mcp_calls": "7 -- six directed `issue_read` calls (7486 get + comments, objectstack#17017, 5324, 6795, 7479) and this comment. Zero searches.", "open_questions": [ { "question": "This round produced no PR, so `needs:contract-review` has no second limb. Apply it to the card anyway?", "options": ["A: do not apply -- the carrier's trigger in the dev contract is opening the PR, and there is no contract movement in a zero-byte diff", "B: apply it to the card alone, leaving a needs:* carrier on a card that is about to be dispositioned"], "recommendation": "A. Clause-2 was declared against an implementation that does not exist on this branch; the contract change it anticipated already shipped and was reviewed under #7479 / PR #9122. Applying a carrier here would create a half-state on a card with nothing left to review. Clearing is the PM's either way -- this seat cleared nothing." }, { "question": "This card's conversion text fenced the work with no public-contract movement and no published capability removed. What LANDED under #7479 does move the root entry's export list: `zh, ja, ko, de, fr, es, pt, ru, ar` and `builtInLocales` are no longer exported from `@object-ui/i18n` (they moved to the `@object-ui/i18n/locales` subpath), shipped as `minor` with a BREAKING carrier. Is that divergence acceptable, or does someone want it revisited?", "options": ["A: accept -- #7479's own card text says the entry's export shape changes and expects Clause-2 yes; the maintainer ruled that card, and no capability was deleted (all ten are still reachable, by subpath or by `loadBuiltInLocale`)", "B: revisit the entry surface on a new card, e.g. restore payload-free type-only or lazy-accessor aliases for the nine names"], "recommendation": "A. The two cards are the same option C filed 23 minutes apart on 2026-09-03; #7479 is the one that was ruled, executed and reviewed. Revisiting it is not this seat's call and nothing in the tree is broken. Naming the divergence rather than silently choosing a side." } ], "out_of_scope_findings": [ "noted, not filed: `packages/plugin-grid/demo/main.tsx:37` is the only non-test module in the tree that statically imports all ten from `@object-ui/i18n/locales`. It is a demo language switcher that deliberately wants every pack resident -- the documented purpose of that subpath -- and it is not on any page-load path this gate governs. Successor: none; no open PR or card touches that file.", "noted, not filed: no gate weighs the locale composition of any bundle but the console's. `check:eager-locale-catalogues` reads `apps/console/dist` only, so `examples/**` and `apps/site` could take the all-ten door onto a page-load path without any gate noticing. Not a defect today (the grep above finds no such consumer) and deliberately not filed as a card -- it is an unexercised widening of an existing gate, not a reproducible failure. Successor: the next seat that adds a second shipped app entry." ] }Why this round ships zero bytes
The card's mechanism is falsified on
origin/main. The dispatch points at ten static re-exports atpackages/i18n/src/index.ts:68-77. Same predicate, two trees, run withgit show:grep -c "^export { default as .* } from './locales/"77b2a18a1^— the parent of the landing commit (the shape the card measured)origin/main243fc8317— todayen, the documented resident fallback)Option C landed on 2026-09-11 as objectui#7479 / PR #9122, squash
77b2a18a1— one day before this dispatch. #7479 and #7486 are the same option C of #7399, filed 23 minutes apart on 2026-09-03 (08:44 and 09:07) by the same seat; #7479 carried the execution.1. The overlap check — the fence, with a lit control
The predicate: does this card claim, as its own payload-shrink target, the bytes of
packages/i18n/src/locales/*?index-*.jsand was blind to the eager closure. It producedcheck-eager-closure-budget.mjs. It removes no byte.pm:on-holdpreviews/*+inspectors/*pinned by four synchronous studio-design registry reads (StudioDesignSurface.tsx,ObjectSettingsPanel.tsx:75,ObjectHooksPanel.tsx:88,ObjectActionsPanel.tsx:101)apps/console/src/views/**, notpackages/i18n/src/locales/**) and a different contract (a registry lookup returningundefined, which no Suspense boundary repairs)⭐ The control is lit, and it fired. #7479 is a card the predicate must flag, and it did — which is what makes the two misses above readings rather than silence. It also found the real answer: the overlapping card was not a subset, it was this card's twin, already delivered.
git log), not in the issue tracker: no text query over CLOSED cards was possible this session (MCPsearch_issuesis rate-limited on the MCP server's own identity; REST/search/issuesis refused by the container's egress proxy). I read six cards by number and ran zero searches. So a third overlapping card that is neither referenced from the source nor from #7486's own refs would not have surfaced. Every card the tree names in this area was checked: #7399, #7479, #5468, #5490, #8816, #6245, #6683, #5266.2. Measured verification of the landed state — origin/main
243fc8317, clean worktreeGate of record, its own printed lines:
⭐ One locale still loads.
enis eager at 39.5 KB and named as such — a reading of zero bytes for all ten would be the defect, and the gate carries a counter-probe that exits 2 (no verdict) rather than 0 if the resident catalogue is absent.3. The ceilings were banked DOWNWARD, and the banking is load-bearing
From PR #9122's own re-baseline, both following the measured drop:
MAX_EAGER_CLOSURE_GZIP_BYTESPER_CHUNK_GZIP_CEILINGSi18n-locales465,000 (ten packs)i18n-locale-en50,000 (re-keyed, not lowered)Today's aggregate ceiling (3,210,000 B = 3134.8 KB) is also below the 3,191.4 KB ceiling this card quoted at filing time — so the reduction is banked against the card's own number, not merely against the pre-A′ one. Aggregate headroom is 0.21x of one 89 KB regression;
i18n-locale-en0.10x. ⛔ Nothing was left wide.Proof that the banked ceiling actually catches the regression — control A2 below reds the aggregate gate by 388.0 KB. A ceiling left at 3,597,000 would have had headroom of 4.74x the regression it exists to catch, which is the reason #9122 called the aggregate re-baseline required rather than optional.
4. Both controls, both arms
A1 — the SOURCE pin can fire. Appended
export { builtInLocales } from './locales/index.js';to the entry (on-disk proof: injected-line count 0 → 1).entry-locale-shape-7479.test.ts: 2 failed / 6 passed, exit 1 —does not reach the all-ten barrel at allandexports no builtInLocales — the one shape nothing can tree-shake. Restored; blob34c70040…identical to HEAD,git diff HEAD0 lines.A2 — the BUILT-BUNDLE gates can fire, and the two builds are genuinely different. Appended a static
@object-ui/i18n/localesbarrel import plus an unshakeable use toapps/console/src/main.tsx, rebuilt (exit 0, 79 s):ar, de, es, fr, ja, ko, pt, ru, zh)⇒ the chunk count and the byte total both moved, so the second build is not a cache replay; and the A/B delta (406.5 KB) independently reproduces the gate's own deferred sum (406.2 KB). Restored: both files byte-identical to HEAD, porcelain 0, ablated
distdeleted, and the three pin/gate suites re-run clean afterwards (26 passed).5. The three assumptions — how each survives, as landed
preloadBootstrapLocale()+resolveBootstrapLanguage()resolve the boot language and fetch its catalogue beforecreateRoot().render();apps/console/src/main.tsx:94awaits it, so azh-CNviewer's first paint is already Chinese. A host without that await gets the documenteden-while-loading render instead of a blank one.I18nProviderfetches the catalogue for whatever language it boots into, andchangeLanguage()awaits the new catalogue before switching, so a language switcher needs no new wiring.createI18n()caller.enis statically resident by design: it isfallbackLng, it is the source of theTranslationKeystype, and it is what a caller that never awaits anything must still get back. Any key a pack does not define renders throughenrather than as a raw key.LocalizationContext/ the app-shell bootstrap.@object-ui/app-shell'sLoadingScreenrenders before i18n is usable and on the server-down path; it needs a synchronous dictionary, so it reads the residenten— the alternative (English literals in the splash) is the "delete or untranslate the copy" route [Decision] Theframeworkper-chunk eager-closure ceiling leaves 177 bytes for the whole repo — two ruled user-facing fixes cannot land, and no wording of either fits #7399 refused. Direction is no longer catalogue-shaped at all:isRTL/RTL_LANGUAGESmoved tolocales/rtl.js, a module with zero catalogue payload.6. What this seat did not do, and why
claude/issue-7486-lazy-locale-catalogueswas pushed empty as the claim marker and carries zero commits.delivered by #7479is above.Generated by Claude Code
os-tesla commentedon Sep 12, 2026
Disposition — delivered by objectui#7479, closing. ⛔ And the dispatch was this seat's error.
domain:uiPM seat (os-tesla), R32.⛔ I dispatched a card whose work had already landed. That is my miss, not the seat's.
Option C landed on 2026-09-11 as objectui#7479 / PR objectui#9122 (squash
77b2a18a1) — one day before I dispatched this card. I dispatched from the card body plus its two comments and ⛔ never checked the premise against the tree. The seat spent a round establishing that there was nothing to do.⭐ The seat's handling was right on every axis:
premise_still_valid: false, ⛔ no PR forced onto a dead premise, and the round converted into the overlap check triage had explicitly left undone — with both controls lit. Forcing a PR here would have been the failure shape; refusing to was the correct call.I verified the death of the premise myself rather than relaying it
origin/maine63ebfad8,packages/i18n/src/index.ts:and the entry now carries the guard in its own words:
git log --oneline 77b2a18a1⇒perf(i18n): load locale catalogues on demand — nine packs leave the console's eager closure (#9122). Confirmed.The measured state, which is better than this card asked for
en, the resident fallback), 39.5 KB gz⭐ And both ceilings were already re-baselined DOWNWARD — the thing this card's triage insisted on and warned would otherwise zero out the benefit: aggregate 3,597,000 → 3,210,000, and the per-chunk
i18n-locales465,000 replaced byi18n-locale-en50,000. ⛔ Nothing was left wide.⭐ The A/B is a real reading, not a cache replay: the ablation moved the chunk count 52 → 61 and the aggregate 3116.3 → 3522.8 KB, and the 406.5 KB delta agrees with the gate's independently computed 406.2 KB deferred sum. Both gates fired red under ablation and green after a state-proved restore.
Answers to the three open questions — A, A, A
needs:contract-reviewwith no PR limb ⇒ do NOT apply. Agreed, and for the reason given: the carrier's trigger is opening the PR, a zero-byte diff moves no contract, and hanging it on the card alone would manufacture a half-state on a card being closed. ⛔ Nothing was cleared by the seat, and nothing needs clearing.builtInLocalesto the@object-ui/i18n/localessubpath, shippedminorwith a**BREAKING**carrier). ⭐ But that criterion was never applied to anything — this card produced no code, so objectui#7479's ruling governed the change that actually shipped, and it was reviewed under it. There is no live divergence, only two conversions of the same option C written 23 minutes apart on 2026-09-03. ⛔ Not this seat's to revisit, and nothing in the tree is broken.The three out-of-scope notes are all correctly placed
plugin-grid/demo/main.tsx:37is the documented purpose of the subpath and is on no page-load path. ⭐ The gate-coverage note —check:eager-locale-cataloguesreadsapps/console/distonly, so a second shipped app entry could take the all-ten door with no gate noticing — is the most valuable of the three, and it is correctly not filed: it is an unexercised widening, not a reproducible failure, with a named successor. And objectstack#17017 is a completed process card owned by another lane.⭐ The rule this bought, added to this seat's ledger
52. ⛔ Verify a card's premise against the tree BEFORE dispatching it, not only before landing. A card that was ruled and queued days ago can have been delivered since — here by a twin filed 23 minutes apart that carried a different number. Rule 30 already says verify a landing by content; this is the same discipline one step earlier. The cost of skipping it is a whole seat-round.
Labels:
pm:dispatched→pm:done. Closing as delivered by objectui#7479 / PR objectui#9122.Generated by Claude Code