perf(console): the first screen stops loading the spec entries only the metadata designers validate with; the eager-closure ceiling comes down by what that recovered (objectui#11101) - #11342
Conversation
…tatic closure, so an import()ed spec entry stays lazy (objectui#11101) `vendor-objectstack` is a static import of the console entry, and an `advancedChunks` group claims by module id, not by reachability, so every `@objectstack/*` module it claimed was on every page load, including the spec's `/ai` and `/integration` entries that only the metadata designers' client validation reaches, through `await import()` in `clientValidation.ts`. `tags: ['$initial']` restricts the group to the modules rolldown tags as statically reachable from the entry; a vendor module behind an `import()` now follows its importer into a lazy chunk. Nothing the first screen runs moves: `$initial` is the entry's static closure. The group-table pin in the budget's unit test records the tag. Claude-Session: https://claude.ai/code/session_01TdiauJaVCHuj45EzZGUxHh Co-authored-by: Claude <noreply@anthropic.com>
…i#11101 recovered, the first payback of the 17.5.0 loan (objectui#11101) Two console builds of one tree (`5262f7d`, and `4acbea07` = that tree plus the `tags: ['$initial']` line) weigh 3,671,724 and 3,638,104 gzipped bytes: -33,620 in aggregate, -33,735 in `vendor-objectstack`, the rest import bookkeeping. Under objectui#11088 decision 1 = A the raise was a loan, so: - `MAX_EAGER_CLOSURE_GZIP_BYTES` 3,694,000 -> 3,660,380 (exactly -33,620), `BASELINE` re-pinned onto the `4acbea07` reading; - `vendor-objectstack`'s ceiling 1,723,000 -> 1,689,265 (exactly -33,735), `PER_CHUNK_BASELINE` with it. Not re-derived as reading + half a regression: that would absorb main's drift since `048e7f6` into the ceiling. The live headroom main had before the payback is kept to the byte on both lines (0.24x / 0.20x). `048e7f6`'s owed squash handle (`81f8498`, objectui#11086) is back-filled in the prose that still cites that reading; `BASELINE.squashMerge` is null again, owed for `4acbea07` once this merges (the field's own rule). The near-ceiling `framework` fixture pair now holds the aggregate at BASELINE by construction, since the aggregate's constant headroom is now the narrower one. Patch changeset for `@object-ui/console`, whose published `dist` changes chunk layout. Claude-Session: https://claude.ai/code/session_01TdiauJaVCHuj45EzZGUxHh Co-authored-by: Claude <noreply@anthropic.com>
|
changeset-claim-re-read
|
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Fixes #11101
Clause-②: no. Load timing only; no contract, export or accept set moves, and validation is unchanged once loaded.
The console-side payback of the objectui#11088 decision 1 = A loan (the
@objectstack/*17.5.0 ceiling raise of PR #11086). Implemented byos-devfor thedomain:devxseat 2 claim5924908810, sessionhttps://claude.ai/code/session_01TdiauJaVCHuj45EzZGUxHh.Measured first
Every
@objectstack/specentry the console bundles sits in one chunk,vendor-objectstack, and that chunk is a static import of the entry. Read on a console build ofmainat5262f7dwith a throwaway module-graph dump (not committed). "gzip alone" is each module's rendered code gzipped by itself: an attribution, not additive, and larger than the module's share of the chunk.@objectstack/spec@object-ui/core,@object-ui/auth,@objectstack/formula, metadata-adminview-item-normalize.ts/systemviews/FlowRunner.tsx(runtime), metadata-adminmetadata-form-i18n.ts,previews/doc-draft.ts/kernel@object-ui/data-objectstack(runtime), preview and designer modules/data/ui/sharedpreview/DraftChangesPanel.tsx(throughConsoleLayout's draft preview bar),inspectors/ObjectFieldInspector.tsx,previews/simulator/flow-sim-validate.ts/api/automationutils/approverIdentity.ts(runtime,DeclaredActionsBar) plus metadata-admin inspectors/aiclientValidation.ts, throughawait import()/security/identityinspectors/FlowReferenceField.tsx, eager throughregister-builtins.ts/integrationclientValidation.ts, throughawait import()/contracts@objectstack/sdui-parser(not spec)⇒ The hypothesis held only in part.
clientValidation.tsalready loads every spec entry throughawait import(). What kept/aiand/integrationon the first screen was thevendor-objectstackchunk group: a group claims by module id, not by reachability. That is the objectui#5266 mechanism, one package over; the lookaheads inVENDOR_OBJECTSTACK_TESTonly ever excluded@objectstack/lint. Every other entry the validator loads is also imported statically by something on the first screen, so no lazy import in the validator can move it.The change
apps/console/vite.config.ts: thevendor-objectstackgroup getstags: ['$initial']. That is rolldown's built-in tag for "statically reachable from an entry", so the group now claims only the entry's static closure. A vendor module that sits behind animport()follows its importer into a lazy chunk. Nothing the first screen executes moves, because$initialIS that closure. The lint lookaheads stay. A group-table pin in the budget's unit test records the tag.scripts/check-eager-closure-budget.mjs: the ceilings come down by exactly what was recovered (below). The owedsquashMergehandle for048e7f6is back-filled, and the near-ceilingframeworkfixture pair holds the aggregate atBASELINEby construction.@object-ui/console, whose publisheddistchanges chunk layout.check-changeset-presenceowes none, because no published source moved.Eager closure, before and after
Two builds of one tree, one container, one instrument (
CI=true pnpm exec vite buildinapps/console, readingapps/console/dist/eager-closure.json), in oneos-verify-lock.shhold. The trees differ only in the vite.config line; the before leg put5262f7d's blob on disk and the after leg restoredHEAD's, each proven by blob hash.5262f7d(main)4acbea07(this PR)vendor-objectstack, gzipai,integration,contractsNoise: the instrument is deterministic per tree. The untagged config read 3,671,724 on three builds (the graph-dump build, the canonical before leg, the ablation's mutated leg), and the tagged config read 3,638,104 on three (the graph-dump build, the canonical after leg, the ablation's restored leg). The recovered 33,620 is 0.37x the 89 KiB regression the gate exists to catch.
The ceiling: lowered by the recovered amount
MAX_EAGER_CLOSURE_GZIP_BYTESBASELINE048e7f64acbea07PER_CHUNK_GZIP_CEILINGS['vendor-objectstack']PER_CHUNK_BASELINE['vendor-objectstack']048e7f64acbea07The ceilings are not re-derived as "reading + half a regression". That would absorb
main's own drift since048e7f6(+22,987 on5262f7d) into the ceiling, which is a raise under another name. The live headroommainhad before the payback is kept to the byte: 22,276 (0.24x) on the aggregate and 18,301 (0.20x) onvendor-objectstack. No build that passed before and measures under 3,660,380 fails after.REGRESSION_THIS_GATE_MUST_CATCH_BYTESand the other three per-chunk rows did not move.squashMerge:048e7f6landed onmainas81f8498(#11086, single parent, an ancestor oforigin/main). That handle is back-filled in the header prose that still cites the048e7f6reading. This change re-pinsBASELINEonto its own reading, so the field isnullagain by its own rule ("the change that re-pins the field above cannot write its own here"). The4acbea07→ squash back-fill is owed once this merges.Ablation: re-eager, and the lowered ceiling goes red
node ../objectstack/scripts/ablation-replace.mjsdroppedtags: ['$initial'](anchor 1 → 0, blob40f6d42c→32c403ab), rebuilt, and ran the gate:node scripts/check-eager-closure-budget.mjsexit 1. The aggregate line read "3585.7 KB … 11.1 KB over the 3574.6 KB budget", andvendor-objectstackread "1664.7 KB / 1649.7 KB ceiling (OVER by 15.1 KB)".HEAD(40f6d42c),git diff HEADempty. The rebuild read 3,638,104 and the gate exit 0.Validation unchanged once a designer opens
vite preview, proxied to a--freshobjectstack 17.5.0 showcase backend started bye2e/live/ci/start-backend.sh, Playwright +/opt/pw-browsers/chromium). The probe ran two builds of the same tree, with and without the tag./apps/showcase_app, 331 JS requests): noai-*orintegration-*chunk is fetched./apps/showcase_app/metadata/agent/new: opening the designer fetchesai-rfegJgG7.js. TypingBad Name!into Name raises "This metadata does not match the spec — 4 validation error(s). Name: Invalid string: must match pattern …"./apps/showcase_app/metadata/connector/new: this designer fetchesintegration-DM--okeM.js, and the 3-error banner shows.GET /api/v1/usage/storage404 from the backend, present on both builds. Nopageerror.packages/app-shell/src/views/metadata-admin/**(373 files): 36 clientValidation / ResourceEditPage / createConformance files plus 1 console file, 398 passed. The other 337 files ran in three chunks: 1066 passed; 1455 passed and 1 skipped; 1462 passed.Verification (head
7d24740c)scripts/__tests__: the 18 files that readapps/console/vite.config.tsor the budget script, 1020 passed. That includescheck-eager-closure-budget.test.tsat 179, plusvite-objectstack-spec-dist,vite-objectstack-client-dist,render-budget-commentandcheck-node-esm-load.apps/consolevitest project: 139 files, 1593 passed.pnpm --filter @object-ui/console run type-checkexit 0. Reverse check:tags: ['$bogus']givestsc -b tsconfig.node.jsonexit 1 with TS2769 at the group, then restored.pnpm --filter @object-ui/app-shell run type-checkexit 0.turbo run build --filter='@object-ui/console^...'.pnpm check:control-bytesOK.node scripts/check-changeset-presence.mjsexit 0.pnpm changeset:checkexit 0.pnpm check:changeset-claimsexit 0, report-only. It shows two pending bodies naming files touched here,6681-declared-lazy-marketplace-routes.mdand7173-ai-pending-actions-inbox-i18n.md. I read both, and this diff falsifies neither.pnpm check:new-line-citations0 new.eslint --no-inline-config --format jsonon the 3 changed code files gives 3 files, 0 errors, 0 warnings.eslint.config.js's population; none was reported ignored.@object-ui/app-shellsuite outsidemetadata-admin/**, because its ~600 remaining files exceed the foreground window on a box shared by three devs.packages/app-shell/srcis untouched by this diff, and CI runs the suite.pnpm lint, which CI owns.Acceptance notes (observations, not filed)
/shared(81,754 gzip alone) and/identity(9,348) are reached on the first screen only by designer code. Their routes in areregister-builtins.ts→inspectors/index.ts/previews/index.ts, which the package entry bare-imports, andConsoleLayout→DraftPreviewBar→DraftChangesPanel. Recovering them means lazy-registering the inspector and preview tables, which is an architecture change, not this card. Carrier: none.DraftChangesPanel.tsx's comment says@objectstack/core(inside@objectstack/client) has a runtime import of@objectstack/spec/sharedthat keeps that subpath eager. In this 17.5.0 bundle,/shared's importers are the three objectui modules above plus the linter. The comment's premise no longer holds. Carrier: none.e2e/live/studio-editor.spec.tswaits ongetByTestId('metadata-edit-page').git grepfinds that string in no other tracked file, and the agent and connector edit pages rendered without it (this probe's first wait on it timed out). Whether the live suite still runs that spec, and how it passes, is NOT MEASURED. Carrier: none.Generated by Claude Code