fix(console): return packages/core to the framework chunk by grouping rule, and pin chunk membership - #9488
Draft
claude[bot] wants to merge 1 commit into
Draft
fix(console): return packages/core to the framework chunk by grouping rule, and pin chunk membership#9488claude[bot] wants to merge 1 commit into
claude[bot] wants to merge 1 commit into
Conversation
… rule, and pin chunk membership
`data-adapter` declares `includeDependenciesRecursively: false`, so the group
holds exactly what its regex names. The rule that re-attributed the subgraph is
rolldown's `includeDependenciesRecursively` (default `true`): a group captures
its matched modules AND everything they import, and the same option's priority
doc states those are then removed from lower-priority groups whose regex does
match them. `packages/data-objectstack` imports `@object-ui/core` and this group
outranks `framework` (84 over 80), so the recursive half handed `framework`'s
declared members to a group with no ceiling and no baseline.
The transfer tracks TREE-SHAKING, which is why it moved with no edit here:
while `@object-ui/core` was imported by name only, the slice reachable through
`data-objectstack` was a handful of modules; objectui#9185 added an
`import('@object-ui/core')` of the BARREL, every export became live, and the
same walk reached all 92.
Measured, console build either side of the one-line repair:
data-adapter 78,110 -> 18,537 gzipped, holding only packages/data-objectstack
framework 45,278 -> 104,636 gzipped, holding core|react|types and nothing else
eager closure -461 bytes across the whole bundle
The bytes were always downloaded — `data-adapter` is in the eager closure — so
this is a re-attribution, not a payload change.
Second half: `scripts/check-eager-closure-budget.mjs` gains a MEMBERSHIP half.
`apps/console/vite.config.ts` emits `dist/chunk-membership.json` (which chunk
each workspace package's modules landed in, counted over every emitted chunk,
lazy ones included) and `evaluatePerChunkMembership` asserts each budgeted
group's declared packages landed wholly in their declared chunk. EXACT, not a
ratchet: one stray module is a finding, by name. Every pass-by-measuring-nothing
route is an error rather than a pass — absent artifact, unknown version, empty
attribution, a declared package absent from the bundle, a declaration naming a
chunk no ceiling governs.
No ceiling, baseline or exemption constant moves.
Refs: objectui#9345
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013VGeMu3p6qEFWR6K6GGLaW
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. Which half objected:
📦 Bundle Size Report
Size Limits
|
Collaborator
HELD — correct, reviewed, and ⛔ not landable until another lane's one-liner lands
The option-B measurement is in (card objectui#9345, comment
⇒ this PR stays open, stays draft, and is ⛔ NOT enqueued. Card objectui#9345 moves ⛔ No approval from this seat, ⛔ no merge, ⛔ no ceiling moved, ⛔ no test skipped. Generated by Claude Code |
This was referenced Sep 14, 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 free
to 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.
Part of objectui#9345 — route B, director seat decision batch #128 item 2
Two ruled deliverables land here.⚠️ One acceptance criterion is NOT met and no constant was moved to meet it — read the first section before the rest.
frameworkis over its ceilingThe ruling expected
frameworkto read about 82.6 K with membership restored, under its 100,000 ceiling, so that nothing needed re-pinning. Measured, it reads 104,636 gzipped bytes — 4,636 over.pnpm check:eager-closureexits 1 on this branch and prints:⛔ No ceiling, baseline or exemption constant was moved to absorb that, per the ruling. The row is left red and reported.
Why the two figures differ, measured rather than guessed. The card's ~82.6 K came from an ablation that ALSO reverted objectui#9185's dynamic import. That import is what keeps every export of
@object-ui/corelive, so reverting it shrinks the retained module set. This repair keeps the import — the ruling requires that — so the full core surface stays, and it now lands on the line that names it.These bytes are not new.
data-adapteris in the eager closure, so the browser was already downloading them. Across the pair, before and after this change on the same tree: 123,388 gzipped before, 123,173 after, and the whole eager closure moves by −461 bytes. ⇒ the overage is PRE-EXISTING and this change makes it visible for the first time; it does not create it.⇒ this is the "new reading for a new card" the ruling's point 3 anticipates. It needs a decision this seat does not carry, which is why this pull request is filed as
Part of— the repair and the pin land here, the ceiling question stays open on the card.1. The rolldown rule that re-attributed the subgraph, named
CodeSplittingGroup.includeDependenciesRecursively, whose default istrue.The reading that identifies it, from rolldown 1.2.7's own type documentation, two options one paragraph apart:
includeDependenciesRecursively— "Whether to include captured modules' dependencies." Defaulttrue.priority— "Group with higher priority will be chosen first to match modules and create chunks. When converting the group to a chunk, modules of that group will be removed from other groups."Together: a group takes the modules its
testmatches AND everything those modules import, and the lower-priority groups whose regex DOES match them lose them.packages/data-objectstackimports@object-ui/core;data-adapteris priority 84 andframeworkis 80. Soframework's own declared members were written into a group with no ceiling and no baseline.Why it moved with no edit to the chunking config. The size of the transfer tracks TREE-SHAKING. While
@object-ui/corewas imported by name only, the retained slice reachable throughdata-objectstackwas a handful of modules — the config's own comment disclosed it as five. objectui#9185 added animport('@object-ui/core')of the BARREL inpackages/app-shell, every export became live, and the same recursive walk reached all 92.Causal proof, not inference. Adding
includeDependenciesRecursively: falseto that one group — nothing else changed, same tree — returns all 92 modules toframework. The control is below.⛔ Not fixed by priority.
@object-ui/reactdepends on both@object-ui/i18nand@object-ui/data-objectstack, so a recursiveframeworklifted above tier 84 would swallow the locale catalogues and this group in one move — objectui#7399's defect, re-run. Narrowing the receiving group leaves every priority untouched.2. Membership after a real build, from the emitted chunks' own module lists
turbo run build --filter=@object-ui/console...over the dependency closure (35 tasks, exit 0), thenvite buildper reading. Attribution is read from the bundle, never from the config.ff1d5ea8d1)packages/coredata-adapter, 0 inframeworkframeworkpackages/typesframework, 3 indata-adapterframeworkpackages/reactframeworkframeworkpackages/data-objectstackdata-adapterdata-adapterdata-adapternow holds exactly its declared regex and nothing else. Every other workspace package's attribution is unchanged row for row.3. The byte table, re-measured
frameworkdata-adapterdata-adapterreturns to the neighbourhood of the 19.6 K the gate's exemption rests on, as the ruling predicted.frameworkdoes not land where the ruling predicted — see the first section.4. The membership pin, and its firing control
apps/console/vite.config.tsemitsdist/chunk-membership.json: which chunk each workspace package's modules landed in, counted over EVERY emitted chunk, lazy ones included.evaluatePerChunkMembershipinscripts/check-eager-closure-budget.mjsis a fifth half of that gate and asserts each budgeted group's declared packages landed wholly in their declared chunk. ⛔ EXACT, not a ratchet: one stray module is a finding, by name. A declaration is limited to chunks that carry a ceiling, and each package name is cross-checked in a unit test against that group's OWN regex, so the table cannot become a second opinion about the config.Firing control — the mutation is the defect itself. Removing the flag, rebuilding, and reading:
⭐ That is the incident, reproduced and caught: every byte ceiling green, with 53.4 KB of apparent headroom, while the whole of
packages/coresat in a chunk nothing budgets. The new half is the only thing in the tree that sees it.Restore, rebuild, read again: the membership half is
passand names its population — 5 packages, 462 modules, each wholly in its declared chunk. Mutation proven on disk by blob hash (the file's HEAD blob against the mutated one, both printed), restore proven by that blob hash matching HEAD again AND bygit diff HEADbeing empty; the script carries atrap ... EXIT INT TERMrestore on an absolute path. The static half fires too: the unit pin on the flag reds under the same mutation and is green after.Every zero in this pull request carries a control that returned non-zero in the same command — the membership pass case is paired with the stray, whole-package-moved, majority-in-place, absent-artifact, unknown-version, empty-attribution, absent-package and unbudgeted-declaration cases; the regex cross-check carries a must-miss control.
5. Verification
pnpm exec vitest run scripts/— 158 files, 4,654 tests, exit 0. The gate's own suite is 162 of them.pnpm exec tsc -p tsconfig.scripts.json --noEmitexit 0;pnpm exec tsc -b apps/console/tsconfig.node.json --forceexit 0.check:control-bytes,check:new-line-citations,check:eager-locale-catalogues,check:docs-route-closure,check:comment-mask-corpus,check:shell-escape-residue,check:governed-queue-guard,check:required-check-set,check:changeset-claims— all exit 0.check:eager-closureexits 1 on theframeworkceiling row above. Its membership half ispass.eslint --no-inline-configover the 5 linted files in this diff, 0 errors and 0 warnings, file count read from--format json. The narrowing is a measurement, not a skip —eslint.config.jsconfigures noprojectorprojectService, so type-aware linting is off, and no rule undereslint-rules/reads the filesystem at all (a grep forreadFileSync/readdirSync/existsSyncover that directory exits 1). ⇒ this diff cannot move the verdict on a file it does not touch. The.ymlfile is outside eslint's population entirely.gates_weakened:NONE. Nothing was relaxed, deleted or given headroom. The only pre-existing assertions edited are theadvancedChunksgroup parse in the gate's test file — widened to read an options tail it previously could not, which had made it stop matching the group ENTIRELY rather than degrade — and themainharnesses, which now co-write the second artifact the way a real build does. Both are additions of subject, not removals of it.6. Changeset
None owed, measured rather than argued:
node scripts/check-changeset-presence.mjsexits 0 and prints its own reading — 6 files changed, 0 of them published source of a package the release covers, 0 of them a manifest whose published contract moved.Scope
The diff is the build config, the gate, the gate's tests, and the two files the gate's own tests REQUIRE a new half to be wired through (the workflow env line and the PR-comment renderer —
render-budget-comment.test.tsfails until a published output reaches the comment step). ⛔ Nothing underapps/console/src/is touched, so the lane fence this card carries is intact. objectui#9185's dynamic import is untouched.Generated by Claude Code, session
session_013VGeMu3p6qEFWR6K6GGLaW— written here as prose and as a code span so the reference survives a body rewrite.Generated by Claude Code
Generated by Claude Code