Skip to content

fix(data-grid): edits land on the wrong row or field - #201

Open
sdonche wants to merge 3 commits into
mainfrom
claude/github-issues-bundle-639368
Open

sdonche wants to merge 3 commits into
mainfrom
claude/github-issues-bundle-639368

Conversation

@sdonche

@sdonche sdonche commented Oct 6, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes four Data Grid bugs where an edit, select or delete hit the wrong row or field without any error.

Changes

Docs: CHANGELOG [Unreleased], README, user manual, onCellEdit event description. No props schema change. For #130 I chose a console warning over a new output.* prop.

Testing

Closes #127
Closes #128
Closes #129
Closes #130

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Editing or pasting in grids with pinned columns now targets the correct field.
    • Open editors stay with their row when data refreshes and close without saving if the row or column is no longer visible.
    • Cell-edit events include all pending edits for the row, including when IDs or fields contain ::.
    • Pending edits clear when bound data changes to a different value.
    • Rows with missing or duplicate IDs remain visible but cannot be selected, edited, or deleted; a warning is shown when these actions are enabled.
    • CSV exports list pinned columns first.
  • Documentation
    • Clarified pinned-column ordering, pending edits in cell-edit events, and row ID requirements.

Four ways a grid edit could be written to the wrong place without any
error:

- Cell positions are display indexes (pinned first), but edit and paste
  looked them up in config/columnLayout order, so a pinned column that
  wasn't first shifted every edit onto its neighbour (#127).
  effectiveColumns now returns pinned columns first.
- The open editor stored a row index. A rebind that inserted a row moved
  a different record under it and the commit went there (#128). The
  editor now holds the row id + field, commits by id, and closes if
  the row or column leaves the view.
- onCellEdit's row carried only the one new value, so persisting it
  undid an earlier unrebound edit of the same row (#129). It now applies
  every pending edit of that row.
- Rows with a missing or shared idField value all answered to one id,
  so one click selected them all and Delete removed them all (#130).
  Such rows are now read-only and the grid logs a warning. Pending edits
  use a structured key, so ids containing '::' no longer split wrong.

Closes #127, closes #128, closes #129, closes #130.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 9ea098bc-0953-4783-91ad-db97d0df48a7
📥 Commits

Reviewing files that changed from the base of the PR and between 14c76c4 and 4883873.

📒 Files selected for processing (6)
  • CHANGELOG.md
  • README.md
  • e2e/tests/grid.spec.ts
  • web/typescript/components/__tests__/gridLogic.test.ts
  • web/typescript/components/grid/DataGrid.tsx
  • web/typescript/components/grid/gridLogic.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • CHANGELOG.md

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 6 remain after this review.


📝 Walkthrough

Walkthrough

The Data Grid now orders pinned columns first, tracks editors by row ID and field, and restricts actions on rows with missing or duplicate IDs. Pending edits use structured keys, reconcile against rebound values, and appear together in cell-edit event rows.

Changes

Data Grid behavior

Layer / File(s) Summary
Column order and edit sessions
web/typescript/components/grid/gridLogic.ts, web/typescript/components/grid/DataGrid.tsx, web/typescript/components/__tests__/gridLogic.test.ts, e2e/tests/grid.spec.ts, CHANGELOG.md
Pinned columns appear before scrolling columns. Edit sessions locate cells by row ID and field after grid updates, and commit and keyboard navigation use the current cell position. Tests cover pinned-column order, editing the displayed cell, and retaining the edited row after a rebind.
Rows with unusable IDs
web/typescript/components/grid/gridLogic.ts, web/typescript/components/grid/DataGrid.tsx, web/typescript/components/__tests__/gridLogic.test.ts, e2e/tests/grid.spec.ts, README.md, docs/user_manual.md, CHANGELOG.md
Rows with missing or duplicate IDs remain visible but are excluded from selection, editing, paste, and deletion. The grid warns when relevant interactions are enabled. Tests and documentation describe the row-ID rules and resulting behavior.
Pending edits and event rows
web/typescript/components/grid/gridLogic.ts, web/typescript/components/grid/DataGrid.tsx, web/typescript/components/__tests__/gridLogic.test.ts, common/src/main/resources/datagrid.oncelledit.event.json, README.md, CHANGELOG.md
Pending edits use structured keys, including when IDs or fields contain ::. Reconciliation retains edits until bound values match the pending value or change from the recorded baseline. Cell-edit event rows include all pending edits for the row.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Bug fix · Severity of issue fixed: Medium

Merge Risk: ⚪ Minimal · up to 48838

The Data Grid changes keep edits attached to the intended row and column and block interactions on rows with unusable IDs. No actionable merge-blocking issue was identified in the supplied evidence.

Security Architecture Review

Security architecture risk: 🟡 Moderate · up to 48838

The changes improve normal edit targeting and reject ambiguous row IDs. However, an unresolved edit can be carried into a later record that reuses the same ID, and the expanded event payload can forward that stale value for persistence. This is a conditional data-integrity risk, not a verified authorization bypass.

Retained concerns

  • Medium · reliability · inferred: The expanded onCellEdit row can replay an unresolved value from an earlier record after its ID is reused. For example, edit field A, remove that row before settlement, then bind a different record with the same ID and the same baseline A value. Pending A survives; editing field B now includes stale A in the replacement record's event.row. Current uniqueness checks accept the replacement because its ID is unique. Missing-row retention existed before this PR, but forwarding unrelated pending fields through an immediate cell-edit event is new. The outcome depends on ID reuse and a consumer that persists the full row; this compromises pending-state ownership and write containment, without establishing a privilege escalation.
Security review details

Security Blast Radius

  • inferred — The supported exposure is pending values and records addressed through one mounted grid and its configured write-back handlers. The public contract supports database updates, but production data-store, tenant, and privilege scope could not be established.

Security Findings and Attack Paths

  • inferred — ID reuse can carry a previously validated pending value into a later record's full-row event. This is a conditional unintended-write path; attacker control over record rebinding and a resulting authorization bypass are not established.

Trust Boundaries and Controls

  • observed — The eventsEnabled dispatch gate is unchanged between base and head. Row eligibility and edit validation are browser-local controls; the inspected component delegates events to the host and does not establish server-side authorization.

Resilience and Maintainability Implications

  • observed — Batch payload construction still uses last-row-wins ID lookup without a duplicate-ID rejection. This behavior and retention of pending values for absent rows predate the PR; they are existing containment limitations, not separate introduced concerns.

Hardening Proposals

  • proposed — Make pending-edit ownership explicit across dataset changes: require lifetime-stable row identities or associate pending entries with a dataset generation and quarantine entries when identity becomes ambiguous. Apply the same eligibility policy before constructing persistence payloads.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The incremental diff adds AGENTS.md, deletes CLAUDE.md, and adds an unrelated [0.8.0] changelog section for system.mustry.toast(). These changes do not implement or document [#127]–[#130]. The… Remove the unrelated AGENTS.md/CLAUDE.md changes and the system.mustry.toast() release entry from this PR, or move them to a separate PR.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: fixing Data Grid edits that target the wrong row or field.
Linked Issues check ✅ Passed The PR meets the coding requirements for all four active issues. [#127] effectiveColumns puts pinned columns first, matching rendered cell positions. [#128] DataGrid stores edits by rowId and `f…
Docstring Coverage ✅ Passed Docstring coverage is 90.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 10 functions across 4 files. (2 skipped: 2 …
Full details: Out of Scope Changes check

Explanation

The incremental diff adds AGENTS.md, deletes CLAUDE.md, and adds an unrelated [0.8.0] changelog section for system.mustry.toast(). These changes do not implement or document [#127]–[#130]. The Data Grid changelog and documentation changes remain in scope.

  • Fix all pre-merge checks with AI
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@sdonche

sdonche commented Oct 6, 2026

Copy link
Copy Markdown
Collaborator Author

Review and test results

Tested

Should fix before merge

  1. The editor loses keyboard focus after a rebind. Rows are keyed by index, so when a row is inserted the input is remounted and focus drops to <body>. The editor stays open, but typing, Enter and Escape do nothing until the user clicks into it again. Keying rows by id (id:${id} for usable rows, at:${i} otherwise) keeps focus; verified live.
  2. The editor can still commit to another record when its id becomes a duplicate (CodeRabbit's first point, reproduced). Opened an editor on WO-10005, rebound so another row also had WO-10005: the editor rendered on both rows, and Enter fired onCellEdit with the other record's data (oldValue 17, product "Widget B" instead of "Valve V2"). Closing the editor in componentDidUpdate when its id is unusable fixes it (verified live); commitEdit should also recheck cellEditable.

Smaller
3. Shift-range selection across a duplicate row puts the duplicate id into state.selection, and both duplicate rows are highlighted, including one outside the range. Delete skips it, but scripts reading state.selection get the bad id.
4. Pending edits only clear when the bound value matches. When a write-back rejects or normalizes a value, the stale pending value is now re-sent in the next onCellEdit row for that record (CodeRabbit's second point). The root cause predates this PR. The README ("clear as soon as any data.rows change arrives") and the DataGridState comment describe this wrongly.
5. The missing-id warning also fires for read-only grids (no select, edit or delete), where ids don't matter.
6. CSV export now orders pinned columns first. That matches the screen, but the CHANGELOG doesn't mention it.

Pre-existing, not from this PR: one Enter in the cell editor fires onCellEdit twice (verified on main too): the commit focuses the grid, the editor's blur runs commitEdit again before state flushes. Tracked separately.

Follow-ups from testing this PR live:

- Rows were keyed by index, so a rebind that shifted the edited row
  remounted the editor's input and dropped the focus: typing, Enter and
  Escape went nowhere. Rows are now keyed by id.
- An editor whose row id became shared after it opened still committed
  to the first row with that id, with that record's data. locateCell now
  treats an unusable id as gone, and commit rechecks that the cell is
  still editable.
- A Shift-range selection swept duplicate ids into state.selection, and
  a selected duplicate id highlighted every row with it.
- A pending edit only cleared when the bound value matched it, so a
  rejected or normalized write-back left the old value shown and re-sent
  it in the row's next onCellEdit. It now also clears when the cell's
  bound value changes to anything else (settlePending).
- The missing-id warning no longer fires for grids that never select,
  edit or delete.

Docs: README described pending as clearing on any rows change; it never
did. CHANGELOG notes the CSV column order and the batch-mode conflict
behaviour.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sdonche

sdonche commented Oct 6, 2026

Copy link
Copy Markdown
Collaborator Author

Findings 1 to 6 from the review above are addressed in b118016. New tests: unit tests for locateCell with a shared id, settlePending and unusableIdWarning; three e2e tests (editor keeps focus across a row-inserting rebind, editor closes when its id becomes a duplicate, Shift-range skips duplicates). Each new e2e test fails against 14c76c4 and passes now. ops/e2e.sh --fresh: 109 passed. The double onCellEdit on Enter predates this PR and is being fixed separately.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment