Repository navigation
Commit ff827bc
perf(app-shell): a console-created app's object and page routes read what they name by name (#29542)
<!-- claude-projects-attribution: {"github_login":"zhuangjianguo"} -->
_Requested by **Steedos** · [project
thread](https://claude.ai/code/project/chan_018ETKaNwMZCNRYD7D8Q9Ubb?thread=cmsg_018ETKaNwMZCNRYD7D8Q9UbbYLBU9GQbwZ53pqtiGNhgqR)_
Fixes #29416
Clause-②: no
Metadata-protocol: none
Claim: 6581096637 · amendments: 6581167613, 6581536193
## 维护者速读
-
改了什么:在控制台里创建的应用(没有包)里,三处读取不再下载整张元数据列表。侧边栏的能力门按名读对象,不再带视图。对象路由按名读本对象、`requiresObject`
和查找字段目标,不再读整张对象注册表。应用包(bundle)里没有的页面按名读单条,不再读整张 `/meta/page` 列表。
- 为什么改:在 app-crm 上打开这种应用的一个对象页,每次都要下载 551 KB 的全部对象定义和 7 KB
的视图列表。打开包外页面时,还会额外下载 15 KB 的页面列表和视图列表。
- 风险与回滚:读法从「整表一次」变成「按名多次」。对象页的请求数从 24 增加到 33,但字节数从 821 KB 降到 366
KB。界面文字逐字不变,视图页签和面包屑的标签已在测试中逐字节钉住。在有包的应用里,如果包外页面的按名回答属于另一个包或者不存在,会先多一次按名请求,再回退到整表(ADR-0048
preferLocal)。回滚方法是 revert 本 PR。
-
先看:`ui/packages/app-shell/src/console/appObjectScope.ts`(`readObjectRouteNames`)、`ui/packages/app-shell/src/hooks/useObjectPresence.ts`(`readItemPreferLocal`)、`ui/packages/app-shell/src/providers/MetadataProvider.tsx:1744`(按名读取只组合提问者所问名字的视图)。
- 两分钟验证:新建一个不带包的应用,打开它的对象页,在开发者工具 Network 里过滤 `meta/object`:应该没有不带名字的
`GET /api/v1/meta/object`。再打开该应用里一个包外页面:应该是 `GET
/api/v1/meta/page/<名字>`,没有 `/meta/page` 整表,也没有 `/meta/view`。
- 需要你做的:无,席位落地。
## What changes
An app created in the console has no `_packageId`. On such an app, three
readers still downloaded a whole metadata list. A page outside the app
bundle had the same problem in any app.
| Leg | Before | Now |
|---|---|---|
| 1. `useNavMemberGates` inside a package-less app | `readObjectScope({
names: unionNames })` **with** views. The union names other apps'
objects outside this bundle, so composing their views read the whole
`/meta/view` list. | `{ names: unionNames, views: false }`, the same
call it already made outside an app and on the package leg. Nothing in
the app reads views off this presence answer. |
| 1'. `MetadataProvider` names-only compose (probe-proved, amendment
6581536193) | The names-only entry is shared by every names-only reader,
and a with-views read composed the views of **every** name the entry
held, so another reader's union leaked in. | It composes the views of
the names its own reader asked (`MetadataProvider.tsx:1744`). |
| 2. `readAppObjectScope` for a package-less app's object route | No
scope: the whole object registry. | `readObjectRouteNames`
(`appObjectScope.ts:547`) reads the route's object, the app's
`requiresObject` names and the palette's nav objects by name. It reads
the object's lookup targets by name with `views: false`. Until the app
list names the app, nothing is read (a candidate without a known package
must not ask early). The app's root still reads the whole registry, as
before. |
| 2'. `useServedViewItems` names-only branch (amendment 6581536193) |
The served names were the app's `requiresObject` only, so a route object
named only by a navigation entry lost its served view labels once the
registry was no longer loaded. | `packagelessAppObjectNames(app)`:
navigation `type: 'object'` names plus `requiresObject`, across
`navigation` and every `areas[].navigation`. |
| 3. A page the app bundle does not hold: `AppContent`'s page scope,
`PageView`, `AppHeader`'s phone title | `preferLocal(metadata.pages,
name, _packageId)`: the whole `/meta/page` list. | `readItemPreferLocal`
(`useObjectPresence.ts:190`), built on `useReadItemsByName` from #29413.
While the type is idle, it asks the page by name. It takes the answer
when the app has no package, or when the answer is the app package's
own. Otherwise (another package's page, or no page) it falls back to the
list, so the item is always the one `preferLocal` would pick. Once the
list is loading or loaded, it reads the list and asks nothing. |
## Tests
- `console/__tests__/AppContent.packagelessReads-29416.test.tsx` (7).
These use the real `AppContent`, `PageView` and `MetadataProvider`, with
a recording adapter and a ready app bundle.
- (a) A package-less object route reads no `object`, `view` or `page`
list.
- (b) An out-of-bundle page is read by name, and a bundled page asks
nothing.
- (c) CONTROL: with the lists loaded, nothing more is asked.
- (d) preferLocal: in a packaged app, another package's answer falls
back to the list and draws the app's own page. The app's own answer
reads no list. A package-less app takes the first match.
- `hooks/__tests__/packagelessServedViews-29416.test.tsx` (2).
- The route object's views stay served. The `viewTabLabel` labels (tab
and crumb) are byte-identical before and after the whole registry loads,
and no list is read.
- The provider composes views only for the names its reader asked.
- `layout/__tests__/AppHeader.pageTitleByName-29416.test.tsx` (2). The
phone title asks the page by name with no list read, plus a CONTROL with
the list loaded.
- Updated pins whose CONTROL encoded the old whole-list read:
- `appObjectScope-12811`: an app without a package now expects
`object/crm_account`, `object/sys_user`.
- `AppContent.objectScope-12811`: the route object and its lookup target
by name.
- `AppContent.pageRouteObjects-12980`: the double serves pages by name.
- The census `openPathObjectReaders-12811` is unchanged and green. No
row moved.
## Acceptance notes
- Requests vs bytes on the object route: 24 → 33 requests, 821,430 →
365,762 B. The nine extra requests are by-name reads that replace one
551,834 B `/meta/object` list: `sys_user` and `sys_organization` (the
lookup targets), and the Setup app's `requiresObject` sys_* names from
the nav gate's union, which the whole list used to answer.
- A page in a packaged app that resolves to another package's page, or
to none, now costs one serial by-name read before the list.
- Not changed, found while measuring (outside the claim's surface):
- Every object route, packaged apps included, still reads `/meta/view`
through `ObjectView`'s saved-views overlay
(`ui/packages/app-shell/src/views/ObjectView.tsx:416`,
`dataSource.listViews(objectName)` → the adapter's `readViewList`). On
this route it is now the only `/meta/view` reader.
- A packaged app's object route whose lookup target is outside the
bundle still reads `/meta/view` through the package scope's with-views
lookup read.
- A package-less app's record route still reads the `/meta/page` list
(`readPages`). Its root still reads the whole registry.
- Merging main (#29472, dashboard route reads) needed two resolution
fixes in the merge commit: a duplicated `ReadNamesWithoutViews` import,
and the `preferLocal` import that main's new dashboard read uses.
## Verification
<details><summary>Production Console, app-crm (<code>objectstack dev
--seed-admin --fresh</code>), before (app-shell sources at base
<code>5f7d554c</code>) and after (head <code>bd111303</code>; later
commits only merge main)</summary>
Setup:
- A runtime app `ops_app` with no package, saved through `PUT
/api/v1/meta/app/ops_app` with two object entries (`crm_account`,
`crm_contact`). `crm_welcome` is not in its navigation, so it is outside
the bundle.
- Playwright signs in and opens each route. It then reloads with the
cache disabled and logs every `/api/` request with its CDP initiator
stack (`Debugger.setAsyncCallStackDepth 64`).
Object route `/apps/ops_app/crm_account`, en:
- Before: 24 requests, 821,430 B. It reads `GET /api/v1/meta/object`
(551,834 B) and `/meta/view` (7,014 B), both from `get objects`.
- After: 33 requests, 365,762 B. There is no `GET /api/v1/meta/object`
without a name. `/meta/view` (7,014 B) is still read, and its only
initiator is `ObjectView`'s `listViews` (see Acceptance notes).
- For comparison, the packaged `crm_app` object route after:
`/meta/object?package=com.example.crm` and `/meta/view` from
`appObjectScope`'s lookup read (unchanged).
Out-of-bundle page route `/apps/ops_app/page/crm_welcome`, en:
- Before: 29 requests, 335,061 B. It reads `GET /api/v1/meta/page`
(15,463 B, from `get pages`) and `/meta/view` (7,014 B, the nav gate's
names-only scope).
- After: 28 requests, 313,334 B. It reads `GET
/api/v1/meta/page/crm_welcome` (1,044 B). There is no `/meta/page` list
and no `/meta/view`.
The visible text of both routes is identical before and after, including
the view tab `All Records` and the page body.
</details>
<details><summary>Ablations (each through
<code>scripts/ablation-replace.mjs</code>, restored with <code>git diff
HEAD</code> empty, at <code>bd111303</code>)</summary>
- A1: the in-app gate asks with views. 3 red.
- A2: a package-less app gets no scope. 2 red.
- A3: `readObjectRouteNames` always reads the list. 5 red.
- A4: the provider composes every name the entry holds. 4 red.
- A5: the served names are `requiresObject` only. 1 red.
- A6: `readItemPreferLocal` drops the ownership check. 1 red ((d), the
other package's answer).
- A7: package-less lookups asked with views. 1 red.
- A8: `AppHeader` reads the page list. 1 red.
- A9: `PageView` reads the page list. 3 red.
</details>
<details><summary>Local runs</summary>
- Consumer sweep at `bd111303`, under `os-verify-lock` with
`--maxWorkers=2`: 210 test files naming the touched modules, 1,920
tests, all passed.
- After merging main, at `3a894d8b`:
- `console/__tests__` and `hooks/__tests__`: 157 files, 1,273 tests.
Main's `AppContent.dashboardRouteReads-28784` was red on the dropped
`preferLocal` import, then green after the fix.
- The `PageView`, `AppHeader`, `AppContent` and `appObjectScope` files
plus the new tests: 95 files, 754 tests, all passed.
- `turbo run type-check --filter=@object-ui/app-shell`: 65/65, at both
heads.
- `eslint --no-inline-config` on the changed files: 0 errors.
- Derived gates (`dispatch-gates --commands`): 34 commands, all exit 0
at `bd111303`, reconciled with `--ran` (34 derived, 34 run, 0
NOT-MEASURED). At `3a894d8b`, `check-sdui-manifest` and
`check-changeset-no-major` pass.
</details>
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
(not touched)
- [x] 2. Every name I added uses the casing for its kind.
- [x] 3. No formatter ran; `pnpm lint` passes.
- [x] 4. One issue, one branch off an up-to-date `main`, one pull
request.
- [x] 5. A change that publishes carries a changeset, and both
declaration lines are filled in here and in the changeset.
- [x] 6. `packages/spec` touched: its generated artifacts are
regenerated and committed. (not touched)
- [x] 7. No hand edits to `content/docs/references/`,
`packages/*/CHANGELOG.md` or `content/docs/releases/`.
- [x] 8. Title, body and comments are in English.
- [x] The *Before Submitting* list in CONTRIBUTING.md is done.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01QsR2jTxHVLQcafzV1G73bX
---
_Generated by [Claude
Code](https://claude.ai/code/session_01QsR2jTxHVLQcafzV1G73bX)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 208d3e2 commit ff827bc
15 files changed
Lines changed: 912 additions & 62 deletions
File tree
- .changeset
- ui/packages/app-shell/src
- console
- __tests__
- hooks
- __tests__
- layout
- __tests__
- providers
- views
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
34 | 34 | | |
35 | 35 | | |
36 | 36 | | |
| 37 | + | |
37 | 38 | | |
38 | 39 | | |
39 | 40 | | |
| |||
343 | 344 | | |
344 | 345 | | |
345 | 346 | | |
| 347 | + | |
| 348 | + | |
346 | 349 | | |
347 | 350 | | |
348 | 351 | | |
| |||
481 | 484 | | |
482 | 485 | | |
483 | 486 | | |
484 | | - | |
| 487 | + | |
| 488 | + | |
485 | 489 | | |
486 | 490 | | |
487 | 491 | | |
488 | | - | |
489 | | - | |
490 | | - | |
491 | | - | |
492 | | - | |
| 492 | + | |
| 493 | + | |
| 494 | + | |
| 495 | + | |
| 496 | + | |
| 497 | + | |
| 498 | + | |
| 499 | + | |
| 500 | + | |
493 | 501 | | |
494 | 502 | | |
495 | 503 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
18 | 18 | | |
19 | 19 | | |
20 | 20 | | |
21 | | - | |
22 | | - | |
23 | | - | |
24 | | - | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
25 | 27 | | |
26 | 28 | | |
27 | 29 | | |
| |||
231 | 233 | | |
232 | 234 | | |
233 | 235 | | |
234 | | - | |
| 236 | + | |
235 | 237 | | |
236 | 238 | | |
237 | | - | |
| 239 | + | |
238 | 240 | | |
239 | | - | |
| 241 | + | |
240 | 242 | | |
241 | | - | |
| 243 | + | |
| 244 | + | |
242 | 245 | | |
243 | 246 | | |
244 | 247 | | |
| |||
0 commit comments