Skip to content

perf(app-shell): take metadata-admin out of the console eager closure, and re-baseline the ceiling with it - #6913

Draft
os-sam wants to merge 5 commits into
mainfrom
claude/issue-6776-metadata-admin-lazy-registration
Draft

perf(app-shell): take metadata-admin out of the console eager closure, and re-baseline the ceiling with it#6913
os-sam wants to merge 5 commits into
mainfrom
claude/issue-6776-metadata-admin-lazy-registration

Conversation

@os-sam

@os-samos-sam commented Aug 30, 2026

Copy link
Copy Markdown
Collaborator

Fixes#6776

Executes the maintainer ruling of 2026-08-30 (comment 5468951901, batch #4, 「同意」): option B — take the 31 KB — plus the ceiling re-baseline in the same batch. Arm C (the ~141 KB in views/studio-design/, #6795) is untouched.

Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB

Result

Measured from apps/console/dist/eager-closure.json — the artefact, never a source-level search — on two full console builds:

eager closure (gz)eager chunksmetadata-admin chunk
fab4802e3 (base)3,254,23045 / 513172,945 B, EAGER
ae9f949ac (this PR)3,222,31448 / 517not in the eager set
delta−31,916 B+3−172,945 B eager

The ruling projected −31,794 B; the measured figure is −31,916 B, 122 bytes apart. The base reading was taken on fab4802e3, which is not the commit the E1b projection was measured on, so a small difference is expected — reported rather than reconciled.

The metadata-admin chunk still exists; it is now reached only through the six lazy() declarations that always claimed to defer it. Its module list moved too: 144 modules before, 26 after, with the previews/inspectors/anchors/default-schemas half relocating into the eager src chunk (where it belongs — those registrations must still run at load), and the page half staying behind the lazy boundary.

What changed, and why the edges had to be cut in three places at once

AppContent has declared six lazy() imports of views/metadata-admin/index.ts for a long time and none of them deferred anything. Two static edges held it, and the first made the second unremovable:

  1. the module ran five registrations at load, so @object-ui/app-shell's published sideEffects array named it (Should @object-ui/app-shell declare a sideEffects field? Measured at 242.6 KB gzipped — 8x today's console headroom — but it is a published contract whose failure mode is silent #6683) and no bundler could shake it; and
  2. the package barrel re-exported 25 runtime values from it — which the console entry imports statically — and services/builtinComponents.tsx held MetadataDirectoryPage / MetadataResourceRouterby value.

So:

  • the five registrations move to views/metadata-admin/register-builtins.ts, bare-imported by the package entry. They stay exactly as eager as they were; the page barrel stops being side-effectful. ⛔ The bare import must not be attached to the page barrel — bareSideEffectImport in scripts/vite-declared-lazy-views.ts reads that line as "not pure" and declared-lazy-views would refuse it, which is the same defect under a new name.
  • the barrel's 25 runtime re-exports name the leaf modules. Same names, same types.
  • metadata:directory / metadata:resource register lazy() values, each wrapping itself in its own Suspense inside the registration value — the shape apps/console's already-lazy registrations use. registerAppComponent's signature is unchanged and no render site changes.
  • the ledger line for views/metadata-admin/index.ts is deleted, and MAX_EAGER_CLOSURE_GZIP_BYTES is re-baselined.

Re-derived counts (#6785's correction, confirmed): the barrel carries 25 runtime re-exports (each a static edge) and 11 type-only (erased, no edge). Derived mechanically from both trees; the runtime name set and the type name set are byte-identical before and after.

Published surface — clause ② applies by content

Two published contract surfaces move.

1. packages/app-shell/package.json's sideEffects array (#6683's field). One entry pair swaps: views/metadata-admin/indexviews/metadata-admin/register-builtins, in both the ./dist/*.js and ./src/*.ts spellings. The five registrations still run at package load. check:side-effects-array derives the enumeration mechanically and agrees: "@object-ui/app-shell: sideEffects names exactly the 12 module(s) that register at load time, plus its entry forms (27 entries, 434 modules walked)."check:sdui-registration-pins weighs the built console: "All 14 registration(s) a sideEffects array promises are present in the built console (517 chunks weighed; the 3 ruled control(s) are in the derived set)."

2. The barrel's 25 runtime + 11 type-only re-exports. Same names, same types, different source modules.

What an out-of-package consumer could import before, and after: exactly the same set, by exactly the same specifiers. Verified, not assumed:

  • exports is root-only — its keys are "." and "./styles.css", and ./styles.css is a CSS asset, not a JS path. There is no typesVersions. So @object-ui/app-shell/views/metadata-admin/index.js was never resolvable by any consumer, and is not now.
  • grepping the whole workspace for any deep specifier under @object-ui/app-shell returns only @object-ui/app-shell/styles.css occurrences. No JS deep import exists anywhere.

Which module a name is re-exported from is therefore invisible outside the package. The only observable difference is the one the change is for: a consumer's bundler can now shake the page barrel.

⛔ The ceiling was reset in the same batch — the ruling wrote this down rather than leaving it to me

MAX_EAGER_CLOSURE_GZIP_BYTES 3,300,000 → 3,268,000, with BASELINE moved in the same commit so the two checked constraints hold. Measured, the old ceiling carried 75.9 KB of headroom over the new payload — the gate printed it: headroom 75.9 KB = 0.85x the 89.0 KB regression. That is the blind band reopening the day the win lands, which would make the optimisation net-negative: the budget table stops being able to see the next regression.

Ruling, verbatim:

⛔ ceiling 处置写死(不作实施者临场判断):-31KB 把余量推到 ~0.89x 门禁 89KB 回归阈值(近盲),同批重设 MAX_EAGER_CLOSURE_GZIP_BYTES;抬 ceiling 是有申报程序的 ratchet,裁决原话引入 PR 正文。

The direction is down: no build that passed before this edit and measures under 3,268,000 fails after it. Headroom is back to 0.50x the 89.0 KB regression. REGRESSION_THIS_GATE_MUST_CATCH_BYTES is untouched, and the three per-chunk ceilings are deliberately left alone — this change moved none of those chunks. (The measured drift was 0.85x rather than the 0.89x the ruling estimated; recorded in the file.)

⚠️ The E2 trap — verified here, not transcribed

The smallest and most in-fence-looking version of this change is to make the value lazy only in builtinComponents.tsx. Measured on a full console build of fab4802e3 carrying only that edit, against the same commit unmodified:

baseE2-onlydelta
eager closure3,254,2303,254,441+211 B
metadata-admin chunk172,945173,341+396 B
eager chunks45 / 51345 / 513unchanged, still EAGER

Build exit 0. ineffective-dynamic-import printed its usual 43 pinned entries, no 44th; declared-lazy-views printed "2 eager, all pinned". Every gate green, nothing moved, bytes spent on the lazy()/Suspense scaffolding itself. The ledger cannot see it because the static edge that defeats the import() lives in another module — the package barrel's re-export. That is the #5486 shape.

The ruling predicted −30 B and +189 B. The chunk growth, the EAGER verdict and the green gates all reproduced; the closure figure did not — it came back positive. The trap note beside builtinComponents.tsx and the header of scripts/vite-declared-lazy-views.ts both carry the measured numbers and say so.

Ablation — the deleted ledger line is a measurement, not an assertion

Re-pinned packages/app-shell/src/views/metadata-admin/index.ts in DECLARED_LAZY_VIEWS_STILL_EAGERon the fixed tree and rebuilt the console. Prediction: the ledger's missing half fires and the build stops.

ablation console build exit: 1
RolldownError: [declared-lazy-views] 1 pinned view(s) are NO LONGER in the eager closure. …
- packages/app-shell/src/views/metadata-admin/index.ts

Mutation proved on disk before the build (HEAD blob 5cc79a16… vs mutated eaa76737…, anchored line count 0 → 1); restore proved after it (hash back to 5cc79a16…, git diff HEAD empty for the file). The restore leg ran git checkout HEAD -- against the file's ABSOLUTE path, inside a trap on EXIT INT TERMHEAD named explicitly so a path-scoped checkout could not take the mutation back out of a polluted index.

Counter-probe 1 of the same plugin (every declared view must be found in some chunk) ran green on the fixed build, which is what separates "the view became lazy" from "the matcher stopped matching". The plugin's own line moved from 10/12 modules … genuinely lazy; 2 eager, all pinned to 11/12 … 1 eager, all pinned.

Verification

Run against ae9f949ac, the final commit, unless stated.

Builds (2 full console builds for the before/after pair, 1 for E2, 1 for the ablation, 1 final):

  • pnpm turbo run build --filter='./packages/*' then pnpm --filter @object-ui/console build — exit 0.

Gates — all exit 0 (verdict lines quoted from the gates themselves, exit codes captured before any pipe):

  • check:eager-closure✅ Console eager closure is 3146.8 KB gzipped across 48 of 517 chunks (budget: 3191.4 KB, headroom: 44.6 KB). / ✅ aggregate closure 3146.8 KB measured / 3191.4 KB ceiling (headroom 44.6 KB = 0.50x the 89.0 KB regression)
  • check:side-effects-array✅ @object-ui/app-shell: sideEffects names exactly the 12 module(s) that register at load time, plus its entry forms (27 entries, 434 modules walked).
  • check:sdui-registration-pins✅ All 14 registration(s) a sideEffects array promises are present in the built console (517 chunks weighed; the 3 ruled control(s) are in the derived set).
  • check:readme-exports, check:entry-guard, check:self-import, check:phantom-deps, check:control-bytes, check:docs-route-closure, check:i18n-keys, check:i18n-drift, check-changeset-presence — all exit 0.

Tests (from the repo root, per this repo's documented invocation rule):

  • pnpm exec vitest run packages/app-shell/577 files passed, 5674 passed / 1 skipped, exit 0. Run on 3d257c85a+1; the app-shell diff from there to head is comment-only (git diff … -- packages/app-shell/ has no non-comment +/- line).
  • pnpm exec vitest run scripts/__tests__/{vite-declared-lazy-views,check-eager-closure-budget,check-side-effects-array,side-effects-declaration-consistency,vite-ineffective-dynamic-imports,render-budget-comment,check-docs-route-eager-closure,check-sdui-registration-pins}.test.ts255 passed of 256, 1 known unrelated failure (below).
  • pnpm --filter @object-ui/app-shell type-check — exit 0.
  • pnpm --filter @object-ui/app-shell lint — exit 0 (0 errors). pnpm lint:root — exit 0 (0 errors). The 5 new warnings in builtinComponents.tsx (3 × react-refresh/only-export-components, 2 × no-explicit-any) are the same profile the established precedent carries on main: apps/console/src/registerDeveloperComponents.tsx has 7 of exactly these, registerAccountComponents.tsx 3.

Two pin tests moved with the change, and both moves are stated rather than silent:

  • vite-declared-lazy-views.test.ts — the "declared side-effectful" pin now names register-builtins.ts, and a new pin asserts the page barrel is not declared side-effectful and carries no bare side-effect import. That second one is the assertion that makes the shakeability claim testable without a build.
  • check-eager-closure-budget.test.ts — two assertions carry BASELINE.gzipBytes as a rendered literal on purpose (a rendering derived in the test would agree with the renderer by construction and pin nothing). 3177.7 → 3146.8.

Known unrelated failure, already filed:check-sdui-registration-pins.test.ts › "derives the keys from the arrays…" expects the src spelling for mcp:connect-agent and gets the dist one on any tree where packages/app-shell/dist has been built. Proved independent of this diff by removing that single build artefact and re-running: 11/11 pass, then restored. It is #6893.

Landing

Human merge path. The sideEffects array is #6683's published field and the ruling designates it a governed surface for this purpose: "⛔ 治理面(#6683 sideEffects 数组)PR 走人工合并道,不 arm auto-merge." This PR stays draft: auto-merge is not armed, it is not enqueued, and it is not marked ready. Not by me either, green and reviewed.

Generated by Claude Code

`AppContent` declares six `lazy()` imports of `views/metadata-admin/index.ts`
and none of them deferred anything. Two static edges held the module, and the
first one is what made the other unremovable:
1. the module ran FIVE registrations at load, so `@object-ui/app-shell`'s
published `sideEffects` array named it (#6683) and no bundler could shake
it; and
2. the package barrel re-exported 25 runtime values FROM it, which the
console's entry imports statically, and `services/builtinComponents.tsx`
held `MetadataDirectoryPage` / `MetadataResourceRouter` by value.
So every page, preview and inspector under `views/metadata-admin/` was fetched
and parsed before first render — 172,945 gzipped bytes, 144 modules, the
largest entry the declared-lazy ledger ever carried.
Split the two concerns instead of cutting either edge where it was observed:
- the five registrations move to `views/metadata-admin/register-builtins.ts`,
bare-imported by the PACKAGE ENTRY, so they stay exactly as eager as they
were and the page barrel becomes a pure re-export module. The `sideEffects`
array names the new leaf.
- the barrel's 25 runtime re-exports (and 11 type-only ones, which are erased
and carry no edge) name the LEAF modules. Same names, same types.
- `metadata:directory` / `metadata:resource` register `lazy()` values, each
with its own `Suspense` inside the registration value — the shape the
already-lazy registrations in `apps/console` use, so
`registerAppComponent`'s signature is untouched and no render site changes.
Measured from `apps/console/dist/eager-closure.json` on two full builds:
3,254,230 -> 3,222,314 gzipped bytes (-31,916 B); the `metadata-admin` chunk
leaves the eager set entirely (172,945 -> 0) and 45 -> 48 eager chunks.
The ledger line for `views/metadata-admin/index.ts` is deleted — its `missing`
half fails the build until it goes, which is how a win here gets recorded.
The trap is written up beside `builtinComponents.tsx`: doing ONLY the `lazy()`
there is worth -30 B, GROWS the chunk, leaves it EAGER, and passes every gate,
because `ineffective-dynamic-import` cannot see a static edge that lives in
another module. That is the #5486 shape.
Refs #6776
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB
 payload
3,300,000 -> 3,268,000 over a measured 3,222,314 on `3d257c85a`, with
{@link BASELINE} moved in the same commit so the two checked constraints hold.
Taking 31,916 bytes out without moving the ceiling leaves 75.9 KB of headroom —
0.85x the 89 KiB regression this gate exists to catch, which the gate itself
printed on the post-change build. That is the blind band reopening the day the
win lands, and it makes the optimisation net-negative: the budget table stops
being able to see the next regression.
The maintainer ruling of 2026-08-30 on #6776 wrote this down rather than leaving
it to the implementer, verbatim:
⛔ ceiling 处置写死(不作实施者临场判断):-31KB 把余量推到 ~0.89x 门禁 89KB
回归阈值(近盲),同批重设 `MAX_EAGER_CLOSURE_GZIP_BYTES`;抬 ceiling 是有申报
程序的 ratchet,裁决原话引入 PR 正文。
Direction is DOWN: no build that passed before this edit and measures under
3,268,000 fails after it. REGRESSION_THIS_GATE_MUST_CATCH_BYTES is untouched,
and the three per-chunk ceilings are deliberately left alone — this change moved
none of those chunks.
Refs #6776
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB
…e predicted ones
The ruling that ordered this comment predicted the in-fence-only change (making
the value lazy in `builtinComponents.tsx` alone) at -30 B on the eager closure
and +189 B on the `metadata-admin` chunk. Measured it rather than transcribing
it: a full console build of `fab4802e3` carrying ONLY that change, against the
same commit unmodified.
eager closure 3,254,230 -> 3,254,441 B gzipped (+211 B)
`metadata-admin` chunk 172,945 -> 173,341 B gzipped (+396 B)
eager chunk count 45 -> 45 of 513 (UNCHANGED)
Both directions of the qualitative claim reproduced — the chunk grows, it stays
EAGER, and every gate is green (build exit 0, `ineffective-dynamic-import`
prints its usual 43 pinned entries with no 44th, `declared-lazy-views` prints
"2 eager, all pinned"). The closure figure did not: it came back POSITIVE, so
the change costs bytes in both places rather than buying 30. Both write-ups now
carry the measurement and say the prediction differed.
Refs #6776
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB
…-baseline
Two assertions in `check-eager-closure-budget.test.ts` carry `BASELINE.gzipBytes`
as a RENDERED literal — deliberately, so a rendering derived in the test cannot
agree with the renderer by construction and pin nothing. Moving the baseline to
3,222,314 moves that rendering from 3177.7 to 3146.8, so both literals move with
it. The comment now names both re-baselines rather than only #6683's.
Refs #6776
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3147.3 KB3191.4 KB
Main entry chunk (gzip)143.6 KB350 KB
Entry fileindex-BA8NhiEi.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)14.51KB5.35KB
app-shell (runtime-config.js)20.61KB7.35KB
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)512.13KB116.43KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)174.02KB48.28KB
fields (index.js)243.65KB61.63KB
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)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.95KB10.97KB
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)46.92KB12.93KB
plugin-charts (index.js)64.68KB18.35KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)133.48KB34.51KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)245.43KB62.46KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)133.32KB32.69KB
plugin-gantt (index.js)165.23KB40.37KB
plugin-grid (index.js)202.08KB54.61KB
plugin-kanban (index.js)53.14KB14.64KB
plugin-list (index.js)113.15KB27.59KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)28.95KB8.33KB
plugin-tree (index.js)9.00KB3.08KB
plugin-view (index.js)85.83KB21.11KB
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)76.75KB25.49KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.72KB2.24KB
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)3.40KB1.71KB
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-samClaude

Copy link
Copy Markdown
CollaboratorAuthor

Clause-② contract review — verdict: ACCEPT WITH FOLLOW-UP

CONTRACT_REVIEW_TIER clause-② reviewer, dispatched by the domain:ui PM seat (session session_013hfmP9hoMd3dJwTh85J4yB), card objectui#6776. Reviewed at head ae9f949ac (4 commits over merge-base fab4802e3; origin/main at 3b9c774c5), in a detached read-only worktree. Base figures re-derived in a second detached worktree at fab4802e3 — both trees rebuilt from scratch, nothing below is transcribed from the PR body unless marked "on report".

The one follow-up is non-blocking and stated at the bottom. Everything the ruling ordered is in the diff, and everything the PR body claims that I re-measured came back exactly as claimed.

1. Published surface — verified against the emitted dist/index.d.ts, both trees

  • Enumerated every export of packages/app-shell/dist/index.d.ts with the TypeScript compiler API (names, value/type kinds, fully resolved signatures, interfaces expanded property-by-property): 233 exports on both trees, and the two surfaces are byte-identical after normalizing the absolute worktree prefix. No name appears, disappears, changes kind, or changes type.
  • The metadata-admin block counts exactly 25 runtime + 11 type-only re-exports on both sides (counted from the emitted d.ts, confirming finding(build): DECLARED_LAZY_VIEWS_STILL_EAGER's metadata-admin entry says "eleven runtime values" — the runtime block has 25, and eleven is the TYPE-only block beneath it #6785's correction).
  • registerAppComponent emits as (entry: AppComponentRegistryEntry) => void on both sides; no render site is touched (the Suspense lives inside the registration value, matching the registerAccountComponents/registerDeveloperComponents shape).
  • exports is root-only (. + ./styles.css, a CSS asset; no typesVersions), and a workspace-wide grep for deep @object-ui/app-shell/* JS specifiers is empty while its styles.css control hits — so which module a name is re-exported from is genuinely unobservable outside the package.
  • Runtime layer, not just types: dist/index.js:143 bare-imports ./views/metadata-admin/register-builtins.js; the emitted leaf exists (so both sideEffects spellings name real files) and carries all five registration calls; the emitted page barrel dist/views/metadata-admin/index.js carries zero registration calls, with the base tree's barrel as the control that the same grep hits there.

2. The governed half — the sideEffects array is correct, not merely green

  • check:side-effects-array on head prints exactly the quoted sentence ("names exactly the 12 module(s) that register at load time, plus its entry forms (27 entries, 434 modules walked)") and exits 0.
  • The gate judges what its sentence claims — proved by mutation, not prose. In my worktree (restored after, git status clean): (a) deleting the two register-builtins entries → exit 1, MISSING for both spellings, i.e. the gate independently derives that the new leaf registers at load time; (b) re-adding the two old metadata-admin/index entries → exit 1, STALE ("nothing in it registers at load time any more"), i.e. the gate has verified the page barrel is now effect-free. Both failure directions of this exact edit fire.
  • check:sdui-registration-pins on head: "All 14 registration(s) … present in the built console (517 chunks weighed; the 3 ruled control(s) are in the derived set)", exit 0.

3. The measurement — re-derived from the artefact on two independent builds

From apps/console/dist/eager-closure.json and the emitted chunks' module lists (stats.html), never a source-level search:

base fab4802e3 (my build)head ae9f949ac (my build)PR claim
eager closure gz3,254,2303,222,314matches to the byte
eager / total chunks45 / 51348 / 517matches
metadata-admin172,945 B gz, in files[], 144 modulesabsent from files[], 26 modules, 35,339 B gz on diskmatches
Δ−31,916 Bmatches

The zero has its controls: the same files[] query hits MetadataInspector and runtime-metadata-persistence, and metadata-admin-C5OJvVve.js exists on disk — lazy, not deleted. eagerGzipBytes equals the sum over files[]. A post-review rebuild of head reproduced 3,222,314 byte-for-byte. The −31,794 ruling projection vs −31,916 measured (122 B, bases differ) is accepted as reported-not-reconciled per the dispatch.

4. The E2 trap — direction and green-gates claim independently reproduced; one robustness note

I rebuilt fab4802e3 carrying only the head version of builtinComponents.tsx (nothing else):

  • chunk still EAGER, 45/513 unchanged, metadata-admin chunk +183 B (173,128), build exit 0, ineffective-dynamic-import printed its usual 43 pinned entries with no 44th, declared-lazy-views printed "10/12 … 2 eager, all pinned". Every gate green through a change that defers nothing — the claim the trap note is for, reproduced in full.
  • The closure delta in my run was −7 B, not the dev's +211 B. My edit is not byte-identical to the dev's E2 experiment (theirs predates the final file text), and ±~200 B on a 3.25 MB closure is evidently within the variance the byte-form of the edit induces — note my chunk delta (+183) lands near the ruling's +189 prediction while the dev's was +396. So the robust invariant is: an in-fence-only lazy() pays nothing beyond noise in either direction, grows the chunk, leaves it eager, and no gate can see it. The specific "+211 B, costs bytes in both places" is true of the dev's run but its sign does not survive re-derivation from a different byte-form of the same change.

5. The ceiling — arithmetic and rule both check

REGRESSION_THIS_GATE_MUST_CATCH_BYTES = 89*1024 = 91,136. New headroom 3,268,000 − 3,222,314 = 45,686 B = 0.5013x (gate prints "headroom 44.6 KB = 0.50x the 89.0 KB regression", exit 0). The file documents H = REGRESSION / 2 explicitly, and the two prior re-baselines sat at 0.5047x (#6683) and 0.4949x (#5924) — the discipline is real and matched. Had the ceiling stayed at 3,300,000: 77,686 B = 0.852x — the PR's "0.85 x" is the measurement, the ruling's 0.89x was an estimate, and the PR says so. Direction is down; the ruling's ratchet caution concerns raising. REGRESSION_… and the three per-chunk ceilings are untouched, as stated.

6. The ablation — re-run, not just audited

Re-pinned packages/app-shell/src/views/metadata-admin/index.ts in DECLARED_LAZY_VIEWS_STILL_EAGER on the fixed tree myself: blob 5cc79a16…eaa76737… (both hashes match the PR's stated pair exactly — my mutation was byte-identical), rebuild failed exit 1 with the verbatim RolldownError: [declared-lazy-views] 1 pinned view(s) are NO LONGER in the eager closure, restore verified by hash and clean git status. The error fires because the view left the eager closure — the deleted ledger line is a demonstrated win. The restored head tree rebuilds green with the plugin printing "11/12 … 1 eager, all pinned" (moved from 10/12 / 2 eager, as claimed).

7. The known failure is genuinely pre-existing (#6893) — checked, not waved through

Because this PR edits the array, this was treated as a live question: (a) on head with dist built, check-sdui-registration-pins.test.ts fails 1/11, exactly the mcp:connect-agent src-vs-dist spelling; (b) control: the same single failure fires on unmodified base fab4802e3 with dist built; (c) with the dist artefact moved aside, head passes 11/11 (restored after); (d) the diff swaps only the metadata-admin pair within each spelling block — the ConnectAgentWidget entries and the dist-before-src walk order derivePinnedKeys depends on are untouched. Pre-existing, correctly attributed to #6893.

8. The changeset — every figure re-verified; correct

3,254,230 → 3,222,314 / −31,916 B, 172,945 B chunk leaving the eager set, 25 runtime + 11 type-only, root-only exports, six lazy() declarations (AppContent.tsx:63-68), 3,300,000 → 3,268,000, 0.85x — all re-measured above and all correct. Patch bump for a shape-preserving change is consistent with the identical d.ts surface; Changeset Bump Policy / Fixed Group / Declaration checks are all green.

Verified vs taken on report

Re-ran/measured here: both console builds and artefacts; the d.ts surface diff; the three gates (side-effects-array + two mutation controls, sdui-registration-pins, eager-closure); the E2 reproduction; the ablation; ceiling constants and arithmetic; the #6893 controls; gate test files vite-declared-lazy-views / check-eager-closure-budget / check-side-effects-array / side-effects-declaration-consistency162/162; app-shell targeted slice views/metadata-admin/ + services/228 files, 2391 passed / 1 skipped; type-check exit 0; the app-shell diff 3d257c85a..head is comment-only (confirms the suite basis).

NOT MEASURED here: the full pnpm exec vitest run packages/app-shell/ locally — my run hit its 560 s budget, exit 143, which is no reading. Covered instead by CI's four test shards (all green on the merge ref) plus the targeted slice above; no conclusion in this review depends on the local full-suite run. The dev's 577-file/5674-pass figure is taken on report + CI. check:readme-exports, check:entry-guard, check:self-import, check:phantom-deps, check:control-bytes, check:docs-route-closure, i18n gates, and lint were taken from CI (all green), not re-run locally. The dev's E2 figures (+211/+396) are their measured run, taken on report — my independent variant measured −7/+183 (see §4).

CI at review time: all 30 check runs completed — 27 success, 3 skipped (coverage variants, dependabot), zero failures. Test shards 1–4, Type Check, Lint, Build & E2E, Bundle Analysis, Governed Surface Queue Guard: success.

Follow-up (concrete, non-blocking, fixable on this branch)

  1. Make the trap note robust to the sign flip it will meet.builtinComponents.tsx and the vite-declared-lazy-views.ts header both state the E2 closure cost as "+211 B … it costs bytes in both places". An independent E2 re-run (this review, head's exact file text on fab4802e3) got −7 B closure / +183 B chunk — same verdict, different sign on the closure half. A next person who tries their own variant, measures a small negative number, and concludes the note is wrong is exactly the reader this note exists for. Suggested one-line addition to both comments: the closure delta's sign varies with the byte-form of the edit (runs have measured +211 and −7); the invariant is that it never pays more than noise, the chunk grows, the chunk stays EAGER, and every gate stays green. Keep the dev's measured figures and the ruling's prediction as they stand — this adds the third data point rather than replacing the two.

Landing

This PR stays draft and is not mine to close, mark ready, or merge — and not the dev seat's either. Per the ruling's designation of the sideEffects array as a governed surface: "⛔ 治理面(#6683 sideEffects 数组)PR 走人工合并道,不 arm auto-merge."Human merge path only; auto-merge is not armed and must not be. Draft status and the green Governed Surface Queue Guard are consistent with that at review time.


Generated by Claude Code

@os-samClaude

Copy link
Copy Markdown
CollaboratorAuthor

PM: clause ② verdict received — ACCEPT WITH FOLLOW-UP. And the follow-up corrects a call of mine.

domain:ui execution seat, PM session session_013hfmP9hoMd3dJwTh85J4yB.

Both landing constraints are confirmed intact by the review: clause ② was reviewed by content (the
barrel plus the sideEffects array), and the PR stays draft on the human merge path — auto-merge
is not armed and this is ⛔ not mine or the dev seat's to land, green and reviewed or not.

⚠️ My own correction, first, because the follow-up exists because of it

Earlier in this round the dev asked whether to keep the measured E2 figures, and I answered A —
keep them
, adding a reason of my own:

+211 B, a regression, versus the ruling's predicted −30 B — the sign flip strengthens the trap
warning
.

The reviewer rebuilt the E2 variant independently and measured −7 B, not +211 B.

Keeping the figure was right; my inference from its sign was not. The closure delta on this
variant is small and sign-unstable across the byte-form of the edit — so "it came back positive,
therefore the trap is worse than the ruling thought" is not a reading the measurement supports. The
trap's actual substance reproduced completely on both runs, and that half never depended on the
sign:

E2-only, what reproduceddevreviewer
chunk still EAGERyesyes
eager chunks 45 / 513 unchangedyesyes
build exit 0, every gate greenyesyes
ineffective-dynamic-import 43 pinned, no 44thyesyes
declared-lazy-views "2 eager, all pinned"yesyes
closure delta+211 B−7 B

The lesson the trap comment must carry is "the closure barely moves and the module stays eager,
because the static edge that defeats the import() lives in another module"
not a signed byte
figure. A comment that presents ±200 B as the finding invites the next reader to re-derive the sign
and conclude the comment is wrong. That is the same defect class this seat has been closing all
round: a stated measurement outliving the conditions it was taken under.

⇒ I am treating the reviewer's follow-up as required before a human merges, not optional. It is
comment text only and does not touch the change.

What the review established, that I am relying on

Reproduced to the byte rather than taken on report:

  • Published surface — all 233 exports of the emitted dist/index.d.ts enumerated with the
    compiler API on both trees, byte-identical after path normalization. 25 runtime + 11 type-only
    confirmed; registerAppComponent's signature unchanged; exports root-only; zero deep imports
    with the control hitting. dist/index.js:143 bare-imports the leaf with all five calls, and the
    emitted page barrel has 0 — against a base control that hit.
  • The governed half — ⭐ the sideEffects array is proven correct, not merely green: both
    mutation controls fire. Dropping register-builtins (MISSING) and re-adding
    metadata-admin/index (STALE) each exit 1 naming both spellings. A gate that only ever prints ✅
    has not been shown to discriminate; this one has.
  • The headline measurement — both consoles rebuilt from scratch, every figure matching to the
    byte: base 3,254,230 / 45 of 513 / 172,945 gz EAGER / 144 modules; head 3,222,314 / 48 of 517 /
    absent from files[] / 26 modules; Δ −31,916 B. Zeroes controlled, head rebuild deterministic.
  • Ceiling — 45,686 B = 0.5013x of 91,136, against the file's own documented H = REGRESSION / 2
    rule and priors of 0.5047x / 0.4949x. The old ceiling would have carried 0.852x. Direction down.
  • Ablation — re-run by the reviewer: blob pair 5cc79a16…eaa76737… matches the dev's exactly,
    the RolldownError verbatim on exit 1, restore hash-verified.
  • finding(test-infra): check-sdui-registration-pins.test.ts fails on any tree where packages/app-shell/dist exists #6893 — the 1/11 failure reproduces identically on unmodified base with dist built, and
    goes 11/11 with dist aside. The edit never touches the mcp:connect-agent entries or the walk
    order ⇒ genuinely pre-existing, and ⛔ not this PR's to fix.

NOT MEASURED, declared: the full local app-shell suite (exit 143 at the reviewer's 560 s budget —
no reading, neither red nor green). Covered by CI's four green test shards plus a 228-file targeted
slice at 2391 passed. No conclusion above depends on it.

CI at review time: all 30 checks completed — 27 success, 3 skipped, zero failures.

Next

Dispatching the follow-up now: a robustness line on the two trap comments so neither presents a
sign-unstable byte delta as the finding. Comment text only, on this branch. The PR stays draft,
auto-merge stays unarmed, and it remains a human merge.


Generated by Claude Code

… notes
The E2 trap (making the value lazy in `builtinComponents.tsx` alone) is real and
its write-up stays. What does not stand is the SIGN of the closure delta it
quotes. A second, independent rebuild of the same variant measured that delta at
-7 B where the recorded run measured +211 B. Both figures stand as what their run
measured; together they say the delta is small and sensitive to the byte-form of
the edit, so its sign carries no finding.
Everything else reproduced identically on both rebuilds: the chunk stays EAGER,
the eager chunk count stays 45 of 513, the build exits 0, `declared-lazy-views`
prints "2 eager, all pinned" and `ineffective-dynamic-import` prints its usual 43
with no 44th. That pair -- the EAGER verdict and the unmoved 45/513 -- is the
finding, and both notes now say so, so the next reader who re-derives the byte
figure and gets the other sign does not conclude the note is wrong.
Comment text only. No recorded measurement is deleted; the +211 B and +396 B
figures stay exactly where they were. Proven comment-only two ways: all 16
changed lines are added lines inside an existing JSDoc block (0 removed), and
each file's non-comment body is byte-identical to its pre-edit blob
(`builtinComponents.tsx` 5,629 B, sha256 cc0fac46...; `vite-declared-lazy-views.ts`
20,977 B, sha256 c2802841...).
Refs #6776
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB
@os-samClaude

Copy link
Copy Markdown
CollaboratorAuthor

Follow-up commit 17f7b078comment text only, no code.

What changed

Both E2 trap notes now say that the SIGN of the closure delta is not the finding:

  • packages/app-shell/src/services/builtinComponents.tsx — +9 lines, inside the existing JSDoc trap note
  • scripts/vite-declared-lazy-views.ts — +7 lines, inside the file header

Each note keeps the figure it already carried (+211 B closure, +396 B chunk — no recorded measurement was deleted) and adds that a second, INDEPENDENT rebuild of the same E2 variant measured that closure delta at −7 B. Both figures stand as what their run measured; together they say only that this delta is small and sensitive to the byte-form of the edit. What both rebuilds reproduced identically is now stated as the finding: the chunk stays EAGER and the eager chunk count stays 45 of 513 (build exit 0, declared-lazy-views printing "2 eager, all pinned", ineffective-dynamic-import printing its usual 43 with no 44th). The point is that the next reader who re-derives the byte figure and gets the other sign does not conclude the note is wrong.

This is documentation robustness, not a repair. Nothing here was red before the commit and nothing claims it was — there is no failing state a comment edit could start from.

Comment-only proof, two independent ways

1. Line level.git diff -U0 against the parent: 16 added lines, 0 removed. All 16 match comment syntax, and — the stronger check — all 16 fall strictly inside a real comment range reported by the TypeScript scanner (MultiLineCommentTrivia). 16/16 on both checks, 0 outside.

2. Byte level. Each file's non-comment body — the file with exactly the scanner's comment ranges deleted — hashed on both sides:

filenon-comment bytessha256 beforesha256 after
builtinComponents.tsx5,629 → 5,629cc0fac468eb75d94…4bd4beb0cc0fac468eb75d94…4bd4beb0
vite-declared-lazy-views.ts20,977 → 20,977c28028416660b166…49bb0f14c28028416660b166…49bb0f14

Comment-range counts are unchanged in both files (34 and 22); only comment bytes grew, +596 and +462, matching the file totals exactly.

Checks

  • ESLint, post-edit vs. the same two files at their pre-edit blobs (restored with git checkout HEAD~1 --, blob ids verified in both directions — 11b60354…/5cc79a16… going back, 2caa7710…/6dc67961… coming forward — and the restore proved by an empty git status, not by an exit code): identical. 0 errors in both runs; the same 5 pre-existing warnings in builtinComponents.tsx (3 × react-refresh/only-export-components, 2 × no-explicit-any), same rule ids, same messages, shifted by exactly +9 lines. vite-declared-lazy-views.ts: 0 errors / 0 warnings in both runs.
  • pnpm --filter @object-ui/app-shell type-checkexit 0, after pnpm --filter '@object-ui/app-shell^...' build (exit 0) built the dependency closure, so this is a real green and not a TS2307 false red from an unbuilt closure. --listFilesOnly confirms builtinComponents.tsx is one of the 3,815 files in that program.
  • pnpm type-check:scriptsexit 0; --listFiles confirms scripts/vite-declared-lazy-views.ts is in that program.
  • pnpm exec vitest run scripts/__tests__/vite-declared-lazy-views.test.ts26 passed of 26: the edited script's own pin suite, run because the diff touches a gate script.
  • pnpm check:control-bytes — gate verdict line: check-control-bytes: OK (scanned 5752 tracked text file(s); skipped 85 binary).

Lint was narrowed to the two touched files rather than run repo-wide, and the narrowing is declared: eslint.config.js enables no type-aware linting (no projectService, no parserOptions.project), so no rule reads across files, and the non-comment bodies are byte-identical — no untouched file's verdict can move. CI runs the full farm regardless.

No changeset (nothing release-relevant), nothing under content/docs/releases/, no gate constant, ceiling, default or test logic touched. The PR is still a draft, auto-merge is not armed and it is not enqueued.

Generated by Claude Code


Generated by Claude Code

@os-samClaude

Copy link
Copy Markdown
CollaboratorAuthor

PM: the review's follow-up is applied at 17f7b0784. The clause-② verdict carries forward — here is why, stated rather than assumed.

domain:ui execution seat, PM session session_013hfmP9hoMd3dJwTh85J4yB.

The reviewer's ACCEPT WITH FOLLOW-UP was measured on ae9f949ac. The head is now 17f7b0784,
so the verdict does not automatically transfer and I am not treating it as if it did. It transfers
here on two specific grounds:

  1. The delta is comment text only, proven at the byte level rather than by eye: with the
    TypeScript scanner's comment ranges deleted, each file's non-comment body is byte-identical
    across the change — builtinComponents.tsx 5,629 B → 5,629 B, sha256 cc0fac46… unchanged;
    vite-declared-lazy-views.ts 20,977 B → 20,977 B, sha256 c2802841… unchanged. Real non-empty
    hashes on both sides. ⇒ Every measurement the reviewer reproduced to the byte — the 233 exports,
    the mutation-controlled sideEffects array, both console builds, the ablation — reads the same
    bytes it read before.
  2. The change is the reviewer's own requested follow-up, not a new edit for it to re-examine.

⭐ And it is worth saying what the follow-up actually was, since a "one-line robustness note" undersells
it: the two trap comments were presenting a sign-unstable byte figure as the finding. Both now
keep the +211 B they measured (⛔ no recorded measurement was deleted), cite the reviewer's
opposite-signed −7 B from an independent rebuild, say the delta is small and variant-sensitive,
and state that the finding is the EAGER verdict plus the unmoved 45-of-513 chunk count. That is
the durable half, and it is the half that reproduced on both runs.

Two things the follow-up did that were not asked for and were right

  • It refused a false RED rather than reporting one. The fresh worktree had no dist in any of
    types / core / react / components / auth / data-objectstack / app-shell, so an app-shell type-check
    would have produced TS2307 noise that reads as a real failure. It built the dependency closure
    first (192 s) and then type-checked green — and proved the green actually saw the edited file
    with tsc --listFilesOnly (3,815 paths, builtinComponents.tsx among them) rather than assuming
    the exclude list.
  • It ran the gate script's own suite, because the diff touches a gate script — 26 tests, green —
    and then checked that suite for header-text pins, confirming it asserts on parsed AppContent /
    package.json content and never on this file's own comment text. ⇒ The addition cannot move it.
    That is the difference between "the tests passed" and "I established the tests could have caught
    this".

No NOT MEASURED at all this run: no exit 143, no exit 99, no PREREQUISITE NOT MET, no TS2307 false
RED. The shared verify lock was free on all five acquisitions (0 s waited).

⛔ Landing state — unchanged, and deliberately so

draft: true · auto-merge: not armed · not enqueued · not marked ready. The push was a
fast-forward ae9f949ac..17f7b0784 — ⛔ no rebase, no amend, no force-push. This PR remains a
human merge: the ruling designates the sideEffects array a governed surface for this purpose,
and it is not mine or the dev seat's to land, green and reviewed or not.

⚠️CI must go green on 17f7b0784 before a human merges. The 30 checks that came back clean were
on ae9f949ac; that sha is now stale and its result does not speak for this head. I am watching the
new run and will say so here when it is terminal.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3147.8 KB3191.4 KB
Main entry chunk (gzip)143.6 KB350 KB
Entry fileindex-D2x0dBxt.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)14.51KB5.35KB
app-shell (runtime-config.js)20.61KB7.35KB
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)512.13KB116.43KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)175.69KB48.80KB
fields (index.js)243.65KB61.63KB
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)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.95KB10.97KB
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)46.92KB12.93KB
plugin-charts (index.js)64.68KB18.35KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)133.48KB34.51KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)245.43KB62.46KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)133.32KB32.69KB
plugin-gantt (index.js)165.23KB40.37KB
plugin-grid (index.js)202.08KB54.61KB
plugin-kanban (index.js)53.14KB14.64KB
plugin-list (index.js)113.15KB27.59KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.05KB8.37KB
plugin-tree (index.js)9.00KB3.08KB
plugin-view (index.js)85.83KB21.11KB
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)76.75KB25.49KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.72KB2.24KB
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)3.40KB1.71KB
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-samClaude

Copy link
Copy Markdown
CollaboratorAuthor

The re-baselined ceiling holds after main moved — the drift question, answered with a reading

The budget bot ran on 17f7b0784 and reports PASS: eager closure 3147.8 KB across 48
chunks
, budget 3191.4 KB.

Two things that reading confirms independently of the PR's own numbers:

  1. The new ceiling is live and enforcing. 3191.4 KB is MAX_EAGER_CLOSURE_GZIP_BYTES = 3,268,000 B,
    not the old 3,300,000. The gate is measuring against the value this PR sets.
  2. 48 chunks, matching the PR's claimed post-change 48 of 517 (base was 45 of 513) — so the
    metadata-admin chunk really is out of the eager set on the head a merge would take.

⚠️ The headroom moved, and it moved for a reason worth stating

I raised this earlier as an open risk: this PR's figures were measured on base fab4802e3, and main
has since taken several merges (#6082, #6905, #6911, #6912). If that drift ate the headroom, the
re-baselined ceiling would fail on the merge result rather than on the PR head.

It did move, slightly, and in the expected direction:

at ae9f949ac (reviewer-verified)at 17f7b0784 (bot, this run)
headroom45,686 B = 44.6 KB≈ 44,650 B = 43.6 KB
as a fraction of the 91,136 B regression threshold0.5013x0.490x

⚠️ The byte figures on the right are derived from the bot's rounded KB output, so treat them as ±1 KB,
not exact. The direction is what matters and it is not in doubt: ~1 KB of headroom was consumed by
what landed on main, not by this PR — the follow-up commit was comment-only, with both files'
non-comment bodies proven byte-identical.

⇒ The ceiling is now marginally tighter than the file's own H = REGRESSION / 2 rule rather than
looser. That is the conservative side — less headroom means the gate catches the next regression
sooner, not later — so it needs no adjustment and I am not proposing one. Re-baselining a ceiling
because ordinary base drift nudged it is how a budget stops being a budget.

No action. Recording it because a human merges this PR, and "the ceiling was chosen on a base that
has since moved" is exactly the thing that should be checked before that happens rather than after.


Generated by Claude Code

@os-samClaude

Copy link
Copy Markdown
CollaboratorAuthor

CI is terminal green on 17f7b0784. This PR is now complete on every axis except the human merge.

I said above that the 30 green checks were on ae9f949ac, that the sha was stale, and that I would
report here when the new run finished. It has.

Reading at head 17f7b0784f046c672eacfd337a914e885702ffa6:

count
total check runs30
success27
skipped (never ran)3
failure / cancelled / timed out0
still in progress0

The three skipped are Test (coverage), Test (coverage shard …) and dependabot — conditional
jobs, NOT MEASURED rather than green, none of them a required gate. All four Test (shard N/4)
succeeded, as did Type Check, Lint, Build & E2E, Bundle Analysis and every doc/changeset gate.

Standing state, so a human merging this does not have to reconstruct it

axisstatus
clause ② contract reviewACCEPT WITH FOLLOW-UP — every measurement reproduced to the byte, the governed sideEffects array proven correct by mutation controls rather than by a green tick
the review's one follow-upapplied at 17f7b0784, comment-only, non-comment bodies proven byte-identical
CI on the current headterminal green, above
eager-closure budget on the current headPASS — 3147.8 KB against the re-baselined 3191.4 KB ceiling, 48 chunks
ceiling vs base driftheadroom ≈ 43.6 KB ≈ 0.490x of the 91,136 B threshold — tighter than the file's own H = REGRESSION / 2 rule after main moved, i.e. the conservative side. No adjustment proposed
#6893 (the 1/11 check-sdui-registration-pins failure)pre-existing, reproduced on unmodified base with dist built, already filed. ⛔ Not this PR's

Still not armed, and still not mine to land. The ruling designates the sideEffects array a
governed surface for this purpose and routes this PR to a human merge. It stays a draft with
auto-merge unarmed until a human takes it — green, reviewed, and complete is not the same as merged,
and this seat does not close that gap.

One consequence worth naming for whoever picks it up: #6795 (arm C of #6776, the ~141 KB in
views/studio-design/) is deliberately held behind this PR, because both would re-baseline the same
eager-closure ceiling and touch the same sideEffects array. It unblocks when this lands.


Generated by Claude Code

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants

@os-sam@claude