From 3d257c85a3345986777fed20b6e87c2dc896d744 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 30 Aug 2026 14:29:18 +0000 Subject: [PATCH 1/5] perf(app-shell): take metadata-admin out of the console's eager closure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `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 Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB --- .../6776-metadata-admin-lazy-registration.md | 40 ++++++++ packages/app-shell/package.json | 4 +- packages/app-shell/src/index.ts | 69 ++++++++++---- .../src/services/builtinComponents.tsx | 67 +++++++++++-- .../src/views/metadata-admin/index.ts | 44 ++++----- .../views/metadata-admin/register-builtins.ts | 77 +++++++++++++++ .../vite-declared-lazy-views.test.ts | 30 +++++- scripts/vite-declared-lazy-views.ts | 95 +++++++++++-------- 8 files changed, 327 insertions(+), 99 deletions(-) create mode 100644 .changeset/6776-metadata-admin-lazy-registration.md create mode 100644 packages/app-shell/src/views/metadata-admin/register-builtins.ts diff --git a/.changeset/6776-metadata-admin-lazy-registration.md b/.changeset/6776-metadata-admin-lazy-registration.md new file mode 100644 index 0000000000..17bdd4d2d9 --- /dev/null +++ b/.changeset/6776-metadata-admin-lazy-registration.md @@ -0,0 +1,40 @@ +--- +'@object-ui/app-shell': patch +--- + +Take the metadata-admin engine out of the console's eager closure (objectui#6776). + +`AppContent` has declared six `lazy()` imports of `views/metadata-admin/index.ts` +for a long time, and none of them deferred anything: the module ran five +registrations at load, so the package's published `sideEffects` array named it, +an array entry is unshakeable, and the package barrel re-exported 25 runtime +values from it — an ordinary static edge from an entry every consumer imports. +Every page, preview and inspector under `views/metadata-admin/` was therefore +fetched and parsed before first render. Measured from +`apps/console/dist/eager-closure.json`: **3,254,230 → 3,222,314 gzipped bytes, +−31,916 B**, and the 172,945-byte `metadata-admin` chunk leaves the eager set +entirely. + +**Published surface — two contract-bearing changes, no signature change:** + +- `packages/app-shell/package.json`'s `sideEffects` array now names + `views/metadata-admin/register-builtins` (the new leaf that performs the five + registrations) instead of `views/metadata-admin/index`. The five + registrations still run at package load, bare-imported by the package entry, + so nothing a consumer could observe changes — but the array is a contract + every consumer's bundler reads, so the swap is stated here rather than left + to a diff. +- The package barrel's 25 metadata-admin runtime re-exports (and 11 type-only + ones) now point at their leaf modules. **Same names, same types.** They are + unreachable from outside the package by any other path — `exports` is + root-only — so no import an out-of-package consumer can write is affected. + +`registerAppComponent`'s signature is unchanged. `metadata:directory` and +`metadata:resource` are now registered as `lazy()` values, each wrapping itself +in its own `Suspense` boundary, which is the shape the already-lazy +registrations in `apps/console` use; no render site changes. + +Also re-baselined `MAX_EAGER_CLOSURE_GZIP_BYTES` in the same change, from +3,300,000 to 3,268,000. Taking 31,916 bytes out without moving the ceiling would +leave 0.85x of the 89 KiB regression the gate exists to catch as headroom — +near-blind — so the ratchet advances with the win rather than after it. diff --git a/packages/app-shell/package.json b/packages/app-shell/package.json index 7c4c3ed9a3..c9eae81835 100644 --- a/packages/app-shell/package.json +++ b/packages/app-shell/package.json @@ -12,7 +12,7 @@ "./dist/services/builtinComponents.js", "./dist/views/global-notifications-renderer.js", "./dist/views/global-search-renderer.js", - "./dist/views/metadata-admin/index.js", + "./dist/views/metadata-admin/register-builtins.js", "./dist/views/record-approvals-renderer.js", "./dist/views/record-attachments-renderer.js", "./dist/views/studio-design/studio-canvas-preview.js", @@ -25,7 +25,7 @@ "./src/services/builtinComponents.tsx", "./src/views/global-notifications-renderer.tsx", "./src/views/global-search-renderer.tsx", - "./src/views/metadata-admin/index.ts", + "./src/views/metadata-admin/register-builtins.ts", "./src/views/record-approvals-renderer.tsx", "./src/views/record-attachments-renderer.tsx", "./src/views/studio-design/studio-canvas-preview.tsx", diff --git a/packages/app-shell/src/index.ts b/packages/app-shell/src/index.ts index 6200b1b679..22003e1439 100644 --- a/packages/app-shell/src/index.ts +++ b/packages/app-shell/src/index.ts @@ -309,57 +309,88 @@ import './views/record-approvals-renderer.js'; // `global:notifications`. import './views/global-search-renderer.js'; import './views/global-notifications-renderer.js'; +// The metadata-admin engine's five load-time registrations (built-in anchors, +// default JSONSchemas, the datasource resource, built-in previews, built-in +// inspectors). objectui#6776 moved them OUT of `views/metadata-admin/index.ts` +// into this leaf so the page barrel became shakeable; the bare import lives +// HERE, on the package entry, and must not be moved onto the page barrel — +// `scripts/vite-declared-lazy-views.ts` reads a bare import as "this module is +// not pure" and the whole eager closure comes back. See the leaf's own header. +import './views/metadata-admin/register-builtins.js'; // Phase 3c — generic metadata admin engine. Re-exported so plugins // can call `registerMetadataResource()` to override the per-type // list / edit / create components, and host apps can compose the // page primitives directly when needed. +// +// ⚠️ These 25 runtime re-exports name the LEAF modules, never +// `./views/metadata-admin/index.js` (objectui#6776). The names and their types +// are unchanged — an out-of-package consumer imports exactly what it imported +// before — but a named re-export is an ordinary STATIC EDGE, and the console's +// entry imports this barrel, so pointing them at the page barrel made that +// barrel (and every page, preview and inspector it reaches) eager on every +// console page load, past the six `lazy()` declarations `AppContent` writes for +// it. The 11 TYPE-ONLY re-exports below are erased at build and carry no edge; +// they are grouped separately for that reason and not because they are less +// public. +export { MetadataDirectoryPage } from './views/metadata-admin/DirectoryPage.js'; +export { MetadataResourceRouter } from './views/metadata-admin/ResourceRouter.js'; +export { MetadataResourceListPage } from './views/metadata-admin/ResourceListPage.js'; +export { MetadataResourceEditPage } from './views/metadata-admin/ResourceEditPage.js'; +export { MetadataResourceHistoryPage } from './views/metadata-admin/ResourceHistoryPage.js'; +export { MetadataDiagnosticsPage } from './views/metadata-admin/DiagnosticsPage.js'; +export { MetadataQuickFind } from './views/metadata-admin/QuickFind.js'; +export { PageShell as MetadataPageShell } from './views/metadata-admin/PageShell.js'; +export { SchemaForm } from './views/metadata-admin/SchemaForm.js'; +export { LayeredDiff } from './views/metadata-admin/LayeredDiff.js'; export { - MetadataDirectoryPage, - MetadataResourceRouter, - MetadataResourceListPage, - MetadataResourceEditPage, - MetadataResourceHistoryPage, - MetadataDiagnosticsPage, - MetadataQuickFind, - MetadataPageShell, - SchemaForm, - LayeredDiff, registerMetadataResource, getMetadataResource, listMetadataResources, resolveResourceConfig, +} from './views/metadata-admin/registry.js'; +export { useMetadataClient, useMetadataTypes, useTypesIndex, useGlobalDiagnostics, matchesQuery, +} from './views/metadata-admin/useMetadata.js'; +export { registerMetadataPreview, getMetadataPreview, listMetadataPreviewTypes, +} from './views/metadata-admin/preview-registry.js'; +export { registerMetadataInspector, getMetadataInspector, listMetadataInspectorTypes, -} from './views/metadata-admin/index.js'; +} from './views/metadata-admin/inspector-registry.js'; export type { MetadataResourceConfig, MetadataDomain, - RichMetadataTypeEntry, +} from './views/metadata-admin/registry.js'; +export type { RichMetadataTypeEntry } from './views/metadata-admin/useMetadata.js'; +export type { MetadataPreview, MetadataPreviewProps, MetadataSelection, +} from './views/metadata-admin/preview-registry.js'; +export type { MetadataInspector, MetadataInspectorProps, - // The form authoring surface, in ONE declaration per layer: the field - // (objectui#5040 / #5542) and the two containers above it (objectui#5596). - // `apps/console` renders the same authored `FormView` documents this - // package's metadata-admin does; before it could import these names it kept - // its own hand-written copies of all three shapes. See the note on the - // re-export in `views/metadata-admin/index.ts`. +} from './views/metadata-admin/inspector-registry.js'; +// The form authoring surface, in ONE declaration per layer: the field +// (objectui#5040 / #5542) and the two containers above it (objectui#5596). +// `apps/console` renders the same authored `FormView` documents this package's +// metadata-admin does; before it could import these names it kept its own +// hand-written copies of all three shapes. See the note on the re-export in +// `views/metadata-admin/index.ts`. +export type { FormFieldSpec, FormSectionSpec, FormViewSpec, -} from './views/metadata-admin/index.js'; +} from './views/metadata-admin/form-spec.js'; // Studio WYSIWYG design surface (ADR-0080) — the open-source design surface. // The left AI copilot is an injected `aiSlot`; OSS renders three zones. diff --git a/packages/app-shell/src/services/builtinComponents.tsx b/packages/app-shell/src/services/builtinComponents.tsx index 6e706838f8..6fd42604be 100644 --- a/packages/app-shell/src/services/builtinComponents.tsx +++ b/packages/app-shell/src/services/builtinComponents.tsx @@ -15,12 +15,9 @@ * metadata type. */ +import { lazy, Suspense } from 'react'; import { registerAppComponent } from './componentRegistry.js'; -import { - MetadataDirectoryPage, - MetadataResourceRouter, - registerMetadataResource, -} from '../views/metadata-admin/index.js'; +import { registerMetadataResource } from '../views/metadata-admin/registry.js'; import { PermissionMatrixEditPage } from '../views/metadata-admin/PermissionMatrixEditor.js'; import { PackagesPage } from '../views/metadata-admin/PackagesPage.js'; import { PackagedAutomationPage } from '../views/setup/PackagedAutomationPage.js'; @@ -33,18 +30,74 @@ import { /* 1) Top-level admin pages — bound to `metadata:directory` + `metadata:resource` */ /* -------------------------------------------------------------------------- */ +/** + * ⚠️ TRAP — read this before "just making something here lazy" (objectui#6776). + * + * The two registrations below hold their pages behind `lazy()` + `Suspense`, and + * that is ONE HALF of a two-part change. The other half is that + * `packages/app-shell/src/index.ts` re-exports the metadata-admin names from + * their LEAF modules rather than from `views/metadata-admin/index.ts`, and that + * the five load-time registrations moved to + * `views/metadata-admin/register-builtins.ts`. Doing the `lazy()` here WITHOUT + * the other half is the smallest, most in-fence-looking version of this change, + * and it is worth almost nothing: + * + * measured, this file's `lazy()` alone − 30 B off the eager closure, + * + 189 B on the `metadata-admin` + * chunk, which stays EAGER + * + * ...and every gate stays GREEN while it does. The `ineffective-dynamic-import` + * ledger (`scripts/vite-ineffective-dynamic-imports.ts`) cannot see it, because + * the static edge that defeats the `import()` does not live in this module at + * all — it lives in the package barrel's re-export. That is the objectui#5486 + * shape: code that CLAIMS a code split it does not have, with a loading fallback + * no user can ever reach. + * + * So: a `lazy()` in this file is only ever true when nothing in the package's + * EAGER graph still names the same module statically. Check the barrel first, + * and measure from `apps/console/dist/eager-closure.json` and the emitted + * chunk's own module list — never from a source-level search, which cannot see + * chunk co-tenancy (objectui#6680, objectui#6681). + */ +const MetadataDirectoryPage = lazy(() => + import('../views/metadata-admin/index.js').then((m) => ({ default: m.MetadataDirectoryPage })), +); +const MetadataResourceRouter = lazy(() => + import('../views/metadata-admin/index.js').then((m) => ({ default: m.MetadataResourceRouter })), +); + +function MetadataAdminFallback({ label }: { label: string }) { + return
Loading {label}…
; +} + +/** + * The Suspense boundary lives INSIDE the registration value, which is the shape + * every other lazy `registerAppComponent` entry already uses + * (`apps/console/src/registerAccountComponents.tsx`, + * `registerDeveloperComponents.tsx`, `registerApprovalsComponents.tsx`). It + * keeps `registerAppComponent`'s published signature unchanged — a component + * VALUE, as before — and it means no render site has to learn about pending. + */ registerAppComponent({ ref: 'metadata:directory', label: 'All Metadata Types', source: '@object-ui/app-shell', - component: MetadataDirectoryPage, + component: (props: any) => ( + }> + + + ), }); registerAppComponent({ ref: 'metadata:resource', label: 'Metadata Resource', source: '@object-ui/app-shell', - component: MetadataResourceRouter, + component: (props: any) => ( + }> + + + ), }); registerAppComponent({ diff --git a/packages/app-shell/src/views/metadata-admin/index.ts b/packages/app-shell/src/views/metadata-admin/index.ts index d0a2697f83..10edaac8c5 100644 --- a/packages/app-shell/src/views/metadata-admin/index.ts +++ b/packages/app-shell/src/views/metadata-admin/index.ts @@ -66,30 +66,26 @@ export type { MetadataAnchor, } from './registry.js'; -// Side-effect: register the built-in anchor relationships so the Related -// tab works out of the box for objects (hooks, views, pages, …). -import { registerBuiltinAnchors } from './anchors.js'; -registerBuiltinAnchors(); - -// Side-effect: register fallback JSONSchemas for the 12 writable types -// so the generic SchemaForm renders a real form (vs raw-JSON fallback) -// until the framework wires Zod→JSONSchema generation into /meta/types. -import { registerDefaultMetadataSchemas } from './default-schemas.js'; -registerDefaultMetadataSchemas(); -import { registerDatasourceResource } from './datasource/register.js'; -registerDatasourceResource(); - -// Side-effect: register built-in Preview-tab renderers (page, view, -// dashboard, report, app, object, email_template). Plugins can add or -// override entries via `registerMetadataPreview()`. -import { registerBuiltinPreviews } from './previews/index.js'; -registerBuiltinPreviews(); - -// Side-effect: register built-in scoped inspectors (dashboard widget, -// …). Plugins can add or override entries via -// `registerMetadataInspector()`. -import { registerBuiltinInspectors } from './inspectors/index.js'; -registerBuiltinInspectors(); +/** + * ⛔ NO LOAD-TIME REGISTRATION BELONGS IN THIS FILE (objectui#6776). + * + * The five built-in registrations (`registerBuiltinAnchors`, + * `registerDefaultMetadataSchemas`, `registerDatasourceResource`, + * `registerBuiltinPreviews`, `registerBuiltinInspectors`) used to run here. + * They now live in `./register-builtins.js`, which the PACKAGE ENTRY + * (`packages/app-shell/src/index.ts`) bare-imports; that file carries the + * reasoning. In one line: a module that registers at load time is named by + * `@object-ui/app-shell`'s published `sideEffects` array, an array entry is + * unshakeable, and the package barrel re-exports 25 runtime values from HERE — + * so a registration in this file drags every page, preview and inspector under + * `views/metadata-admin/` into the console's eager closure, past the six + * `lazy()` boundaries `AppContent` declares for it. + * + * That also rules out the tidy-looking version of the same mistake: a bare + * `import './register-builtins.js';` on THIS module. `bareSideEffectImport` in + * `scripts/vite-declared-lazy-views.ts` reads that line and refuses to declare + * this barrel pure, which puts the closure straight back. + */ export { registerMetadataPreview, diff --git a/packages/app-shell/src/views/metadata-admin/register-builtins.ts b/packages/app-shell/src/views/metadata-admin/register-builtins.ts new file mode 100644 index 0000000000..576060a086 --- /dev/null +++ b/packages/app-shell/src/views/metadata-admin/register-builtins.ts @@ -0,0 +1,77 @@ +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * The metadata-admin engine's five LOAD-TIME registrations, in a leaf module of + * their own (objectui#6776). + * + * ## Why they are not in `./index.ts` any more + * + * They used to sit at the bottom of the directory barrel, and that placement — + * not the registrations themselves — is what held 172,945 gzipped bytes of + * metadata-admin in the console's EAGER closure. The mechanism, in two links: + * + * 1. a module that registers at load time cannot be tree-shaken, so + * `@object-ui/app-shell`'s published `sideEffects` array named + * `views/metadata-admin/index.ts` (objectui#6683) and every bundler kept + * it whole; and + * 2. the package barrel (`packages/app-shell/src/index.ts`) re-exported 25 + * runtime values FROM that barrel, and the console's entry imports the + * package barrel statically. A named re-export is an ordinary static edge, + * so the unshakeable module — and its whole import closure, every page, + * preview and inspector under `views/metadata-admin/` — was reachable + * eagerly even though `AppContent` declares six `lazy()` imports of it. + * + * Neither link can be cut where it is observed. Deleting the registrations is + * not on the table: they are load-bearing (drop them and the Related tab, the + * generic SchemaForm, the datasource resource, and every built-in Preview and + * Inspector go missing), and `scripts/vite-declared-lazy-views.ts` carries a + * guard that refuses to declare the module pure for exactly that reason. + * + * So the two concerns are SPLIT instead. This module is the side-effectful half + * and nothing re-exports through it; `./index.ts` is the pure re-export half and + * registers nothing. The package entry bare-imports THIS file, which keeps all + * five registrations exactly as eager as they were, while `./index.ts` becomes + * shakeable and reaches the browser only through the `lazy()` boundaries that + * always claimed to defer it. + * + * ## ⛔ Do not attach this import to the page barrel + * + * The bare import belongs to the PACKAGE ENTRY (`packages/app-shell/src/index.ts`). + * Putting it back on `./index.ts` — even as `import './register-builtins.js';` — + * re-creates the defect under a new name: `bareSideEffectImport` in + * `scripts/vite-declared-lazy-views.ts` reads that line and refuses to declare + * the barrel pure, so the barrel is unshakeable again and the closure returns. + * + * ## Ordering + * + * These five are independent of one another and of `./index.ts`: each writes + * into a registry module that holds a plain `Map`, and no registrar reads + * another's table at module scope. The order below is the order they were + * written in the barrel, kept so a `git log -p` of the move reads as a move. + */ + +import { registerBuiltinAnchors } from './anchors.js'; +import { registerDefaultMetadataSchemas } from './default-schemas.js'; +import { registerDatasourceResource } from './datasource/register.js'; +import { registerBuiltinPreviews } from './previews/index.js'; +import { registerBuiltinInspectors } from './inspectors/index.js'; + +// Register the built-in anchor relationships so the Related tab works out of +// the box for objects (hooks, views, pages, ...). +registerBuiltinAnchors(); + +// Register fallback JSONSchemas for the 12 writable types so the generic +// SchemaForm renders a real form (vs raw-JSON fallback) until the framework +// wires Zod->JSONSchema generation into /meta/types. +registerDefaultMetadataSchemas(); + +registerDatasourceResource(); + +// Register built-in Preview-tab renderers (page, view, dashboard, report, app, +// object, email_template). Plugins can add or override entries via +// `registerMetadataPreview()`. +registerBuiltinPreviews(); + +// Register built-in scoped inspectors (dashboard widget, ...). Plugins can add +// or override entries via `registerMetadataInspector()`. +registerBuiltinInspectors(); diff --git a/scripts/__tests__/vite-declared-lazy-views.test.ts b/scripts/__tests__/vite-declared-lazy-views.test.ts index d4088c98e0..7cb4a66c4e 100644 --- a/scripts/__tests__/vite-declared-lazy-views.test.ts +++ b/scripts/__tests__/vite-declared-lazy-views.test.ts @@ -188,17 +188,22 @@ describe('declaredSideEffectful', () => { expect(declaredSideEffectful(['./src/**/*.css'], 'src/views/Alpha.tsx')).toContain('glob'); }); - it('agrees with the real package: metadata-admin/index.ts is declared side-effectful', () => { + it('agrees with the real package: register-builtins.ts is declared side-effectful', () => { // The subject. This is the file whose FIVE top-level registration calls // `bareSideEffectImport` cannot see, so without this guard the plugin would // declare it pure the moment someone deleted its ledger line - // (objectui#6681). - const owner = nearestPackage('packages/app-shell/src/views/metadata-admin/index.ts', REPO_ROOT); + // (objectui#6681). objectui#6776 MOVED those five calls out of the page + // barrel and into this leaf; the guard's job and its blind spot are + // unchanged, only the file that carries them. + const owner = nearestPackage( + 'packages/app-shell/src/views/metadata-admin/register-builtins.ts', + REPO_ROOT, + ); expect(owner?.packageJsonPath).toBe('packages/app-shell/package.json'); - expect(owner?.packageRelative).toBe('src/views/metadata-admin/index.ts'); + expect(owner?.packageRelative).toBe('src/views/metadata-admin/register-builtins.ts'); const manifest = JSON.parse(read(owner!.packageJsonPath)) as { sideEffects?: unknown }; expect(declaredSideEffectful(manifest.sideEffects, owner!.packageRelative)).toBe( - './src/views/metadata-admin/index.ts', + './src/views/metadata-admin/register-builtins.ts', ); // The positive control in the same query shape: a declared-lazy module the // array does NOT name, so a matcher that answered "side-effectful" to @@ -208,6 +213,21 @@ describe('declaredSideEffectful', () => { ).toBeNull(); }); + it('the page barrel is NOT declared side-effectful any more (objectui#6776)', () => { + // The other half of the move, stated as a pin rather than left to the + // ledger: `views/metadata-admin/index.ts` is what the console's six + // `lazy()` declarations name, and it is shakeable ONLY while the published + // `sideEffects` array does not name it. Re-adding it there — or putting a + // bare `import './register-builtins.js';` back on the barrel, which + // `bareSideEffectImport` reads as the same claim — puts 172,945 gzipped + // bytes back into every console page load, silently. + const owner = nearestPackage('packages/app-shell/src/views/metadata-admin/index.ts', REPO_ROOT); + expect(owner?.packageJsonPath).toBe('packages/app-shell/package.json'); + const manifest = JSON.parse(read(owner!.packageJsonPath)) as { sideEffects?: unknown }; + expect(declaredSideEffectful(manifest.sideEffects, owner!.packageRelative)).toBeNull(); + expect(bareSideEffectImport(read('packages/app-shell/src/views/metadata-admin/index.ts'))).toBeNull(); + }); + it('the source-reading guard is blind to it, which is why this one exists', () => { // Stated as a test rather than a comment: if `bareSideEffectImport` ever // learns to see top-level calls, this expectation flips and the reader is diff --git a/scripts/vite-declared-lazy-views.ts b/scripts/vite-declared-lazy-views.ts index e1971ad302..f5c61f53de 100644 --- a/scripts/vite-declared-lazy-views.ts +++ b/scripts/vite-declared-lazy-views.ts @@ -104,7 +104,7 @@ import type { Plugin, Rollup } from 'vite'; * * | chunk | gz eager | mechanism | * |----------------------------|----------|------------------------------------| - * | `metadata-admin` | 172,651 | real static edges — PINNED below | + * | `metadata-admin` | 172,651 | real static edges — FIXED, #6776 | * | `MarketplacePackagePage` | 7,647 | chunk co-tenancy — FIXED | * | `MarketplaceInstalledPage` | 1,836 | chunk co-tenancy — FIXED | * | `MarketplacePage` | 0 | already lazy (the control) | @@ -178,52 +178,64 @@ export const EAGER_WALK_CONTROL = 'packages/app-shell/src/views/ObjectView.tsx'; * (`scripts/__tests__/vite-declared-lazy-views.test.ts` checks that, and that * every entry still names a file that exists). * - * Two entries stand, and neither stands for the barrel re-export objectui#6535 - * removed: + * ONE entry stands, and it does not stand for the barrel re-export + * objectui#6535 removed: * * - `RecordDetailView` — a real static edge. * `packages/app-shell/src/views/ObjectView.tsx` imports it by name, and * `ObjectView` sits in AppContent's own "eagerly loaded — always needed" * block. Splitting it would mean giving `ObjectView` a lazy boundary. * - * - `views/metadata-admin/index.ts` — the LARGEST single entry this ledger has - * ever carried (172,651 bytes gzipped, 144 modules, 5.3% of the whole eager - * closure) and the one most likely to be "fixed" by someone who has not read - * why it stands. It is the target of SIX `lazy()` declarations in AppContent - * and it is statically imported by two modules that are eager by - * construction, both measured from the emitted chunk's module list on - * `b98352a15` (objectui#6681): - * - * 1. `packages/app-shell/src/index.ts` — the package barrel, which - * re-exports eleven runtime values from it (`registerMetadataPreview`, - * `useMetadataClient`, …). The console's entry imports that barrel. - * 2. `packages/app-shell/src/services/builtinComponents.tsx` — which the - * barrel BARE-imports (`import './services/builtinComponents.js';`) for - * its ComponentRegistry registrations, and which imports - * `MetadataDirectoryPage` and `MetadataResourceRouter` from this module - * BY VALUE. A registry entry that names a component must hold the - * component. - * - * Neither edge is removable inside a bundling change, and the module cannot - * be declared pure: it performs FIVE top-level registrations at module load - * (`registerBuiltinAnchors`, `registerDefaultMetadataSchemas`, - * `registerDatasourceResource`, `registerBuiltinPreviews`, - * `registerBuiltinInspectors`), which is why - * `@object-ui/app-shell`'s own `sideEffects` array names it (objectui#6683). - * ⚠️ Those five are CALLS, not bare imports, so {@link bareSideEffectImport} - * returns `null` for this file — the guard that actually refuses to declare - * it pure is {@link declaredSideEffectful}, reading the package's own array. - * - * The six pages the declarations name (`DirectoryPage`, `StudioHomePage`, - * `ResourceListPage`, `ResourceEditPage`, `ResourceHistoryPage`, - * `DiagnosticsPage`) have NO dynamic importer of their own in the emitted - * graph — they are reached only through this barrel's static re-exports — - * so no chunking policy separates them from it. Making this lazy means - * changing what `registerAppComponent` accepts (a component VALUE today) and - * what the package barrel re-exports; that is a published-contract decision, - * not a bundling one, and it is recorded as such rather than attempted here. - * - * ## Two entries were REMOVED here, and that removal is a recorded win + * ## `views/metadata-admin/index.ts` was the third removal — objectui#6776 + * + * It was the LARGEST single entry this ledger ever carried: 172,945 bytes + * gzipped over 144 modules, 5.3% of the eager closure, the target of SIX + * `lazy()` declarations in AppContent that deferred nothing. Two static edges + * held it, both measured from the emitted chunk's module list (objectui#6681): + * + * 1. `packages/app-shell/src/index.ts` — the package barrel re-exported 25 + * runtime values from it (`registerMetadataPreview`, `useMetadataClient`, + * …; the earlier note here said eleven, which counted only part of the + * list and omitted the 11 type-only names that carry no edge at all — + * corrected by objectui#6785). The console's entry imports that barrel. + * 2. `packages/app-shell/src/services/builtinComponents.tsx` — which the + * barrel BARE-imports for its ComponentRegistry registrations, and which + * imported `MetadataDirectoryPage` and `MetadataResourceRouter` from this + * module BY VALUE. A registry entry that names a component must hold the + * component. + * + * The note that stood here said neither edge was removable inside a bundling + * change, and that was right — the repair was NOT a bundling change. Under the + * maintainer ruling of 2026-08-30 (objectui#6776), three things moved together: + * + * - the five top-level registrations (`registerBuiltinAnchors`, + * `registerDefaultMetadataSchemas`, `registerDatasourceResource`, + * `registerBuiltinPreviews`, `registerBuiltinInspectors`) moved to + * `views/metadata-admin/register-builtins.ts`, bare-imported by the PACKAGE + * ENTRY, so they stay exactly as eager as they were while the page barrel + * stops being side-effectful. `@object-ui/app-shell`'s `sideEffects` array + * names the new leaf instead of the barrel; + * - the package barrel's 25 runtime re-exports now name the LEAF modules, + * same names and same types, which removes edge 1; and + * - `builtinComponents.tsx` registers the two pages as `lazy()` values, each + * behind its own `Suspense` inside the registration value, which removes + * edge 2 without touching `registerAppComponent`'s signature. + * + * ⚠️ The `lazy()` in `builtinComponents.tsx` is the half that looks sufficient + * and is not: on its own it is worth −30 bytes, GROWS the `metadata-admin` + * chunk, leaves it EAGER, and passes every gate — including + * `scripts/vite-ineffective-dynamic-imports.ts`, which cannot see a static edge + * that lives in another module. That trap is written up beside the code in + * `builtinComponents.tsx`. + * + * ⛔ What has NOT changed: the five registrations are still load-bearing and + * still unshakeable. {@link declaredSideEffectful} — not + * {@link bareSideEffectImport}, which returns `null` for a top-level CALL — + * is the guard that refuses to declare their module pure, and it now reads + * `register-builtins.ts` out of the package's own array. Moving the bare import + * back onto the page barrel would re-arm the whole defect under a new name. + * + * ## Two entries were REMOVED here BEFORE that, and both removals are wins * * `RecordFormPage` and `ReportView` were pinned for a third reason, the one no * `grep` over the source shows: CHUNK CO-TENANCY (objectui#6680). Rolldown @@ -246,7 +258,6 @@ export const EAGER_WALK_CONTROL = 'packages/app-shell/src/views/ObjectView.tsx'; */ export const DECLARED_LAZY_VIEWS_STILL_EAGER: readonly string[] = Object.freeze([ 'packages/app-shell/src/views/RecordDetailView.tsx', - 'packages/app-shell/src/views/metadata-admin/index.ts', ]); /** From 45e999eff21947e04b031f3f308829fcfa6b5ddc Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 30 Aug 2026 14:31:37 +0000 Subject: [PATCH 2/5] perf(console): re-baseline the eager-closure ceiling onto the post-#6776 payload MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB --- scripts/check-eager-closure-budget.mjs | 59 +++++++++++++++++++------- 1 file changed, 43 insertions(+), 16 deletions(-) diff --git a/scripts/check-eager-closure-budget.mjs b/scripts/check-eager-closure-budget.mjs index 186ff45012..314b15e0d3 100644 --- a/scripts/check-eager-closure-budget.mjs +++ b/scripts/check-eager-closure-budget.mjs @@ -138,6 +138,26 @@ * reopening the same day it was measured shut. The two numbers move in ONE * commit for the reason the paragraph above gives. * + * objectui#6776 lowered it a third time, to 3,268,000 over 3,222,314, and this + * one was earned the same way. `views/metadata-admin/index.ts` stopped being a + * registering module — its five load-time registrations moved to a leaf the + * PACKAGE ENTRY bare-imports — so the `sideEffects` array stopped naming it, the + * package barrel's 25 runtime re-exports were re-pointed at leaf modules, and + * the whole `metadata-admin` chunk (172,945 gzipped bytes, 144 modules) left the + * eager closure: 3,254,230 -> 3,222,314, −31,916 bytes, measured on two full + * console builds. The maintainer ruling of 2026-08-30 made the re-baseline part + * of the change rather than a follow-up, in its own words: + * + * ⛔ ceiling 处置写死(不作实施者临场判断):-31KB 把余量推到 ~0.89x 门禁 + * 89KB 回归阈值(近盲),同批重设 `MAX_EAGER_CLOSURE_GZIP_BYTES`;抬 ceiling + * 是有申报程序的 ratchet,裁决原话引入 PR 正文。 + * + * Measured, the drift was 0.85x rather than 0.89x — the gate printed + * `headroom 75.9 KB = 0.85x the 89.0 KB regression` on the post-change build + * before this constant moved. Either way it is the blind band reopening, and the + * direction of this edit is DOWN: no build that passed before it and measures + * under 3,268,000 fails after it. + * * ⚠️ Read the direction correctly: the closure did NOT fall by the 242.6 KB the * objectui#6683 card projected. That figure was measured for * `"sideEffects": false`, which is closed by measurement because it also DROPS @@ -157,12 +177,12 @@ * become an excuse to widen it — a ceiling that rises while the sensitivity * relaxes is a gate quietly retiring itself. * - * This is a truthful CURRENT-STATE ceiling, not a target. 3.15 MB gzipped + * This is a truthful CURRENT-STATE ceiling, not a target. 3.07 MB gzipped * before first render is a bad payload, and the honest long-term line is far * below it — but lowering the line to a TARGET is a separate decision with its * own work behind it (objectui#5324 names the candidates), and re-baselining * onto a fresh measurement is not that. Nothing here should be read as a - * finding that 3.19 MB is acceptable. + * finding that 3.12 MB is acceptable. * * ## Per-chunk ceilings (objectui#5490) * @@ -210,10 +230,10 @@ import { isEntrypoint } from './invoked-as.mjs'; /** * Ceiling for the console eager closure, in gzipped bytes. See the header for - * how this number was chosen; measured 3,254,004 on `bd2a7ec50`. + * how this number was chosen; measured 3,222,314 on `3d257c85a`. * - * Re-baselined DOWNWARD twice, each time toward a measurement the payload had - * already fallen to: + * Re-baselined DOWNWARD three times, each time toward a measurement the payload + * had already fallen to: * * - objectui#5924, from 4,086,000 (derived from the 4,005,911 reading on * `4c1623c0c`) to 3,345,000 over 3,299,898 on `48e53814e`. @@ -224,10 +244,17 @@ import { isEntrypoint } from './invoked-as.mjs'; * at 1.00x its own sensitivity and, on the next byte of shrink, tripped the * exit-2 verdict about the gauge. Lowering it in the SAME change is the * tightening the maintainer ruling of 2026-08-29 asked for. - * - * Headroom is 45,996 bytes — 0.50x {@link REGRESSION_THIS_GATE_MUST_CATCH_BYTES}. + * - objectui#6776, to 3,268,000 over 3,222,314. The metadata-admin engine's + * five load-time registrations moved out of the page barrel, so the barrel + * stopped being named by the `sideEffects` array and the 172,945-byte + * `metadata-admin` chunk left the eager closure (−31,916 bytes). The + * 3,300,000 ceiling was measured carrying 75.9 KB of headroom afterwards — + * 0.85x the regression this gate must catch, the blind band reopening — and + * the 2026-08-30 ruling made moving it part of the same change. + * + * Headroom is 45,686 bytes — 0.50x {@link REGRESSION_THIS_GATE_MUST_CATCH_BYTES}. */ -export const MAX_EAGER_CLOSURE_GZIP_BYTES = 3_300_000; +export const MAX_EAGER_CLOSURE_GZIP_BYTES = 3_268_000; /** * The measurement the ceiling above was derived from. Exported so the two @@ -240,17 +267,17 @@ export const BASELINE = Object.freeze({ /** * `emitEagerClosureReport`'s `eagerGzipBytes` on this commit. * - * `bd2a7ec50` is the commit that carries the array and the gates; this - * constant was written one commit later, and the two trees differ ONLY by - * this recorded identifier. That is safe to state rather than hope: the + * `3d257c85a` is the commit that carries the metadata-admin split + * (objectui#6776); this constant was written one commit later, and the two + * trees differ ONLY by this file. That is safe to state rather than hope, and + * it is the same argument the previous baseline (`bd2a7ec50`) made: the * console build's turbo `inputs` cover `scripts/vite-*.ts`, not - * `scripts/check-*.mjs`, so nothing in this file reaches the bundler. The - * figure was re-measured on the later commit and came back identical. + * `scripts/check-*.mjs`, so nothing in this file reaches the bundler. */ - gzipBytes: 3_254_004, + gzipBytes: 3_222_314, chunks: 48, - totalChunks: 513, - commit: 'bd2a7ec50', + totalChunks: 517, + commit: '3d257c85a', }); /** From fe5eead139ac37ffc268bf567724f998ba1e47d7 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 30 Aug 2026 14:47:48 +0000 Subject: [PATCH 3/5] docs(app-shell): put the measured E2 figures in the trap note, not the predicted ones MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB --- .../src/services/builtinComponents.tsx | 31 +++++++++++++------ scripts/vite-declared-lazy-views.ts | 12 ++++--- 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/packages/app-shell/src/services/builtinComponents.tsx b/packages/app-shell/src/services/builtinComponents.tsx index 6fd42604be..11b6035435 100644 --- a/packages/app-shell/src/services/builtinComponents.tsx +++ b/packages/app-shell/src/services/builtinComponents.tsx @@ -42,16 +42,29 @@ import { * the other half is the smallest, most in-fence-looking version of this change, * and it is worth almost nothing: * - * measured, this file's `lazy()` alone − 30 B off the eager closure, - * + 189 B on the `metadata-admin` - * chunk, which stays EAGER + * Measured on `fab4802e3` — a full console build of that commit with ONLY this + * file's two registrations turned into `lazy()` values, against the same + * commit unmodified: * - * ...and every gate stays GREEN while it does. The `ineffective-dynamic-import` - * ledger (`scripts/vite-ineffective-dynamic-imports.ts`) cannot see it, because - * the static edge that defeats the `import()` does not live in this module at - * all — it lives in the package barrel's re-export. That is the objectui#5486 - * shape: code that CLAIMS a code split it does not have, with a loading fallback - * no user can ever reach. + * 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) + * the chunk itself still EAGER + * + * So it does not merely fail to pay — it costs bytes in both places, and it is + * the `lazy()`/`Suspense` scaffolding itself that it spends them on. And every + * gate stays GREEN while it does: that build exits 0, the + * `ineffective-dynamic-import` ledger prints its usual 43 pinned entries with no + * 44th, and `declared-lazy-views` prints "2 eager, all pinned". The ledger + * cannot see this because the static edge that defeats the `import()` does not + * live in this module at all — it lives in the package barrel's re-export. That + * is the objectui#5486 shape: code that CLAIMS a code split it does not have, + * with a loading fallback no user can ever reach. + * + * (The ruling that ordered this comment predicted −30 B and +189 B. The + * direction of the chunk growth and the green gates reproduced; the closure + * figure did not, and it came back POSITIVE. The measured numbers are the ones + * above.) * * So: a `lazy()` in this file is only ever true when nothing in the package's * EAGER graph still names the same module statically. Check the barrel first, diff --git a/scripts/vite-declared-lazy-views.ts b/scripts/vite-declared-lazy-views.ts index f5c61f53de..5cc79a1641 100644 --- a/scripts/vite-declared-lazy-views.ts +++ b/scripts/vite-declared-lazy-views.ts @@ -222,11 +222,13 @@ export const EAGER_WALK_CONTROL = 'packages/app-shell/src/views/ObjectView.tsx'; * edge 2 without touching `registerAppComponent`'s signature. * * ⚠️ The `lazy()` in `builtinComponents.tsx` is the half that looks sufficient - * and is not: on its own it is worth −30 bytes, GROWS the `metadata-admin` - * chunk, leaves it EAGER, and passes every gate — including - * `scripts/vite-ineffective-dynamic-imports.ts`, which cannot see a static edge - * that lives in another module. That trap is written up beside the code in - * `builtinComponents.tsx`. + * and is not. Measured on `fab4802e3` with ONLY that half applied: the eager + * closure went UP by 211 bytes, the `metadata-admin` chunk went up by 396, the + * eager chunk count did not move, the chunk stayed EAGER — and the build exited + * 0 with this plugin printing "2 eager, all pinned" and + * `scripts/vite-ineffective-dynamic-imports.ts` printing its usual 43, because + * neither can see a static edge that lives in another module. That trap is + * written up beside the code in `builtinComponents.tsx`. * * ⛔ What has NOT changed: the five registrations are still load-bearing and * still unshakeable. {@link declaredSideEffectful} — not From ae9f949acc3f3f0cff4e6f74c2bf4bc682fa726d Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 30 Aug 2026 14:55:43 +0000 Subject: [PATCH 4/5] test(scripts): re-pin the rendered baseline figure after the #6776 re-baseline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB --- scripts/__tests__/check-eager-closure-budget.test.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/scripts/__tests__/check-eager-closure-budget.test.ts b/scripts/__tests__/check-eager-closure-budget.test.ts index b3ac408d64..a602f15baa 100644 --- a/scripts/__tests__/check-eager-closure-budget.test.ts +++ b/scripts/__tests__/check-eager-closure-budget.test.ts @@ -470,10 +470,11 @@ describe('ceiling sensitivity, judged live (objectui#5924)', () => { ]); // A passing run still prints every measurement, so a reader watching a // ceiling drift upward sees it coming rather than the day it reds. The - // literal is `BASELINE.gzipBytes` rendered, re-taken when objectui#6683 - // re-baselined it downward — a rendering derived in the test would agree - // with the renderer by construction and pin nothing. - expect(result.message).toContain('3177.7'); + // literal is `BASELINE.gzipBytes` rendered, re-taken each time the baseline + // moves (objectui#6683 down to 3177.7, objectui#6776 down to 3146.8) — a + // rendering derived in the test would agree with the renderer by + // construction and pin nothing. + expect(result.message).toContain('3146.8'); }); it('is exactly one regression wide, from either side of the line', () => { @@ -648,7 +649,7 @@ describe('main', () => { expect(code).toBe(0); expect(outputs.closure_status).toBe('pass'); expect(outputs.closure_chunks).toBe('5'); - expect(outputs.closure_gzip_kb).toBe('3177.7'); + expect(outputs.closure_gzip_kb).toBe('3146.8'); }); it('exits 1 — a verdict about the BUNDLE — when over budget', () => { From 17f7b0784f046c672eacfd337a914e885702ffa6 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 30 Aug 2026 15:49:29 +0000 Subject: [PATCH 5/5] docs(app-shell): mark the E2 closure delta sign-unstable in both trap 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 Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB --- packages/app-shell/src/services/builtinComponents.tsx | 9 +++++++++ scripts/vite-declared-lazy-views.ts | 7 +++++++ 2 files changed, 16 insertions(+) diff --git a/packages/app-shell/src/services/builtinComponents.tsx b/packages/app-shell/src/services/builtinComponents.tsx index 11b6035435..2caa771099 100644 --- a/packages/app-shell/src/services/builtinComponents.tsx +++ b/packages/app-shell/src/services/builtinComponents.tsx @@ -66,6 +66,15 @@ import { * figure did not, and it came back POSITIVE. The measured numbers are the ones * above.) * + * ⚠️ And a second, INDEPENDENT rebuild of the same variant disagreed with that + * closure figure on its SIGN: −7 B where the run above measured +211 B. Both + * stand as what their run measured; together they say only that this delta is + * small and sensitive to the exact byte-form of the edit, so the sign is not a + * finding and neither is the "it costs bytes" reading of it. What both + * rebuilds reproduced identically IS the finding: the chunk stays EAGER and the + * eager chunk count stays 45 of 513 (build exit 0, every gate green). Cite + * those two, never a signed byte delta. + * * So: a `lazy()` in this file is only ever true when nothing in the package's * EAGER graph still names the same module statically. Check the barrel first, * and measure from `apps/console/dist/eager-closure.json` and the emitted diff --git a/scripts/vite-declared-lazy-views.ts b/scripts/vite-declared-lazy-views.ts index 5cc79a1641..6dc67961ae 100644 --- a/scripts/vite-declared-lazy-views.ts +++ b/scripts/vite-declared-lazy-views.ts @@ -230,6 +230,13 @@ export const EAGER_WALK_CONTROL = 'packages/app-shell/src/views/ObjectView.tsx'; * neither can see a static edge that lives in another module. That trap is * written up beside the code in `builtinComponents.tsx`. * + * Read that 211 as a magnitude, not a direction: an INDEPENDENT rebuild of the + * same variant measured the closure delta at -7 bytes. Both figures stand as + * what their run measured, and the disagreement is the point — the delta is + * small and sensitive to the byte-form of the edit, so its sign is not a + * finding. The finding is the pair that reproduced on both rebuilds: the chunk + * stayed EAGER and the eager chunk count did not move (45 of 513). + * * ⛔ What has NOT changed: the five registrations are still load-bearing and * still unshakeable. {@link declaredSideEffectful} — not * {@link bareSideEffectImport}, which returns `null` for a top-level CALL —