Skip to content

fix(plugin-grid): on a phone, each list card offers its row's actions through one 44 px menu - #29301

Merged
objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-yk6p5l
Nov 2, 2026
Merged

objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-yk6p5l

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Steedos · project thread

Fixes #29150

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:手机宽度(768 px 以下,列表画成卡片)时,每张卡片右侧多了一个 44 px 的「⋮」按钮,点开就是这一行在桌面上的全部行操作(编辑、删除、对象声明的行操作),顺序与桌面行的右键菜单一致。点卡片其它位置仍然打开记录。这一行没有任何可用操作时不画按钮。桌面表格不变。
  • 为什么改:Console at phone width: Lists flow audit and fixes at 375 and 390 px #29074 在 375 / 390 px 实测:手机卡片上没有任何行操作入口,长按也只是打开记录。手机用户要编辑或删除一条记录必须先进记录页,只在列表上提供的行操作(list_item)根本够不到。
  • 风险与回滚:低。只改 ObjectGrid.tsx 卡片渲染里一处:复用桌面行的 RowActionMenu 和 rowActionMenuProps(row)(同一套权限、谓词、处理函数和确认框),内联预算设为 0,所以所有操作都在一个菜单里。按钮外层用 -my-2,实测卡片高度不变(62 px)。不加键、不加 prop、不加导出、不加文案。回滚即回退本 PR。
  • 先看:ui/packages/plugin-grid/src/ObjectGrid.tsx 的 buildMobileCardRenderer 末尾 grid-card-actions 那一段(只在 rowOffersAction(row) 时绘制);新测试 ui/packages/plugin-grid/src/__tests__/phoneCardRowActions-29150.test.tsx。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/plugin-grid/src/__tests__/phoneCardRowActions-29150.test.tsx,预期 4 个用例全部通过;在 main 上同一文件 3 个失败。
  • 需要你做的:无,席位落地。

Claim: issuecomment-6574754357.

What changes

Below the 768 px card breakpoint, ObjectGrid draws each row as a card (the #28484 layout: title, one subtitle line, one status chip). A card had no action control, so a phone reader had to open each record to edit, delete or run a declared row action, and an action offered only on the list (list_item) could not be reached at all.

  • ObjectGrid (plugin-grid), buildMobileCardRenderer: each card whose row offers an action ends with the row's own RowActionMenu, rendered from rowActionMenuProps(row), the props the desktop row's "⋮" cell and its context menu already read. So it applies the same capability gate, per-record predicates, record-level verdicts, handlers and confirm steps.
  • The inline budget is 0 (maxInlineActions={0}), the value the desktop row's context menu passes: every action sits in the one menu, in the plan's order (Edit, Delete, primary actions, other actions, legacy actions). On desktop the same set shows in the same order, except that up to one primary action is drawn as a button left of the "⋮".
  • The trigger is RowActionMenu's existing one, 44×44 below sm. Its wrapper stops the click, so a tap on the control does not open the record, and a tap anywhere else on the card still does. The card's wrapper box takes -my-2, so the 44 px target does not make the card taller.
  • A card whose row offers nothing (rowOffersAction(row) false) draws no control.

The desktop table, the row context menu and the metadata are unchanged. No key, prop, export or locale string is added; the trigger's screen-reader label is the existing grid.openMenu.

Tests

  • phoneCardRowActions-29150.test.tsx (new), on the real ObjectGrid with Edit, Delete and three list_item actions (one primary, one gated by visible):
    • At 1280 px, the row context menu is read as the reference: Edit, Delete, Promote (primary), Submit for review (draft rows only), Archive. At 390 px, each card's menu lists exactly that row's reference, in the same order, and no inline button is drawn on a card.
    • One trigger per card, inside the card's last box, with the 44 px classes.
    • Choosing Edit from a card's menu calls onEdit with that row and does not call onRowClick. A tap on another card's title calls onRowClick with that row.
    • Control: a grid with no action draws no trigger on any card.
  • Red on main (0c0d3b6): the first three cases fail ("Unable to find an element by: [data-testid="row-action-trigger"]"), the control passes.
  • Ablation (one time, not kept): maxInlineActions={0} changed to maxInlineActions={1} through scripts/ablation-replace.mjs reds the order case (the primary moves onto the card as an inline button). The restore left git diff HEAD empty. A first attempt replaced the anchor with a string that already occurred 41 times; the tool refused it as a no-op, so it did not run.
  • Neighbours: phoneCardRow-28484.test.tsx and rowContextMenu-28434.test.tsx pass unchanged (29/29 with the new file).

Acceptance notes

Live measurement, Console production build of this branch (pnpm console:build at 49c4abf), served at /_console by pnpm dev:crm -- --seed-admin --fresh. Chromium with isMobile and hasTouch, DPR 2, at 390×844 and 375×812; desktop reference at 1280×900. Script and readings: /mnt/project-files/issue-29150/measure.cjs, after/measure.json (crm_lead) and after/measure-account.json (crm_account).

Check (crm_lead, which declares the list_item action Convert Lead) 390 375
Each card has one row-action control yes, 44×44, inside the card yes, 44×44, inside the card
Card height with / without the control 62 / 62 px 62 / 62 px
Card menu equals the desktop row's actions, same order Edit, Delete, Convert Lead = desktop "⋮" and context menu same
Tap on the control opens the record no (URL unchanged) no
Tap on the card opens the record yes yes
Menu fits the viewport; no horizontal page scroll yes; 390 = 390 yes; 375 = 375
Title width beside the chip and the control 200 px on a lead card; 292 px on an account card 277 px on an account card

crm_account (Edit and Delete only) reads the same: 44×44 control, card 62 px either way, menu equal to the desktop menu, the card tap opens the record. Before this change (main a4015ab, #29074's run record, check L06) a card held no control and a long press opened the record.

Out of scope, reported to the seat: the menu's items are 32 px tall at phone width. That is the shared dropdown item, the same in every phone menu, not this card's control.

Verification

All local readings are on head 49c4abf.

Touched package and neighbours
  • pnpm --dir ui exec vitest run packages/plugin-grid/src/__tests__/phoneCardRowActions-29150.test.tsx packages/plugin-grid/src/__tests__/phoneCardRow-28484.test.tsx packages/plugin-grid/src/__tests__/rowContextMenu-28434.test.tsx: 3 files, 29 tests passed.
  • The plugin-grid test files that import ObjectGrid (git grep set, 163 files) at --maxWorkers=2 in four batches: 41 + 41 + 41 + 40 files passed, 269 + 372 + 286 + 360 = 1287 tests passed. CI's Test UI runs the rest of the package.
  • pnpm exec turbo run type-check --filter=@object-ui/plugin-grid: exit 0 (tsc --noEmit && tsc -p tsconfig.test.json; --listFilesOnly on the test project lists the new file).
  • eslint on the two changed ui files: 0 errors (warnings only, the file's existing any).
  • pnpm --dir ui run check:new-line-citations: 0 new citations.
Derived gates

node scripts/pm/dispatch-gates.mjs --commands derived 35 families for this diff; each ran locally on 49c4abf and exited 0. dispatch-gates --ran reconciliation: 35 derived, 35 run, 0 NOT-MEASURED, 0 UNRUN.

node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0
node scripts/check-adr-0087-registration.mjs --self-test :: exit 0
node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0
node scripts/check-closing-keyword-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0
node scripts/check-comment-mask-corpus.mjs :: exit 0
node scripts/check-empty-changeset.mjs --base origin/main :: exit 0
node scripts/check-empty-changeset.mjs --self-test :: exit 0
node scripts/check-sdui-manifest.mjs :: exit 0
node scripts/check-sdui-manifest.mjs --self-test :: exit 0
node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0
node scripts/release-pending-publish.mjs --self-test :: exit 0
pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0
pnpm check:changeset-gate-self-tests :: exit 0
pnpm check:closed-ceremony-text :: exit 0
pnpm check:cross-package-test-inputs :: exit 0
pnpm check:dispatcher-error-vocabulary :: exit 0
pnpm check:driver-memory-census :: exit 0
pnpm check:engine-double-contract :: exit 0
pnpm check:error-status-conformance :: exit 0
pnpm check:gitlink-declared :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:pm-changeset-deadline-census :: exit 0
pnpm check:published-files :: exit 0
pnpm check:query-options-erasure :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:skill-identifier-liveness :: exit 0
pnpm check:test-source-alias :: exit 0
pnpm check:tier-file-adoption :: exit 0
pnpm check:type-check-coverage :: exit 0
pnpm check:type-check-debt :: exit 0
pnpm check:undeclared-dep-imports :: exit 0
pnpm check:watch-hint-literal :: exit 0
pnpm check:where-matcher :: exit 0
pnpm --dir ui exec vitest run scripts/ :: exit 0

vitest run scripts/ read 169 files passed, 2 skipped (4900 tests passed). The type-check lanes and the wide-population families are CI's.

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic. (packages/spec not touched.)
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; lint passes on the changed files.
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset.
  • 6. packages/spec not touched.
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English (the 维护者速读 section is the maintainer summary the lane asks for).
  • The Before Submitting list in CONTRIBUTING.md is done.

Generated by Claude Code

…e 44 px menu

Each card in ObjectGrid's phone card face ends with the row's own
RowActionMenu (inline budget 0) when the row offers an action, so the
desktop row's actions are reachable from the list on a phone and a tap on
the card still opens the record.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsR2jVMaEeoiKQgHPaTy3Z
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tooling labels Nov 2, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#1 · zhuangjianguo · session_01QsR2jUJ19MstFvFhjgiYTu

席位核对 · ACCEPT · 2026-11-02T11:37Z

Head-sha: 49c4abf863309f5f391910b5a039cf0eed295bb8

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 2, 2026 11:39
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge November 2, 2026 11:39
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 2, 2026
Merged via the queue into main with commit d5ab8fc Nov 2, 2026
56 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-yk6p5l branch November 2, 2026 12:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Phone list card: no row-action control, and a long press opens the record, so a phone reader can't reach a row's actions from the list

1 participant