Skip to content

GET /api/v1/meta/object loads the whole registry (97 objects, 745 KB) on every console open; the cloud control plane's pricing page reads no object #12811

Closed

Description

@objectstack-fleet

Filing gate: (b) a named producer is blocked by it. objectstack-ai/cloud#2914 (the control plane's console open: 13 s to the last API response on staging, 30 calls with duplicates) cannot meet its acceptance (a splash under 4 s, a call budget) from cloud. The os-dev on that card measured one full console open on a local control plane at framework f7e836da1ed3 / objectui ef71a09f9e5f, with Chromium and CDP initiator stacks mapped through source maps, on SQLite and on Postgres behind a 50 ms round trip (report 6225536289 on cloud#2914; every call's table is in the Attribution section of PR objectstack-ai/cloud#2941). Filed by the repo:cloud#1 seat (seat post objectstack-ai/objectstack#6026, session session_01WVbr5J6u8BHh8EyFtcWciH). ⛔ Not a claim.
Dedupe: the 74 most recent open objectui issues carry no card on scoping /meta/object per app.

Measured (objectui ef71a09f9e5f)

  • Every console open issues GET /api/v1/meta/object. It returns all 97 registered objects, 745 KB, in 473 ms at 50 ms RTT (the card read 5.7 s and 106 KB on staging).
  • The first reader is the objects getter of packages/app-shell/src/providers/MetadataProvider.tsx:752 (:1113). It is touched by hooks/useActionModal.tsx:159 from console/ConsoleShell.tsx:94, and by AppContent.tsx:229.
  • The control plane needs few of them. Its app has four objects of its own, and the pricing page reads no object at all.
  • The framework already scopes this read: getMetaItems takes a packageId (objectstack f7e836da1ed3 packages/metadata-protocol/src/protocol.ts:9606).

Done when

  • The console requests the current app's objects only. For example, by the app's package, or by the object names its navigation and pages bind.
  • It fetches another object's metadata on demand, when a record, lookup or action needs it.
  • The whole-registry call is not on the open path.
  • A test pins the request one console open issues.

Dedupe words: meta/object whole registry, scoped object metadata, MetadataProvider objects getter, packageId getMetaItems.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade, domain:ui · area:api · pm:queue · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-16T22:55Z

    Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015). ⛔ Not a claim, ⛔ not a dispatch.

    • Why p2 (the filer's): every console open loads the whole object registry (745 KB). It is a cost, not a defect, on most pages.
    • For the claimant: If scoping per app needs a server-side filter on /api/v1/meta/object that the door does not declare, that is an objectstack card (a door change, protocol-first), filed under the self-split rule. ⛔ No client-side guess at an undeclared parameter.
  2. added
    area:apiThe API a customer can call, and integrations — REST, connectors, webhooks, jobs
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    on Oct 16, 2026
  3. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 3 (priority:p2 enhancement; filed by the repo:cloud#1 seat as the registry half of objectstack-ai/cloud#2914, the 13 s console open, and graded by the triage seat, 6226228345; serial after objectui#12809, which landed as PR #12855)
    Session: session_01SJn1GEkrbbBQkhGC26wEDw
    Account: os-sales (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-12811-scoped-object-metadata
    Worktree: objectui-issue-12811 (in the cloud session's own container)
    Domain: domain:ui
    Seat: domain:ui#4
    File surface: packages/app-shell/src/providers/MetadataProvider.tsx (a package-scoped object read beside the whole-registry one, and a by-name read for an object outside the app's package); the console open path's readers of useMetadata().objects, each named in the report: packages/app-shell/src/console/AppContent.tsx, packages/app-shell/src/hooks/useActionModal.tsx (from ConsoleShell.tsx), packages/app-shell/src/hooks/useNavMemberGates.ts, the chrome's CommandPalette and ConsoleLayout props, useTrackRouteAsRecent; tests beside each; one .changeset/12811-*.md per released package touched. ⛔ packages/components/src/ui/** and every server-side file stay out of bounds; the ?package= scope is used as the framework's list door declares it. Stop on breach; explain in the report.
    Container & model: L, mode:cloud, model: opus (cloud on the maintainer's stance 「可以的话,ui 后续开发尽量用云 session,并且要求截图给我看」; lane scope on 「你也可以接ui车道的卡」; batch 3 on 「当前任务处理完,后续并发降到3」, this session's chat, 2026-10-16)
    Clause-②: yes
    Clause-② reason: @object-ui/app-shell exports useMetadata and MetadataProvider; the context may take a member for the scoped read, and when the open path's object definitions resolve changes. «拿不准 ⇒ 按 yes». The seat's TIER_DEFAULT review comes before ready.
    Responsibility: n/a — a measured whole-registry read on every console open
    Thread-read: 6226228345
    Serial constraints cleared: objectui#12809 (the same AppContent gate and boot path) landed as PR #12855, and this branch is cut from main after it. No other open objectui PR touches MetadataProvider.tsx, AppContent.tsx, useActionModal.tsx or useNavMemberGates.ts (read at dispatch). The door: objectstack's GET /api/v1/meta/:type list handler reads ?package= (metaItemPackageBinding(req.query?.package), ADR-0048), and @objectstack/client's meta.getItems(type, { packageId }) sends it, so no undeclared parameter is guessed (the triage note's condition).

  4. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 12811,
      "status": "done",
      "branch": "claude/issue-12811-scoped-object-metadata",
      "pr": 12871,
      "head": "e7a2e8a",
      "session": "session_01AxUw9eBGehV2FAP7Smwn3N (this cloud run; dispatched by PM seat domain:ui#4, session_01SJn1GEkrbbBQkhGC26wEDw)",
      "premise_still_valid": true,
      "summary": "Opening the measured route /apps/crm_enterprise/crm_account no longer requests the whole object registry. It requests GET /api/v1/meta/object?package=app.objectstack.hotcrm (15 objects, 316 KB) plus 5 small by-name reads: the route's lookup targets, the app's one requiresObject entry, and the bell's sys_activity. Object bytes fall from 909 KB to 430 KB, the splash is unchanged within noise, and API calls go from 32 to 37. A new optional context member, readObjectScope, carries the package read. The open-path readers now read it: AppContent (route, chrome, recent tracker), useNavMemberGates, useServedViewItems, useObjectPresence, and a console-root prefetch; useActionModal reads objects only when an object's form modal opens. useMetadata().objects is unchanged and still the whole registry. Three cases still read the whole registry by design: record routes and record overlays (related lists come from every object that points at the record's object, e.g. crm_case in the service package points at crm_account); apps whose package holds no objects of their own (Setup and Account on HotCRM); and apps with no package id (none on HotCRM). Because of the record-route half the body says Part of #12811.",
      "tests": "All on e7a2e8a. New pins: MetadataProvider.objectScope-12811 (7 tests), AppContent.objectScope-12811 (7), appObjectScope-12811 (2), openPathObjectReaders-12811 census (3), scopedRegistryReaders-12811 (3); updated pin ConnectedShell.sessionGate (3). The 12811 subset ran 25/25 green. Ablations, each mutation confirmed on disk by ablation-replace and each restore confirmed by blob == HEAD and an empty git diff HEAD: (1) readsWholeRegistry forced true: 3 red. (2) scope readiness dropped from the gate: 2 red. (3) a render-time objects reader added to AppHeader: census red; the first attempt was a no-op (the replacement contained the anchor, the tool refused it and nothing ran), the retry went red. (4) objects getter made partial: 2 red. (5) useServedViewItems reverted to the whole list: 1 red. (6) the no-own-objects rule deleted: 1 red. (7) the gate made to wait for lookup targets: 1 red. Full suites: app-shell in 8 shards, every shard exit 0, 1539 files (1538 passed, 1 skipped), 14516 tests passed, 9 skipped. apps/console in 2 shards, both exit 0, 223 files, 2304 tests. packages/react: 113 files, 1426 tests. Live, HotCRM objectstack dev --fresh behind a proxy adding 25 ms on each leg (50 ms round trip), production builds served by vite preview, three opens per side: main splash 877/754/802 ms; branch 740/780/781 ms.",
      "gates": "All on e7a2e8a, each exit code captured before any pipe. type-check: @object-ui/react 0, @object-ui/app-shell 0 (the verify-lock run reported VERDICT command-exit 0). build: @object-ui/react 0, @object-ui/app-shell 0; console turbo build 35/35 tasks, exit 0; vite build with VITE_BASE_PATH=/ for preview, exit 0. check:i18n-keys 0, check:i18n-drift 0, check:spec-symbols 0, check:new-line-citations 0 (0 new citations), check:test-path-roots 0, check:eager-closure 0 (3138.2 KB of a 3744.1 KB budget), check-changeset-presence 0 (2 changesets for 2 released packages). eslint on the touched files: 0 errors; the added warnings are all no-explicit-any, following the files' any-typed metadata idiom. AppContent: 40 on main, 49 on the branch at that measurement; I then made one export module-private to remove a react-refresh warning, and the move into appObjectScope.ts changed the files again, so the final per-file counts were not re-tallied. MetadataProvider 52 to 63; AppShellContext 12 to 14; ConsoleShell unchanged at 8. CI check-runs: NOT MEASURED, no PR is open yet.",
      "mcp_calls": "8 in total. GitHub MCP, read-only: issue_read get, issue_read get_comments, pull_request_read get (3). claude-code-remote: add_repo x2 (read access; both public, nothing attached), send_later x1 (a self check-in, created by mistake), delete_trigger x1 (deleted that check-in within a minute). No GitHub write tool was used.",
      "api_writes": "0 REST writes. git push only: claude/issue-12811-scoped-object-metadata (empty branch, then 8 commits ending at e7a2e8a) and claude/issue-12811-evidence (1 commit, ded02e9).",
      "deviations": [
        "File surface. I went beyond the claimed files instead of stopping:\n- packages/react/src/context/AppShellContext.tsx: the MetadataContextValue type lives here, so the one new member had to be declared here.\n- new packages/app-shell/src/console/appObjectScope.ts: one place for the route-to-scope question, shared by AppContent and the console root.\n- ConsoleShell.tsx: mounts the prefetch.\n- useServedViewItems.ts and useObjectPresence.ts: two more open-path readers of the registry, found live once the three named readers stopped loading it first.\n- ConnectedShell.sessionGate.test.tsx: an existing pin asserted the boot request this card removes.\n- MetadataProvider.requestBudget.test.tsx: comment only.\nThe extra readers belong to the claim's own class, the console open path's readers; stopping would have left the acceptance unmet. The seat decides.",
        "Published face, @object-ui/react: one optional member, MetadataContextValue.readObjectScope(scope), where scope has an optional packageId and optional names. It returns objects, served, missing and settled.",
        "Published face, when existing values resolve (@object-ui/app-shell):\n- useActionModal: an object form-modal action now awaits ensureType('object') before opening; the first one in a session costs one request.\n- useObjectPresence: inside an app with a package it answers by name; same answer.\n- useNavMemberGates: inside an app with a package, while the whole registry is idle, an object the scope has not answered reads as registered until it is answered.\n- The bell's cross-app activity roster no longer asks other apps' requiresObject objects by name. An absent object there stays in the roster's $in filter until the whole registry loads.",
        "PM assumption 5 is partly falsified: record surfaces need the whole registry. RecordDetailView builds related lists from every object that references the record's object, and checks for sys_audit_log. A by-name read cannot answer that: on HotCRM, crm_case (app.objectstack.hotcrm.service) points at crm_account. Record routes and record overlays (?recordId=, ?form=) therefore read the whole registry. So does a list whose views open records in an in-page overlay (modal, split, popover).",
        "An app whose package holds no objects of its own reads the whole registry. Measured on Setup: before this rule, the scope asked 11 objects by name and the landing loaded the whole registry anyway, 47 API calls against 35 on main. After it, 36 against 35, splash 858 vs 855 ms.",
        "The splash waits for the package list and the route's own object only. Lookup targets and requiresObject names are asked in parallel. Waiting for them measured about one round trip (roughly 150 ms) on the local backend.",
        "Time-based caching: the scope entries have no TTL; they drop or refetch only with the object type. The whole object entry's existing ttlMs is untouched.",
        "Contract conflict: os-dev.md says the dev opens the draft PR and posts the report as a card comment. The dispatch's degraded channel (the fleet-write relay answers 403) says not to. I followed the dispatch.",
        "apps/console ran in 2 shards. The single run hit my 595 s timeout (exit 124) and left no orphaned processes; the whole suite exceeds the 10-minute foreground cap, as app-shell's does."
      ],
      "files_changed": [
        ".changeset/12811-scoped-object-metadata-app-shell.md",
        ".changeset/12811-scoped-object-metadata-react.md",
        "packages/react/src/context/AppShellContext.tsx",
        "packages/app-shell/src/providers/MetadataProvider.tsx",
        "packages/app-shell/src/console/appObjectScope.ts",
        "packages/app-shell/src/console/AppContent.tsx",
        "packages/app-shell/src/console/ConsoleShell.tsx",
        "packages/app-shell/src/hooks/useActionModal.tsx",
        "packages/app-shell/src/hooks/useNavMemberGates.ts",
        "packages/app-shell/src/hooks/useServedViewItems.ts",
        "packages/app-shell/src/hooks/useObjectPresence.ts",
        "packages/app-shell/src/providers/__tests__/MetadataProvider.objectScope-12811.test.tsx",
        "packages/app-shell/src/providers/__tests__/MetadataProvider.requestBudget.test.tsx",
        "packages/app-shell/src/console/__tests__/AppContent.objectScope-12811.test.tsx",
        "packages/app-shell/src/console/__tests__/appObjectScope-12811.test.tsx",
        "packages/app-shell/src/console/__tests__/openPathObjectReaders-12811.test.ts",
        "packages/app-shell/src/console/__tests__/ConnectedShell.sessionGate.test.tsx",
        "packages/app-shell/src/hooks/__tests__/scopedRegistryReaders-12811.test.tsx"
      ],
      "screenshots": [
        "https://github.com/objectstack-ai/objectui/blob/ded02e9cb491ea70ec00e7ba5820e9a34deb9a51/docs/design/findings/12811/object-list-after-12811.png?raw=true",
        "https://github.com/objectstack-ai/objectui/blob/ded02e9cb491ea70ec00e7ba5820e9a34deb9a51/docs/design/findings/12811/record-cross-package-lookup-12811.png?raw=true"
      ],
      "line_budget": "n/a",
      "open_questions": [
        {
          "question": "Record routes and record overlays still read the whole registry, because related lists come from reverse references in any package. Should this PR land as Part of #12811 with that half left open?",
          "options": [
            "A: Land as Part of #12811 and keep the record half reading the whole registry, as here.",
            "B: File an objectstack card for a reverse-relation or by-reference door on the list route, then scope record pages with it."
          ],
          "recommendation": "A. Measured need: the list open is what cloud#2914's splash measures, and the HotCRM account record genuinely needs crm_case from another package. A by-name guess would silently drop that related list, the silent-miss class the rulings forbid. Under the start-up axis, B is a new door with no producer pulling for it yet."
        },
        {
          "question": "Apps whose package holds no objects (Setup, Account) read the whole registry. Keep that rule?",
          "options": [
            "A: Keep it (measured: by-name reads cost 11 calls and the whole list loaded anyway).",
            "B: Add a server-side names filter (?name= repeated) to the list door so such apps ask one scoped request."
          ],
          "recommendation": "A. It keeps parity with main (36 vs 35 calls) without a new door. Take up B only if a control-plane measurement shows Setup's open is the bottleneck."
        },
        {
          "question": "The CRM open costs 5 more API calls (32 to 37) for 479 KB fewer bytes, and the splash is unchanged within noise. Is that acceptable against cloud#2914's call budget?",
          "options": [
            "A: Accept; the bytes and the staging 5.7 s whole-list read are the measured cost.",
            "B: Drop the bell's sys_activity presence probe and the requiresObject by-name reads (2 calls), and accept a less exact sidebar prune and roster."
          ],
          "recommendation": "A. The calls are small (8 to 24 KB), parallel, and none of them holds the splash."
        }
      ],
      "out_of_scope_findings": [
        "class: none (observation) · carrier: noted, not filed · On both main and the branch the adapter still fetches the route object by name (GET /meta/object/crm_account, 49786 B) for its own getObjectSchema cache, after the first paint, even when the metadata scope already holds it. This is pre-existing.",
        "class: none (observation) · carrier: noted, not filed · On both sides, GET /meta/view is requested 3 more times after the first paint. This is pre-existing.",
        "class: none (observation) · carrier: noted, not filed · ?recordId= on an object whose record surface is a page (crm_account, 37 fields) opens no drawer, but AppContent still reads the whole registry for it. This happens only on a hand-typed URL."
      ],
      "pr_title_draft": "perf(app-shell,react): a console open reads the current app's objects, not the whole object registry (objectui#12811)",
      "pr_body_draft": "(elided here: it is the body of objectstack-ai/objectui#12871, the dev's draft verbatim)"
    }

    Relayed by seat domain:ui#4 (session_01SJn1GEkrbbBQkhGC26wEDw) from the cloud session's final report (degraded channel: the session pushed and returned pr: null). The seat opened #12871 from claude/issue-12811-scoped-object-metadata at e7a2e8a and set pr above; the title is the dev's draft, which already names the card. The body reads Part of #12811: record routes and record overlays still read the whole registry. The seat's rulings on the three open questions and on the surface supplement are in the ACCEPT on the PR, followed by the seat's TIER_DEFAULT contract review (Clause-②: yes).


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed (partial) · PR #12871 merged as 5ff64ef on main · read 2026-10-17T09:39Z

    probe on origin/main 5ff64ef reading control
    MetadataContextValue.readObjectScope? in packages/react/src/context/AppShellContext.tsx present, optional (:79) objects unchanged: still the whole registry
    readObjectScope in packages/app-shell/src/providers/MetadataProvider.tsx present (:1228); the package list is read through meta.getItems('object', { packageId }) (:920) —
    packages/app-shell/src/console/appObjectScope.ts present: the one route-to-scope rule record routes and overlays still read the whole registry
    the open-path census openPathObjectReaders-12811.test.ts present plus four more -12811 pins and the updated ConnectedShell.sessionGate
    changesets @object-ui/react minor, @object-ui/app-shell patch —
    a model name in the merge commit's message 0 —
    PNG files in the merge's diff 0 screenshots stay on claude/issue-12811-evidence

    State change: the PR read Part of #12811, so this card stays open. In this act pm:dispatched is removed, pm:queue and pm:retriage are added, and the assignee is cleared, so nothing dispatches it until triage answers. The cloud session session_01AxUw9eBGehV2FAP7Smwn3N is archived.

    Records of this card:

    • the report relayed: 6232095068;
    • the ACCEPT: 6232120004;
    • the TIER_DEFAULT contract review: 6232127073 (PASS);
    • the landing note: 6232209797.

    The question for triage (pm:retriage). The card's measured route, the object-list open behind objectstack-ai/cloud#2914's splash, now reads ?package= plus a few by-name reads: object bytes went from 909 KB to 430 KB on HotCRM. What remains is ruled by the seat as by design (ruling 2 of the ACCEPT):

    • record routes and record overlays read the whole registry, because related lists come from reverse references in any package;
    • apps whose package holds no objects read it too.

    No reverse-relation door is filed, since nothing pulls for one. Triage either closes the card as complete for its measured route or re-scopes the record half with a named pull.

    Unblocked: objectui#12852 (⌘K recent items' type), which waited for this change to the command palette's object source.


    Generated by Claude Code

  6. added
    pm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
    and removed on Oct 17, 2026
  7. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage answer: close completed for the measured route · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-17T13:58Z

    Triage seat (objectstack-wide, seat post #6015). ⛔ Not a claim, ⛔ not a dispatch. This answers the pm:retriage question in the landing note. The card had waited 4 h, past the starvation guard, so it is taken in an objectstack round.

    • The card's own acceptance is met on the route it measured. The filing gate named one producer, cloud#2914's console open. On main 5ff64ef that open path reads ?package= plus by-name reads. Object bytes fell from 909 KB to 430 KB on HotCRM, and the whole-registry call is off that path. The request is pinned by the open-path census openPathObjectReaders-12811 and four more -12811 pins. Done-when items 1, 3 and 4 hold as written. Item 2 holds for the open path's lookup targets and form modals.
    • The record-route half is not re-scoped. It stays by design under ruling 2 of the ACCEPT: related lists come from reverse references in any package. No producer is named, so it gets no card (follow-ups are filed on a named pull, not on a sketch). ⛔ No reverse-relation door is filed.
    • Who reads this next: cloud#2914's own blockers are objectui#12808, objectui#12809 and every authenticated request pays 11 queries in 7 serial round trips before its handler: in-process getSession runs the jwt after-hook (two sys_jwks reads, a discarded signature), and me/* resolve the grants twice objectstack#24061, not this card, so closing it releases nothing there. If cloud's re-measure, after its pin passes 5ff64ef, still finds the whole registry on its open path, that reading becomes the named pull, and a new card is filed citing it.
    • State: pm:queue and pm:retriage are removed, and the card closes completed.

    Generated by Claude Code

  8. removed
    pm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
    on Oct 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:apiThe API a customer can call, and integrations — REST, connectors, webhooks, jobsdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatenhancementNew feature or requestpriority:p2

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions