Repository navigation
Commit ea4dcf9
fix(app-shell): an app whose navigation holds only doc entries opens its first doc (#29769)
<!-- claude-projects-attribution: {"github_login":"zhuangjianguo"} -->
_Requested by **Steedos** · [project
thread](https://claude.ai/code/project/chan_018ETKaNwMZCNRYD7D8Q9Ubb?thread=cmsg_018ETKaNwMZCNRYD7D8Q9UbbLCajCYUR31RRZKTSbAZhke)_
Fixes #29678
Clause-②: no
Metadata-protocol: none
## 维护者速读
-
**改了什么**:控制台打开一个应用的根地址时,若导航里没有对象、页面、仪表盘、报表或组件条目可落地,就打开成员能看到的第一个文档条目(应用内文档门户)。设计器预览里的“首页”提示同步这条规则。
- **为什么改**:导航只有文档条目的应用,打开后主区域一片空白,没有跳转也没有空状态,管理员、读者、外部成员都一样(#29672 QA
实测,本 PR 现场复现)。
-
**风险与回滚**:只在原来什么都不落地的情况下生效;第一条是文档、后面有对象的应用照旧落到第一个对象。文档的可读性由服务端把关:成员读不了的文档条目服务端已从导航里去掉,此时仍是“无权限”空状态。回滚即
revert 本 PR。
- **先看**:`ui/packages/app-shell/src/console/AppContent.tsx` 的
`resolveLandingRoute`(兜底顺序)与
`findFirstRoute`(类型集合参数化);`ui/packages/app-shell/src/views/metadata-admin/previews/AppPreview.tsx`
的 `findFirstLanding`。
- **两分钟验证**:`pnpm --dir ui exec vitest run
packages/app-shell/src/console/__tests__/AppContent.docOnlyLanding-29678.test.tsx`,应见
7 个用例全绿:纯文档应用落到 `/apps/APP/docs/BOOK`,各对照组照旧。
- **需要你做的**:无,席位落地。
## What changes
Claim: issuecomment 6593568853 (round 30), amended by 6593605696
(AppPreview parity).
An app whose navigation holds only `doc` entries opened `/apps/APP` to
an empty `main`: `findFirstRoute` lands on object, page, dashboard,
report and component entries only, and `AppRootLanding` rendered `null`
when none landed.
- **`AppContent.tsx`**: `findFirstRoute` takes the set of entry types it
lands on (default: the five it always took). `resolveLandingRoute` is
now: the declared landing, else the first working entry, else the first
`doc` entry the member is shown (groups walked, `url` passed over). A
`doc` entry's route is the one the sidebar follows, `resolveHref`'s
in-app docs portal (`docs/BOOK[/DOC]` under the app), which the console
host mounts in its `extraRoutes`.
- Docs are a last resort, not a tree-order peer: an app whose first
entry is its manual, followed by working entries, still opens on its
first working entry, as before and as the designer preview's existing
pin states.
- The doc is gated as a declared `doc` landing already is (#28450): by
the server's audience gate on the app read (it drops an unreadable `doc`
entry) and on the doc read. The sidebar's own doc-readability answer
(`useNavDocTargetCheck`) is NOT available to the landing without a new
wait: it is a lazy `doc`/`book` list read that answers `undefined` (pass
every entry) until it loads, so on the landing's first render it never
has an answer. Measured live below: a doc behind a book the member may
not read never reaches the landing, because the server already dropped
its entry.
- The pending-read hold (objectui#13097) is unchanged: both of its
landing reads go through `resolveLandingRoute`, so a pending bound entry
ahead of the doc still holds the skeleton.
- **`AppPreview.tsx`** (amendment): `findFirstLanding` mirrors the
runtime, so the designer preview names the first doc as the landing when
nothing else lands.
- The `return null` branch now covers only apps whose entries are all
`url`/`action` links or live only under `areas`; its comment says so.
## Tests
-
`ui/packages/app-shell/src/console/__tests__/AppContent.docOnlyLanding-29678.test.tsx`
(7, harness as `AppContent.landingWaitsReads-13097.test.tsx`, with the
host's docs route stubbed): a doc-only app lands on its first doc; a doc
inside a group behind a `url` link; CONTROL first entry a doc then an
object lands on the object; CONTROL object app redirects; CONTROL no
navigation keeps `AppNoAccessEmptyState`; CONTROL a pending bound entry
ahead of the doc holds the skeleton and lands on the bound entry when
shown; the same pending entry failing lands on the doc.
- `AppPreview.docEntry-11197.test.tsx`: one new case, a `url` + two
`doc` entries names the first doc as the landing. The existing "→
Accounts" pin is unchanged and green.
<details><summary>Ablations (scripts/ablation-replace.mjs, mutation
verified on disk, restored to HEAD with an empty git diff)</summary>
- Drop the doc fallback in `resolveLandingRoute`: 3 failed / 4 passed
(the two doc-only cases and the pending-then-fail case red; the four
controls green).
- Drop the doc pass in `AppPreview`: 1 failed / 3 passed (the new
preview case red).
</details>
<details><summary>Sweep</summary>
Every ui test file importing `AppContent` or `AppPreview` (59 files,
`git grep -l`), two locked batches at 2 workers, on the merged head
e5ff194: 31/31 files 202 tests, 28/28 files 333 tests. The same 59
files were green on d8352d2 before the merge of main (30/30 201, 29/29
334). `@object-ui/app-shell` type-check exit 0 on e5ff194.
</details>
## Acceptance notes
- Live measurement (showcase `--fresh` backend, Vite console on this
branch's source, 1440x900): package `com.qa.r29678` with a published doc
`r29678_guide`, a doc-only app `r29678_help` (one `doc` entry), a
control app `r29678_ctrl` (one `object` entry), and `r29678_locked_app`
whose only entry opens a book gated to a permission set nobody holds.
Signed in as admin, as `auditor.demo` (reader) and as `phone.demo`
(outsider):
- before (AppContent.tsx from `origin/main` in the same running
console): `r29678_help` stays at `/apps/com.qa.r29678` with an empty
`main` for all three;
- after: all three land on
`/apps/com.qa.r29678/docs/com.qa.r29678/r29678_guide` (the portal's
canonical in-book URL) and read the doc;
- control `r29678_ctrl`: `/apps/r29678_ctrl/showcase_task` in both legs,
for all three;
- `r29678_locked_app`: the server drops the unreadable entry, so all
three get "Nothing in R29678 Locked is available to you yet" in both
legs; nobody is sent to a refused doc page.
Screenshots and the JSON readings are in the project files under
`issue-29678/shots/`; the seat links them.
- Not changed, reported to the seat as a finding: an app whose entries
are all `url`/`action` links, or live only under `areas`, still opens to
an empty `main` (measured live on this head: `r29678_urlonly`, one `url`
entry, empty `main` for admin, reader and outsider).
- `ui/packages/app-shell/src/console/appServesNoNavigation.ts`'s
docblock still lists `doc` among the entries the landing resolver never
lands on. It is outside this card's file surface, so it is left for the
seat.
## Verification
Local, `dispatch-gates --commands --repo objectstack-ai/objectstack`
derived 35 families on this diff: all 35 run, every one exit 0, `--ran`
reconciliation `35 run, 0 NOT-MEASURED, 0 UNRUN` (the ui `vitest run
scripts/` suite under the verify lock: 170 files / 4977 tests green).
Re-run on the merged head e5ff194: all 35 again exit 0, `--ran` `35
run, 0 NOT-MEASURED, 0 UNRUN` (ui `vitest run scripts/` 170 files / 4977
tests). eslint on the four touched ui files: 0 errors. `dispatch-gates
--tier`: no path-derived mandate. Changed lines 431 (under 3000). CI
check-runs: see this PR.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01QsR2jRL46SawdHef859orD)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 57a2612 commit ea4dcf9
5 files changed
Lines changed: 405 additions & 31 deletions
File tree
- .changeset
- ui/packages/app-shell/src
- console
- __tests__
- views/metadata-admin/previews
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1759 | 1759 | | |
1760 | 1760 | | |
1761 | 1761 | | |
1762 | | - | |
| 1762 | + | |
| 1763 | + | |
| 1764 | + | |
| 1765 | + | |
| 1766 | + | |
| 1767 | + | |
| 1768 | + | |
| 1769 | + | |
| 1770 | + | |
| 1771 | + | |
1763 | 1772 | | |
1764 | 1773 | | |
1765 | | - | |
1766 | | - | |
1767 | | - | |
1768 | | - | |
1769 | | - | |
1770 | | - | |
1771 | | - | |
1772 | | - | |
1773 | | - | |
1774 | | - | |
1775 | | - | |
| 1774 | + | |
1776 | 1775 | | |
1777 | 1776 | | |
1778 | 1777 | | |
1779 | 1778 | | |
1780 | | - | |
1781 | 1779 | | |
1782 | | - | |
| 1780 | + | |
1783 | 1781 | | |
1784 | 1782 | | |
1785 | 1783 | | |
| |||
1819 | 1817 | | |
1820 | 1818 | | |
1821 | 1819 | | |
1822 | | - | |
1823 | | - | |
| 1820 | + | |
| 1821 | + | |
| 1822 | + | |
| 1823 | + | |
| 1824 | + | |
| 1825 | + | |
| 1826 | + | |
| 1827 | + | |
| 1828 | + | |
| 1829 | + | |
1824 | 1830 | | |
1825 | 1831 | | |
1826 | 1832 | | |
| |||
1837 | 1843 | | |
1838 | 1844 | | |
1839 | 1845 | | |
1840 | | - | |
| 1846 | + | |
| 1847 | + | |
1841 | 1848 | | |
1842 | 1849 | | |
1843 | 1850 | | |
| |||
1967 | 1974 | | |
1968 | 1975 | | |
1969 | 1976 | | |
1970 | | - | |
1971 | | - | |
1972 | | - | |
1973 | | - | |
1974 | | - | |
| 1977 | + | |
| 1978 | + | |
| 1979 | + | |
| 1980 | + | |
| 1981 | + | |
| 1982 | + | |
1975 | 1983 | | |
1976 | 1984 | | |
1977 | 1985 | | |
| |||
0 commit comments