Skip to content

Commit bf658d1

Browse files
feat(spec): a report nav item without reportName opens the app's report index; the console stops injecting All reports (#27566)
domain:spec#3 · os-support-ai · session_01WGufzgbJhFoXYsLSe5WSPP <!-- claude-projects-attribution: {"github_login":"os-support-ai"} --> _Requested by **Yibo Wang** · [project thread](https://claude.ai/code/project/chan_01BVUyyPie8gHwoPM18iABwB?thread=cmsg_01BVUyyPie8gHwoPM18iABwBARQoNehDR2CAhzzKsfZvvA)_ Fixes #27525 Clause-②: yes (widening) Metadata-protocol: added Skills-docs: none edited. Four governed hits teach a `report` nav item and all stay true for a named report; none teaches the index form, and none teaches that `reportName` is required: `skills/objectstack-ui/SKILL.md:171` (the `report` row lists `reportName`, `label`, `icon`, "Link to a report"), `skills/objectstack-ui/rules/navigation.md:38` (a named example), `skills/objectui/guides/app-composition.md:21` (`{type:'report', reportName}` routes to `/report/:name`) and `:34` (emit the typed target field for the chosen type). Named, not edited. ADR-0087: a widening, not a retirement. Nothing an author wrote before parses differently. **The claim's `Metadata-protocol: none` is measured as `added`.** The order says "as measured", and AGENTS.md's kinds put "a new accepted value" under `added`: `{ type: 'report' }` with no `reportName` was refused and is now accepted. The changesets carry `added`, and every package the PR moves is graded `minor`: `@objectstack/spec`, `@objectstack/cli` (help text only, but the PR-wide `added` sets its floor), `@object-ui/layout`, `@object-ui/app-shell`, `@object-ui/i18n` and `@object-ui/types`. **Migration** (in the `@object-ui/app-shell` changeset): an app that relied on the injected row adds one entry to its navigation where the index belongs: `{ id: 'nav_reports', type: 'report' }`. ## 维护者速读 - 改了什么:导航项 `type: 'report'` 不写 `reportName` 时表示「本应用的报表索引」(`/apps/APP/reports`);控制台不再给每个应用自动加「全部报表」行,只有导航里声明了这一项才画出来;Studio 与元数据编辑器的导航检查器都把未选报表的报表项显示为「报表索引」,选它会删掉 `reportName` 键而不是存 `''`;`os explain app`、两页文档和 QA 清单同步改写。 - 为什么改:维护者定了「加上才有」:索引入口由作者自己加。原来每个非 Studio 应用都被注入一行,没有报表的应用也有;而 spec 又不允许作者自己声明一个索引项。 - 风险与回滚:放宽接受集,原来能解析的导航照旧;依赖注入行的应用会少一个入口,迁移是加一行 `{ type: 'report' }`,仓内自带报表的 showcase 与 todo 示例已加。回滚即还原本 PR。 - 先看:`ui/packages/app-shell/src/layout/UnifiedSidebar.tsx`(删掉的注入块)、`ui/packages/layout/src/NavigationRenderer.tsx`(`resolveHref` 的 `report` 分支与无标签时的 `reportIndex` 询问)、`ui/packages/app-shell/src/views/metadata-admin/previews/AppNavCanvas.tsx`(`NAV_TARGET_OPTIONAL`,保存不再丢掉索引项)。 - 两分钟验证:`cd ui && pnpm exec vitest run packages/app-shell/src/layout/__tests__/UnifiedSidebar.reportIndexEntry-27525.test.tsx`,应见 6 个用例全绿:未声明时无「All reports」行,声明后链接到 `/apps/crm/reports` 且在该路由高亮。 - 需要你做的:无,席位落地。 ## What changes **Measured on `origin/main` `57c51aab` (before).** 1. Spec: `NavigationItemSchema.safeParse({ id: 'nav_reports', type: 'report' })`: ``` bare report item: [{"expected":"string","code":"invalid_type","path":["reportName"],"message":"Invalid input: expected string, received undefined"}] named report item: ACCEPTED empty-string reportName: ACCEPTED ``` 2. Console: `UnifiedSidebar.tsx` drew a static row under `{!isStudioApp && (…)}` after the app's `NavigationRenderer`: a `Link` to `${basePath}/reports` with `data-testid="sidebar-report-index"`, text `sidebar.allReports` ("All reports" / 「全部报表」), for every non-Studio app, whatever its navigation or its reports. Pinned on main by `UnifiedSidebar.reportIndexLink-13020.test.tsx` ("links the app's report index, though the navigation names no report"). 3. ⌘K: **the card's premise does not hold on main.** `CommandPalette.tsx` already lists reports beyond the navigation: `useAppReportEntries(activeApp, open && !inStudio, gates)` (`views/appReportIndex.ts`, objectui#13020 / #13273 / objectstack#26892), the reports the app ships (its package's own, plus those its served navigation names), limited by `mayOpenReport` to the ones whose datasets read objects the member may read, and by the navigation's gates. That is the same list `AppReportIndexView` shows. Pinned on main by `AppReportIndex-13020.test.tsx` ("⌘K offers the app's reports beyond its navigation": a report no entry names is offered to a reader; one on an unreadable object, a joined report with such a block, and another package's report are not). The HotCRM comment the card quotes predates it. So Target 3 needed no new reader; this PR reuses that one and pins it under an index-only navigation (below). **After (head `dd0bd483`; unchanged at `19f4cf83`).** The same probe answers `bare report item: ACCEPTED`; the other two lines are unchanged. ### The spec (`packages/spec/src/ui/app.zod.ts`) `ReportNavItemSchema.reportName` is `z.string().optional()`. Its describe: "Target report name. Omit it and the entry opens the app's report index (the reports the app ships that the member may open) instead of one report." The variant's TSDoc states the two targets and that the console draws an index entry only where the navigation declares one. The unknown-key teaching at `navItemSurface` (`reportname` → "type: 'report' (with reportName)" on every other variant) stays true and is pinned. `stack.zod.ts`'s cross-check of a named `reportName` against `reports` reads `typeof nav.reportName === 'string'`, so an absent name skips it; lint `validate-nav-target-refs` likewise skips an absent target. Regenerated: `content/docs/references/ui/app.mdx` (five `reportName` rows go from ✅ to optional with the new text). `check:generated` reports the other 13 artifacts current, `check:authorable-surface` included: the base lists keys (`ui/ReportNavItem:reportName` stays), not required-ness, so it does not move. No metadata-form row or form translation names `reportName`, so no bundle moves. ### Readers of a nav item's `reportName`, measured | Reader | Absent `reportName`, before | Change | |:---|:---|:---| | `layout` `resolveHref` (sidebar link, mobile tab bar, ⌘K, Studio preview, active-row election) | `#`, a dead entry | `BASE/reports`; the active-row election reads the same answer, so the entry lights on the index route (exact match) and not on `report/NAME` | | `layout` `inheritedNavItemLabel` (label-less entry) | the entry's `id` | asks the host's resolver for the new `{ kind: 'reportIndex' }` target, else the `id` | | `app-shell` `useNavTargetLabel` (the console's resolver) | n/a | answers `sidebar.allReports` ("All reports" / 「全部报表」), the injected row's own text | | `app-shell` `UnifiedSidebar` | injected an index row for every non-Studio app | injects nothing; the declared entry is drawn by `NavigationRenderer` | | `app-shell` `CommandPalette` | n/a (an absent name would put `undefined` in the set of nav-named reports and in the item value) | the index entry is offered as itself; only a non-empty `reportName` counts as nav-named; the value never holds `undefined` | | `app-shell` `appReportIndex` (`reportNamesIn`) | already skipped a non-string name | unchanged | | `app-shell` `AppHeader` (breadcrumb) | reads the route, not the nav item | unchanged | | Studio nav-item inspector (`StudioDesignSurface`) | "unbound", with the "left out of the saved navigation" hint | not unbound; the report picker's empty face reads 「报表索引」 / "Report index", with a hint saying what it opens; picking it removes `reportName` | | Studio save (`AppNavCanvas` `navPayloadOf`) | left the entry out of the save | kept (`NAV_TARGET_OPTIONAL`); the file's own parity pin (left out exactly when the spec refuses it bare) now judges `report` taken bare | | metadata-admin `AppNavInspector` | the combo field, empty; it offered reports only, so once a report was picked there was no way back to the index | the report combo leads with a "Report index" row (new optional `emptyLabel` on `InspectorComboField`), and picking it REMOVES `reportName`, as the Studio does, never writing `''`; the 「报表索引」 hint shows while `reportName` is absent; its live preview shows `/apps/APP/reports` | | metadata-admin `AppPreview` | dropped a label-less, childless entry with no target | keeps it: the target reads `reports` | | `navSurface` `bindTarget` (what the Studio rail opens) | `null` | unchanged: the index is no metadata item to design | ### Shipped apps Every non-Studio app showed the injected row. Read as the maintainer's 「加上才有」: the apps this repo ships that **ship reports** get the entry, so none loses a working index; an app with no report had an index that said "No reports", and gets nothing. - `examples/app-showcase` (four reports, all named in its navigation): `{ id: 'nav_report_index', type: 'report', icon: 'bar-chart-3' }`, appended last in its reports group so no existing index path moves (`nav-and-detail-grants.test.ts` pins `navigation[3].children[11]`). - `examples/app-todo` (ships `task.report.ts`, which its navigation never named, so the injected row was its only door): `{ id: 'nav_reports', type: 'report', icon: 'bar-chart-3' }` in its Analytics group. - `examples/app-crm`, `examples/app-multi-package`: ship no report, unchanged. No package under `packages/` ships a report. Both new entries carry no `label`, so they show the console's own name for the index and need no translation key. ## Tests - **Spec pin** (`packages/spec/src/ui/app.test.ts`, replacing "should reject report nav item without reportName"): a bare `report` item parses on the variant, through `NavigationItemSchema` and inside an `AppSchema`, and holds no `reportName` after parse; an unknown key on it is still refused, and `reportName` on a `page` item still answers "type: 'report' (with reportName)"; the describe names the report index. - **Console** (`UnifiedSidebar.reportIndexEntry-27525.test.tsx`, the 13020 sidebar file moved and rewritten, since its first assertion is now the opposite behaviour): (a) no injected row on an app whose navigation declares no index entry (no "All reports" link, no `/apps/crm/reports` href, no `sidebar-report-index`); (b) a declared label-less index entry links `/apps/crm/reports` named "All reports", a labelled one shows its label; (c) it is the active row on `/apps/crm/reports` and the named report entry is not; (d) drawing it reads no report list; CONTROL: a named report entry still routes to `/apps/crm/report/crm_pipeline`. - **⌘K** (`AppReportIndex-13020.test.tsx`, new block): under a navigation that declares only `{ type: 'report' }` and names no report, the palette finds "Win Rate" and "Pipeline", and not "Case Aging" (unreadable object), "Mixed Blocks" (a block on it) or "Headcount" (another package); it offers the index entry itself, and its value holds no `undefined`. - **Studio**: `StudioDesignSurface.navItemTypes-11790` (a report entry is born as the index, parses, is sent, shows the index hint and no "left out" hint, the picker offers `Report index` first; a pick writes `reportName`; the index face removes it again), `StudioDesignSurface.sharedSelect-11865` (`reportBorn` and a new `reportBound` row on the index face), `AppNavCanvas.navPayload-11776` (bare `report` taken and sent). - **metadata-admin** (`AppNavInspector.reportIndexClear-27525.test.tsx`, new): picking "Report index" on a named report entry writes it with no `reportName` key; the trigger names the index while the key is absent; CONTROL: picking a report writes its name; CONTROL: a `page` entry's combo offers no empty row. Ablated (the empty pick routed back to `patch({ reportName: '' })`): the first case goes red, the other three stay green; restored. - Updated for the removed row: `UnifiedSidebar.iconModeLabelFocus-12795` (the tab walk no longer ends on "All reports") and `UnifiedSidebar.favoritesMenu-12427` (no row to exclude). <details><summary>Runs</summary> All on the shared container, under `scripts/pm/os-verify-lock.sh`. - Spec, at `0b6a152e` (pre-merge): `pnpm --filter @objectstack/spec test` → `Test Files 751 passed (751)`, `Tests 22330 passed | 1 todo`; `test:repo` → `Test Files 86 passed (86)`, `Tests 1432 passed`; `typecheck` → `VERDICT command-exit 0` (`check:test-typecheck: OK`). - Spec, after merging `origin/main` (head `dd0bd483`): `build` + `check:generated` → "All 14 generated artifacts are up to date"; `test` → `Test Files 752 passed (752)`, `Tests 22341 passed | 1 todo`. - ui, 138 files that import or pin the edited modules (the sidebar, ⌘K, report index, Studio nav inspector and save, metadata-admin nav inspector / canvas / preview, `@object-ui/layout`, the `@object-ui/types` nav and mirror-parity suites), `cd ui && pnpm exec vitest run --maxWorkers=3 FILES`: first run 4 red, all four pinning the old behaviour (a bare report entry unbound / left out of the save / not taken bare); updated as listed above. Final, on `dd0bd483`: `Test Files 138 passed (138)`, `Tests 1755 passed (1755)`. Narrowed to these files on purpose; CI's Test UI runs the rest. - ui type-check, `@object-ui/types`, `@object-ui/layout`, `@object-ui/i18n`, `@object-ui/app-shell`: all four `type-check: Done`, `VERDICT command-exit 0`. The full `turbo run build --concurrency=2` (115/115 tasks, the console's `tsc` included) is green too. </details> <details><summary>Ablation</summary> One ablation, of the behaviour the console pins test: `resolveHref`'s index answer put back to `'#'` with `scripts/ablation-replace.mjs` (`ui/packages/layout/src/NavigationRenderer.tsx`, anchor hit 1 → 0, blob `9f866973` → `f4b35fdd`), then the sidebar and report-index files run (the ui suites alias `@object-ui/layout` to its source, so no build sits between): `Tests 3 failed | 13 passed`: (b) both "draws a declared index entry" cases and (c) "the active row" went red. Restored: blob `9f866973` == HEAD, `git diff HEAD` empty. The ⌘K index-entry pin stayed green under it, as expected: it asserts the entry is offered, not where it goes. (a) "no injected row" is the inverse of the assertion `main` pinned (`UnifiedSidebar.reportIndexLink-13020.test.tsx`: "links the app's report index, though the navigation names no report"), so it is red against `main`'s sidebar by construction. </details> ## Acceptance notes - `reportName: ''` was accepted by the spec before and still is (this PR widens absence only). The console read `''` as falsy and answered `#`; it now answers the index, the same as absence. - The mobile tab bar (`AppSchemaRenderer`) takes no label resolver, so a label-less index entry there shows its `id`, as every other label-less entry already does in that host. - `reportName: ''` stays accepted by the spec and refused by `defineStack` when the app declares reports; this PR does not rule on it. Both inspectors now reach the index by removing the key, so neither writes `''`. - Teaching text (added to the file surface by the claim amendment, comment 6497681948): `os explain app` (`packages/cli/src/commands/explain.ts`) now says a report item may omit `reportName` to open the index; `content/docs/ui/apps.mdx` (Report Navigation, and the label-fallback paragraph) and `content/docs/ui/reports.mdx` document `{ type: 'report' }` and say the console adds no index entry of its own. - QA ledger: `docs/qa/platform-checklist/areas/platform-core.json` still told runners the console adds the "All reports" row to every app. `platform-core.nav-surfaces-render` (r8: `steps[2]`, `acceptance[1]` clause and verify) and `platform-core.builtin-apps-nav-render` (r10: `steps[9]`) now say the index is a declared, served entry, each with a `history` row; `check:platform-checklist` OK. History rows that record the old row are left as written. - The skills' index form (`skills/objectstack-ui/SKILL.md:171`, `rules/navigation.md:38`, `skills/objectui/guides/app-composition.md:21,34`) is carded separately by seat 3. - The Studio's "+" still sends a new report entry into the create-a-report flow; an index entry is made by choosing the `report` type on an entry and leaving the report unpicked. ## Verification - `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived **108** commands at `01ed76af` and the same 108 at `0b6a152e`. Run serially; first pass 103 exit 0, `pnpm --dir ui exec vitest run scripts/` exit 1 (two 15 s sweep timeouts beside a locked run: `check-vi-mock-inherit.test.ts`, `js-comment-mask-returns-interpolation-9424.test.ts`; rerun alone: `Test Files 2 passed (2)`, `Tests 91 passed`; the rest of that run: `Tests 4899 passed`), and three exit 3 (prerequisite: no dist) that went to exit 0 after the full build: `check:skill-examples` ("274 prose examples type-check"), `check:dual-build-cjs-loads` ("112 published require entry point(s) across 69 package(s) load"), `check:i18n-coverage` ("OK (14 config(s), 527 baselined untranslated string(s), none new)"). - `dispatch-gates --ran`: "108 derived, 108 run, 0 NOT-MEASURED, 0 UNRUN". - After the changeset grades moved to `minor` and the merge: every changeset gate in the list again (`check-changeset-no-major --base origin/main`, `check-empty-changeset` and its self-test, `check:changeset-gate-self-tests`, `check:pm-changeset-deadline-census`): exit 0. `GITHUB_EVENT_NAME=pull_request node scripts/check-changeset-no-major.mjs --base origin/main --event` (this body): exit 0, "LEVEL AXIS: this PR declares clause-② `yes (widening)` and metadata-protocol `added`, and no package … it moves is graded `patch`". - `gen:authorable-surface-base` refuses on this shallow clone (it cannot establish the anchor's ancestry); `check:authorable-surface` is green inside `check:generated`, and the base, a key list, has no entry this change moves. - Round 2 (head `19f4cf83`; `origin/main` `72b08eec` merged at `fad30718`). The teaching-text paths derive **85** commands, run serially: 83 exit 0, and `check:docs` (exit 1) and `check:skill-examples` (exit 3) refused because the spec build predated the merge; both exit 0 after `pnpm --filter @objectstack/spec build`. The inspector paths derive **34**, all exit 0. Over the whole branch the list is now **111**: the 108 above plus `check:i18n`, `check:i18n-walk-parity` and `check:platform-checklist`, all green. `dispatch-gates --ran`: "111 derived, 111 run, 0 NOT-MEASURED, 0 UNRUN". `cd packages/cli && pnpm test`: `Test Files 529 passed | 1 skipped (530)`, `Tests 6602 passed | 9 skipped`. ui metadata-admin inspectors: `Test Files 205 passed (205)`, `Tests 2786 passed | 1 skipped`. `@object-ui/app-shell` `type-check`: exit 0. - Not run locally: the dogfood battery (no `!` in the title and no `(narrowing)` arm); the repo-wide CI families. CI's check-runs are the record for those. --- _Generated by [Claude Code](https://claude.ai/code/session_01WGufzhEW5k2a4Vd5KSupfu)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 144702c commit bf658d1

36 files changed

Lines changed: 531 additions & 108 deletions
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
'@object-ui/app-shell': minor
3+
---
4+
5+
feat(console): the sidebar draws the report index only where the app's navigation declares it
6+
7+
Clause-②: yes (widening)
8+
Metadata-protocol: added
9+
10+
The console no longer injects an "All reports" row at the foot of every app's sidebar. The app's report index (`/apps/APP/reports`) is an entry its navigation declares, a `report` item with no `reportName`, drawn and highlighted like any other entry; an app whose navigation declares none shows no index entry. With no `label`, the entry shows the console's own name for the index ("All reports"). The ⌘K palette offers the declared entry, and keeps finding every report the member may open from the reports the app ships, entry or not.
11+
12+
In the Studio, a `report` entry with no report chosen reads as the report index (「报表索引」 / "Report index") instead of an unbound entry, and a save keeps it. The metadata editor's navigation inspector matches it: the report picker leads with a "Report index" row, and picking it removes `reportName` from the entry rather than saving `reportName: ''`.
13+
14+
**Migration.** An app that relied on the injected row adds one entry to its navigation where the index belongs: `{ id: 'nav_reports', type: 'report' }`.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@objectstack/cli': minor
3+
---
4+
5+
docs(cli): `os explain app` says a `report` navigation item may omit `reportName` to open the app's report index
6+
7+
Clause-②: yes (widening)
8+
Metadata-protocol: added
9+
10+
The `navigation` row of `os explain app` now ends its `report -> reportName` target with "(omit it on a report item to open the app's report index)", the widening `@objectstack/spec` ships in this release. Only the help text changes; the command's behaviour and output shape are the same. Graded `minor` because this PR declares `Metadata-protocol: added`, which sets the floor for every package it moves.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@object-ui/i18n': minor
3+
---
4+
5+
feat(i18n): the Studio's navigation inspector names the report index face
6+
7+
Clause-②: yes (widening)
8+
Metadata-protocol: added
9+
10+
New `studio` strings, en and zh: `engine.inspector.appNav.reportIndex` (the empty face of a `report` entry's report picker) and `engine.inspector.appNav.reportIndexHint`.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@object-ui/layout': minor
3+
---
4+
5+
feat(layout): a `report` navigation entry with no `reportName` resolves to the app's report index
6+
7+
Clause-②: yes (widening)
8+
Metadata-protocol: added
9+
10+
`resolveHref` answers `BASE/reports` for a `report` entry that names no report (it answered `#`), so the entry links, and is the active row, on the report index route. A label-less one asks the host's `NavTargetLabelResolver` for the new `{ kind: 'reportIndex' }` target, else shows its `id`.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@objectstack/spec': minor
3+
---
4+
5+
feat(spec): a `report` navigation item without `reportName` opens the app's report index
6+
7+
Clause-②: yes (widening)
8+
Metadata-protocol: added
9+
10+
`ReportNavItemSchema.reportName` is optional. `{ type: 'report', reportName }` opens that report, as before; `{ type: 'report' }` with no `reportName` opens the app's report index (`/apps/APP/reports`), the list of the reports the app ships that the member may open. Before, an item with no `reportName` was refused (`navigation.N.reportName: expected string, received undefined`). Nothing that parsed before parses differently, and the stack's cross-check of a named `reportName` against `reports` is unchanged.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@object-ui/types': minor
3+
---
4+
5+
docs(types): `NavigationItem.reportName` says that an omitted name opens the app's report index
6+
7+
Clause-②: yes (widening)
8+
Metadata-protocol: added
9+
10+
The description of the mirror's optional `reportName` now states what its absence means on a `report` entry. No shape changes.

‎content/docs/references/ui/app.mdx‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ const result = ActionNavItemSchema.parse(data);
194194
| **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime |
195195
| **requiresService** | `string` | optional | Hide/disable this entry unless the named kernel service is registered |
196196
| **type** | `'report'` | ✅ | |
197-
| **reportName** | `string` | ✅ | Target report name |
197+
| **reportName** | `string` | optional | Target report name. Omit it and the entry opens the app's report index (the reports the app ships that the member may open) instead of one report. |
198198

199199
### Nested Shape: `App.navigation[number][type='action']`
200200

@@ -588,7 +588,7 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc`
588588
| **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime |
589589
| **requiresService** | `string` | optional | Hide/disable this entry unless the named kernel service is registered |
590590
| **type** | `'report'` | ✅ | |
591-
| **reportName** | `string` | ✅ | Target report name |
591+
| **reportName** | `string` | optional | Target report name. Omit it and the entry opens the app's report index (the reports the app ships that the member may open) instead of one report. |
592592

593593
### Nested Shape: `NavigationArea.navigation[number][type='action']`
594594

@@ -787,7 +787,7 @@ A navigation contribution: a package injecting nav items into an app it does not
787787
| **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime |
788788
| **requiresService** | `string` | optional | Hide/disable this entry unless the named kernel service is registered |
789789
| **type** | `'report'` | ✅ | |
790-
| **reportName** | `string` | ✅ | Target report name |
790+
| **reportName** | `string` | optional | Target report name. Omit it and the entry opens the app's report index (the reports the app ships that the member may open) instead of one report. |
791791

792792
### Nested Shape: `NavigationContribution.items[number][type='action']`
793793

@@ -1039,7 +1039,7 @@ This schema accepts one of the following structures:
10391039
| **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime |
10401040
| **requiresService** | `string` | optional | Hide/disable this entry unless the named kernel service is registered |
10411041
| **type** | `'report'` | ✅ | |
1042-
| **reportName** | `string` | ✅ | Target report name |
1042+
| **reportName** | `string` | optional | Target report name. Omit it and the entry opens the app's report index (the reports the app ships that the member may open) instead of one report. |
10431043

10441044
### Nested Shape: `NavigationItem[type='report'].dataSource`
10451045

@@ -1294,7 +1294,7 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc`
12941294
| **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime |
12951295
| **requiresService** | `string` | optional | Hide/disable this entry unless the named kernel service is registered |
12961296
| **type** | `'report'` | ✅ | |
1297-
| **reportName** | `string` | ✅ | Target report name |
1297+
| **reportName** | `string` | optional | Target report name. Omit it and the entry opens the app's report index (the reports the app ships that the member may open) instead of one report. |
12981298

12991299
### Nested Shape: `ReportNavItem.dataSource`
13001300

‎content/docs/ui/apps.mdx‎

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,21 @@ Links to a report — a `report` metadata item, named by `reportName`:
170170
{ id: 'nav_pipeline', type: 'report', label: 'Pipeline Report', reportName: 'sales_pipeline', icon: 'file-bar-chart' }
171171
```
172172

173+
Leave `reportName` out and the entry opens the app's **report index** instead
174+
(`/apps/<app>/reports`): one page listing the reports the app ships that the
175+
member may open, each linking to its own report. The console draws no index
176+
entry of its own, so an app that wants one declares it, once, wherever it
177+
belongs in the menu:
178+
179+
```typescript
180+
{ id: 'nav_reports', type: 'report', icon: 'bar-chart-3' }
181+
```
182+
183+
With no `label`, the entry shows the console's own name for the index
184+
("All reports"). The ⌘K palette finds every report the member may open from the
185+
reports the app ships, so an app does not need one entry per report to make
186+
them searchable.
187+
173188
### Action Navigation
174189

175190
Runs an action instead of navigating to a surface. The reference lives in a
@@ -290,7 +305,8 @@ labelled view, else the object's or dashboard's label, else the target's machine
290305
name. A renamed object, view or dashboard therefore shows its new name on the next
291306
load with no navigation edit. Other entries fall back to their target's name
292307
(`pageName`, `reportName`, the `url`) and a group to its `id`, so give those a
293-
`label`. A `label` you write renders verbatim; never write an empty one, omit the
308+
`label`. A report index entry (a `report` entry with no `reportName`) shows the
309+
console's own name for the index. A `label` you write renders verbatim; never write an empty one, omit the
294310
key instead.
295311

296312
## Designing the navigation

‎content/docs/ui/reports.mdx‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,11 @@ stack does not define fails validation with `App '<app>' navigation references r
300300
no reports at all, where the target may come from another package.) The entry resolves to
301301
`/report/<reportName>` in the console.
302302

303+
A `report` entry **without** `reportName` opens the app's report index
304+
(`/apps/<app>/reports`), which lists every report the app ships that the member may
305+
open. The console adds no index entry by itself: declare `{ id: 'nav_reports', type:
306+
'report' }` where the menu should carry it.
307+
303308
## Permissions and protection
304309

305310
The report schema carries exactly **one** access-shaped block, and it is not about

0 commit comments

Comments
 (0)