Repository navigation
Commit b177920
perf(app-shell): a platform admin reads the package list once per session for the app-to-Studio entries (#29240)
<!-- claude-projects-attribution: {"github_login":"zhuangjianguo"} -->
_Requested by **Steedos** · [project
thread](https://claude.ai/code/project/chan_018ETKaNwMZCNRYD7D8Q9Ubb?thread=cmsg_018ETKaNwMZCNRYD7D8Q9UbbP3ZkhgACTq7KozHL2hkAAX)_
Fixes #29119
Clause-②: no
Metadata-protocol: none
The two lines are copied from the claim (comment 6572848864).
## 维护者速读
- 改了什么:平台管理员进应用页时,顶栏「在 Studio 中编辑应用」铅笔、侧栏 Design
入口和页面铅笔用来判断能否绘制的包列表(`GET /api/v1/packages`),现在按会话(登录用户 +
当前组织)只读一次并持有;切换应用不再重读。Studio 自己的包列表读取不变,仍每次读服务器。
- 为什么改:这个列表是应用页上最大的一笔响应(staging 实测编码 317
KB),之前每进一个应用页都重读一遍,同一页的读者晚挂载时还会读两遍,只为决定一个铅笔是否可点。
-
风险与回滚:持有的答案可能过期。本标签页内的每一次包写入(新建、编辑、复制、定制配对、包页面的发布/回退/丢弃/删除/认领/启停/导入、市场安装与卸载)都会清掉它;列表里没有的包
id(例如 agent
在服务端新建的包)会重读一次;别处改变某个已列出包的可写判定时,入口会显示旧姿态,直到本页写包、换会话或刷新,Studio
打开时仍按自己新读的判定,服务端写门禁不变。回滚即撤销本 PR。非管理员仍不发这个请求。
- 先看:`packages-io.ts` 新增的 `fetchSessionPackages` 与
`forgetPackageListReads`;`useStudioDesignGate.ts` 的
`usePackagePosture`;新测试
`useStudioDesignGate.sessionHold-29119.test.tsx`。
- 两分钟验证:`pnpm --dir ui exec vitest run
packages/app-shell/src/hooks/useStudioDesignGate.sessionHold-29119.test.tsx`,预期
15 个用例全部通过。
- 需要你做的:无,席位落地。
## What changes
- `packages-io.ts` gains `fetchSessionPackages(session, packageId)`: the
package list the app → Studio entries read, held per `fetch` transport
for one session once read. A first ask (and concurrent first asks) goes
through the existing in-flight share; later asks answer from the hold.
The hold carries its session (the signed-in user id and the active
organization id), so a sign-in as someone else or an in-page
organization switch reads again. An id the held list does not name,
kernel packages counted as named, is read again once per hold, so a
package created on the server since (an agent, another tab) still gets
its entry, and an id no list names is not re-read on every visit. A
failed read is not held, and a read still on the wire when a write lands
answers its callers but does not become the hold.
- `fetchPackages` and every other Studio reader are unchanged and hold
nothing. The documented reason they are not a response cache (no
client-side list can name every write, an agent creates packages
server-side) still binds a list that must show the server's current
answer. The gate's hold keeps to the bounds stated in
`fetchSessionPackages`' doc.
- `forgetPackageListReads()` replaces the module-private drop: it drops
the pending list read and the session's hold. Every package write in the
ui tree now calls it however the write ends:
- in `packages-io.ts`: `duplicatePackage`, `ensureCustomizationPackage`
and the `objectui:packages-changed` listener (which covers
`PackageFormDialog`'s create and edit), as before for the pending read;
- `PackagesPage.tsx`: its `apiJson` helper on any non-GET request, which
is every write the page sends (publish, revert, discard-drafts, delete,
adopt-orphans, enable/disable, and the JSON import through
`install-local`);
- `marketplaceApi.ts`: `installLocal`, `uninstallLocal`, and
`installPackage`'s same-origin `cloud-connection/install` leg (the
control-plane leg installs into another environment, whose list this tab
does not hold).
- `useStudioDesignGate.ts`: `usePackagePosture` reads
`fetchSessionPackages` with the session from `useAuth()`, and its
verdict carries the session it answers for. The Cost section of its
docblock now states the per-session read.
What a held answer can still get wrong: a writable verdict that flips
outside this tab on a package the list already names. The entry then
shows the previous posture until this tab writes a package, the session
changes or the page reloads. A stale edit pencil opens Studio, which
reads the verdict itself and opens read-only; a stale view eye costs a
click. The server's write gate is the authority either way.
## Tests
- New
`ui/packages/app-shell/src/hooks/useStudioDesignGate.sessionHold-29119.test.tsx`
(15 cases), the real hooks over the real `packages-io` reads with every
request counted on a stubbed `fetch`:
- one app page's four readers send one list read, including a reader
mounted after the read settled;
- moving through five apps and back sends none, and the held list still
answers each app's posture (edit, view, none for the kernel Setup
package);
- each package write (duplicate, ensured customization pair, the
packages-changed announcement, marketplace install, marketplace
uninstall) makes the next ask read again and see the new verdict, which
is then held;
- a read on the wire when a duplicate lands answers but is not held;
- an id created since the hold is read again once and gets its entry; an
id no list names is read again once in three visits;
- another organization and another user each read their own list;
- a non-admin and an admin the server refuses authoring send no list
read;
- `fetchPackages` still reads the server on every call.
- Ablation (one-shot, `scripts/ablation-replace.mjs`, restored to HEAD's
blob `fbaa0b6d`, `git diff HEAD` empty): with the hold's answer branch
disabled, 10 of 15 cases go red on the request count (for example
"expected 6 to be 1" for the app switches); the session, non-admin,
in-flight and Studio cases stay green, as they should.
## Acceptance notes
- Acceptance 2 (cloud `verify-console-pin.mjs` on welcome) is NOT
MEASURED here: that script lives in the cloud repository. The same
property (no repeated list read on one admin app page load, none across
app switches) is measured by the first two cases above.
- The optional server ETag on `packages/rest/src/package-routes.ts` is
out of this card (domain:server). With the session hold the console
sends one list read per session, so an ETag would save the one remaining
body per session and Studio's own reads; noted, not filed.
## Verification
Readings on head `49bae7f9`, which merges `origin/main` `dedc3505` into
the branch.
<details><summary>Tests</summary>
- New file: 15 passed.
- Consumer sweep on the pre-merge head `62a695fc`, every test file
naming the gate, `packages-io`, `PackagesPage`, `marketplaceApi`,
`AppHeader`, `SidebarItemMenu`, `PageView`, the app switcher, the
marketplace pages or `HomePage` (195 files in five batches): 1982 tests
passed.
- After the merge of main, the 73 files naming the gate, `packages-io`,
`marketplaceApi` or `PackagesPage` (Studio pillar suites excluded): 693
tests passed.
- `pnpm --dir ui exec vitest run scripts/`: 169 files passed, 2 skipped;
4900 tests passed.
</details>
<details><summary>Type-check and gates</summary>
- `pnpm --filter @object-ui/app-shell run type-check` (source and test
programs), on the pre-merge head `d5f99fa0`: exit 0. The merge of main
touched none of this PR's files; CI's Type Check UI is the reading on
the merged head.
- `node scripts/pm/dispatch-gates.mjs --commands` derived 34 families
for this diff: all 34 ran with exit 0, and `--ran` reconciled 34
derived, 34 run, 0 NOT MEASURED. The changeset and closing-keyword gates
ran against this body.
- `node scripts/check-sdui-manifest.mjs`: exit 0 (no registry change).
- `pnpm check:new-line-citations` (under `ui/`): 0 new citations.
- The whole-tree families and CI-only lanes are CI's; their check-runs
on this PR are the reading.
</details>
---
_Generated by [Claude
Code](https://claude.ai/code/session_01QsR2jREm6PtRBXSCPhrGQE)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 7788eed commit b177920
6 files changed
Lines changed: 545 additions & 31 deletions
File tree
- .changeset
- ui/packages/app-shell/src
- console/marketplace
- hooks
- views
- metadata-admin
- studio-design
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
14 | 14 | | |
15 | 15 | | |
16 | 16 | | |
| 17 | + | |
17 | 18 | | |
18 | 19 | | |
19 | 20 | | |
| |||
334 | 335 | | |
335 | 336 | | |
336 | 337 | | |
| 338 | + | |
| 339 | + | |
337 | 340 | | |
338 | 341 | | |
339 | 342 | | |
| |||
342 | 345 | | |
343 | 346 | | |
344 | 347 | | |
345 | | - | |
| 348 | + | |
346 | 349 | | |
347 | 350 | | |
348 | 351 | | |
| |||
871 | 874 | | |
872 | 875 | | |
873 | 876 | | |
| 877 | + | |
874 | 878 | | |
875 | 879 | | |
876 | 880 | | |
| |||
880 | 884 | | |
881 | 885 | | |
882 | 886 | | |
883 | | - | |
| 887 | + | |
884 | 888 | | |
885 | 889 | | |
886 | 890 | | |
| |||
1000 | 1004 | | |
1001 | 1005 | | |
1002 | 1006 | | |
| 1007 | + | |
1003 | 1008 | | |
1004 | 1009 | | |
1005 | 1010 | | |
1006 | 1011 | | |
1007 | | - | |
| 1012 | + | |
1008 | 1013 | | |
1009 | 1014 | | |
1010 | 1015 | | |
| |||
0 commit comments