Skip to content

fix(plugin-grid): inline cells read the record-grained update verdict the row kebab reads - #27384

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-ejerax
Oct 29, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-ejerax

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Warren · project thread
Fixes #27365

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:列表行内编辑现在按「这一行」的服务端写权限判定;服务端拒绝的行点不开单元格编辑器,和该行的 ⋮ 菜单给出同一个答案。
  • 为什么改:对象级有编辑权、但共享/归属/RLS 拒绝某行时,⋮ 菜单已隐藏「编辑」,单元格却仍可编辑,保存时被服务端整单拒绝。
  • 风险与回滚:只收窄不放宽;没拿到行级答案(未返回、请求失败、行无 id)时保持原行为。回滚即 revert 本 PR。
  • 先看:ui/packages/plugin-grid/src/ObjectGrid.tsx(isRowEditable 与 update: canEdit || inlineEditable)、ui/packages/components/src/renderers/complex/data-table.tsx(rowMayEdit)、ui/packages/types/src/data-display.ts(新运行时槽位)。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/plugin-grid/src/__tests__/inlineEditRecordVerdict-27365.test.tsx,预期 8 个用例全绿。
  • 需要你做的:无,席位落地。

What changes

An inline-editable grid gated its cells on the object-level affordance only (inlineEditable), while the row kebab on the same rows also read the server's record-grained update verdict (useRecordCrudVerdicts). A row refused by sharing, ownership or RLS lost Edit from its kebab but still opened a cell editor, and the save was refused whole. Now the cells read the same verdict.

  • @object-ui/types: DataTableSchema declares a new runtime slot, isRowEditable?: (row: any) => boolean. The zod mirror refuses it by name (handlerKeyRefusal, runtime-slot), like renderCellEditor and rowGroups. The ledgers move with it: the KnownDrift entry for data-display.zod.ts#DataTableSchema gains the key (51 entries, 88 keys), and the read lists in the body/children tombstones name it.
  • @object-ui/components: data-table reads isRowEditable through one rowMayEdit(row) check in startEdit (the door click, double-click and Enter all pass through), in the Enter key path, and in the per-cell isEditable. A refused row reads like a row of a read-only table: no editor, no edit cursor, and a click reaches the row's own click handler. With the slot absent, every row behaves as before.
  • @object-ui/plugin-grid: ObjectGrid supplies isRowEditable as resolveRowRecordCrudAffordance(inlineEditable, recordVerdict(rowRecordId(row), 'update')), the exact accessor the kebab reads. It asks the batched probe whenever the grid is inline-editable (update: canEdit || inlineEditable), so an editable grid whose kebab offers no Edit is narrowed too. That is still one probe, read by both affordances, with no second evaluator (ruling 6457467702 B).
  • ListView reaches the cells through ObjectGrid (type: 'object-grid', editable: inlineEdit && inlineEditOffered), so it has no separate door and needs no plugin-list change.
  • Test doubles: editable-grid suites now get the explain probe. Nine plugin-grid suites and one app-shell suite (ListView.gridRowColorSelect-12393) answer it from an explain double instead of reaching the network. This is test-only, per claim amendment 6487558539. The assertions are unchanged.

Why

The card: one grid gave two answers for one row. Fail-open is preserved exactly as the useRecordCrudVerdicts docblock requires: an unknown verdict keeps the object-level behaviour.

Tests

  • plugin-grid/src/__tests__/inlineEditRecordVerdict-27365.test.tsx (8): a refused row opens no editor on click or Enter and shows no edit cursor. CONTROLs: an allowed row stays editable; a non-OK response, a network failure, a response with no records[], and the pre-answer window all keep the row editable. The kebab and the cells agree on both rows from ONE update probe. An editable grid with no kebab Edit still asks and still narrows.
  • components/src/__tests__/data-table-row-editable-27365.test.tsx (7): the seam on its own covers click, double-click and Enter, a click that reaches onRowClick, an allowed-row control, the no-predicate control, and narrows-only.
  • types/src/__tests__/data-table-declared-keys-6882.test.ts: the declared exact shape, a widened-return control (@ts-expect-error), and the zod mirror refusing an authored isRowEditable by name.

Acceptance notes

  • Ablation 1, ObjectGrid's per-row read replaced by (_row: any) => true via scripts/ablation-replace.mjs: 4 of 8 tests in the new grid suite red (the repro, agreement, no-kebab and pre-answer legs). Restored blob equals HEAD and git diff HEAD is empty.
  • Ablation 2, data-table's rowMayEdit replaced by () => true: 3 of 7 tests in the seam suite red (click, double-click, Enter). Restored blob equals HEAD.
  • ui/apps/site was retired on main. After merging main, an ignored leftover directory (node_modules) in the worktree reddened quick-reference-commands-4149 locally. That was a local-state artifact only: the test is green once the leftover is removed, and nothing in the tree changed.

Verification

All readings were taken on head 3b46f5a1 (origin/main c4c515ad merged in), with the @object-ui/console^... closure built under the lock.

Tests
  • pnpm --dir ui exec vitest run over the two new suites, packages/types/, the ten double-carrying suites, inlineEditPermissionGate and rowRecordCrudVerdict: Test Files 398 passed (398), Tests 10521 passed (10521).
  • Before the merge, at e77abe74 plus the doubles: packages/plugin-grid/, packages/types/ and the data-table suites went 934/935 files green with one skipped; the 9 red files were the network-escape suites that now carry the double (46/46). Across 59 ui consumer suites that render an editable grid, 58 were green and the app-shell one was fixed (2/2).
  • pnpm --dir ui exec vitest run scripts/: 168 passed, 2 skipped, 1 red on a stale ignored ui/apps/site leftover. That test is green (10/10) once the leftover is removed.
  • pnpm --filter @object-ui/types|components|plugin-grid run type-check: exit 0, all three.
Gates
  • node scripts/pm/dispatch-gates.mjs --commands: 35 families, all run on 3b46f5a1, every exit code 0, including node scripts/check-sdui-manifest.mjs.
  • dispatch-gates --ran: 35 derived, 35 run, 0 NOT-MEASURED, 0 UNRUN.
  • pnpm --dir ui run check:new-line-citations: 0 new citations.
  • Repo-wide lint and the full ui battery are CI's (Lint UI, Type Check UI, Test UI).

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic.
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; pnpm lint was not run locally (CI Lint UI and Lint & Repo Gates own it).
  • 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 touched: n/a, 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 维护者速读 block is the dispatch's required Chinese summary).
  • The Before Submitting list in CONTRIBUTING.md is done.

🤖 Generated with Claude Code


Generated by Claude Code

… the row kebab reads

A row the server's sharing, ownership or RLS refuses lost Edit from its
kebab but still opened a cell editor. DataTableSchema gains the runtime
slot isRowEditable (refused by name on the zod mirror); data-table reads
it before opening an editor on any trigger, and ObjectGrid supplies it
from the same useRecordCrudVerdicts accessor the kebab reads. Unknown
verdicts fail open.

Claude-Session: https://claude.ai/code/session_01JQwxba6qJDidRrN2u71o5N
Co-authored-by: Claude <noreply@anthropic.com>
…le-grid suites

An inline-editable grid now asks the record-grained update verdict even
when its kebab offers no Edit, so these suites need the explain double
instead of reaching the network.

Claude-Session: https://claude.ai/code/session_01JQwxba6qJDidRrN2u71o5N
Co-authored-by: Claude <noreply@anthropic.com>
…d-grid row colour suite

The suite renders an inline-editable ObjectGrid, which now asks the
record-grained update verdict for its rows. Test-only; per claim
amendment 6487558539.

Claude-Session: https://claude.ai/code/session_01JQwxba6qJDidRrN2u71o5N
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Oct 29, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#2 · os-warren · session_01JQwxbXpF772qWJjojJvRXw

席位核对

ACCEPT · domain:ui#2 · session_01JQwxbXpF772qWJjojJvRXw · head 3b46f5a1c1 · 2026-10-29T04:54Z

席位意见:可编辑表格里,服务端按记录拒绝 update 的行不再打开单元格编辑器,和行菜单读的是同一个判定。

  1. 契约面: 四面都不碰:Clause-②: no,不是收窄,不改 packages/spec/src/**,不碰 governed 路径。所以不需要契约复核,CI 加席位自读即可。@object-ui/types 新增的 isRowEditable 是运行时插槽,zod 镜像按名拒收作者写的值,不扩大可写的元数据。
  2. 席位自读:
    • data-table.tsx:rowMayEdit(row) = isRowEditable?.(row) !== false,同时管住 startEdit、Enter 路径和单元格 isEditable 三处。Enter 路径用的 paginatedData[rowIndex] 与它随后传给 startEdit 的是同一行。被拒的行不显示编辑光标,点击仍然落到行上。
    • ObjectGrid.tsx:isRowEditable 读 resolveRowRecordCrudAffordance(inlineEditable, recordVerdict(rowRecordId(row), 'update')),即 !!object && record !== false。这和行菜单是同一个访问器。没有答案、探测失败或没有 id 的行都按对象级处理,失败时放行。
    • update: canEdit || inlineEditable:行菜单没有"编辑"、但表格可编辑时,也会发同一个批量探测,不会多发第二次。
    • 测试:
      • components 有 7 条:点击、双击、Enter 被拒;三条对照;一条"只收窄不放宽"。
      • plugin-grid 有 inlineEditRecordVerdict-27365。
      • 两处消融实验都变红:ObjectGrid 一侧 4/8,data-table 一侧 3/7。
  3. 扩面文件(claim 修订 2,6487558539): 9 个 plugin-grid 测试,加 app-shell 的 ListView.gridRowColorSelect-12393。逐个核对过,都只装了 explain 的 fetch 替身,没有改断言。types 侧的 4 个文件属于修订 1(6487003956)。
  4. changeset 逐句对照 diff: plugin-grid、components、types 各 patch。"被拒行只读""一次探测、失败放行""新运行时插槽只收窄 editable"三句都与树一致。
  5. 门禁: 3b46f5a1 上 55 个 check-run,44 success、11 skipped、0 失败,mergeable clean。dev 本地派生门禁 35/35 退出码为 0,--ran 中 0 个 NOT-MEASURED。
  6. 落地: 不属 Tier H。席位现在翻 ready、开启自动合并,跟到 MERGED,然后清 plugin-grid: list inline edit opens cell editors on rows the server's record-grained verdict refuses, because the cells read only the object-level grant while the row kebab reads the per-row verdict #27365 的 pm:dispatched。

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 29, 2026 04:55
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 29, 2026
Merged via the queue into main with commit ad2c286 Oct 29, 2026
56 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-ejerax branch October 29, 2026 05:11
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/l tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

2 participants