Repository navigation
Commit a7af1c1
feat(console): report index groups reports by object, with All/Recent views and search (#27622)
domain:ui#2 · os-bill · session_01Utp6CY2n1JmZZKrHgtp9jC
<!-- claude-projects-attribution: {"github_login":"os-bill"} -->
_Requested by **Bill** · [project
thread](https://claude.ai/code/project/chan_016iMXWSZDzqWRWvWzdNo8wg?thread=cmsg_016iMXWSZDzqWRWvWzdNo8wgP6XkfpjCfyzzG2KwQPUZfy)_
Fixes #27526
Clause-②: no
Metadata-protocol: none
## 维护者速读
- 改了什么:应用的报表索引页从一张平铺列表改为按报表主对象分组(导航顺序,「其它」垫底,组头带计数可折叠),顶部加「全部 /
最近」两个视图(按用户记住选择)和搜索框;每行显示报表名、所属对象、上次打开时间。
- 为什么改:维护者裁决「索引页按对象分组 + 最近/我的/全部」;报表多了以后平铺列表找不到东西。「我创建的」和行内「所有者」按维护者裁决 B
不做(服务端下发的报表没有创建者;所有权随 North Star 报表那一行再做)。
- 风险与回滚:只动 Console 的索引页和十个语言包,不动 spec、不动服务端、不加元数据键;回滚即 revert 本
PR。视图偏好存在浏览器 localStorage 的 `os:pref:reports.index.view`。
- 先看:`ui/packages/app-shell/src/views/appReportIndex.ts`(主对象推导
`reportPrimaryObject`、分组 `groupReportsByObject`、偏好声明)→
`AppReportIndexView.tsx`(视图与搜索)→ 新测试
`AppReportIndex.groupsViews-27526.test.tsx`。
- 两分钟验证:`pnpm --dir ui exec vitest run
packages/app-shell/src/views/__tests__/AppReportIndex.groupsViews-27526.test.tsx`,预期
17/17 通过。
- 需要你做的:无。裁决 B 已记在卡上(6509006168),席位 ACCEPT 后落地。
## What changes
The app's report index (`/apps/APP/reports`, `AppReportIndexView`)
listed every report the member may open as one flat list. It now follows
the card's Target 1–3, except the 「我创建的」 view and the row's owner, which
the maintainer's ruling B dropped from the card (see Acceptance notes).
- **Grouped by primary object, derived, no new key.**
`reportPrimaryObject` in `appReportIndex.ts` takes the object of the
first dataset the report binds, in `reportDatasets` order (its own
`dataset`, then each `blocks[].dataset`), that the dataset list maps to
an object. Rule chosen for a report with several datasets: **the first
dataset that resolves to an object**, so a joined report whose first
block names an unknown dataset still lands under the object of its next
block. A report with none goes to the 「其它」 section.
- **Section order.** `groupReportsByObject`: first the objects the app's
served navigation names (`type: 'object'` entries, in the order
`flattenNavigation` first meets them), then the objects it does not
name, by label, then 「其它」. Each section header is a button with
`aria-expanded` that shows the count and collapses its section
(visit-only state).
- **Views.** A view switch with two buttons, **All reports** (全部) and
**Recent** (最近). The choice is `REPORT_INDEX_VIEW_PREFERENCE`, declared
through `defineWorkspacePreference` in the `user` scope, stored as
`{"value":"all"|"recent","v":1}` under `os:pref:reports.index.view`; a
reader who never chose, or whose stored value the codec does not read,
sees All reports. Recent reads the recent-items store (`useRecentItems`,
the `report` entries and their `visitedAt`) and lists the index's
reports the reader opened, most recent first, as one flat list: the
order is the point of that view, so it is not split by object (my
reading of Target 1 + 2; the sections apply to All reports). A recent
report the index does not list (another app's, an unreadable one) is not
shown.
- **Rows.** A row is the report's link (label and description, so the
link's accessible name stays the report's label, which the existing
index pins rely on), its object's label and when the reader last opened
it (`timeAgo` in the display locale), with the link's overlay covering
the row. The object and time cells hide below `sm`.
- **Search.** A search box filters the current view by report label,
description or object label, case-insensitively; a view with no match
says so.
- **Object labels.** `useReportObjectLabels` labels the sections
(plural) and rows (singular) from the object definitions the shell reads
for the app: the app's package object scope, the same `readObjectScope`
read the navigation gates already make, plus the grouped objects outside
the package by name; an app with no package reads those objects by name.
The index still never reads the whole object registry (the existing
CONTROL pin holds, and a new one beside it).
- **i18n.** Nine new `console.reportIndex.*` keys in all ten packs:
`viewsLabel`, `viewAll`, `viewRecent`, `searchPlaceholder`,
`searchLabel`, `otherSection`, `lastOpened`, `recentEmpty`, `noMatch`.
The ⌘K palette reads the same `useAppReportEntries`; its entries gain an
optional `object` and are otherwise unchanged.
`useReportObjectLabels` reads `getItemsByType('object')` only where the
app's object scope cannot be read, so it is a reader the objectui#12811
census counts: `console/__tests__/openPathObjectReaders-12811.test.ts`
gains one `scope` row for `appReportIndex.ts`, the same kind as
`useServedViewItems` and `useObjectPresence`.
## Why
Maintainer ruling on the card, quoted verbatim: 「ui(索引页按对象分组 +
最近/我的/全部)」. A flat list stops working once an app ships more than a
handful of reports.
## Tests
New
`ui/packages/app-shell/src/views/__tests__/AppReportIndex.groupsViews-27526.test.tsx`
(17 tests), through the real `MetadataProvider`,
`MePermissionsProvider`, `ExpressionProvider`, `RecentItemsProvider` and
`I18nProvider`:
- grouping: sections in navigation order (Opportunity before Lead
although L sorts first), an object the navigation does not name next,
「其它」 last, with counts; a joined report goes under the first of its
datasets that resolves; the row's object cell; a header collapses only
its own section; labels read from the package scope and never the whole
registry;
- views: All reports with no stored choice; Recent lists opened reports
most recent first, only the index's, with "Opened N hours ago"; the
choice is written as `{"value":"recent","v":1}` and read back by a fresh
mount; an unreadable stored value is no choice; Recent with nothing
opened says so; no 我创建的 button;
- search: within All reports by label (empty sections dropped, counts
recomputed) and by object label; within Recent only among opened
reports; the no-match message.
The three existing index files (`AppReportIndex-13020`,
`.navContributions-13273`, `.navGates-26892`) pass unchanged.
Run at head `82d69466` (after the latest merge of `main`):
- `pnpm --dir ui exec vitest run --maxWorkers=2
packages/app-shell/src/views/__tests__/
packages/app-shell/src/__tests__/` plus 56 named files: the ⌘K palette,
`AppContent`, `AppHeader`, sidebar and route-chunk files that read
`useAppReportEntries` or the console routes, and the objectui#12811
census: Test Files 164 passed (164), Tests 1523 passed (1523). The rest
of the app-shell suite is CI's `Test UI`, green on this head.
- `pnpm --dir ui exec vitest run packages/i18n/`: Test Files 90 passed,
Tests 1380 passed, 13 skipped (the packs' key-parity pins included).
- Four ablations, each through `scripts/ablation-replace.mjs` (restored
and proved restored): navigation order replaced by label order, 2 tests
red; primary object taken from the first dataset only, 3 red; the view
held in `useState` instead of the preference, 2 red; Recent listing
every report, 3 red.
## Acceptance notes
- **Ruled B: 「我创建的」 and the row's owner are dropped.** Ruling-ref:
6509006168. The maintainer ruled B on #27526 (on dev report 6500744179):
both leave this card, and report ownership comes back with the North
Star reports line, not here. No spec, engine or metadata-protocol
change. The measurement behind the question stays on record: the served
`/meta/report` item is the stored row's parsed body plus read
decorations (`storedOverlayEntries` in
`packages/metadata-protocol/src/protocol-stored-overlay-reads.ts`); a
report the signed-in admin saved through `PUT /api/v1/meta/report/NAME`
on a live showcase has `sys_metadata.created_by` set, yet neither
`/meta/report` nor `/meta/report/NAME` serves a creator; `ReportSchema`
declares no owner key; a code-authored report (every HotCRM report) has
no creator; and a code package refuses report saves (422), so a code
app's index never lists a member's console-saved report. The index
renders no 我创建的 control, and a test pins its absence.
- **Recent is one flat list, not grouped**, and **sections apply to All
reports**: my reading of Target 1 with Target 2.
- **The primary-object rule** is the first dataset that resolves to an
object, not strictly the first dataset (see What changes).
- **Last-opened time** comes from the recent-items store, which keeps
the reader's last 20 targets of every kind: a report opened long ago
shows no time and is not in Recent.
- **Docs.** `ui/AGENTS.md` #2 asks a feature to update the package
README and its `content/docs/frontend/` page; no docs page describes the
report index today and the dispatch's file surface excludes docs, so
none is written here.
- **Live run.** HotCRM is outside this session's repository scope, so
the run booted `examples/app-showcase` (`objectstack dev --ui --fresh`)
on the built console. A code package is read-only for report saves, so
four env-wide reports over the invoice, account and project datasets and
a console app naming those objects were saved. Its index showed Invoices
2, Accounts 1, Projects 1, Tasks 2 in navigation order; Recent listed
the two opened reports with their times; the stored choice survived a
reload; search narrowed to one section. The run found the last-opened
cell cutting its text, fixed in `19b7a1d9`. Screenshots are in the
project thread; nothing is uploaded here.
## Verification
<details><summary>Typecheck and lint (head `82d69466`)</summary>
- `pnpm --filter @object-ui/app-shell type-check`: exit 0, after
rebuilding the `@object-ui/app-shell^...` closure the merge moved; the
new test file is in `tsconfig.test.json`'s program (`--listFilesOnly`
count 1).
- `pnpm --filter @object-ui/i18n type-check`: exit 0.
- `eslint` on the four touched app-shell source and test files: 0
errors. Its 5 warnings are `no-explicit-any`: 3 on lines of
`appReportIndex.ts` that `main` already has, 2 on the `getObjectSchema`
test double the new file copies from `AppReportIndex-13020.test.tsx`.
- `pnpm --dir ui check:new-line-citations`: 0 new citations.
- Declarations re-measured on the final diff: `appReportIndex.ts` is
internal (not re-exported by the package entry), the preference is a
browser storage key, and the ten packs gain keys only, so `Clause-②:
no`; no metadata author can write anything new, so `Metadata-protocol:
none`.
</details>
<details><summary>Derived gates</summary>
At head `82d69466`, `node scripts/pm/dispatch-gates.mjs --commands
--repo objectstack-ai/objectstack`: 34 derived (the same list as at
every earlier head); each run and recorded, reconciled with `--ran`: "34
derived famil(ies) accounted for — 34 run, 0 NOT-MEASURED". `pnpm --dir
ui exec vitest run scripts/` timed out in one tree-wide scan file
(`check-vi-mock-inherit.test.ts`, 15 s) inside the gate loop and passed
when run alone (Test Files 169 passed, 2 skipped); the line below is the
solo run.
```
node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0
node scripts/check-adr-0087-registration.mjs --self-test :: exit 0
node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0
node scripts/check-closing-keyword-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0
node scripts/check-comment-mask-corpus.mjs :: exit 0
node scripts/check-empty-changeset.mjs --base origin/main :: exit 0
node scripts/check-empty-changeset.mjs --self-test :: exit 0
node scripts/check-sdui-manifest.mjs :: exit 0
node scripts/check-sdui-manifest.mjs --self-test :: exit 0
node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0
node scripts/release-pending-publish.mjs --self-test :: exit 0
pnpm --dir ui exec vitest run scripts/ :: exit 0
pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0
pnpm check:changeset-gate-self-tests :: exit 0
pnpm check:closed-ceremony-text :: exit 0
pnpm check:cross-package-test-inputs :: exit 0
pnpm check:dispatcher-error-vocabulary :: exit 0
pnpm check:driver-memory-census :: exit 0
pnpm check:engine-double-contract :: exit 0
pnpm check:gitlink-declared :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:pm-changeset-deadline-census :: exit 0
pnpm check:published-files :: exit 0
pnpm check:query-options-erasure :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:skill-identifier-liveness :: exit 0
pnpm check:test-source-alias :: exit 0
pnpm check:tier-file-adoption :: exit 0
pnpm check:type-check-coverage :: exit 0
pnpm check:type-check-debt :: exit 0
pnpm check:undeclared-dep-imports :: exit 0
pnpm check:watch-hint-literal :: exit 0
pnpm check:where-matcher :: exit 0
```
</details>
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
(`packages/spec` untouched.)
- [x] 2. Every name I added uses the casing for its kind.
- [x] 3. No formatter ran; `pnpm lint` passes. (eslint on the touched
files: 0 errors; the repo-wide run is CI's.)
- [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: n/a.
- [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_01Utp6CYhwxF1tGkRRemmhXK)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 01eab23 commit a7af1c1
15 files changed
Lines changed: 993 additions & 24 deletions
File tree
- .changeset
- ui/packages
- app-shell/src
- console/__tests__
- views
- __tests__
- 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 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
63 | 63 | | |
64 | 64 | | |
65 | 65 | | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
66 | 70 | | |
67 | 71 | | |
68 | 72 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
15 | 15 | | |
16 | 16 | | |
17 | 17 | | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
18 | 33 | | |
19 | 34 | | |
| 35 | + | |
20 | 36 | | |
21 | | - | |
22 | | - | |
23 | | - | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
24 | 40 | | |
25 | 41 | | |
26 | | - | |
| 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 | + | |
27 | 69 | | |
28 | 70 | | |
29 | | - | |
| 71 | + | |
| 72 | + | |
30 | 73 | | |
31 | 74 | | |
32 | 75 | | |
33 | 76 | | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
34 | 83 | | |
35 | 84 | | |
36 | 85 | | |
| |||
41 | 90 | | |
42 | 91 | | |
43 | 92 | | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
44 | 158 | | |
45 | 159 | | |
46 | 160 | | |
| |||
61 | 175 | | |
62 | 176 | | |
63 | 177 | | |
64 | | - | |
65 | | - | |
66 | | - | |
67 | | - | |
68 | | - | |
69 | | - | |
70 | | - | |
71 | | - | |
72 | | - | |
73 | | - | |
74 | | - | |
75 | | - | |
76 | | - | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
77 | 194 | | |
78 | | - | |
79 | | - | |
80 | | - | |
81 | | - | |
82 | | - | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
83 | 271 | | |
84 | 272 | | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
85 | 332 | | |
86 | 333 | | |
87 | 334 | | |
0 commit comments