Uh oh!
There was an error while loading. Please reload this page.
fix(console,scripts): give the locale catalogue its own chunk, so the framework ceiling budgets the framework - #7489
Conversation
… framework ceiling budgets the framework `apps/console/vite.config.ts` declared five workspace groups at `priority: 80` with `framework` written first. That tie was not benign: the emitted `framework` chunk held 166 modules and only 145 of them came from `core|react|types`, its own test. The other 21 were all ten `@object-ui/i18n` locale catalogues (78.7% of the chunk's bytes) plus `data-objectstack`, whose declared `data-adapter` group emitted no chunk at all. So `PER_CHUNK_GZIP_CEILINGS['framework']` was, in operation, a budget on the translation catalogue: 523,959 measured against 524,000, 41 bytes of headroom for the whole repository, and a failure message that named the wrong cause. Lift the two swallowed groups one tier above the tie (priority 84), name the catalogue's chunk for what it holds, and re-baseline both affected per-chunk ceilings in the same change — leaving `framework` at 524,000 over a 61,465 payload makes it a blind gauge and the sensitivity half returns exit 2 for exactly that. No module bytes move: same eager closure, written to two more files. The extra chunk boundaries cost 779 gzipped bytes (+0.024%), all of it `import` bookkeeping; `MAX_EAGER_CLOSURE_GZIP_BYTES` is untouched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
os-project-manager
commented
Sep 3, 2026
ACCEPT — verified on the branch by content. Clause ② NO, so this seat lands it.
Verified by content
⭐ Three things this got right that the brief did not ask for1. It refused my own suggestion, with a reason. The dispatch pointed at the one-character probe from the measurement (raise 2. It caught a naming trap I did not see. 3. It measured the intermediate state, which is the proof the re-baseline belongs here. With the attribution fixed but the ceilings not yet moved, the gate returns exit 2: The load-bearing claim holdsThe eager module set is identical: 4,472 modules on each side, 0 only-in-control, 0 only-in-branch. Aggregate +779 B (+0.024%), disclosed as the gzip cost of splitting one chunk into three and explicitly ⛔ not headroom to borrow against. Sensitivity passes for both re-baselined chunks at 0.10x — the same convention the retired Ablation, two legs: mutating The LandingBranch is behind ⇒ On landing, Generated by Claude Code |
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Uh oh!
There was an error while loading. Please reload this page.
Fixes#7399
Implements the maintainer's ruling of 2026-09-03 — option A′, verbatim 「同意你的建议」, recorded on #7399. A pure re-attribution plus the per-chunk re-baseline it forces.
MAX_EAGER_CLOSURE_GZIP_BYTESis untouched; the locale packs are not restructured (that is option C, deferred to its own card by the same ruling).The defect, measured
apps/console/vite.config.tsdeclared five workspace groups atpriority: 80withframeworkwritten first. Measured on basee307c9896, from the emitted chunk's own module list — theframeworkchunk held 166 modules, and only 145 came fromcore|react|types, its own test:packages/i18n— all ten locale cataloguespackages/corepackages/reactpackages/data-objectstackpackages/typesframework's regex matches neither intruder, anddata-adapter— declared since #5490 — emitted no chunk at all. On the tie, the subgraph reached through@object-ui/reactwas absorbed by the group listed first. SoPER_CHUNK_GZIP_CEILINGS['framework']was in operation a budget on the translation catalogue: 523,959 gzipped against a 524,000 ceiling, 41 bytes of headroom for the whole repository, and a failure message that pointed away from its own cause.What changed
i18n-localesanddata-adapterlifted topriority: 84, one tier above the workspace groups and below the vendor tier at 85+;i18nleavesinfrastructure's alternation because a dedicated group now owns it. The two tests are disjoint from each other and from every vendor test, so the lift introduces no new tie.PER_CHUNK_BASELINEmoving with them. Not optional: left at 524,000 over a 61,465 payload,frameworkreads 5.08x above its own measurement andevaluateHeadroomSensitivityreturns exit 2 for exactly that (shown below).scripts/__tests__/check-eager-closure-budget.test.ts— eight cases, in the gate's existing unit-tested policy half as the measurement recommended.⛔ Not touched:
MAX_EAGER_CLOSURE_GZIP_BYTES, the locale packs' loading behaviour, and the copy of any of the parked PRs.Why this grouping, and not the one-character probe
The probe on #7399 raised
infrastructurefrom 80 to 85 with its test unchanged. That works, and it was deliberately not copied. Two reasons:infrastructurethat is ~94% translation catalogue — the same illegibility one step over. The expensive half of this defect was never the arithmetic: a gate whose message says "you grewcore|react|types" when the cause is a translation key teaches a false rule, and it was followed twice into publicly blaming changes that ship zero bytes ([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's own retraction). Naming the chunk for what it holds is what makes the constraint legible where it is authored.data-objectstackinframeworkanddata-adapterstill emitting nothing — a declared group that weighs nothing, which is the same defect class the ruling names.i18nwas rejected as the chunk name: an unrelatedpackages/app-shellmodule already emits a chunk auto-namedi18n(59,974 gz), and per-chunk ceilings are summed per name, so the two would have shared one budget.Measurements — every figure with its base SHA
Toolchain identical for every leg: node v22.22.2, pnpm 10.31.0,
pnpm turbo run build --filter='./packages/*' --concurrency=2thenpnpm --filter @object-ui/console buildthennode scripts/check-eager-closure-budget.mjs.Control —
mainuntouched, basee307c9896Gate exit 0.
framework523,959 B against 524,000 — the "headroom 0.0 KB" is a true rounding of 41 bytes.This branch, head
b437996cd(basee307c9896)Gate exit 0, sensitivity passes — not exit 2 — for both re-baselined chunks, each at 0.10x, the same headroom convention the retired
frameworkpair carried (9,137 B, 0.10x).⭐ The aggregate is unchanged: the eager module set is IDENTICAL, 4,472 modules
Read from the two builds' own visualizer data, restricted to the chunks the eager-closure report names:
The four are
components/src/lib/utils.tsx,components/src/sidebar-fixes.css,i18n/src/LocalizationContext.tsxandi18n/src/locales/en.ts— chunk-local rendering artefacts, not content.e307c9896, untouched)b437996cd)Where the +779 goes, per chunk:
framework−462,494,i18n-locales+446,076,data-adapter+17,846 (net +1,428 for the three, the gzip cost of splitting one chunk into three),infrastructure−592,ui-components−287, and ~+230 spread over 20 chunks that now name two imported files where they named one. ⛔ This is the one thing the change spends, and it is not headroom to borrow against.Chunk membership after the change, counted
frameworkcore63,react63,types14 — 100% its own test (was 166 modules, 21 of them foreign)i18n-localespackages/i18n100%data-adapterdata-objectstack5 (91.8%),core4,types1infrastructureauth22,node_modules33,permissions8 (was 65, incl. 2i18n)data-adapternow holds 5core/typesmodules (8.5 KB raw) reached only throughdata-objectstack— the same shared-module pull-in, one tier down and three orders of magnitude smaller.frameworkcannot be lifted above these two without re-absorbing the catalogue, which is the whole defect.data-adaptergets no ceiling17,846 B is smaller than a dozen unbudgeted eager chunks.
PER_CHUNK_GZIP_CEILINGSis a line per BIG chunk, not per named group; inventing one here would be a number with no incident behind it. The catalogue's chunk does get one — a re-attribution that moved 446 KB into an unbudgeted chunk would weaken the gate, and one of the pin's cases asserts against exactly that.The composition pin, and proof it is not blind
Eight cases in
scripts/__tests__/check-eager-closure-budget.test.ts. They parse the group table out ofapps/console/vite.config.tsand assert on the deciding input — the predicate that was actually violated is a tie between a group whose test matches the catalogue and one whose test does not. Counter-probes first, all failing closed: the parse must find the whole table (not a fragment), a control module id must resolve toframework, exactly one group may carry a computed test, and a probe id matching no group is an error rather than a pass.Home: the gate's unit-tested policy half, as the measurement recommended. The byte-level backstop is the re-baselined ceiling itself —
frameworkat 71,000 over 61,465 means a regression that puts the catalogue back reds the gate six times over. That verdict is loud but mute about the cause; this one names it.Ablation — leg 1, the pin. Mutated
priority: 84back to80on both lifted groups, restoring the tie. Mutation proved on disk before the run —priority: 84occurrences 2 to 0, both mutated group lines present at 80, blob3c7e2f79…to4600c365…. Result:Ablation — leg 2, the emitted bundle. Put the group table back to
main's five-at-80 form and rebuilt. Mutation proved on disk (blob equalsorigin/main's,priority: 84count 0,i18n-localesmentions 0,main's tiedinfrastructureline present). The rebuild reproduces the control to the byte — aggregate 3,255,233 andframework523,959, both identical to the untouched-maincontrol — and the gate returns exit 2:Both legs used
trap restore EXIT INT TERMwith absolute paths andgit checkout HEAD --on an absolute path; each restore is proved by state — blob hash equal to theHEADblob andgit diff HEADempty — never by an exit code. An empty hash was treated as failure, not as "nothing to compare".What this unblocks, and what becomes binding next
i18n-localescarries 8,924 B of headroom — about sixty translation keys at the measured ~147 gzipped bytes a short key costs across ten locales. That covers #7467, PR #7391, #7194, #7442 and #7400 with room to spare. ⭐ After roughly 3–4 KB more of copy the aggregate line (11,988 B of headroom) becomes the binding one. That is the correct place for the constraint to live, and it is an argument for option C, not for raising anything.Verification
node scripts/check-eager-closure-budget.mjs— exit 0 atb437996cd, verdict lines quoted above.pnpm exec vitest run --maxWorkers=2 scripts/__tests__— 100 files, 2,876 tests, atb437996cd. One pre-existing failure,check-sdui-registration-pins.test.ts, already filed as finding(test-infra):check-sdui-registration-pins.test.tsfails on any tree wherepackages/app-shell/distexists #6893: it resolves throughpackages/app-shell/distwhen that gitignored directory exists, so it reds on any tree where packages have been built. Not touched by this diff (none of its inputs are in it) and not re-filed.pnpm exec vitest run scripts/__tests__/check-eager-closure-budget.test.ts— 97 passed (89 before this change).pnpm exec tsc -p tsconfig.scripts.json --noEmit— exit 0.node scripts/check-changeset-presence.mjs— exit 0, its own verdict: "No source or published contract of a released package changed in this range, so no changeset is owed." No changeset, and noskip-changesetlabel: nothing in this repository reads that label.node scripts/check-control-bytes.mjs— exit 0, 6,152 tracked text files scanned; plus a direct control-byte scan of the three changed files, zero hits.apps/console/vite.config.ts:check-doc-links,check-docs-route-eager-closure,check-package-self-import,check-phantom-dependencies,check-published-tsconfig-tooling-exclude— all exit 0.Clause ② — declared lint narrowing.
pnpm lintiseslint . --no-inline-configover the whole repository; this run was narrowed to the three changed files, exit 0, zero errors and zero warnings. The narrowing is a measurement, not a skip:--format json: 3 file entries returned, exactly the three files in the diff.eslint.config.jsdeclares noprojectServiceand noparserOptions.project, so type-aware linting is off; and no rule ineslint-rules/reads any file other than the one under lint (grep -ln 'readFileSync|readdirSync|globSync' eslint-rules/*.jsreturns zero). A verdict on a file this diff does not touch therefore cannot move because of this diff.CI runs the full farm regardless; the aggregate above was taken on the final commit
b437996cd, with the tree clean.🤖 Generated with Claude Code
https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
Generated by Claude Code
Generated by Claude Code