Skip to content

perf(app-shell): a packaged app's record route reads the objects pointing at the record through its package scope, not the registry - #29846

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-04g04g
Nov 3, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-04g04g

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Steedos · project thread

Fixes #29681

Clause-②: no
Metadata-protocol: none

Claim: 6596739699. Design approved by the seat before code (step 1: the provider's existing { packageId, names } scope, not a names-only read beside it).

维护者速读

  • 改了什么:在有包的应用(如 CRM 示例的 crm_app)里打开一条记录、而该对象没有声明记录页时,不再下载整张对象注册表;改为读应用所在包的对象列表,再按名读取查找目标和其他包里指向该对象的对象(这些对象已在应用 bundle 里,不发请求)。
  • 为什么改:在 CRM 示例上,这类记录页一次打开共 1,584,710 B,其中整张对象注册表 561,247 B、视图整表 7,014 B;改后为 1,075,729 B,少了约 510 KB。
  • 风险与回滚:相关列表的集合与顺序都不变,实测改前改后页面正文逐字一致(只差相对时间),另一个包里新建的 Visits 对象在改前改后都排在同一位置。导航外的对象、无 bundle、草稿预览、记录抽屉仍读整表,实测抽屉与改前同为 24 个请求。回滚方法是 revert 本 PR。
  • 先看:ui/packages/app-shell/src/console/appObjectScope.ts 记录分支里 !page 的处理(去掉了有包应用直接读整表的那一行);测试文件末尾新增的 objectstack#29681 那组用例。
  • 两分钟验证:打开 CRM 应用的任意一条客户(Account)记录,在开发者工具 Network 里过滤 meta/object:应看到 meta/object?package=com.example.crm 和几条按名读取,没有不带参数的 meta/object;页签仍是 Activities、Contacts、Leads、Opportunities。
  • 需要你做的:无,席位落地。

What changes

A record route whose object has no declared record page draws a synthesized page, which derives its related lists from every object that points at the record's object. Since #29697 an app with no package reads those objects by name from the app bundle (BundledReads.objectsReferencing). A packaged app still answered scope: null there and read the whole object registry. It now reads the same names through its package scope, the shape its object routes already read: the package list (/meta/object?package=), then by name each name the list does not hold. A pointing object from another package is one of those names, answered from the bundle with no request. inRelatedOrder keeps the bundle's (object-list) order.

  • ui/packages/app-shell/src/console/appObjectScope.ts: the record arm's !page branch no longer returns scope: null for a packaged app; it hands packageId to readObjectRouteNames. The module header says so.
  • No MetadataProvider.tsx or bundledReads.ts change: objectsReferencing was never package-gated, and the provider's { packageId, names } scope already reads a name outside the package by name.

Unchanged: an object outside the app's navigation, no bundle, draft preview, a declared record page, a record form overlay, the record drawer (reads the registry, live: same 24 requests), the package-less route, and the /meta/page list.

Why

Measured on a production Console (app-crm, crm_app, Initech crm_account, no declared record page), the registry was 561,247 B of the record open's 1,584,710 B.

Tests

In the shared harness console/__tests__/AppContent.packagelessRecordRelated-29552.test.tsx, over the real MetadataProvider and AppContent, a new top-level describe (7 cases). The fixture adds ops_visit, an object outside the app's package that points at the account, listed between the package's own objects:

  • the packaged record route reads no unscoped /meta/object: exactly object?package=app.crm and object/sys_user (the cross-package object comes from the bundle);
  • the related lists equal the registry's in set AND order, ops_visit.account in its registry place (pinned literally);
  • an object pointed at only from another package (crm_case to crm_contact) draws that list;
  • CONTROLs: a declared record page reads the objects it binds as before; an object outside the navigation reads the registry; no bundle reads the registry; a record drawer reads the registry.

Flipped: the old CONTROL "a packaged app's record route is unchanged: the registry, and nothing by name" is removed; the new describe replaces it. The package-less pins are byte-unchanged in their assertions. The harness now reads its object set and page list from server.objects / server.pages (reset to the old values in beforeEach), so the packaged describe can add ops_visit without touching them.

Ablations through scripts/ablation-replace.mjs, each restored with git diff HEAD empty and the blob equal to HEAD:

  • A1, put the package gate back (if (packageId) return { candidate, scope: null, … }): 2 red at daaec33, the packaged no-unscoped-read pin and the cross-package crm_case pin (the order pin stays green: the registry draws that order too). At ad4df19 it was 3 red: the drawer pin then also asserted a package-list call that only the harness makes, narrowed in daaec33 (see Acceptance notes);
  • A2, drop inRelatedOrder: 2 red, the packaged order pin with ops_visit and the package-less requiresObject order pin.

Sweep at ad4df19: the 135 app-shell test files naming AppContent, appObjectScope or bundledReads, plus every provider test, 1,118 tests passed. @object-ui/app-shell type-check (both programs) exit 0 at ad4df19 and again at the merged head f440eb5; ESLint 0 errors on the changed files. Harness 17/17 at daaec33. Final head 8d42b23 merges main again (42ff86f); none of the incoming commits touches the changed files or MetadataProvider.tsx / bundledReads.ts.

Acceptance notes

  • The record drawer pin. In the harness, the drawer case also read the package list (the scope is read before the drawer's registry read starts). Live it does not: the registry is in flight first and the package scope is cut from it, so the drawer route is the same 24 requests, 1,586,558 B, as on main. The pin asserts only the registry read, as the package-less drawer pin does.
  • Sidebar presence. With no registry read, the sidebar's presence gate answers from the package scope entry the route also reads (one ?package= request shared). Sampled every 25 ms from navigation start, the sidebar went from 0 to all 7 entries in one step, at 1,109 ms before and 1,046 ms after; no entry flashed or went missing.
  • New by-name reads that the registry used to answer: sys_organization 12,259 B and sys_business_unit 8,874 B (the account's lookup targets, without views, as the packaged list route reads them), and sys_comment_reaction 4,291 B (RecordDetailView's useObjectPresence for reactions).
  • Cross-package inbound, live. ops_visit was saved with PUT /meta/object/ops_visit (outside com.example.crm, a lookup to crm_account). The server put it in the crm_app bundle and the registry lists it as inbound of crm_account, so it is answered from the bundle. Before and after, the tabs are Overview, Activity, Activities, Contacts, Leads, Opportunities, and the overflow menu holds Visits.
  • GET /api/v1/packages (676,423 B) is read on this route before and after; not this card's read.

Verification

Production Console (pnpm build, pnpm console:build), app-crm objectstack dev --seed-admin --fresh, measure2.cjs (about:blank then a fresh goto, cache disabled, CDP initiators). The before leg is a Console built from main's appObjectScope.ts (be6b15c).

Per read, packaged crm_app Initech crm_account record (no declared page)
read before (be6b15c) after (ad4df19) final (f440eb5, merged main)
total 24 requests, 1,584,710 B 26 requests, 1,074,028 B 26 requests, 1,075,729 B
/meta/object (registry) 561,247 none none
/meta/view (whole) 7,014 none none
/meta/object?package=com.example.crm none 32,008 32,008
object/sys_user 28,467 28,467 28,467
object/sys_organization, object/sys_business_unit none 12,259 + 8,874 12,259 + 8,874
object/sys_comment_reaction none 4,291 4,291
/meta/page 15,463 15,463 15,463
record drawer over the route (?recordId=) n/a n/a 24 requests, 1,586,558 B: registry 561,247 + view 7,014, no ?package=

Page text before and after is identical except the record's relative created time. Data, screenshots (tabs.png, tabs-overflow-open.png, record-full.png per leg) and scripts (measure2.cjs, tabs.cjs, probe.cjs, xpkg.cjs) are in the project's issue-29681/measure/.

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic. (Not touched.)
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; ESLint 0 errors on the changed files.
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset.
  • 6. packages/spec touched: not touched.
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English.
  • The Before Submitting list in CONTRIBUTING.md is done.

Generated by Claude Code

…ting at the record through its package scope, not the registry

A record route whose object has no declared record page, in a packaged
app, now reads the app's package scope with the names the bundle gives it
(the objects that point at the record's object), as the package-less
route already does. A pointing object from another package is read by
name and answered from the bundle. The harness's packaged CONTROL that
pinned the registry read is flipped.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsR2jStt5LFpRDtMbVaQyh
…istry beside the route

Live, the drawer over a packaged record route reads the registry alone
(the package scope is cut from it), the same requests as before; the
harness's extra package-list assertion pinned its own call order.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsR2jStt5LFpRDtMbVaQyh
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tooling labels Nov 3, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#2 · zhuangjianguo · session_01QsR2jRFuJz9QBZqPVHYVhJ

维护者速读

  • 改了什么:在已打包的应用里打开一条记录,且该对象没有声明记录页时,路由不再读取整个对象注册表来寻找「指向本对象的对象」(用于生成相关列表),改为读取应用所在软件包的对象列表,包外的对象按名称补读(多数直接由应用包答复)。
  • 为什么改:实测 CRM 示例(crm_app,Initech 客户记录)打开一条记录共 1.58 MB,其中整个注册表占 561 KB。Console: a console-created (package-less) app's record route reads the whole /meta/page list, and its root the whole object registry #29552 第一段已对无软件包的应用做了同样的改动,本 PR 补上已打包应用。
  • 风险与回滚:只改 Console 前端一个分支的读取范围,无服务端、无数据改动。相关列表的集合和顺序与之前一致,包括来自其他软件包的对象。对象不在应用导航内、没有应用包(草稿预览)或叠加记录抽屉时,仍按原样读注册表。回滚即 revert 本 PR。
  • 先看:ui/packages/app-shell/src/console/appObjectScope.ts 中 readAppObjectScope 的 !page 分支(一行改动:去掉 packageId 门,并把 packageId 传给 readObjectRouteNames)。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/app-shell/src/console/__tests__/AppContent.packagelessRecordRelated-29552.test.tsx,预期 17/17 绿。
  • 需要你做的:无。席位验收后进合并队列。

席位意见: 实现与本席 06:20Z 裁定的设计一致,没有新增读取路径:已打包应用的对象路由本来就用「软件包列表 + 按名补读」,这里只是让相关列表也走这条路。四个验收条件都有实测:集合与顺序、旧 CONTROL 翻转、放回门后变红、线上字节和侧栏无闪烁。

席位核对 — ACCEPT · domain:ui#2 · head 8d42b239 · 2026-11-03T07:33Z

  • 形态: draft PR on main, Fixes #29681 on its own line. 3 files, +140/−25: appObjectScope.ts, the Console: a console-created (package-less) app's record route reads the whole /meta/page list, and its root the whole object registry #29552 harness, one changeset. Tier S: check-governed-merges --pr 29846 says not governed (0 of 3 paths), under 3000 lines. The surface is the one the seat ruled at 06:20Z.
  • 前提实测: on main be6b15c7, live crm_app (app-crm, Initech crm_account, an ops_visit object outside the package pointing at the account): the record open read 1,584,710 B, of which the registry /meta/object was 561,247 B and /meta/view 7,014 B. On this branch (ad4df191, same tree for the source file) the open reads 1,075,729 B: the registry and /meta/view are gone; in are ?package= 32,008 B plus sys_organization, sys_business_unit and sys_comment_reaction by name. The page text is identical and the tabs plus the overflow (Visits) match. Sidebar sampled every 25 ms: 0 → all 7 entries in one step (1,109 ms before, 1,046 ms after), no flash. Screenshots: /mnt/project-files/issue-29681/measure/. Report comment 6597754959.
  • 门禁 check-runs (pinned to head 8d42b2395c2369df4c7f50d30bbd48dbc55ae881): 55 runs: 44 success, 11 path-skipped, none failed or cancelled, including Lint & Repo Gates, TypeScript Type Check, Type Check UI, Test UI (16/16), Build & E2E UI, Live E2E UI, Lint UI, Check Changeset and Governed Surface Queue Guard. Dev local gates: 35 derived, 35 run, all exit 0, 0 NOT-MEASURED; body gates 4/4; the harness 17/17.
  • Code read: the !page branch drops if (packageId) return … scope: null and passes packageId to readObjectRouteNames, whose read asks { packageId, names }; the provider answers the package list and reads by name only the names it does not hold (a bundled one from the bundle). inRelatedOrder still orders the scope by relatedNames, and the lookup targets still go through the same scope without views. Pending and miss arms of readObjectsReferencing are unchanged, so an object outside the navigation, no bundle, and draft preview read the registry as before. Pins: a new describe (7 cases) asserts exactly object?package=app.crm + object/sys_user, the registry's related set and order with ops_visit placed between package objects, and a cross-package-only inbound case (crm_case); CONTROLs for a declared page, outside navigation, no bundle and the drawer. The old packaged CONTROL is removed and named in the file header. Ablations: the gate put back → 2 red; inRelatedOrder dropped → 2 red.
  • 接受的偏差: the drawer CONTROL had asserted a ?package= read that only the harness made; live the drawer reads the registry alone (24 requests, same as main), so the pin was narrowed to that. No provider change.
  • 范围外去向: none new. The drawer's registry read (objectstack#27626) stays as it is. No Skills-docs: line is owed.
  • 落地条款: Clause-②: no (@object-ui/app-shell is private), Metadata-protocol: none, patch. No contract review is owed. Land by pr_ready + automerge_enable once every check is green on this head.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 3, 2026 07:33
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge November 3, 2026 07:34
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 3, 2026
Merged via the queue into main with commit a932270 Nov 3, 2026
56 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-04g04g branch November 3, 2026 07:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

console: a packaged app's record route with no declared record page downloads the whole object registry (553 KB)

1 participant