Skip to content

Console: a console-created (package-less) app's routes and an out-of-bundle page route still read whole /meta/object, /meta/view and /meta/page lists #29416

Closed

Description

@objectstack-fleet

domain:ui#3 · zhuangjianguo · session_01QsR2jV4dWohojjbTJTQq5m

Filing gate: ② out-of-scope finding, class a (perf observation), from #29235 (PR #29413; report comment 6579566736, /mnt/project-files/issue-29235/os-dev-report.json) · filed by domain:ui#3 · ⛔ Not a claim. No grade: triage sets it.

Business reason. An admin who builds an app in the Console (no package behind it, the common shape for a tenant's own and AI-generated apps) pays for whole metadata lists on routes that name one item. #28645, #29121 and #29235 removed these list reads for packaged apps and for Home; a console-created app and a page outside the app bundle still download them, which is the heaviest first open a tenant sees (the object registry is 551 KB raw on app-crm).

Observed (the #29235 os-dev, CDP Network.requestWillBeSent initiator stacks on a production Console, objectstack dev --fresh, app-crm plus a runtime app ops_app).

  1. Inside a package-less app, useNavMemberGates reads its names-only scope with views, which reads the whole /meta/view list (7,014 B on app-crm), on every route of that app. Outside an app 租户首页为一个关着的命令面板读整个对象注册表(HotCRM 约 875 KB 原始 / 129 KB gz):HomeLayout 挂载时就把 useMetadata().objects 交给面板(cloud#2914 子卡 OU-1) #29121 already passes views: false.
  2. A package-less app's object routes read the whole object registry from AppContent: ui/packages/app-shell/src/console/appObjectScope.ts documents that "an app item without a package id has no package to scope by", so the route falls back to the registry instead of reading the route's object (and its lookup targets) by name.
  3. A page route for a page the app bundle does not hold reads the whole /meta/page list (AppContent's page scope, prefer-local) instead of the one page by name.

These are read-only inferences from the measured initiators, not re-measured by the seat.

Wanted. On these three routes, read the named item(s) by name (readObjectsByName, the by-name getItem read, or useReadItemsByName from PR #29413) while nothing else has loaded the list, and use the list once something has. Record overlays that need the whole registry for related lists keep it, as appObjectScope.ts already reasons. Labels and route behaviour unchanged.

Tests. Over the real MetadataProvider with a recording adapter: a package-less app's object route, its nav gates, and an out-of-bundle page route each ask no unscoped /meta/object, /meta/view or /meta/page; a CONTROL where the list is already loaded asks nothing more. Ablation back to the list read goes red. A before/after byte count on a production Console in the PR.

Landing site: ui/packages/app-shell (console/appObjectScope.ts, AppContent's page scope, useNavMemberGates). The three may split into separate PRs. Serialize after PR #29413 (#29235), whose by-name helper this reuses.

Dedupe: is:issue package-less app registry, is:issue appObjectScope, is:issue useNavMemberGates views, is:issue AppContent page scope list (only #29235, #28784, #29121, #28645 and epics/QA runs; #28784 is the packaged dashboard route). No card covers package-less app routes or out-of-bundle page routes.

Links back: #29235, PR #29413, #29121, #28645.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: p3 · pm:queue · 2026-11-02T15:00Z

    ⛔ Not a claim.


    Generated by Claude Code

  2. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Nov 2, 2026
  3. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#3 · zhuangjianguo · session_01QsR2jV4dWohojjbTJTQq5m

    Claim: PM loop round 20 · 2026-11-02T15:31Z
    Session: session_01QsR2jTxHVLQcafzV1G73bX (card thread, os-dev); dispatched by seat session session_01QsR2jV4dWohojjbTJTQq5m
    Account: zhuangjianguo
    Branch: claude/project-thread-w8lwl4 (platform-assigned to the card thread; it can push only this branch)
    Worktree: objectstack-issue-29416
    Domain: domain:ui
    Seat: domain:ui#3
    File surface: ui/packages/app-shell/src/hooks/useNavMemberGates.ts, ui/packages/app-shell/src/console/appObjectScope.ts, ui/packages/app-shell/src/console/AppContent.tsx (the object-scope and page-scope reads only), hooks/useObjectPresence.ts only to reuse or extend useReadItemsByName, their tests, and a changeset; a census/ratchet row that pins these readers is named in the thread before it is edited (stop on breach; explain in the report)
    Container & model: M, mode:thread, model: default tier (dispatch-gates --tier: no path-derived mandate), effort: high
    Clause-②: no
    Metadata-protocol: none
    Responsibility: open — recorded at review
    Thread-read: none
    Premise: holds (at origin/main 6fe99f86: useNavMemberGates.ts:~390 asks readObjectScope({ names: unionNames }) with views in a package-less app; appObjectScope.ts falls back to the whole registry for an app without a package id; AppContent.tsx:~490 picks an out-of-bundle page from the /meta/page list via preferLocal).
    Serial constraints cleared: PR #29413 (#29235), which this card reuses, landed as a6441636. No open PR of this seat touches these files.

  4. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#3 · zhuangjianguo · session_01QsR2jV4dWohojjbTJTQq5m

    Claim amended · 2026-11-02T15:33Z · on the os-dev's surface reading (at origin/main 5f7d554c), before any edit. This adds to claim 6581096637; branch, session and size are unchanged.

    Clause-②: no
    Metadata-protocol: none

    • Leg 3 has three readers, not one: AppContent's page scope, views/PageView.tsx and layout/AppHeader.tsx (the phone route title) each pick an out-of-bundle page from the /meta/page list with preferLocal. File surface widened to PageView.tsx and AppHeader.tsx, their page reads only.
    • preferLocal rule (accepted): GET /meta/page/NAME takes no package parameter and answers first match (ADR-0048 §3.3). In a package-less app the by-name answer is used; in a packaged app it is used only when its _packageId is the app's package, otherwise the list is kept. Pinned in a test.
    • Provider leg (conditional): providers/MetadataProvider.tsx readObjectScope composes a names-only scope's views over every name in the shared names-only entry (itemNames(entry.items)), which holds the nav gate's cross-app union, so a with-views reader in the app can still read the whole /meta/view list. File surface widened to that compose line (compose over the asked names, as the package scope does) and hooks/useServedViewItems.ts (comment only, if it goes false), ONLY after a jsdom probe proves the inference; if it does not hold, neither file is touched and the report says so.
    • Census: openPathObjectReaders-12811 rows for AppContent / PageView may change prose; name each moved row in the report.
  5. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#3 · zhuangjianguo · session_01QsR2jV4dWohojjbTJTQq5m

    Claim amended · 2026-11-02T15:50Z · second amendment, on the os-dev's probe, before any edit to the added branch. This adds to claim 6581096637 and amendment 6581167613; branch, session and size are unchanged.

    Clause-②: no
    Metadata-protocol: none

    • Provider leg proved: a jsdom probe (real MetadataProvider, bundle double) shows a with-views names-only read asks the /meta/view list only after a views: false union read put another app's object in the shared entry, and asks none with the compose line over the asked names. The MetadataProvider.tsx compose line is in, as amendment 6581167613 allowed.
    • File surface widened to the names-only branch of ui/packages/app-shell/src/hooks/useServedViewItems.ts (logic, not only its comment) and its test. Once leg 2 keeps the object type idle on a package-less app's object route, that branch reads only the app's requiresObject names and answers NONE_SERVED for a console-built app, so ObjectView's tabs and the AppHeader view crumb would run the bundle translation over every view: a label change the card forbids. The branch asks the app's navigation object entries plus its requiresObject names (answered by name, no list read). A test pins that the route's object views stay served and their labels unchanged on a package-less app.
  6. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#3 · zhuangjianguo · session_01QsR2jTxHVLQcafzV1G73bX
    os-dev-report

    {
      "issue": 29416,
      "status": "done",
      "branch": "claude/project-thread-w8lwl4",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/29542",
      "head": "3a894d8b",
      "session": "session_01QsR2jTxHVLQcafzV1G73bX",
      "premise_still_valid": true,
      "premise_note": "Valid on all three legs. A jsdom probe (amendment 6581536193) showed a second cause for leg 1: the names-only scope entry is shared, and a with-views read composed the views of every name the entry held, so the gate's cross-app union leaked into the route's view composition. Fixed at MetadataProvider.tsx:1744.",
      "summary": "Leg 1: useNavMemberGates asks its names-only union with views: false in a package-less app too, and the provider composes a names-only read's views only for the names its reader asked. Leg 2: readAppObjectScope reads a package-less app's object route by name (readObjectRouteNames: route object, requiresObject, palette nav objects; lookup targets by name without views), after the app list names the app. useServedViewItems' names-only branch serves navigation object names plus requiresObject, so view labels stay served. Leg 3: a page the bundle does not hold is read by name (readItemPreferLocal on useReadItemsByName) in AppContent's page scope, PageView and AppHeader's phone title. The answer is taken when the app has no package or the answer is its own package's; otherwise the list (preferLocal). Production Console, app-crm, ops_app without a package: object route before 24 requests / 821,430 B incl. GET /meta/object 551,834 B + /meta/view 7,014 B; after 33 / 365,762 B, no unscoped /meta/object (9 by-name reads replace the 551 KB list). Out-of-bundle page route before 29 / 335,061 B incl. /meta/page 15,463 B + /meta/view 7,014 B; after 28 / 313,334 B with /meta/page/crm_welcome 1,044 B, no /meta/page or /meta/view list. Visible text identical (en).",
      "files_changed": [
        ".changeset/29416-packageless-app-reads-by-name.md",
        "ui/packages/app-shell/src/console/AppContent.tsx",
        "ui/packages/app-shell/src/console/__tests__/AppContent.objectScope-12811.test.tsx",
        "ui/packages/app-shell/src/console/__tests__/AppContent.packagelessReads-29416.test.tsx",
        "ui/packages/app-shell/src/console/__tests__/AppContent.pageRouteObjects-12980.test.tsx",
        "ui/packages/app-shell/src/console/__tests__/appObjectScope-12811.test.tsx",
        "ui/packages/app-shell/src/console/appObjectScope.ts",
        "ui/packages/app-shell/src/hooks/__tests__/packagelessServedViews-29416.test.tsx",
        "ui/packages/app-shell/src/hooks/useNavMemberGates.ts",
        "ui/packages/app-shell/src/hooks/useObjectPresence.ts",
        "ui/packages/app-shell/src/hooks/useServedViewItems.ts",
        "ui/packages/app-shell/src/layout/AppHeader.tsx",
        "ui/packages/app-shell/src/layout/__tests__/AppHeader.pageTitleByName-29416.test.tsx",
        "ui/packages/app-shell/src/providers/MetadataProvider.tsx",
        "ui/packages/app-shell/src/views/PageView.tsx"
      ],
      "line_budget": "Against origin/main: source +180/-48 over 9 files, changeset +16, tests +717/-15 (new files 315, 196, 185 lines; three pins updated). Total passes ~600 only through tests; seat 3 ruled one PR, no split.",
      "tests": "New: AppContent.packagelessReads-29416 (7: object route no list; out-of-bundle page by name, bundled page asks nothing; CONTROL lists loaded; preferLocal packaged other-package answer -> list, own answer -> no list, package-less first match), packagelessServedViews-29416 (2: served views and byte-identical viewTabLabel labels vs whole registry; provider compose pin), AppHeader.pageTitleByName-29416 (2: by name, CONTROL). Updated pins: appObjectScope-12811, AppContent.objectScope-12811, AppContent.pageRouteObjects-12980. Census openPathObjectReaders-12811 unchanged, green. Ablations A1-A9 each red (3,2,5,4,1,1,1,1,3), restored with git diff HEAD empty. Sweeps: 210 files / 1,920 tests at bd111303; after merging main at 3a894d8b, console+hooks __tests__ 157 files / 1,273 tests (one red from the merge, fixed, file re-run green) and 95 PageView/AppHeader/AppContent/appObjectScope/new files / 754 tests. app-shell type-check 65/65 at both heads. eslint 0 errors.",
      "gates": "dispatch-gates --commands at bd111303: 34 derived, 34 run, all exit 0; --ran reconciled, 0 NOT-MEASURED. At 3a894d8b (merge of main): check-sdui-manifest and check-changeset-no-major exit 0. PR-body gates on the final body: check-changeset-no-major, check-adr-0087-registration, check-closing-keyword-parity, check-partof-closing-keyword all exit 0.",
      "dogfood_battery": "n/a, no ! in the PR title and no (narrowing) arm",
      "measurement": "Playwright login + CDP Network.requestWillBeSent initiator stacks (async depth 64) on a prod Console (objectstack dev --seed-admin --fresh, app-crm), runtime app ops_app saved via PUT /api/v1/meta/app/ops_app; before = app-shell sources restored from 5f7d554c and Console rebuilt. Script and JSON: /mnt/project-files/issue-29416/measure/.",
      "deviations": [
        "MetadataProvider.tsx compose line and useServedViewItems.ts names-only branch (logic), via amendments 6581167613 and 6581536193 after the jsdom probe.",
        "PageView.tsx and AppHeader.tsx page reads, via amendment 6581167613.",
        "Merge of main (#29472) needed two resolution fixes inside the merge commit: a duplicated ReadNamesWithoutViews import in appObjectScope.ts, and the preferLocal import in AppContent.tsx that main's new dashboard read uses.",
        "PR not subscribed for activity by the os-dev: re-runs and landing are the seat's (common rules)."
      ],
      "mcp_calls": "No GitHub MCP call of any kind. Reads: gh api issues/29416 + comments, issues/29235 comments.",
      "api_writes": "2 relay/stamped writes: fleet-write pr_create (PR #29542, body read back 11,596/11,596 bytes identical, no assignees) and this os-dev-report comment via post-stamped. Plus git push (not a REST write).",
      "open_questions": [],
      "security_findings": "none",
      "out_of_scope_findings": [
        "Every object route still reads the whole /meta/view list through ObjectView's saved-views overlay: ui/packages/app-shell/src/views/ObjectView.tsx:416, (dataSource as any).listViews(objectName, { previewDrafts }) -> data-objectstack listViews -> readViewList (ui/packages/data-objectstack/src/index.ts:6121). On ops_app's object route it is now the only /meta/view reader (7,014 B, measured initiator). class: observation (perf). reach: every object route, packaged apps included. carrier: none",
        "A packaged app's object route whose lookup target is outside the bundle reads /meta/view through the package scope's with-views lookup read (appObjectScope.ts readObjectRouteNames, packaged branch; measured on crm_app). class: observation. reach: packaged apps with out-of-bundle lookup targets. carrier: none",
        "A package-less app's record route still reads the /meta/page list (readPages in AppContent), and its root still reads the whole object registry. class: observation (read from source). reach: record routes and the root of console-created apps. carrier: none"
      ],
      "dedup_terms": [
        "ObjectView listViews meta/view",
        "saved-views overlay view list",
        "package-less record route meta/page",
        "lookup target views packaged app"
      ]
    }
  7. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#3 · zhuangjianguo · session_01QsR2jTxHVLQcafzV1G73bX
    os-dev-report

    {
      "issue": 29416,
      "status": "done",
      "branch": "claude/project-thread-w8lwl4",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/29542",
      "head": "3a894d8b",
      "session": "session_01QsR2jTxHVLQcafzV1G73bX",
      "premise_still_valid": true,
      "premise_note": "Valid on all three legs. A jsdom probe (amendment 6581536193) showed a second cause for leg 1: the names-only scope entry is shared, and a with-views read composed the views of every name the entry held, so the gate's cross-app union leaked into the route's view composition. Fixed at MetadataProvider.tsx:1744.",
      "summary": "Leg 1: useNavMemberGates asks its names-only union with views: false in a package-less app too, and the provider composes a names-only read's views only for the names its reader asked. Leg 2: readAppObjectScope reads a package-less app's object route by name (readObjectRouteNames: route object, requiresObject, palette nav objects; lookup targets by name without views), after the app list names the app. useServedViewItems' names-only branch serves navigation object names plus requiresObject, so view labels stay served. Leg 3: a page the bundle does not hold is read by name (readItemPreferLocal on useReadItemsByName) in AppContent's page scope, PageView and AppHeader's phone title. The answer is taken when the app has no package or the answer is its own package's; otherwise the list (preferLocal). Production Console, app-crm, ops_app without a package: object route before 24 requests / 821,430 B incl. GET /meta/object 551,834 B + /meta/view 7,014 B; after 33 / 365,762 B, no unscoped /meta/object (9 by-name reads replace the 551 KB list). Out-of-bundle page route before 29 / 335,061 B incl. /meta/page 15,463 B + /meta/view 7,014 B; after 28 / 313,334 B with /meta/page/crm_welcome 1,044 B, no /meta/page or /meta/view list. Visible text identical (en).",
      "files_changed": [
        ".changeset/29416-packageless-app-reads-by-name.md",
        "ui/packages/app-shell/src/console/AppContent.tsx",
        "ui/packages/app-shell/src/console/__tests__/AppContent.objectScope-12811.test.tsx",
        "ui/packages/app-shell/src/console/__tests__/AppContent.packagelessReads-29416.test.tsx",
        "ui/packages/app-shell/src/console/__tests__/AppContent.pageRouteObjects-12980.test.tsx",
        "ui/packages/app-shell/src/console/__tests__/appObjectScope-12811.test.tsx",
        "ui/packages/app-shell/src/console/appObjectScope.ts",
        "ui/packages/app-shell/src/hooks/__tests__/packagelessServedViews-29416.test.tsx",
        "ui/packages/app-shell/src/hooks/useNavMemberGates.ts",
        "ui/packages/app-shell/src/hooks/useObjectPresence.ts",
        "ui/packages/app-shell/src/hooks/useServedViewItems.ts",
        "ui/packages/app-shell/src/layout/AppHeader.tsx",
        "ui/packages/app-shell/src/layout/__tests__/AppHeader.pageTitleByName-29416.test.tsx",
        "ui/packages/app-shell/src/providers/MetadataProvider.tsx",
        "ui/packages/app-shell/src/views/PageView.tsx"
      ],
      "line_budget": "Against origin/main: source +180/-48 over 9 files, changeset +16, tests +717/-15 (new files 315, 196, 185 lines; three pins updated). Total passes ~600 only through tests; seat 3 ruled one PR, no split.",
      "tests": "New: AppContent.packagelessReads-29416 (7: object route no list; out-of-bundle page by name, bundled page asks nothing; CONTROL lists loaded; preferLocal packaged other-package answer -> list, own answer -> no list, package-less first match), packagelessServedViews-29416 (2: served views and byte-identical viewTabLabel labels vs whole registry; provider compose pin), AppHeader.pageTitleByName-29416 (2: by name, CONTROL). Updated pins: appObjectScope-12811, AppContent.objectScope-12811, AppContent.pageRouteObjects-12980. Census openPathObjectReaders-12811 unchanged, green. Ablations A1-A9 each red (3,2,5,4,1,1,1,1,3), restored with git diff HEAD empty. Sweeps: 210 files / 1,920 tests at bd111303; after merging main at 3a894d8b, console+hooks __tests__ 157 files / 1,273 tests (one red from the merge, fixed, file re-run green) and 95 PageView/AppHeader/AppContent/appObjectScope/new files / 754 tests. app-shell type-check 65/65 at both heads. eslint 0 errors.",
      "gates": "dispatch-gates --commands at bd111303: 34 derived, 34 run, all exit 0; --ran reconciled, 0 NOT-MEASURED. At 3a894d8b (merge of main): check-sdui-manifest and check-changeset-no-major exit 0. PR-body gates on the final body: check-changeset-no-major, check-adr-0087-registration, check-closing-keyword-parity, check-partof-closing-keyword all exit 0.",
      "dogfood_battery": "n/a, no ! in the PR title and no (narrowing) arm",
      "measurement": "Playwright login + CDP Network.requestWillBeSent initiator stacks (async depth 64) on a prod Console (objectstack dev --seed-admin --fresh, app-crm), runtime app ops_app saved via PUT /api/v1/meta/app/ops_app; before = app-shell sources restored from 5f7d554c and Console rebuilt. Script and JSON: /mnt/project-files/issue-29416/measure/.",
      "deviations": [
        "MetadataProvider.tsx compose line and useServedViewItems.ts names-only branch (logic), via amendments 6581167613 and 6581536193 after the jsdom probe.",
        "PageView.tsx and AppHeader.tsx page reads, via amendment 6581167613.",
        "Merge of main (#29472) needed two resolution fixes inside the merge commit: a duplicated ReadNamesWithoutViews import in appObjectScope.ts, and the preferLocal import in AppContent.tsx that main's new dashboard read uses.",
        "PR not subscribed for activity by the os-dev: re-runs and landing are the seat's (common rules)."
      ],
      "mcp_calls": "No GitHub MCP call of any kind. Reads: gh api issues/29416 + comments, issues/29235 comments.",
      "api_writes": "2 relay/stamped writes: fleet-write pr_create (PR #29542, body read back 11,596/11,596 bytes identical, no assignees) and this os-dev-report comment via post-stamped. Plus git push (not a REST write).",
      "open_questions": [],
      "security_findings": "none",
      "out_of_scope_findings": [
        "Every object route still reads the whole /meta/view list through ObjectView's saved-views overlay: ui/packages/app-shell/src/views/ObjectView.tsx:416, (dataSource as any).listViews(objectName, { previewDrafts }) -> data-objectstack listViews -> readViewList (ui/packages/data-objectstack/src/index.ts:6121). On ops_app's object route it is now the only /meta/view reader (7,014 B, measured initiator). class: observation (perf). reach: every object route, packaged apps included. carrier: none",
        "A packaged app's object route whose lookup target is outside the bundle reads /meta/view through the package scope's with-views lookup read (appObjectScope.ts readObjectRouteNames, packaged branch; measured on crm_app). class: observation. reach: packaged apps with out-of-bundle lookup targets. carrier: none",
        "A package-less app's record route still reads the /meta/page list (readPages in AppContent), and its root still reads the whole object registry. class: observation (read from source). reach: record routes and the root of console-created apps. carrier: none"
      ],
      "dedup_terms": [
        "ObjectView listViews meta/view",
        "saved-views overlay view list",
        "package-less record route meta/page",
        "lookup target views packaged app"
      ]
    }
  8. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#3 · zhuangjianguo · session_01QsR2jV4dWohojjbTJTQq5m

    Landed · 2026-11-02T19:20Z

    PR #29542 merged at 2026-11-02T19:19Z as ff827bcf. Closes #29416 closed this card, and pm:dispatched is removed.

    What landed: a console-created (package-less) app no longer downloads the whole metadata lists for these reads:

    • The sidebar gate asks its requiresObject union without views.
    • An object route reads its own objects by name. Measured on app-crm, it went from 821,430 B to 365,762 B, with no unscoped /meta/object.
    • A page outside the app bundle is read by name, and only when the answer belongs to the app's own package (otherwise the list read stays).

    The remaining list reads are filed as #29550 (the ObjectView saved-views /meta/view read), #29551 (a packaged app's out-of-bundle lookup read with views) and #29552 (a package-less record route's /meta/page list and its root's object registry). Nothing is left on this card.

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

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions