docs(scripts): stop restating the eager-closure headroom in prose - #7529

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-7518-eager-closure-headroom-prose
Sep 3, 2026
Merged

docs(scripts): stop restating the eager-closure headroom in prose#7529
os-project-manager merged 1 commit into
mainfrom
claude/issue-7518-eager-closure-headroom-prose

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes#7518

Comment-only. Every constant keeps its value; the gate's verdict is byte-identical
before and after.

The disposition, and why it is not "write today's number"

The card offered three routes — (a) restate the figure, (b) restate a ratio,
(c) point at the live verdict. The dispatching seat ruled (c), and the argument
is one this file has already proved on itself: a hardcoded headroom here went stale
badly enough to produce three mutually contradictory versions, two of them in the
same file. (a) only winds the clock forward to today; (b) drifts with the baseline.

So the prose now says which line is binding and how to read the current value
(pnpm check:eager-closure) and does not say what it equals.

Same shape as #7517 (f620a26f6), which fixed the ten locale packs' stale headroom
note by naming the ceiling's distance from the baseline it was measured from and
pointing at the same command.

Whole-file census

The card required a survey of the entire file, not only the line it names. Line
numbers are on the pre-change file at f620a26f6.

Aggregate surface — rewritten

sitewhat was writtenwhat it actually wasnow
:50-51"Headroom above the current 3,254,004 bytes: 45,996 (1.41%)"3,300,000 − 3,254,004 — the objectui#6683 ceiling/baseline pair, one full re-baseline stale, and "the current" is a baseline that was replaced by objectui#6776no figure; points at the constant that states it once, and at the live verdict
:54"45,996 < 91,136, so this ceiling would have failed"the same retired figure reused as the proof of the second constraintargues from the policy (headroom is half the regression by construction) instead of a literal
:455"the AGGREGATE line (11,988 bytes of headroom) becomes the binding one"3,268,000 − 3,256,012 — objectui#7399's own post-change live reading frozen into a sentenceno figure; the claim that the aggregate becomes binding stands, the number is read off the gate
:1245-1246"With the aggregate headroom now 44.0 KB against an 89.0 KB regression class"3,345,000 − 3,299,898 = 45,102 — the objectui#5924 pair, two re-baselines back, present tenseno figure
:548-550"they are the TIGHTER half — 0.21x, 0.08x, 0.04x against the aggregate's 0.47x"dated e33b44796 ratios promoted into a present-tense structural claim — and the claim has inverted (below)states that which line is tightest moves with every build and is ranked by the gate's own sensitivity table

Aggregate surface — deliberately kept, with the reason

sitefigurewhy it is not in the class above
:255"Headroom is 45,686 bytes — 0.50x"3,268,000 − 3,222,314: arithmetic on two constants in this file, correct at write time and correct now, and it cannot drift independently of them (the unit test asserts MAX − BASELINE.gzipBytes < REGRESSION). What was missing is that this is headroom above the baseline, not what the closure has left — the sentence now says so and points at the live figure.
:530-531"4,086,000 over the 3,298,620 reading, 787,380 bytes, 8.64x"explicitly a retired claim that the same paragraph goes on to call false. A dated record of a defect, not an assertion about today.
:64-65"~45 KB of room in each direction rather than 66 KB one way and 25 KB the other"arithmetic on REGRESSION_THIS_GATE_MUST_CATCH_BYTES and on the retired ~2% policy it replaced. A statement about the policy; no measurement in it.
:117, :136, :156, :242, :251"~200 KB", "45,102", "75.9 KB = 0.85x"each dated to the card that measured it, inside a narrative that names that card.
:544-547the four-line e33b44796 run tableone dated run, already carrying its own "do not size a re-baseline off these; the answer in force is the table the gate prints on YOUR build". One clause added: the run predates objectui#7399's re-attribution, so its framework row weighs locale catalogues that line no longer holds — without it a reader takes a retired subject for the current one, which is the false rule objectui#7399 was itself written about.

The inverted ranking, measured

:548-550 asserted the three per-chunk lines were tighter than the aggregate. On this
branch's base the gate prints the opposite for one of them, and there are four
per-chunk ceilings now, not three:

aggregate closure headroom 9.5 KB = 0.11x
chunk `vendor-objectstack` headroom 18.2 KB = 0.20x

That is why the replacement asserts no ranking at all.

Per-chunk surface — reported separately, numbers never joined to the aggregate's

The dispatching seat's fence: the three budget surfaces are three things and their
numbers do not go in one sentence. Nothing in this PR joins them.

sitefiguredisposition
:442-446"9,535 bytes (0.10x) for framework, 8,924 (0.10x) for i18n-locales, against the 9,137 (0.10x) the retired framework pair carried"correct, untouched.71,000 − 61,465 = 9,535 and 455,000 − 446,076 = 8,924, both from constants in this file; 9,137 is dated to the retired pair.
:452"8,924 bytes is about sixty translation keys"correct, number kept. It was silent about being headroom above the baseline; it now says so. Its trailing clause was the 11,988 defect and is rewritten.
:346-348, :386, :411, :431per-chunk narrativesdated to their cards. Kept.

Live per-chunk drift is not written into the file — that is the whole ruling —
but for the record, measured on f620a26f6: i18n-locales has spent 1,882 of its
8,924 baseline headroom and ui-components is down to 2,716 bytes (0.03x). The gate
prints all of it.

Verification

Full apps/console build (this file is in performance-budget.yml's runtime
closure, so the gate cannot be judged without one). Both builds and the test run went
through the shared verify lock; wall-clock figures are shared-box seconds.

pnpm turbo run build --filter='./packages/*' --concurrency=2 39 tasks ok
pnpm --filter @object-ui/console build ok
node scripts/check-eager-closure-budget.mjs exit 0

The verdict this PR's new prose points at, printed on f620a26f6:

✅ Console eager closure is 3181.9 KB gzipped across 50 of 518 chunks (budget: 3191.4 KB, headroom: 9.5 KB).
✅ Per-chunk eager budgets (4 chunks weighed):
✅ vendor-objectstack 926.1 KB / 944.3 KB ceiling (headroom 18.2 KB)
✅ i18n-locales 437.5 KB / 444.3 KB ceiling (headroom 6.9 KB)
✅ ui-components 387.0 KB / 389.6 KB ceiling (headroom 2.7 KB)
✅ framework 61.0 KB / 69.3 KB ceiling (headroom 8.3 KB)
✅ Ceiling sensitivity (5 ceilings, each weighed against the report just read):
✅ aggregate closure 3181.9 KB measured / 3191.4 KB ceiling (headroom 9.5 KB = 0.11x 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` 437.5 KB measured / 444.3 KB ceiling (headroom 6.9 KB = 0.08x the 89.0 KB regression) [PER_CHUNK_GZIP_CEILINGS['i18n-locales']]
✅ chunk `framework` 61.0 KB measured / 69.3 KB ceiling (headroom 8.3 KB = 0.09x the 89.0 KB regression) [PER_CHUNK_GZIP_CEILINGS['framework']]
✅ chunk `ui-components` 387.0 KB measured / 389.6 KB ceiling (headroom 2.7 KB = 0.03x the 89.0 KB regression) [PER_CHUNK_GZIP_CEILINGS['ui-components']]

Exactly: eagerGzipBytes = 3,258,288, so the headroom in force is 9,712 bytes.
None of 45,996 / 45,686 / 11,988 is that number — and the figure this seat was handed
in dispatch, taken from CI on a neighbouring commit, was 9,734. Twenty-two bytes
apart, in the same afternoon, is the argument for (c) in one line.

The gate's behaviour did not move

The report is held constant (one build) and the checker is run on the pristine tree
and again on the edited one:

cmp verdict-before.txt verdict-after.txt -> exit 0, identical
md5 both -> 754fcf61b9a33e9e0cf1b92ce644abc3

Negative control, so "identical" is a reading and not a tautology: appending one byte
to a copy makes the same cmp exit 1 at byte 2186. Re-run again at the final commit
2e001f5f2, still byte-identical to the pristine reading.

scripts/check-eager-closure-budget.mjs is not a build input (turbo's console
inputs cover scripts/vite-*.ts, not scripts/check-*.mjs — the argument
BASELINE already makes about its own commit), which is why one build is enough to
hold the report fixed across the edit.

Gates, on 2e001f5f2

pnpm exec vitest run scripts/__tests__/check-eager-closure-budget.test.ts \
scripts/__tests__/render-budget-comment.test.ts \
scripts/__tests__/check-side-effects-array.test.ts \
scripts/__tests__/vite-declared-lazy-views.test.ts \
scripts/__tests__/vite-ineffective-dynamic-imports.test.ts
Test Files 5 passed (5) Tests 207 passed (207)

Those five are every test file in the repo that reads this script's source. The set
matters here because objectui#7046 pins prose against live values —
attachedDocs locates the block attached to each baseline and asserts what it says
about commits and chunk names — so a comment-only edit to these blocks is exactly the
kind that can red them. It does not.

node scripts/check-control-bytes.mjs ✅ 6199 tracked text files
node scripts/check-changeset-presence.mjs ✅ no changeset owed

The changeset checker's own verdict line: "1 file(s) changed, 0 of them published
source of a package the release covers ... no changeset is owed."
No
skip-changeset label is applied — this repo declares with an empty-frontmatter
changeset when one is owed, and here none is.

Not run locally: the rest of the gate farm, which CI runs exactly once anyway.

Fences honoured

No ceiling is raised, lowered, or otherwise moved. MAX_EAGER_CLOSURE_GZIP_BYTES,
BASELINE, PER_CHUNK_GZIP_CEILINGS, PER_CHUNK_BASELINE and
REGRESSION_THIS_GATE_MUST_CATCH_BYTES are byte-identical to origin/main; the diff
is 38 insertions and 22 deletions, all of them inside comments. If 9.7 KB of
aggregate headroom is too tight, that is a different card and a maintainer's call.

Filed, not fixed here

#7528 — two more frozen measurements in this gate's prose that the ruling above does
not cover: "one number over 52 chunks" asserted in the present tense in this file and
in its test (the build measures 50; BASELINE.chunks says 48), and
performance-budget.yml citing "3.15 MB", the ceiling objectui#6776 retired. Out of
scope: neither is an aggregate-headroom sentence, and one lives in another file.
#7528 is not addressed by this PR.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC


Generated by Claude Code

`scripts/check-eager-closure-budget.mjs` carried three different aggregate
headroom figures and none of them was the one in force. 45,996 (header) was
objectui#6683's ceiling minus objectui#6683's baseline, a whole re-baseline
stale. 11,988 (`PER_CHUNK_GZIP_CEILINGS`) was objectui#7399's post-change live
reading frozen into a sentence. 45,686 (`MAX_EAGER_CLOSURE_GZIP_BYTES`) is
arithmetic on the two constants beside it and is correct — but it is the
headroom above the BASELINE, not what the closure has left, and the prose did
not say so. Measured on this branch's base, the gate prints 9.5 KB.
Two more of the same shape: "the aggregate headroom now 44.0 KB"
(`evaluateCeilingFreshness`) was objectui#5924-era, and the "TIGHTER half —
0.21x, 0.08x, 0.04x against the aggregate's 0.47x" ranking in
`PER_CHUNK_BASELINE` had INVERTED (`vendor-objectstack` is 0.20x against the
aggregate's 0.11x today).
Comment-only. Every constant — `MAX_EAGER_CLOSURE_GZIP_BYTES`, `BASELINE`,
`PER_CHUNK_GZIP_CEILINGS`, `PER_CHUNK_BASELINE`,
`REGRESSION_THIS_GATE_MUST_CATCH_BYTES` — keeps its value, and the gate's
verdict is byte-identical before and after.
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)3181.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-CkpJ-S5M.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.19KB117.80KB
core (index.js)6.12KB2.42KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)242.42KB61.26KB
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.15KB13.35KB
plugin-charts (index.js)70.87KB19.72KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.82KB34.64KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.07KB64.12KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.46KB41.06KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)52.71KB14.55KB
plugin-list (index.js)113.33KB27.60KB
plugin-map (index.js)20.55KB6.80KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)9.40KB3.23KB
plugin-view (index.js)85.22KB20.93KB
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)4.63KB2.18KB
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 17:33
@os-project-manager
os-project-manager added this pull request to the merge queueSep 3, 2026
Merged via the queue into main with commit d04e79aSep 3, 2026
32 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-7518-eager-closure-headroom-prose branch September 3, 2026 17:57
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

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

docs(scripts): stop restating the eager-closure headroom in prose - #7529

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-7518-eager-closure-headroom-prose
Sep 3, 2026
Merged

docs(scripts): stop restating the eager-closure headroom in prose#7529
os-project-manager merged 1 commit into
mainfrom
claude/issue-7518-eager-closure-headroom-prose

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes#7518

Comment-only. Every constant keeps its value; the gate's verdict is byte-identical
before and after.

The disposition, and why it is not "write today's number"

The card offered three routes — (a) restate the figure, (b) restate a ratio,
(c) point at the live verdict. The dispatching seat ruled (c), and the argument
is one this file has already proved on itself: a hardcoded headroom here went stale
badly enough to produce three mutually contradictory versions, two of them in the
same file. (a) only winds the clock forward to today; (b) drifts with the baseline.

So the prose now says which line is binding and how to read the current value
(pnpm check:eager-closure) and does not say what it equals.

Same shape as #7517 (f620a26f6), which fixed the ten locale packs' stale headroom
note by naming the ceiling's distance from the baseline it was measured from and
pointing at the same command.

Whole-file census

The card required a survey of the entire file, not only the line it names. Line
numbers are on the pre-change file at f620a26f6.

Aggregate surface — rewritten

sitewhat was writtenwhat it actually wasnow
:50-51"Headroom above the current 3,254,004 bytes: 45,996 (1.41%)"3,300,000 − 3,254,004 — the objectui#6683 ceiling/baseline pair, one full re-baseline stale, and "the current" is a baseline that was replaced by objectui#6776no figure; points at the constant that states it once, and at the live verdict
:54"45,996 < 91,136, so this ceiling would have failed"the same retired figure reused as the proof of the second constraintargues from the policy (headroom is half the regression by construction) instead of a literal
:455"the AGGREGATE line (11,988 bytes of headroom) becomes the binding one"3,268,000 − 3,256,012 — objectui#7399's own post-change live reading frozen into a sentenceno figure; the claim that the aggregate becomes binding stands, the number is read off the gate
:1245-1246"With the aggregate headroom now 44.0 KB against an 89.0 KB regression class"3,345,000 − 3,299,898 = 45,102 — the objectui#5924 pair, two re-baselines back, present tenseno figure
:548-550"they are the TIGHTER half — 0.21x, 0.08x, 0.04x against the aggregate's 0.47x"dated e33b44796 ratios promoted into a present-tense structural claim — and the claim has inverted (below)states that which line is tightest moves with every build and is ranked by the gate's own sensitivity table

Aggregate surface — deliberately kept, with the reason

sitefigurewhy it is not in the class above
:255"Headroom is 45,686 bytes — 0.50x"3,268,000 − 3,222,314: arithmetic on two constants in this file, correct at write time and correct now, and it cannot drift independently of them (the unit test asserts MAX − BASELINE.gzipBytes < REGRESSION). What was missing is that this is headroom above the baseline, not what the closure has left — the sentence now says so and points at the live figure.
:530-531"4,086,000 over the 3,298,620 reading, 787,380 bytes, 8.64x"explicitly a retired claim that the same paragraph goes on to call false. A dated record of a defect, not an assertion about today.
:64-65"~45 KB of room in each direction rather than 66 KB one way and 25 KB the other"arithmetic on REGRESSION_THIS_GATE_MUST_CATCH_BYTES and on the retired ~2% policy it replaced. A statement about the policy; no measurement in it.
:117, :136, :156, :242, :251"~200 KB", "45,102", "75.9 KB = 0.85x"each dated to the card that measured it, inside a narrative that names that card.
:544-547the four-line e33b44796 run tableone dated run, already carrying its own "do not size a re-baseline off these; the answer in force is the table the gate prints on YOUR build". One clause added: the run predates objectui#7399's re-attribution, so its framework row weighs locale catalogues that line no longer holds — without it a reader takes a retired subject for the current one, which is the false rule objectui#7399 was itself written about.

The inverted ranking, measured

:548-550 asserted the three per-chunk lines were tighter than the aggregate. On this
branch's base the gate prints the opposite for one of them, and there are four
per-chunk ceilings now, not three:

aggregate closure headroom 9.5 KB = 0.11x
chunk `vendor-objectstack` headroom 18.2 KB = 0.20x

That is why the replacement asserts no ranking at all.

Per-chunk surface — reported separately, numbers never joined to the aggregate's

The dispatching seat's fence: the three budget surfaces are three things and their
numbers do not go in one sentence. Nothing in this PR joins them.

sitefiguredisposition
:442-446"9,535 bytes (0.10x) for framework, 8,924 (0.10x) for i18n-locales, against the 9,137 (0.10x) the retired framework pair carried"correct, untouched.71,000 − 61,465 = 9,535 and 455,000 − 446,076 = 8,924, both from constants in this file; 9,137 is dated to the retired pair.
:452"8,924 bytes is about sixty translation keys"correct, number kept. It was silent about being headroom above the baseline; it now says so. Its trailing clause was the 11,988 defect and is rewritten.
:346-348, :386, :411, :431per-chunk narrativesdated to their cards. Kept.

Live per-chunk drift is not written into the file — that is the whole ruling —
but for the record, measured on f620a26f6: i18n-locales has spent 1,882 of its
8,924 baseline headroom and ui-components is down to 2,716 bytes (0.03x). The gate
prints all of it.

Verification

Full apps/console build (this file is in performance-budget.yml's runtime
closure, so the gate cannot be judged without one). Both builds and the test run went
through the shared verify lock; wall-clock figures are shared-box seconds.

pnpm turbo run build --filter='./packages/*' --concurrency=2 39 tasks ok
pnpm --filter @object-ui/console build ok
node scripts/check-eager-closure-budget.mjs exit 0

The verdict this PR's new prose points at, printed on f620a26f6:

✅ Console eager closure is 3181.9 KB gzipped across 50 of 518 chunks (budget: 3191.4 KB, headroom: 9.5 KB).
✅ Per-chunk eager budgets (4 chunks weighed):
✅ vendor-objectstack 926.1 KB / 944.3 KB ceiling (headroom 18.2 KB)
✅ i18n-locales 437.5 KB / 444.3 KB ceiling (headroom 6.9 KB)
✅ ui-components 387.0 KB / 389.6 KB ceiling (headroom 2.7 KB)
✅ framework 61.0 KB / 69.3 KB ceiling (headroom 8.3 KB)
✅ Ceiling sensitivity (5 ceilings, each weighed against the report just read):
✅ aggregate closure 3181.9 KB measured / 3191.4 KB ceiling (headroom 9.5 KB = 0.11x 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` 437.5 KB measured / 444.3 KB ceiling (headroom 6.9 KB = 0.08x the 89.0 KB regression) [PER_CHUNK_GZIP_CEILINGS['i18n-locales']]
✅ chunk `framework` 61.0 KB measured / 69.3 KB ceiling (headroom 8.3 KB = 0.09x the 89.0 KB regression) [PER_CHUNK_GZIP_CEILINGS['framework']]
✅ chunk `ui-components` 387.0 KB measured / 389.6 KB ceiling (headroom 2.7 KB = 0.03x the 89.0 KB regression) [PER_CHUNK_GZIP_CEILINGS['ui-components']]

Exactly: eagerGzipBytes = 3,258,288, so the headroom in force is 9,712 bytes.
None of 45,996 / 45,686 / 11,988 is that number — and the figure this seat was handed
in dispatch, taken from CI on a neighbouring commit, was 9,734. Twenty-two bytes
apart, in the same afternoon, is the argument for (c) in one line.

The gate's behaviour did not move

The report is held constant (one build) and the checker is run on the pristine tree
and again on the edited one:

cmp verdict-before.txt verdict-after.txt -> exit 0, identical
md5 both -> 754fcf61b9a33e9e0cf1b92ce644abc3

Negative control, so "identical" is a reading and not a tautology: appending one byte
to a copy makes the same cmp exit 1 at byte 2186. Re-run again at the final commit
2e001f5f2, still byte-identical to the pristine reading.

scripts/check-eager-closure-budget.mjs is not a build input (turbo's console
inputs cover scripts/vite-*.ts, not scripts/check-*.mjs — the argument
BASELINE already makes about its own commit), which is why one build is enough to
hold the report fixed across the edit.

Gates, on 2e001f5f2

pnpm exec vitest run scripts/__tests__/check-eager-closure-budget.test.ts \
scripts/__tests__/render-budget-comment.test.ts \
scripts/__tests__/check-side-effects-array.test.ts \
scripts/__tests__/vite-declared-lazy-views.test.ts \
scripts/__tests__/vite-ineffective-dynamic-imports.test.ts
Test Files 5 passed (5) Tests 207 passed (207)

Those five are every test file in the repo that reads this script's source. The set
matters here because objectui#7046 pins prose against live values —
attachedDocs locates the block attached to each baseline and asserts what it says
about commits and chunk names — so a comment-only edit to these blocks is exactly the
kind that can red them. It does not.

node scripts/check-control-bytes.mjs ✅ 6199 tracked text files
node scripts/check-changeset-presence.mjs ✅ no changeset owed

The changeset checker's own verdict line: "1 file(s) changed, 0 of them published
source of a package the release covers ... no changeset is owed."
No
skip-changeset label is applied — this repo declares with an empty-frontmatter
changeset when one is owed, and here none is.

Not run locally: the rest of the gate farm, which CI runs exactly once anyway.

Fences honoured

No ceiling is raised, lowered, or otherwise moved. MAX_EAGER_CLOSURE_GZIP_BYTES,
BASELINE, PER_CHUNK_GZIP_CEILINGS, PER_CHUNK_BASELINE and
REGRESSION_THIS_GATE_MUST_CATCH_BYTES are byte-identical to origin/main; the diff
is 38 insertions and 22 deletions, all of them inside comments. If 9.7 KB of
aggregate headroom is too tight, that is a different card and a maintainer's call.

Filed, not fixed here

#7528 — two more frozen measurements in this gate's prose that the ruling above does
not cover: "one number over 52 chunks" asserted in the present tense in this file and
in its test (the build measures 50; BASELINE.chunks says 48), and
performance-budget.yml citing "3.15 MB", the ceiling objectui#6776 retired. Out of
scope: neither is an aggregate-headroom sentence, and one lives in another file.
#7528 is not addressed by this PR.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC


Generated by Claude Code

`scripts/check-eager-closure-budget.mjs` carried three different aggregate
headroom figures and none of them was the one in force. 45,996 (header) was
objectui#6683's ceiling minus objectui#6683's baseline, a whole re-baseline
stale. 11,988 (`PER_CHUNK_GZIP_CEILINGS`) was objectui#7399's post-change live
reading frozen into a sentence. 45,686 (`MAX_EAGER_CLOSURE_GZIP_BYTES`) is
arithmetic on the two constants beside it and is correct — but it is the
headroom above the BASELINE, not what the closure has left, and the prose did
not say so. Measured on this branch's base, the gate prints 9.5 KB.
Two more of the same shape: "the aggregate headroom now 44.0 KB"
(`evaluateCeilingFreshness`) was objectui#5924-era, and the "TIGHTER half —
0.21x, 0.08x, 0.04x against the aggregate's 0.47x" ranking in
`PER_CHUNK_BASELINE` had INVERTED (`vendor-objectstack` is 0.20x against the
aggregate's 0.11x today).
Comment-only. Every constant — `MAX_EAGER_CLOSURE_GZIP_BYTES`, `BASELINE`,
`PER_CHUNK_GZIP_CEILINGS`, `PER_CHUNK_BASELINE`,
`REGRESSION_THIS_GATE_MUST_CATCH_BYTES` — keeps its value, and the gate's
verdict is byte-identical before and after.
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)3181.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-CkpJ-S5M.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.19KB117.80KB
core (index.js)6.12KB2.42KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)242.42KB61.26KB
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.15KB13.35KB
plugin-charts (index.js)70.87KB19.72KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.82KB34.64KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.07KB64.12KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.46KB41.06KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)52.71KB14.55KB
plugin-list (index.js)113.33KB27.60KB
plugin-map (index.js)20.55KB6.80KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)9.40KB3.23KB
plugin-view (index.js)85.22KB20.93KB
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)4.63KB2.18KB
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 17:33
@os-project-manager
os-project-manager added this pull request to the merge queueSep 3, 2026
Merged via the queue into main with commit d04e79aSep 3, 2026
32 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-7518-eager-closure-headroom-prose branch September 3, 2026 17:57
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

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

docs(scripts): stop restating the eager-closure headroom in prose - #7529

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-7518-eager-closure-headroom-prose
Sep 3, 2026
Merged

docs(scripts): stop restating the eager-closure headroom in prose#7529
os-project-manager merged 1 commit into
mainfrom
claude/issue-7518-eager-closure-headroom-prose

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes#7518

Comment-only. Every constant keeps its value; the gate's verdict is byte-identical
before and after.

The disposition, and why it is not "write today's number"

The card offered three routes — (a) restate the figure, (b) restate a ratio,
(c) point at the live verdict. The dispatching seat ruled (c), and the argument
is one this file has already proved on itself: a hardcoded headroom here went stale
badly enough to produce three mutually contradictory versions, two of them in the
same file. (a) only winds the clock forward to today; (b) drifts with the baseline.

So the prose now says which line is binding and how to read the current value
(pnpm check:eager-closure) and does not say what it equals.

Same shape as #7517 (f620a26f6), which fixed the ten locale packs' stale headroom
note by naming the ceiling's distance from the baseline it was measured from and
pointing at the same command.

Whole-file census

The card required a survey of the entire file, not only the line it names. Line
numbers are on the pre-change file at f620a26f6.

Aggregate surface — rewritten

sitewhat was writtenwhat it actually wasnow
:50-51"Headroom above the current 3,254,004 bytes: 45,996 (1.41%)"3,300,000 − 3,254,004 — the objectui#6683 ceiling/baseline pair, one full re-baseline stale, and "the current" is a baseline that was replaced by objectui#6776no figure; points at the constant that states it once, and at the live verdict
:54"45,996 < 91,136, so this ceiling would have failed"the same retired figure reused as the proof of the second constraintargues from the policy (headroom is half the regression by construction) instead of a literal
:455"the AGGREGATE line (11,988 bytes of headroom) becomes the binding one"3,268,000 − 3,256,012 — objectui#7399's own post-change live reading frozen into a sentenceno figure; the claim that the aggregate becomes binding stands, the number is read off the gate
:1245-1246"With the aggregate headroom now 44.0 KB against an 89.0 KB regression class"3,345,000 − 3,299,898 = 45,102 — the objectui#5924 pair, two re-baselines back, present tenseno figure
:548-550"they are the TIGHTER half — 0.21x, 0.08x, 0.04x against the aggregate's 0.47x"dated e33b44796 ratios promoted into a present-tense structural claim — and the claim has inverted (below)states that which line is tightest moves with every build and is ranked by the gate's own sensitivity table

Aggregate surface — deliberately kept, with the reason

sitefigurewhy it is not in the class above
:255"Headroom is 45,686 bytes — 0.50x"3,268,000 − 3,222,314: arithmetic on two constants in this file, correct at write time and correct now, and it cannot drift independently of them (the unit test asserts MAX − BASELINE.gzipBytes < REGRESSION). What was missing is that this is headroom above the baseline, not what the closure has left — the sentence now says so and points at the live figure.
:530-531"4,086,000 over the 3,298,620 reading, 787,380 bytes, 8.64x"explicitly a retired claim that the same paragraph goes on to call false. A dated record of a defect, not an assertion about today.
:64-65"~45 KB of room in each direction rather than 66 KB one way and 25 KB the other"arithmetic on REGRESSION_THIS_GATE_MUST_CATCH_BYTES and on the retired ~2% policy it replaced. A statement about the policy; no measurement in it.
:117, :136, :156, :242, :251"~200 KB", "45,102", "75.9 KB = 0.85x"each dated to the card that measured it, inside a narrative that names that card.
:544-547the four-line e33b44796 run tableone dated run, already carrying its own "do not size a re-baseline off these; the answer in force is the table the gate prints on YOUR build". One clause added: the run predates objectui#7399's re-attribution, so its framework row weighs locale catalogues that line no longer holds — without it a reader takes a retired subject for the current one, which is the false rule objectui#7399 was itself written about.

The inverted ranking, measured

:548-550 asserted the three per-chunk lines were tighter than the aggregate. On this
branch's base the gate prints the opposite for one of them, and there are four
per-chunk ceilings now, not three:

aggregate closure headroom 9.5 KB = 0.11x
chunk `vendor-objectstack` headroom 18.2 KB = 0.20x

That is why the replacement asserts no ranking at all.

Per-chunk surface — reported separately, numbers never joined to the aggregate's

The dispatching seat's fence: the three budget surfaces are three things and their
numbers do not go in one sentence. Nothing in this PR joins them.

sitefiguredisposition
:442-446"9,535 bytes (0.10x) for framework, 8,924 (0.10x) for i18n-locales, against the 9,137 (0.10x) the retired framework pair carried"correct, untouched.71,000 − 61,465 = 9,535 and 455,000 − 446,076 = 8,924, both from constants in this file; 9,137 is dated to the retired pair.
:452"8,924 bytes is about sixty translation keys"correct, number kept. It was silent about being headroom above the baseline; it now says so. Its trailing clause was the 11,988 defect and is rewritten.
:346-348, :386, :411, :431per-chunk narrativesdated to their cards. Kept.

Live per-chunk drift is not written into the file — that is the whole ruling —
but for the record, measured on f620a26f6: i18n-locales has spent 1,882 of its
8,924 baseline headroom and ui-components is down to 2,716 bytes (0.03x). The gate
prints all of it.

Verification

Full apps/console build (this file is in performance-budget.yml's runtime
closure, so the gate cannot be judged without one). Both builds and the test run went
through the shared verify lock; wall-clock figures are shared-box seconds.

pnpm turbo run build --filter='./packages/*' --concurrency=2 39 tasks ok
pnpm --filter @object-ui/console build ok
node scripts/check-eager-closure-budget.mjs exit 0

The verdict this PR's new prose points at, printed on f620a26f6:

✅ Console eager closure is 3181.9 KB gzipped across 50 of 518 chunks (budget: 3191.4 KB, headroom: 9.5 KB).
✅ Per-chunk eager budgets (4 chunks weighed):
✅ vendor-objectstack 926.1 KB / 944.3 KB ceiling (headroom 18.2 KB)
✅ i18n-locales 437.5 KB / 444.3 KB ceiling (headroom 6.9 KB)
✅ ui-components 387.0 KB / 389.6 KB ceiling (headroom 2.7 KB)
✅ framework 61.0 KB / 69.3 KB ceiling (headroom 8.3 KB)
✅ Ceiling sensitivity (5 ceilings, each weighed against the report just read):
✅ aggregate closure 3181.9 KB measured / 3191.4 KB ceiling (headroom 9.5 KB = 0.11x 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` 437.5 KB measured / 444.3 KB ceiling (headroom 6.9 KB = 0.08x the 89.0 KB regression) [PER_CHUNK_GZIP_CEILINGS['i18n-locales']]
✅ chunk `framework` 61.0 KB measured / 69.3 KB ceiling (headroom 8.3 KB = 0.09x the 89.0 KB regression) [PER_CHUNK_GZIP_CEILINGS['framework']]
✅ chunk `ui-components` 387.0 KB measured / 389.6 KB ceiling (headroom 2.7 KB = 0.03x the 89.0 KB regression) [PER_CHUNK_GZIP_CEILINGS['ui-components']]

Exactly: eagerGzipBytes = 3,258,288, so the headroom in force is 9,712 bytes.
None of 45,996 / 45,686 / 11,988 is that number — and the figure this seat was handed
in dispatch, taken from CI on a neighbouring commit, was 9,734. Twenty-two bytes
apart, in the same afternoon, is the argument for (c) in one line.

The gate's behaviour did not move

The report is held constant (one build) and the checker is run on the pristine tree
and again on the edited one:

cmp verdict-before.txt verdict-after.txt -> exit 0, identical
md5 both -> 754fcf61b9a33e9e0cf1b92ce644abc3

Negative control, so "identical" is a reading and not a tautology: appending one byte
to a copy makes the same cmp exit 1 at byte 2186. Re-run again at the final commit
2e001f5f2, still byte-identical to the pristine reading.

scripts/check-eager-closure-budget.mjs is not a build input (turbo's console
inputs cover scripts/vite-*.ts, not scripts/check-*.mjs — the argument
BASELINE already makes about its own commit), which is why one build is enough to
hold the report fixed across the edit.

Gates, on 2e001f5f2

pnpm exec vitest run scripts/__tests__/check-eager-closure-budget.test.ts \
scripts/__tests__/render-budget-comment.test.ts \
scripts/__tests__/check-side-effects-array.test.ts \
scripts/__tests__/vite-declared-lazy-views.test.ts \
scripts/__tests__/vite-ineffective-dynamic-imports.test.ts
Test Files 5 passed (5) Tests 207 passed (207)

Those five are every test file in the repo that reads this script's source. The set
matters here because objectui#7046 pins prose against live values —
attachedDocs locates the block attached to each baseline and asserts what it says
about commits and chunk names — so a comment-only edit to these blocks is exactly the
kind that can red them. It does not.

node scripts/check-control-bytes.mjs ✅ 6199 tracked text files
node scripts/check-changeset-presence.mjs ✅ no changeset owed

The changeset checker's own verdict line: "1 file(s) changed, 0 of them published
source of a package the release covers ... no changeset is owed."
No
skip-changeset label is applied — this repo declares with an empty-frontmatter
changeset when one is owed, and here none is.

Not run locally: the rest of the gate farm, which CI runs exactly once anyway.

Fences honoured

No ceiling is raised, lowered, or otherwise moved. MAX_EAGER_CLOSURE_GZIP_BYTES,
BASELINE, PER_CHUNK_GZIP_CEILINGS, PER_CHUNK_BASELINE and
REGRESSION_THIS_GATE_MUST_CATCH_BYTES are byte-identical to origin/main; the diff
is 38 insertions and 22 deletions, all of them inside comments. If 9.7 KB of
aggregate headroom is too tight, that is a different card and a maintainer's call.

Filed, not fixed here

#7528 — two more frozen measurements in this gate's prose that the ruling above does
not cover: "one number over 52 chunks" asserted in the present tense in this file and
in its test (the build measures 50; BASELINE.chunks says 48), and
performance-budget.yml citing "3.15 MB", the ceiling objectui#6776 retired. Out of
scope: neither is an aggregate-headroom sentence, and one lives in another file.
#7528 is not addressed by this PR.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC


Generated by Claude Code

`scripts/check-eager-closure-budget.mjs` carried three different aggregate
headroom figures and none of them was the one in force. 45,996 (header) was
objectui#6683's ceiling minus objectui#6683's baseline, a whole re-baseline
stale. 11,988 (`PER_CHUNK_GZIP_CEILINGS`) was objectui#7399's post-change live
reading frozen into a sentence. 45,686 (`MAX_EAGER_CLOSURE_GZIP_BYTES`) is
arithmetic on the two constants beside it and is correct — but it is the
headroom above the BASELINE, not what the closure has left, and the prose did
not say so. Measured on this branch's base, the gate prints 9.5 KB.
Two more of the same shape: "the aggregate headroom now 44.0 KB"
(`evaluateCeilingFreshness`) was objectui#5924-era, and the "TIGHTER half —
0.21x, 0.08x, 0.04x against the aggregate's 0.47x" ranking in
`PER_CHUNK_BASELINE` had INVERTED (`vendor-objectstack` is 0.20x against the
aggregate's 0.11x today).
Comment-only. Every constant — `MAX_EAGER_CLOSURE_GZIP_BYTES`, `BASELINE`,
`PER_CHUNK_GZIP_CEILINGS`, `PER_CHUNK_BASELINE`,
`REGRESSION_THIS_GATE_MUST_CATCH_BYTES` — keeps its value, and the gate's
verdict is byte-identical before and after.
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)3181.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-CkpJ-S5M.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.19KB117.80KB
core (index.js)6.12KB2.42KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)242.42KB61.26KB
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.15KB13.35KB
plugin-charts (index.js)70.87KB19.72KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.82KB34.64KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.07KB64.12KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.46KB41.06KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)52.71KB14.55KB
plugin-list (index.js)113.33KB27.60KB
plugin-map (index.js)20.55KB6.80KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)9.40KB3.23KB
plugin-view (index.js)85.22KB20.93KB
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)4.63KB2.18KB
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 17:33
@os-project-manager
os-project-manager added this pull request to the merge queueSep 3, 2026
Merged via the queue into main with commit d04e79aSep 3, 2026
32 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-7518-eager-closure-headroom-prose branch September 3, 2026 17:57
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

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

docs(scripts): stop restating the eager-closure headroom in prose - #7529

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-7518-eager-closure-headroom-prose
Sep 3, 2026
Merged

docs(scripts): stop restating the eager-closure headroom in prose#7529
os-project-manager merged 1 commit into
mainfrom
claude/issue-7518-eager-closure-headroom-prose

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes#7518

Comment-only. Every constant keeps its value; the gate's verdict is byte-identical
before and after.

The disposition, and why it is not "write today's number"

The card offered three routes — (a) restate the figure, (b) restate a ratio,
(c) point at the live verdict. The dispatching seat ruled (c), and the argument
is one this file has already proved on itself: a hardcoded headroom here went stale
badly enough to produce three mutually contradictory versions, two of them in the
same file. (a) only winds the clock forward to today; (b) drifts with the baseline.

So the prose now says which line is binding and how to read the current value
(pnpm check:eager-closure) and does not say what it equals.

Same shape as #7517 (f620a26f6), which fixed the ten locale packs' stale headroom
note by naming the ceiling's distance from the baseline it was measured from and
pointing at the same command.

Whole-file census

The card required a survey of the entire file, not only the line it names. Line
numbers are on the pre-change file at f620a26f6.

Aggregate surface — rewritten

sitewhat was writtenwhat it actually wasnow
:50-51"Headroom above the current 3,254,004 bytes: 45,996 (1.41%)"3,300,000 − 3,254,004 — the objectui#6683 ceiling/baseline pair, one full re-baseline stale, and "the current" is a baseline that was replaced by objectui#6776no figure; points at the constant that states it once, and at the live verdict
:54"45,996 < 91,136, so this ceiling would have failed"the same retired figure reused as the proof of the second constraintargues from the policy (headroom is half the regression by construction) instead of a literal
:455"the AGGREGATE line (11,988 bytes of headroom) becomes the binding one"3,268,000 − 3,256,012 — objectui#7399's own post-change live reading frozen into a sentenceno figure; the claim that the aggregate becomes binding stands, the number is read off the gate
:1245-1246"With the aggregate headroom now 44.0 KB against an 89.0 KB regression class"3,345,000 − 3,299,898 = 45,102 — the objectui#5924 pair, two re-baselines back, present tenseno figure
:548-550"they are the TIGHTER half — 0.21x, 0.08x, 0.04x against the aggregate's 0.47x"dated e33b44796 ratios promoted into a present-tense structural claim — and the claim has inverted (below)states that which line is tightest moves with every build and is ranked by the gate's own sensitivity table

Aggregate surface — deliberately kept, with the reason

sitefigurewhy it is not in the class above
:255"Headroom is 45,686 bytes — 0.50x"3,268,000 − 3,222,314: arithmetic on two constants in this file, correct at write time and correct now, and it cannot drift independently of them (the unit test asserts MAX − BASELINE.gzipBytes < REGRESSION). What was missing is that this is headroom above the baseline, not what the closure has left — the sentence now says so and points at the live figure.
:530-531"4,086,000 over the 3,298,620 reading, 787,380 bytes, 8.64x"explicitly a retired claim that the same paragraph goes on to call false. A dated record of a defect, not an assertion about today.
:64-65"~45 KB of room in each direction rather than 66 KB one way and 25 KB the other"arithmetic on REGRESSION_THIS_GATE_MUST_CATCH_BYTES and on the retired ~2% policy it replaced. A statement about the policy; no measurement in it.
:117, :136, :156, :242, :251"~200 KB", "45,102", "75.9 KB = 0.85x"each dated to the card that measured it, inside a narrative that names that card.
:544-547the four-line e33b44796 run tableone dated run, already carrying its own "do not size a re-baseline off these; the answer in force is the table the gate prints on YOUR build". One clause added: the run predates objectui#7399's re-attribution, so its framework row weighs locale catalogues that line no longer holds — without it a reader takes a retired subject for the current one, which is the false rule objectui#7399 was itself written about.

The inverted ranking, measured

:548-550 asserted the three per-chunk lines were tighter than the aggregate. On this
branch's base the gate prints the opposite for one of them, and there are four
per-chunk ceilings now, not three:

aggregate closure headroom 9.5 KB = 0.11x
chunk `vendor-objectstack` headroom 18.2 KB = 0.20x

That is why the replacement asserts no ranking at all.

Per-chunk surface — reported separately, numbers never joined to the aggregate's

The dispatching seat's fence: the three budget surfaces are three things and their
numbers do not go in one sentence. Nothing in this PR joins them.

sitefiguredisposition
:442-446"9,535 bytes (0.10x) for framework, 8,924 (0.10x) for i18n-locales, against the 9,137 (0.10x) the retired framework pair carried"correct, untouched.71,000 − 61,465 = 9,535 and 455,000 − 446,076 = 8,924, both from constants in this file; 9,137 is dated to the retired pair.
:452"8,924 bytes is about sixty translation keys"correct, number kept. It was silent about being headroom above the baseline; it now says so. Its trailing clause was the 11,988 defect and is rewritten.
:346-348, :386, :411, :431per-chunk narrativesdated to their cards. Kept.

Live per-chunk drift is not written into the file — that is the whole ruling —
but for the record, measured on f620a26f6: i18n-locales has spent 1,882 of its
8,924 baseline headroom and ui-components is down to 2,716 bytes (0.03x). The gate
prints all of it.

Verification

Full apps/console build (this file is in performance-budget.yml's runtime
closure, so the gate cannot be judged without one). Both builds and the test run went
through the shared verify lock; wall-clock figures are shared-box seconds.

pnpm turbo run build --filter='./packages/*' --concurrency=2 39 tasks ok
pnpm --filter @object-ui/console build ok
node scripts/check-eager-closure-budget.mjs exit 0

The verdict this PR's new prose points at, printed on f620a26f6:

✅ Console eager closure is 3181.9 KB gzipped across 50 of 518 chunks (budget: 3191.4 KB, headroom: 9.5 KB).
✅ Per-chunk eager budgets (4 chunks weighed):
✅ vendor-objectstack 926.1 KB / 944.3 KB ceiling (headroom 18.2 KB)
✅ i18n-locales 437.5 KB / 444.3 KB ceiling (headroom 6.9 KB)
✅ ui-components 387.0 KB / 389.6 KB ceiling (headroom 2.7 KB)
✅ framework 61.0 KB / 69.3 KB ceiling (headroom 8.3 KB)
✅ Ceiling sensitivity (5 ceilings, each weighed against the report just read):
✅ aggregate closure 3181.9 KB measured / 3191.4 KB ceiling (headroom 9.5 KB = 0.11x 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` 437.5 KB measured / 444.3 KB ceiling (headroom 6.9 KB = 0.08x the 89.0 KB regression) [PER_CHUNK_GZIP_CEILINGS['i18n-locales']]
✅ chunk `framework` 61.0 KB measured / 69.3 KB ceiling (headroom 8.3 KB = 0.09x the 89.0 KB regression) [PER_CHUNK_GZIP_CEILINGS['framework']]
✅ chunk `ui-components` 387.0 KB measured / 389.6 KB ceiling (headroom 2.7 KB = 0.03x the 89.0 KB regression) [PER_CHUNK_GZIP_CEILINGS['ui-components']]

Exactly: eagerGzipBytes = 3,258,288, so the headroom in force is 9,712 bytes.
None of 45,996 / 45,686 / 11,988 is that number — and the figure this seat was handed
in dispatch, taken from CI on a neighbouring commit, was 9,734. Twenty-two bytes
apart, in the same afternoon, is the argument for (c) in one line.

The gate's behaviour did not move

The report is held constant (one build) and the checker is run on the pristine tree
and again on the edited one:

cmp verdict-before.txt verdict-after.txt -> exit 0, identical
md5 both -> 754fcf61b9a33e9e0cf1b92ce644abc3

Negative control, so "identical" is a reading and not a tautology: appending one byte
to a copy makes the same cmp exit 1 at byte 2186. Re-run again at the final commit
2e001f5f2, still byte-identical to the pristine reading.

scripts/check-eager-closure-budget.mjs is not a build input (turbo's console
inputs cover scripts/vite-*.ts, not scripts/check-*.mjs — the argument
BASELINE already makes about its own commit), which is why one build is enough to
hold the report fixed across the edit.

Gates, on 2e001f5f2

pnpm exec vitest run scripts/__tests__/check-eager-closure-budget.test.ts \
scripts/__tests__/render-budget-comment.test.ts \
scripts/__tests__/check-side-effects-array.test.ts \
scripts/__tests__/vite-declared-lazy-views.test.ts \
scripts/__tests__/vite-ineffective-dynamic-imports.test.ts
Test Files 5 passed (5) Tests 207 passed (207)

Those five are every test file in the repo that reads this script's source. The set
matters here because objectui#7046 pins prose against live values —
attachedDocs locates the block attached to each baseline and asserts what it says
about commits and chunk names — so a comment-only edit to these blocks is exactly the
kind that can red them. It does not.

node scripts/check-control-bytes.mjs ✅ 6199 tracked text files
node scripts/check-changeset-presence.mjs ✅ no changeset owed

The changeset checker's own verdict line: "1 file(s) changed, 0 of them published
source of a package the release covers ... no changeset is owed."
No
skip-changeset label is applied — this repo declares with an empty-frontmatter
changeset when one is owed, and here none is.

Not run locally: the rest of the gate farm, which CI runs exactly once anyway.

Fences honoured

No ceiling is raised, lowered, or otherwise moved. MAX_EAGER_CLOSURE_GZIP_BYTES,
BASELINE, PER_CHUNK_GZIP_CEILINGS, PER_CHUNK_BASELINE and
REGRESSION_THIS_GATE_MUST_CATCH_BYTES are byte-identical to origin/main; the diff
is 38 insertions and 22 deletions, all of them inside comments. If 9.7 KB of
aggregate headroom is too tight, that is a different card and a maintainer's call.

Filed, not fixed here

#7528 — two more frozen measurements in this gate's prose that the ruling above does
not cover: "one number over 52 chunks" asserted in the present tense in this file and
in its test (the build measures 50; BASELINE.chunks says 48), and
performance-budget.yml citing "3.15 MB", the ceiling objectui#6776 retired. Out of
scope: neither is an aggregate-headroom sentence, and one lives in another file.
#7528 is not addressed by this PR.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC


Generated by Claude Code

`scripts/check-eager-closure-budget.mjs` carried three different aggregate
headroom figures and none of them was the one in force. 45,996 (header) was
objectui#6683's ceiling minus objectui#6683's baseline, a whole re-baseline
stale. 11,988 (`PER_CHUNK_GZIP_CEILINGS`) was objectui#7399's post-change live
reading frozen into a sentence. 45,686 (`MAX_EAGER_CLOSURE_GZIP_BYTES`) is
arithmetic on the two constants beside it and is correct — but it is the
headroom above the BASELINE, not what the closure has left, and the prose did
not say so. Measured on this branch's base, the gate prints 9.5 KB.
Two more of the same shape: "the aggregate headroom now 44.0 KB"
(`evaluateCeilingFreshness`) was objectui#5924-era, and the "TIGHTER half —
0.21x, 0.08x, 0.04x against the aggregate's 0.47x" ranking in
`PER_CHUNK_BASELINE` had INVERTED (`vendor-objectstack` is 0.20x against the
aggregate's 0.11x today).
Comment-only. Every constant — `MAX_EAGER_CLOSURE_GZIP_BYTES`, `BASELINE`,
`PER_CHUNK_GZIP_CEILINGS`, `PER_CHUNK_BASELINE`,
`REGRESSION_THIS_GATE_MUST_CATCH_BYTES` — keeps its value, and the gate's
verdict is byte-identical before and after.
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)3181.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-CkpJ-S5M.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.19KB117.80KB
core (index.js)6.12KB2.42KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)242.42KB61.26KB
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.15KB13.35KB
plugin-charts (index.js)70.87KB19.72KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.82KB34.64KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.07KB64.12KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.46KB41.06KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)52.71KB14.55KB
plugin-list (index.js)113.33KB27.60KB
plugin-map (index.js)20.55KB6.80KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)9.40KB3.23KB
plugin-view (index.js)85.22KB20.93KB
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)4.63KB2.18KB
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 17:33
@os-project-manager
os-project-manager added this pull request to the merge queueSep 3, 2026
Merged via the queue into main with commit d04e79aSep 3, 2026
32 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-7518-eager-closure-headroom-prose branch September 3, 2026 17:57
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

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

docs(scripts): stop restating the eager-closure headroom in prose - #7529

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-7518-eager-closure-headroom-prose
Sep 3, 2026
Merged

docs(scripts): stop restating the eager-closure headroom in prose#7529
os-project-manager merged 1 commit into
mainfrom
claude/issue-7518-eager-closure-headroom-prose

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes#7518

Comment-only. Every constant keeps its value; the gate's verdict is byte-identical
before and after.

The disposition, and why it is not "write today's number"

The card offered three routes — (a) restate the figure, (b) restate a ratio,
(c) point at the live verdict. The dispatching seat ruled (c), and the argument
is one this file has already proved on itself: a hardcoded headroom here went stale
badly enough to produce three mutually contradictory versions, two of them in the
same file. (a) only winds the clock forward to today; (b) drifts with the baseline.

So the prose now says which line is binding and how to read the current value
(pnpm check:eager-closure) and does not say what it equals.

Same shape as #7517 (f620a26f6), which fixed the ten locale packs' stale headroom
note by naming the ceiling's distance from the baseline it was measured from and
pointing at the same command.

Whole-file census

The card required a survey of the entire file, not only the line it names. Line
numbers are on the pre-change file at f620a26f6.

Aggregate surface — rewritten

sitewhat was writtenwhat it actually wasnow
:50-51"Headroom above the current 3,254,004 bytes: 45,996 (1.41%)"3,300,000 − 3,254,004 — the objectui#6683 ceiling/baseline pair, one full re-baseline stale, and "the current" is a baseline that was replaced by objectui#6776no figure; points at the constant that states it once, and at the live verdict
:54"45,996 < 91,136, so this ceiling would have failed"the same retired figure reused as the proof of the second constraintargues from the policy (headroom is half the regression by construction) instead of a literal
:455"the AGGREGATE line (11,988 bytes of headroom) becomes the binding one"3,268,000 − 3,256,012 — objectui#7399's own post-change live reading frozen into a sentenceno figure; the claim that the aggregate becomes binding stands, the number is read off the gate
:1245-1246"With the aggregate headroom now 44.0 KB against an 89.0 KB regression class"3,345,000 − 3,299,898 = 45,102 — the objectui#5924 pair, two re-baselines back, present tenseno figure
:548-550"they are the TIGHTER half — 0.21x, 0.08x, 0.04x against the aggregate's 0.47x"dated e33b44796 ratios promoted into a present-tense structural claim — and the claim has inverted (below)states that which line is tightest moves with every build and is ranked by the gate's own sensitivity table

Aggregate surface — deliberately kept, with the reason

sitefigurewhy it is not in the class above
:255"Headroom is 45,686 bytes — 0.50x"3,268,000 − 3,222,314: arithmetic on two constants in this file, correct at write time and correct now, and it cannot drift independently of them (the unit test asserts MAX − BASELINE.gzipBytes < REGRESSION). What was missing is that this is headroom above the baseline, not what the closure has left — the sentence now says so and points at the live figure.
:530-531"4,086,000 over the 3,298,620 reading, 787,380 bytes, 8.64x"explicitly a retired claim that the same paragraph goes on to call false. A dated record of a defect, not an assertion about today.
:64-65"~45 KB of room in each direction rather than 66 KB one way and 25 KB the other"arithmetic on REGRESSION_THIS_GATE_MUST_CATCH_BYTES and on the retired ~2% policy it replaced. A statement about the policy; no measurement in it.
:117, :136, :156, :242, :251"~200 KB", "45,102", "75.9 KB = 0.85x"each dated to the card that measured it, inside a narrative that names that card.
:544-547the four-line e33b44796 run tableone dated run, already carrying its own "do not size a re-baseline off these; the answer in force is the table the gate prints on YOUR build". One clause added: the run predates objectui#7399's re-attribution, so its framework row weighs locale catalogues that line no longer holds — without it a reader takes a retired subject for the current one, which is the false rule objectui#7399 was itself written about.

The inverted ranking, measured

:548-550 asserted the three per-chunk lines were tighter than the aggregate. On this
branch's base the gate prints the opposite for one of them, and there are four
per-chunk ceilings now, not three:

aggregate closure headroom 9.5 KB = 0.11x
chunk `vendor-objectstack` headroom 18.2 KB = 0.20x

That is why the replacement asserts no ranking at all.

Per-chunk surface — reported separately, numbers never joined to the aggregate's

The dispatching seat's fence: the three budget surfaces are three things and their
numbers do not go in one sentence. Nothing in this PR joins them.

sitefiguredisposition
:442-446"9,535 bytes (0.10x) for framework, 8,924 (0.10x) for i18n-locales, against the 9,137 (0.10x) the retired framework pair carried"correct, untouched.71,000 − 61,465 = 9,535 and 455,000 − 446,076 = 8,924, both from constants in this file; 9,137 is dated to the retired pair.
:452"8,924 bytes is about sixty translation keys"correct, number kept. It was silent about being headroom above the baseline; it now says so. Its trailing clause was the 11,988 defect and is rewritten.
:346-348, :386, :411, :431per-chunk narrativesdated to their cards. Kept.

Live per-chunk drift is not written into the file — that is the whole ruling —
but for the record, measured on f620a26f6: i18n-locales has spent 1,882 of its
8,924 baseline headroom and ui-components is down to 2,716 bytes (0.03x). The gate
prints all of it.

Verification

Full apps/console build (this file is in performance-budget.yml's runtime
closure, so the gate cannot be judged without one). Both builds and the test run went
through the shared verify lock; wall-clock figures are shared-box seconds.

pnpm turbo run build --filter='./packages/*' --concurrency=2 39 tasks ok
pnpm --filter @object-ui/console build ok
node scripts/check-eager-closure-budget.mjs exit 0

The verdict this PR's new prose points at, printed on f620a26f6:

✅ Console eager closure is 3181.9 KB gzipped across 50 of 518 chunks (budget: 3191.4 KB, headroom: 9.5 KB).
✅ Per-chunk eager budgets (4 chunks weighed):
✅ vendor-objectstack 926.1 KB / 944.3 KB ceiling (headroom 18.2 KB)
✅ i18n-locales 437.5 KB / 444.3 KB ceiling (headroom 6.9 KB)
✅ ui-components 387.0 KB / 389.6 KB ceiling (headroom 2.7 KB)
✅ framework 61.0 KB / 69.3 KB ceiling (headroom 8.3 KB)
✅ Ceiling sensitivity (5 ceilings, each weighed against the report just read):
✅ aggregate closure 3181.9 KB measured / 3191.4 KB ceiling (headroom 9.5 KB = 0.11x 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` 437.5 KB measured / 444.3 KB ceiling (headroom 6.9 KB = 0.08x the 89.0 KB regression) [PER_CHUNK_GZIP_CEILINGS['i18n-locales']]
✅ chunk `framework` 61.0 KB measured / 69.3 KB ceiling (headroom 8.3 KB = 0.09x the 89.0 KB regression) [PER_CHUNK_GZIP_CEILINGS['framework']]
✅ chunk `ui-components` 387.0 KB measured / 389.6 KB ceiling (headroom 2.7 KB = 0.03x the 89.0 KB regression) [PER_CHUNK_GZIP_CEILINGS['ui-components']]

Exactly: eagerGzipBytes = 3,258,288, so the headroom in force is 9,712 bytes.
None of 45,996 / 45,686 / 11,988 is that number — and the figure this seat was handed
in dispatch, taken from CI on a neighbouring commit, was 9,734. Twenty-two bytes
apart, in the same afternoon, is the argument for (c) in one line.

The gate's behaviour did not move

The report is held constant (one build) and the checker is run on the pristine tree
and again on the edited one:

cmp verdict-before.txt verdict-after.txt -> exit 0, identical
md5 both -> 754fcf61b9a33e9e0cf1b92ce644abc3

Negative control, so "identical" is a reading and not a tautology: appending one byte
to a copy makes the same cmp exit 1 at byte 2186. Re-run again at the final commit
2e001f5f2, still byte-identical to the pristine reading.

scripts/check-eager-closure-budget.mjs is not a build input (turbo's console
inputs cover scripts/vite-*.ts, not scripts/check-*.mjs — the argument
BASELINE already makes about its own commit), which is why one build is enough to
hold the report fixed across the edit.

Gates, on 2e001f5f2

pnpm exec vitest run scripts/__tests__/check-eager-closure-budget.test.ts \
scripts/__tests__/render-budget-comment.test.ts \
scripts/__tests__/check-side-effects-array.test.ts \
scripts/__tests__/vite-declared-lazy-views.test.ts \
scripts/__tests__/vite-ineffective-dynamic-imports.test.ts
Test Files 5 passed (5) Tests 207 passed (207)

Those five are every test file in the repo that reads this script's source. The set
matters here because objectui#7046 pins prose against live values —
attachedDocs locates the block attached to each baseline and asserts what it says
about commits and chunk names — so a comment-only edit to these blocks is exactly the
kind that can red them. It does not.

node scripts/check-control-bytes.mjs ✅ 6199 tracked text files
node scripts/check-changeset-presence.mjs ✅ no changeset owed

The changeset checker's own verdict line: "1 file(s) changed, 0 of them published
source of a package the release covers ... no changeset is owed."
No
skip-changeset label is applied — this repo declares with an empty-frontmatter
changeset when one is owed, and here none is.

Not run locally: the rest of the gate farm, which CI runs exactly once anyway.

Fences honoured

No ceiling is raised, lowered, or otherwise moved. MAX_EAGER_CLOSURE_GZIP_BYTES,
BASELINE, PER_CHUNK_GZIP_CEILINGS, PER_CHUNK_BASELINE and
REGRESSION_THIS_GATE_MUST_CATCH_BYTES are byte-identical to origin/main; the diff
is 38 insertions and 22 deletions, all of them inside comments. If 9.7 KB of
aggregate headroom is too tight, that is a different card and a maintainer's call.

Filed, not fixed here

#7528 — two more frozen measurements in this gate's prose that the ruling above does
not cover: "one number over 52 chunks" asserted in the present tense in this file and
in its test (the build measures 50; BASELINE.chunks says 48), and
performance-budget.yml citing "3.15 MB", the ceiling objectui#6776 retired. Out of
scope: neither is an aggregate-headroom sentence, and one lives in another file.
#7528 is not addressed by this PR.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC


Generated by Claude Code

`scripts/check-eager-closure-budget.mjs` carried three different aggregate
headroom figures and none of them was the one in force. 45,996 (header) was
objectui#6683's ceiling minus objectui#6683's baseline, a whole re-baseline
stale. 11,988 (`PER_CHUNK_GZIP_CEILINGS`) was objectui#7399's post-change live
reading frozen into a sentence. 45,686 (`MAX_EAGER_CLOSURE_GZIP_BYTES`) is
arithmetic on the two constants beside it and is correct — but it is the
headroom above the BASELINE, not what the closure has left, and the prose did
not say so. Measured on this branch's base, the gate prints 9.5 KB.
Two more of the same shape: "the aggregate headroom now 44.0 KB"
(`evaluateCeilingFreshness`) was objectui#5924-era, and the "TIGHTER half —
0.21x, 0.08x, 0.04x against the aggregate's 0.47x" ranking in
`PER_CHUNK_BASELINE` had INVERTED (`vendor-objectstack` is 0.20x against the
aggregate's 0.11x today).
Comment-only. Every constant — `MAX_EAGER_CLOSURE_GZIP_BYTES`, `BASELINE`,
`PER_CHUNK_GZIP_CEILINGS`, `PER_CHUNK_BASELINE`,
`REGRESSION_THIS_GATE_MUST_CATCH_BYTES` — keeps its value, and the gate's
verdict is byte-identical before and after.
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)3181.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-CkpJ-S5M.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.19KB117.80KB
core (index.js)6.12KB2.42KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)242.42KB61.26KB
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.15KB13.35KB
plugin-charts (index.js)70.87KB19.72KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.82KB34.64KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.07KB64.12KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.46KB41.06KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)52.71KB14.55KB
plugin-list (index.js)113.33KB27.60KB
plugin-map (index.js)20.55KB6.80KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)9.40KB3.23KB
plugin-view (index.js)85.22KB20.93KB
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)4.63KB2.18KB
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 17:33
@os-project-manager
os-project-manager added this pull request to the merge queueSep 3, 2026
Merged via the queue into main with commit d04e79aSep 3, 2026
32 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-7518-eager-closure-headroom-prose branch September 3, 2026 17:57
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

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

docs(scripts): stop restating the eager-closure headroom in prose - #7529

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-7518-eager-closure-headroom-prose
Sep 3, 2026
Merged

docs(scripts): stop restating the eager-closure headroom in prose#7529
os-project-manager merged 1 commit into
mainfrom
claude/issue-7518-eager-closure-headroom-prose

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes#7518

Comment-only. Every constant keeps its value; the gate's verdict is byte-identical
before and after.

The disposition, and why it is not "write today's number"

The card offered three routes — (a) restate the figure, (b) restate a ratio,
(c) point at the live verdict. The dispatching seat ruled (c), and the argument
is one this file has already proved on itself: a hardcoded headroom here went stale
badly enough to produce three mutually contradictory versions, two of them in the
same file. (a) only winds the clock forward to today; (b) drifts with the baseline.

So the prose now says which line is binding and how to read the current value
(pnpm check:eager-closure) and does not say what it equals.

Same shape as #7517 (f620a26f6), which fixed the ten locale packs' stale headroom
note by naming the ceiling's distance from the baseline it was measured from and
pointing at the same command.

Whole-file census

The card required a survey of the entire file, not only the line it names. Line
numbers are on the pre-change file at f620a26f6.

Aggregate surface — rewritten

sitewhat was writtenwhat it actually wasnow
:50-51"Headroom above the current 3,254,004 bytes: 45,996 (1.41%)"3,300,000 − 3,254,004 — the objectui#6683 ceiling/baseline pair, one full re-baseline stale, and "the current" is a baseline that was replaced by objectui#6776no figure; points at the constant that states it once, and at the live verdict
:54"45,996 < 91,136, so this ceiling would have failed"the same retired figure reused as the proof of the second constraintargues from the policy (headroom is half the regression by construction) instead of a literal
:455"the AGGREGATE line (11,988 bytes of headroom) becomes the binding one"3,268,000 − 3,256,012 — objectui#7399's own post-change live reading frozen into a sentenceno figure; the claim that the aggregate becomes binding stands, the number is read off the gate
:1245-1246"With the aggregate headroom now 44.0 KB against an 89.0 KB regression class"3,345,000 − 3,299,898 = 45,102 — the objectui#5924 pair, two re-baselines back, present tenseno figure
:548-550"they are the TIGHTER half — 0.21x, 0.08x, 0.04x against the aggregate's 0.47x"dated e33b44796 ratios promoted into a present-tense structural claim — and the claim has inverted (below)states that which line is tightest moves with every build and is ranked by the gate's own sensitivity table

Aggregate surface — deliberately kept, with the reason

sitefigurewhy it is not in the class above
:255"Headroom is 45,686 bytes — 0.50x"3,268,000 − 3,222,314: arithmetic on two constants in this file, correct at write time and correct now, and it cannot drift independently of them (the unit test asserts MAX − BASELINE.gzipBytes < REGRESSION). What was missing is that this is headroom above the baseline, not what the closure has left — the sentence now says so and points at the live figure.
:530-531"4,086,000 over the 3,298,620 reading, 787,380 bytes, 8.64x"explicitly a retired claim that the same paragraph goes on to call false. A dated record of a defect, not an assertion about today.
:64-65"~45 KB of room in each direction rather than 66 KB one way and 25 KB the other"arithmetic on REGRESSION_THIS_GATE_MUST_CATCH_BYTES and on the retired ~2% policy it replaced. A statement about the policy; no measurement in it.
:117, :136, :156, :242, :251"~200 KB", "45,102", "75.9 KB = 0.85x"each dated to the card that measured it, inside a narrative that names that card.
:544-547the four-line e33b44796 run tableone dated run, already carrying its own "do not size a re-baseline off these; the answer in force is the table the gate prints on YOUR build". One clause added: the run predates objectui#7399's re-attribution, so its framework row weighs locale catalogues that line no longer holds — without it a reader takes a retired subject for the current one, which is the false rule objectui#7399 was itself written about.

The inverted ranking, measured

:548-550 asserted the three per-chunk lines were tighter than the aggregate. On this
branch's base the gate prints the opposite for one of them, and there are four
per-chunk ceilings now, not three:

aggregate closure headroom 9.5 KB = 0.11x
chunk `vendor-objectstack` headroom 18.2 KB = 0.20x

That is why the replacement asserts no ranking at all.

Per-chunk surface — reported separately, numbers never joined to the aggregate's

The dispatching seat's fence: the three budget surfaces are three things and their
numbers do not go in one sentence. Nothing in this PR joins them.

sitefiguredisposition
:442-446"9,535 bytes (0.10x) for framework, 8,924 (0.10x) for i18n-locales, against the 9,137 (0.10x) the retired framework pair carried"correct, untouched.71,000 − 61,465 = 9,535 and 455,000 − 446,076 = 8,924, both from constants in this file; 9,137 is dated to the retired pair.
:452"8,924 bytes is about sixty translation keys"correct, number kept. It was silent about being headroom above the baseline; it now says so. Its trailing clause was the 11,988 defect and is rewritten.
:346-348, :386, :411, :431per-chunk narrativesdated to their cards. Kept.

Live per-chunk drift is not written into the file — that is the whole ruling —
but for the record, measured on f620a26f6: i18n-locales has spent 1,882 of its
8,924 baseline headroom and ui-components is down to 2,716 bytes (0.03x). The gate
prints all of it.

Verification

Full apps/console build (this file is in performance-budget.yml's runtime
closure, so the gate cannot be judged without one). Both builds and the test run went
through the shared verify lock; wall-clock figures are shared-box seconds.

pnpm turbo run build --filter='./packages/*' --concurrency=2 39 tasks ok
pnpm --filter @object-ui/console build ok
node scripts/check-eager-closure-budget.mjs exit 0

The verdict this PR's new prose points at, printed on f620a26f6:

✅ Console eager closure is 3181.9 KB gzipped across 50 of 518 chunks (budget: 3191.4 KB, headroom: 9.5 KB).
✅ Per-chunk eager budgets (4 chunks weighed):
✅ vendor-objectstack 926.1 KB / 944.3 KB ceiling (headroom 18.2 KB)
✅ i18n-locales 437.5 KB / 444.3 KB ceiling (headroom 6.9 KB)
✅ ui-components 387.0 KB / 389.6 KB ceiling (headroom 2.7 KB)
✅ framework 61.0 KB / 69.3 KB ceiling (headroom 8.3 KB)
✅ Ceiling sensitivity (5 ceilings, each weighed against the report just read):
✅ aggregate closure 3181.9 KB measured / 3191.4 KB ceiling (headroom 9.5 KB = 0.11x 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` 437.5 KB measured / 444.3 KB ceiling (headroom 6.9 KB = 0.08x the 89.0 KB regression) [PER_CHUNK_GZIP_CEILINGS['i18n-locales']]
✅ chunk `framework` 61.0 KB measured / 69.3 KB ceiling (headroom 8.3 KB = 0.09x the 89.0 KB regression) [PER_CHUNK_GZIP_CEILINGS['framework']]
✅ chunk `ui-components` 387.0 KB measured / 389.6 KB ceiling (headroom 2.7 KB = 0.03x the 89.0 KB regression) [PER_CHUNK_GZIP_CEILINGS['ui-components']]

Exactly: eagerGzipBytes = 3,258,288, so the headroom in force is 9,712 bytes.
None of 45,996 / 45,686 / 11,988 is that number — and the figure this seat was handed
in dispatch, taken from CI on a neighbouring commit, was 9,734. Twenty-two bytes
apart, in the same afternoon, is the argument for (c) in one line.

The gate's behaviour did not move

The report is held constant (one build) and the checker is run on the pristine tree
and again on the edited one:

cmp verdict-before.txt verdict-after.txt -> exit 0, identical
md5 both -> 754fcf61b9a33e9e0cf1b92ce644abc3

Negative control, so "identical" is a reading and not a tautology: appending one byte
to a copy makes the same cmp exit 1 at byte 2186. Re-run again at the final commit
2e001f5f2, still byte-identical to the pristine reading.

scripts/check-eager-closure-budget.mjs is not a build input (turbo's console
inputs cover scripts/vite-*.ts, not scripts/check-*.mjs — the argument
BASELINE already makes about its own commit), which is why one build is enough to
hold the report fixed across the edit.

Gates, on 2e001f5f2

pnpm exec vitest run scripts/__tests__/check-eager-closure-budget.test.ts \
scripts/__tests__/render-budget-comment.test.ts \
scripts/__tests__/check-side-effects-array.test.ts \
scripts/__tests__/vite-declared-lazy-views.test.ts \
scripts/__tests__/vite-ineffective-dynamic-imports.test.ts
Test Files 5 passed (5) Tests 207 passed (207)

Those five are every test file in the repo that reads this script's source. The set
matters here because objectui#7046 pins prose against live values —
attachedDocs locates the block attached to each baseline and asserts what it says
about commits and chunk names — so a comment-only edit to these blocks is exactly the
kind that can red them. It does not.

node scripts/check-control-bytes.mjs ✅ 6199 tracked text files
node scripts/check-changeset-presence.mjs ✅ no changeset owed

The changeset checker's own verdict line: "1 file(s) changed, 0 of them published
source of a package the release covers ... no changeset is owed."
No
skip-changeset label is applied — this repo declares with an empty-frontmatter
changeset when one is owed, and here none is.

Not run locally: the rest of the gate farm, which CI runs exactly once anyway.

Fences honoured

No ceiling is raised, lowered, or otherwise moved. MAX_EAGER_CLOSURE_GZIP_BYTES,
BASELINE, PER_CHUNK_GZIP_CEILINGS, PER_CHUNK_BASELINE and
REGRESSION_THIS_GATE_MUST_CATCH_BYTES are byte-identical to origin/main; the diff
is 38 insertions and 22 deletions, all of them inside comments. If 9.7 KB of
aggregate headroom is too tight, that is a different card and a maintainer's call.

Filed, not fixed here

#7528 — two more frozen measurements in this gate's prose that the ruling above does
not cover: "one number over 52 chunks" asserted in the present tense in this file and
in its test (the build measures 50; BASELINE.chunks says 48), and
performance-budget.yml citing "3.15 MB", the ceiling objectui#6776 retired. Out of
scope: neither is an aggregate-headroom sentence, and one lives in another file.
#7528 is not addressed by this PR.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC


Generated by Claude Code

`scripts/check-eager-closure-budget.mjs` carried three different aggregate
headroom figures and none of them was the one in force. 45,996 (header) was
objectui#6683's ceiling minus objectui#6683's baseline, a whole re-baseline
stale. 11,988 (`PER_CHUNK_GZIP_CEILINGS`) was objectui#7399's post-change live
reading frozen into a sentence. 45,686 (`MAX_EAGER_CLOSURE_GZIP_BYTES`) is
arithmetic on the two constants beside it and is correct — but it is the
headroom above the BASELINE, not what the closure has left, and the prose did
not say so. Measured on this branch's base, the gate prints 9.5 KB.
Two more of the same shape: "the aggregate headroom now 44.0 KB"
(`evaluateCeilingFreshness`) was objectui#5924-era, and the "TIGHTER half —
0.21x, 0.08x, 0.04x against the aggregate's 0.47x" ranking in
`PER_CHUNK_BASELINE` had INVERTED (`vendor-objectstack` is 0.20x against the
aggregate's 0.11x today).
Comment-only. Every constant — `MAX_EAGER_CLOSURE_GZIP_BYTES`, `BASELINE`,
`PER_CHUNK_GZIP_CEILINGS`, `PER_CHUNK_BASELINE`,
`REGRESSION_THIS_GATE_MUST_CATCH_BYTES` — keeps its value, and the gate's
verdict is byte-identical before and after.
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)3181.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-CkpJ-S5M.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.19KB117.80KB
core (index.js)6.12KB2.42KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)242.42KB61.26KB
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.15KB13.35KB
plugin-charts (index.js)70.87KB19.72KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.82KB34.64KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.07KB64.12KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.46KB41.06KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)52.71KB14.55KB
plugin-list (index.js)113.33KB27.60KB
plugin-map (index.js)20.55KB6.80KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)9.40KB3.23KB
plugin-view (index.js)85.22KB20.93KB
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)4.63KB2.18KB
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 17:33
@os-project-manager
os-project-manager added this pull request to the merge queueSep 3, 2026
Merged via the queue into main with commit d04e79aSep 3, 2026
32 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-7518-eager-closure-headroom-prose branch September 3, 2026 17:57
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

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

docs(scripts): stop restating the eager-closure headroom in prose - #7529

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-7518-eager-closure-headroom-prose
Sep 3, 2026
Merged

docs(scripts): stop restating the eager-closure headroom in prose#7529
os-project-manager merged 1 commit into
mainfrom
claude/issue-7518-eager-closure-headroom-prose

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes#7518

Comment-only. Every constant keeps its value; the gate's verdict is byte-identical
before and after.

The disposition, and why it is not "write today's number"

The card offered three routes — (a) restate the figure, (b) restate a ratio,
(c) point at the live verdict. The dispatching seat ruled (c), and the argument
is one this file has already proved on itself: a hardcoded headroom here went stale
badly enough to produce three mutually contradictory versions, two of them in the
same file. (a) only winds the clock forward to today; (b) drifts with the baseline.

So the prose now says which line is binding and how to read the current value
(pnpm check:eager-closure) and does not say what it equals.

Same shape as #7517 (f620a26f6), which fixed the ten locale packs' stale headroom
note by naming the ceiling's distance from the baseline it was measured from and
pointing at the same command.

Whole-file census

The card required a survey of the entire file, not only the line it names. Line
numbers are on the pre-change file at f620a26f6.

Aggregate surface — rewritten

sitewhat was writtenwhat it actually wasnow
:50-51"Headroom above the current 3,254,004 bytes: 45,996 (1.41%)"3,300,000 − 3,254,004 — the objectui#6683 ceiling/baseline pair, one full re-baseline stale, and "the current" is a baseline that was replaced by objectui#6776no figure; points at the constant that states it once, and at the live verdict
:54"45,996 < 91,136, so this ceiling would have failed"the same retired figure reused as the proof of the second constraintargues from the policy (headroom is half the regression by construction) instead of a literal
:455"the AGGREGATE line (11,988 bytes of headroom) becomes the binding one"3,268,000 − 3,256,012 — objectui#7399's own post-change live reading frozen into a sentenceno figure; the claim that the aggregate becomes binding stands, the number is read off the gate
:1245-1246"With the aggregate headroom now 44.0 KB against an 89.0 KB regression class"3,345,000 − 3,299,898 = 45,102 — the objectui#5924 pair, two re-baselines back, present tenseno figure
:548-550"they are the TIGHTER half — 0.21x, 0.08x, 0.04x against the aggregate's 0.47x"dated e33b44796 ratios promoted into a present-tense structural claim — and the claim has inverted (below)states that which line is tightest moves with every build and is ranked by the gate's own sensitivity table

Aggregate surface — deliberately kept, with the reason

sitefigurewhy it is not in the class above
:255"Headroom is 45,686 bytes — 0.50x"3,268,000 − 3,222,314: arithmetic on two constants in this file, correct at write time and correct now, and it cannot drift independently of them (the unit test asserts MAX − BASELINE.gzipBytes < REGRESSION). What was missing is that this is headroom above the baseline, not what the closure has left — the sentence now says so and points at the live figure.
:530-531"4,086,000 over the 3,298,620 reading, 787,380 bytes, 8.64x"explicitly a retired claim that the same paragraph goes on to call false. A dated record of a defect, not an assertion about today.
:64-65"~45 KB of room in each direction rather than 66 KB one way and 25 KB the other"arithmetic on REGRESSION_THIS_GATE_MUST_CATCH_BYTES and on the retired ~2% policy it replaced. A statement about the policy; no measurement in it.
:117, :136, :156, :242, :251"~200 KB", "45,102", "75.9 KB = 0.85x"each dated to the card that measured it, inside a narrative that names that card.
:544-547the four-line e33b44796 run tableone dated run, already carrying its own "do not size a re-baseline off these; the answer in force is the table the gate prints on YOUR build". One clause added: the run predates objectui#7399's re-attribution, so its framework row weighs locale catalogues that line no longer holds — without it a reader takes a retired subject for the current one, which is the false rule objectui#7399 was itself written about.

The inverted ranking, measured

:548-550 asserted the three per-chunk lines were tighter than the aggregate. On this
branch's base the gate prints the opposite for one of them, and there are four
per-chunk ceilings now, not three:

aggregate closure headroom 9.5 KB = 0.11x
chunk `vendor-objectstack` headroom 18.2 KB = 0.20x

That is why the replacement asserts no ranking at all.

Per-chunk surface — reported separately, numbers never joined to the aggregate's

The dispatching seat's fence: the three budget surfaces are three things and their
numbers do not go in one sentence. Nothing in this PR joins them.

sitefiguredisposition
:442-446"9,535 bytes (0.10x) for framework, 8,924 (0.10x) for i18n-locales, against the 9,137 (0.10x) the retired framework pair carried"correct, untouched.71,000 − 61,465 = 9,535 and 455,000 − 446,076 = 8,924, both from constants in this file; 9,137 is dated to the retired pair.
:452"8,924 bytes is about sixty translation keys"correct, number kept. It was silent about being headroom above the baseline; it now says so. Its trailing clause was the 11,988 defect and is rewritten.
:346-348, :386, :411, :431per-chunk narrativesdated to their cards. Kept.

Live per-chunk drift is not written into the file — that is the whole ruling —
but for the record, measured on f620a26f6: i18n-locales has spent 1,882 of its
8,924 baseline headroom and ui-components is down to 2,716 bytes (0.03x). The gate
prints all of it.

Verification

Full apps/console build (this file is in performance-budget.yml's runtime
closure, so the gate cannot be judged without one). Both builds and the test run went
through the shared verify lock; wall-clock figures are shared-box seconds.

pnpm turbo run build --filter='./packages/*' --concurrency=2 39 tasks ok
pnpm --filter @object-ui/console build ok
node scripts/check-eager-closure-budget.mjs exit 0

The verdict this PR's new prose points at, printed on f620a26f6:

✅ Console eager closure is 3181.9 KB gzipped across 50 of 518 chunks (budget: 3191.4 KB, headroom: 9.5 KB).
✅ Per-chunk eager budgets (4 chunks weighed):
✅ vendor-objectstack 926.1 KB / 944.3 KB ceiling (headroom 18.2 KB)
✅ i18n-locales 437.5 KB / 444.3 KB ceiling (headroom 6.9 KB)
✅ ui-components 387.0 KB / 389.6 KB ceiling (headroom 2.7 KB)
✅ framework 61.0 KB / 69.3 KB ceiling (headroom 8.3 KB)
✅ Ceiling sensitivity (5 ceilings, each weighed against the report just read):
✅ aggregate closure 3181.9 KB measured / 3191.4 KB ceiling (headroom 9.5 KB = 0.11x 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` 437.5 KB measured / 444.3 KB ceiling (headroom 6.9 KB = 0.08x the 89.0 KB regression) [PER_CHUNK_GZIP_CEILINGS['i18n-locales']]
✅ chunk `framework` 61.0 KB measured / 69.3 KB ceiling (headroom 8.3 KB = 0.09x the 89.0 KB regression) [PER_CHUNK_GZIP_CEILINGS['framework']]
✅ chunk `ui-components` 387.0 KB measured / 389.6 KB ceiling (headroom 2.7 KB = 0.03x the 89.0 KB regression) [PER_CHUNK_GZIP_CEILINGS['ui-components']]

Exactly: eagerGzipBytes = 3,258,288, so the headroom in force is 9,712 bytes.
None of 45,996 / 45,686 / 11,988 is that number — and the figure this seat was handed
in dispatch, taken from CI on a neighbouring commit, was 9,734. Twenty-two bytes
apart, in the same afternoon, is the argument for (c) in one line.

The gate's behaviour did not move

The report is held constant (one build) and the checker is run on the pristine tree
and again on the edited one:

cmp verdict-before.txt verdict-after.txt -> exit 0, identical
md5 both -> 754fcf61b9a33e9e0cf1b92ce644abc3

Negative control, so "identical" is a reading and not a tautology: appending one byte
to a copy makes the same cmp exit 1 at byte 2186. Re-run again at the final commit
2e001f5f2, still byte-identical to the pristine reading.

scripts/check-eager-closure-budget.mjs is not a build input (turbo's console
inputs cover scripts/vite-*.ts, not scripts/check-*.mjs — the argument
BASELINE already makes about its own commit), which is why one build is enough to
hold the report fixed across the edit.

Gates, on 2e001f5f2

pnpm exec vitest run scripts/__tests__/check-eager-closure-budget.test.ts \
scripts/__tests__/render-budget-comment.test.ts \
scripts/__tests__/check-side-effects-array.test.ts \
scripts/__tests__/vite-declared-lazy-views.test.ts \
scripts/__tests__/vite-ineffective-dynamic-imports.test.ts
Test Files 5 passed (5) Tests 207 passed (207)

Those five are every test file in the repo that reads this script's source. The set
matters here because objectui#7046 pins prose against live values —
attachedDocs locates the block attached to each baseline and asserts what it says
about commits and chunk names — so a comment-only edit to these blocks is exactly the
kind that can red them. It does not.

node scripts/check-control-bytes.mjs ✅ 6199 tracked text files
node scripts/check-changeset-presence.mjs ✅ no changeset owed

The changeset checker's own verdict line: "1 file(s) changed, 0 of them published
source of a package the release covers ... no changeset is owed."
No
skip-changeset label is applied — this repo declares with an empty-frontmatter
changeset when one is owed, and here none is.

Not run locally: the rest of the gate farm, which CI runs exactly once anyway.

Fences honoured

No ceiling is raised, lowered, or otherwise moved. MAX_EAGER_CLOSURE_GZIP_BYTES,
BASELINE, PER_CHUNK_GZIP_CEILINGS, PER_CHUNK_BASELINE and
REGRESSION_THIS_GATE_MUST_CATCH_BYTES are byte-identical to origin/main; the diff
is 38 insertions and 22 deletions, all of them inside comments. If 9.7 KB of
aggregate headroom is too tight, that is a different card and a maintainer's call.

Filed, not fixed here

#7528 — two more frozen measurements in this gate's prose that the ruling above does
not cover: "one number over 52 chunks" asserted in the present tense in this file and
in its test (the build measures 50; BASELINE.chunks says 48), and
performance-budget.yml citing "3.15 MB", the ceiling objectui#6776 retired. Out of
scope: neither is an aggregate-headroom sentence, and one lives in another file.
#7528 is not addressed by this PR.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC


Generated by Claude Code

`scripts/check-eager-closure-budget.mjs` carried three different aggregate
headroom figures and none of them was the one in force. 45,996 (header) was
objectui#6683's ceiling minus objectui#6683's baseline, a whole re-baseline
stale. 11,988 (`PER_CHUNK_GZIP_CEILINGS`) was objectui#7399's post-change live
reading frozen into a sentence. 45,686 (`MAX_EAGER_CLOSURE_GZIP_BYTES`) is
arithmetic on the two constants beside it and is correct — but it is the
headroom above the BASELINE, not what the closure has left, and the prose did
not say so. Measured on this branch's base, the gate prints 9.5 KB.
Two more of the same shape: "the aggregate headroom now 44.0 KB"
(`evaluateCeilingFreshness`) was objectui#5924-era, and the "TIGHTER half —
0.21x, 0.08x, 0.04x against the aggregate's 0.47x" ranking in
`PER_CHUNK_BASELINE` had INVERTED (`vendor-objectstack` is 0.20x against the
aggregate's 0.11x today).
Comment-only. Every constant — `MAX_EAGER_CLOSURE_GZIP_BYTES`, `BASELINE`,
`PER_CHUNK_GZIP_CEILINGS`, `PER_CHUNK_BASELINE`,
`REGRESSION_THIS_GATE_MUST_CATCH_BYTES` — keeps its value, and the gate's
verdict is byte-identical before and after.
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)3181.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-CkpJ-S5M.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.19KB117.80KB
core (index.js)6.12KB2.42KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)242.42KB61.26KB
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.15KB13.35KB
plugin-charts (index.js)70.87KB19.72KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.82KB34.64KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.07KB64.12KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.46KB41.06KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)52.71KB14.55KB
plugin-list (index.js)113.33KB27.60KB
plugin-map (index.js)20.55KB6.80KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)9.40KB3.23KB
plugin-view (index.js)85.22KB20.93KB
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)4.63KB2.18KB
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 17:33
@os-project-manager
os-project-manager added this pull request to the merge queueSep 3, 2026
Merged via the queue into main with commit d04e79aSep 3, 2026
32 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-7518-eager-closure-headroom-prose branch September 3, 2026 17:57
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

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

docs(scripts): stop restating the eager-closure headroom in prose - #7529

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-7518-eager-closure-headroom-prose
Sep 3, 2026
Merged

docs(scripts): stop restating the eager-closure headroom in prose#7529
os-project-manager merged 1 commit into
mainfrom
claude/issue-7518-eager-closure-headroom-prose

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes#7518

Comment-only. Every constant keeps its value; the gate's verdict is byte-identical
before and after.

The disposition, and why it is not "write today's number"

The card offered three routes — (a) restate the figure, (b) restate a ratio,
(c) point at the live verdict. The dispatching seat ruled (c), and the argument
is one this file has already proved on itself: a hardcoded headroom here went stale
badly enough to produce three mutually contradictory versions, two of them in the
same file. (a) only winds the clock forward to today; (b) drifts with the baseline.

So the prose now says which line is binding and how to read the current value
(pnpm check:eager-closure) and does not say what it equals.

Same shape as #7517 (f620a26f6), which fixed the ten locale packs' stale headroom
note by naming the ceiling's distance from the baseline it was measured from and
pointing at the same command.

Whole-file census

The card required a survey of the entire file, not only the line it names. Line
numbers are on the pre-change file at f620a26f6.

Aggregate surface — rewritten

sitewhat was writtenwhat it actually wasnow
:50-51"Headroom above the current 3,254,004 bytes: 45,996 (1.41%)"3,300,000 − 3,254,004 — the objectui#6683 ceiling/baseline pair, one full re-baseline stale, and "the current" is a baseline that was replaced by objectui#6776no figure; points at the constant that states it once, and at the live verdict
:54"45,996 < 91,136, so this ceiling would have failed"the same retired figure reused as the proof of the second constraintargues from the policy (headroom is half the regression by construction) instead of a literal
:455"the AGGREGATE line (11,988 bytes of headroom) becomes the binding one"3,268,000 − 3,256,012 — objectui#7399's own post-change live reading frozen into a sentenceno figure; the claim that the aggregate becomes binding stands, the number is read off the gate
:1245-1246"With the aggregate headroom now 44.0 KB against an 89.0 KB regression class"3,345,000 − 3,299,898 = 45,102 — the objectui#5924 pair, two re-baselines back, present tenseno figure
:548-550"they are the TIGHTER half — 0.21x, 0.08x, 0.04x against the aggregate's 0.47x"dated e33b44796 ratios promoted into a present-tense structural claim — and the claim has inverted (below)states that which line is tightest moves with every build and is ranked by the gate's own sensitivity table

Aggregate surface — deliberately kept, with the reason

sitefigurewhy it is not in the class above
:255"Headroom is 45,686 bytes — 0.50x"3,268,000 − 3,222,314: arithmetic on two constants in this file, correct at write time and correct now, and it cannot drift independently of them (the unit test asserts MAX − BASELINE.gzipBytes < REGRESSION). What was missing is that this is headroom above the baseline, not what the closure has left — the sentence now says so and points at the live figure.
:530-531"4,086,000 over the 3,298,620 reading, 787,380 bytes, 8.64x"explicitly a retired claim that the same paragraph goes on to call false. A dated record of a defect, not an assertion about today.
:64-65"~45 KB of room in each direction rather than 66 KB one way and 25 KB the other"arithmetic on REGRESSION_THIS_GATE_MUST_CATCH_BYTES and on the retired ~2% policy it replaced. A statement about the policy; no measurement in it.
:117, :136, :156, :242, :251"~200 KB", "45,102", "75.9 KB = 0.85x"each dated to the card that measured it, inside a narrative that names that card.
:544-547the four-line e33b44796 run tableone dated run, already carrying its own "do not size a re-baseline off these; the answer in force is the table the gate prints on YOUR build". One clause added: the run predates objectui#7399's re-attribution, so its framework row weighs locale catalogues that line no longer holds — without it a reader takes a retired subject for the current one, which is the false rule objectui#7399 was itself written about.

The inverted ranking, measured

:548-550 asserted the three per-chunk lines were tighter than the aggregate. On this
branch's base the gate prints the opposite for one of them, and there are four
per-chunk ceilings now, not three:

aggregate closure headroom 9.5 KB = 0.11x
chunk `vendor-objectstack` headroom 18.2 KB = 0.20x

That is why the replacement asserts no ranking at all.

Per-chunk surface — reported separately, numbers never joined to the aggregate's

The dispatching seat's fence: the three budget surfaces are three things and their
numbers do not go in one sentence. Nothing in this PR joins them.

sitefiguredisposition
:442-446"9,535 bytes (0.10x) for framework, 8,924 (0.10x) for i18n-locales, against the 9,137 (0.10x) the retired framework pair carried"correct, untouched.71,000 − 61,465 = 9,535 and 455,000 − 446,076 = 8,924, both from constants in this file; 9,137 is dated to the retired pair.
:452"8,924 bytes is about sixty translation keys"correct, number kept. It was silent about being headroom above the baseline; it now says so. Its trailing clause was the 11,988 defect and is rewritten.
:346-348, :386, :411, :431per-chunk narrativesdated to their cards. Kept.

Live per-chunk drift is not written into the file — that is the whole ruling —
but for the record, measured on f620a26f6: i18n-locales has spent 1,882 of its
8,924 baseline headroom and ui-components is down to 2,716 bytes (0.03x). The gate
prints all of it.

Verification

Full apps/console build (this file is in performance-budget.yml's runtime
closure, so the gate cannot be judged without one). Both builds and the test run went
through the shared verify lock; wall-clock figures are shared-box seconds.

pnpm turbo run build --filter='./packages/*' --concurrency=2 39 tasks ok
pnpm --filter @object-ui/console build ok
node scripts/check-eager-closure-budget.mjs exit 0

The verdict this PR's new prose points at, printed on f620a26f6:

✅ Console eager closure is 3181.9 KB gzipped across 50 of 518 chunks (budget: 3191.4 KB, headroom: 9.5 KB).
✅ Per-chunk eager budgets (4 chunks weighed):
✅ vendor-objectstack 926.1 KB / 944.3 KB ceiling (headroom 18.2 KB)
✅ i18n-locales 437.5 KB / 444.3 KB ceiling (headroom 6.9 KB)
✅ ui-components 387.0 KB / 389.6 KB ceiling (headroom 2.7 KB)
✅ framework 61.0 KB / 69.3 KB ceiling (headroom 8.3 KB)
✅ Ceiling sensitivity (5 ceilings, each weighed against the report just read):
✅ aggregate closure 3181.9 KB measured / 3191.4 KB ceiling (headroom 9.5 KB = 0.11x 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` 437.5 KB measured / 444.3 KB ceiling (headroom 6.9 KB = 0.08x the 89.0 KB regression) [PER_CHUNK_GZIP_CEILINGS['i18n-locales']]
✅ chunk `framework` 61.0 KB measured / 69.3 KB ceiling (headroom 8.3 KB = 0.09x the 89.0 KB regression) [PER_CHUNK_GZIP_CEILINGS['framework']]
✅ chunk `ui-components` 387.0 KB measured / 389.6 KB ceiling (headroom 2.7 KB = 0.03x the 89.0 KB regression) [PER_CHUNK_GZIP_CEILINGS['ui-components']]

Exactly: eagerGzipBytes = 3,258,288, so the headroom in force is 9,712 bytes.
None of 45,996 / 45,686 / 11,988 is that number — and the figure this seat was handed
in dispatch, taken from CI on a neighbouring commit, was 9,734. Twenty-two bytes
apart, in the same afternoon, is the argument for (c) in one line.

The gate's behaviour did not move

The report is held constant (one build) and the checker is run on the pristine tree
and again on the edited one:

cmp verdict-before.txt verdict-after.txt -> exit 0, identical
md5 both -> 754fcf61b9a33e9e0cf1b92ce644abc3

Negative control, so "identical" is a reading and not a tautology: appending one byte
to a copy makes the same cmp exit 1 at byte 2186. Re-run again at the final commit
2e001f5f2, still byte-identical to the pristine reading.

scripts/check-eager-closure-budget.mjs is not a build input (turbo's console
inputs cover scripts/vite-*.ts, not scripts/check-*.mjs — the argument
BASELINE already makes about its own commit), which is why one build is enough to
hold the report fixed across the edit.

Gates, on 2e001f5f2

pnpm exec vitest run scripts/__tests__/check-eager-closure-budget.test.ts \
scripts/__tests__/render-budget-comment.test.ts \
scripts/__tests__/check-side-effects-array.test.ts \
scripts/__tests__/vite-declared-lazy-views.test.ts \
scripts/__tests__/vite-ineffective-dynamic-imports.test.ts
Test Files 5 passed (5) Tests 207 passed (207)

Those five are every test file in the repo that reads this script's source. The set
matters here because objectui#7046 pins prose against live values —
attachedDocs locates the block attached to each baseline and asserts what it says
about commits and chunk names — so a comment-only edit to these blocks is exactly the
kind that can red them. It does not.

node scripts/check-control-bytes.mjs ✅ 6199 tracked text files
node scripts/check-changeset-presence.mjs ✅ no changeset owed

The changeset checker's own verdict line: "1 file(s) changed, 0 of them published
source of a package the release covers ... no changeset is owed."
No
skip-changeset label is applied — this repo declares with an empty-frontmatter
changeset when one is owed, and here none is.

Not run locally: the rest of the gate farm, which CI runs exactly once anyway.

Fences honoured

No ceiling is raised, lowered, or otherwise moved. MAX_EAGER_CLOSURE_GZIP_BYTES,
BASELINE, PER_CHUNK_GZIP_CEILINGS, PER_CHUNK_BASELINE and
REGRESSION_THIS_GATE_MUST_CATCH_BYTES are byte-identical to origin/main; the diff
is 38 insertions and 22 deletions, all of them inside comments. If 9.7 KB of
aggregate headroom is too tight, that is a different card and a maintainer's call.

Filed, not fixed here

#7528 — two more frozen measurements in this gate's prose that the ruling above does
not cover: "one number over 52 chunks" asserted in the present tense in this file and
in its test (the build measures 50; BASELINE.chunks says 48), and
performance-budget.yml citing "3.15 MB", the ceiling objectui#6776 retired. Out of
scope: neither is an aggregate-headroom sentence, and one lives in another file.
#7528 is not addressed by this PR.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC


Generated by Claude Code

`scripts/check-eager-closure-budget.mjs` carried three different aggregate
headroom figures and none of them was the one in force. 45,996 (header) was
objectui#6683's ceiling minus objectui#6683's baseline, a whole re-baseline
stale. 11,988 (`PER_CHUNK_GZIP_CEILINGS`) was objectui#7399's post-change live
reading frozen into a sentence. 45,686 (`MAX_EAGER_CLOSURE_GZIP_BYTES`) is
arithmetic on the two constants beside it and is correct — but it is the
headroom above the BASELINE, not what the closure has left, and the prose did
not say so. Measured on this branch's base, the gate prints 9.5 KB.
Two more of the same shape: "the aggregate headroom now 44.0 KB"
(`evaluateCeilingFreshness`) was objectui#5924-era, and the "TIGHTER half —
0.21x, 0.08x, 0.04x against the aggregate's 0.47x" ranking in
`PER_CHUNK_BASELINE` had INVERTED (`vendor-objectstack` is 0.20x against the
aggregate's 0.11x today).
Comment-only. Every constant — `MAX_EAGER_CLOSURE_GZIP_BYTES`, `BASELINE`,
`PER_CHUNK_GZIP_CEILINGS`, `PER_CHUNK_BASELINE`,
`REGRESSION_THIS_GATE_MUST_CATCH_BYTES` — keeps its value, and the gate's
verdict is byte-identical before and after.
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)3181.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-CkpJ-S5M.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.19KB117.80KB
core (index.js)6.12KB2.42KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)242.42KB61.26KB
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.15KB13.35KB
plugin-charts (index.js)70.87KB19.72KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.82KB34.64KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.07KB64.12KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.46KB41.06KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)52.71KB14.55KB
plugin-list (index.js)113.33KB27.60KB
plugin-map (index.js)20.55KB6.80KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)9.40KB3.23KB
plugin-view (index.js)85.22KB20.93KB
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)4.63KB2.18KB
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 17:33
@os-project-manager
os-project-manager added this pull request to the merge queueSep 3, 2026
Merged via the queue into main with commit d04e79aSep 3, 2026
32 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-7518-eager-closure-headroom-prose branch September 3, 2026 17:57
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants

@os-project-manager@claude