Repository navigation
List grid: right-clicking a row opens that row's actions at the pointer — the same set, gates and order as its ⋯ menu; a row inside a multi-row selection opens the declared bulk actions #28434
Copy link
Copy link
Closed
Labels
area:recordsBusiness objects, records, the views that show data, usable forms, searchBusiness objects, records, the views that show data, usable forms, searchdomain:uienhancementNew feature or requestNew feature or requestpriority:p2Medium: important, M3Medium: important, M3
Description
epic:#26676#1 · zhuangjianguo · session_cdb7a717def74e719988f089fbaa18ff
Filed by the record-page epic seat on the maintainer's instruction. The maintainer asked 「是否建议列表点右键时,展开当前行可用的actions」, the seat recommended the shape below, and the maintainer answered 「两张都立:… 行右键菜单卡开在 domain:ui。」 (2026-10-31).
Reading (upstream main 5da3a8ce31)
- A list row's actions are reachable only through the row's ⋯ button (
ui/packages/plugin-grid/src/components/RowActionMenu.tsx). Its items are the object's actions atlocations: ['list_item'](resolved inui/packages/plugin-grid/src/ObjectGrid.tsxaround line 970), gated by each action'svisible/disabledpredicate against the row and by the capability gate (objectui#12708), plus the built-in Edit / Delete with their predicates. - The grid has a context menu only on column headers (
handleColumnContextMenu,ui/packages/components/src/renderers/complex/data-table.tsxline 2110). A right-click on a row gets the browser's menu. - Bulk actions are drawn by
BulkActionBaronly from a view'sbulkActions/bulkActionDefs.ActionSchema.bulkEnabledis retired (spec 17.0.0), so a single-record action is never implicitly a bulk one.
Target
- Right-click on a row that is not part of a multi-row selection opens a menu at the pointer.
- Its items are exactly the items that row's ⋯ menu shows: same source, same predicates, same capability gate, same order, same confirm dialogs.
- Build it from the one resolver
RowActionMenuuses, not a second list, so the two menus cannot drift. - Above the actions, add two platform items: Open and Open in new tab (the record URL the row already links to).
- When the row has no actions, the menu still shows those two items.
- Right-click on a row that is part of a selection of two or more rows opens the declared bulk actions for that selection: the same items and gates
BulkActionBarshows. With no declared bulk action, the menu says the selection count and offers Clear selection, as the bar does. It never lists single-record actions. - The browser's own menu stays on a link inside a cell, on a cell in edit mode or any text input, and when the user has a text selection inside the row. Copying text from a list must keep working.
- Keyboard: the context-menu key and Shift+F10 on a focused row open the same menu. Esc closes it and returns focus to the row.
- Touch: no long-press menu. Touch keeps the ⋯ button.
- Every surface the ⋯ menu is on: the menu applies wherever
RowActionMenurenders today (object list grids, related-list tables, the enlarged related list). It does not apply where rows are not selectable and have no row actions.
No spec change, no new action location, no new metadata key. The right-click menu is a second way to reach what list_item already declares.
Why (four axes)
- Business need: this came from the maintainer's own review of the list on the latest main. On a dense list, the ⋯ column is a small target at the far right edge of a wide row. A right-click reaches the same actions where the pointer already is.
- Long-term fit: one resolver feeds two entry points. Ruled out: expanding all single-record actions when rows are selected (the maintainer's earlier question, 「是否建议列表页选中行时,展开可用的actions」). It mixes selection (a bulk act) with a per-row act, and single actions are not bulk-safe now that
bulkEnabledis retired. - AI-error resistance: an app author declares nothing new. A
list_itemaction shows up in both menus or in neither, so an AI cannot declare a right-click item that the ⋯ menu does not honour. - Startup focus: pure renderer work on existing declarations, with no new surface.
Acceptance
- On
examples/app-crm, right-clicking an account row shows the same items, in the same order, as that row's ⋯ menu. An action whosevisiblepredicate is false for that row is absent from both. - Select three rows and right-click one of them: the menu shows the view's bulk actions (or count + Clear when none are declared), never a single-record action.
- Right-clicking a link inside a cell, or a cell in edit mode, shows the browser menu.
- Tests pin item parity with
RowActionMenufor one row (same resolver input gives the same items), the selection branch, and the native-menu exceptions. - Screenshots at 1440 on the evidence branch: one row menu, one selection menu.
Metadata
Metadata
Assignees
Labels
area:recordsBusiness objects, records, the views that show data, usable forms, searchBusiness objects, records, the views that show data, usable forms, searchdomain:uienhancementNew feature or requestNew feature or requestpriority:p2Medium: important, M3Medium: important, M3
Activity
objectstack-fleet commentedon Oct 31, 2026
domain:ui#5 · os-sales · session_01L6bhgUhKx1XHzkoikGjRB4
Claim: PM loop round 6 · 2026-10-31T16:13Z
Session:
session_01L6bhgUhKx1XHzkoikGjRB4(seat; implementation by a card thread,mode:thread, its session named in its first report)Account:
os-salesBranch: the card thread's harness-pinned branch (named in its first report; this claim is the identity record)
Worktree: the card thread's own container checkout
Domain:
domain:uiSeat:
domain:ui#5File surface:
ui/packages/plugin-grid/src/components/RowActionMenu.tsx(the one resolver, extracted where both menus read it),ui/packages/plugin-grid/src/ObjectGrid.tsx,ui/packages/plugin-grid/src/components/BulkActionBar.tsx(the bulk set read from one place), a new row context-menu component beside them,ui/packages/components/src/renderers/complex/data-table.tsx(a rowonContextMenuhook beside the existing header one, ≈L2110), i18n for Open / Open in new tab / Clear selection in all ten packs, tests beside, root changeset (stop on breach; explain in the report). ⛔packages/spec/**. ⛔ui/packages/plugin-detail/**, held by #27524 PR ④ (this seat, card thread onclaude/project-thread-orbrul): related-list and enlarged-list tables get the menu only throughObjectGrid/data-table; if they need an edit inplugin-detail, stop and report.Container & model: M,
mode:thread,model: default(dispatch-gates --repo objectstack-ai/objectstack --tierat630b4078: no path-derived mandate),effort: highClause-②: no
Metadata-protocol: none
Responsibility: n/a — not a defect card
Thread-read: none
Premise: holds at
origin/main630b4078:data-table.tsxhas a context menu only on column headers (handleColumnContextMenu≈L2110, wired ≈L3325);plugin-grid/srchas noonContextMenu; row actions are reachable only throughRowActionMenu.tsxSerial constraints cleared: no open PR (27 read) touches
plugin-grid/**,complex/data-table.tsx,BulkActionBarorplugin-list/src/ListView.tsx; surface notice to seat 4 (#28286) sentGenerated by Claude Code
objectstack-fleet commentedon Oct 31, 2026
domain:ui#5 · os-sales · session_01L6bhgUhKx1XHzkoikGjRB4
Claim amendment to 6545912125 · 2026-10-31T16:21Z
File surface: as claimed, plus
ui/packages/types/src/data-display.ts,ui/packages/types/src/zod/data-display.zod.tsandui/packages/types/src/__tests__/zod-mirror-parity.test.ts:data-tablehands a row right-click toObjectGridthrough a new runtime slotonRowContextMenuonDataTableSchema, the same shape asisRowEditable(declared on the TS face, refused by name in the zod mirror, registered in the parity ledger), because the alternative is anas any, whichui/AGENTS.md#0.1 forbids. No new i18n key (reusestable.open,detail.openInNewTab,grid.bulkClear,grid.bulkSelected, present in all ten packs).Clause-②: yes (widening) —
@object-ui/typesgains one optional member on its public TS face; nopackages/spec/src/**file, so the review owed isTIER_DEFAULTby this seat.Metadata-protocol: none (the zod mirror refuses the slot by name, so an author cannot write it)
Seat reading on Target 1 / Acceptance 1 (for the filer, epic:#26676#1): the row menu lists all of that row's actions in
planRowActionMenuorder, including the inline primary buttons the ⋯ menu leaves out only for its width budget. The maintainer's ask was the row's available actions (「展开当前行可用的actions」); a right-click menu with no width limit that omitted the row's primary action would be the ⋯ menu's layout constraint copied, not its item set. Every item still comes from the one resolver with the same predicates, gate and confirm dialogs, so nothing can appear in the right-click menu that the row does not offer. The PR states this deviation; say so here if the strict ⋯-only reading is wanted.Generated by Claude Code
objectstack-fleet commentedon Oct 31, 2026
domain:ui#5 · os-sales · session_01L6bhgUp9U9hou7ccEhNS13
os-dev-report
{
"issue": 28434,
"status": "done",
"branch": "claude/project-thread-e7cfqb",
"pr": "#28457",
"head": "8a537fb0",
"session": "session_01L6bhgUp9U9hou7ccEhNS13",
"reported_at": "2026-10-31T17:35Z",
"premise_still_valid": true,
"summary": "Right-click on a list row opens a menu at the pointer: Open (when the row click opens something), Open in new tab (when the host publishes a record URL), then the row's actions from the same resolver the row's ⋯ cell reads (RowActionMenu split into useRowActionPlan + RowActionMenuItems; the cell's exact props via rowActionMenuProps), so the same capability gate, per-record visible/visibleWhen, order, handlers and confirm dialogs. A row inside a selection of two or more opens the selection's menu from BulkActionBar's own resolver (useBulkActionItems): count, bulk actions, Clear; never a single-record action. data-table owns the menu shell (controlled non-modal DropdownMenu at the pointer, mounted only while open), the keyboard (context-menu key, Shift+F10), Esc focus return and the native exceptions (link, input/contenteditable, cell in edit mode, text selection in the row, touch long-press, portalled menus/dialogs, nothing to offer). New runtime slot DataTableSchema.renderRowContextMenu(row, {selected}) with a zod handlerKeyRefusal and a KnownDrift ledger entry. Related-list tables get data-table's own menu (Open + the row's ⋯ items); plugin-detail not edited. No packages/spec file; i18n reuses existing keys.",
"tests": "New plugin-grid rowContextMenu-28434.test.tsx (17) on the real ObjectGrid and components data-table-row-context-menu-28434.test.tsx (6): parity per row with the ⋯ menu, predicate removes from both, inline primary, Open/new-tab href, dispatch; selection branch (bulk + count, no bulk -> count + Clear, row outside selection, selection of one); native exceptions with controls; Shift+F10, ContextMenu key, Esc focus return. Ablations via scripts/ablation-replace.mjs under os-verify-lock, each red then restored blob == HEAD: link exception (1 failed), touch (1), focus return (1), selection branch (2), inline budget 0->1 (1), built-in selection guard (1). vitest plugin-grid/ + types/ + components/src/renderers/complex/: 687 files, 13003 passed (os-verify-lock VERDICT command-exit 0). After merging origin/main 776c1d8: types/ + new files + ObjectGrid.declaredKeys-11068: 388 files, 10475 passed. type-check (tsc --noEmit + tsconfig.test.json) exit 0 for types, components, plugin-grid; full turbo build exit 0. Screenshots at 1440 on app-crm Accounts linked from the PR body (evidence commit 438798b, removed in 8a537fb): live readings ⋯ [Edit, Delete], context [Open, Open in new tab, Edit, Delete], selection '3 selected / Delete / Clear'.",
"gates": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 35 families, each run with its exit code captured; all 35 exit 0; --ran reconcile 35/35, 0 NOT-MEASURED, 0 UNRUN. check-sdui-manifest exit 0. eslint per changed file: 0 errors. No open PR touches these files (REST file lists; git merge-tree where a merge base exists). PR check-runs: none read yet at report time; this thread is subscribed to the PR and fixes any red this diff causes.",
"dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
"line_budget": "12 files, +1391 / -257 (tests +533)",
"deviations": [
"Inline primary actions are listed in the context menu (order per planRowActionMenu), approved in claim amendment 6546007567; stated in the PR Acceptance notes.",
"Related-list tables (data-table built-in menu) have no Open in new tab (no record URL there) and no selection branch (no bulk actions), so a 2+ selection keeps the browser's menu there; stated in the PR Acceptance notes.",
"The first full-suite run caught a regression in ObjectGrid.declaredKeys-11068 (an always-mounted closed menu drew a Radix trigger id that differed between identical renders); fixed by mounting the menu only while open, before the PR.",
"An early ablation attempt wrapped os-verify-lock inside ablation-replace, so a mutation sat on disk while it waited for the lock; that run was killed, the file verified equal to HEAD, and every reported ablation re-run with the lock outside.",
"The PR body's first two lines are the harness attribution block; the signature line is the third line."
],
"mcp_calls": "reads only: issue_read (get_comments), pull_request_read (get, get_files), list_pull_requests; plus subscribe_pr_activity (session subscription, not a GitHub write). No MCP GitHub write tool was called.",
"api_writes": "2 — relay pr_create (draft PR 28457, run 41225155611, body read back 8868 bytes identical, no assignees) and this os-dev-report comment via post-stamped. git push is not a REST write.",
"open_questions": [],
"out_of_scope_findings": []
}
objectstack-fleet commentedon Oct 31, 2026
domain:ui#5 · os-sales · session_01L6bhgUhKx1XHzkoikGjRB4
Landed · 2026-10-31T18:34Z · PR #28457 (seat ACCEPT 6547276847 and TIER_DEFAULT contract review PASS 6547268983, pinned to
976d6a29)bede9deaedcompletedby the PR'sFixesline;pm:dispatchedremoved in this landingFor the filer (epic:#26676#1): the row menu lists the row's inline primary actions as well as its ⋯ items (seat reading 6546007567); related-list tables get
data-table's own menu without Open in new tab or a selection branch. No follow-up card owed.Generated by Claude Code