Skip to content

perf(app-shell,react): a console open reads the current app's objects, not the whole object registry (objectui#12811) - #12871

Merged
objectstack-fleet[bot] merged 7 commits into
mainfrom
claude/issue-12811-scoped-object-metadata
Oct 17, 2026
Merged

objectstack-fleet[bot] merged 7 commits into
mainfrom
claude/issue-12811-scoped-object-metadata

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Part of #12811
Clause-②: yes

The object list open path (the card's measured route, /apps/crm_enterprise/crm_account) no longer requests the whole object registry. One half of the card stays open: record routes and record overlays still read the whole registry, because a record's related lists come from every object that points at its object, in any package. On HotCRM, crm_case (app.objectstack.hotcrm.service) points at crm_account. That half needs a decision (a reverse-relation door, or keep as is), recorded on the card.

What changed

The scoped read (@object-ui/react, MetadataProvider). A new optional member, MetadataContextValue.readObjectScope({ packageId, names }):

  • reads GET /api/v1/meta/object?package=ID with the package id the app's served item carries (ADR-0048; @objectstack/client's meta.getItems(type, { packageId }) sends it);
  • reads each named object the package does not hold by name, through the provider's existing getItem;
  • uses the same ingestion as the whole list (normalizeSchemaReferenceKeys, then composeObjects), so a scoped definition equals the same object from objects;
  • returns objects, served (the definitions as served, before views are merged), missing and settled;
  • with no package id, returns the whole registry.

Scope entries are dropped and refetched only with the object type (invalidate, refresh, an organization or preview-mode switch); there is no timestamp. When the whole registry is already loaded (after Studio, say), a scope is cut from it and nothing is requested. useMetadata().objects is unchanged: it is the whole registry wherever it is read.

Which route reads what (console/appObjectScope.ts). These routes read the app's scope: the app root, an object's list, and a list view, with no record overlay in the URL and an app item that carries a package id. They read the package's objects, plus by name:

  • the route's own object;
  • the objects its lookup fields point at;
  • the objects the app's navigation entries name in requiresObject;
  • while the command palette is open, the navigation's objects.

The splash and the route skeleton wait for the package list and the route's own object. The other names are asked in parallel and are not waited on: a lookup cell draws its value without a link until its target lands.

Everything else reads the whole registry, as before:

  • record routes and record overlays;
  • a list whose views open records in an in-page overlay (modal, split, popover);
  • an app whose package holds no objects of its own (Setup and Account on HotCRM);
  • an app item without a package id (none on HotCRM);
  • every route outside an app.

