Uh oh!
There was an error while loading. Please reload this page.
fix(ci): weigh the console eager closure, not one entry chunk - #5466
Merged
Conversation
The Bundle Analysis workflow gzipped `index-*.js` and called the result "the console performance budget". On 77f846a that chunk is 25,910 bytes gzipped against a 350 KB line, while the closure it statically pulls in — what the browser must fetch and parse before the app renders — is 3,881,609 bytes across 58 of 507 chunks. The gate passed on 0.67% of the payload it claimed to govern, which is why the 89 KiB regression of #5266 landed in `vendor-objectstack-*.js` unseen. - `emitEagerClosureReport` in apps/console/vite.config.ts walks rolldown's own `chunk.imports` from the entry chunks (static edges only) and writes dist/eager-closure.json. Two counter-probes refuse a verdict rather than publish a number: react-dom must be IN the closure, and not every chunk may be. - scripts/check-eager-closure-budget.mjs applies the ceiling. Exit 1 = over budget, exit 2 = no trustworthy measurement, so a broken gauge is never reported as a clean bundle. - The entry-chunk budget and its 350 KB line stay exactly as they were. The ceiling is today's measurement plus ~2% (78,391 bytes) — it passes on current main, and the headroom is deliberately narrower than the 89 KiB regression the gate exists to catch. Both constraints are asserted in scripts/__tests__/check-eager-closure-budget.test.ts rather than argued in a comment. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RV6yuVCxymHYE16PL9vQkE
…ng (#5324) CI and build tooling only. Empty frontmatter is the explicit "no release" declaration; no package src/ is touched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RV6yuVCxymHYE16PL9vQkE
Contributor
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
os-support-ai
marked this pull request as ready for review
August 21, 2026 01:03
Uh oh!
There was an error while loading. Please reload this page.
This was referenced Aug 21, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes#5324
#5325 is NOT fixed here and remains open — the concrete half stops at a public-surface
breach, measured and explained below. Dispatch folded the two cards into one lane slot and
asked for both closing lines; the standing rule against closing a card that is still a
decision wins, so #5325 keeps its own. (#5325's claim comment named branch
claude/issue-5325-fields-lazy-imports-defeated; the folded dispatch put both on this one.)Re-measured on current
main(77f846a8b), not on3fbbea1f3index-*.jsalone — what the budget weighedSo the gate passed on 0.67% of the payload it claims to govern. The card said ~110 KB
and 2.8%; the entry chunk is smaller than that today (the renderer's own fixture has used
28.1 KB since objectui#3152), so the defect is worse than filed, not overstated. Chunk
count (58) and the total (3,881,609 vs 3,957,301) both reproduce the card's method — main
has shed 75,692 bytes since
3fbbea1f3.Two independent walks agree on 3,881,609: rolldown's
chunk.importsinside the build, anda throwaway regex BFS over
index.htmlindist.What lands
emitEagerClosureReport(apps/console/vite.config.ts) walks the entry chunks overstatic edges only — the dynamic edge is the lazy boundary — gzips the bytes actually
written to disk, and writes
dist/eager-closure.json.scripts/check-eager-closure-budget.mjsapplies the ceiling. Exit1= over budget(a verdict about the bundle), exit
2= no trustworthy measurement (a verdict about thegauge). Collapsing those would let a broken gauge read as a size regression and a size
regression read as a broken gauge.
with the closure. An absent closure figure renders as an explicit warning, never as a
quiet one-row table — a table showing only the entry chunk is the old gauge.
softened.
The measurement and the verdict are split on purpose: a size ceiling enforced inside
vite buildwould fail every Vercel preview and every local build, which is how a budgetgets switched off rather than fixed.
The ceiling: 3,960,000 gzipped bytes, and why that number
Today's measurement plus 78,391 bytes (2.02%) of headroom. Two constraints pin it:
main— a gate that lands red is a gate someone disables.catch. Verified: baseline plus 89 KiB comes out 12.4 KB over, so a repeat of apps/console: the
vendor-objectstackadvancedChunks group folds the lazily-imported@objectstack/lintinto an eagerly-loaded chunk — 89 KiB gzipped on every page load #5266fails this gate.
Both constraints are
expect()s inscripts/__tests__/check-eager-closure-budget.test.ts,not prose — an edit that widens the ceiling past the regression size fails a test.
This is a truthful current-state ceiling, not a target. 3.79 MB gzipped before first
render is a bad payload and the honest long-term line is far below it. Per dispatch I did
not lower it unilaterally; the gap is reported for a separate decision.
Counter-probes, because this gate fails silently
A walk that finds too little, a stale report, an absent field read as zero — each produces a
small number, and a budget reads small as good news. So:
react-domis inside the closure and atleast one chunk is outside it;
it does not recognise, or that has collapsed to its entry chunk — that last one being
precisely the gauge this replaces.
#5325 — measured, and it stops at a public-surface breach
Warnings in one console build: 46, not "13+" (43 from
packages/fields, 3 fromapps/console/src/pages/*). The card blamedindex.tsxalone;packages/fields/src/FieldEditWidget.tsxis a second static importer for 26 of them.Removing the barrel's
export *edges (keeping only what in-repo code imports by name):INEFFECTIVE_DYNAMIC_IMPORTZero bytes leave the eager closure — it grew by 329. Predicted before running, and the
mechanism is #5266's own: the
advancedChunksgroup namedui-components, whose testmatches
packages/(components|fields), claims everypackages/fieldsmodule and folds itinto a chunk the entry imports statically, which overrides async-only reachability.
Control that proves it independently:
widgets/MarkdownContent.tsxhas a working lazyimport()and no static importer anywhere — and it still lands in the eagerui-components-*.js. Counter-probed: the same method reportslazyfor the maplibre andplugin-map chunks, so it is not an oracle stuck on one answer.
And the fix is a public-surface change, in-repo as well as published: 14 widget classes are
imported by name from
@object-ui/fieldsby production code inplugin-detail,plugin-form,app-shellandapps/console(16MISSING_EXPORTbuild errors on the firstattempt). Per dispatch, that is a stop-and-report, so
packages/fieldsis untouched here.The warnings are real, but silencing them buys 0 bytes until the chunk grouping is
decided too. Evidence is on #5325.
Reverse verification (predicted before running, both legs rebuilt)
vite.config.tsis read directly by Vite, so no packagedistsits between the mutationand the run; each leg is a full console build.
eager: 1/507, "a walk that finds too little produces a SMALL number"closure_status=failclosure_gzip_kbemptyWorking tree proven byte-identical to
HEADafter each restore.Verification on
7515fa295vitest run scripts/__tests__/ apps/console/— 117 files, 2288 tests, 0 failures (27 newfor the checker, 21 for the renderer).
check:control-bytes,lint:coverage,type-check:coverage,changeset:check,check-changeset-presence,type-check:scripts,console
type-check,lint:rootand consolelintall exit 0. Changeset has emptyfrontmatter: CI and build tooling only, no package
src/touched.Generated by Claude Code