Skip to content

feat(app-shell): the built-in metadata designers leave the console's first load (objectui#11939 step 2) - #11982

Merged
objectstack-fleet[bot] merged 12 commits into
mainfrom
claude/issue-11939-lazy-designers
Oct 9, 2026
Merged

objectstack-fleet[bot] merged 12 commits into
mainfrom
claude/issue-11939-lazy-designers

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #11939

Clause-②: yes

Step 2 of objectui#6795's order, as ruled on objectui#11942 (item C, comment 6056819248): the built-in metadata designers leave the console's first load. Step 1 (a835c31, PR objectui#11963) made their three registries observable, so a reader that rendered before a designer was registered re-renders when it is; this step relies on that. Neither refused shape of ruling A on objectui#11798 is used: there is no per-route await and no lazy sub-entry — importing the package still registers every built-in designer with no host action.

Implemented by a dispatched os-dev (session https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8), claim 6060604189 as amended by the seat after report 6062475046.

The ruling, and the ceiling it pins

The ceiling is ruled: record 6070754914 on objectui#11939 (director-seat batch #294 item 3, letter D), the maintainer's 「11939 上线适当多预留一些没问题,不要一直因为这个改代码」. Its operative paragraph, verbatim:

D — the conventional rebaseline. The PR that moves the designers out of the first load (objectui#11982) pins MAX_EAGER_CLOSURE_GZIP_BYTES to the landing head's measured first load plus half a regression (45,568 B), the formula the gate already documents for a re-pin, and re-pins BASELINE in the same change. The docblock states what the margin is for: catching an 89 KB-class regression, with room for the copy-level and fix-level churn main takes every day, ⛔ not an allowance to be spent on a feature. The 5,120 B Studio allowance of objectui#11942 ends with this re-pin (its reason is gone), and the drift it absorbed is recorded in the docblock, not reclaimed by a lower ceiling. This amends item 3 of 6056819248 (objectui#11942), whose literal formula would land below the measured figure, against the gate's own rule.

Pinned in scripts/check-eager-closure-budget.mjs, in one commit (8d5100ed):

gzip bytes
main at 28557855 (control) 3,391,416
this branch at f05f015 (the reading) 3,235,899
bytes that left 155,517
half a regression (89 KiB / 2) 45,568
MAX_EAGER_CLOSURE_GZIP_BYTES: 3,391,484 → 3,281,467
BASELINE: 3,375,344 on 406f760 → 3,235,899 on f05f015 (289 of 2474 chunks)
headroom over BASELINE 45,568 = 0.50x the regression
  • The new docblock entry ("RE-BASELINED WHEN THE BUILT-IN DESIGNERS LEFT THE FIRST LOAD, ON EXPLICIT MAINTAINER RULING (objectui#11939)") carries: the ruling verbatim with its record id; the standing rule the record draws from it; what the margin is for, in the ruling's words; the bytes that left (chunk table and module groups); and the end of objectui#11942's allowance.
  • The drift the allowance absorbed, recorded and not reclaimed. main read 3,385,979 on 1c51e973, the commit that landed the allowance, which is 385 B under the pre-allowance line of 3,386,364. It read 3,391,416 on 28557855: +5,437 in all, 5,052 B of it above that line and still eager after the designers leave. The literal objectui#11942 formula would land 5,052 B under this branch's reading. The same gap was 4,057 B on d7e9e9ab in report 6062475046, before main moved.
  • The allowance entry gains one pointer line: its scheduled reclaim was amended by this ruling.
  • The constant's re-baseline history gains a fifth downward line, and the unit test's negative control phrase follows it.
  • Per-chunk rows: none invalidated, none moved. Across the two builds vendor-objectstack moved −6,468 (1,599,283 against its 1,623,000 ceiling, 0.26x), ui-components −19, framework +10 and i18n-locale-en 0. The PER_CHUNK_BASELINE prose now names f05f015 as the aggregate's commit.
  • Unit-test invariants hold: MAX greater than BASELINE.gzipBytes, and headroom (45,568) under one regression (91,136).

pnpm check:eager-closure on this branch's build: "✅ aggregate closure 3160.1 KB measured / 3204.6 KB ceiling (headroom 44.5 KB = 0.50x the 89.0 KB regression)", every per-chunk line ✅, exit 0.

Bundle Analysis on head 6db39695 (merge ref on main 5bc55c0c) reads "✅ aggregate closure 3160.0 KB measured / 3204.6 KB ceiling (headroom 44.5 KB = 0.50x the 89.0 KB regression)", with every per-chunk line, membership and ceiling freshness ✅.

When each built-in becomes available

before (main) after (this PR)
built-in previews, scoped inspectors, default inspectors registered while the package entry is evaluated (bare import of register-builtins.ts) registered when the chunk the entry loads with a dynamic import() arrives: the entry's module scope runs import('./views/metadata-admin/register-builtin-designers.js').then(({ registerBuiltinDesigners }) => registerBuiltinDesigners()), with a .catch that logs one console.error
Related-tab anchors, fallback JSONSchemas, datasource resource during the entry's evaluation unchanged — their registries are not observable, so a late arrival would never reach a reader; they stay in register-builtins.ts
Studio canvas preview for object (studio-canvas-preview.tsx, the fourth registry) during the entry's evaluation unchanged — kept eager: 2,943 rendered bytes in the entry chunk, reached by the entry's named re-exports, not moved by this change, so it needs no observability

Between a reader's first render and the chunk's arrival, every reader shows its existing "designer missing" state, then the designer, with no reload (step 1's hooks). A plain get* / list* read right after importing the package can find no built-in yet; that is stated in the changeset. No export is added, removed or changed.

How a host's own registration keeps precedence

The built-ins register inside registerAsBuiltIns (module-internal, in preview-registry.ts): during that pass the shared store fills only a type that has no entry, in all three observable registries. So:

  • host registers first (right after importing the package, before the chunk lands): its component is kept, the built-in for that type is skipped — the case objectui#11798 measured being overwritten under a naive lazy door;
  • host registers after the built-ins landed: register* replaces the built-in and readers re-render, last-write-wins as always.

previews/index.ts and inspectors/index.ts are untouched; they still call the public register*, and only the pass changes what a registration may replace.

The sideEffects diff

None. register-builtin-designers.ts performs no load-time registration: it exports one function, which the entry's .then calls, so every bundler keeps it by its USE. Measured alternative (the dispatch's suggested route — a side-effectful module named in the array, both spellings): check:side-effects-array exits 1 with "STALE ./dist/views/metadata-admin/register-builtin-designers.js — the array names it, but nothing in it registers at load time", because the gate walks the entry's static edges only. register-builtins.ts keeps its two entries (it still registers three things at load time).

Measurements

Instrument: the console's apps/console/vite.config.ts through Vite build(), with one read-only chunk-module dump plugin, under os-verify-lock.sh, all in the same container. It is calibrated against the ordinary CI=true pnpm exec vite build, which wrote the same eagerGzipBytes to the byte on f05f015 (3,235,899). Two earlier pairs gave the same shape of move: d7e9e9ab → its merge, 155,532 B (report 6062475046), and a835c314 → its merge.

28557855 (main) f05f015 (this branch)
eager closure, gzip 3,391,416 3,235,899
eager chunks 336 of 2458 289 of 2474
eager raw 11,496,244 10,888,809

165 modules leave the eager closure and none joins. By rendered bytes:

  • metadata-admin/previews 554,838 (56 modules);
  • metadata-admin/inspectors 396,857 (38);
  • other metadata-admin 41,664 (12: the three registries, the external-datasource panels, three schema helpers, metadata-form-i18n);
  • lucide-react icons 37,230 (56);
  • @objectstack/spec identity/index.mjs 27,098 (1);
  • InterfaceListPage + pageKindNode 14,572.

Per-chunk gzip:

  • src −150,696, RecordDetailView −19,482, vendor-objectstack −6,468, createDerive −1,556, ActionResultDialog −1,244;
  • chunks that left: default-inspector-registry −13,284, ReportDefaultInspector −8,156, InterfaceListPage −4,454, pageKindNode −80, and 56 vendor-icon-* chunks −16,781;
  • 13 chunk names new to the closure, +66,548. They hold modules that were already eager, re-chunked under a lazy neighbour's name; no module joins.
  • 27 others, +136 in all.
  • Net −155,517.

Tests

New pins:

  • views/metadata-admin/__tests__/builtinDesigners.hostPrecedence-11939.test.tsx — host-first kept in all three registries; host-after replaces and its reader re-renders; a second pass changes no entry and commits no render (counted with React.Profiler).
  • views/metadata-admin/__tests__/builtinDesigners.lateArrival-11939.test.tsx — the Data pillar field rail (objectui#6795's silent-failure site) shows its missing-inspector sentence, then the REAL ObjectFieldInspector when registerBuiltinDesigners() lands, with no remount.
  • src/__tests__/entryRegistersBuiltinDesigners-11939.test.tsx — importing the package entry registers FlowPreview, FlowInspector and HookDefaultInspector by identity, with no host call.

Suites that relied on synchronous registration now register explicitly at module scope: five metadata-admin suites that bare-import ./register-builtins, and three console suites (preview-samples-designer-options, App.agentChatLink-10640, ApiConsolePage.requestPreset-10591), which load the registration with vi.importActual so the app-shell sources stay out of the console's tsc program.

The dev-only designer gallery waits for the entry's own chunk through the public useRegisteredMetadataPreviewTypes hook before it mounts its list. Browser check (the verify skill, own dev server): "Loading the built-in designers…" at DOMContentLoaded, then 19 designer sections; control on d7e9e9ab: 19 sections.

Reverse verification, each through scripts/ablation-replace.mjs on a committed tree, each restored to the HEAD blob with git diff HEAD empty:

  1. the built-in pass's skip removed → the host-precedence pin goes RED 2 of 3 ("expected [Function FlowPreview] to be [Function HostFlowPreview]");
  2. the entry's .then no longer calling the registration → the entry pin goes RED (no designer registered);
  3. the entry importing the designers statically again → console eager closure 3,392,679 B, over today's ceiling by 1,195 and over every candidate lowered ceiling by at least 156,727. The first attempt was a no-op the tool refused (its replacement contained its anchor); re-run with a non-overlapping anchor.

On the final head 6db39695 (after merging 28557855 and re-pinning the ceiling), these runs passed:

  • 26 targeted files, 417 tests: the new pins; step 1's late-registration and registry-missing, partial and populated suites; every edited suite; and the budget script's own suite with its siblings (check-eager-closure-budget, check-docs-route-eager-closure, check-eager-locale-catalogues, console-vite-alias-closure-4925, render-budget-comment, check-side-effects-array, vite-declared-lazy-views).
  • Both type-checks exited 0 on the re-pin commit 8d5100ed: pnpm --filter @object-ui/app-shell type-check and pnpm --filter @object-ui/console type-check.
  • Gates on 6db39695, every one exit 0: check:eager-closure, check:side-effects-array, check:sdui-registration-pins, check:control-bytes, check:changeset-claims, and the changeset-presence check.

The budget suite's two rendered-baseline pins follow BASELINE to "3160.1" (commit 6db39695).

Earlier, on 7a30a7b6, a run over 186 derived files (every suite that imports the entry, register-builtins, the registries or a built-in registrar) had 185 pass. The one failure is the flake noted under Acceptance notes.

Deviations

  • The dispatch's suggested side-effectful door module named in sideEffects was not taken (measured STALE, above); the door is a pure module plus an explicit call.
  • Host precedence lives in one built-in pass in the shared store rather than per-registry helpers, so the built-in registrars stay untouched.
  • The gallery's first form imported the registration by workspace source path; reverted, because it pulled five designer files into the console's noUnusedLocals type-check (TS6133 ×5).
  • origin/main merged three times (d7e9e9ab, 989b1908, 28557855), never rebased.
  • The ceiling was held unchanged until ruling 6070754914, then re-pinned by its formula (above). No per-chunk row moves.

Acceptance notes

Observed, not filed (no carrier):

  • The ineffective-dynamic-import ledger did not flag ablation 3 (a static import beside the dynamic one): it printed its usual 43 pinned entries. The eager-closure gate is what catches that edge.
  • On a cold deep link, a Studio pillar can show its "designer missing" sentence for the moment the designers' chunk is in flight, then recover. The sentences state only what is true when they render, as their i18n comment requires.
  • inspectors/index.ts's header still says it is imported as a side effect from metadata-admin/index.ts (stale since objectui#6776). previews/index.ts's "register after this module runs, last-write-wins" is still true, and now incomplete (a host may also register before; it is kept).
  • scripts/vite-declared-lazy-views.ts keeps its "25 runtime values" count: it sits in the dated objectui#6776 narrative.
  • ObjectHooksPanel.newHookTarget-11820.test.tsx failed once in a 186-file parallel batch (its label not yet hydrated) and passes alone on this branch and on d7e9e9ab.

Generated by Claude Code

claude added 9 commits October 8, 2026 13:22
…mports dynamically (objectui#11939 step 2)

The package entry keeps its bare import of `register-builtins.ts` for the
three registrations whose registries are not observable (anchors, default
JSONSchemas, the datasource resource) and loads the built-in previews and
inspectors with a module-scope dynamic `import()` of the new
`register-builtin-designers.ts`, then calls its `registerBuiltinDesigners()`.

The built-in pass runs inside `registerAsBuiltIns`, so a built-in fills only
a type with no entry: a host registration made before the chunk arrives is
kept, and one made after replaces the built-in as before.

The dev-only preview gallery registers the designers explicitly before its
first render.

Claude-Session: https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8
Co-authored-by: Claude <noreply@anthropic.com>
…dence, late arrival, entry auto-registration (objectui#11939 step 2)

Three new pins:
- builtinDesigners.hostPrecedence-11939: a host designer registered before
  the built-ins arrive is kept in all three registries; one registered after
  replaces the built-in and its reader re-renders; a second pass is inert.
- builtinDesigners.lateArrival-11939: the Data pillar field rail shows its
  missing-inspector sentence, then the real ObjectFieldInspector when
  registerBuiltinDesigners() lands.
- entryRegistersBuiltinDesigners-11939: importing the package entry registers
  the built-in designers with no host call.

Tests that relied on the entry (or register-builtins) registering the
designers synchronously now call registerBuiltinDesigners() at module scope.

Claude-Session: https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8
Co-authored-by: Claude <noreply@anthropic.com>
…he lazy-views plugin's note on register-builtins (objectui#11939 step 2)

Claude-Session: https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8
Co-authored-by: Claude <noreply@anthropic.com>
…a render-time mutation (objectui#11939 step 2)

Claude-Session: https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8
Co-authored-by: Claude <noreply@anthropic.com>
…ing app-shell sources (objectui#11939 step 2)

The console's tsc program checks with noUnusedLocals, app-shell's does not, so
a static import of an app-shell source module pulled five designer files into
the console's type-check (TS6133). The gallery now waits for the package
entry's own designer chunk through the public `useRegisteredMetadataPreviewTypes`
hook before it mounts its list, and the three console suites load the
registration with `vi.importActual` at module scope.

Claude-Session: https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8
Co-authored-by: Claude <noreply@anthropic.com>
… (objectui#11939 step 2)

Claude-Session: https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8
Co-authored-by: Claude <noreply@anthropic.com>
… registrations run now (objectui#11939 step 2)

Claude-Session: https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

⚠️ Console Performance Budget — gauge not trustworthy

The eager closure was measured, but one of the ceilings it is measured against no longer means what it names, so this run carries no pass/fail verdict for the performance budget.

This is not a budget violation. Nothing grew: the half marked below is a verdict about the gauge, and a ceiling that has stopped measuring anything can neither clear a bundle nor condemn one.

Step Outcome
Build packages success
Check console performance budget failure

Which half objected:

Eager-closure half Verdict
Aggregate closure ceiling ✅ pass
Per-chunk ceilings ✅ pass
Per-chunk membership (declared packages) ✅ pass
Ceiling sensitivity (headroom) ⚠️ broken gauge
Ceiling freshness (checkout vs. base branch) ✅ pass

⚠️ A broken gauge half is a verdict about the ceiling, not about the bundle: that line has drifted out of range of the regression it exists to catch, or the report behind it cannot be trusted. It does not say anything grew. The Check console performance budget step log carries the ceiling and the number it was compared against.

Reason: The entry chunk measured 71.5 KB, but the eager-closure half of this gate returned no trustworthy VERDICT: the report could not be read, a ceiling has drifted out of range of the regression it must catch, or (objectui#6245) a ceiling was replaced on the base branch after this checkout was made. The step log says which. This is not a passing budget — and it is not a size regression either.

See the workflow run for details.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 19.52KB 7.19KB
app-shell (runtime-config.js) 22.59KB 7.89KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 41.19KB 11.12KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.11KB 7.97KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.50KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 587.49KB 141.58KB
core (index.js) 10.00KB 3.96KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 240.39KB 67.09KB
fields (index.js) 268.74KB 68.04KB
i18n (LocalizationContext.js) 2.92KB 1.42KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.52KB 2.39KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.35KB 12.88KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 36.87KB 9.88KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 41.50KB 11.82KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.86KB 5.00KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.52KB 2.26KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.04KB 3.92KB
plugin-calendar (index.js) 53.43KB 15.54KB
plugin-charts (index.js) 84.71KB 23.25KB
plugin-chatbot (index.js) 199.63KB 47.46KB
plugin-dashboard (index.js) 144.20KB 38.95KB
plugin-designer (index.js) 231.46KB 48.87KB
plugin-detail (index.js) 248.57KB 65.47KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 177.11KB 45.88KB
plugin-gantt (index.js) 179.17KB 45.07KB
plugin-grid (index.js) 248.22KB 68.84KB
plugin-kanban (index.js) 52.22KB 16.38KB
plugin-list (index.js) 117.68KB 29.40KB
plugin-map (index.js) 27.24KB 9.03KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.12KB 12.29KB
plugin-timeline (index.js) 39.10KB 11.81KB
plugin-tree (index.js) 15.07KB 5.33KB
plugin-view (index.js) 91.09KB 22.94KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.81KB 3.58KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 120.63KB 39.56KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.31KB 2.07KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.07KB 1.30KB
sdui-parser (index.js) 7.30KB 3.12KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 23.87KB 7.83KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (authoring-nodes.js) 0.20KB 0.19KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (cloud.js) 0.20KB 0.18KB
types (complex.js) 4.44KB 2.07KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (dashboard-widget-layout.js) 2.06KB 0.96KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 1.13KB 0.65KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 5.78KB 2.70KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (node-slots.js) 7.18KB 2.34KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.93KB 7.25KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

claude added 3 commits October 8, 2026 23:15
…e designers left, plus half a regression (objectui#11939 step 2, ruling 6070754914)

MAX_EAGER_CLOSURE_GZIP_BYTES 3,391,484 -> 3,281,467: the reading 3,235,899 on
f05f015 plus 45,568 (half of the 89 KiB regression), under ruling D on
objectui#11939 (record 6070754914). BASELINE moves onto f05f015 in the same
commit. The docblock entry carries the ruling verbatim, the bytes that left
(155,517 against the 2855785 control), the end of objectui#11942's 5,120 B
allowance and the 5,052 B of drift it absorbed. No per-chunk row moves.

Claude-Session: https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8
Co-authored-by: Claude <noreply@anthropic.com>
…INE to 3160.1 (objectui#11939 step 2)

Claude-Session: https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 289 chunks) 3160.0 KB 3204.6 KB
Main entry chunk (gzip) 71.6 KB 350 KB
Entry file index-Ur-p_cVK.js —
Status PASS —

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 19.52KB 7.19KB
app-shell (runtime-config.js) 22.59KB 7.89KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 41.19KB 11.12KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.11KB 7.97KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.50KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 587.83KB 141.44KB
core (index.js) 10.00KB 3.96KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 240.39KB 67.09KB
fields (index.js) 268.74KB 68.04KB
i18n (LocalizationContext.js) 2.92KB 1.42KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.52KB 2.39KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.35KB 12.88KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 36.87KB 9.88KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 41.50KB 11.82KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.86KB 5.00KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.52KB 2.26KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.04KB 3.92KB
plugin-calendar (index.js) 53.43KB 15.54KB
plugin-charts (index.js) 84.71KB 23.25KB
plugin-chatbot (index.js) 199.63KB 47.46KB
plugin-dashboard (index.js) 144.20KB 38.95KB
plugin-designer (index.js) 233.53KB 49.80KB
plugin-detail (index.js) 248.57KB 65.47KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 177.11KB 45.88KB
plugin-gantt (index.js) 179.17KB 45.07KB
plugin-grid (index.js) 249.56KB 69.16KB
plugin-kanban (index.js) 52.22KB 16.38KB
plugin-list (index.js) 117.80KB 29.42KB
plugin-map (index.js) 27.24KB 9.03KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.12KB 12.29KB
plugin-timeline (index.js) 39.10KB 11.81KB
plugin-tree (index.js) 15.07KB 5.33KB
plugin-view (index.js) 91.16KB 22.97KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.81KB 3.58KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 120.63KB 39.56KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.31KB 2.07KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.07KB 1.30KB
sdui-parser (index.js) 7.30KB 3.12KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 23.87KB 7.83KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (authoring-nodes.js) 0.20KB 0.19KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (cloud.js) 0.20KB 0.18KB
types (complex.js) 4.44KB 2.07KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (dashboard-widget-layout.js) 2.06KB 0.96KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 1.13KB 0.65KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 5.78KB 2.70KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (node-slots.js) 7.18KB 2.34KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.93KB 7.25KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 6db3969539a4c657be04b1a2093b61b1f47831e8
Local-runs: none

PR objectui#11982 (card objectui#11939, step 2 of objectui#6795's order), read-only at tier. Inputs: the card's body and its 13 comments, the PR body, its 23-file list and the net diff against main at the merge base 28557855 (12 commits, three merges of main, no rebase), and the 43 check-runs on the head: 40 success, 3 skipped (Test (coverage), its shard template, dependabot), 0 failure. Bundle Analysis on the head is success, and the budget bot's PR comment 6071153601 reads 3160.0 KB measured against the 3204.6 KB ceiling, every half PASS.

① Derived judgments

  1. Registration timing, the widening. The entry keeps its bare import of register-builtins.ts for the three registrations whose registries are not observable (anchors, default JSONSchemas, datasource resource) and, at its own module scope, starts import('./views/metadata-admin/register-builtin-designers.js') and calls registerBuiltinDesigners() on arrival. Importing the package still registers every built-in designer with no host call; the entry pin holds it by identity (FlowPreview, FlowInspector, HookDefaultInspector). Right: this is the shape ruling A on objectui#11798 left open, neither a per-route await nor a lazy sub-entry, in the order objectui#6795 set (step 1, a835c31, made the three registries observable). The one behaviour that moves is stated in the changeset: a plain get* / list* read right after import can see no built-in yet; render-time reads go through step 1's useRegistered* hooks.
  2. Host precedence. registerAsBuiltIns (in preview-registry.ts) runs the built-in pass; inside it the shared store's set returns on a type that already has an entry, before the same-value short-circuit, so a host registration made before the chunk lands is kept, one made after replaces (last-write-wins, unchanged outside a pass), and a second pass changes nothing and commits no render. Right, and pinned in all three registries (builtinDesigners.hostPrecedence-11939). The pass counter is one module-level integer shared by every store the factory creates, which is what lets one pass cover three registries.
  3. Public surface: nothing added, removed or changed. Read on the head's packages/app-shell/src/index.ts: the re-exports from preview-registry.js and inspector-registry.js are named lists, untouched by this diff; registerAsBuiltIns, createObservableTypeRegistry and registerBuiltinDesigners are not on the entry, and package.json publishes . and ./styles.css only, so the door module is reachable through the entry alone. The console suites reach it by a workspace source path through vi.importActual, test-only. Right.
  4. sideEffects unchanged by design. register-builtin-designers.ts performs no load-time effect and the entry keeps it by USE of its export; register-builtins.ts keeps its two entries. The dev measured the dispatch's suggested route, a side-effectful door named in the array, as STALE under check:side-effects-array, which walks static edges only. Right. The claim's Clause-② reason named the array; the timing change alone carries the declaration (see ②).
  5. What stays eager. The three non-observable registrations (a late arrival there is a silent loss, not a delay) and the fourth registry studio-design/studio-canvas-preview.tsx (untouched, still in sideEffects, 2,943 rendered bytes). Right; the claim allowed "stays eager, or becomes observable".
  6. The gallery. GalleryOnceDesignersArrive waits on the public useRegisteredMetadataPreviewTypes hook instead of registering explicitly; the claim's condition, never listing zero designers, is met by an await of the package's own door, and the console's tsc program stays free of app-shell sources. Right; dev-only.
  7. The failure path. .catch logs one console.error; readers keep their "designer missing" state, and the i18n.ts comment now says recovery is real but not promised. Right. No pin on this path; acceptable for a logged failure of the package's own chunk.
  8. The eager-ceiling re-pin, against ruling 6070754914 (letter D). Implemented as ruled, in one commit (8d5100ed: the budget script and its test, nothing else):
    • MAX_EAGER_CLOSURE_GZIP_BYTES 3,391,484 → 3,281,467 = the landing reading 3,235,899 on f05f015 plus 45,568 (91,136 / 2, the gate's own re-pin formula). BASELINE re-pinned in the same commit to 3,235,899, 289 of 2474 chunks, f05f015; squashMerge left null to be back-filled after the merge. The two commits after f05f015 touch scripts/ only, so the reading is the landing head's first load; CI's merge-ref reading on the head agrees within 0.1 KB.
    • The docblock carries: the ruling verbatim with its record id; the standing rule drawn from it; WHAT THE MARGIN IS FOR in the ruling's words (an 89 KB-class regression plus the daily copy- and fix-level churn, not an allowance to be spent on a feature); the bytes that left (chunk table and module groups); THE STUDIO ALLOWANCE ENDS HERE; the drift recorded, not reclaimed; the objectui#11942 entry's new "AMENDED before it ran" pointer; the history reads "five times" and the test's negative control follows it.
    • Arithmetic re-done from the diff: 3,391,416 − 3,235,899 = 155,517, and the chunk table's delta column sums to −155,517; the literal objectui#11942 formula 3,391,484 − 155,517 − 5,120 = 3,230,847 sits 5,052 under the reading; main 1c51e973 to 28557855 is +5,437, of which 5,052 above the pre-allowance line 3,386,364 and 68 unspent, and the by-chunk drift list sums to +5,437; headroom 45,568 is 0.50x of 91,136, under one regression; MAX is greater than BASELINE; 3,235,899 / 1024 renders 3160.1 and 3,281,467 / 1024 renders 3204.6, the figures the budget suite pins and CI prints.
    • Per-chunk rows untouched (vendor-objectstack −6,468 at 0.26x of its ceiling, ui-components −19, framework +10, i18n-locale-en 0); the PER_CHUNK_BASELINE prose names f05f015. Right.
  9. Scope. All 23 files are on claim 6060604189 as amended by 6062655308: the door and its sibling module, the entry, preview-registry.ts, the gallery, the budget script and its test, vite-declared-lazy-views.ts, the comment-only corrections (i18n.ts, StudioDesignSurface.tsx, metadata-admin/index.ts), the changeset, three new pins, five app-shell suites and three console suites that now register the designers at module scope. services/builtinComponents.tsx is on the claim and untouched: it holds resource edit pages, not designers. No governed path. Fixes #11939 closes the card, as the ruling's State line says; Clause-②: yes at line start. Commit trailers are the model-free pair.

② Semver level

  • .changeset/11939-lazy-designers.md: @object-ui/app-shell minor, @object-ui/console patch. Right.
    • app-shell: no export changes; what is published is behaviour, when the built-ins become available and that a host's pre-chunk registration is kept. The claim set minor as the floor, and Changeset Bump Policy, Changeset Declaration, Changeset Claim Re-read, Changeset Fixed Group Check and Changeset Overwrite Report are all success on the head. The one reading that could ask for more, a consumer's synchronous get* right after import now returning undefined, is the behaviour ruling A on objectui#11798 chose over the refused lazy sub-entry, and the changeset states it in words together with the hook route. The changeset also retires step 1's "still registered when the package loads" sentence. minor stands.
    • console: a dev-only gallery and three test suites. patch stands.
  • Clause-②: yes at line start of the PR body. Right: the published registration timing and precedence change for every consumer, so a record at CONTRACT_REVIEW_TIER is owed, and this is it.

③ Boundary flags

Open questions (report 6062475046):

  • [0] the ceiling number — answered by ruling 6070754914 (D), implemented as ruled (①.8).
  • [1] the file surface — answered by amendment 6062655308 (A): the three console suites and the metadata-admin/index.ts comment are on the surface and in the diff. The sub-ask (five app-shell suites that bare-import ./register-builtins, two new pins, the entry pin) falls under the claim's "the tests beside these"; every one is beside a claim file.

Deviations (report and PR body):

  • Pure door module instead of a side-effectful one named in sideEffects — right (①.4).
  • One shared built-in pass instead of per-registry helpers — right; previews/index.ts and inspectors/index.ts untouched (①.2).
  • The gallery's source-path registration reverted for the hook wait — right (①.6).
  • Three console suites outside the surface — amended.
  • Ceiling held until the ruling, then re-pinned — right (①.8).
  • main merged three times, never rebased — the compare reads 12 ahead; the net diff is what is judged here. Fine.
  • Full console builds at a 6144 MB heap — a build-environment note with no artefact in the diff; not a flag on the change.
  • Dev worktree kept — housekeeping for the seat after the merge.

Acceptance notes (observed, no carrier):

  • The ineffective-dynamic-import ledger did not flag a static import placed beside the dynamic one — escalated to the seat as a candidate card: a gate gap the eager-closure ceiling catches by bytes, not by cause. Not blocking.
  • A cold deep link can show a "designer missing" sentence while the chunk is in flight, then recover — accepted: it is the order the ruling chose (readers observable first, lazy second), and the strings promise nothing. A product question for the release line, not a defect.
  • Stale headers in inspectors/index.ts and previews/index.ts, and the "25 runtime values" count inside the dated objectui#6776 narrative — outside this claim; carried to the seat's round report.
  • ObjectHooksPanel.newHookTarget-11820 flaked once in a 186-file batch; the eight Test shards on the head are success. Noted for the seat.

Nothing is left unanswered, and nothing escalated here blocks the landing.

Implemented-by: claude/issue-11939-lazy-designers
Reviewed-by: session_01CGZy1BGCjdN5cXqL9cnvB8

VERDICT: PASS

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 9, 2026 00:01
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 9, 2026 00:01
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 9, 2026
Merged via the queue into main with commit 4babf40 Oct 9, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-11939-lazy-designers branch October 9, 2026 00:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants