Repository navigation
Commit 85c40fd
fix(app-shell): Home, the app switcher and launcher blocks offer only the apps /me/apps lists, so a tabPermissions-hidden app draws no tile (#27021)
<!-- claude-projects-attribution: {"github_login":"os-warren"} -->
_Requested by **Warren** · [project
thread](https://claude.ai/code/project/chan_01U5RdnAAmkSQmZbqh6FtqZ8?thread=cmsg_01U5RdnAAmkSQmZbqh6FtqZ8X61MrFcMwootjvrLF4Py6o)_
Fixes #26838
Clause-②: no
Metadata-protocol: none
## 维护者速读
- 改了什么:首页磁贴、应用切换器菜单和 `app:launcher` 页面块都按当前用户的 `GET /me/apps`
结果收窄应用列表,权限集里 `tabPermissions: hidden` 的应用不再出现。命令面板的应用列表和不带应用段的默认落地也按
/me/apps 收窄。
- 为什么改:隐藏只在 `/me/apps` 生效,而这三个入口都读 `/meta/app`(卡上说切换器已正确,实测它也读
`/meta/app`),成员仍能看到并点开被管理员隐藏的应用。
- 风险与回滚:无安全插件时 `/me/apps` 答 404、或读取失败,一律回退到原来的完整列表;首页多等一次 `/me/apps`
首读。回滚即 revert 本 PR。
-
先看:`ui/packages/app-shell/src/hooks/useLauncherApps.tsx`(收窄与回退规则)、`ui/apps/console/src/AppContent.tsx`(唯一挂载点)、`ui/packages/app-shell/src/console/home/HomePage.tsx`(首页等待首读)。
- 两分钟验证:`pnpm --dir ui exec vitest run
packages/app-shell/src/console/home/__tests__/HomePage.launcherHiddenApps-26838.test.tsx`,预期
4 个用例通过,被隐藏的 Showcase 不出磁贴。
- 需要你做的:无,席位落地。
## What changes
- New `ui/packages/app-shell/src/hooks/useLauncherApps.tsx`:
- `LauncherAppsProvider` reads `GET /api/v1/me/apps` once per signed-in
user, and again only when the active `/meta/app` name set gains a name
the held answer was not asked about (an install or a publish
mid-session).
- `useLauncherApps(apps)` returns `filterActiveApps(apps)` minus every
app the answer was asked about and did not list. The entries stay
`/meta/app`'s own objects (labels, icons, `_packageId`, publish state);
`/me/apps` contributes names only.
- No provider, a `404` (no security plugin serves the route) or a failed
read narrows nothing. A failure other than `404` logs one
`console.warn`; a `404` is not asked again for that user.
- `pending` is true only while the first read is out.
- `ui/apps/console/src/AppContent.tsx`: `ConsolePermissionsScope`, the
one common ancestor of `/home` and `/apps/:appName/*`, mounts
`LauncherAppsProvider` inside `MePermissionsProvider`, with the same
server URL and authenticated fetch. The seat extended the claim's file
surface to this file (amendment 6457698059).
- `HomePage`: the tiles and the System Settings link come from
`useLauncherApps`; the page's loading gate also waits for the first
read, so a hidden tile is never drawn and then removed. The host-app
resolution and the recent rows' app labels keep the full active list,
because a hidden app still opens.
- `AppSwitcher`: the menu lists `useLauncherApps(apps)`. The pill still
names a hidden app while the user is in it (its existing fallback over
all active apps).
- `app:launcher` block (`views/app-launcher-renderer.tsx`): its openable
set is `useLauncherApps(apps)`; it still issues no request of its own.
- `LauncherAppsProvider` and `useLauncherApps` are exported from
`@object-ui/app-shell`.
- `useServedApps(apps)` (exported): the `/me/apps` narrowing alone, no
active/hidden filter. `CommandPalette` lists
`useServedApps(apps.filter(active !== false))`, so its own `hidden:
true` behaviour is unchanged.
- app-shell `console/AppContent.tsx`: the default-app landing (a URL
naming no or an unknown app) picks from `useLauncherApps(apps)`; the
route lookup still finds a hidden app, which keeps opening from its URL.
- Changeset `.changeset/26838-launcher-hidden-tabs.md`:
`@object-ui/app-shell` and `@object-ui/console` patch.
Not changed: no server change, no route guard (a hidden app keeps
opening from its URL), `default_off` untouched,
`console/appObjectScope.ts` untouched.
### Why a provider, not a hook that fetches
No `@objectstack/client` method reads `/me/apps`, so the read is a
direct authenticated fetch. The ui network-escape guard
(`ui/vitest.setup.network-escape-guard.ts`) reds any test whose render
reaches a relative fetch, and about 130 test files render `HomePage`,
`AppSwitcher`, `AppHeader` or `HomeLayout`. An explicitly mounted
provider, the `MePermissionsProvider` pattern, keeps those tests
unchanged: without it a surface reads the unnarrowed list, exactly as
before.
## Tests
- `hooks/__tests__/useLauncherApps-26838.test.tsx` (8 cases): narrows by
the answer and keeps the `/meta/app` entries; no provider narrows
nothing; `404` narrows nothing, silently, and is not re-asked; `500`
narrows nothing and warns once; `pending` only for the first read; an
app added after the read passes and triggers one re-read; two consumers
share one read; no user, no read.
- `console/home/__tests__/HomePage.launcherHiddenApps-26838.test.tsx` (4
cases): `/meta/app` = [setup, crm, showcase_app] and `/me/apps` =
[setup, crm] draws no Showcase tile; a set hiding Setup removes the
System Settings link; the `404` control draws every tile; the loading
line holds while the first read is out.
- `layout/__tests__/AppSwitcher.launcherHiddenApps-26838.test.tsx` (3
cases): the hidden app is not in the menu; the `404` control lists it;
the pill still names it when the user is in it.
- `chrome/CommandPalette.launcherHiddenApps-26838.test.tsx` (2 cases):
the hidden app is not offered; the `404` control offers it.
- `console/__tests__/AppContent.defaultLandingHiddenApp-26838.test.tsx`
(3 cases): the declared default `crm` hidden by `/me/apps` = [sales]
makes an unknown-app URL land on `sales`; the `404` control keeps `crm`;
a URL naming `crm` still opens it.
<details><summary>Ablations (one-time; restored, blob equals HEAD each
time)</summary>
Each leg ran under the lock through `node scripts/ablation-replace.mjs`
(anchor hit 1 -> 0, restored blob equals HEAD, `git diff HEAD` empty),
on commit `9f4f6f31`:
- `HomePage.tsx`: the tiles from `activeApps` instead of `launcher.apps`
-> `HomePage.launcherHiddenApps-26838`: 2 failed / 2 passed (the
hidden-tile case and the loading-hold case go red).
- `useLauncherApps.tsx`: the narrowing predicate replaced by `return
true` -> hook, Home and switcher files: 7 cases red across the three
files.
- `AppSwitcher.tsx`: the menu from the old inline `active`/`hidden`
filter -> `AppSwitcher.launcherHiddenApps-26838`: 2 failed / 1 passed.
- Round 2, on `bb175bf7`: `CommandPalette` `useServedApps(...)` -> the
plain active filter: 1 failed / 1 passed; app-shell `console/AppContent`
`useLauncherApps(apps)` -> the inline active/hidden filter: 1 failed / 2
passed.
</details>
## Acceptance notes
- **CommandPalette** and **default-app landing**: fixed in this PR at
the seat's REWORK (6458597628).
- **Card premise correction**: the card says the app switcher reads
`/me/apps` and hides the app. On `origin/main` no file under `ui/` read
`/me/apps`; the switcher read `/meta/app` like Home. Both are fixed
here.
- **Observation, not filed**: `GET /me/apps` answers `200 { apps: [] }`
when its handler throws (the outer `catch` in
`current-user-endpoints.ts`). This client cannot tell that from "every
app is hidden", so on that answer Home draws no app tiles and no System
Settings link (only the page frame), the switcher menu lists no app, and
the palette offers no app; a hidden-by-throw state lasts until the next
read (a new user or a new app name). Read-only inference from source;
not reproduced.
- Home now waits for the first `/me/apps` read alongside `/meta/app`;
the read starts once the app list has loaded, so it adds one request
round trip before the tiles draw on a cold load.
## Verification
Round 2 on head `7b20f2d5` (`8cc708ab` merged `origin/main` `acc9c57d`;
`7b20f2d5` changes one test file): affected set 308 files / 2514 tests
passed on `8cc708ab`; `dispatch-gates --commands` 34/34 exit 0 and
`--ran` 0 NOT-MEASURED on `8cc708ab`; app-shell package lint 0 errors;
CI on `7b20f2d5` green (43 success, 10 skipped). Round 1 readings below.
Round 1 on head `cbe8d370` (origin/main merged in), app-shell closure
built first (7m33s under the lock).
- New tests: 3 files, 15/15 passed.
- Affected set: every app-shell and console test file that names
`HomePage`, `AppSwitcher`, `AppHeader`, `HomeLayout`, `ConsoleLayout`,
`AppContent`, the launcher block or the hook, plus all of
`console/home/__tests__` and `layout/__tests__`: 284 files, 2359 tests
passed (on `9f4f6f31`, before the merge of origin/main, which touched no
file under these paths).
- `pnpm --filter @object-ui/app-shell run type-check`: exit 0. `pnpm
--filter @object-ui/console run type-check`: exit 0 (after `--filter
'@object-ui/console^...' build`).
- `node scripts/pm/dispatch-gates.mjs --commands`: 34 derived commands,
each exit 0; `--ran` reconciliation: 34 derived, 34 run, 0 NOT-MEASURED.
Includes `node scripts/check-sdui-manifest.mjs` (exit 0) and `pnpm --dir
ui exec vitest run scripts/` (exit 0).
- Also run: `pnpm --dir ui run check:spec-symbols`,
`check:new-line-citations`, `check:test-path-roots`: exit 0.
- CI owns the repo lint and the remaining type-check lanes.
## 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. (CI runs the repo lint)
- [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 by [Claude
Code](https://claude.ai/code/session_01JQwxbYwBbR8jCpWPE5Kosz)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 6af79c1 commit 85c40fd
15 files changed
Lines changed: 1169 additions & 18 deletions
File tree
- .changeset
- ui
- apps/console/src
- packages/app-shell/src
- chrome
- console
- __tests__
- home
- __tests__
- hooks
- __tests__
- layout
- __tests__
- 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 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
11 | 11 | | |
12 | 12 | | |
13 | 13 | | |
14 | | - | |
| 14 | + | |
15 | 15 | | |
16 | 16 | | |
17 | 17 | | |
| |||
406 | 406 | | |
407 | 407 | | |
408 | 408 | | |
409 | | - | |
| 409 | + | |
| 410 | + | |
| 411 | + | |
| 412 | + | |
| 413 | + | |
| 414 | + | |
| 415 | + | |
410 | 416 | | |
411 | 417 | | |
412 | 418 | | |
| |||
| 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
73 | 73 | | |
74 | 74 | | |
75 | 75 | | |
| 76 | + | |
76 | 77 | | |
77 | 78 | | |
78 | 79 | | |
| |||
251 | 252 | | |
252 | 253 | | |
253 | 254 | | |
254 | | - | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
255 | 259 | | |
256 | 260 | | |
257 | 261 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
33 | 33 | | |
34 | 34 | | |
35 | 35 | | |
| 36 | + | |
36 | 37 | | |
37 | 38 | | |
38 | 39 | | |
| |||
336 | 337 | | |
337 | 338 | | |
338 | 339 | | |
339 | | - | |
340 | | - | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
| 343 | + | |
| 344 | + | |
341 | 345 | | |
342 | 346 | | |
343 | 347 | | |
| |||
0 commit comments