Repository navigation
Commit 99c0696
fix(app-shell): an areas-only app lands on its first area entry; a url/action-only app says it opens from its menu (#29849)
<!-- claude-projects-attribution: {"github_login":"zhuangjianguo"} -->
_Requested by **Steedos** · [project
thread](https://claude.ai/code/project/chan_018ETKaNwMZCNRYD7D8Q9Ubb?thread=cmsg_018ETKaNwMZCNRYD7D8Q9Ubb6YPmefvezx1CHWqAYCx6ZW)_
Fixes #29771
Clause-②: yes
Metadata-protocol: none
## 维护者速读
-
**改了什么**:控制台打开一个应用的根地址时,落地页不再只看顶层导航:先看顶层导航,再按顺序看每个分区(areas)的导航,在全部这些导航里先找对象、页面、仪表盘、报表或组件条目,找不到再找第一个文档条目。若成员能看到的条目全是外部链接或动作,根页面显示一个空状态,说明“这个应用从菜单使用”,不再是一片空白。设计器预览里的“首页”提示同步这条规则。
-
**为什么改**:条目只放在分区里的应用、以及菜单只有链接或动作的应用,打开后主区域一片空白,没有跳转也没有任何说明,成员分不清是应用坏了还是本来就空(#29678
的现场实测,#29769 只修了纯文档应用)。
-
**风险与回滚**:只在原来什么都不落地的情况下生效;顶层导航有可落地条目的应用照旧落到原处。新空状态不带“去设置”按钮,也不说“无权限”,因为成员确实有权限。新增两条界面文案(十种语言)。回滚即
revert 本 PR。
- **先看**:`ui/packages/app-shell/src/console/AppContent.tsx` 的
`resolveLandingRoute` / `firstRouteAcross`(跨全部导航树的落地顺序)和
`AppRootLanding` 末尾(原来的 `return
null`);`ui/packages/app-shell/src/console/AppNoAccessEmptyState.tsx` 的
`menu-only` 分支。
- **两分钟验证**:`pnpm --dir ui exec vitest run
packages/app-shell/src/console/__tests__/AppContent.docOnlyLanding-29678.test.tsx`,应见
13 个用例全绿:只有分区的应用落到分区里的第一个对象,只有链接/动作的应用显示 `data-reason="menu-only"`
的空状态,原有对照组照旧。
- **需要你做的**:无,席位落地。
## What changes
Claim: issuecomment 6595973888, amended by 6596042756 (declaration lines
`Clause-②: yes`, `Metadata-protocol: none`; surface plus
`AppNoAccessEmptyState.tsx` and the ten locale packs).
An app whose entries live only under `areas`, or whose every entry is a
`url` or `action` link, opened `/apps/APP` to an empty `main`.
`resolveLandingRoute` walked only `shownTrees[0]` (the top-level
`navigation`), although `AppRootLanding` already handed it every shown
tree, and `AppRootLanding` then rendered `null`.
- **Landing walk across every shown tree** (`AppContent.tsx`, new
`firstRouteAcross`). The declared landing first, then the first working
entry (object, page, dashboard, report, component) across `navigation`
and each `areas[].navigation` in order, then the first `doc` entry
across the same order. The preference holds **across** the trees, not
per tree: a working entry in any area outranks a `doc` entry in the
top-level navigation. That is #29678's rule (a working entry outranks
reading material) applied to the app rather than to one tree. The
sidebar elects the area that holds the entry the route addresses
(`electSidebarArea`), so a landing in a later area opens with that area
active.
- **No in-app landing at all** (`AppContent.tsx`). When nothing lands
and the app still shows the member entries, every one of them is a `url`
or `action` link. The root now draws `AppNoAccessEmptyState` with a new
`reason="menu-only"` instead of `null`.
- **`AppNoAccessEmptyState.tsx`**: the `menu-only` reason has its own
title and description (`empty.appOpensFromMenu`,
`empty.appOpensFromMenuDescription`), a menu icon instead of the lock,
and no Setup link even when `setupPath` is given, because the member has
access. The other two reasons are unchanged.
- **`@object-ui/i18n`**: the two keys in all ten locale packs.
- **`AppPreview.tsx`**: `findFirstLandingAcross` normalizes each area's
navigation and walks the trees in the same order, so the designer names
the entry the console opens on.
- **`appServesNoNavigation.ts`**: the docblock said the resolver walks
the top-level navigation only and that a doc entry does not land; both
went stale (#29678, and this PR). Docblock only.
- **Moved pin**: `AppContent.studioRootLanding-12113.test.tsx` pinned,
for objectui#12113, that a url-only app "stays on its root, with no
empty state and no page" (it asserted `app-no-access-empty-state`
absent). This card makes exactly that blank root the defect, so the case
now asserts the `menu-only` empty state; the "stays on its root, no
page" half is kept. Seat-approved in the card thread.
## Tests
- `AppContent.docOnlyLanding-29678.test.tsx`, new top-level `describe`
on the same harness (real `AppContent`, `ExpressionProvider`,
`MePermissionsProvider`): an areas-only app lands on the first working
entry of its areas; a working entry in an area outranks a top-level doc;
a doc in an area lands when no tree holds a working entry; a
url/action-only app (group and area included) draws
`data-reason="menu-only"` with no Setup link; CONTROL: no shown
navigation keeps `data-reason="no-navigation"`; CONTROL: a pending read
in an area holds the skeleton, then lands. The file's #29678 cases
(doc-only lands, object app redirects, no-navigation, pending read) stay
as controls.
- `AppNoAccessEmptyState.sharedState-25915.test.tsx`: `menu-only` added
to the `data-reason` table; its copy is not the no-access sentence; no
Setup link with a setup path.
- `AppPreview.areas-11027.test.tsx`: the preview names the area entry
for an areas-only app, prefers it over a top-level doc, and CONTROL
keeps a top-level working entry first.
- `AppContent.studioRootLanding-12113.test.tsx`: the moved pin above.
## Acceptance notes
- **Across trees, not per tree** (seat ruling, card thread): a working
entry anywhere outranks a doc anywhere. An app with a manual at the top
of `navigation` and objects in an area opens on the first object.
- **The menu-only screen reuses `AppNoAccessEmptyState`** (seat ruling)
and keeps its `data-testid="app-no-access-empty-state"`;
`data-reason="menu-only"` is what tells it apart. It says nothing about
access and offers no Setup link.
- **`Clause-②: yes`**: the two new keys widen `@object-ui/i18n`'s built
`dist/locales/en.d.ts` (every leaf is a readonly literal and the entry
re-exports `en` and `type TranslationKeys`). Changeset level stays
`patch` with `Metadata-protocol: none`.
- Read from source (`UnifiedSidebar.tsx`), not measured, not changed:
when an app has a visible area, `UnifiedSidebar` draws the active area's
tree in place of the top-level `navigation` (`activeArea?.navigation ||
readableAppNavigation`). So in an app that has both, a landing on a
top-level entry can open a page with no sidebar entry. This PR does not
change that; the order here only decides which entry lands.
- Observed, not changed: on showcase the url-only app's root rewrote to
its package address (`/apps/com.example.r29771-landing`), which is the
existing package-address behaviour for the package's first app.
## Verification
Final head: `d1b4d8e8` (merge of `origin/main` at `b0ef25f5`).
<details><summary>Tests on the final head</summary>
On `d1b4d8e8`: every `console/__tests__/AppContent.*.test.tsx` file,
`AppNoAccessEmptyState.sharedState-25915.test.tsx`, every
`previews/AppPreview*.test.tsx` file and `packages/i18n/`: 150/150
files, 1932 passed, 13 skipped. `turbo run type-check
--filter=@object-ui/app-shell --filter=@object-ui/i18n`: 66/66 tasks.
Before the main merge (head `fecc3403`): the four touched files 4/4,
33/33; the consumer sweep (every ui test file naming `AppContent`,
`AppNoAccessEmptyState`, `AppPreview`, `appServesNoNavigation` or the
empty-state keys) 155/155 files, 1305/1305 tests in four locked batches;
`packages/i18n/` 95/95 files, 1443 passed, 13 skipped; `turbo run
type-check --filter=@object-ui/app-shell --filter=@object-ui/i18n` 66/66
tasks; `eslint` on the touched ui files 0 errors.
</details>
<details><summary>Ablations (each restored, blob equal to HEAD and git
diff HEAD empty, by scripts/ablation-replace.mjs)</summary>
1. `resolveLandingRoute` walks `shownTrees.slice(0, 1)` only:
`AppContent.docOnlyLanding-29678.test.tsx` 4 failed, 9 passed (the
areas-only, area-over-doc, doc-in-area and pending-in-area cases).
2. The fallthrough back to `return null`: 2 failed across
`docOnlyLanding-29678` and `studioRootLanding-12113` (both menu-only
cases).
3. `AppPreview` walks `[navItems]` only:
`AppPreview.areas-11027.test.tsx` 2 failed, 6 passed.
4. The `menu-only` branch made unreachable:
`AppNoAccessEmptyState.sharedState-25915.test.tsx` 2 failed, 5 passed.
</details>
<details><summary>Local gate union</summary>
`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` derived 35 families on `fecc3403`; all 35
ran with exit 0 (`pnpm --dir ui exec vitest run scripts/`: 170 files
passed, 2 skipped). `--ran` reconciliation: 35 derived, 35 run, 0
NOT-MEASURED, 0 UNRUN. Also `node scripts/check-i18n-en-drift.mjs` (from
`ui/`) exit 0: 2 keys added, 0 en values changed. The body gates
(`check-adr-0087-registration`, `check-changeset-no-major`,
`check-closing-keyword-parity`) ran against this body.
</details>
<details><summary>Live, showcase --fresh with the Console Vite on
source</summary>
Four apps published into a scratch package `com.example.r29771-landing`:
url-only (one `url` and one `action` entry), areas-only (`navigation:
[]`, first area holds an object entry for `showcase_account`, second a
`url`), doc-only (a doc entry for `showcase_manual`), and an object app.
Opened at `/apps/APP` as admin and as `auditor.demo@example.com`.
| app | before (AppContent.tsx at main) | after |
|:---|:---|:---|
| url-only | empty `main` (0 chars), admin and auditor |
`data-reason="menu-only"`, "R29771 Links opens from its menu", admin and
auditor |
| areas-only | empty `main` (0 chars), admin and auditor | redirects to
`/apps/r29771_areas/showcase_account`, the list renders |
| doc-only (control) | |
`/apps/r29771_doconly/docs/manual/showcase_index` |
| object app (control) | | `/apps/r29771_object/showcase_account` |
The before leg swapped only `AppContent.tsx` to main under Vite (Vite
was confirmed serving main's module), then restored it (blob equal to
HEAD). The after shots were taken before the swap. Shots: desktop for
both personas plus a 390 px phone shot of the menu-only screen.
</details>
---
_Generated by [Claude
Code](https://claude.ai/code/session_01QsR2jSW6s8ABou6nBvnfbz)_
Co-authored-by: Claude <noreply@anthropic.com>1 parent 2a0f0b7 commit 99c0696
19 files changed
Lines changed: 326 additions & 26 deletions
File tree
- .changeset
- ui/packages
- app-shell/src
- console
- __tests__
- views/metadata-admin/previews
- i18n/src/locales
| 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 | |
|---|---|---|---|
| |||
1825 | 1825 | | |
1826 | 1826 | | |
1827 | 1827 | | |
| 1828 | + | |
| 1829 | + | |
| 1830 | + | |
1828 | 1831 | | |
1829 | 1832 | | |
1830 | 1833 | | |
| |||
1850 | 1853 | | |
1851 | 1854 | | |
1852 | 1855 | | |
1853 | | - | |
1854 | | - | |
| 1856 | + | |
| 1857 | + | |
| 1858 | + | |
| 1859 | + | |
| 1860 | + | |
| 1861 | + | |
| 1862 | + | |
| 1863 | + | |
| 1864 | + | |
| 1865 | + | |
| 1866 | + | |
| 1867 | + | |
| 1868 | + | |
| 1869 | + | |
| 1870 | + | |
| 1871 | + | |
| 1872 | + | |
| 1873 | + | |
| 1874 | + | |
| 1875 | + | |
| 1876 | + | |
| 1877 | + | |
1855 | 1878 | | |
1856 | 1879 | | |
1857 | 1880 | | |
| |||
1981 | 2004 | | |
1982 | 2005 | | |
1983 | 2006 | | |
1984 | | - | |
1985 | | - | |
1986 | | - | |
1987 | | - | |
1988 | | - | |
1989 | | - | |
1990 | | - | |
| 2007 | + | |
| 2008 | + | |
| 2009 | + | |
| 2010 | + | |
| 2011 | + | |
| 2012 | + | |
| 2013 | + | |
| 2014 | + | |
1991 | 2015 | | |
1992 | 2016 | | |
1993 | 2017 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
23 | 23 | | |
24 | 24 | | |
25 | 25 | | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
26 | 33 | | |
27 | 34 | | |
28 | 35 | | |
29 | | - | |
| 36 | + | |
30 | 37 | | |
31 | 38 | | |
32 | 39 | | |
| |||
47 | 54 | | |
48 | 55 | | |
49 | 56 | | |
50 | | - | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
51 | 62 | | |
52 | | - | |
| 63 | + | |
53 | 64 | | |
54 | 65 | | |
55 | 66 | | |
| |||
58 | 69 | | |
59 | 70 | | |
60 | 71 | | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
61 | 90 | | |
62 | 91 | | |
63 | 92 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
23 | 23 | | |
24 | 24 | | |
25 | 25 | | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
26 | 35 | | |
27 | 36 | | |
28 | 37 | | |
| |||
326 | 335 | | |
327 | 336 | | |
328 | 337 | | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
| 343 | + | |
| 344 | + | |
| 345 | + | |
| 346 | + | |
| 347 | + | |
| 348 | + | |
| 349 | + | |
| 350 | + | |
| 351 | + | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
| 355 | + | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
| 370 | + | |
| 371 | + | |
| 372 | + | |
| 373 | + | |
| 374 | + | |
| 375 | + | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
| 387 | + | |
| 388 | + | |
| 389 | + | |
| 390 | + | |
| 391 | + | |
| 392 | + | |
| 393 | + | |
| 394 | + | |
| 395 | + | |
| 396 | + | |
| 397 | + | |
| 398 | + | |
| 399 | + | |
| 400 | + | |
| 401 | + | |
| 402 | + | |
| 403 | + | |
| 404 | + | |
| 405 | + | |
| 406 | + | |
| 407 | + | |
| 408 | + | |
| 409 | + | |
| 410 | + | |
| 411 | + | |
| 412 | + | |
| 413 | + | |
| 414 | + | |
| 415 | + | |
| 416 | + | |
| 417 | + | |
| 418 | + | |
| 419 | + | |
| 420 | + | |
| 421 | + | |
| 422 | + | |
| 423 | + | |
| 424 | + | |
| 425 | + | |
| 426 | + | |
| 427 | + | |
| 428 | + | |
| 429 | + | |
| 430 | + | |
| 431 | + | |
| 432 | + | |
| 433 | + | |
| 434 | + | |
| 435 | + | |
| 436 | + | |
| 437 | + | |
| 438 | + | |
| 439 | + | |
| 440 | + | |
| 441 | + | |
| 442 | + | |
| 443 | + | |
| 444 | + | |
| 445 | + | |
| 446 | + | |
| 447 | + | |
| 448 | + | |
| 449 | + | |
| 450 | + | |
| 451 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
23 | 23 | | |
24 | 24 | | |
25 | 25 | | |
26 | | - | |
| 26 | + | |
| 27 | + | |
27 | 28 | | |
28 | 29 | | |
29 | 30 | | |
| |||
243 | 244 | | |
244 | 245 | | |
245 | 246 | | |
246 | | - | |
| 247 | + | |
247 | 248 | | |
248 | 249 | | |
249 | 250 | | |
| |||
255 | 256 | | |
256 | 257 | | |
257 | 258 | | |
258 | | - | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
259 | 263 | | |
260 | 264 | | |
261 | 265 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
60 | 60 | | |
61 | 61 | | |
62 | 62 | | |
| 63 | + | |
63 | 64 | | |
64 | 65 | | |
65 | 66 | | |
| |||
74 | 75 | | |
75 | 76 | | |
76 | 77 | | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
28 | 28 | | |
29 | 29 | | |
30 | 30 | | |
31 | | - | |
32 | | - | |
33 | | - | |
34 | | - | |
35 | | - | |
36 | | - | |
37 | | - | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
38 | 40 | | |
39 | 41 | | |
40 | 42 | | |
| |||
0 commit comments