You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 3e04799
Browse filesBrowse the repository at this point in the historyBrowse files
perf(app-shell): Home's navigation roster gate asks its required objects in one ?names= request (#30864)
<!-- claude-projects-attribution: {"github_login":"os-tesla"} -->
_Requested by **Tesla** · [project
thread](https://claude.ai/code/project/chan_01AXsQkLFKRdQCqQ3RTr6Jao?thread=cmsg_01AXsQkLFKRdQCqQ3RTr6JaoHb1GehvoiX4DcV3e24Gypu)_Fixes#29444
Clause-②: no
Metadata-protocol: none
## 维护者速读
- 改了什么:Console 首页(应用外)导航名册的「对象是否存在」门禁,现在把所有 `requiresObject` 对象名合成一次
`GET /meta/object?names=a,b,c` 读取,不再每个对象名发一次请求。
- 为什么改:app-crm 首页每次打开都要为这道门禁发 9 个对象元数据请求;服务端 `?names=` 批量读(#29236 裁决
A,PR #30709)已上线,实测改后只剩 1 个。
- 风险与回滚:只影响应用外(首页等没有当前应用的页面);应用内仍逐个按名读,记录表单继续与数据适配器共用那次读取。批量答案里缺的名字按 404
处理(入口隐藏),请求失败则所有名字都算未答(入口照常显示),与原来逐个读的行为一致。回滚即还原本 PR。
- 先看:`ui/packages/app-shell/src/providers/MetadataProvider.tsx` 的
`fetchScopeNames`(缺名、失败、刷新三种情形);`ui/packages/app-shell/src/hooks/useNavMemberGates.ts`
里 `outsideApp`
那一处分支;`ui/packages/app-shell/src/console/home/__tests__/homeObjectReads-29121.test.tsx`。
- 两分钟验证:`pnpm --dir ui exec vitest run
packages/app-shell/src/console/home/__tests__/homeObjectReads-29121.test.tsx`,预期
7 个用例全绿,其中门禁用例只记到一条 `object?names=sys_cloud_only,sys_queue` 请求。
- 需要你做的:无,席位落地。
- **席位意见**:ui 席 2 对照 head `382ce259` 的 diff 复核:5 个文件、+221 / −26,只在 app
外(首页)把路由门要的对象并成一次 `GET /meta/object?names=` 读;缺名照旧算未注册(同按名
404),整次读失败则每个名都算未答(同按名读失败),刷新再并读一次,预览与 app
内仍按名读,路由读者不会拿列表回答当自己的。`MetadataContextValue` 类型未改(钩子内本地类型转交
`batch`),无新导出,Clause-② no 成立,不欠契约复审;382ce259 只改测试以过 Lint UI。CI 全绿,同意落地。
## What changes
- **The roster gate outside an app asks once.** `useNavMemberGates`
reads the app list's `requiresObject` union through the names-only
scope. Outside an app (a frame whose app has no `name`: Home, or a frame
with no expression scope), it now passes a package-local `batch: true`
hand-off beside `views: false`. Inside an app nothing changes: the union
is still read by name, because the route's own objects are among those
names and their by-name read is shared with the adapter's
`getObjectSchema` cache (objectui#13044).
- **The provider answers the hand-off from its own scope entry.**
`MetadataProvider.readObjectScope` keys a batched read under
`NAMES_BATCH_SCOPE`, a symbol, so no package id can collide with it. It
never shares the names-only entry: a list answer carries no by-name
envelope and cannot tell an absent object from a refused one
(`OBJECT_PACKAGE_DISABLED`), so a route reader never takes it for its
own. The names not asked yet go out in one `meta.getItems('object', {
names })` (the client door PR #30709 added; no new URL builder).
- A name in the answer is answered with its definition, through the same
list ingestion as every object list (`ingestObjectList`).
- A name the answer leaves out is answered missing, exactly as a by-name
`404` was (ruling A on #29236, 6580261740: an unknown name is simply
absent).
- A read that gets no answer fails every name it asked, as a failed
by-name read failed its one (objectui#13009, objectui#13052). The gate
shows those entries and does not ask again until `invalidate`, `refresh`
or an organization switch.
- Only the asked names are read out of the answer, so an item the
request did not name never lands in the scope.
- `refresh` asks the batched names again in one request.
- Under the draft preview, whose `MetadataClient.list` has no names
filter, the hand-off falls back to the names-only scope (by name).
- **Same filtering as the by-name read (measured on `main` b618224).**
The list door runs `?names=` after the per-caller list gate (whose
`object` arm is the by-name read's `filterObjectListViewsForUser`) and
before the ADR-0106 mask and translation. The store read drops a
disabled package's objects, where the by-name read refuses them `404
OBJECT_PACKAGE_DISABLED`. The gate reads both answers as "not
registered". The answer is the `{ type, items: [...] }` array envelope,
which `extractItems` already reads.
- No new export from `@object-ui/app-shell`. The hand-off type
`ReadNamesInOneRequest` is local to `useNavMemberGates.ts`.
## Tests
- `homeObjectReads-29121.test.tsx` (real `MetadataProvider` + real gate,
fake adapter):
- Outside an app, the gate sends one
`object?names=sys_cloud_only,sys_queue` request. The absent
`sys_cloud_only` is hidden and `sys_queue` is shown. This replaces the
two by-name reads the case pinned before.
- A batch that rejects leaves both entries shown and is not asked again.
- `refresh('object')` re-asks the names in one request.
- `navGateUnionScope-12980.test.tsx`: the outside-app case asserts the
single `?names=` request for the five-name union. Its fake adapter now
honours `names` the way the door does: it keeps those items, and an
unknown name is absent. The package-less-app cases (inside an app, by
name) are unchanged and green.
- Ablations, each through `scripts/ablation-replace.mjs`, which
confirmed the mutation landed and the restore matched the HEAD blob:
- gate hand-off off (`outsideApp = false`): 2 cases red;
- absent name not recorded missing: 1 red;
- refresh asks by name: 1 red.
<details><summary>Related suite (347 files that import the provider or
the gate)</summary>
`pnpm --dir ui exec vitest run --maxWorkers=2` over every app-shell /
console test that names `readObjectScope`, `useNavMemberGates` or
`MetadataProvider`, before the union-scope pin below was updated. 345
files passed and 2 failed:
- `navGateUnionScope-12980` (outside-app case): its fake adapter ignored
`names`. Updated above, and the file is now 12/12 with the Home file.
- `spec-symbol-parity.test.ts`: 16 cases failed because the probe reads
`@objectstack/spec`'s `.d.ts` files, and that run's build used
`OS_SKIP_DTS=1` (`SPEC_NAMES.size` 0). After a declarations build, the
file is green together with the two gate files: 3 files, 53 tests.
</details>
## Verification
Live, on app-crm. Backend `objectstack dev --seed-admin --fresh -p 4417`
in `examples/app-crm`. Console `vite --port 5197 --strictPort` with
`DEV_PROXY_TARGET` pointed at it. Headless Chromium signed in as the
seeded admin, then loaded `/home` fresh, counting every request whose
path starts with `/api/v1/meta/object`.
| Tree | Roster gate requests | Other object reads on Home |
|---|---|---|
| `origin/main` (the two source files restored from b618224) | 9: `GET
/meta/object/NAME` for `sys_assignment_rule`, `sys_business_unit`,
`sys_notification`, `sys_notification_preference`,
`sys_notification_template`, `sys_queue`, `sys_queue_member`,
`sys_record_share`, `sys_sharing_rule` | 1 (`sys_activity`, the activity
feed's presence check) |
| this branch | **1**: `GET /meta/object?names=` with the same nine
names, 200, 9 items | 1 (`sys_activity`, unchanged) |
The card counted 8 names on app-crm. The app list now carries 9.
<details><summary>Gates</summary>
At head `c2ce30e3`. `node scripts/pm/dispatch-gates.mjs --commands
--repo objectstack-ai/objectstack` derived 36 families from this diff;
each ran with stdin closed, and the exit code was captured before any
pipe. `--ran` reconciliation: 36 derived, 36 run, 0 NOT-MEASURED, 0
UNRUN.
```
node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0
node scripts/check-adr-0087-registration.mjs --self-test :: exit 0
node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0
node scripts/check-closing-keyword-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0
node scripts/check-comment-mask-corpus.mjs :: exit 0
node scripts/check-empty-changeset.mjs --base origin/main :: exit 0
node scripts/check-empty-changeset.mjs --self-test :: exit 0
node scripts/check-sdui-manifest.mjs :: exit 0
node scripts/check-sdui-manifest.mjs --self-test :: exit 0
node scripts/ci/snapshot-next.mjs --self-test :: exit 0
node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0
node scripts/release-pending-publish.mjs --self-test :: exit 0
pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0
pnpm check:changeset-gate-self-tests :: exit 0
pnpm check:closed-ceremony-text :: exit 0
pnpm check:cross-package-test-inputs :: exit 0
pnpm check:dispatcher-error-vocabulary :: exit 0
pnpm check:driver-memory-census :: exit 0
pnpm check:engine-double-contract :: exit 0
pnpm check:gitlink-declared :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:published-files :: exit 0
pnpm check:query-options-erasure :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:registry-log-declared :: exit 0
pnpm check:rest-log-spy-declared :: exit 0
pnpm check:skill-identifier-liveness :: exit 0
pnpm check:test-source-alias :: exit 0
pnpm check:tier-file-adoption :: exit 0
pnpm check:type-check-coverage :: exit 0
pnpm check:type-check-debt :: exit 0
pnpm check:undeclared-dep-imports :: exit 0
pnpm check:watch-hint-literal :: exit 0
pnpm check:where-matcher :: exit 0
pnpm --dir ui exec vitest run scripts/ :: exit 0
pnpm --dir ui run check:new-line-citations :: exit 0
pnpm --filter @object-ui/app-shell type-check :: exit 0
```
`pnpm --filter @object-ui/app-shell type-check` (both `tsc --noEmit` and
`tsconfig.test.json`) ran against a forced declarations build of
`@object-ui/app-shell^...`. `node scripts/check-sdui-manifest.mjs` is in
the derived list above.
</details>
## Acceptance notes
- `sys_activity`'s by-name read on Home belongs to `useObjectPresence`
(the activity feeds), not to the roster gate. It is left as it is: one
name, and its by-name read is the one the adapter cache shares.
- Inside an app with no package, the gate still reads the union by name
(objectstack#29416's path). Folding it too would cost the record form's
shared by-name read (objectui#13044) for the route's own objects. That
is not this card's acceptance, so it is noted here and not changed.
- Under the draft preview the gate keeps its by-name reads, because
`MetadataClient.list` in `@object-ui/data-objectstack` has no `names`
option. Adding one is a data-adapter change this card does not need.
<!-- signed: domain:ui#2 · os-tesla · session_01L9qibw7DekEe3wx5f3zphf
-->
---------
Co-authored-by: Claude <noreply@anthropic.com>
perf(app-shell): Home's navigation roster gate asks the objects it requires in one `GET /meta/object?names=a,b,c` request, instead of one by-name read per object
6
+
7
+
Clause-②: no
8
+
Metadata-protocol: none
9
+
10
+
Outside an app (Home, whose activity feeds gate every app's navigation), the runtime-capability gate for `requiresObject` entries read each required object by name, one `GET /meta/object/NAME` per name (eight on app-crm). It now asks the names it has not asked yet in one `?names=` request (`meta.getItems('object', { names })`). A name the answer leaves out is treated as not registered, exactly as a by-name `404` was, so its entry stays hidden. A request that gets no answer leaves every name unanswered, so each entry shows, as when a by-name read failed. A refresh asks the same names again in one request. Inside an app nothing changes: the gate still reads the union by name, so the record form keeps sharing those reads with the data adapter's cache. Under the draft preview the gate also keeps the by-name reads.
it('outside an app, the roster’s capability gate asks the app list’s required objects by name, and answers as the whole list did',async()=>{
155
+
// objectstack#29444 — in ONE `?names=` request, no longer one by-name read
156
+
// per name. `sys_cloud_only` is not in this deployment: the answer leaves it
157
+
// out, and the gate hides its entry, as it did on the by-name read's 404.
158
+
it('outside an app, the roster’s capability gate asks the app list’s required objects in one ?names= request, and answers as the whole list did',async()=>{
it('objectstack#29121 — outside an app (no app name, the home feeds), the gate reads the app list’s union by name, never the whole registry',async()=>{
226
+
it('objectstack#29121 — outside an app (no app name, the home feeds), the gate reads the app list’s union in one ?names= request (objectstack#29444), never the whole registry',async()=>{
0 commit comments