Repository navigation
feat(ui): record pane v4 mechanics — split beside the list, non-modal drawer over a list, maximize in place, narrowed list faces - #27953
Conversation
… drawer over it, maximize in place, narrowed list faces Part A of objectstack#27545 (Refs, not Fixes): the components, grid and i18n mechanics the console wires in the following change, plus the console's 720 px default pane width. - NavigationOverlay: the split face beside the host's list, the non-modal drawer over a list, the 360 px–60vw drag range, the `maximizable` ⤢/⤡ in place, `activeListRecordId`, ↗ for `onExpand`. - record-list-face: the narrowed list faces and the open-row subscription; DataTable marks and scrolls the open row. - ObjectGrid: three columns beside the pane, card rows below 440 px. - i18n: detail.maximize / detail.restoreSize in all ten packs. - app-shell: the pane defaults to md (720 px) whatever the field count. Claude-Session: https://claude.ai/code/session_01Utp6CY5hd7qEmdXd6Wg8Ap Co-authored-by: Claude <noreply@anthropic.com>
|
domain:ui#2 · os-bill · session_01Utp6CY2n1JmZZKrHgtp9jC Contract reviewServed-tier: Face: the ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
|
domain:ui#2 · os-bill · session_01Utp6CY2n1JmZZKrHgtp9jC Preview review (proxy for epic:#26676#1) · afeb540 · PASS Acceptance 3 for A, run under the #26676 ruling B (6520722066). A fresh review subagent with no development context had three inputs: #27545's Acceptance, the v4 boards ( Reason: every reader-visible claim A makes is delivered, and the default path is unchanged. The deviations from the boards it found are either declared in this PR and owned by B, or sit in code A does not touch.
Deviations from RecordDrawerV4Peek inside A's scope (minor, both already declared in the PR body as B's):
B scope, not counted:
Other notes (not A's):
Screenshots (project files,
Per the ruling, B gets one full walk against the boards before its ACCEPT. Generated by Claude Code |
|
domain:ui#2 · os-bill · session_01Utp6CY2n1JmZZKrHgtp9jC 席位核对ACCEPT · head
Generated by Claude Code |
domain:ui#2 · os-bill · session_01Utp6CY2n1JmZZKrHgtp9jC
Requested by Bill · project thread
Refs #27545
Clause-②: yes (widening)
Metadata-protocol: none
维护者速读
NavigationOverlay新增列表旁的分栏面(非模态、列表不重挂)、盖在列表上的非模态抽屉、⤢ 原地最大化(宽度 = 视口减实时侧栏宽)、拖拽 360px 到 60vw;列表的两种窄面(≥440px 保留前三列,更窄改卡片行)和当前行订阅;ObjectGrid画这两种窄面;i18n 加两个键;Console 记录抽屉默认宽度统一 720px。Fixes #27545。ui/packages/components/src/custom/navigation-overlay.tsx→record-list-face.tsx→ui/packages/plugin-grid/src/ObjectGrid.tsx的窄面 → 新测试navigation-overlay-split-27545.test.tsx。pnpm --dir ui exec vitest run packages/components/src/__tests__/navigation-overlay-split-27545.test.tsx packages/components/src/__tests__/record-list-face-27545.test.tsx packages/plugin-grid/src/__tests__/ObjectGrid.recordPaneFace-27545.test.tsx,预期 3 个文件 25 条全过。Clause-②: yes (widening)由座位做声明审查;epic 座位在预览里验收后座位才 ACCEPT,本 PR 保持 draft。What changes
Part A of #27545, the mechanics. The console wires them in part B; until then it passes none of the new props, and its only change of its own is the pane's default width.
@object-ui/components—NavigationOverlaymainContent), at that list's full height, as a complementary landmark, not a dialog. The list is one element in every state (closed, open, another record, closed again), so the overlay never remounts it. Below a 1000 px content area (RECORD_SPLIT_MIN_CONTENT_PX, measured on the host, not the window) the split face falls back to the drawer.mainContentlist the drawer is non-modal and draws no dimming overlay: an outside click leaves it open, and the list behind it stays clickable; ✕ and Esc close it. A drawer with no list (plugin views, the record page's related drawer) is the modal sheet it was.min(Npx, 60vw), so a remembered 1300 clamps). An authored width is no longer floored atmin(60vw, 880px).maximizableprop (Target 3). It draws ⤢ / ⤡ and maximizes the pane in place: the pane's width becomes the viewport less the live sidebar width, read from the sidebar's own state on every render, never cached (--sidebar-width, or--sidebar-width-iconwhen collapsed;SIDEBAR_WIDTH_FALLBACK/SIDEBAR_WIDTH_ICON_FALLBACKwhere the variable does not reach a portalled drawer). The list stays mounted and inert under it. Esc restores and only the next Esc closes; ✕ closes the path; a pane closed while maximized opens at its own width next time.activeListRecordIdprop. A list under the pane reads its narrowed face (useRecordListFace():columnsat 440 px and wider,cardsbelow,fullwith no pane beside it) and which row is the open record (ActiveListRow,useIsActiveListRecord,useScrollActiveListRecordIntoView,ActiveListRecordProvider,RecordListFaceProvider). The open row is a per-rowuseSyncExternalStoresubscription: a change re-renders the row it leaves and the row it reaches, never the list around them.onExpandis drawn as ↗ (the full page), so ⤢ is free for maximize.DataTablemarks the open record's row (data-active-record,aria-current) and scrolls it into view.@object-ui/plugin-grid—ObjectGrid. Narrowed tocolumns, it keeps its first three view columns that are not avatars (by the column's type or the object field's), plus its row actions. Narrowed tocards, it draws one card row per record, the name and two chips, that opens the record like a table row. Under no pane nothing changes.@object-ui/i18n.detail.maximizeanddetail.restoreSizein all ten packs (en Maximize / Restore size, zh 最大化 / 还原).@object-ui/app-shell. The console's record pane defaults tomd(720 px, at most 92vw) whatever the object's field count:size: 'auto'and no size both read asmd; an authoredsizeorwidthstands. This applies to the object page's drawer and to the record page's related-record drawer. It is in A because removing the 880 floor would otherwise leave a narrow object's drawer at 480 px.What a console reader sees from A alone
min(60vw, 880px): 864 px at a 1440 px viewport for an object of up to eight fields.activeListRecordIduntil B.Nothing else a reader sees moves: the default preference, the row click, the ⤢ / full-page route hop and the record header menu are untouched until B.
Split plan (approved by seat 2)
The whole card measured 3421 changed lines, over the 3000-line human-merge threshold. Seat 2 approved a serial split along the Target items: this PR (A,
Refs #27545) first; when it merges, the branch is reset to the newmainand B opens withFixes #27545. B is the console wiring: split as the default preference and the Display menu's third option 「整页」, the row click swapping the root, the open row tinted and followed by ↑ / ↓, ⤢ in the record top bar, the full page only from ↗ and the header ⋯ (「在整页打开」, withhandleExpandDrawer's route hop retired and 「收为抽屉」 only for ↗ arrivals), stepping that keeps the previous record and prefetches both neighbours, the?recordId=render-count test, the #26679 / #27031 pin updates, and the docs. B's commits are kept on a local branch and in a patch under the seat's project files. Each PR leavesmaincoherent.Tests
New:
components/src/__tests__/navigation-overlay-split-27545.test.tsx(16): the pane beside the list with no dialog and the list clickable; one list element from closed to open to another record to closed; the narrowed faces; the fallback below 1000 px and the 1000 px control; the drawer over a list draws no overlay and survives an outside click, with the modal-sheet CONTROL for a drawer with no list; ⤢ / ⤡, the list inert while maximized, Esc restores before it closes, ✕ closes while maximized, maximize not remembered, the sidebar read live expanded and collapsed, the portal fallbacks equal the sidebar provider's widths; the drag range and the remembered-1300 clamp.components/src/__tests__/record-list-face-27545.test.tsx(5): the open row follows the record and re-renders only the two rows that change (render counts, list count unchanged), scrolls into view, no provider means no open row, row ids, the 440 px face threshold;DataTabletints and names the open row and moves with it.plugin-grid/src/__tests__/ObjectGrid.recordPaneFace-27545.test.tsx(4):columnskeeps the first three non-avatar columns in view order;fullCONTROL draws them all;cardsdraws name + two chips and opens the record; the open row is marked in both faces.Updated, not deleted (width now drawn as given, the dragged range
min(Npx, 60vw)):navigation-overlay-9299,gridNavigationMembers-8071, the kanban / calendar / ganttnavWidthDefaultpins, and the kanban / calendar / timelineNavigationMemberspins.Runs (from
ui/,NODE_OPTIONS=--max-old-space-size=4096):ObjectView.record*,recordDrawerStack*, record navigator, interface list hydration, row-click payload, defaults-map mirror, plugin-kanban / gantt / calendar / timeline / tree, plugin-view split popover and record surface, react hooks, i18n):Test Files 504 passed | 2 skipped (506),Tests 4635 passed | 266 skipped (4901).packages/plugin-grid(whole package):Test Files 262 passed (262),Tests 2297 passed (2297)on the head after mergingmain(withapp-shellObjectView.headerSelectionGate-27831, the onemaintest that reads the mergedObjectGridchange); before the merge261 / 2290.Test Files 3 passed (3),Tests 25 passed (25).pnpm run type-check, both programs): components, plugin-grid, i18n, plugin-kanban, plugin-calendar, plugin-gantt, plugin-timeline, app-shell: exit 0 each.eslinton the 26 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
afeb540a12(node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack): 35 families, 1751 changed lines (+1395 / -356, 29 files) against the 3000 threshold. Each was run and its exit code recorded before any pipe;--ranreconciles 35 derived, 35 run, 0 NOT-MEASURED, 0 UNRUN. Every one exited 0:check-adr-0087-registration(base + self-test),check-changeset-no-major,check-closing-keyword-parity(+ self-test),check-comment-mask-corpus,check-empty-changeset(base + self-test),check-sdui-manifest(+ self-test),release-rehearsal-clone --self-test,release-pending-publish --self-test,pnpm --dir ui exec vitest run scripts/, speccheck:duration-unit-keys, andcheck:changeset-gate-self-tests, closed-ceremony-text, cross-package-test-inputs, dispatcher-error-vocabulary, driver-memory-census, engine-double-contract, error-status-conformance, gitlink-declared, nul-bytes, pm-changeset-deadline-census, published-files, query-options-erasure, refd-timer-probe, skill-identifier-liveness, test-source-alias, tier-file-adoption, type-check-coverage, type-check-debt, undeclared-dep-imports, watch-hint-literal, where-matcher. The record isgates-ran-A.txtin the seat's card folder. The pre-commit and pre-push checks (check-regen-pending,check-commit-card-trailers --pre-push) were run by hand: exit 0.Not run locally: the root
check-i18n-bundles/check-i18n-coverage/check-i18n-walk-parity(they read server metadata bundles, not the ui packs; they answer exit 3 without the server closure'sdist, and they are not in the derived list). Every family CI runs is cited from the PR's check-runs at ACCEPT, not re-run here.Acceptance notes
examples/app-crm, not HotCRM (outside this session's repository scope). Saved under the seat's project files,ui-seat2/card-27545/shots/, at 1440 x 900 and 1000 x 800 on thecrm_leadlist (five seeded leads, the admin account, sidebar expanded at 1440, auto-collapsed to its icon rail at 1000):a-01-drawer-default-1440.png(the default drawer, 720 px, ↗ in its bar),a-02-split-preference-1440.png(a reader on Split: the side panel, the list at three columns),a-03-split-preference-fallback-1000.png(the same reader below a 1000 px content area: a drawer with no dimming, the list visible and clickable behind it).b-01-split-1440.png(split by default, the open row tinted),b-02-split-another-row-1440.png(another row clicked: the root swapped, the tint followed, the URL'srecordIdchanged),b-03-maximized-1440.png(⤢: the pane 1184 px = 1440 less the 256 px sidebar, the sidebar kept, the same URL, ⤡ in the bar),b-04-overlay-fallback-1000.png(the fallback drawer at 1000, the open row still tinted behind it).record-count-footerunder the list andListView's count bar; this is already so onmain's Split preference); and at the default 720 px pane beside an expanded sidebar the narrowed list is 464 px against 487 px of columns, so the sticky Actions column covers the end of the Status chip (a 23 px scroll).packages/spec/**, no server file, nothing underexamples/**.Verification
Generated by Claude Code