Repository navigation
Commit be6b15c
perf(app-shell): a console-created app's record route reads the objects pointing at the record from the bundle, not the registry (#29697)
<!-- claude-projects-attribution: {"github_login":"zhuangjianguo"} -->
_Requested by **Steedos** · [project
thread](https://claude.ai/code/project/chan_018ETKaNwMZCNRYD7D8Q9Ubb?thread=cmsg_018ETKaNwMZCNRYD7D8Q9UbbCuqLbVvnJHftXrbkzXKBiC)_
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。
- 风险与回滚:相关列表的集合与顺序都不变。#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 `reference`s 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 #29680
has landed.** Before #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.
#29752 (#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 #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: #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.
<details><summary>Before (origin/main b391feb), after (98b5aa1) and
final (3b62862, with #29752), bytes and requests</summary>
| 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`).
</details>
---
_Generated by [Claude
Code](https://claude.ai/code/session_01QsR2jQxTKQHZstB1KT2GXV)_
<!-- signed: domain:ui#2 · zhuangjianguo ·
session_01QsR2jQxTKQHZstB1KT2GXV -->
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent eeb3117 commit be6b15c
6 files changed
Lines changed: 555 additions & 8 deletions
File tree
- .changeset
- ui/packages/app-shell/src
- console
- __tests__
- providers
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
20 | 20 | | |
21 | 21 | | |
22 | 22 | | |
23 | | - | |
| 23 | + | |
24 | 24 | | |
25 | 25 | | |
26 | 26 | | |
| |||
521 | 521 | | |
522 | 522 | | |
523 | 523 | | |
| 524 | + | |
| 525 | + | |
| 526 | + | |
| 527 | + | |
| 528 | + | |
| 529 | + | |
| 530 | + | |
524 | 531 | | |
525 | 532 | | |
526 | 533 | | |
| |||
0 commit comments