The open path's readers, and what each reads now:

  • AppContent: the scope for the route element, ConsoleLayout, CommandPalette and useTrackRouteAsRecent. It no longer destructures objects.
  • useActionModal (mounted from ConsoleShell): reads objects only while an object's form modal is open, and the open waits for ensureType('object'). The form's subforms are derived from every object.
  • useNavMemberGates: inside an app with a package, while nothing has loaded the whole registry, answers from the scope. It asks for nothing itself.
  • useServedViewItems (AppHeader and ObjectView view-tab labels) and useObjectPresence (the bell's activity feed): read the scope until something loads the whole registry. These two were found live; main hid them because the whole list was already in flight.
  • ConsoleShell: starts the app's scope as soon as the app list names the app. AppContent mounts only after the session's permission read, while the whole list used to start at the console root beside it.

openPathObjectReaders-12811.test.ts finds every reader of objects or getItemsByType('object') in the packages and the console, and fails on one it does not classify (scope, action time, or off the open path).

Requests, measured

HotCRM objectstack dev --fresh, behind a proxy adding 25 ms on each leg (a 50 ms round trip), production builds served by vite preview, Chromium via Playwright: one warm open, then three measured opens per side of /apps/crm_enterprise/crm_account. Times are milliseconds from navigation start.

main e43a162 this branch e7a2e8a
splash on screen 783-1660, 900-1654, 711-1513 (877, 754, 802 ms) 731-1471, 852-1632, 808-1589 (740, 780, 781 ms)
meta/object (whole) 96 objects, 858893 B, 869-1078 not requested
meta/object?package=app.objectstack.hotcrm none 15 objects, 316198 B, 1002-1159
by name: sys_approval_request, sys_organization, sys_user, sys_business_unit none 1 each, 7957-24199 B, 1170-1329
by name: sys_activity (bell) none 1, 8074 B, 1206-1433
meta/object/crm_account (the adapter's own read, both sides) 49786 B, after paint 49786 B, after paint
object bytes, total 908679 B in 2 calls 430281 B in 7 calls
API calls in the open 32 37

On the Setup app (/apps/setup, a package with no objects of its own): main 35 calls, splash 855 ms; branch 36 calls (one empty ?package= answer, then the whole list), splash 858 ms.

On this local backend the whole list costs about 200 ms, so the splash does not move. The card measured it at 5.7 s on staging, which this read no longer makes on the object-list open.

Controls, live

  • Record page /apps/crm_enterprise/crm_account/record/01T4qqNXvOOOINtK: the Account Owner lookup (sys_user, outside the package) renders as Dev Admin, with 16 related lists. Same on main and on the branch.
  • Metadata admin /apps/crm_enterprise/metadata/object: still requests the whole meta/object when it mounts and lists the same rows on both sides.
  • /apps/crm_enterprise/crm_case (an object outside the app's package): asked by name, renders its list, no not-found state.

Deviations

  • Files beyond the claim's list: packages/react/src/context/AppShellContext.tsx (the context type), the new console/appObjectScope.ts, ConsoleShell.tsx, useServedViewItems.ts, useObjectPresence.ts. Existing pins: ConnectedShell.sessionGate.test.tsx (its third boot request was the whole list) and a comment in MetadataProvider.requestBudget.test.tsx.
  • One new optional member on MetadataContextValue.
  • Values that now resolve differently: an object form modal waits for the object list the first time; useObjectPresence and the capability gate answer from the app scope inside an app with a package; the bell's cross-app roster no longer asks other apps' requiresObject objects by name.
  • Record surfaces and apps without objects of their own keep the whole registry, for the measured reasons above.

Screenshots (light, evidence branch, not merged here)

Tests, pinned to e7a2e8a

  • MetadataProvider.objectScope-12811:
    • asks ?package=, never the bare list;
    • asks a name outside the package after the list, and is not settled until it answers;
    • a scoped definition equals the whole list's;
    • an unanswered name is missing;
    • CONTROL: objects is the whole registry, and so is a scope with no package;
    • with the whole registry loaded, a scope requests nothing;
    • invalidate and refresh drop and refetch it.
  • AppContent.objectScope-12811 (real provider, recorded client):
    • the open asks the package list and only the by-name objects it reads;
    • CONTROL: an app item with no package id, or a package with no objects of its own, asks the whole registry;
    • CONTROL: a record overlay reads the whole registry;
    • a route object outside the package holds the splash, then after the first paint the route skeleton, until it lands;
    • lookup targets do not hold the route.
  • appObjectScope-12811: the console root asks the package list, then the lookup targets; nothing outside an app, on a record overlay, or for an app without a package id.
  • scopedRegistryReaders-12811: useServedViewItems and useObjectPresence read the scope inside an app; CONTROLs outside an app and once the whole registry loads.
  • openPathObjectReaders-12811: the census, in both directions.
  • Ablation, one per new pin, each mutation confirmed on disk and each restore confirmed by an empty git diff HEAD: whole-registry forced (3 red), scope readiness dropped (2 red), a new objects reader in AppHeader (census red), objects made partial (2 red), useServedViewItems reverted (1 red), no-own-objects rule deleted (1 red), gate waiting for lookups (1 red).
  • app-shell in 8 shards (pnpm exec vitest run packages/app-shell/ --shard=K/8; the whole suite exceeds the container's foreground cap): every shard exit 0, 1539 files (1538 passed, 1 skipped), 14516 tests passed, 9 skipped.
  • pnpm exec vitest run apps/console/ in 2 shards (it also exceeds the cap): both exit 0, 223 files, 2304 tests. packages/react: 113 files, 1426 tests.
  • type-check and build for @object-ui/react and @object-ui/app-shell: 0. Console build: 0.
  • check:eager-closure 0, check:spec-symbols 0, check:i18n-keys 0, check:i18n-drift 0, check:new-line-citations 0, check:test-path-roots 0, node scripts/check-changeset-presence.mjs 0.
  • eslint on the touched files: 0 errors; the added warnings are no-explicit-any, in the files' existing idiom.

Acceptance notes

  • Not filed, observed on both sides: the adapter fetches the route object by name for its own schema cache after the first paint, and meta/view is asked three more times after the first paint.
  • HotCRM declares no inlineEdit field, so a scope's attachInlineSubforms sees the same children as the whole list there. The record form, which shows subforms, reads the whole registry everywhere.

Generated by Claude Code

🤖 Generated with Claude Code

https://claude.ai/code/session_01AxUw9eBGehV2FAP7Smwn3N

…, not the whole registry (objectui#12811)

The open path's three readers of useMetadata().objects (AppContent,
useActionModal, useNavMemberGates) loaded GET /api/v1/meta/object for
every registered object on every console open. They now read the app's
object scope: the new optional MetadataContextValue.readObjectScope asks
?package= with the package id the app's served item carries, and by name
each object outside the package the open reads. objects keeps its
meaning, the whole registry, for every other reader.

Claude-Session: https://claude.ai/code/session_01AxUw9eBGehV2FAP7Smwn3N
Co-authored-by: Claude <noreply@anthropic.com>
… feed read the app scope (objectui#12811)

Measured live once the three objects readers had moved: AppHeader still
loaded the whole registry through getItemsByType('object') in
useServedViewItems and useObjectPresence (the bell's sys_activity
presence). Both read the app's object scope now, until something loads
the whole registry; readObjectScope also returns the definitions as
served. The capability gate asks nothing itself: AppContent names the
objects the app's own entries require, so the bell's cross-app roster
no longer asks another app's objects one by one. The census pin reads
the getItemsByType('object') spelling too.

Claude-Session: https://claude.ai/code/session_01AxUw9eBGehV2FAP7Smwn3N
Co-authored-by: Claude <noreply@anthropic.com>
…eaders pin's probe inside its mount (objectui#12811)

Claude-Session: https://claude.ai/code/session_01AxUw9eBGehV2FAP7Smwn3N
Co-authored-by: Claude <noreply@anthropic.com>
…e the permission read (objectui#12811)

Measured on HotCRM behind a 50 ms round trip, production builds: the
whole object list used to start at the console root (useActionModal),
beside me/permissions; the scope AppContent asked for started only after
it, so the splash on the branch held about 140 ms longer than on main.
The scope question moves to console/appObjectScope.ts, which AppContent
and a root hook in ConsoleShell's action runtime both ask, so the
package list starts as soon as the app list names the app.

Claude-Session: https://claude.ai/code/session_01AxUw9eBGehV2FAP7Smwn3N
Co-authored-by: Claude <noreply@anthropic.com>
…ect list at boot (objectui#12811)

The pin's third request was the whole registry, which useActionModal loaded
on every open. Its purpose stands: no metadata request before auth, and
each eager request once after it.

Claude-Session: https://claude.ai/code/session_01AxUw9eBGehV2FAP7Smwn3N
Co-authored-by: Claude <noreply@anthropic.com>
…e registry (objectui#12811)

Measured on HotCRM: Setup's package holds no objects, and its navigation
requires 15 objects of other packages. Its scope asked 11 of them by name,
and its landing loaded the whole registry anyway: 47 API calls against 35
on main. The package list is read first; an app it shows to own no objects
reads the whole registry, and no name is asked.

Claude-Session: https://claude.ai/code/session_01AxUw9eBGehV2FAP7Smwn3N
Co-authored-by: Claude <noreply@anthropic.com>
…ect, not its lookup targets (objectui#12811)

Measured on HotCRM behind a 50 ms round trip: the lookup targets (asked
by name once the package list lands) held the splash one more round trip.
They decide only whether a lookup cell draws a link, and the
requiresObject names only whether an entry stays shown; both are asked
beside the route and land after it, as the whole list's did.

Claude-Session: https://claude.ai/code/session_01AxUw9eBGehV2FAP7Smwn3N
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 — ACCEPT · objectui#12811 · head e7a2e8a · 2026-10-17T09:08Z

Seat domain:ui#4, session session_01SJn1GEkrbbBQkhGC26wEDw. Checked against GitHub and the diff, not against the report (relayed on the card, 6232095068). The seat opened this PR from the dev's drafts (degraded channel). The body and the title are the dev's drafts verbatim. The seat's TIER_DEFAULT contract review follows this record.

Checklist

  • Form:

    • Draft → main, assignee os-sales.
    • The first line is Part of #12811, and the second is Clause-②: yes, as the claim declares (6229848647).
    • Seven commits, each carrying only the model-free trailer pair.
    • git merge-tree against current main is clean. 18 files, +1524 / −54. No PNG. Not a governed surface.
  • The ruling, as bound (useMetadata().objects keeps its meaning, the open path uses the ?package= scope, and an object outside the package is read by name):

    • The new optional MetadataContextValue.readObjectScope({ packageId, names }) reads GET /api/v1/meta/object?package=ID through meta.getItems('object', { packageId }). A name the package does not hold goes through the provider's existing getItem. Both use the whole list's ingestion (normalizeSchemaReferenceKeys, then composeObjects).
    • objects is untouched, and still the whole registry.
    • Scope entries clear with the object type (invalidate, refresh, an organization or preview switch). There is no clock.
    • A scope is cut from the whole registry when that is already loaded, so nothing extra is requested after Studio.
    • openPathObjectReaders-12811.test.ts is a census: a new open-path reader of objects that nobody classified turns it red.
  • Live (HotCRM objectstack dev --fresh, 50 ms round trip, production builds, three opens per side of /apps/crm_enterprise/crm_account):

    main e43a162 this branch
    whole meta/object 96 objects, 858,893 B not requested
    ?package=app.objectstack.hotcrm — 15 objects, 316,198 B
    object bytes 908,679 B in 2 calls 430,281 B in 7 calls
    API calls 32 37
    splash 877 / 754 / 802 ms 740 / 780 / 781 ms

    Controls: a record page whose owner lookup points at sys_user (outside the package) renders it, with 16 related lists, on both sides. The metadata admin still reads the whole list. An object outside the app's package (crm_case) is asked by name and renders its list.

  • Screenshots, read by the seat (light; never-merged claude/issue-12811-evidence at ded02e9):

    • the Accounts list on the branch, 9 records, with the view tabs and the header intact;
    • Globex Industries' record page, with Account Owner "Dev Admin" (sys_user, outside the package) and Related 16.

    Sent to the maintainer at 2026-10-17T09:08Z.

  • Tests:

    • Five new pins:
      • MetadataProvider.objectScope-12811 (7);
      • AppContent.objectScope-12811 (7);
      • appObjectScope-12811 (2);
      • scopedRegistryReaders-12811 (3);
      • the census (3).
    • One updated pin, ConnectedShell.sessionGate, whose third boot request was the whole list.
    • Seven ablations, each red and each restored.
    • app-shell in 8 shards: 14516 tests passed. apps/console in 2 shards: 2304 passed. packages/react: 1426 passed.
    • type-check and build for @object-ui/react and @object-ui/app-shell, the console build, and check:eager-closure (3138.2 of 3744.1 KB), check:spec-symbols, check:i18n-keys, check:i18n-drift and changeset presence: all exit 0.
  • Changesets: @object-ui/react minor (one optional context member) and @object-ui/app-shell patch; user-facing prose.

  • CI: running at this record. Landing waits for green and for the contract review below.

Rulings

  1. The surface supplement: A, accepted. The claim named the open path's readers of useMetadata().objects as its class, and these sit inside it:

    • AppShellContext.tsx declares the one member the claim's Clause-② reason anticipated;
    • the new appObjectScope.ts is the one place that answers "which route reads which scope";
    • ConsoleShell.tsx mounts the scope's prefetch, and the claim named it as useActionModal's mount;
    • useServedViewItems and useObjectPresence are two more open-path readers, found live once the named ones stopped loading the whole list first;
    • ConnectedShell.sessionGate asserted the very request this card removes.

    No other claim holds those files.

  2. Record routes and record overlays keep the whole registry: A, Part of #12811.

    • A record's related lists come from every object that points at its object, in any package. On HotCRM, crm_case (the service package) points at crm_account. A by-name guess would drop such a list silently.
    • A reverse-relation door on the list route is not filed now. No producer pulls for it, and the card's measured route, the object-list open behind objectstack-ai/cloud#2914's splash, is met here.
    • After this lands, the card goes back to triage with that half named, to close or re-scope.
  3. An app whose package holds no objects of its own reads the whole registry: A. Measured on Setup: by-name reads cost 11 calls, the landing loaded the whole list anyway, and parity is now 36 vs 35 calls.

  4. Five more calls for 479 KB fewer object bytes: A. The calls are small (8–24 KB), parallel, and none holds the splash. The staging cost the card measured (5.7 s for the whole list) is gone from the object-list open.

  5. The published-face notes: accepted as declared in the changesets.

    • The first object form modal in a session waits for the object list (one request).
    • Inside an app with a package, while the whole registry is idle, the nav capability gate reads an unanswered object as registered. That is main's own rule while its list is empty, narrowed: an object the server answered as absent is pruned.
    • The bell's cross-app roster no longer asks other apps' requiresObject objects by name.
  6. The three pre-existing observations (the adapter's own by-name read after paint, three more meta/view reads, and a hand-typed ?recordId= on a page-surface object) stay notes. Each is on both sides and outside this card.

Implemented-by: cloud session session_01AxUw9eBGehV2FAP7Smwn3N, branch claude/issue-12811-scoped-object-metadata


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 76 chunks) 3139.7 KB 3744.1 KB
Main entry chunk (gzip) 80.1 KB 350 KB
Entry file index-BUAInESD.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) 21.42KB 7.85KB
app-shell (runtime-config.js) 23.60KB 8.19KB
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) 45.59KB 12.03KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 20.67KB 6.12KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.74KB 3.94KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.62KB 0.83KB
auth (authStyles.js) 5.27KB 1.86KB
auth (createAuthClient.js) 42.84KB 11.66KB
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.26KB 8.03KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (commentAuthorship.js) 1.39KB 0.78KB
collaboration (index.js) 1.93KB 0.85KB
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) 606.28KB 146.84KB
core (index.js) 10.18KB 4.04KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 248.83KB 69.44KB
fields (index.js) 291.36KB 74.46KB
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.33KB 11.96KB
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) 15.35KB 5.51KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.82KB 2.38KB
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) 54.94KB 16.05KB
plugin-charts (index.js) 84.97KB 23.36KB
plugin-chatbot (index.js) 210.60KB 49.94KB
plugin-dashboard (index.js) 146.23KB 39.59KB
plugin-designer (index.js) 233.95KB 50.08KB
plugin-detail (index.js) 263.79KB 69.75KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 180.22KB 46.59KB
plugin-gantt (index.js) 178.36KB 45.08KB
plugin-grid (index.js) 271.96KB 75.43KB
plugin-kanban (index.js) 62.17KB 19.22KB
plugin-list (index.js) 138.78KB 35.68KB
plugin-map (index.js) 27.07KB 8.96KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 46.66KB 13.13KB
plugin-timeline (index.js) 39.21KB 11.85KB
plugin-tree (index.js) 15.07KB 5.33KB
plugin-view (index.js) 94.68KB 24.36KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 12.07KB 3.68KB
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.23KB 39.46KB
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.81KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.29KB 0.24KB
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-approval-decision.js) 0.20KB 0.20KB
types (record-components.js) 0.20KB 0.19KB
types (record-panels.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.26KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.48KB 3.50KB
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: TIER_DEFAULT
Head-sha: e7a2e8ad45720b4916377c7f0dd081ba4a974416
Local-runs: none

Face: the Clause-②: yes declaration of the claim 6229848647 on objectui#12811. @object-ui/react's published MetadataContextValue takes a member, and when the open path's object definitions resolve changes in @object-ui/app-shell. The PR's file list carries no packages/spec/src/** path and no governed path.

Inputs read:

  • the card, its triage note and the claim;
  • the diff at the head above;
  • the two changesets;
  • the PR body's request table.

Nothing was built or run locally.

① Derived judgments

  • MetadataContextValue.readObjectScope is one optional member, (scope: { packageId?; names? }) => { objects, served, missing, settled }.

    • Optional, exactly as getTypeStatus already is, so a hand-rolled context value (tests, an older host) still type-checks, and its readers fall back to objects.
    • No existing member changes shape or meaning. objects is still the whole registry, and the changeset says so.
  • The door it reads is declared, not guessed. objectstack's GET /api/v1/meta/:type list handler reads ?package= (ADR-0048), and @objectstack/client's meta.getItems(type, { packageId }) sends it. That was read at dispatch, and the live run shows the server answering 15 objects for the HotCRM package.

  • Values that resolve differently in @object-ui/app-shell, all declared in its changeset:

    • an object form modal now waits for the object list on its first open;
    • useObjectPresence and the nav capability gate answer from the app's scope inside an app with a package;
    • the bell's cross-app roster no longer asks other apps' objects by name.

    No exported function, component, prop or data-testid is added or removed.

  • Freshness: scope entries carry no TTL and clear with the object type. The whole entry's existing ttlMs is untouched. Nothing new is served from a clock.

  • Failure: a failed scope read settles as an error with no items, the same outcome a failed whole-list read has on main.

② Semver level

  • @object-ui/react minor: a new optional member on a published interface.
  • @object-ui/app-shell patch: when existing values resolve, with no export change.

Both are in pre mode on tag next. Consistent with the repo's treatment of an added optional member (objectui#12297's @object-ui/types minor).

③ Boundary flags

  • None that needs the maintainer's ruling. The record half stays on the whole registry by the seat's ruling 2 in the ACCEPT. No reverse-relation door is proposed.
  • To know: the new member returns any[] definitions, matching objects' own any[] in the same interface. A typed definition is the interface's broader question, not this card's.

Implemented-by: session_01AxUw9eBGehV2FAP7Smwn3N
Reviewed-by: session_01SJn1GEkrbbBQkhGC26wEDw

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Landing · head e7a2e8a · 2026-10-17T09:18Z

  • Head since the ACCEPT: unchanged, e7a2e8a. The ACCEPT (6232120004) and the TIER_DEFAULT contract review (6232127073, PASS) were both written at this head.
  • Checks on e7a2e8a: 36 runs, 33 green and 3 expected skips, none red. That includes Repo Gates, Build & E2E, all 16 test shards, Type Check, Lint, Changeset Bump Policy (the @object-ui/react minor included) and Bundle Analysis (eager closure 3139.7 KB against the 3744.1 KB ceiling).
  • Merge state: clean against main 8d6e435.
  • Commit messages: seven commits carry only the model-free trailer pair, so the squash adds no model name to main.
  • Action: mark it ready for review and enable auto-merge through the merge queue. The body reads Part of #12811, so the card stays open. After the merge it goes back to triage with the record half named (ruling 2 of the ACCEPT).

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 17, 2026 09:19
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 17, 2026 09:19
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 17, 2026
Merged via the queue into main with commit 5ff64ef Oct 17, 2026
37 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-12811-scoped-object-metadata branch October 17, 2026 09:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants