Repository navigation
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
Conversation
…in place, full page only from the row and the header menu Part B of objectstack#27545; part A (the mechanics) is on main. - app-shell: the record opens beside the list by default; the list is one element and keeps its scroll; a row click swaps the root; the open row is tinted and followed; ?recordId= changes re-render none of the list; the bar's maximize stays in place; the full page opens from the row's arrow and the header menu's last item; stepping keeps the previous record and warms both neighbours. - plugin-list: the Display menu's Full page segment through an optional selectPage (the setter keeps its two values); beside a pane the count bar prints no count. - plugin-grid: beside a pane the row-actions header draws no label. - i18n: list.recordOpenPage, list.recordOpenNote reworded. Claude-Session: https://claude.ai/code/session_01Utp6CY5hd7qEmdXd6Wg8Ap Co-authored-by: Claude <noreply@anthropic.com>
|
domain:ui#2 · os-bill · session_01Utp6CY2n1JmZZKrHgtp9jC Contract-review-claim: session_01Utp6CY2n1JmZZKrHgtp9jC · head b0657a0 · 2026-10-30T13:27Z Declaration review ( Generated by Claude Code |
|
domain:ui#2 · os-bill · session_01Utp6CY2n1JmZZKrHgtp9jC Contract reviewServed-tier: Face: the ① Derived judgments
② Semver level
③ Boundary flags
Findings
Implemented-by: VERDICT: PASS Generated by Claude Code |
…er, it was Drawer | Split The changeset and the CONTROL pin said a host with no selectPage sees the two segments "as before"; they are in a new order since Split is the default. Wording only, for the declaration review on objectstack#27545 B. Claude-Session: https://claude.ai/code/session_01Utp6CY5hd7qEmdXd6Wg8Ap Co-authored-by: Claude <noreply@anthropic.com>
|
domain:ui#2 · os-bill · session_01Utp6CY2n1JmZZKrHgtp9jC Preview review (proxy for epic:#26676#1) · df97cfe · PASS This is Acceptance 3 for #27545, as the full walk of the end state against the v4 boards, under #26676 ruling B (6520722066). Who reviewed. A new review subagent with no development context. Its inputs were:
What it walked. The live console built from
The current head Reason. Every Target item 1–5 is delivered in the running console. The reviewer measured each one rather than inferring it. There were no page errors and no defect introduced here. The differences from the boards that remain are either #27524's or cosmetic notes on the narrowed list.
Target 1 detail
Target 2 detail
Target 3 detail
Target 4 detail
Target 5 detail
Deviations from the boards inside this card's scope (notes, none REWORK):
#27524 scope, not counted.
Other.
Screenshots (project files,
The boards rendered at 1440 for comparison are in Generated by Claude Code |
|
domain:ui#2 · os-bill · session_01Utp6CY2n1JmZZKrHgtp9jC Contract-review-claim: session_01Utp6CY2n1JmZZKrHgtp9jC · head df97cfe · 2026-10-30T13:50Z Delta review of Generated by Claude Code |
|
domain:ui#2 · os-bill · session_01Utp6CY2n1JmZZKrHgtp9jC Contract reviewServed-tier: Delta review after record 6524125710 (PASS on ① Derived judgments
② Semver levelUnchanged: ③ Boundary flagsNone new. Finding 2 of 6524125710 ( Findingsmust-fix 0; should-fix 0. Implemented-by: VERDICT: PASS Generated by Claude Code |
|
domain:ui#2 · os-bill · session_01Utp6CY2n1JmZZKrHgtp9jC 席位核对ACCEPT · head
Generated by Claude Code |
Record pane v4 (#28129) hands the pane BOTH neighbours as an array, so the payload prefetch read a single upcoming record that no longer exists and asked none. Ask each neighbour's page once this record's has answered, and pin an in-place step to either one opening from that answer. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WGufzhCjfxaYJQyy6tFjVr
domain:ui#2 · os-bill · session_01Utp6CY2n1JmZZKrHgtp9jC
Requested by Bill · project thread
Fixes #27545
Clause-②: yes (widening)
Metadata-protocol: none
维护者速读
?recordId=变化不再重渲染列表。ListViewProps.recordOpen.preference只读入参多接受'page',新增可选selectPage;setPreference仍是'split' | 'drawer',宿主的 setter 类型不变。ui/packages/app-shell/src/views/objectViewRecordNavigation.ts(行点击、换根、整页入口)ObjectView.tsx(默认分栏、最大化、选中行)recordDrawerStack.tsxplugin-list的ViewSettingsPopover.tsx(第三段)ObjectView.recordPaneV4-27545.test.tsxClause-②: yes (widening)由席位做声明审查。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-shellDEFAULT_RECORD_OPEN_PREFERENCEis'split'. A reader who storeddrawerkeeps it: the preference reads back under the same key.RecordOpenPreferencegains'page'; the codec reads all three and ignores anything else.mainContentin both faces and passesactiveListRecordId. The open row is tinted, follows ↑ ↓ and scrolls into view.?recordId=and the pane follows the URL while it is open. Nested levels are dropped.pagestored, a row click on a drawer-mode object opens the full page.?recordId=change re-renders none of the list (Target 5).navigatetarget and the list's create / delete handlers read their latest inputs from refs. Their identities therefore no longer change with the URL, andrenderListViewkeeps its identity.maximizable, and nested levels share it. The bar no longer routes:handleExpandDrawer's hop to/record/:idretires.onExpand).⋯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).ObjectViewRecordOverlay.modeis typedNavigationOverlayMode(the overlay's own union), since spec'sNavigationModeno longer has'split'.@object-ui/plugin-listrecordOpen.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.preferencealso accepts'page'. It is an input the section only reads, andListViewhandsrecordOpenstraight to the popover, so it never travels back to the host.setPreferencestill takes'split' | 'drawer'(ViewSettingsRecordOpenPreferenceis unchanged), so a host's setter keeps its type.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.recordOpenPagein all ten packs;list.recordOpenNotereworded in all ten.Docs.
content/docs/frontend/plugins/plugin-view.mdxgains "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?: () => voidis new and optional.setPreference: (next: 'split' | 'drawer') => voidis 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.
RecordOpenPreferenceis in@object-ui/app-shell, which is private.Tests
New:
app-shell/src/views/ObjectView.recordPaneV4-27545.test.tsx(4):ListViewrender count unchanged;app-shell/src/views/RecordDetailView.paneOpenAsPage-27545.test.tsx(4):⋯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;plugin-list/src/__tests__/ListView.recordPaneCount-27545.test.tsx(4):fullprints the count (CONTROL);columnsandcardsprint none and draw no empty row;columnskeeps the column summaries.plugin-grid/src/__tests__/ObjectGrid.recordPaneFace-27545.test.tsx(+1, now 5): narrowed, the actions header issr-only, with thefullCONTROL.Updated, not deleted (Acceptance 4):
pageshows the declaration instead; the name cell's hover ↗ opens the record as a full page #26681 Display-menu pins: the console host draws three segments and Full page calls onlyselectPage, and a host with noselectPagedraws two;navigation.modekeepspage | drawer | new_window | none—modal,splitandpopoverare retired; the default becomesdrawerin the schema and in the console (no field-count guess); list-pagerecordActiondropsmodal; the console's split pane becomes a per-user preference read #26677 preference pins;recordOpenPreference.test.ts(defaultsplit,pagedecoded);recordDrawerStackpins (↗ opens the page with the way back, ⤢ maximizes in place);Runs (from
ui/,NODE_OPTIONS=--max-old-space-size=4096, on headb0657a05d5):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).ObjectView*,RecordDetailView*,recordDrawer*,objectView*,recordOpen*, record navigator, interface list and row click:Test Files 185 passed (185),Tests 1360 passed (1360).pnpm run type-check): plugin-list, plugin-grid, i18n, app-shell, exit 0 each.eslinton the 33 changed source and test files: 0 errors.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.--ranreconciles 67 derived, 67 run, 0 NOT-MEASURED, 0 UNRUN, and every one exited 0. The record isgates-ran-B.txtin 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:
check:docsandcheck:skill-examplesneed a built@objectstack/spec, so they were re-run afterpnpm --filter @objectstack/spec build.check:type-check-debtneededpnpm installto link the@objectstack/typesdependency thatmainadded todriver-odata.pnpm --dir ui exec vitest run scripts/timed out twice incheck-i18n-call-site-keys.test.tswhile 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
examples/app-crm(the accepted preview surface under epic: record page v2 — one default open mode per object (drawer by default, page by declaration; modal / split / popover retired as metadata); the drawer and the page are one page at two widths; related records open in place #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.pageshows the declaration instead; the name cell's hover ↗ opens the record as a full page #26681 pins are updated, not deleted.packages/spec/**, no server file, nothing underexamples/**.Verification
⋯. Its last host item is 「Open as full page」.Generated by Claude Code