Repository navigation
Commit 0a4e74e
perf(app-shell): a console-created app's root reads its required objects by name (#29614)
<!-- claude-projects-attribution: {"github_login":"zhuangjianguo"} -->
_Requested by **Steedos** · [project
thread](https://claude.ai/code/project/chan_018ETKaNwMZCNRYD7D8Q9Ubb?thread=cmsg_018ETKaNwMZCNRYD7D8Q9UbbNKT22zb7bwiwcAVkZKTaj7)_
Part of #29552
Clause-②: no
Metadata-protocol: none
Claim: 6587040937 · amendment: 6587055597 (this PR is leg 2 only; the
record route's `/meta/page` list, leg 1, is out of this claim and stays
on the card)
## 维护者速读
- 改了什么:在控制台里创建的应用(没有包)打开根路径 `/apps/应用名` 时,不再下载整张对象注册表,只按名读取导航条目
`requiresObject` 要求的对象(命令面板打开时再加上导航里的对象),与该应用的对象路由读法一致。
- 为什么改:在 app-crm 上打开这种应用的根路径,落到第一个导航条目之前要先下载 553 KB 的全部对象定义。
- 风险与回滚:根路径从「整表一次」变成「按名多次」:请求数 24 → 33,字节数 824,793 → 367,941
B,界面文字逐字不变。在根路径上打开记录抽屉(`?recordId=`)仍按原样读注册表(#27626)。回滚方法是 revert 本 PR。
- 先看:`ui/packages/app-shell/src/console/appObjectScope.ts` 中
`readAppObjectScope` 的无包分支,以及 `readObjectRouteNames` 的
`read`(空名字列表不再落到整表读取)。
- 两分钟验证:新建一个不带包的应用,打开 `/apps/应用名`,在开发者工具 Network 里过滤
`meta/object`:应该没有不带名字的 `GET /api/v1/meta/object`,页面照常跳到第一个导航条目。
- 需要你做的:无,席位落地。
## What changes
An app created in the console has no `_packageId`. Its root route
(`/apps/APP`, no object in the path) had no object scope:
`readAppObjectScope` returned `scope: null`, so `AppContent` read
`metadata.objects`, the whole object registry, before `AppRootLanding`
navigated to the app's first entry.
- `appObjectScope.ts`, the package-less branch of `readAppObjectScope`:
the root now reads through `readObjectRouteNames` with no route object,
as a packaged app's root already does through its package scope. That is
the app's `requiresObject` names (navigation and areas) and, while the
command palette is open, the navigation's object names, by name through
the names-only scope. Only the route's object holds a route, so the root
waits for nothing.
- `readObjectRouteNames`'s `read`: with no package and no name it
answers the empty, settled scope (`NO_OBJECT_SCOPE`) instead of calling
`readObjectScope({ names: [] })`. The provider treats an empty
names-only ask as the whole registry (`namesOnly` needs a non-empty
list), so without this guard the root's empty route read would have read
the registry anyway. `readPageObjectScope` already guards the same way.
- Unchanged: a record drawer over the root keeps its registry read
beside the scope (`drawerReadsRegistry`, #27626); a record form overlay
(`?form=`) still has no scope candidate; a packaged root is untouched;
`AppContent.tsx` is not edited (`readsWholeRegistry` follows from the
scope).
## Tests
New
`ui/packages/app-shell/src/console/__tests__/AppContent.packagelessRoot-29552.test.tsx`,
on the harness of `AppContent.packagelessReads-29416`: the real
`MetadataProvider`, `AppContent`, `PageView` and sidebar gate
(`useNavMemberGates`) over a client double that records each call.
- (a) A package-less root with no landing (its only entry is a link
requiring `note`) asks `object/note` by name and no list.
- (b) A package-less root whose first entry is a bundled page lands on
it, and no list is asked on the way.
- (c) CONTROL: with the lists already loaded, the root asks nothing by
name.
- (d) CONTROL: a record drawer over the root (`?recordId=`) still reads
the registry.
## Acceptance notes
- Requests vs bytes on the root: 24 → 33 requests, 824,793 → 367,941 B.
The extra requests are by-name reads that replace one 553,666 B
`/meta/object` list: the landing route's own names and the Setup app's
`requiresObject` `sys_*` names from the nav gate's union, which the
whole list used to answer.
- Leg 1 of the card is not in this PR (claim amendment 6587055597).
Measured at base `dbca3ef0`, the record route's first `/meta/page` list
initiator is `AppContent`'s record-page reader (`get pages`, called from
`readAppObjectScope`). Two more readers share the cached list, from
source: `RecordDetailView.tsx:1049` (`usePageAssignment`, which calls
`ensureType('page')` and scans `meta.pages`) and
`RecordDetailView.tsx:1050` (the named-page check). The default record
page is picked by a scan (`type === 'record'` and `object`, `isDefault`
first, else the first in served order); no object-side name exists. The
app bundle (`packages/rest/src/meta-app-bundle-routes.ts:285`) holds
every page bound to a bundled object, so it can answer the pick for
those objects, but reaching it from `@object-ui/react`'s
`usePageAssignment` needs a new `MetadataContextValue` member. The seat
records that design on the card.
- The record route also reads the whole registry when the object has no
declared record page (its synthesized page derives related lists from
every object pointing at it). Unchanged here, as designed
(objectui#13046).
- `/meta/view` (7,014 B) on the landing object route is `ObjectView`'s
saved-views overlay, #29550.
## Verification
<details><summary>Production Console, app-crm (<code>objectstack dev
--fresh --seed-admin</code>), before at base <code>dbca3ef0</code>,
after at <code>335742fa</code></summary>
Setup: a runtime app `ops_app` with no package, saved through `PUT
/api/v1/meta/app/ops_app` with entries `crm_account`, `crm_contact`
(objects) and `crm_welcome` (page). Playwright signs in, opens each
route, goes to `about:blank`, then opens the route again with the cache
disabled, logging every `/api/` request with its CDP initiator stack
(`Debugger.setAsyncCallStackDepth 64`). A fresh navigation, not a
reload: a reload would reload the URL the root redirected to.
Root `/_console/apps/ops_app` (lands on `/crm_account`), en:
- Before: 24 requests, 824,793 B. `GET /api/v1/meta/object` 553,666 B,
initiator `get objects` in `AppContent` (the `readsWholeRegistry` arm of
`allObjects`), and `/meta/view` 7,014 B (`listViews`).
- After: 33 requests, 367,941 B. No `GET /api/v1/meta/object` without a
name; the `/meta/view` read is unchanged.
- Visible text identical before and after.
Record route `/_console/apps/ops_app/crm_account/record/ID` (unchanged
by this PR, leg 1): 23 requests, 888,328 B at both heads, with
`/meta/page` 15,463 B, `/meta/object` 553,666 B and `/meta/view` 7,014
B.
</details>
<details><summary>Ablations (each through
<code>scripts/ablation-replace.mjs</code>, restored with <code>git diff
HEAD</code> empty, at <code>335742fa</code>)</summary>
- A1: a package-less root gets no scope again (`if (!app ||
!route.objectName)`). (a) and (b) red, 2 of 4.
- A2: `readObjectRouteNames` asks `readObjectScope({ names: [] })` with
no package. (a) and (b) red, 2 of 4.
</details>
<details><summary>Local runs</summary>
- Consumer sweep at `335742fa`, under `os-verify-lock` with
`--maxWorkers=2`: the 141 app-shell test files that name `AppContent`,
`appObjectScope`, `readAppObjectScope`, `useAppObjectScopePrefetch` or
`readObjectScope`, in three batches: 50 files / 391 tests, 45 / 383, 46
/ 341, all passed.
- After merging main, at `4ac31922`: `turbo run type-check
--filter=@object-ui/app-shell` 65/65 (also 65/65 at `335742fa`); the new
test, `AppContent.packagelessReads-29416`, `appObjectScope-12811`,
`AppContent.objectScope-12811`, `packagelessChromeScope-12980` and
`scopedRegistryReaders-12811`: 6 files, 31 tests, all passed.
- `eslint` on the changed files: 0 errors (warnings are existing `any`s
in `appObjectScope.ts`).
- Derived gates (`dispatch-gates --commands` at `335742fa`): 34
commands, all exit 0, reconciled with `--ran`. `pnpm --dir ui exec
vitest run scripts/`: 170 files passed, 2 skipped. Main's 22 incoming
commits touch none of this diff's files.
</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_01QsR2jTUtDJFjiE2P4EstZz
---
_Generated by [Claude
Code](https://claude.ai/code/session_01QsR2jTUtDJFjiE2P4EstZz)_
Co-authored-by: Claude <noreply@anthropic.com>1 parent e43d326 commit 0a4e74e
3 files changed
Lines changed: 311 additions & 6 deletions
File tree
- .changeset
- ui/packages/app-shell/src/console
- __tests__
| 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 | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
58 | 58 | | |
59 | 59 | | |
60 | 60 | | |
61 | | - | |
62 | | - | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
63 | 66 | | |
64 | 67 | | |
65 | 68 | | |
| |||
518 | 521 | | |
519 | 522 | | |
520 | 523 | | |
521 | | - | |
522 | | - | |
523 | | - | |
| 524 | + | |
| 525 | + | |
| 526 | + | |
| 527 | + | |
| 528 | + | |
| 529 | + | |
524 | 530 | | |
525 | 531 | | |
526 | 532 | | |
| |||
551 | 557 | | |
552 | 558 | | |
553 | 559 | | |
| 560 | + | |
| 561 | + | |
554 | 562 | | |
555 | | - | |
| 563 | + | |
556 | 564 | | |
557 | 565 | | |
558 | 566 | | |
| |||
0 commit comments