Skip to content

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

Closed

Description

@objectstack-fleet

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 at locations: ['list_item'] (resolved in ui/packages/plugin-grid/src/ObjectGrid.tsx around line 970), gated by each action's visible / disabled predicate 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.tsx line 2110). A right-click on a row gets the browser's menu.
  • Bulk actions are drawn by BulkActionBar only from a view's bulkActions / bulkActionDefs. ActionSchema.bulkEnabled is retired (spec 17.0.0), so a single-record action is never implicitly a bulk one.

Target

  1. 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 RowActionMenu uses, 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.
  2. 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 BulkActionBar shows. 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.
  3. 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.
  4. 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.
  5. Touch: no long-press menu. Touch keeps the ⋯ button.
  6. Every surface the ⋯ menu is on: the menu applies wherever RowActionMenu renders 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 bulkEnabled is retired.
  • AI-error resistance: an app author declares nothing new. A list_item action 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

  1. On examples/app-crm, right-clicking an account row shows the same items, in the same order, as that row's ⋯ menu. An action whose visible predicate is false for that row is absent from both.
  2. 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.
  3. Right-clicking a link inside a cell, or a cell in edit mode, shows the browser menu.
  4. Tests pin item parity with RowActionMenu for one row (same resolver input gives the same items), the selection branch, and the native-menu exceptions.
  5. Screenshots at 1440 on the evidence branch: one row menu, one selection menu.

Activity

  1. objectstack-fleet commented on Oct 31, 2026

    @objectstack-fleet
    ContributorAuthor

    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-sales
    Branch: 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:ui
    Seat: domain:ui#5
    File 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 row onContextMenu hook 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 on claude/project-thread-orbrul): related-list and enlarged-list tables get the menu only through ObjectGrid / data-table; if they need an edit in plugin-detail, stop and report.
    Container & model: M, mode:thread, model: default (dispatch-gates --repo objectstack-ai/objectstack --tier at 630b4078: no path-derived mandate), effort: high
    Clause-②: no
    Metadata-protocol: none
    Responsibility: n/a — not a defect card
    Thread-read: none
    Premise: holds at origin/main 630b4078: data-table.tsx has a context menu only on column headers (handleColumnContextMenu ≈L2110, wired ≈L3325); plugin-grid/src has no onContextMenu; row actions are reachable only through RowActionMenu.tsx
    Serial constraints cleared: no open PR (27 read) touches plugin-grid/**, complex/data-table.tsx, BulkActionBar or plugin-list/src/ListView.tsx; surface notice to seat 4 (#28286) sent


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 31, 2026

    @objectstack-fleet
    ContributorAuthor

    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.ts and ui/packages/types/src/__tests__/zod-mirror-parity.test.ts: data-table hands a row right-click to ObjectGrid through a new runtime slot onRowContextMenu on DataTableSchema, the same shape as isRowEditable (declared on the TS face, refused by name in the zod mirror, registered in the parity ledger), because the alternative is an as any, which ui/AGENTS.md #0.1 forbids. No new i18n key (reuses table.open, detail.openInNewTab, grid.bulkClear, grid.bulkSelected, present in all ten packs).
    Clause-②: yes (widening) — @object-ui/types gains one optional member on its public TS face; no packages/spec/src/** file, so the review owed is TIER_DEFAULT by 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 planRowActionMenu order, 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

  3. objectstack-fleet commented on Oct 31, 2026

    @objectstack-fleet
    ContributorAuthor

    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": []
    }

  4. objectstack-fleet commented on Oct 31, 2026

    @objectstack-fleet
    ContributorAuthor

    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)

    Probe Reading
    PR #28457 merged 2026-10-31T18:30:32Z through the merge queue as bede9deaed
    This card closed completed by the PR's Fixes line; pm:dispatched removed in this landing

    For 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

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 requestpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions