Skip to content

perf(app-shell): a console-created app's record route reads the objects pointing at the record from the bundle, not the registry - #29697

Merged
objectstack-fleet[bot] merged 8 commits into
mainfrom
claude/project-thread-gern50
Nov 3, 2026
Merged

objectstack-fleet[bot] merged 8 commits into
mainfrom
claude/project-thread-gern50

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Steedos · project thread

Part of #29552

Clause-②: no
Metadata-protocol: none

Claim: 6591721332 · amendment 6592122772 (leg 1 re-scoped to the record route's registry read). The record route's /meta/page list (15.8 KB) stays, by the seat's ruling in that amendment.

维护者速读

  • 改了什么:在控制台里创建的应用(没有包)中,打开一条记录、而该对象没有声明记录页时,不再下载整张对象注册表;改为从应用包(app bundle)里取出所有指向该对象的对象,连同记录对象及其查找目标一起按名读取,相关列表照旧。
  • 为什么改:在 app-crm 上,这类记录页一次打开共 888,599 B,其中整张对象注册表就占 553,397 B。
  • 风险与回滚:相关列表的集合与顺序都不变。rest(app bundle): serve the bundle's objects in the object list's order, not navigation-reach first #29680(服务端按对象列表顺序返回 bundle 的 objects)已合入,合入 main 后实测顺序仍是 Activities·Contacts·Leads·Opportunities,与有包应用同一条记录的页面正文逐字一致。有包的应用、导航外的对象、草稿预览、无 bundle、记录抽屉仍读整表,不受影响。回滚方法是 revert 本 PR。
  • 先看:ui/packages/app-shell/src/console/appObjectScope.ts 记录分支里 !page 的处理与 inRelatedOrder;ui/packages/app-shell/src/providers/MetadataProvider.tsx 的 readBundledReferencing 及其等待 bundle 的 effect。
  • 两分钟验证:新建一个不带包的应用,导航里放 Account 和 Contact,打开一条 Account 记录,在开发者工具 Network 里过滤 meta/object:应只看到按名读取(如 meta/object/sys_user),没有不带名字的 meta/object;页面上的相关列表与有包应用里同一条记录一致。
  • 需要你做的:无,席位落地。

What changes

A record route whose object has no declared record page draws a synthesized page, and that page derived its related lists from the whole object registry: readAppObjectScope's record arm answered scope: null and AppContent read metadata.objects. In an app with no package, for an object the app's navigation reaches, the route now reads a names scope instead: the record's object, its lookup targets, the app's requiresObject names and every object that points at the record's object, which the app bundle already holds (the server adds them as inboundRelationObjects, over the same object list the registry is read from). Bundled objects are answered from the bundle with no request.

  • providers/bundledReads.ts, providers/MetadataProvider.tsx: one new BundledReads member, objectsReferencing(appName, objectName). It is a hit with the names of the bundled objects whose lookup / master_detail field references the object, only when the active bundle is that app's and its navigation (top level, areas, children at any depth) reaches the object. It is pending while that app's bundle is not in: the provider's effect looks again after the app is entered. It misses with no bundle, in draft preview, or when the bundle answered without the app. @object-ui/app-shell is private, so no published export moves.
  • console/appObjectScope.ts: the record arm's !page branch reads readObjectRouteNames with the related names in an app with no package. It waits while the bundle is pending, and reads the registry as before on a miss. readObjectRouteNames gains relatedNames: they are asked with the route's names, the route waits for them, and they are handed out in the bundle's order (inRelatedOrder). A names-only scope holds names in landing order across readers, and an app's requiresObject names land first.
  • console/AppContent.tsx: hands the reader in, beside the page and dashboard readers.

Unchanged: a packaged app's record route (the registry, byte-identical), a declared record page, a record drawer (reads the registry beside the scope), a record form overlay, and the /meta/page list.

Tests

New console/__tests__/AppContent.packagelessRecordRelated-29552.test.tsx (11), over the real MetadataProvider and AppContent with a recording client that answers bundled items from the bundle, as the adapter does:

  • (a) a package-less record route reads no object list, only object/sys_user by name;
  • (b) CONTROL: the related lists drawn are the set the whole registry draws today, also for an object reached inside a navigation group and pointed at from another package;
  • (c) while the bundle is on the wire nothing is read, then reads by name;
  • (e) with the bundle in object-list order, the lists are drawn in the registry's order, including when requiresObject names a related object;
  • CONTROLs, registry as before: object outside the navigation, no bundle, bundle without the app, record drawer, and a packaged app (exactly ['object'], the same lists).

Ablations through scripts/ablation-replace.mjs, each restored with git diff HEAD empty: A1 (answer scope: null again) 3 red; A2 (drop the package gate) 1 red, the packaged pin; A3 (drop the navigation-reach check) 1 red, the outside-navigation pin; A4 (drop inRelatedOrder) 1 red, the requiresObject order pin.

Sweep: 135 app-shell test files naming AppContent, appObjectScope, bundledReads plus every provider test, 1,104 tests passed before the order commit, 1,106 at f3895a4, and 1,107 at the final head 3b62862 (after merging main with #29752). @object-ui/app-shell type-check (both programs) exit 0, and ESLint 0 errors on the changed files.

Acceptance notes

  • Related-list order: the registry's, measured live now that rest(app bundle): serve the bundle's objects in the object list's order, not navigation-reach first #29680 has landed. Before rest(app bundle): serve the bundle's objects in the object list's order, not navigation-reach first #29680 the server listed the bundle's objects as navigation objects first, then inbound ones, so at 98b5aa1 this branch drew Contacts, Activities, Leads, Opportunities on the Initech record. fix(rest): the app metadata bundle serves its objects in the object list's order #29752 (rest(app bundle): serve the bundle's objects in the object list's order, not navigation-reach first #29680) now serves them in object-list order. At head 3b62862, which merges it, the package-less route draws Activities, Contacts, Leads, Opportunities. That is the pre-change order (b391feb), and the record body text is identical to the packaged crm_app route on the same record, which still reads the registry. The (e) pins hold that order with a list-order bundle, and inRelatedOrder keeps it when requiresObject names land first.
  • Packaged apps keep the registry read. Measured on crm_app (Initech, no declared page): 24 requests, 1,569,536 B, of which /meta/object is 553,397 B. That is console: a packaged app's record route with no declared record page downloads the whole object registry (553 KB) #29681, not built here.
  • No reachable drop found. For objects the item chain never refuses a served document (meta-item-read-gate.ts, the object arm only prunes list views), and stored reference_to rows are rewritten to reference on rehydration for both the list and the bundle, so the bundle's inbound set and the registry's agree. Measured equal on app-crm: activity, contact, lead and opportunity.
  • The registry list gates per caller exactly as the bundle does, so no related list drops for a caller. Neither door refuses an object document whole: the object item gate only prunes listViews the caller does not hold (meta-item-read-gate.ts, createMetaItemReadGate's object arm), and the list gate does the same per item (createMetaListReadGate's object arm). Both apply the same ADR-0106 field mask (projectMetaObjectSchema): on the list in step 7 of createMetaListAnswer, on each bundle member through createMetaItemAnswer. A schema the mask empties fails the whole list and the whole bundle alike. So an inbound object is in the bundle exactly when it is in the list. If the mask hides an inbound object's lookup field from this caller, the registry draws no related list for it today, and neither does this branch: the server picks inbound candidates from the unmasked list, but the client reads reference from the masked members it was served. Re-checked at 3b62862: fix(rest): the app metadata bundle serves its objects in the object list's order #29752 changed only the order of the bundle's members, not the gate code. Live, as admin, the objects pointing at crm_account and at crm_contact are the same set in the same order in /meta/object and in the bundle (inbound-check.cjs). A non-member account was refused 403 by both doors alike.
  • An object reached only as a lookup target or an inbound relation (not a navigation entry) still reads the registry: the server computes inbound only for the navigation's objects.

Verification

Production Console (pnpm build and pnpm console:build), app-crm (objectstack dev --fresh --seed-admin), a package-less ops_app (Account, Contact, Welcome) saved with PUT /api/v1/meta/app/ops_app, and a package-less record page of crm_contact as the control. Measured with measure2.cjs: fresh navigation through about:blank, cache disabled, CDP initiator stacks.

Before (origin/main b391feb), after (98b5aa1) and final (3b62862, with #29752), bytes and requests
route before after final
ops_app crm_account record, no declared page 23 requests, 888,599 B; /meta/page 15,785, /meta/object 553,397, /meta/view 7,014 31 requests, 400,662 B; /meta/page 15,785 only, no /meta/object list, no /meta/view 31 requests, 401,878 B; /meta/page 15,785 only; tabs Activities, Contacts, Leads, Opportunities
ops_app crm_contact record, declared page (control) 32 requests, 396,322 B 32 requests, 396,325 B, text identical 32 requests, 397,910 B
crm_app crm_account record, packaged (unchanged arm) n/a 24 requests, 1,569,536 B, /meta/object 553,397 24 requests, 1,580,091 B, /meta/object 558,633; body text identical to the ops_app route

The after route reads 11 objects by name (sys_user, sys_organization, sys_business_unit and eight sys_* names the declared-page route also reads), 100,553 B together. The final run is on a fresh --fresh seed (Console now at /_console/), so it compares record text within the run, not across runs. Data, screenshots and scripts: the project's issue-29552/leg1/measure/ (final-3b62862c/, inbound-check.cjs).


Generated by Claude Code

…age reads its related objects from the bundle, not the registry

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsR2jQxTKQHZstB1KT2GXV
…bject reads and its registry fallbacks

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsR2jQxTKQHZstB1KT2GXV
… from the bundle

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsR2jQxTKQHZstB1KT2GXV
…ndle lists them in, ahead of names other readers landed first

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsR2jQxTKQHZstB1KT2GXV
…cing defs without any

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsR2jQxTKQHZstB1KT2GXV
@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Nov 3, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#2 · zhuangjianguo · session_01QsR2jRFuJz9QBZqPVHYVhJ

维护者速读

  • 改了什么:在控制台里创建、不属于任何包的应用中,打开一条没有声明记录页的记录时,不再下载整张对象注册表。改为从应用包(app bundle)里取出所有指向该对象的对象,连同记录对象和它的查找目标一起按名读取。相关列表的集合和顺序都不变。
  • 为什么改:app-crm 上这类记录页一次打开 888,599 B,其中对象注册表占 553,397 B。改后 401,878 B,降幅约 55%。
  • 风险与回滚:低。有包的应用、导航外的对象、草稿预览、没有 bundle、记录抽屉,都照旧读整表。相关列表顺序依赖服务端 fix(rest): the app metadata bundle serves its objects in the object list's order #29752(已合入),合入后实测与旧顺序一致。回滚即 revert 本 PR。
  • 先看:ui/packages/app-shell/src/console/appObjectScope.ts 记录分支里的 !page 和 inRelatedOrder;ui/packages/app-shell/src/providers/MetadataProvider.tsx 的 readBundledReferencing。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/app-shell/src/console/__tests__/AppContent.packagelessRecordRelated-29552.test.tsx,预期 11 个用例全绿。
  • 需要你做的:无。席位验收后进合并队列。

席位意见: 字节降幅已实测,相关列表与有包应用同一条记录的正文逐字一致。等 #29752 是为了不让页签顺序变化,现在顺序已回到原样。有包应用的同类整表读取另立了 #29681,本 PR 不碰。

席位核对 — ACCEPT · domain:ui#2 · head 3b62862c · 2026-11-03T05:44Z

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/l tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant