Repository navigation
Commit 277275a
feat(ui): record pane v4 in the console — split by default, maximize in place, full page only from the row and the header menu (#28129)
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 #27545
Clause-②: yes (widening)
Metadata-protocol: none
## 维护者速读
- 改了什么:#27545 的 B 段(Console 接线),A 段机制层已随 #27953 合入。
- 从列表打开记录默认在列表旁分栏打开。
- 显示菜单「打开记录」多出第三项「整页」。
- 面板开着时点另一行会换掉根记录;当前行高亮,并跟随 ↑ ↓。
- ⤢ 原地最大化,同一 URL,不压历史。
- 整页只从行上的 ↗ 和记录头 ⋯ 的最后一项「在整页打开」进入。
- 步进时不空白,并预热前后两条记录;`?recordId=` 变化不再重渲染列表。
- 另外修两处缺口:分栏时记录数打印两次,以及 464px 窄列表里操作列压住状态标签 23px。
- 为什么改:维护者 10-29 裁决的 Target 1–5(画布 60 定稿),A 段只交付了机制,读者看到的默认行为在这一段。
- 风险与回滚:默认偏好由「抽屉」改为「分栏」,已选过「抽屉」的读者不受影响(偏好仍按原键读)。
- 唯一的公共类型改动是扩展型:`ListViewProps.recordOpen.preference` 只读入参多接受
`'page'`,新增可选 `selectPage`;`setPreference` 仍是 `'split' | 'drawer'`,宿主的
setter 类型不变。
- 不动 spec、不动服务端、不加元数据键;回滚即 revert 本 PR。
- 先看:
1.
`ui/packages/app-shell/src/views/objectViewRecordNavigation.ts`(行点击、换根、整页入口)
2. `ObjectView.tsx`(默认分栏、最大化、选中行)
3. `recordDrawerStack.tsx`
4. `plugin-list` 的 `ViewSettingsPopover.tsx`(第三段)
5. 新测试 `ObjectView.recordPaneV4-27545.test.tsx`
- 两分钟验证:运行下面这条命令,预期 4 个文件全过。
```
pnpm --dir ui exec vitest run
packages/app-shell/src/views/ObjectView.recordPaneV4-27545.test.tsx
packages/app-shell/src/views/RecordDetailView.paneOpenAsPage-27545.test.tsx
packages/plugin-list/src/__tests__/ListView.recordPaneCount-27545.test.tsx
packages/plugin-grid/src/__tests__/ObjectGrid.recordPaneFace-27545.test.tsx
```
- 需要你做的:无。
- `Clause-②: yes (widening)` 由席位做声明审查。
- 卡 Acceptance 3 按 #26676 裁决 B(6520722066)由卡线程跑一次对照画板的完整代理预览走查,然后席位
ACCEPT。
- 本 PR 先保持 draft。
## What changes
Part B of #27545: the console wiring. Part A, the mechanics (the split
face, the non-modal drawer over a list, maximize, the narrowed list
faces), merged as #27953.
**`@object-ui/app-shell`**
- **Split is the default (Target 1).** `DEFAULT_RECORD_OPEN_PREFERENCE`
is `'split'`. A reader who stored `drawer` keeps it: the preference
reads back under the same key. `RecordOpenPreference` gains `'page'`;
the codec reads all three and ignores anything else.
- **The list stays one element and readable.** The console hands the
list to the overlay as `mainContent` in both faces and passes
`activeListRecordId`. The open row is tinted, follows ↑ ↓ and scrolls
into view.
- **A click on another row swaps the pane's root record (Target 1).**
The click pushes `?recordId=` and the pane follows the URL while it is
open. Nested levels are dropped.
- **The full-page preference.** With `page` stored, a row click on a
drawer-mode object opens the full page.
- **A `?recordId=` change re-renders none of the list (Target 5).**
- The row-click handler, the router's `navigate` target and the list's
create / delete handlers read their latest inputs from refs. Their
identities therefore no longer change with the URL, and `renderListView`
keeps its identity.
- A render-count test pins this.
- **⤢ maximizes in place (Target 3).** The overlay is `maximizable`, and
nested levels share it. The bar no longer routes: `handleExpandDrawer`'s
hop to `/record/:id` retires.
- **The full page only from ↗ and ⋯ (Target 4).**
- The pane bar no longer draws a page button (no `onExpand`).
- The row's ↗ opens the full page and hands it the way back to this list
with the record open.
- The record header's `⋯` ends with 「Open as full page」 as its last host
item, above the destructive band, in a record pane only (`onOpenAsPage`,
the path from the root to the record shown).
- 「Collapse to drawer」 is drawn only on a page the ↗ opened.
- **Stepping never blanks (Target 5).** An embedded record keeps the
previous record on screen until the next one arrives; there is a
skeleton only on the first open. ↑ ↓ warm the related counts of both
neighbours (#27056's prefetch).
- `ObjectViewRecordOverlay.mode` is typed `NavigationOverlayMode` (the
overlay's own union), since spec's `NavigationMode` no longer has
`'split'`.
**`@object-ui/plugin-list`**
- **Display menu, Open records.**
- It draws a third segment, Full page, when the host passes the new
optional `recordOpen.selectPage`. A host that passes none still sees two
segments, but in the new order Split | Drawer (it was Drawer | Split),
because Split is now the default.
- `recordOpen.preference` also accepts `'page'`. It is an input the
section only reads, and `ListView` hands `recordOpen` straight to the
popover, so it never travels back to the host.
- `setPreference` still takes `'split' | 'drawer'`
(`ViewSettingsRecordOpenPreference` is unchanged), so a host's setter
keeps its type.
- The note says a record's ↗ always opens the full page.
- **Beside a record pane the record-count bar prints no count**
(`useRecordListFace() !== 'full'`). The console prints it under the
list, so a split face no longer shows it twice. The rest of the bar row
stays: the grid's column summaries are still drawn.
**`@object-ui/plugin-grid`.** Narrowed beside a pane, the row-actions
column keeps its header's accessible name (`sr-only`) but draws no
label. It is then only as wide as its ⋮, and the three columns fit the
464 px list beside a 720 px pane and an expanded sidebar.
**`@object-ui/i18n`.** `list.recordOpenPage` in all ten packs;
`list.recordOpenNote` reworded in all ten.
**Docs.** `content/docs/frontend/plugins/plugin-view.mdx` gains "The
Console's record pane".
### Clause-② — widening
The one public type touched is `ListViewProps.recordOpen`
(`@object-ui/plugin-list`):
- `preference: 'split' | 'drawer'` becomes `'split' | 'drawer' |
'page'`. It is a property the component only reads. Every value a host
passed before is still accepted.
- `selectPage?: () => void` is new and optional.
- `setPreference: (next: 'split' | 'drawer') => void` is unchanged. A
host implementing it for two values still type-checks, because the
component never calls it with `'page'`.
No export is removed or narrowed. `RecordOpenPreference` is in
`@object-ui/app-shell`, which is private.
## Tests
New:
- `app-shell/src/views/ObjectView.recordPaneV4-27545.test.tsx` (4):
- with no choice made, the pane opens beside the list with the open row
marked and no dialog;
- a click on another row swaps the record, and the mark moves;
- a ↓ step and a row click leave the `ListView` render count unchanged;
- ⤢ keeps the URL and the history length, hides the list, and Esc
restores.
- `app-shell/src/views/RecordDetailView.paneOpenAsPage-27545.test.tsx`
(4):
- the header `⋯` ends with 「Open as full page」 in a pane and hands the
pane the gesture, with a CONTROL for an embedded record no pane hosts;
- a step keeps the previous record on screen until the next arrives,
with a CONTROL for the record page resetting on a route change.
- `plugin-list/src/__tests__/ListView.recordPaneCount-27545.test.tsx`
(4): `full` prints the count (CONTROL); `columns` and `cards` print none
and draw no empty row; `columns` keeps the column summaries.
- `plugin-grid/src/__tests__/ObjectGrid.recordPaneFace-27545.test.tsx`
(+1, now 5): narrowed, the actions header is `sr-only`, with the `full`
CONTROL.
Updated, not deleted (Acceptance 4):
- the #26681 Display-menu pins: the console host draws three segments
and Full page calls only `selectPage`, and a host with no `selectPage`
draws two;
- the #26677 preference pins;
- `recordOpenPreference.test.ts` (default `split`, `page` decoded);
- the #26679 `recordDrawerStack` pins (↗ opens the page with the way
back, ⤢ maximizes in place);
- the #27056 prefetch pins.
Runs (from `ui/`, `NODE_OPTIONS=--max-old-space-size=4096`, on head
`b0657a05d5`):
- B's own files plus the #27594 title-switcher files: `Test Files 12
passed (12)`, `Tests 84 passed (84)`.
- `packages/plugin-list`, `packages/plugin-grid`, `packages/i18n`,
components overlay and record-list-face: `Test Files 533 passed (533)`,
`Tests 5454 passed | 13 skipped (5467)`.
- app-shell, every test file under `ObjectView*`, `RecordDetailView*`,
`recordDrawer*`, `objectView*`, `recordOpen*`, record navigator,
interface list and row click: `Test Files 185 passed (185)`, `Tests 1360
passed (1360)`.
- Type-check (`pnpm run type-check`): plugin-list, plugin-grid, i18n,
app-shell, exit 0 each.
- `eslint` on the 33 changed source and test files: 0 errors.
- i18n: `check-i18n-call-site-keys`, `check-i18n-en-drift`,
`check-i18n-dead-keys`, exit 0.
## Gates
Derived on head `b0657a05d5` (`node scripts/pm/dispatch-gates.mjs
--commands --repo objectstack-ai/objectstack`): 67 families, 1817
changed lines (+1448 / -369, 35 files) against the 3000 threshold. Each
was run and its exit code recorded before any pipe. `--ran` reconciles
**67 derived, 67 run, 0 NOT-MEASURED, 0 UNRUN**, and every one exited 0.
The record is `gates-ran-B.txt` in the seat's card folder.
Four answered exit 3 or timed out on the first pass because of the
container, not the diff. Each was re-run after the cause was fixed and
then exited 0:
- spec `check:docs` and `check:skill-examples` need a built
`@objectstack/spec`, so they were re-run after `pnpm --filter
@objectstack/spec build`.
- `check:type-check-debt` needed `pnpm install` to link the
`@objectstack/types` dependency that `main` added to `driver-odata`.
- `pnpm --dir ui exec vitest run scripts/` timed out twice in
`check-i18n-call-site-keys.test.ts` while a parallel test run held the
CPU; its re-run alone passed (`169 passed | 2 skipped (171)`).
The pre-commit and pre-push checks (`check-regen-pending`,
`check-commit-card-trailers --pre-push`) were run by hand: exit 0. Every
family CI runs is cited from the PR's check-runs at ACCEPT, not re-run
here.
## Acceptance notes
- **Screenshots (Acceptance 2).** Taken on `examples/app-crm` (the
accepted preview surface under #26676 ruling B), at 1440 × 900 with the
sidebar expanded and at 1000 × 800, under the seat's project files,
`ui-seat2/card-27545/shots/`:
- `b3-01-split-1440.png`: split by default, the list readable beside it,
the open row tinted;
- `b3-02-split-another-row-1440.png`: another row clicked, the root
swapped and the tint followed;
- `b3-03-maximized-1440.png`: maximized, sidebar kept, same URL;
- `b3-04-overlay-fallback-1000.png`: the fallback drawer at 1000.
- **Acceptance 3.** The card thread runs one full proxy preview walk
against the v4 boards on this head before the seat's ACCEPT (#26676
ruling B, 6520722066).
- **Acceptance 4.** The #26679 and #27031 / #26681 pins are updated, not
deleted.
- No metadata key. Nothing under `packages/spec/**`, no server file,
nothing under `examples/**`.
## Verification
1. Open an object list in the console (no stored preference) and click a
row. The record opens beside the list, the list keeps three columns and
the open row is tinted. Click another row and the pane swaps to it.
Press ↓ and the tint follows.
2. Press ⤢. The pane fills the content area beside the sidebar, the URL
is unchanged, and Esc restores it.
3. Open the record header's `⋯`. Its last host item is 「Open as full
page」.
4. In the Display menu, choose Full page and click a row. The full page
opens.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01Utp6CY5hd7qEmdXd6Wg8Ap)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 96367f1 commit 277275a
35 files changed
Lines changed: 1448 additions & 369 deletions
File tree
- .changeset
- content/docs/frontend/plugins
- ui/packages
- app-shell/src
- utils
- views
- __tests__
- i18n/src/locales
- plugin-grid/src
- __tests__
- plugin-list/src
- __tests__
- components
| 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
503 | 503 | | |
504 | 504 | | |
505 | 505 | | |
| 506 | + | |
| 507 | + | |
| 508 | + | |
| 509 | + | |
| 510 | + | |
| 511 | + | |
| 512 | + | |
| 513 | + | |
| 514 | + | |
| 515 | + | |
| 516 | + | |
| 517 | + | |
| 518 | + | |
| 519 | + | |
| 520 | + | |
| 521 | + | |
| 522 | + | |
| 523 | + | |
| 524 | + | |
| 525 | + | |
| 526 | + | |
| 527 | + | |
| 528 | + | |
| 529 | + | |
506 | 530 | | |
507 | 531 | | |
508 | 532 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
11 | 11 | | |
12 | 12 | | |
13 | 13 | | |
14 | | - | |
| 14 | + | |
15 | 15 | | |
| 16 | + | |
| 17 | + | |
16 | 18 | | |
17 | 19 | | |
18 | 20 | | |
| |||
28 | 30 | | |
29 | 31 | | |
30 | 32 | | |
31 | | - | |
| 33 | + | |
32 | 34 | | |
33 | 35 | | |
34 | | - | |
| 36 | + | |
35 | 37 | | |
36 | 38 | | |
| 39 | + | |
37 | 40 | | |
38 | 41 | | |
39 | | - | |
40 | | - | |
41 | | - | |
42 | | - | |
43 | | - | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
44 | 46 | | |
45 | 47 | | |
46 | | - | |
47 | | - | |
| 48 | + | |
| 49 | + | |
48 | 50 | | |
49 | | - | |
| 51 | + | |
50 | 52 | | |
51 | 53 | | |
52 | | - | |
| 54 | + | |
53 | 55 | | |
54 | 56 | | |
55 | 57 | | |
56 | | - | |
| 58 | + | |
57 | 59 | | |
58 | 60 | | |
59 | 61 | | |
| |||
74 | 76 | | |
75 | 77 | | |
76 | 78 | | |
| 79 | + | |
| 80 | + | |
77 | 81 | | |
78 | 82 | | |
79 | 83 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
24 | 24 | | |
25 | 25 | | |
26 | 26 | | |
27 | | - | |
28 | | - | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
29 | 33 | | |
30 | 34 | | |
31 | 35 | | |
| |||
44 | 48 | | |
45 | 49 | | |
46 | 50 | | |
47 | | - | |
| 51 | + | |
48 | 52 | | |
49 | 53 | | |
50 | 54 | | |
51 | | - | |
| 55 | + | |
52 | 56 | | |
53 | 57 | | |
54 | 58 | | |
55 | 59 | | |
56 | | - | |
57 | | - | |
| 60 | + | |
| 61 | + | |
58 | 62 | | |
59 | 63 | | |
60 | 64 | | |
| |||
71 | 75 | | |
72 | 76 | | |
73 | 77 | | |
74 | | - | |
| 78 | + | |
75 | 79 | | |
76 | 80 | | |
77 | 81 | | |
78 | 82 | | |
79 | 83 | | |
80 | 84 | | |
81 | | - | |
| 85 | + | |
82 | 86 | | |
83 | 87 | | |
84 | 88 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
14 | 14 | | |
15 | 15 | | |
16 | 16 | | |
17 | | - | |
18 | | - | |
19 | | - | |
20 | | - | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
21 | 24 | | |
22 | 25 | | |
23 | 26 | | |
| |||
171 | 174 | | |
172 | 175 | | |
173 | 176 | | |
| 177 | + | |
174 | 178 | | |
175 | 179 | | |
176 | 180 | | |
| |||
190 | 194 | | |
191 | 195 | | |
192 | 196 | | |
193 | | - | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
194 | 207 | | |
195 | 208 | | |
196 | 209 | | |
197 | 210 | | |
198 | 211 | | |
199 | 212 | | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
200 | 224 | | |
201 | 225 | | |
202 | 226 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
19 | 19 | | |
20 | 20 | | |
21 | 21 | | |
22 | | - | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
23 | 25 | | |
24 | 26 | | |
25 | 27 | | |
| |||
165 | 167 | | |
166 | 168 | | |
167 | 169 | | |
168 | | - | |
| 170 | + | |
| 171 | + | |
169 | 172 | | |
170 | 173 | | |
171 | 174 | | |
172 | 175 | | |
173 | 176 | | |
174 | 177 | | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
175 | 185 | | |
176 | 186 | | |
177 | 187 | | |
| |||
0 commit comments