Repository navigation
Console records: a hover card on every record reference (compact related rows, relation columns, lookup chips) shows the target's declared highlightFields without navigating #27604
Description
epic:#26676#1 · zhuangjianguo · session_cdb7a717def74e719988f089fbaa18ff
Filed from the record-page epic #26676 as a sub-issue (the maintainer, 2026-10-29, looking at Twenty's inline relation expand: 「他的相关表字段点击下拉箭头可以展开」; the seat recommended a stateless hover card over an inline accordion and the maintainer answered 「同意」). It adds one face to a referenced record — a glance — without a drill-down; it is not part of the v4 anatomy (#27524) and flies on its own.
Source (main 273137d227)
- A referenced record has three faces today: the compact related row (name + one chip, Record page v4: same-shaped related panels with no collapse, a persistent tab row under the header (the enlarged state is the active tab), Activity as the first panel and a tab, one top bar on every face, tiers by container width #27524 item 11), the row in the enlarged tab's table, and the drill-down record one level down (console record drawer v2: the bar is ↑ ↓ · ⤢ · ✕ (no object name, no counter); a related row or a lookup opens its record in place with a trail, ← / Esc go back one level; ⤢ carries the trail into the page; on the record page a related row opens a drawer over the page #26679). Looking at a referenced record's key fields means drilling down and coming back.
- Twenty expands a relation chip inline into a field list (stateful; it stretches the card and pushes the panels below). Linear, GitHub and Attio show a hover card on a reference instead.
- The record header's highlights and the list's highlights both read the object's declared
highlightFields(max 6, the name field excluded).
Target
- One hover card for every record reference: hovering the name in a compact related row, a relation cell in a list / the enlarged tab's table, or a lookup chip in the details column shows, after ~400 ms, a card with the target's icon + name (a link that opens it the way a click would) and its declared
highlightFieldsminus the name (max 6), in the target object's own field renderers (chips, currency, dates). Keyboard: focusing the reference and pressing Space shows the same card; Esc hides it. - Stateless: no expand state, no stretching of the host; the card floats (popover), dismisses on mouse-out / blur; one card at a time.
- Data: one request per shown card, cached per record for the page's life; never prefetched for every row (a list of 200 rows must not fire 200 requests).
- Scope: console only, every object; the card's content comes from metadata (
highlightFields), nothing authored per object; ⛔packages/spec/**, no new key. Objects whosehighlightFieldsresolve to nothing show the name and the object label only.
File surface
ui/packages/plugin-detail/src/RelatedCardStack.tsx / relatedListCompact.ts (the row's reference), the lookup / relation field renderers in ui/packages/components/src/** (one shared RecordHoverCard component beside the existing lookup popover), the list / grid relation cells in ui/packages/plugin-list / plugin-grid, i18n (the object label fallback), tests beside, changeset.
Acceptance
- Tests: the card shows the declared highlights minus the name for a lead, a contact (no status field — nothing invented) and a product; a reference with no highlights shows name + object label; one request per card, none on row render; keyboard Space / Esc; the card never changes the host's layout.
- Screenshots on HotCRM: the card over a related row in the split pane, over a relation cell in the 任务 tab's table, and over a lookup chip in the details column, on a never-merged evidence branch.
- The epic seat verifies in the console preview before ACCEPT (maintainer: 「车队开发完后你审核效果」).
Activity
objectstack-fleet commentedon Oct 30, 2026
domain:ui#4 · os-bill · session_01Utp6CbDAucNyQ4UtzCJKyh
Claim: PM loop round 1
Session:
session_01Utp6CbDAucNyQ4UtzCJKyhAccount:
os-billBranch:
claude/project-thread-xisvxo(the card thread's designated push branch, the recorded platform exception toclaude/issue-<n>-<slug>)Worktree:
objectstack-issue-27604Domain:
domain:uiSeat:
domain:ui#4File surface: a shared
RecordHoverCardinui/packages/fields/src/besideReferencedRecordLink(amended 2026-10-30T01:57Z:fieldsdepends oncomponents, and the card draws highlightFields through the target's own cell renderers, so it cannot live incomponentswithout a cycle; ⛔components/src/ui/**still untouched), exported from@object-ui/fieldsforplugin-detail's compact related row (RelatedList.tsxcompact face ~L2890); the lookup/relation cells wired once throughLookupCellRenderer→ReferencedRecordLink;@object-ui/i18npacks for the object-label fallback if needed; tests beside; root.changeset/; ⛔packages/spec/**; ⛔plugin-detailRelatedList.tsx~L811 docblock (in-flight PR #27793) (stop on breach; explain in the report)Container & model:
M,mode:thread,model: TIER_DEFAULT(dispatch-gates --tieronfec60e00: no path-derived mandate),effort: xhighClause-②: yes (widening)
Metadata-protocol: none
Responsibility: a stateless hover card on every record reference shows the target name + declared highlightFields (max 6, name excluded) in its own renderers, one request per shown card cached per record, keyboard Space/Esc, host layout unchanged; tests + screenshots on a never-merged evidence branch; the epic seat re-measures in the console preview before ACCEPT
Thread-read: none
Premise: holds
Serial constraints cleared: maintainer 2026-10-30 01:47Z/01:49Z dispatch-now; in-flight #27626 (seat 4) adds a data-layer cache — keep this card's cache local
Dispatched 2026-10-30T01:52Z to a card thread of the seat's project; the order lives in the project. ⛔ The thread posts no claim and writes no assignee.
Generated by Claude Code
objectstack-fleet commentedon Oct 30, 2026
domain:ui#4 · os-bill · session_01Utp6Ca4Vk1969xZxELoSFG
os-dev-report
{ "issue": 27604, "status": "done", "branch": "claude/project-thread-xisvxo", "pr": "https://github.com/objectstack-ai/objectstack/pull/27912", "head": "090db133549c4ab7b5dbb8127a9e76a1d7255e11", "session": "session_01Utp6Ca4Vk1969xZxELoSFG (card thread, mode:thread; seat session_01Utp6CbDAucNyQ4UtzCJKyh)", "premise_still_valid": true, "summary": "Premise true on main: a record reference only linked to its target, with no preview. One card for every record reference: new RecordHoverCard in @object-ui/fields, wired into ReferencedRecordLink (the anchor LookupCellRenderer draws, so a list relation column, a related list's table and enlarged view, and a details-column lookup chip) and into the name of a compact related row (RelatedList compact face). After 400 ms hover or Space on the focused reference it shows the target's icon, its name (opens as a click on the reference does) and the declared highlightFields minus the name field and minus fields the viewer may not read, capped at 6, drawn with getCellRenderer; Esc / pointer-out / blur hide it; one card at a time; portalled to document.body so the host layout is unchanged. No highlightFields declared: name plus object label. Read seam: nothing on render; one dataSource.findOne per shown card (with $expand only for readable reference highlights), kept in a module-local Map in RecordHoverCard.tsx (at most 200, dropped on subscribeDataChanges). It calls findOne on whatever DataSource the host hands it, so #27626's data-objectstack in-flight dedupe sits underneath with no second layer here. No spec change, no new metadata key; new public export RecordHoverCard (Clause-2 yes, widening).", "files_changed": [ ".changeset/27604-record-hover-card.md", "ui/packages/fields/src/RecordHoverCard.tsx", "ui/packages/fields/src/__tests__/RecordHoverCard-27604.test.tsx", "ui/packages/fields/src/__tests__/RecordHoverCard.fieldAccess-27604.test.tsx", "ui/packages/fields/src/index.tsx", "ui/packages/plugin-detail/src/RelatedList.tsx", "ui/packages/plugin-detail/src/__tests__/RelatedList.compactHoverCard-27604.test.tsx" ], "tests": "New 3 files, 15 tests (lead, contact with no status field, product, no-highlights object, unreadable record, unreadable highlight field, 200 rows with 0 reads then 1 per shown card and a cache hit, hover delay, focus vs Space/Esc, one card at a time, host markup unchanged, title opens through the host; compact row hover, Space/Esc, title). On e57402aa: the 3 new files plus 34 existing files that render LookupCellRenderer / recordHref / RelatedListCompactContext in fields, plugin-detail, plugin-grid, plugin-list: Test Files 37 passed (37), Tests 283 passed (283), VERDICT command-exit 0; type-check fields + plugin-detail exit 0; eslint on the 6 changed ui files 0 errors. Ablation (scripts/ablation-replace.mjs, anchor 'name !== nameField &&' 1 -> 0, restored, git diff HEAD empty): 1 failed | 10 passed. On merged head 49504eb8: plugin-detail closure rebuilt, both type-checks, same 37 files: 37 passed, 283 passed, VERDICT command-exit 0. On 090db133 (main merged again, 6 commits outside ui/): 3 new files, 15 passed, VERDICT command-exit 0. Browser (Chromium, examples/app-showcase, 49504eb8): 0 by-id reads after the Projects list rendered, 1 read after one hover on an Account cell; cards seen on a list relation cell, a details lookup chip and a compact related row; Space showed one card without navigating, Esc left none. Evidence branch claude/issue-27604-evidence @ a8b2da2d, folder evidence/27604/ (3 PNG, 2 Playwright scripts, README).", "gates": "dispatch-gates --commands on e57402aa and again on 49504eb8: 35 derived, each recorded as `<command> :: exit <code>` (all exit 0, check-sdui-manifest and its --self-test included) plus check:changeset-fixed exit 0; --ran reconciliation both times: 35 derived, 35 run, 0 NOT-MEASURED, 0 UNRUN. check:new-line-citations 0 new. 090db133 adds only files outside ui/. CI families: see PR check-runs.", "line_budget": "n/a — no skills/** or ledgered file touched (net +1147 / -35 over 7 files, 518 of them the new component)", "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm", "deviations": [ "Component placed in @object-ui/fields, not under components/src/ui/**: fields depends on components, and the card draws target values with getCellRenderer from fields. Agreed with the seat, which amended the Claim's file surface.", "Screenshots taken on examples/app-showcase (showcase_account declares highlightFields), not HotCRM: this container reaches only this repository.", "PR body line 2 links this card thread (cmsg_016iMXWSZDzqWRWvWzdNo8wgDSnrkE4tAZzd2PBTPkxkA8), not the seat thread named in the order: the session's required attribution block names this thread and says to keep its link exactly. Line 1 is as ordered.", "Harness post-PR assignee/reviewer step skipped per the maintainer's routing rule; relay read-back shows no assignees.", "Two GitHub MCP reads (mcp__github__issue_read on 27604) made before reading this order, which forbids MCP; the card and comments were then read through REST. No MCP write.", "Commit trailers are the model-free pair from AGENTS.md (Claude-Session + Co-authored-by: Claude), not the harness's model-named co-author line." ], "mcp_calls": "4 — mcp__github__issue_read x2 (reads, see deviations); mcp__claude-code-remote__get_session x1 (session id lookup); mcp__claude-code-remote__subscribe_pr_activity x1 (PR 27912 event subscription, not a GitHub write)", "api_writes": "2 — relay pr_create (repository_dispatch -> POST /repos/objectstack-ai/objectstack/pulls, draft, read-back identical 10198 bytes, assignees none); this os-dev-report comment via post-stamped (POST /repos/objectstack-ai/objectstack/issues/27604/comments). git pushes (designated branch, evidence branch) not counted.", "open_questions": [], "out_of_scope_findings": [] }Report taken 2026-10-30T03:28Z.
objectstack-fleet commentedon Oct 30, 2026
domain:ui#4 · os-bill · session_01Utp6CbDAucNyQ4UtzCJKyh
This card is claimed by ui seat 4 and is not waiting in the queue. Its PR is #27912: green, NOT governed and
mergeable_stateclean. The PR had stayed draft only for the epic seat's preview check. The maintainer ruled at 10:39Z that the seat accepts and merges on its own. ACCEPT → PR #27912 comment 6520829118 · 2026-10-30T10:42Z. The PR is now marked ready with automerge on, and the card closes when it merges.Generated by Claude Code