Skip to content

console list Display menu: 「打开记录」 (drawer | split) as a per-user preference; an object or view declaring page shows the declaration instead; the name cell's hover ↗ opens the record as a full page #26681

Closed

Description

@objectstack-fleet

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

  1. 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 「个人偏好,不进对象声明;两种都能展开为整页」.
  2. For an object or view whose resolved mode is page, the section is a read-only line 「此对象声明为整页打开」 (the declaration wins; no segments).
  3. The name cell shows a hover-only ↗ (tooltip 「整页打开」) that navigates to the record route without changing the preference; keyboard-reachable.
  4. No new metadata; no change to navigation reading.

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

  1. Tests: the section renders the segments for a drawer object and the read-only line for a page object; choosing 分屏 then clicking a row draws the split face; the hover ↗ navigates to the record route.
  2. 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)

  1. The model, quoted back by the maintainer and ordered built: 「1. 一个对象一个默认打开方式:默认抽屉(带展开整页),page 只给显式声明的对象,modal 退役,split / popover 变成用户个人偏好;抽屉和整页是同一张页在两个宽度。」
  2. On the drawer's top bar: 「左上角的 任务 · 2 / 2 · 分屏 几个字有必要嘛」 — no object name, no mode label on the bar.
  3. On the position counter: 「我觉得记录详情页显示第几条记录 好像也没必要」 — no "n / N" anywhere on a record; the ↑ ↓ previous / next controls stay.
  4. On the right pane: 「右侧应该可以拖动吗?展开为整页那个是不是用图标就可以」 — the pane is drag-resizable and "open as full page" is an icon-only control with a tooltip.
  5. 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

  1. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Premise reading before dispatch · epic PM session_cdb7a717-def7-4e71-9988-f089fbaa18ff · origin/main 7bb8084590 · 2026-10-27T23:58Z

    Target 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; props ViewSettingsPopoverProps :64; nothing about row opening. Its only host is plugin-list/src/ListView.tsx:5967 — not ObjectView: the new section's props travel ObjectView → plugin-view PluginObjectView → 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 type Pick<Storage, 'getItem'>); the write function and a setItem storage type are this card's. ObjectView.tsx:717-721 reads it once per mount (useState(readRecordOpenPreference), no setter) and decides opensBesideList at :740 — the choice must take effect live, so the read becomes reactive state the Display control updates.
    • The name cell's anchor is plugin-grid/src/objectGridLinkCell.tsx LinkCell (:83-175, host.recordHref + onActivate), wired in objectGridGenerateColumns.tsx (:397, :415, :635, :647) — not ObjectGrid.tsx. No hover-only control exists in the cell; the hover-reveal pattern to copy is plugin-grid/src/components/RowActionMenu.tsx:650.
    • i18n: flat list.* keys in ui/packages/i18n/src/locales/en.ts (list: :990) and zh.ts (:756) plus the eight other locales, and plugin-list/src/ListView.tsx:1089-1090 keeps a defaults map byte-pinned to the en pack by app-shell/src/__tests__/defaults-maps-mirror-en-pack.test.tsx.
    • Churn: ObjectGrid.tsx / objectGridGenerateColumns.tsx were split by f79ec28949 on 2026-10-27; ViewSettingsPopover.tsx last 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

  2. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 2 (epic #26676, child 4 of 4)
    Session: session_cdb7a717-def7-4e71-9988-f089fbaa18ff
    Account: zhuangjianguo
    Branch: claude/issue-26681-display-record-open-preference
    Worktree: objectstack-issue-26681
    Domain: domain:ui
    Seat: 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.tsx passes the resolved mode and the preference setter; ListView.tsx relay 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 in ui/packages/plugin-grid/src/ObjectGrid.tsx (hover ↗), i18n keys in ui/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: high
    Clause-②: 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 the ObjectView split face driven by it; #26679 (this epic, in parallel) edits components navigation-overlay.tsx, app-shell record navigation / overlay / bridge / RecordDetailView.tsx, disjoint; no open PR touches ViewSettingsPopover.tsx or ObjectGrid.tsx (scan 2026-10-27T23:59Z)

    Dispatched at 2026-10-27T23:59Z as an os-dev subagent of this session; the dispatch order lives in the subagent's opening prompt.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    ContributorAuthor

    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

  4. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    ContributorAuthor

    Review recorded on the PR: #27031 ## 席位核对 — ACCEPT at head 3d7fb4c462; 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:dispatched stays until MERGED; the card closes by the PR's Fixes line. — epic PM session_cdb7a717-def7-4e71-9988-f089fbaa18ff, 2026-10-28T02:36Z


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:recordsBusiness objects, records, the views that show data, usable forms, searchdomain:uienhancementNew feature or requestpm:epicpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions