Repository navigation
Description
Part of the record-page-v2 epic #26676 (child 4 of 4). Runs after #26677 (the preference util it writes) and after #26637 (claimed by the domain:ui seat 4, no PR yet; it edits the same popover's Layout row and width).
Source (origin/main 416a76030e)
ui/packages/plugin-list/src/components/ViewSettingsPopover.tsx has a Layout row (:322-329), the Sections Group (:332), Row Color (:358) and Hide Fields (:385), a Density row (:416-419) and the Row numbers / Edit inline toggles (:443, :452), and nothing about how a row opens (grep for navigation, drawer, split: empty); the split pane used to be authored (navigation.mode: 'split'), which #26677 retires in favour of a per-user preference read (recordOpenPreference, app-shell). A row's name cell opens the record one way only (the resolved mode); there is no affordance to open it as a full page from the list.
Target
- Display menu, a new section 「打开记录」 with two segments 抽屉 | 分屏 writing
recordOpenPreference; the choice is the user's, per browser, across objects; the section's note reads 「个人偏好,不进对象声明;两种都能展开为整页」. - For an object or view whose resolved mode is
page, the section is a read-only line 「此对象声明为整页打开」 (the declaration wins; no segments). - The name cell shows a hover-only
↗(tooltip 「整页打开」) that navigates to the record route without changing the preference; keyboard-reachable. - No new metadata; no change to
navigationreading.
File surface
ui/packages/plugin-list/src/components/ViewSettingsPopover.tsx and its host wiring in app-shell (ObjectView.tsx passes the resolved mode and the preference setter), the name-cell renderer in ui/packages/plugin-grid (the link column of objectui#4490), i18n keys in ui/packages/i18n, tests beside; changesets for the touched published packages.
Acceptance
- Tests: the section renders the segments for a drawer object and the read-only line for a
pageobject; choosing 分屏 then clicking a row draws the split face; the hover ↗ navigates to the record route. - Screenshot of the Display menu on HotCRM leads and on opportunities (HotCRM lead declares
page).
Rulings (maintainer, 2026-10-27, chat in the filing session — verbatim, not re-decidable here)
- The model, quoted back by the maintainer and ordered built: 「1. 一个对象一个默认打开方式:默认抽屉(带展开整页),
page只给显式声明的对象,modal退役,split / popover 变成用户个人偏好;抽屉和整页是同一张页在两个宽度。」 - On the drawer's top bar: 「左上角的 任务 · 2 / 2 · 分屏 几个字有必要嘛」 — no object name, no mode label on the bar.
- On the position counter: 「我觉得记录详情页显示第几条记录 好像也没必要」 — no "n / N" anywhere on a record; the ↑ ↓ previous / next controls stay.
- On the right pane: 「右侧应该可以拖动吗?展开为整页那个是不是用图标就可以」 — the pane is drag-resizable and "open as full page" is an icon-only control with a tooltip.
- On related records, the maintainer confirmed the in-place model: 「所以关联记录是一层一层的往下点,然后可以一层一层的反悔吗」 — one level down per click, one level back per ← / Esc, never a second drawer.
Filed by the epic PM session session_cdb7a717-def7-4e71-9988-f089fbaa18ff on 2026-10-27, from the record-page / list-page assessment and the clickable prototype the maintainer walked and ruled on in that session (objectstack main 416a76030e, HotCRM 1f3f8880 on platform 18.2.0). The maintainer's instruction to build it, verbatim: 「你直接立卡派发处理这个开发,然后审核。」 Design evidence (never merged): branch claude/record-page-v2-evidence-20261027b at commit 345ec777220843ce1fd7f95b6cc0b2b6dcc81bf8, folder docs/design/record-page-v2-2026-10-27/ (prototype.html, artboards/, assessment.md).
Activity
objectstack-fleet commentedon Oct 27, 2026
Premise reading before dispatch · epic PM
session_cdb7a717-def7-4e71-9988-f089fbaa18ff· origin/main7bb8084590· 2026-10-27T23:58ZTarget unchanged; three Source statements are corrected and the landing path is wider than the card says:
ui/packages/plugin-list/src/components/ViewSettingsPopover.tsx(586 lines, after fix(plugin-list): fold the Display popover's layout segments to icons when they would not fit #26696): Layout row:390-396(LayoutSlot, folds to icons), Group:400, Row Color:426, Hide Fields:453, Density:484-506, Row numbers:510, Edit inline:519, footer:529; propsViewSettingsPopoverProps:64; nothing about row opening. Its only host isplugin-list/src/ListView.tsx:5967— notObjectView: the new section's props travel ObjectView → plugin-viewPluginObjectView→ListView→ the popover.ui/packages/app-shell/src/utils/recordOpenPreference.ts(feat(spec,ui)!: a record opens one way per object — navigation.mode keeps drawer (the default), page, new_window and none; modal, split and popover are retired; the split pane becomes a reader preference #26804) is READ-only (readRecordOpenPreference, storage typePick<Storage, 'getItem'>); the write function and asetItemstorage type are this card's.ObjectView.tsx:717-721reads it once per mount (useState(readRecordOpenPreference), no setter) and decidesopensBesideListat:740— the choice must take effect live, so the read becomes reactive state the Display control updates.plugin-grid/src/objectGridLinkCell.tsxLinkCell(:83-175,host.recordHref+onActivate), wired inobjectGridGenerateColumns.tsx(:397,:415,:635,:647) — notObjectGrid.tsx. No hover-only control exists in the cell; the hover-reveal pattern to copy isplugin-grid/src/components/RowActionMenu.tsx:650.list.*keys inui/packages/i18n/src/locales/en.ts(list::990) andzh.ts(:756) plus the eight other locales, andplugin-list/src/ListView.tsx:1089-1090keeps a defaults map byte-pinned to the en pack byapp-shell/src/__tests__/defaults-maps-mirror-en-pack.test.tsx.ObjectGrid.tsx/objectGridGenerateColumns.tsxwere split byf79ec28949on 2026-10-27;ViewSettingsPopover.tsxlast changed by fix(plugin-list): fold the Display popover's layout segments to icons when they would not fit #26696 (5cc31c7885).Generated by Claude Code
objectstack-fleet commentedon Oct 28, 2026
Claim: PM loop round 2 (epic #26676, child 4 of 4)
Session:
session_cdb7a717-def7-4e71-9988-f089fbaa18ffAccount:
zhuangjianguoBranch:
claude/issue-26681-display-record-open-preferenceWorktree:
objectstack-issue-26681Domain:
domain:uiSeat: epic #26676 (the epic PM session, not a domain seat)
File surface:
ui/packages/plugin-list/src/components/ViewSettingsPopover.tsx(the new 「打开记录」 section) and its host wiring in app-shell (ObjectView.tsxpasses the resolved mode and the preference setter;ListView.tsxrelay if the popover is reached through it),ui/packages/app-shell/src/utils/recordOpenPreference.ts(the WRITE side beside the read side #26804 shipped), the name-cell renderer inui/packages/plugin-grid/src/ObjectGrid.tsx(hover ↗), i18n keys inui/packages/i18n, tests beside, changesets for the touched published packages; ⛔packages/spec/**, ⛔navigation-overlay.tsx/objectViewRecordNavigation.ts(#26679) (stop on breach; explain in the report)Container & model:
M,mode:subagent(desktop session on the maintainer's macOS; verify lock in declared-unlocked mode; one of two parallel subagents, the other on components / app-shell record navigation),model: TIER_DEFAULT(dispatch-gates --tier 2026-10-27T23:59Z: no path-derived mandate),effort: highClause-②: no
Metadata-protocol: none
Responsibility: n/a — not a defect card
Thread-read: none
Premise: corrected 6456666557 (line numbers and landing path re-read on origin/main 7bb8084; target unchanged)
Serial constraints cleared: #26637 CLOSED via PR #26696 (the layout row folds to icons) — the popover constraint is gone, work on the merged popover; #26804 (this epic) MERGED at 23:43Z shipped
recordOpenPreference.ts(read side) and theObjectViewsplit face driven by it; #26679 (this epic, in parallel) edits componentsnavigation-overlay.tsx, app-shell record navigation / overlay / bridge /RecordDetailView.tsx, disjoint; no open PR touchesViewSettingsPopover.tsxorObjectGrid.tsx(scan 2026-10-27T23:59Z)Dispatched at 2026-10-27T23:59Z as an
os-devsubagent of this session; the dispatch order lives in the subagent's opening prompt.Generated by Claude Code
objectstack-fleet commentedon Oct 28, 2026
os-dev-report
{ "issue": 26681, "status": "done", "branch": "claude/issue-26681-display-record-open-preference", "pr": "https://github.com/objectstack-ai/objectstack/pull/27031", "session": "session_cdb7a717-def7-4e71-9988-f089fbaa18ff — the dispatching PM session (mode:subagent)", "premise_still_valid": true, "summary": "Display menu (ViewSettingsPopover) gains the Open records section after Layout: drawer-mode rows get 抽屉 | 分屏 segments writing the reader's preference plus the ruling's note; page-mode rows get the read-only line 此对象声明为整页打开; new_window / none and hosts without the prop draw nothing. recordOpenPreference.ts gains the write side and useRecordOpenPreference (useSyncExternalStore, same-page writes plus cross-tab storage events), which replaces ObjectView's read-once useState, so a choice takes effect on the next row click. The name cell's hover ↗ (整页打开, anchor at the host recordHref, plain click via host openRecord, stops before the row) is drawn for drawer rows only, through a new ObjectGrid/ListView host channel recordLinkAdornment beside the first authored link (else the auto-linked primary field). Two dispatch hypotheses corrected: the props travel ObjectView → renderListView → ListView → popover (plugin-view PluginObjectView is not on the path); and the grid's existing rowAdornment was tried first and dropped after a HotCRM measurement put the ↗ in the contacts avatar column (contact nameField full_name is not a column).", "tests": "On head 3d7fb4c462 from ui/: vitest run --maxWorkers=3 packages/app-shell/ packages/plugin-grid/ packages/plugin-list/ packages/i18n/ → Test Files 2307 passed | 1 skipped (2308), Tests 21776 passed | 22 skipped (21798), exit 0. New suites: ViewSettingsPopover.recordOpen-26681 (7+ cases), ObjectGrid.recordLinkAdornment-26681 (4), ObjectView.recordOpenDisplay-26681 (4: Split then row click draws the split face; no choice opens the drawer; page view read-only line and no ↗; ↗ href /apps/demo/duly_task/record/r1 beside the link, click lands on the record route, no drawer, nothing stored), recordOpenPreference.test write side (2). type-check (i18n, plugin-grid, plugin-list, app-shell, after building the three libs) exit 0, each script echoed. Reverse verification via scripts/ablation-replace.mjs with trap restore on committed state: resolvedMode 'split' as const → app-shell tsc TS2322 (Type '\"split\"' is not assignable to '\"none\" | \"drawer\" | \"page\" | \"new_window\"'), exit 2; restored blob 43072da53b2a, git diff HEAD empty. A first leg adding an extra key stayed green (exit 0, no excess-property check on a contextually typed arrow return) and is declared a no-op, not counted. No ablation: the behaviour is new, not changed. Screenshots of HotCRM 1f3f8880 (fresh DB, console from this branch's ui src) on evidence branch commit 964025cf3f3f7a2ecfd4a743d250699f2ec552ec, linked in the PR body.", "gates": "Local, derived by node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at 3d7fb4c462 (35 commands; it warned origin/main had moved 7 commits, only .github/workflows/ci.yml differing in the family source), each exit captured before any pipe: 0 | node scripts/check-adr-0087-registration.mjs --base origin/main; 0 | node scripts/check-adr-0087-registration.mjs --self-test; 0 | node scripts/check-changeset-no-major.mjs --base origin/main; 0 | node scripts/check-closing-keyword-parity.mjs; 0 | node scripts/check-closing-keyword-parity.mjs --self-test; 0 | node scripts/check-comment-mask-corpus.mjs; 0 | node scripts/check-empty-changeset.mjs --base origin/main; 0 | node scripts/check-empty-changeset.mjs --self-test; 0 | node scripts/check-sdui-manifest.mjs; 0 | node scripts/check-sdui-manifest.mjs --self-test; 0 | node scripts/pm/release-rehearsal-clone.mjs --self-test; 0 | node scripts/release-pending-publish.mjs --self-test; 1 | pnpm --dir ui exec vitest run scripts/; 0 | pnpm --filter @objectstack/spec run check:duration-unit-keys; 0 | pnpm check:changeset-gate-self-tests; 0 | pnpm check:closed-ceremony-text; 0 | pnpm check:cross-package-test-inputs; 0 | pnpm check:dispatcher-error-vocabulary; 0 | pnpm check:driver-memory-census; 0 | pnpm check:engine-double-contract; 0 | pnpm check:error-status-conformance; 0 | pnpm check:gitlink-declared; 0 | pnpm check:nul-bytes; 0 | pnpm check:pm-changeset-deadline-census; 0 | pnpm check:published-files; 0 | pnpm check:query-options-erasure; 0 | pnpm check:refd-timer-probe; 0 | pnpm check:skill-identifier-liveness; 0 | pnpm check:test-source-alias; 0 | pnpm check:tier-file-adoption; 0 | pnpm check:type-check-coverage; 0 | pnpm check:type-check-debt; 0 | pnpm check:undeclared-dep-imports; 0 | pnpm check:watch-hint-literal; 0 | pnpm check:where-matcher. The one red, pnpm --dir ui exec vitest run scripts/ (14 files / 22 tests under load average 14), rerun in isolation: 12 of 14 files green, 2 red — select-affected-tests-wiring (mapfile: command not found, exit 127, macOS bash 3; host artifact) and check-i18n-dead-keys one case timing out at 15000 ms; the gates themselves run directly: node ui/scripts/check-i18n-dead-keys.mjs exit 0 (no new key reported), node ui/scripts/check-i18n-call-site-keys.mjs exit 0. --ran reconciliation not run as a separate invocation (every derived command ran, list = the derived list). CI check-runs at 3d7fb4c462: 53 total, 43 success, 10 skipped, 0 failed — Lint & Repo Gates success, TypeScript Type Check success, Test Core success, Build Core skipped, Governed Surface Queue Guard success, Lint UI success, Type Check UI success, Test UI success (16/16 shards success), Build & E2E UI success, Check Changeset success. Every verification ran in DECLARED UNLOCKED MODE (no flock); disclosed in the PR body.", "mcp_calls": "0", "api_writes": "3 — POST /repos/objectstack-ai/objectstack/pulls (draft, #27031); POST /repos/objectstack-ai/objectstack/issues/27031/assignees (zhuangjianguo); POST /repos/objectstack-ai/objectstack/issues/26681/comments (this report, via post-stamped). Label writes: 0 (dispatch named none; a changeset exists, so no skip-changeset). git pushes (not REST): the branch (6 pushes) and one evidence branch claude/issue-26681-evidence-20261028 (orphan commit, screenshots only, never merged).", "open_questions": [], "out_of_scope_findings": [ "carrier: 承接者:无 · noted, not filed — the ↗ tooltip zh 整页打开 (console.objectView.openAsPage, per the dispatch ruling text) and the drawer's expand control console.objectView.expandToPage 以完整页面打开 are two zh spellings of one English sentence on one page; recorded in the PR Acceptance notes, the drawer's controls are #26679's.", "carrier: 承接者:无 · noted, not filed — ui/scripts/__tests__/select-affected-tests-wiring.test.ts cannot pass on macOS (shard.sh uses bash mapfile); host-local, CI runs Linux and is green." ], "line_budget": "24 files, +999 / -18 (tests 497 of the additions); no governed surface, no skills/**.", "deviations": [ "PR opened by direct REST (scripts/pm/with-fleet.sh -- gh api -X POST /pulls, then POST /issues/27031/assignees) because fleet-write/dispatch.mjs refused the local session id (exit 3, PREREQUISITE NOT MET), as the dispatch anticipated.", "One extra git push: the evidence branch for the screenshots (acceptance 2 asks for them; the bot cannot attach files).", "Mechanism hypotheses corrected: props do not pass through plugin-view PluginObjectView; the ↗ lives in app-shell objectViewOpenAsPage.tsx and reaches LinkCell through a new recordLinkAdornment host prop rather than being built inside LinkCell, so plugin-grid stays router-free and only the console decides drawer-only.", "new_window / none draw no Open records section (the ruling names no copy for them); the wide toolbar has no twin of the section.", "First reverse-verification leg was a no-op (extra key not rejected) and was replaced by a rejected-value leg." ], "files_changed": [ ".changeset/26681-display-record-open-preference.md", "ui/packages/app-shell/src/utils/recordOpenPreference.ts", "ui/packages/app-shell/src/utils/recordOpenPreference.test.ts", "ui/packages/app-shell/src/views/ObjectView.tsx", "ui/packages/app-shell/src/views/objectViewListRenderer.tsx", "ui/packages/app-shell/src/views/objectViewOpenAsPage.tsx", "ui/packages/app-shell/src/views/ObjectView.recordOpenDisplay-26681.test.tsx", "ui/packages/plugin-list/src/ListView.tsx", "ui/packages/plugin-list/src/components/ViewSettingsPopover.tsx", "ui/packages/plugin-list/src/__tests__/ViewSettingsPopover.recordOpen-26681.test.tsx", "ui/packages/plugin-grid/src/ObjectGrid.tsx", "ui/packages/plugin-grid/src/objectGridGenerateColumns.tsx", "ui/packages/plugin-grid/src/objectGridLinkCell.tsx", "ui/packages/plugin-grid/src/__tests__/ObjectGrid.recordLinkAdornment-26681.test.tsx", "ui/packages/i18n/src/locales/{en,zh,ar,de,es,fr,ja,ko,pt,ru}.ts" ] }Posted 2026-10-28T02:33Z.
Generated by Claude Code
objectstack-fleet commentedon Oct 28, 2026
Review recorded on the PR: #27031
## 席位核对— ACCEPT at head3d7fb4c462; ready + auto-merge handed to the queue on the maintainer's standing authorization for this epic (verbatim, 2026-10-28: 「授权你用车队身份给 #26758 翻 ready 并挂 auto-merge(squash),后续本 epic 的 PR 复核 ACCEPT 后同样处理」).pm:dispatchedstays until MERGED; the card closes by the PR'sFixesline. — epic PMsession_cdb7a717-def7-4e71-9988-f089fbaa18ff, 2026-10-28T02:36ZGenerated by Claude Code