fix(console,scripts): give the locale catalogue its own chunk, so the framework ceiling budgets the framework - #7489

Merged
os-project-manager merged 2 commits into
mainfrom
claude/issue-7399-reattribute-i18n-chunk
Sep 3, 2026
Merged

fix(console,scripts): give the locale catalogue its own chunk, so the framework ceiling budgets the framework#7489
os-project-manager merged 2 commits into
mainfrom
claude/issue-7399-reattribute-i18n-chunk

Conversation

@claude

@claudeclaudeBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

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_BYTES is 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.ts declared five workspace groups at priority: 80 with framework written first. Measured on base e307c9896, from the emitted chunk's own module list — the framework chunk held 166 modules, and only 145 came from core|react|types, its own test:

sourcemodulesshare of the chunk's module bytes
packages/i18n — all ten locale catalogues1678.7%
packages/core679.6%
packages/react634.7%
packages/data-objectstack54.6%
packages/types152.3%

framework's regex matches neither intruder, and data-adapter — declared since #5490 — emitted no chunk at all. On the tie, the subgraph reached through @object-ui/react was absorbed by the group listed first. So PER_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

  1. i18n-locales and data-adapter lifted to priority: 84, one tier above the workspace groups and below the vendor tier at 85+; i18n leaves infrastructure'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.
  2. Both affected per-chunk ceilings re-baselined in the same change, PER_CHUNK_BASELINE moving with them. Not optional: left at 524,000 over a 61,465 payload, framework reads 5.08x above its own measurement and evaluateHeadroomSensitivity returns exit 2 for exactly that (shown below).
  3. The composition is pinned in 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 infrastructure from 80 to 85 with its test unchanged. That works, and it was deliberately not copied. Two reasons:

⚠️i18n was rejected as the chunk name: an unrelatedpackages/app-shell module already emits a chunk auto-named i18n (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=2 then pnpm --filter @object-ui/console build then node scripts/check-eager-closure-budget.mjs.

Control — main untouched, base e307c9896

✅ Console eager closure is 3178.9 KB gzipped across 48 of 516 chunks (budget: 3191.4 KB, headroom: 12.5 KB).
✅ Per-chunk eager budgets (3 chunks weighed):
✅ vendor-objectstack 926.1 KB / 944.3 KB ceiling (headroom 18.2 KB)
✅ framework 511.7 KB / 511.7 KB ceiling (headroom 0.0 KB)
✅ ui-components 387.8 KB / 389.6 KB ceiling (headroom 1.9 KB)

Gate exit 0. framework 523,959 B against 524,000 — the "headroom 0.0 KB" is a true rounding of 41 bytes.

This branch, head b437996cd (base e307c9896)

✅ Console eager closure is 3179.7 KB gzipped across 50 of 518 chunks (budget: 3191.4 KB, headroom: 11.7 KB).
✅ Per-chunk eager budgets (4 chunks weighed):
✅ vendor-objectstack 926.1 KB / 944.3 KB ceiling (headroom 18.2 KB)
✅ i18n-locales 435.6 KB / 444.3 KB ceiling (headroom 8.7 KB)
✅ ui-components 387.5 KB / 389.6 KB ceiling (headroom 2.1 KB)
✅ framework 60.0 KB / 69.3 KB ceiling (headroom 9.3 KB)
✅ Ceiling sensitivity (5 ceilings, each weighed against the report just read):
✅ aggregate closure 3179.7 KB measured / 3191.4 KB ceiling (headroom 11.7 KB = 0.13x the 89.0 KB regression) [MAX_EAGER_CLOSURE_GZIP_BYTES]
✅ chunk `vendor-objectstack` 926.1 KB measured / 944.3 KB ceiling (headroom 18.2 KB = 0.20x the 89.0 KB regression) [PER_CHUNK_GZIP_CEILINGS['vendor-objectstack']]
✅ chunk `i18n-locales` 435.6 KB measured / 444.3 KB ceiling (headroom 8.7 KB = 0.10x the 89.0 KB regression) [PER_CHUNK_GZIP_CEILINGS['i18n-locales']]
✅ chunk `framework` 60.0 KB measured / 69.3 KB ceiling (headroom 9.3 KB = 0.10x the 89.0 KB regression) [PER_CHUNK_GZIP_CEILINGS['framework']]
✅ chunk `ui-components` 387.5 KB measured / 389.6 KB ceiling (headroom 2.1 KB = 0.02x the 89.0 KB regression) [PER_CHUNK_GZIP_CEILINGS['ui-components']]

Gate exit 0, sensitivity passes — not exit 2 — for both re-baselined chunks, each at 0.10x, the same headroom convention the retired framework pair 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:

control-shape buildthis branch
eager modules4,4724,472
modules only on one side00
shared modules whose rendered bytes differ4, by 183 B in total

The four are components/src/lib/utils.tsx, components/src/sidebar-fixes.css, i18n/src/LocalizationContext.tsx and i18n/src/locales/en.ts — chunk-local rendering artefacts, not content.

gzippedraw
control (e307c9896, untouched)3,255,23311,177,729
this branch (b437996cd)3,256,01211,178,566
delta+779 B (+0.024%)+837 B (+0.0075%)

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

chunkmodulescomposition
framework140core 63, react 63, types 14 — 100% its own test (was 166 modules, 21 of them foreign)
i18n-locales22packages/i18n 100%
data-adapter10data-objectstack 5 (91.8%), core 4, types 1
infrastructure63auth 22, node_modules 33, permissions 8 (was 65, incl. 2 i18n)

⚠️ Disclosed rather than smoothed over: data-adapter now holds 5 core/types modules (8.5 KB raw) reached only through data-objectstack — the same shared-module pull-in, one tier down and three orders of magnitude smaller. framework cannot be lifted above these two without re-absorbing the catalogue, which is the whole defect.

data-adapter gets no ceiling

17,846 B is smaller than a dozen unbudgeted eager chunks. PER_CHUNK_GZIP_CEILINGS is 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 of apps/console/vite.config.ts and 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 to framework, 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 — framework at 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: 84 back to 80 on both lifted groups, restoring the tie. Mutation proved on disk before the run — priority: 84 occurrences 2 to 0, both mutated group lines present at 80, blob 3c7e2f79… to 4600c365…. Result:

× routes the locale catalogue to .../packages/i18n/src/locales/zh-CN.ts at a priority `framework` cannot tie
× routes the ObjectStack data adapter to .../packages/data-objectstack/src/index.ts at a priority `framework` cannot tie
AssertionError: expected 80 to be greater than 80
Tests 2 failed | 6 passed | 89 skipped (97)

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 equals origin/main's, priority: 84 count 0, i18n-locales mentions 0, main's tied infrastructure line present). The rebuild reproduces the control to the byte — aggregate 3,255,233 and framework 523,959, both identical to the untouched-main control — and the gate returns exit 2:

❌ Budgeted chunk `i18n-locales` is ABSENT from the eager closure reported at apps/console/dist/eager-closure.json. That is a FAILURE, not a pass: a ceiling whose chunk does not exist weighs nothing and would be green forever.

Both legs used trap restore EXIT INT TERM with absolute paths and git checkout HEAD -- on an absolute path; each restore is proved by state — blob hash equal to the HEAD blob and git diff HEAD empty — 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-locales carries 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 at b437996cd, verdict lines quoted above.
  • pnpm exec vitest run --maxWorkers=2 scripts/__tests__ — 100 files, 2,876 tests, at b437996cd. One pre-existing failure, check-sdui-registration-pins.test.ts, already filed as finding(test-infra): check-sdui-registration-pins.test.ts fails on any tree where packages/app-shell/dist exists #6893: it resolves through packages/app-shell/dist when 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 no skip-changeset label: 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.
  • The gates that read 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 lint is eslint . --no-inline-config over 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:

  1. Population comes from eslint's own configuration, not from a guess — all three paths were resolved and linted (none ignored).
  2. Count read from --format json: 3 file entries returned, exactly the three files in the diff.
  3. Invariance for untouched files: eslint.config.js declares no projectService and no parserOptions.project, so type-aware linting is off; and no rule in eslint-rules/ reads any file other than the one under lint (grep -ln 'readFileSync|readdirSync|globSync' eslint-rules/*.js returns 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

… 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
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3179.7 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-BID5JT1s.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.24KB117.82KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.08KB61.71KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.27KB13.32KB
plugin-charts (index.js)70.31KB19.55KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.79KB34.62KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.19KB64.18KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.18KB40.94KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.53KB27.66KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.21KB8.66KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-project-managerClaude

Copy link
Copy Markdown
Collaborator

ACCEPT — verified on the branch by content. Clause ② NO, so this seat lands it.

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. Head b437996cd.

⚠️Clause ② declared by this seat, not inherited. The report used "Clause ②" for the lint-narrowing convention, which is a different thing. The contract question — does this move a published surface? — is answered by the diff: three files, all of them build config and gate script (apps/console/vite.config.ts, scripts/check-eager-closure-budget.mjs, its test). No spec key, no zod validator, no exported type. ⇒ NO, no contract review needed.

Verified by content

probeorigin/mainbranch
files changed3
MAX_EAGER_CLOSURE_GZIP_BYTES3_268_0003_268_000 — unchanged
framework ceiling / baseline524_000 / 514_86371_000 / 61_465
i18n-locales ceiling / baselineabsent455_000 / 446_076
vendor-objectstack, ui-components967_000 / 399_000unchanged
pin cases in the gate's test8 (6 it + 1 it.each over two module ids)

⚠️ Two of my own probes were wrong here and the report was right both times, so I am recording it: I grepped the ceilings for a quoted key and framework is spelled unquoted, so it read as absent; and I counted it( alone, which missed the it.each. Neither was a defect in the change.

⭐ Three things this got right that the brief did not ask for

1. It refused my own suggestion, with a reason. The dispatch pointed at the one-character probe from the measurement (raise infrastructure to 85). Copying it would have left a chunk named infrastructure that is ~94% translation catalogue — the same illegibility one step over — and left data-objectstack misattributed with data-adapter still emitting nothing. A new i18n-locales group was the right call. The expensive half of this defect was never the arithmetic; it was a gate whose message pointed away from its cause, and that is what got fixed.

2. It caught a naming trap I did not see.i18n was rejected as the chunk name because an unrelatedpackages/app-shell module already emits a chunk auto-named i18n (59,974 gz), and per-chunk ceilings are summed per name — the two would have shared one budget silently.

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: framework 60.0 KB measured / 511.7 KB ceiling (headroom 451.7 KB = 5.08x the 89.0 KB regression). That is the blind ceiling the ruling anticipated, demonstrated rather than argued. ⇒ A′ genuinely could not have shipped as two PRs.

The load-bearing claim holds

The 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 framework pair carried.

Ablation, two legs: mutating priority: 84 → 80 reddens the pin by name ("expected 80 to be greater than 80"); restoring main's five-at-80 table rebuilds the control to the byte (aggregate 3,255,233 and framework 523,959, both identical to the untouched-main control) and the gate returns exit 2 on the now-absent budgeted chunk. Both restores proved by state.

The PER_CHUNK_BASELINE provenance comment also states plainly that these two entries are not readings of an unmodified tree — the chunks do not exist without the diff — and why the usual argument does not cover them. That is the honesty this file's header demands of its own numbers.

Landing

Branch is behindmain; bringing the base in, then undrafting for the queue. One pre-existing failure is correctly attributed and not re-filed: check-sdui-registration-pins.test.ts resolves through the gitignored packages/app-shell/dist and reds on any built tree — already #6893, and none of its inputs are in this diff.

⇒ On landing, i18n-locales carries ~8,924 B of headroom (about sixty short translation keys), which unblocks #7467, PR #7391, #7194, #7442 and #7400. ⭐ And the report names what becomes binding next: after ~3–4 KB more copy the aggregate line is the constraint — which is the correct place for it, and an argument for #7486 (option C), ⛔ not for raising anything.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3179.7 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-BID5JT1s.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.24KB117.82KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.08KB61.71KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.27KB13.32KB
plugin-charts (index.js)70.31KB19.55KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.79KB34.62KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.19KB64.18KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.18KB40.94KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.53KB27.66KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.21KB8.66KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-project-manager
os-project-manager marked this pull request as ready for review September 3, 2026 10:02
@os-project-manager
os-project-manager added this pull request to the merge queueSep 3, 2026
Merged via the queue into main with commit 177afebSep 3, 2026
32 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-7399-reattribute-i18n-chunk branch September 3, 2026 10:17
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix(console,scripts): give the locale catalogue its own chunk, so the framework ceiling budgets the framework - #7489

Merged
os-project-manager merged 2 commits into
mainfrom
claude/issue-7399-reattribute-i18n-chunk
Sep 3, 2026
Merged

fix(console,scripts): give the locale catalogue its own chunk, so the framework ceiling budgets the framework#7489
os-project-manager merged 2 commits into
mainfrom
claude/issue-7399-reattribute-i18n-chunk

Conversation

@claude

@claudeclaudeBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

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_BYTES is 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.ts declared five workspace groups at priority: 80 with framework written first. Measured on base e307c9896, from the emitted chunk's own module list — the framework chunk held 166 modules, and only 145 came from core|react|types, its own test:

sourcemodulesshare of the chunk's module bytes
packages/i18n — all ten locale catalogues1678.7%
packages/core679.6%
packages/react634.7%
packages/data-objectstack54.6%
packages/types152.3%

framework's regex matches neither intruder, and data-adapter — declared since #5490 — emitted no chunk at all. On the tie, the subgraph reached through @object-ui/react was absorbed by the group listed first. So PER_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

  1. i18n-locales and data-adapter lifted to priority: 84, one tier above the workspace groups and below the vendor tier at 85+; i18n leaves infrastructure'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.
  2. Both affected per-chunk ceilings re-baselined in the same change, PER_CHUNK_BASELINE moving with them. Not optional: left at 524,000 over a 61,465 payload, framework reads 5.08x above its own measurement and evaluateHeadroomSensitivity returns exit 2 for exactly that (shown below).
  3. The composition is pinned in 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 infrastructure from 80 to 85 with its test unchanged. That works, and it was deliberately not copied. Two reasons:

⚠️i18n was rejected as the chunk name: an unrelatedpackages/app-shell module already emits a chunk auto-named i18n (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=2 then pnpm --filter @object-ui/console build then node scripts/check-eager-closure-budget.mjs.

Control — main untouched, base e307c9896

✅ Console eager closure is 3178.9 KB gzipped across 48 of 516 chunks (budget: 3191.4 KB, headroom: 12.5 KB).
✅ Per-chunk eager budgets (3 chunks weighed):
✅ vendor-objectstack 926.1 KB / 944.3 KB ceiling (headroom 18.2 KB)
✅ framework 511.7 KB / 511.7 KB ceiling (headroom 0.0 KB)
✅ ui-components 387.8 KB / 389.6 KB ceiling (headroom 1.9 KB)

Gate exit 0. framework 523,959 B against 524,000 — the "headroom 0.0 KB" is a true rounding of 41 bytes.

This branch, head b437996cd (base e307c9896)

✅ Console eager closure is 3179.7 KB gzipped across 50 of 518 chunks (budget: 3191.4 KB, headroom: 11.7 KB).
✅ Per-chunk eager budgets (4 chunks weighed):
✅ vendor-objectstack 926.1 KB / 944.3 KB ceiling (headroom 18.2 KB)
✅ i18n-locales 435.6 KB / 444.3 KB ceiling (headroom 8.7 KB)
✅ ui-components 387.5 KB / 389.6 KB ceiling (headroom 2.1 KB)
✅ framework 60.0 KB / 69.3 KB ceiling (headroom 9.3 KB)
✅ Ceiling sensitivity (5 ceilings, each weighed against the report just read):
✅ aggregate closure 3179.7 KB measured / 3191.4 KB ceiling (headroom 11.7 KB = 0.13x the 89.0 KB regression) [MAX_EAGER_CLOSURE_GZIP_BYTES]
✅ chunk `vendor-objectstack` 926.1 KB measured / 944.3 KB ceiling (headroom 18.2 KB = 0.20x the 89.0 KB regression) [PER_CHUNK_GZIP_CEILINGS['vendor-objectstack']]
✅ chunk `i18n-locales` 435.6 KB measured / 444.3 KB ceiling (headroom 8.7 KB = 0.10x the 89.0 KB regression) [PER_CHUNK_GZIP_CEILINGS['i18n-locales']]
✅ chunk `framework` 60.0 KB measured / 69.3 KB ceiling (headroom 9.3 KB = 0.10x the 89.0 KB regression) [PER_CHUNK_GZIP_CEILINGS['framework']]
✅ chunk `ui-components` 387.5 KB measured / 389.6 KB ceiling (headroom 2.1 KB = 0.02x the 89.0 KB regression) [PER_CHUNK_GZIP_CEILINGS['ui-components']]

Gate exit 0, sensitivity passes — not exit 2 — for both re-baselined chunks, each at 0.10x, the same headroom convention the retired framework pair 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:

control-shape buildthis branch
eager modules4,4724,472
modules only on one side00
shared modules whose rendered bytes differ4, by 183 B in total

The four are components/src/lib/utils.tsx, components/src/sidebar-fixes.css, i18n/src/LocalizationContext.tsx and i18n/src/locales/en.ts — chunk-local rendering artefacts, not content.

gzippedraw
control (e307c9896, untouched)3,255,23311,177,729
this branch (b437996cd)3,256,01211,178,566
delta+779 B (+0.024%)+837 B (+0.0075%)

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

chunkmodulescomposition
framework140core 63, react 63, types 14 — 100% its own test (was 166 modules, 21 of them foreign)
i18n-locales22packages/i18n 100%
data-adapter10data-objectstack 5 (91.8%), core 4, types 1
infrastructure63auth 22, node_modules 33, permissions 8 (was 65, incl. 2 i18n)

⚠️ Disclosed rather than smoothed over: data-adapter now holds 5 core/types modules (8.5 KB raw) reached only through data-objectstack — the same shared-module pull-in, one tier down and three orders of magnitude smaller. framework cannot be lifted above these two without re-absorbing the catalogue, which is the whole defect.

data-adapter gets no ceiling

17,846 B is smaller than a dozen unbudgeted eager chunks. PER_CHUNK_GZIP_CEILINGS is 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 of apps/console/vite.config.ts and 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 to framework, 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 — framework at 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: 84 back to 80 on both lifted groups, restoring the tie. Mutation proved on disk before the run — priority: 84 occurrences 2 to 0, both mutated group lines present at 80, blob 3c7e2f79… to 4600c365…. Result:

× routes the locale catalogue to .../packages/i18n/src/locales/zh-CN.ts at a priority `framework` cannot tie
× routes the ObjectStack data adapter to .../packages/data-objectstack/src/index.ts at a priority `framework` cannot tie
AssertionError: expected 80 to be greater than 80
Tests 2 failed | 6 passed | 89 skipped (97)

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 equals origin/main's, priority: 84 count 0, i18n-locales mentions 0, main's tied infrastructure line present). The rebuild reproduces the control to the byte — aggregate 3,255,233 and framework 523,959, both identical to the untouched-main control — and the gate returns exit 2:

❌ Budgeted chunk `i18n-locales` is ABSENT from the eager closure reported at apps/console/dist/eager-closure.json. That is a FAILURE, not a pass: a ceiling whose chunk does not exist weighs nothing and would be green forever.

Both legs used trap restore EXIT INT TERM with absolute paths and git checkout HEAD -- on an absolute path; each restore is proved by state — blob hash equal to the HEAD blob and git diff HEAD empty — 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-locales carries 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 at b437996cd, verdict lines quoted above.
  • pnpm exec vitest run --maxWorkers=2 scripts/__tests__ — 100 files, 2,876 tests, at b437996cd. One pre-existing failure, check-sdui-registration-pins.test.ts, already filed as finding(test-infra): check-sdui-registration-pins.test.ts fails on any tree where packages/app-shell/dist exists #6893: it resolves through packages/app-shell/dist when 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 no skip-changeset label: 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.
  • The gates that read 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 lint is eslint . --no-inline-config over 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:

  1. Population comes from eslint's own configuration, not from a guess — all three paths were resolved and linted (none ignored).
  2. Count read from --format json: 3 file entries returned, exactly the three files in the diff.
  3. Invariance for untouched files: eslint.config.js declares no projectService and no parserOptions.project, so type-aware linting is off; and no rule in eslint-rules/ reads any file other than the one under lint (grep -ln 'readFileSync|readdirSync|globSync' eslint-rules/*.js returns 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

… 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
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3179.7 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-BID5JT1s.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.24KB117.82KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.08KB61.71KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.27KB13.32KB
plugin-charts (index.js)70.31KB19.55KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.79KB34.62KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.19KB64.18KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.18KB40.94KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.53KB27.66KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.21KB8.66KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-project-managerClaude

Copy link
Copy Markdown
Collaborator

ACCEPT — verified on the branch by content. Clause ② NO, so this seat lands it.

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. Head b437996cd.

⚠️Clause ② declared by this seat, not inherited. The report used "Clause ②" for the lint-narrowing convention, which is a different thing. The contract question — does this move a published surface? — is answered by the diff: three files, all of them build config and gate script (apps/console/vite.config.ts, scripts/check-eager-closure-budget.mjs, its test). No spec key, no zod validator, no exported type. ⇒ NO, no contract review needed.

Verified by content

probeorigin/mainbranch
files changed3
MAX_EAGER_CLOSURE_GZIP_BYTES3_268_0003_268_000 — unchanged
framework ceiling / baseline524_000 / 514_86371_000 / 61_465
i18n-locales ceiling / baselineabsent455_000 / 446_076
vendor-objectstack, ui-components967_000 / 399_000unchanged
pin cases in the gate's test8 (6 it + 1 it.each over two module ids)

⚠️ Two of my own probes were wrong here and the report was right both times, so I am recording it: I grepped the ceilings for a quoted key and framework is spelled unquoted, so it read as absent; and I counted it( alone, which missed the it.each. Neither was a defect in the change.

⭐ Three things this got right that the brief did not ask for

1. It refused my own suggestion, with a reason. The dispatch pointed at the one-character probe from the measurement (raise infrastructure to 85). Copying it would have left a chunk named infrastructure that is ~94% translation catalogue — the same illegibility one step over — and left data-objectstack misattributed with data-adapter still emitting nothing. A new i18n-locales group was the right call. The expensive half of this defect was never the arithmetic; it was a gate whose message pointed away from its cause, and that is what got fixed.

2. It caught a naming trap I did not see.i18n was rejected as the chunk name because an unrelatedpackages/app-shell module already emits a chunk auto-named i18n (59,974 gz), and per-chunk ceilings are summed per name — the two would have shared one budget silently.

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: framework 60.0 KB measured / 511.7 KB ceiling (headroom 451.7 KB = 5.08x the 89.0 KB regression). That is the blind ceiling the ruling anticipated, demonstrated rather than argued. ⇒ A′ genuinely could not have shipped as two PRs.

The load-bearing claim holds

The 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 framework pair carried.

Ablation, two legs: mutating priority: 84 → 80 reddens the pin by name ("expected 80 to be greater than 80"); restoring main's five-at-80 table rebuilds the control to the byte (aggregate 3,255,233 and framework 523,959, both identical to the untouched-main control) and the gate returns exit 2 on the now-absent budgeted chunk. Both restores proved by state.

The PER_CHUNK_BASELINE provenance comment also states plainly that these two entries are not readings of an unmodified tree — the chunks do not exist without the diff — and why the usual argument does not cover them. That is the honesty this file's header demands of its own numbers.

Landing

Branch is behindmain; bringing the base in, then undrafting for the queue. One pre-existing failure is correctly attributed and not re-filed: check-sdui-registration-pins.test.ts resolves through the gitignored packages/app-shell/dist and reds on any built tree — already #6893, and none of its inputs are in this diff.

⇒ On landing, i18n-locales carries ~8,924 B of headroom (about sixty short translation keys), which unblocks #7467, PR #7391, #7194, #7442 and #7400. ⭐ And the report names what becomes binding next: after ~3–4 KB more copy the aggregate line is the constraint — which is the correct place for it, and an argument for #7486 (option C), ⛔ not for raising anything.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3179.7 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-BID5JT1s.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.24KB117.82KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.08KB61.71KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.27KB13.32KB
plugin-charts (index.js)70.31KB19.55KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.79KB34.62KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.19KB64.18KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.18KB40.94KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.53KB27.66KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.21KB8.66KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-project-manager
os-project-manager marked this pull request as ready for review September 3, 2026 10:02
@os-project-manager
os-project-manager added this pull request to the merge queueSep 3, 2026
Merged via the queue into main with commit 177afebSep 3, 2026
32 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-7399-reattribute-i18n-chunk branch September 3, 2026 10:17
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(console,scripts): give the locale catalogue its own chunk, so the framework ceiling budgets the framework - #7489

Merged
os-project-manager merged 2 commits into
mainfrom
claude/issue-7399-reattribute-i18n-chunk
Sep 3, 2026
Merged

fix(console,scripts): give the locale catalogue its own chunk, so the framework ceiling budgets the framework#7489
os-project-manager merged 2 commits into
mainfrom
claude/issue-7399-reattribute-i18n-chunk

Conversation

@claude

@claudeclaudeBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

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_BYTES is 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.ts declared five workspace groups at priority: 80 with framework written first. Measured on base e307c9896, from the emitted chunk's own module list — the framework chunk held 166 modules, and only 145 came from core|react|types, its own test:

sourcemodulesshare of the chunk's module bytes
packages/i18n — all ten locale catalogues1678.7%
packages/core679.6%
packages/react634.7%
packages/data-objectstack54.6%
packages/types152.3%

framework's regex matches neither intruder, and data-adapter — declared since #5490 — emitted no chunk at all. On the tie, the subgraph reached through @object-ui/react was absorbed by the group listed first. So PER_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

  1. i18n-locales and data-adapter lifted to priority: 84, one tier above the workspace groups and below the vendor tier at 85+; i18n leaves infrastructure'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.
  2. Both affected per-chunk ceilings re-baselined in the same change, PER_CHUNK_BASELINE moving with them. Not optional: left at 524,000 over a 61,465 payload, framework reads 5.08x above its own measurement and evaluateHeadroomSensitivity returns exit 2 for exactly that (shown below).
  3. The composition is pinned in 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 infrastructure from 80 to 85 with its test unchanged. That works, and it was deliberately not copied. Two reasons:

⚠️i18n was rejected as the chunk name: an unrelatedpackages/app-shell module already emits a chunk auto-named i18n (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=2 then pnpm --filter @object-ui/console build then node scripts/check-eager-closure-budget.mjs.

Control — main untouched, base e307c9896

✅ Console eager closure is 3178.9 KB gzipped across 48 of 516 chunks (budget: 3191.4 KB, headroom: 12.5 KB).
✅ Per-chunk eager budgets (3 chunks weighed):
✅ vendor-objectstack 926.1 KB / 944.3 KB ceiling (headroom 18.2 KB)
✅ framework 511.7 KB / 511.7 KB ceiling (headroom 0.0 KB)
✅ ui-components 387.8 KB / 389.6 KB ceiling (headroom 1.9 KB)

Gate exit 0. framework 523,959 B against 524,000 — the "headroom 0.0 KB" is a true rounding of 41 bytes.

This branch, head b437996cd (base e307c9896)

✅ Console eager closure is 3179.7 KB gzipped across 50 of 518 chunks (budget: 3191.4 KB, headroom: 11.7 KB).
✅ Per-chunk eager budgets (4 chunks weighed):
✅ vendor-objectstack 926.1 KB / 944.3 KB ceiling (headroom 18.2 KB)
✅ i18n-locales 435.6 KB / 444.3 KB ceiling (headroom 8.7 KB)
✅ ui-components 387.5 KB / 389.6 KB ceiling (headroom 2.1 KB)
✅ framework 60.0 KB / 69.3 KB ceiling (headroom 9.3 KB)
✅ Ceiling sensitivity (5 ceilings, each weighed against the report just read):
✅ aggregate closure 3179.7 KB measured / 3191.4 KB ceiling (headroom 11.7 KB = 0.13x the 89.0 KB regression) [MAX_EAGER_CLOSURE_GZIP_BYTES]
✅ chunk `vendor-objectstack` 926.1 KB measured / 944.3 KB ceiling (headroom 18.2 KB = 0.20x the 89.0 KB regression) [PER_CHUNK_GZIP_CEILINGS['vendor-objectstack']]
✅ chunk `i18n-locales` 435.6 KB measured / 444.3 KB ceiling (headroom 8.7 KB = 0.10x the 89.0 KB regression) [PER_CHUNK_GZIP_CEILINGS['i18n-locales']]
✅ chunk `framework` 60.0 KB measured / 69.3 KB ceiling (headroom 9.3 KB = 0.10x the 89.0 KB regression) [PER_CHUNK_GZIP_CEILINGS['framework']]
✅ chunk `ui-components` 387.5 KB measured / 389.6 KB ceiling (headroom 2.1 KB = 0.02x the 89.0 KB regression) [PER_CHUNK_GZIP_CEILINGS['ui-components']]

Gate exit 0, sensitivity passes — not exit 2 — for both re-baselined chunks, each at 0.10x, the same headroom convention the retired framework pair 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:

control-shape buildthis branch
eager modules4,4724,472
modules only on one side00
shared modules whose rendered bytes differ4, by 183 B in total

The four are components/src/lib/utils.tsx, components/src/sidebar-fixes.css, i18n/src/LocalizationContext.tsx and i18n/src/locales/en.ts — chunk-local rendering artefacts, not content.

gzippedraw
control (e307c9896, untouched)3,255,23311,177,729
this branch (b437996cd)3,256,01211,178,566
delta+779 B (+0.024%)+837 B (+0.0075%)

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

chunkmodulescomposition
framework140core 63, react 63, types 14 — 100% its own test (was 166 modules, 21 of them foreign)
i18n-locales22packages/i18n 100%
data-adapter10data-objectstack 5 (91.8%), core 4, types 1
infrastructure63auth 22, node_modules 33, permissions 8 (was 65, incl. 2 i18n)

⚠️ Disclosed rather than smoothed over: data-adapter now holds 5 core/types modules (8.5 KB raw) reached only through data-objectstack — the same shared-module pull-in, one tier down and three orders of magnitude smaller. framework cannot be lifted above these two without re-absorbing the catalogue, which is the whole defect.

data-adapter gets no ceiling

17,846 B is smaller than a dozen unbudgeted eager chunks. PER_CHUNK_GZIP_CEILINGS is 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 of apps/console/vite.config.ts and 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 to framework, 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 — framework at 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: 84 back to 80 on both lifted groups, restoring the tie. Mutation proved on disk before the run — priority: 84 occurrences 2 to 0, both mutated group lines present at 80, blob 3c7e2f79… to 4600c365…. Result:

× routes the locale catalogue to .../packages/i18n/src/locales/zh-CN.ts at a priority `framework` cannot tie
× routes the ObjectStack data adapter to .../packages/data-objectstack/src/index.ts at a priority `framework` cannot tie
AssertionError: expected 80 to be greater than 80
Tests 2 failed | 6 passed | 89 skipped (97)

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 equals origin/main's, priority: 84 count 0, i18n-locales mentions 0, main's tied infrastructure line present). The rebuild reproduces the control to the byte — aggregate 3,255,233 and framework 523,959, both identical to the untouched-main control — and the gate returns exit 2:

❌ Budgeted chunk `i18n-locales` is ABSENT from the eager closure reported at apps/console/dist/eager-closure.json. That is a FAILURE, not a pass: a ceiling whose chunk does not exist weighs nothing and would be green forever.

Both legs used trap restore EXIT INT TERM with absolute paths and git checkout HEAD -- on an absolute path; each restore is proved by state — blob hash equal to the HEAD blob and git diff HEAD empty — 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-locales carries 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 at b437996cd, verdict lines quoted above.
  • pnpm exec vitest run --maxWorkers=2 scripts/__tests__ — 100 files, 2,876 tests, at b437996cd. One pre-existing failure, check-sdui-registration-pins.test.ts, already filed as finding(test-infra): check-sdui-registration-pins.test.ts fails on any tree where packages/app-shell/dist exists #6893: it resolves through packages/app-shell/dist when 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 no skip-changeset label: 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.
  • The gates that read 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 lint is eslint . --no-inline-config over 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:

  1. Population comes from eslint's own configuration, not from a guess — all three paths were resolved and linted (none ignored).
  2. Count read from --format json: 3 file entries returned, exactly the three files in the diff.
  3. Invariance for untouched files: eslint.config.js declares no projectService and no parserOptions.project, so type-aware linting is off; and no rule in eslint-rules/ reads any file other than the one under lint (grep -ln 'readFileSync|readdirSync|globSync' eslint-rules/*.js returns 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

… 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
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3179.7 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-BID5JT1s.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.24KB117.82KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.08KB61.71KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.27KB13.32KB
plugin-charts (index.js)70.31KB19.55KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.79KB34.62KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.19KB64.18KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.18KB40.94KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.53KB27.66KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.21KB8.66KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-project-managerClaude

Copy link
Copy Markdown
Collaborator

ACCEPT — verified on the branch by content. Clause ② NO, so this seat lands it.

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. Head b437996cd.

⚠️Clause ② declared by this seat, not inherited. The report used "Clause ②" for the lint-narrowing convention, which is a different thing. The contract question — does this move a published surface? — is answered by the diff: three files, all of them build config and gate script (apps/console/vite.config.ts, scripts/check-eager-closure-budget.mjs, its test). No spec key, no zod validator, no exported type. ⇒ NO, no contract review needed.

Verified by content

probeorigin/mainbranch
files changed3
MAX_EAGER_CLOSURE_GZIP_BYTES3_268_0003_268_000 — unchanged
framework ceiling / baseline524_000 / 514_86371_000 / 61_465
i18n-locales ceiling / baselineabsent455_000 / 446_076
vendor-objectstack, ui-components967_000 / 399_000unchanged
pin cases in the gate's test8 (6 it + 1 it.each over two module ids)

⚠️ Two of my own probes were wrong here and the report was right both times, so I am recording it: I grepped the ceilings for a quoted key and framework is spelled unquoted, so it read as absent; and I counted it( alone, which missed the it.each. Neither was a defect in the change.

⭐ Three things this got right that the brief did not ask for

1. It refused my own suggestion, with a reason. The dispatch pointed at the one-character probe from the measurement (raise infrastructure to 85). Copying it would have left a chunk named infrastructure that is ~94% translation catalogue — the same illegibility one step over — and left data-objectstack misattributed with data-adapter still emitting nothing. A new i18n-locales group was the right call. The expensive half of this defect was never the arithmetic; it was a gate whose message pointed away from its cause, and that is what got fixed.

2. It caught a naming trap I did not see.i18n was rejected as the chunk name because an unrelatedpackages/app-shell module already emits a chunk auto-named i18n (59,974 gz), and per-chunk ceilings are summed per name — the two would have shared one budget silently.

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: framework 60.0 KB measured / 511.7 KB ceiling (headroom 451.7 KB = 5.08x the 89.0 KB regression). That is the blind ceiling the ruling anticipated, demonstrated rather than argued. ⇒ A′ genuinely could not have shipped as two PRs.

The load-bearing claim holds

The 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 framework pair carried.

Ablation, two legs: mutating priority: 84 → 80 reddens the pin by name ("expected 80 to be greater than 80"); restoring main's five-at-80 table rebuilds the control to the byte (aggregate 3,255,233 and framework 523,959, both identical to the untouched-main control) and the gate returns exit 2 on the now-absent budgeted chunk. Both restores proved by state.

The PER_CHUNK_BASELINE provenance comment also states plainly that these two entries are not readings of an unmodified tree — the chunks do not exist without the diff — and why the usual argument does not cover them. That is the honesty this file's header demands of its own numbers.

Landing

Branch is behindmain; bringing the base in, then undrafting for the queue. One pre-existing failure is correctly attributed and not re-filed: check-sdui-registration-pins.test.ts resolves through the gitignored packages/app-shell/dist and reds on any built tree — already #6893, and none of its inputs are in this diff.

⇒ On landing, i18n-locales carries ~8,924 B of headroom (about sixty short translation keys), which unblocks #7467, PR #7391, #7194, #7442 and #7400. ⭐ And the report names what becomes binding next: after ~3–4 KB more copy the aggregate line is the constraint — which is the correct place for it, and an argument for #7486 (option C), ⛔ not for raising anything.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3179.7 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-BID5JT1s.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.24KB117.82KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.08KB61.71KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.27KB13.32KB
plugin-charts (index.js)70.31KB19.55KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.79KB34.62KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.19KB64.18KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.18KB40.94KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.53KB27.66KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.21KB8.66KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-project-manager
os-project-manager marked this pull request as ready for review September 3, 2026 10:02
@os-project-manager
os-project-manager added this pull request to the merge queueSep 3, 2026
Merged via the queue into main with commit 177afebSep 3, 2026
32 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-7399-reattribute-i18n-chunk branch September 3, 2026 10:17
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(console,scripts): give the locale catalogue its own chunk, so the framework ceiling budgets the framework - #7489

Merged
os-project-manager merged 2 commits into
mainfrom
claude/issue-7399-reattribute-i18n-chunk
Sep 3, 2026
Merged

fix(console,scripts): give the locale catalogue its own chunk, so the framework ceiling budgets the framework#7489
os-project-manager merged 2 commits into
mainfrom
claude/issue-7399-reattribute-i18n-chunk

Conversation

@claude

@claudeclaudeBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

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_BYTES is 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.ts declared five workspace groups at priority: 80 with framework written first. Measured on base e307c9896, from the emitted chunk's own module list — the framework chunk held 166 modules, and only 145 came from core|react|types, its own test:

sourcemodulesshare of the chunk's module bytes
packages/i18n — all ten locale catalogues1678.7%
packages/core679.6%
packages/react634.7%
packages/data-objectstack54.6%
packages/types152.3%

framework's regex matches neither intruder, and data-adapter — declared since #5490 — emitted no chunk at all. On the tie, the subgraph reached through @object-ui/react was absorbed by the group listed first. So PER_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

  1. i18n-locales and data-adapter lifted to priority: 84, one tier above the workspace groups and below the vendor tier at 85+; i18n leaves infrastructure'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.
  2. Both affected per-chunk ceilings re-baselined in the same change, PER_CHUNK_BASELINE moving with them. Not optional: left at 524,000 over a 61,465 payload, framework reads 5.08x above its own measurement and evaluateHeadroomSensitivity returns exit 2 for exactly that (shown below).
  3. The composition is pinned in 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 infrastructure from 80 to 85 with its test unchanged. That works, and it was deliberately not copied. Two reasons:

⚠️i18n was rejected as the chunk name: an unrelatedpackages/app-shell module already emits a chunk auto-named i18n (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=2 then pnpm --filter @object-ui/console build then node scripts/check-eager-closure-budget.mjs.

Control — main untouched, base e307c9896

✅ Console eager closure is 3178.9 KB gzipped across 48 of 516 chunks (budget: 3191.4 KB, headroom: 12.5 KB).
✅ Per-chunk eager budgets (3 chunks weighed):
✅ vendor-objectstack 926.1 KB / 944.3 KB ceiling (headroom 18.2 KB)
✅ framework 511.7 KB / 511.7 KB ceiling (headroom 0.0 KB)
✅ ui-components 387.8 KB / 389.6 KB ceiling (headroom 1.9 KB)

Gate exit 0. framework 523,959 B against 524,000 — the "headroom 0.0 KB" is a true rounding of 41 bytes.

This branch, head b437996cd (base e307c9896)

✅ Console eager closure is 3179.7 KB gzipped across 50 of 518 chunks (budget: 3191.4 KB, headroom: 11.7 KB).
✅ Per-chunk eager budgets (4 chunks weighed):
✅ vendor-objectstack 926.1 KB / 944.3 KB ceiling (headroom 18.2 KB)
✅ i18n-locales 435.6 KB / 444.3 KB ceiling (headroom 8.7 KB)
✅ ui-components 387.5 KB / 389.6 KB ceiling (headroom 2.1 KB)
✅ framework 60.0 KB / 69.3 KB ceiling (headroom 9.3 KB)
✅ Ceiling sensitivity (5 ceilings, each weighed against the report just read):
✅ aggregate closure 3179.7 KB measured / 3191.4 KB ceiling (headroom 11.7 KB = 0.13x the 89.0 KB regression) [MAX_EAGER_CLOSURE_GZIP_BYTES]
✅ chunk `vendor-objectstack` 926.1 KB measured / 944.3 KB ceiling (headroom 18.2 KB = 0.20x the 89.0 KB regression) [PER_CHUNK_GZIP_CEILINGS['vendor-objectstack']]
✅ chunk `i18n-locales` 435.6 KB measured / 444.3 KB ceiling (headroom 8.7 KB = 0.10x the 89.0 KB regression) [PER_CHUNK_GZIP_CEILINGS['i18n-locales']]
✅ chunk `framework` 60.0 KB measured / 69.3 KB ceiling (headroom 9.3 KB = 0.10x the 89.0 KB regression) [PER_CHUNK_GZIP_CEILINGS['framework']]
✅ chunk `ui-components` 387.5 KB measured / 389.6 KB ceiling (headroom 2.1 KB = 0.02x the 89.0 KB regression) [PER_CHUNK_GZIP_CEILINGS['ui-components']]

Gate exit 0, sensitivity passes — not exit 2 — for both re-baselined chunks, each at 0.10x, the same headroom convention the retired framework pair 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:

control-shape buildthis branch
eager modules4,4724,472
modules only on one side00
shared modules whose rendered bytes differ4, by 183 B in total

The four are components/src/lib/utils.tsx, components/src/sidebar-fixes.css, i18n/src/LocalizationContext.tsx and i18n/src/locales/en.ts — chunk-local rendering artefacts, not content.

gzippedraw
control (e307c9896, untouched)3,255,23311,177,729
this branch (b437996cd)3,256,01211,178,566
delta+779 B (+0.024%)+837 B (+0.0075%)

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

chunkmodulescomposition
framework140core 63, react 63, types 14 — 100% its own test (was 166 modules, 21 of them foreign)
i18n-locales22packages/i18n 100%
data-adapter10data-objectstack 5 (91.8%), core 4, types 1
infrastructure63auth 22, node_modules 33, permissions 8 (was 65, incl. 2 i18n)

⚠️ Disclosed rather than smoothed over: data-adapter now holds 5 core/types modules (8.5 KB raw) reached only through data-objectstack — the same shared-module pull-in, one tier down and three orders of magnitude smaller. framework cannot be lifted above these two without re-absorbing the catalogue, which is the whole defect.

data-adapter gets no ceiling

17,846 B is smaller than a dozen unbudgeted eager chunks. PER_CHUNK_GZIP_CEILINGS is 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 of apps/console/vite.config.ts and 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 to framework, 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 — framework at 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: 84 back to 80 on both lifted groups, restoring the tie. Mutation proved on disk before the run — priority: 84 occurrences 2 to 0, both mutated group lines present at 80, blob 3c7e2f79… to 4600c365…. Result:

× routes the locale catalogue to .../packages/i18n/src/locales/zh-CN.ts at a priority `framework` cannot tie
× routes the ObjectStack data adapter to .../packages/data-objectstack/src/index.ts at a priority `framework` cannot tie
AssertionError: expected 80 to be greater than 80
Tests 2 failed | 6 passed | 89 skipped (97)

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 equals origin/main's, priority: 84 count 0, i18n-locales mentions 0, main's tied infrastructure line present). The rebuild reproduces the control to the byte — aggregate 3,255,233 and framework 523,959, both identical to the untouched-main control — and the gate returns exit 2:

❌ Budgeted chunk `i18n-locales` is ABSENT from the eager closure reported at apps/console/dist/eager-closure.json. That is a FAILURE, not a pass: a ceiling whose chunk does not exist weighs nothing and would be green forever.

Both legs used trap restore EXIT INT TERM with absolute paths and git checkout HEAD -- on an absolute path; each restore is proved by state — blob hash equal to the HEAD blob and git diff HEAD empty — 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-locales carries 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 at b437996cd, verdict lines quoted above.
  • pnpm exec vitest run --maxWorkers=2 scripts/__tests__ — 100 files, 2,876 tests, at b437996cd. One pre-existing failure, check-sdui-registration-pins.test.ts, already filed as finding(test-infra): check-sdui-registration-pins.test.ts fails on any tree where packages/app-shell/dist exists #6893: it resolves through packages/app-shell/dist when 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 no skip-changeset label: 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.
  • The gates that read 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 lint is eslint . --no-inline-config over 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:

  1. Population comes from eslint's own configuration, not from a guess — all three paths were resolved and linted (none ignored).
  2. Count read from --format json: 3 file entries returned, exactly the three files in the diff.
  3. Invariance for untouched files: eslint.config.js declares no projectService and no parserOptions.project, so type-aware linting is off; and no rule in eslint-rules/ reads any file other than the one under lint (grep -ln 'readFileSync|readdirSync|globSync' eslint-rules/*.js returns 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

… 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
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3179.7 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-BID5JT1s.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.24KB117.82KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.08KB61.71KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.27KB13.32KB
plugin-charts (index.js)70.31KB19.55KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.79KB34.62KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.19KB64.18KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.18KB40.94KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.53KB27.66KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.21KB8.66KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-project-managerClaude

Copy link
Copy Markdown
Collaborator

ACCEPT — verified on the branch by content. Clause ② NO, so this seat lands it.

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. Head b437996cd.

⚠️Clause ② declared by this seat, not inherited. The report used "Clause ②" for the lint-narrowing convention, which is a different thing. The contract question — does this move a published surface? — is answered by the diff: three files, all of them build config and gate script (apps/console/vite.config.ts, scripts/check-eager-closure-budget.mjs, its test). No spec key, no zod validator, no exported type. ⇒ NO, no contract review needed.

Verified by content

probeorigin/mainbranch
files changed3
MAX_EAGER_CLOSURE_GZIP_BYTES3_268_0003_268_000 — unchanged
framework ceiling / baseline524_000 / 514_86371_000 / 61_465
i18n-locales ceiling / baselineabsent455_000 / 446_076
vendor-objectstack, ui-components967_000 / 399_000unchanged
pin cases in the gate's test8 (6 it + 1 it.each over two module ids)

⚠️ Two of my own probes were wrong here and the report was right both times, so I am recording it: I grepped the ceilings for a quoted key and framework is spelled unquoted, so it read as absent; and I counted it( alone, which missed the it.each. Neither was a defect in the change.

⭐ Three things this got right that the brief did not ask for

1. It refused my own suggestion, with a reason. The dispatch pointed at the one-character probe from the measurement (raise infrastructure to 85). Copying it would have left a chunk named infrastructure that is ~94% translation catalogue — the same illegibility one step over — and left data-objectstack misattributed with data-adapter still emitting nothing. A new i18n-locales group was the right call. The expensive half of this defect was never the arithmetic; it was a gate whose message pointed away from its cause, and that is what got fixed.

2. It caught a naming trap I did not see.i18n was rejected as the chunk name because an unrelatedpackages/app-shell module already emits a chunk auto-named i18n (59,974 gz), and per-chunk ceilings are summed per name — the two would have shared one budget silently.

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: framework 60.0 KB measured / 511.7 KB ceiling (headroom 451.7 KB = 5.08x the 89.0 KB regression). That is the blind ceiling the ruling anticipated, demonstrated rather than argued. ⇒ A′ genuinely could not have shipped as two PRs.

The load-bearing claim holds

The 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 framework pair carried.

Ablation, two legs: mutating priority: 84 → 80 reddens the pin by name ("expected 80 to be greater than 80"); restoring main's five-at-80 table rebuilds the control to the byte (aggregate 3,255,233 and framework 523,959, both identical to the untouched-main control) and the gate returns exit 2 on the now-absent budgeted chunk. Both restores proved by state.

The PER_CHUNK_BASELINE provenance comment also states plainly that these two entries are not readings of an unmodified tree — the chunks do not exist without the diff — and why the usual argument does not cover them. That is the honesty this file's header demands of its own numbers.

Landing

Branch is behindmain; bringing the base in, then undrafting for the queue. One pre-existing failure is correctly attributed and not re-filed: check-sdui-registration-pins.test.ts resolves through the gitignored packages/app-shell/dist and reds on any built tree — already #6893, and none of its inputs are in this diff.

⇒ On landing, i18n-locales carries ~8,924 B of headroom (about sixty short translation keys), which unblocks #7467, PR #7391, #7194, #7442 and #7400. ⭐ And the report names what becomes binding next: after ~3–4 KB more copy the aggregate line is the constraint — which is the correct place for it, and an argument for #7486 (option C), ⛔ not for raising anything.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3179.7 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-BID5JT1s.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.24KB117.82KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.08KB61.71KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.27KB13.32KB
plugin-charts (index.js)70.31KB19.55KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.79KB34.62KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.19KB64.18KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.18KB40.94KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.53KB27.66KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.21KB8.66KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-project-manager
os-project-manager marked this pull request as ready for review September 3, 2026 10:02
@os-project-manager
os-project-manager added this pull request to the merge queueSep 3, 2026
Merged via the queue into main with commit 177afebSep 3, 2026
32 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-7399-reattribute-i18n-chunk branch September 3, 2026 10:17
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix(console,scripts): give the locale catalogue its own chunk, so the framework ceiling budgets the framework - #7489

Merged
os-project-manager merged 2 commits into
mainfrom
claude/issue-7399-reattribute-i18n-chunk
Sep 3, 2026
Merged

fix(console,scripts): give the locale catalogue its own chunk, so the framework ceiling budgets the framework#7489
os-project-manager merged 2 commits into
mainfrom
claude/issue-7399-reattribute-i18n-chunk

Conversation

@claude

@claudeclaudeBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

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_BYTES is 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.ts declared five workspace groups at priority: 80 with framework written first. Measured on base e307c9896, from the emitted chunk's own module list — the framework chunk held 166 modules, and only 145 came from core|react|types, its own test:

sourcemodulesshare of the chunk's module bytes
packages/i18n — all ten locale catalogues1678.7%
packages/core679.6%
packages/react634.7%
packages/data-objectstack54.6%
packages/types152.3%

framework's regex matches neither intruder, and data-adapter — declared since #5490 — emitted no chunk at all. On the tie, the subgraph reached through @object-ui/react was absorbed by the group listed first. So PER_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

  1. i18n-locales and data-adapter lifted to priority: 84, one tier above the workspace groups and below the vendor tier at 85+; i18n leaves infrastructure'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.
  2. Both affected per-chunk ceilings re-baselined in the same change, PER_CHUNK_BASELINE moving with them. Not optional: left at 524,000 over a 61,465 payload, framework reads 5.08x above its own measurement and evaluateHeadroomSensitivity returns exit 2 for exactly that (shown below).
  3. The composition is pinned in 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 infrastructure from 80 to 85 with its test unchanged. That works, and it was deliberately not copied. Two reasons:

⚠️i18n was rejected as the chunk name: an unrelatedpackages/app-shell module already emits a chunk auto-named i18n (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=2 then pnpm --filter @object-ui/console build then node scripts/check-eager-closure-budget.mjs.

Control — main untouched, base e307c9896

✅ Console eager closure is 3178.9 KB gzipped across 48 of 516 chunks (budget: 3191.4 KB, headroom: 12.5 KB).
✅ Per-chunk eager budgets (3 chunks weighed):
✅ vendor-objectstack 926.1 KB / 944.3 KB ceiling (headroom 18.2 KB)
✅ framework 511.7 KB / 511.7 KB ceiling (headroom 0.0 KB)
✅ ui-components 387.8 KB / 389.6 KB ceiling (headroom 1.9 KB)

Gate exit 0. framework 523,959 B against 524,000 — the "headroom 0.0 KB" is a true rounding of 41 bytes.

This branch, head b437996cd (base e307c9896)

✅ Console eager closure is 3179.7 KB gzipped across 50 of 518 chunks (budget: 3191.4 KB, headroom: 11.7 KB).
✅ Per-chunk eager budgets (4 chunks weighed):
✅ vendor-objectstack 926.1 KB / 944.3 KB ceiling (headroom 18.2 KB)
✅ i18n-locales 435.6 KB / 444.3 KB ceiling (headroom 8.7 KB)
✅ ui-components 387.5 KB / 389.6 KB ceiling (headroom 2.1 KB)
✅ framework 60.0 KB / 69.3 KB ceiling (headroom 9.3 KB)
✅ Ceiling sensitivity (5 ceilings, each weighed against the report just read):
✅ aggregate closure 3179.7 KB measured / 3191.4 KB ceiling (headroom 11.7 KB = 0.13x the 89.0 KB regression) [MAX_EAGER_CLOSURE_GZIP_BYTES]
✅ chunk `vendor-objectstack` 926.1 KB measured / 944.3 KB ceiling (headroom 18.2 KB = 0.20x the 89.0 KB regression) [PER_CHUNK_GZIP_CEILINGS['vendor-objectstack']]
✅ chunk `i18n-locales` 435.6 KB measured / 444.3 KB ceiling (headroom 8.7 KB = 0.10x the 89.0 KB regression) [PER_CHUNK_GZIP_CEILINGS['i18n-locales']]
✅ chunk `framework` 60.0 KB measured / 69.3 KB ceiling (headroom 9.3 KB = 0.10x the 89.0 KB regression) [PER_CHUNK_GZIP_CEILINGS['framework']]
✅ chunk `ui-components` 387.5 KB measured / 389.6 KB ceiling (headroom 2.1 KB = 0.02x the 89.0 KB regression) [PER_CHUNK_GZIP_CEILINGS['ui-components']]

Gate exit 0, sensitivity passes — not exit 2 — for both re-baselined chunks, each at 0.10x, the same headroom convention the retired framework pair 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:

control-shape buildthis branch
eager modules4,4724,472
modules only on one side00
shared modules whose rendered bytes differ4, by 183 B in total

The four are components/src/lib/utils.tsx, components/src/sidebar-fixes.css, i18n/src/LocalizationContext.tsx and i18n/src/locales/en.ts — chunk-local rendering artefacts, not content.

gzippedraw
control (e307c9896, untouched)3,255,23311,177,729
this branch (b437996cd)3,256,01211,178,566
delta+779 B (+0.024%)+837 B (+0.0075%)

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

chunkmodulescomposition
framework140core 63, react 63, types 14 — 100% its own test (was 166 modules, 21 of them foreign)
i18n-locales22packages/i18n 100%
data-adapter10data-objectstack 5 (91.8%), core 4, types 1
infrastructure63auth 22, node_modules 33, permissions 8 (was 65, incl. 2 i18n)

⚠️ Disclosed rather than smoothed over: data-adapter now holds 5 core/types modules (8.5 KB raw) reached only through data-objectstack — the same shared-module pull-in, one tier down and three orders of magnitude smaller. framework cannot be lifted above these two without re-absorbing the catalogue, which is the whole defect.

data-adapter gets no ceiling

17,846 B is smaller than a dozen unbudgeted eager chunks. PER_CHUNK_GZIP_CEILINGS is 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 of apps/console/vite.config.ts and 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 to framework, 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 — framework at 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: 84 back to 80 on both lifted groups, restoring the tie. Mutation proved on disk before the run — priority: 84 occurrences 2 to 0, both mutated group lines present at 80, blob 3c7e2f79… to 4600c365…. Result:

× routes the locale catalogue to .../packages/i18n/src/locales/zh-CN.ts at a priority `framework` cannot tie
× routes the ObjectStack data adapter to .../packages/data-objectstack/src/index.ts at a priority `framework` cannot tie
AssertionError: expected 80 to be greater than 80
Tests 2 failed | 6 passed | 89 skipped (97)

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 equals origin/main's, priority: 84 count 0, i18n-locales mentions 0, main's tied infrastructure line present). The rebuild reproduces the control to the byte — aggregate 3,255,233 and framework 523,959, both identical to the untouched-main control — and the gate returns exit 2:

❌ Budgeted chunk `i18n-locales` is ABSENT from the eager closure reported at apps/console/dist/eager-closure.json. That is a FAILURE, not a pass: a ceiling whose chunk does not exist weighs nothing and would be green forever.

Both legs used trap restore EXIT INT TERM with absolute paths and git checkout HEAD -- on an absolute path; each restore is proved by state — blob hash equal to the HEAD blob and git diff HEAD empty — 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-locales carries 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 at b437996cd, verdict lines quoted above.
  • pnpm exec vitest run --maxWorkers=2 scripts/__tests__ — 100 files, 2,876 tests, at b437996cd. One pre-existing failure, check-sdui-registration-pins.test.ts, already filed as finding(test-infra): check-sdui-registration-pins.test.ts fails on any tree where packages/app-shell/dist exists #6893: it resolves through packages/app-shell/dist when 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 no skip-changeset label: 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.
  • The gates that read 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 lint is eslint . --no-inline-config over 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:

  1. Population comes from eslint's own configuration, not from a guess — all three paths were resolved and linted (none ignored).
  2. Count read from --format json: 3 file entries returned, exactly the three files in the diff.
  3. Invariance for untouched files: eslint.config.js declares no projectService and no parserOptions.project, so type-aware linting is off; and no rule in eslint-rules/ reads any file other than the one under lint (grep -ln 'readFileSync|readdirSync|globSync' eslint-rules/*.js returns 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

… 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
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3179.7 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-BID5JT1s.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.24KB117.82KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.08KB61.71KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.27KB13.32KB
plugin-charts (index.js)70.31KB19.55KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.79KB34.62KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.19KB64.18KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.18KB40.94KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.53KB27.66KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.21KB8.66KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-project-managerClaude

Copy link
Copy Markdown
Collaborator

ACCEPT — verified on the branch by content. Clause ② NO, so this seat lands it.

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. Head b437996cd.

⚠️Clause ② declared by this seat, not inherited. The report used "Clause ②" for the lint-narrowing convention, which is a different thing. The contract question — does this move a published surface? — is answered by the diff: three files, all of them build config and gate script (apps/console/vite.config.ts, scripts/check-eager-closure-budget.mjs, its test). No spec key, no zod validator, no exported type. ⇒ NO, no contract review needed.

Verified by content

probeorigin/mainbranch
files changed3
MAX_EAGER_CLOSURE_GZIP_BYTES3_268_0003_268_000 — unchanged
framework ceiling / baseline524_000 / 514_86371_000 / 61_465
i18n-locales ceiling / baselineabsent455_000 / 446_076
vendor-objectstack, ui-components967_000 / 399_000unchanged
pin cases in the gate's test8 (6 it + 1 it.each over two module ids)

⚠️ Two of my own probes were wrong here and the report was right both times, so I am recording it: I grepped the ceilings for a quoted key and framework is spelled unquoted, so it read as absent; and I counted it( alone, which missed the it.each. Neither was a defect in the change.

⭐ Three things this got right that the brief did not ask for

1. It refused my own suggestion, with a reason. The dispatch pointed at the one-character probe from the measurement (raise infrastructure to 85). Copying it would have left a chunk named infrastructure that is ~94% translation catalogue — the same illegibility one step over — and left data-objectstack misattributed with data-adapter still emitting nothing. A new i18n-locales group was the right call. The expensive half of this defect was never the arithmetic; it was a gate whose message pointed away from its cause, and that is what got fixed.

2. It caught a naming trap I did not see.i18n was rejected as the chunk name because an unrelatedpackages/app-shell module already emits a chunk auto-named i18n (59,974 gz), and per-chunk ceilings are summed per name — the two would have shared one budget silently.

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: framework 60.0 KB measured / 511.7 KB ceiling (headroom 451.7 KB = 5.08x the 89.0 KB regression). That is the blind ceiling the ruling anticipated, demonstrated rather than argued. ⇒ A′ genuinely could not have shipped as two PRs.

The load-bearing claim holds

The 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 framework pair carried.

Ablation, two legs: mutating priority: 84 → 80 reddens the pin by name ("expected 80 to be greater than 80"); restoring main's five-at-80 table rebuilds the control to the byte (aggregate 3,255,233 and framework 523,959, both identical to the untouched-main control) and the gate returns exit 2 on the now-absent budgeted chunk. Both restores proved by state.

The PER_CHUNK_BASELINE provenance comment also states plainly that these two entries are not readings of an unmodified tree — the chunks do not exist without the diff — and why the usual argument does not cover them. That is the honesty this file's header demands of its own numbers.

Landing

Branch is behindmain; bringing the base in, then undrafting for the queue. One pre-existing failure is correctly attributed and not re-filed: check-sdui-registration-pins.test.ts resolves through the gitignored packages/app-shell/dist and reds on any built tree — already #6893, and none of its inputs are in this diff.

⇒ On landing, i18n-locales carries ~8,924 B of headroom (about sixty short translation keys), which unblocks #7467, PR #7391, #7194, #7442 and #7400. ⭐ And the report names what becomes binding next: after ~3–4 KB more copy the aggregate line is the constraint — which is the correct place for it, and an argument for #7486 (option C), ⛔ not for raising anything.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3179.7 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-BID5JT1s.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.24KB117.82KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.08KB61.71KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.27KB13.32KB
plugin-charts (index.js)70.31KB19.55KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.79KB34.62KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.19KB64.18KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.18KB40.94KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.53KB27.66KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.21KB8.66KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-project-manager
os-project-manager marked this pull request as ready for review September 3, 2026 10:02
@os-project-manager
os-project-manager added this pull request to the merge queueSep 3, 2026
Merged via the queue into main with commit 177afebSep 3, 2026
32 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-7399-reattribute-i18n-chunk branch September 3, 2026 10:17
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(console,scripts): give the locale catalogue its own chunk, so the framework ceiling budgets the framework - #7489

Merged
os-project-manager merged 2 commits into
mainfrom
claude/issue-7399-reattribute-i18n-chunk
Sep 3, 2026
Merged

fix(console,scripts): give the locale catalogue its own chunk, so the framework ceiling budgets the framework#7489
os-project-manager merged 2 commits into
mainfrom
claude/issue-7399-reattribute-i18n-chunk

Conversation

@claude

@claudeclaudeBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

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_BYTES is 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.ts declared five workspace groups at priority: 80 with framework written first. Measured on base e307c9896, from the emitted chunk's own module list — the framework chunk held 166 modules, and only 145 came from core|react|types, its own test:

sourcemodulesshare of the chunk's module bytes
packages/i18n — all ten locale catalogues1678.7%
packages/core679.6%
packages/react634.7%
packages/data-objectstack54.6%
packages/types152.3%

framework's regex matches neither intruder, and data-adapter — declared since #5490 — emitted no chunk at all. On the tie, the subgraph reached through @object-ui/react was absorbed by the group listed first. So PER_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

  1. i18n-locales and data-adapter lifted to priority: 84, one tier above the workspace groups and below the vendor tier at 85+; i18n leaves infrastructure'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.
  2. Both affected per-chunk ceilings re-baselined in the same change, PER_CHUNK_BASELINE moving with them. Not optional: left at 524,000 over a 61,465 payload, framework reads 5.08x above its own measurement and evaluateHeadroomSensitivity returns exit 2 for exactly that (shown below).
  3. The composition is pinned in 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 infrastructure from 80 to 85 with its test unchanged. That works, and it was deliberately not copied. Two reasons:

⚠️i18n was rejected as the chunk name: an unrelatedpackages/app-shell module already emits a chunk auto-named i18n (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=2 then pnpm --filter @object-ui/console build then node scripts/check-eager-closure-budget.mjs.

Control — main untouched, base e307c9896

✅ Console eager closure is 3178.9 KB gzipped across 48 of 516 chunks (budget: 3191.4 KB, headroom: 12.5 KB).
✅ Per-chunk eager budgets (3 chunks weighed):
✅ vendor-objectstack 926.1 KB / 944.3 KB ceiling (headroom 18.2 KB)
✅ framework 511.7 KB / 511.7 KB ceiling (headroom 0.0 KB)
✅ ui-components 387.8 KB / 389.6 KB ceiling (headroom 1.9 KB)

Gate exit 0. framework 523,959 B against 524,000 — the "headroom 0.0 KB" is a true rounding of 41 bytes.

This branch, head b437996cd (base e307c9896)

✅ Console eager closure is 3179.7 KB gzipped across 50 of 518 chunks (budget: 3191.4 KB, headroom: 11.7 KB).
✅ Per-chunk eager budgets (4 chunks weighed):
✅ vendor-objectstack 926.1 KB / 944.3 KB ceiling (headroom 18.2 KB)
✅ i18n-locales 435.6 KB / 444.3 KB ceiling (headroom 8.7 KB)
✅ ui-components 387.5 KB / 389.6 KB ceiling (headroom 2.1 KB)
✅ framework 60.0 KB / 69.3 KB ceiling (headroom 9.3 KB)
✅ Ceiling sensitivity (5 ceilings, each weighed against the report just read):
✅ aggregate closure 3179.7 KB measured / 3191.4 KB ceiling (headroom 11.7 KB = 0.13x the 89.0 KB regression) [MAX_EAGER_CLOSURE_GZIP_BYTES]
✅ chunk `vendor-objectstack` 926.1 KB measured / 944.3 KB ceiling (headroom 18.2 KB = 0.20x the 89.0 KB regression) [PER_CHUNK_GZIP_CEILINGS['vendor-objectstack']]
✅ chunk `i18n-locales` 435.6 KB measured / 444.3 KB ceiling (headroom 8.7 KB = 0.10x the 89.0 KB regression) [PER_CHUNK_GZIP_CEILINGS['i18n-locales']]
✅ chunk `framework` 60.0 KB measured / 69.3 KB ceiling (headroom 9.3 KB = 0.10x the 89.0 KB regression) [PER_CHUNK_GZIP_CEILINGS['framework']]
✅ chunk `ui-components` 387.5 KB measured / 389.6 KB ceiling (headroom 2.1 KB = 0.02x the 89.0 KB regression) [PER_CHUNK_GZIP_CEILINGS['ui-components']]

Gate exit 0, sensitivity passes — not exit 2 — for both re-baselined chunks, each at 0.10x, the same headroom convention the retired framework pair 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:

control-shape buildthis branch
eager modules4,4724,472
modules only on one side00
shared modules whose rendered bytes differ4, by 183 B in total

The four are components/src/lib/utils.tsx, components/src/sidebar-fixes.css, i18n/src/LocalizationContext.tsx and i18n/src/locales/en.ts — chunk-local rendering artefacts, not content.

gzippedraw
control (e307c9896, untouched)3,255,23311,177,729
this branch (b437996cd)3,256,01211,178,566
delta+779 B (+0.024%)+837 B (+0.0075%)

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

chunkmodulescomposition
framework140core 63, react 63, types 14 — 100% its own test (was 166 modules, 21 of them foreign)
i18n-locales22packages/i18n 100%
data-adapter10data-objectstack 5 (91.8%), core 4, types 1
infrastructure63auth 22, node_modules 33, permissions 8 (was 65, incl. 2 i18n)

⚠️ Disclosed rather than smoothed over: data-adapter now holds 5 core/types modules (8.5 KB raw) reached only through data-objectstack — the same shared-module pull-in, one tier down and three orders of magnitude smaller. framework cannot be lifted above these two without re-absorbing the catalogue, which is the whole defect.

data-adapter gets no ceiling

17,846 B is smaller than a dozen unbudgeted eager chunks. PER_CHUNK_GZIP_CEILINGS is 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 of apps/console/vite.config.ts and 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 to framework, 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 — framework at 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: 84 back to 80 on both lifted groups, restoring the tie. Mutation proved on disk before the run — priority: 84 occurrences 2 to 0, both mutated group lines present at 80, blob 3c7e2f79… to 4600c365…. Result:

× routes the locale catalogue to .../packages/i18n/src/locales/zh-CN.ts at a priority `framework` cannot tie
× routes the ObjectStack data adapter to .../packages/data-objectstack/src/index.ts at a priority `framework` cannot tie
AssertionError: expected 80 to be greater than 80
Tests 2 failed | 6 passed | 89 skipped (97)

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 equals origin/main's, priority: 84 count 0, i18n-locales mentions 0, main's tied infrastructure line present). The rebuild reproduces the control to the byte — aggregate 3,255,233 and framework 523,959, both identical to the untouched-main control — and the gate returns exit 2:

❌ Budgeted chunk `i18n-locales` is ABSENT from the eager closure reported at apps/console/dist/eager-closure.json. That is a FAILURE, not a pass: a ceiling whose chunk does not exist weighs nothing and would be green forever.

Both legs used trap restore EXIT INT TERM with absolute paths and git checkout HEAD -- on an absolute path; each restore is proved by state — blob hash equal to the HEAD blob and git diff HEAD empty — 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-locales carries 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 at b437996cd, verdict lines quoted above.
  • pnpm exec vitest run --maxWorkers=2 scripts/__tests__ — 100 files, 2,876 tests, at b437996cd. One pre-existing failure, check-sdui-registration-pins.test.ts, already filed as finding(test-infra): check-sdui-registration-pins.test.ts fails on any tree where packages/app-shell/dist exists #6893: it resolves through packages/app-shell/dist when 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 no skip-changeset label: 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.
  • The gates that read 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 lint is eslint . --no-inline-config over 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:

  1. Population comes from eslint's own configuration, not from a guess — all three paths were resolved and linted (none ignored).
  2. Count read from --format json: 3 file entries returned, exactly the three files in the diff.
  3. Invariance for untouched files: eslint.config.js declares no projectService and no parserOptions.project, so type-aware linting is off; and no rule in eslint-rules/ reads any file other than the one under lint (grep -ln 'readFileSync|readdirSync|globSync' eslint-rules/*.js returns 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

… 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
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3179.7 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-BID5JT1s.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.24KB117.82KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.08KB61.71KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.27KB13.32KB
plugin-charts (index.js)70.31KB19.55KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.79KB34.62KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.19KB64.18KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.18KB40.94KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.53KB27.66KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.21KB8.66KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-project-managerClaude

Copy link
Copy Markdown
Collaborator

ACCEPT — verified on the branch by content. Clause ② NO, so this seat lands it.

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. Head b437996cd.

⚠️Clause ② declared by this seat, not inherited. The report used "Clause ②" for the lint-narrowing convention, which is a different thing. The contract question — does this move a published surface? — is answered by the diff: three files, all of them build config and gate script (apps/console/vite.config.ts, scripts/check-eager-closure-budget.mjs, its test). No spec key, no zod validator, no exported type. ⇒ NO, no contract review needed.

Verified by content

probeorigin/mainbranch
files changed3
MAX_EAGER_CLOSURE_GZIP_BYTES3_268_0003_268_000 — unchanged
framework ceiling / baseline524_000 / 514_86371_000 / 61_465
i18n-locales ceiling / baselineabsent455_000 / 446_076
vendor-objectstack, ui-components967_000 / 399_000unchanged
pin cases in the gate's test8 (6 it + 1 it.each over two module ids)

⚠️ Two of my own probes were wrong here and the report was right both times, so I am recording it: I grepped the ceilings for a quoted key and framework is spelled unquoted, so it read as absent; and I counted it( alone, which missed the it.each. Neither was a defect in the change.

⭐ Three things this got right that the brief did not ask for

1. It refused my own suggestion, with a reason. The dispatch pointed at the one-character probe from the measurement (raise infrastructure to 85). Copying it would have left a chunk named infrastructure that is ~94% translation catalogue — the same illegibility one step over — and left data-objectstack misattributed with data-adapter still emitting nothing. A new i18n-locales group was the right call. The expensive half of this defect was never the arithmetic; it was a gate whose message pointed away from its cause, and that is what got fixed.

2. It caught a naming trap I did not see.i18n was rejected as the chunk name because an unrelatedpackages/app-shell module already emits a chunk auto-named i18n (59,974 gz), and per-chunk ceilings are summed per name — the two would have shared one budget silently.

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: framework 60.0 KB measured / 511.7 KB ceiling (headroom 451.7 KB = 5.08x the 89.0 KB regression). That is the blind ceiling the ruling anticipated, demonstrated rather than argued. ⇒ A′ genuinely could not have shipped as two PRs.

The load-bearing claim holds

The 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 framework pair carried.

Ablation, two legs: mutating priority: 84 → 80 reddens the pin by name ("expected 80 to be greater than 80"); restoring main's five-at-80 table rebuilds the control to the byte (aggregate 3,255,233 and framework 523,959, both identical to the untouched-main control) and the gate returns exit 2 on the now-absent budgeted chunk. Both restores proved by state.

The PER_CHUNK_BASELINE provenance comment also states plainly that these two entries are not readings of an unmodified tree — the chunks do not exist without the diff — and why the usual argument does not cover them. That is the honesty this file's header demands of its own numbers.

Landing

Branch is behindmain; bringing the base in, then undrafting for the queue. One pre-existing failure is correctly attributed and not re-filed: check-sdui-registration-pins.test.ts resolves through the gitignored packages/app-shell/dist and reds on any built tree — already #6893, and none of its inputs are in this diff.

⇒ On landing, i18n-locales carries ~8,924 B of headroom (about sixty short translation keys), which unblocks #7467, PR #7391, #7194, #7442 and #7400. ⭐ And the report names what becomes binding next: after ~3–4 KB more copy the aggregate line is the constraint — which is the correct place for it, and an argument for #7486 (option C), ⛔ not for raising anything.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3179.7 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-BID5JT1s.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.24KB117.82KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.08KB61.71KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.27KB13.32KB
plugin-charts (index.js)70.31KB19.55KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.79KB34.62KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.19KB64.18KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.18KB40.94KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.53KB27.66KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.21KB8.66KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-project-manager
os-project-manager marked this pull request as ready for review September 3, 2026 10:02
@os-project-manager
os-project-manager added this pull request to the merge queueSep 3, 2026
Merged via the queue into main with commit 177afebSep 3, 2026
32 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-7399-reattribute-i18n-chunk branch September 3, 2026 10:17
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(console,scripts): give the locale catalogue its own chunk, so the framework ceiling budgets the framework - #7489

Merged
os-project-manager merged 2 commits into
mainfrom
claude/issue-7399-reattribute-i18n-chunk
Sep 3, 2026
Merged

fix(console,scripts): give the locale catalogue its own chunk, so the framework ceiling budgets the framework#7489
os-project-manager merged 2 commits into
mainfrom
claude/issue-7399-reattribute-i18n-chunk

Conversation

@claude

@claudeclaudeBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

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_BYTES is 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.ts declared five workspace groups at priority: 80 with framework written first. Measured on base e307c9896, from the emitted chunk's own module list — the framework chunk held 166 modules, and only 145 came from core|react|types, its own test:

sourcemodulesshare of the chunk's module bytes
packages/i18n — all ten locale catalogues1678.7%
packages/core679.6%
packages/react634.7%
packages/data-objectstack54.6%
packages/types152.3%

framework's regex matches neither intruder, and data-adapter — declared since #5490 — emitted no chunk at all. On the tie, the subgraph reached through @object-ui/react was absorbed by the group listed first. So PER_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

  1. i18n-locales and data-adapter lifted to priority: 84, one tier above the workspace groups and below the vendor tier at 85+; i18n leaves infrastructure'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.
  2. Both affected per-chunk ceilings re-baselined in the same change, PER_CHUNK_BASELINE moving with them. Not optional: left at 524,000 over a 61,465 payload, framework reads 5.08x above its own measurement and evaluateHeadroomSensitivity returns exit 2 for exactly that (shown below).
  3. The composition is pinned in 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 infrastructure from 80 to 85 with its test unchanged. That works, and it was deliberately not copied. Two reasons:

⚠️i18n was rejected as the chunk name: an unrelatedpackages/app-shell module already emits a chunk auto-named i18n (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=2 then pnpm --filter @object-ui/console build then node scripts/check-eager-closure-budget.mjs.

Control — main untouched, base e307c9896

✅ Console eager closure is 3178.9 KB gzipped across 48 of 516 chunks (budget: 3191.4 KB, headroom: 12.5 KB).
✅ Per-chunk eager budgets (3 chunks weighed):
✅ vendor-objectstack 926.1 KB / 944.3 KB ceiling (headroom 18.2 KB)
✅ framework 511.7 KB / 511.7 KB ceiling (headroom 0.0 KB)
✅ ui-components 387.8 KB / 389.6 KB ceiling (headroom 1.9 KB)

Gate exit 0. framework 523,959 B against 524,000 — the "headroom 0.0 KB" is a true rounding of 41 bytes.

This branch, head b437996cd (base e307c9896)

✅ Console eager closure is 3179.7 KB gzipped across 50 of 518 chunks (budget: 3191.4 KB, headroom: 11.7 KB).
✅ Per-chunk eager budgets (4 chunks weighed):
✅ vendor-objectstack 926.1 KB / 944.3 KB ceiling (headroom 18.2 KB)
✅ i18n-locales 435.6 KB / 444.3 KB ceiling (headroom 8.7 KB)
✅ ui-components 387.5 KB / 389.6 KB ceiling (headroom 2.1 KB)
✅ framework 60.0 KB / 69.3 KB ceiling (headroom 9.3 KB)
✅ Ceiling sensitivity (5 ceilings, each weighed against the report just read):
✅ aggregate closure 3179.7 KB measured / 3191.4 KB ceiling (headroom 11.7 KB = 0.13x the 89.0 KB regression) [MAX_EAGER_CLOSURE_GZIP_BYTES]
✅ chunk `vendor-objectstack` 926.1 KB measured / 944.3 KB ceiling (headroom 18.2 KB = 0.20x the 89.0 KB regression) [PER_CHUNK_GZIP_CEILINGS['vendor-objectstack']]
✅ chunk `i18n-locales` 435.6 KB measured / 444.3 KB ceiling (headroom 8.7 KB = 0.10x the 89.0 KB regression) [PER_CHUNK_GZIP_CEILINGS['i18n-locales']]
✅ chunk `framework` 60.0 KB measured / 69.3 KB ceiling (headroom 9.3 KB = 0.10x the 89.0 KB regression) [PER_CHUNK_GZIP_CEILINGS['framework']]
✅ chunk `ui-components` 387.5 KB measured / 389.6 KB ceiling (headroom 2.1 KB = 0.02x the 89.0 KB regression) [PER_CHUNK_GZIP_CEILINGS['ui-components']]

Gate exit 0, sensitivity passes — not exit 2 — for both re-baselined chunks, each at 0.10x, the same headroom convention the retired framework pair 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:

control-shape buildthis branch
eager modules4,4724,472
modules only on one side00
shared modules whose rendered bytes differ4, by 183 B in total

The four are components/src/lib/utils.tsx, components/src/sidebar-fixes.css, i18n/src/LocalizationContext.tsx and i18n/src/locales/en.ts — chunk-local rendering artefacts, not content.

gzippedraw
control (e307c9896, untouched)3,255,23311,177,729
this branch (b437996cd)3,256,01211,178,566
delta+779 B (+0.024%)+837 B (+0.0075%)

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

chunkmodulescomposition
framework140core 63, react 63, types 14 — 100% its own test (was 166 modules, 21 of them foreign)
i18n-locales22packages/i18n 100%
data-adapter10data-objectstack 5 (91.8%), core 4, types 1
infrastructure63auth 22, node_modules 33, permissions 8 (was 65, incl. 2 i18n)

⚠️ Disclosed rather than smoothed over: data-adapter now holds 5 core/types modules (8.5 KB raw) reached only through data-objectstack — the same shared-module pull-in, one tier down and three orders of magnitude smaller. framework cannot be lifted above these two without re-absorbing the catalogue, which is the whole defect.

data-adapter gets no ceiling

17,846 B is smaller than a dozen unbudgeted eager chunks. PER_CHUNK_GZIP_CEILINGS is 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 of apps/console/vite.config.ts and 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 to framework, 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 — framework at 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: 84 back to 80 on both lifted groups, restoring the tie. Mutation proved on disk before the run — priority: 84 occurrences 2 to 0, both mutated group lines present at 80, blob 3c7e2f79… to 4600c365…. Result:

× routes the locale catalogue to .../packages/i18n/src/locales/zh-CN.ts at a priority `framework` cannot tie
× routes the ObjectStack data adapter to .../packages/data-objectstack/src/index.ts at a priority `framework` cannot tie
AssertionError: expected 80 to be greater than 80
Tests 2 failed | 6 passed | 89 skipped (97)

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 equals origin/main's, priority: 84 count 0, i18n-locales mentions 0, main's tied infrastructure line present). The rebuild reproduces the control to the byte — aggregate 3,255,233 and framework 523,959, both identical to the untouched-main control — and the gate returns exit 2:

❌ Budgeted chunk `i18n-locales` is ABSENT from the eager closure reported at apps/console/dist/eager-closure.json. That is a FAILURE, not a pass: a ceiling whose chunk does not exist weighs nothing and would be green forever.

Both legs used trap restore EXIT INT TERM with absolute paths and git checkout HEAD -- on an absolute path; each restore is proved by state — blob hash equal to the HEAD blob and git diff HEAD empty — 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-locales carries 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 at b437996cd, verdict lines quoted above.
  • pnpm exec vitest run --maxWorkers=2 scripts/__tests__ — 100 files, 2,876 tests, at b437996cd. One pre-existing failure, check-sdui-registration-pins.test.ts, already filed as finding(test-infra): check-sdui-registration-pins.test.ts fails on any tree where packages/app-shell/dist exists #6893: it resolves through packages/app-shell/dist when 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 no skip-changeset label: 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.
  • The gates that read 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 lint is eslint . --no-inline-config over 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:

  1. Population comes from eslint's own configuration, not from a guess — all three paths were resolved and linted (none ignored).
  2. Count read from --format json: 3 file entries returned, exactly the three files in the diff.
  3. Invariance for untouched files: eslint.config.js declares no projectService and no parserOptions.project, so type-aware linting is off; and no rule in eslint-rules/ reads any file other than the one under lint (grep -ln 'readFileSync|readdirSync|globSync' eslint-rules/*.js returns 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

… 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
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3179.7 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-BID5JT1s.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.24KB117.82KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.08KB61.71KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.27KB13.32KB
plugin-charts (index.js)70.31KB19.55KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.79KB34.62KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.19KB64.18KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.18KB40.94KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.53KB27.66KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.21KB8.66KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-project-managerClaude

Copy link
Copy Markdown
Collaborator

ACCEPT — verified on the branch by content. Clause ② NO, so this seat lands it.

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. Head b437996cd.

⚠️Clause ② declared by this seat, not inherited. The report used "Clause ②" for the lint-narrowing convention, which is a different thing. The contract question — does this move a published surface? — is answered by the diff: three files, all of them build config and gate script (apps/console/vite.config.ts, scripts/check-eager-closure-budget.mjs, its test). No spec key, no zod validator, no exported type. ⇒ NO, no contract review needed.

Verified by content

probeorigin/mainbranch
files changed3
MAX_EAGER_CLOSURE_GZIP_BYTES3_268_0003_268_000 — unchanged
framework ceiling / baseline524_000 / 514_86371_000 / 61_465
i18n-locales ceiling / baselineabsent455_000 / 446_076
vendor-objectstack, ui-components967_000 / 399_000unchanged
pin cases in the gate's test8 (6 it + 1 it.each over two module ids)

⚠️ Two of my own probes were wrong here and the report was right both times, so I am recording it: I grepped the ceilings for a quoted key and framework is spelled unquoted, so it read as absent; and I counted it( alone, which missed the it.each. Neither was a defect in the change.

⭐ Three things this got right that the brief did not ask for

1. It refused my own suggestion, with a reason. The dispatch pointed at the one-character probe from the measurement (raise infrastructure to 85). Copying it would have left a chunk named infrastructure that is ~94% translation catalogue — the same illegibility one step over — and left data-objectstack misattributed with data-adapter still emitting nothing. A new i18n-locales group was the right call. The expensive half of this defect was never the arithmetic; it was a gate whose message pointed away from its cause, and that is what got fixed.

2. It caught a naming trap I did not see.i18n was rejected as the chunk name because an unrelatedpackages/app-shell module already emits a chunk auto-named i18n (59,974 gz), and per-chunk ceilings are summed per name — the two would have shared one budget silently.

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: framework 60.0 KB measured / 511.7 KB ceiling (headroom 451.7 KB = 5.08x the 89.0 KB regression). That is the blind ceiling the ruling anticipated, demonstrated rather than argued. ⇒ A′ genuinely could not have shipped as two PRs.

The load-bearing claim holds

The 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 framework pair carried.

Ablation, two legs: mutating priority: 84 → 80 reddens the pin by name ("expected 80 to be greater than 80"); restoring main's five-at-80 table rebuilds the control to the byte (aggregate 3,255,233 and framework 523,959, both identical to the untouched-main control) and the gate returns exit 2 on the now-absent budgeted chunk. Both restores proved by state.

The PER_CHUNK_BASELINE provenance comment also states plainly that these two entries are not readings of an unmodified tree — the chunks do not exist without the diff — and why the usual argument does not cover them. That is the honesty this file's header demands of its own numbers.

Landing

Branch is behindmain; bringing the base in, then undrafting for the queue. One pre-existing failure is correctly attributed and not re-filed: check-sdui-registration-pins.test.ts resolves through the gitignored packages/app-shell/dist and reds on any built tree — already #6893, and none of its inputs are in this diff.

⇒ On landing, i18n-locales carries ~8,924 B of headroom (about sixty short translation keys), which unblocks #7467, PR #7391, #7194, #7442 and #7400. ⭐ And the report names what becomes binding next: after ~3–4 KB more copy the aggregate line is the constraint — which is the correct place for it, and an argument for #7486 (option C), ⛔ not for raising anything.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3179.7 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-BID5JT1s.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.24KB117.82KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.08KB61.71KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.27KB13.32KB
plugin-charts (index.js)70.31KB19.55KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.79KB34.62KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.19KB64.18KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.18KB40.94KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.53KB27.66KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.21KB8.66KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-project-manager
os-project-manager marked this pull request as ready for review September 3, 2026 10:02
@os-project-manager
os-project-manager added this pull request to the merge queueSep 3, 2026
Merged via the queue into main with commit 177afebSep 3, 2026
32 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-7399-reattribute-i18n-chunk branch September 3, 2026 10:17
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix(console,scripts): give the locale catalogue its own chunk, so the framework ceiling budgets the framework - #7489

Merged
os-project-manager merged 2 commits into
mainfrom
claude/issue-7399-reattribute-i18n-chunk
Sep 3, 2026
Merged

fix(console,scripts): give the locale catalogue its own chunk, so the framework ceiling budgets the framework#7489
os-project-manager merged 2 commits into
mainfrom
claude/issue-7399-reattribute-i18n-chunk

Conversation

@claude

@claudeclaudeBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

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_BYTES is 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.ts declared five workspace groups at priority: 80 with framework written first. Measured on base e307c9896, from the emitted chunk's own module list — the framework chunk held 166 modules, and only 145 came from core|react|types, its own test:

sourcemodulesshare of the chunk's module bytes
packages/i18n — all ten locale catalogues1678.7%
packages/core679.6%
packages/react634.7%
packages/data-objectstack54.6%
packages/types152.3%

framework's regex matches neither intruder, and data-adapter — declared since #5490 — emitted no chunk at all. On the tie, the subgraph reached through @object-ui/react was absorbed by the group listed first. So PER_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

  1. i18n-locales and data-adapter lifted to priority: 84, one tier above the workspace groups and below the vendor tier at 85+; i18n leaves infrastructure'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.
  2. Both affected per-chunk ceilings re-baselined in the same change, PER_CHUNK_BASELINE moving with them. Not optional: left at 524,000 over a 61,465 payload, framework reads 5.08x above its own measurement and evaluateHeadroomSensitivity returns exit 2 for exactly that (shown below).
  3. The composition is pinned in 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 infrastructure from 80 to 85 with its test unchanged. That works, and it was deliberately not copied. Two reasons:

⚠️i18n was rejected as the chunk name: an unrelatedpackages/app-shell module already emits a chunk auto-named i18n (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=2 then pnpm --filter @object-ui/console build then node scripts/check-eager-closure-budget.mjs.

Control — main untouched, base e307c9896

✅ Console eager closure is 3178.9 KB gzipped across 48 of 516 chunks (budget: 3191.4 KB, headroom: 12.5 KB).
✅ Per-chunk eager budgets (3 chunks weighed):
✅ vendor-objectstack 926.1 KB / 944.3 KB ceiling (headroom 18.2 KB)
✅ framework 511.7 KB / 511.7 KB ceiling (headroom 0.0 KB)
✅ ui-components 387.8 KB / 389.6 KB ceiling (headroom 1.9 KB)

Gate exit 0. framework 523,959 B against 524,000 — the "headroom 0.0 KB" is a true rounding of 41 bytes.

This branch, head b437996cd (base e307c9896)

✅ Console eager closure is 3179.7 KB gzipped across 50 of 518 chunks (budget: 3191.4 KB, headroom: 11.7 KB).
✅ Per-chunk eager budgets (4 chunks weighed):
✅ vendor-objectstack 926.1 KB / 944.3 KB ceiling (headroom 18.2 KB)
✅ i18n-locales 435.6 KB / 444.3 KB ceiling (headroom 8.7 KB)
✅ ui-components 387.5 KB / 389.6 KB ceiling (headroom 2.1 KB)
✅ framework 60.0 KB / 69.3 KB ceiling (headroom 9.3 KB)
✅ Ceiling sensitivity (5 ceilings, each weighed against the report just read):
✅ aggregate closure 3179.7 KB measured / 3191.4 KB ceiling (headroom 11.7 KB = 0.13x the 89.0 KB regression) [MAX_EAGER_CLOSURE_GZIP_BYTES]
✅ chunk `vendor-objectstack` 926.1 KB measured / 944.3 KB ceiling (headroom 18.2 KB = 0.20x the 89.0 KB regression) [PER_CHUNK_GZIP_CEILINGS['vendor-objectstack']]
✅ chunk `i18n-locales` 435.6 KB measured / 444.3 KB ceiling (headroom 8.7 KB = 0.10x the 89.0 KB regression) [PER_CHUNK_GZIP_CEILINGS['i18n-locales']]
✅ chunk `framework` 60.0 KB measured / 69.3 KB ceiling (headroom 9.3 KB = 0.10x the 89.0 KB regression) [PER_CHUNK_GZIP_CEILINGS['framework']]
✅ chunk `ui-components` 387.5 KB measured / 389.6 KB ceiling (headroom 2.1 KB = 0.02x the 89.0 KB regression) [PER_CHUNK_GZIP_CEILINGS['ui-components']]

Gate exit 0, sensitivity passes — not exit 2 — for both re-baselined chunks, each at 0.10x, the same headroom convention the retired framework pair 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:

control-shape buildthis branch
eager modules4,4724,472
modules only on one side00
shared modules whose rendered bytes differ4, by 183 B in total

The four are components/src/lib/utils.tsx, components/src/sidebar-fixes.css, i18n/src/LocalizationContext.tsx and i18n/src/locales/en.ts — chunk-local rendering artefacts, not content.

gzippedraw
control (e307c9896, untouched)3,255,23311,177,729
this branch (b437996cd)3,256,01211,178,566
delta+779 B (+0.024%)+837 B (+0.0075%)

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

chunkmodulescomposition
framework140core 63, react 63, types 14 — 100% its own test (was 166 modules, 21 of them foreign)
i18n-locales22packages/i18n 100%
data-adapter10data-objectstack 5 (91.8%), core 4, types 1
infrastructure63auth 22, node_modules 33, permissions 8 (was 65, incl. 2 i18n)

⚠️ Disclosed rather than smoothed over: data-adapter now holds 5 core/types modules (8.5 KB raw) reached only through data-objectstack — the same shared-module pull-in, one tier down and three orders of magnitude smaller. framework cannot be lifted above these two without re-absorbing the catalogue, which is the whole defect.

data-adapter gets no ceiling

17,846 B is smaller than a dozen unbudgeted eager chunks. PER_CHUNK_GZIP_CEILINGS is 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 of apps/console/vite.config.ts and 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 to framework, 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 — framework at 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: 84 back to 80 on both lifted groups, restoring the tie. Mutation proved on disk before the run — priority: 84 occurrences 2 to 0, both mutated group lines present at 80, blob 3c7e2f79… to 4600c365…. Result:

× routes the locale catalogue to .../packages/i18n/src/locales/zh-CN.ts at a priority `framework` cannot tie
× routes the ObjectStack data adapter to .../packages/data-objectstack/src/index.ts at a priority `framework` cannot tie
AssertionError: expected 80 to be greater than 80
Tests 2 failed | 6 passed | 89 skipped (97)

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 equals origin/main's, priority: 84 count 0, i18n-locales mentions 0, main's tied infrastructure line present). The rebuild reproduces the control to the byte — aggregate 3,255,233 and framework 523,959, both identical to the untouched-main control — and the gate returns exit 2:

❌ Budgeted chunk `i18n-locales` is ABSENT from the eager closure reported at apps/console/dist/eager-closure.json. That is a FAILURE, not a pass: a ceiling whose chunk does not exist weighs nothing and would be green forever.

Both legs used trap restore EXIT INT TERM with absolute paths and git checkout HEAD -- on an absolute path; each restore is proved by state — blob hash equal to the HEAD blob and git diff HEAD empty — 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-locales carries 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 at b437996cd, verdict lines quoted above.
  • pnpm exec vitest run --maxWorkers=2 scripts/__tests__ — 100 files, 2,876 tests, at b437996cd. One pre-existing failure, check-sdui-registration-pins.test.ts, already filed as finding(test-infra): check-sdui-registration-pins.test.ts fails on any tree where packages/app-shell/dist exists #6893: it resolves through packages/app-shell/dist when 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 no skip-changeset label: 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.
  • The gates that read 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 lint is eslint . --no-inline-config over 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:

  1. Population comes from eslint's own configuration, not from a guess — all three paths were resolved and linted (none ignored).
  2. Count read from --format json: 3 file entries returned, exactly the three files in the diff.
  3. Invariance for untouched files: eslint.config.js declares no projectService and no parserOptions.project, so type-aware linting is off; and no rule in eslint-rules/ reads any file other than the one under lint (grep -ln 'readFileSync|readdirSync|globSync' eslint-rules/*.js returns 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

… 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
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3179.7 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-BID5JT1s.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.24KB117.82KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.08KB61.71KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.27KB13.32KB
plugin-charts (index.js)70.31KB19.55KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.79KB34.62KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.19KB64.18KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.18KB40.94KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.53KB27.66KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.21KB8.66KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-project-managerClaude

Copy link
Copy Markdown
Collaborator

ACCEPT — verified on the branch by content. Clause ② NO, so this seat lands it.

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. Head b437996cd.

⚠️Clause ② declared by this seat, not inherited. The report used "Clause ②" for the lint-narrowing convention, which is a different thing. The contract question — does this move a published surface? — is answered by the diff: three files, all of them build config and gate script (apps/console/vite.config.ts, scripts/check-eager-closure-budget.mjs, its test). No spec key, no zod validator, no exported type. ⇒ NO, no contract review needed.

Verified by content

probeorigin/mainbranch
files changed3
MAX_EAGER_CLOSURE_GZIP_BYTES3_268_0003_268_000 — unchanged
framework ceiling / baseline524_000 / 514_86371_000 / 61_465
i18n-locales ceiling / baselineabsent455_000 / 446_076
vendor-objectstack, ui-components967_000 / 399_000unchanged
pin cases in the gate's test8 (6 it + 1 it.each over two module ids)

⚠️ Two of my own probes were wrong here and the report was right both times, so I am recording it: I grepped the ceilings for a quoted key and framework is spelled unquoted, so it read as absent; and I counted it( alone, which missed the it.each. Neither was a defect in the change.

⭐ Three things this got right that the brief did not ask for

1. It refused my own suggestion, with a reason. The dispatch pointed at the one-character probe from the measurement (raise infrastructure to 85). Copying it would have left a chunk named infrastructure that is ~94% translation catalogue — the same illegibility one step over — and left data-objectstack misattributed with data-adapter still emitting nothing. A new i18n-locales group was the right call. The expensive half of this defect was never the arithmetic; it was a gate whose message pointed away from its cause, and that is what got fixed.

2. It caught a naming trap I did not see.i18n was rejected as the chunk name because an unrelatedpackages/app-shell module already emits a chunk auto-named i18n (59,974 gz), and per-chunk ceilings are summed per name — the two would have shared one budget silently.

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: framework 60.0 KB measured / 511.7 KB ceiling (headroom 451.7 KB = 5.08x the 89.0 KB regression). That is the blind ceiling the ruling anticipated, demonstrated rather than argued. ⇒ A′ genuinely could not have shipped as two PRs.

The load-bearing claim holds

The 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 framework pair carried.

Ablation, two legs: mutating priority: 84 → 80 reddens the pin by name ("expected 80 to be greater than 80"); restoring main's five-at-80 table rebuilds the control to the byte (aggregate 3,255,233 and framework 523,959, both identical to the untouched-main control) and the gate returns exit 2 on the now-absent budgeted chunk. Both restores proved by state.

The PER_CHUNK_BASELINE provenance comment also states plainly that these two entries are not readings of an unmodified tree — the chunks do not exist without the diff — and why the usual argument does not cover them. That is the honesty this file's header demands of its own numbers.

Landing

Branch is behindmain; bringing the base in, then undrafting for the queue. One pre-existing failure is correctly attributed and not re-filed: check-sdui-registration-pins.test.ts resolves through the gitignored packages/app-shell/dist and reds on any built tree — already #6893, and none of its inputs are in this diff.

⇒ On landing, i18n-locales carries ~8,924 B of headroom (about sixty short translation keys), which unblocks #7467, PR #7391, #7194, #7442 and #7400. ⭐ And the report names what becomes binding next: after ~3–4 KB more copy the aggregate line is the constraint — which is the correct place for it, and an argument for #7486 (option C), ⛔ not for raising anything.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3179.7 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-BID5JT1s.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.24KB117.82KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.08KB61.71KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.27KB13.32KB
plugin-charts (index.js)70.31KB19.55KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.79KB34.62KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.19KB64.18KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.18KB40.94KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.53KB27.66KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.21KB8.66KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-project-manager
os-project-manager marked this pull request as ready for review September 3, 2026 10:02
@os-project-manager
os-project-manager added this pull request to the merge queueSep 3, 2026
Merged via the queue into main with commit 177afebSep 3, 2026
32 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-7399-reattribute-i18n-chunk branch September 3, 2026 10:17
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

2 participants

@os-project-manager@claude