Skip to content

fix(data-grid): fire onCellEdit once when Enter or Tab commits - #204

Merged
sdonche merged 5 commits into
mainfrom
claude/intelligent-meitner-ozzl6w
Oct 6, 2026
Merged

sdonche merged 5 commits into
mainfrom
claude/intelligent-meitner-ozzl6w

Conversation

@sdonche

@sdonche sdonche commented Oct 6, 2026 •

Copy link
Copy Markdown
Collaborator

What & why

In cell edit mode (config.editMode: 'cell'), committing an edit with Enter or Tab fired onCellEdit twice with the same payload, so the author's write-back script ran twice per edit (double DB writes, double audit entries).

Cause. onEditorKeyDown calls commitEdit(), then focuses the grid. That focus blurs the editor synchronously, and the editor's onBlur ran commitEdit() again. Inside the React handler, setState({ editing: null }) and the pending overlay hadn't been applied yet. So this.state.editing was still set and the old value still passed the "changed" check.

Fix. An instance flag, editorOpen, is set in startEdit and cleared in a new closeEditor() helper. Every close path (commit, cancel, the invalid-blur revert, a vanished row/column) goes through that helper. commitEdit and the blur handler check the flag instead of the lagging state, so the blur after an Enter/Tab commit does nothing.

Test. New verify page /grid-cell (GridCellEdit): five static rows in cell mode, the reference write-back script, and a counter of script runs. /grid runs in batch mode, so it couldn't show the bug. The new e2e test commits with Enter, Tab and click-away, and cancels with Escape. It wraps componentEvents.fireComponentEvent on the DataGrid (found through its React fiber) and expects exactly one onCellEdit per commit and none for Escape. It also checks the gateway counter. jest is node-only and the bug comes from React's event batching, so it can't be covered there.

Checklist

  • Tests added/updated (Playwright: grid: cell mode fires onCellEdit exactly once per committed edit)
  • CHANGELOG.md updated under ## [Unreleased]
  • Prop-schema changes are additive: no schema change (ops/schema-guard.sh OK)
  • ./gradlew build passes locally: not run. cd web && npx tsc --noEmit && npm test (769 passed after merging main) and npm run lint (clean) pass.

Notes for reviewers

  • The E2E smoke CI job is the only live gateway run. The session that wrote this had no Docker, so ops/e2e.sh --fresh didn't run.
    • The first CI run failed before the test edited anything. Its event spy looked for React 16's fiber key (__reactInternalInstance$), but the Perspective 8.3 client ships React 18.2, which uses __reactFiber$. 2af8ee8 accepts both keys.
    • Every other e2e test passed in that run.
  • Reproduced and verified another way: DataGrid was rendered in headless Chromium with a stub Perspective client, under React 16.14 and under React 18.2 (createRoot). Results are the same under both.
    • Before the fix: Enter 2 events, Tab 2, click-away 1, Escape 0.
    • After the fix: 1, 1, 1, 0.
    • An invalid draft still keeps the editor open on Enter, focus still moves down after Enter, and blur still reverts an invalid draft.
    • The e2e spy code counted 2 events without the fix and 1 with it.
  • Overlap with fix(data-grid): edits land on the wrong row or field #201 ("edits land on the wrong row or field"), which is open and also rewrites commitEdit. Whichever merges second gets a small conflict. To resolve it, keep fix(data-grid): edits land on the wrong row or field #201's rowId/locate logic, add the || !this.editorOpen guard to its early return, and route its setState({ editing: null }) calls (including the new one in componentDidUpdate) through closeEditor().

🤖 Generated with Claude Code

https://claude.ai/code/session_01SwdCxkT64ei9WdHpJH5Qyn

Summary by CodeRabbit

  • Bug Fixes
    • Fixed duplicate edit notifications when committing a Data Grid cell with Enter or Tab. Each committed edit now triggers its write-back action once, including when clicking away from the cell.
    • Pressing Escape cancels the edit without changing the value or triggering a write-back action. Batch editing remains unaffected.
    • Editors now close consistently after a commit or cancellation, including when validation fails.

claude added 2 commits October 6, 2026 14:35
In cell edit mode, Enter/Tab called commitEdit() and then focused the grid
to hand the keyboard back. That focus blurs the editor synchronously, and
its onBlur ran commitEdit() again: setState({ editing: null }) and the
pending overlay are batched inside the React handler, so this.state still
showed the editor open with the old value, and onCellEdit fired a second
time with the same payload. The author's write-back script ran twice per
edit (double DB writes / audit entries). Batch mode only re-set the same
pending value, so it was harmless there.

An instance flag (editorOpen) now marks the editor open from startEdit
until closeEditor(), which every close path goes through. commitEdit and
the blur handler check the flag instead of the lagging state, so the
blur that follows an Enter/Tab commit is a no-op.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SwdCxkT64ei9WdHpJH5Qyn
New verify page /grid-cell (GridCellEdit): five static work orders in
cell edit mode with the reference onCellEdit write-back script and a
counter of its runs. /grid runs in batch mode, so it could not show the
bug.

The e2e test edits a cell and commits it with Enter, with Tab and by
clicking away, then cancels one with Escape. It counts onCellEdit in the
client by wrapping componentEvents.fireComponentEvent on the DataGrid,
found through its React fiber; a counter on the gateway could race and
hide a second event. It expects exactly one event per commit, none for
Escape, and checks the gateway counter too. The jest suite has no DOM,
and the bug lives in React's event batching, so it can't be covered
there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SwdCxkT64ei9WdHpJH5Qyn
@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: a27ac24d-e227-4a53-9b73-4ea50a2aa2bb
📥 Commits

Reviewing files that changed from the base of the PR and between 2af8ee8 and 20cf837.

📒 Files selected for processing (1)
  • CHANGELOG.md
🚧 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

DataGrid now tracks editor state independently of React state and closes the editor during commits, cancellations, and blur validation. A new /grid-cell verification route and e2e checks cover Enter, Tab, blur, and Escape outcomes.

Changes

Cell edit commit handling

Layer / File(s) Summary
Track editor lifecycle and guard commits
web/typescript/components/grid/DataGrid.tsx
DataGrid tracks whether the editor is open, avoids committing when it is already closed, and uses a shared close operation for commits, cancellation, and blur validation.
Add the grid-cell verification route
ops/verify/README.md, ops/verify/project/com.inductiveautomation.perspective/page-config/config.json, ops/verify/project/com.inductiveautomation.perspective/views/GridCellEdit/*
The verification project adds a /grid-cell route with a five-row editable grid, an onCellEdit write-back script, and an edit counter.
Verify commit counts and edit outcomes
e2e/tests/grid.spec.ts, docs/grid-manual-test.md, CHANGELOG.md
The e2e test and manual checklist check that Enter, Tab, and blur each produce one edit event, while Escape produces none. The changelog reports the duplicate-event issue and the new e2e coverage.

Priority: ⬇️ Low

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

Change: Bug fix · Severity of issue fixed: Low

Merge Risk: ⚪ Minimal · up to 20cf8

Enter and Tab commits are guarded against a second blur commit, and the verification test checks the intended edit outcomes. No identified issue blocks merging after normal checks.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 2af8e

The change prevents duplicate edit events in the normal keyboard-and-blur sequence while preserving validation and event payloads. No introduced security issue was identified. Application-specific write-back handlers and production deployment exposure were not available for verification.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The demonstrated new route exposes five static verification rows and a property-update script. No datastore, secret, or infrastructure authority is added by that script. Production consumers may attach more privileged write-back behavior, but their scope and deployment exposure were not supplied.

Trust Boundaries and Controls

  • observed — User-entered drafts retain the existing configured editability and validation path before event dispatch, and eventsEnabled still gates dispatch. The PR does not add authority to this path. These client-side controls do not establish authorization for any downstream server-side write.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 2 files. (1 skipped: 1 …
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main fix: preventing duplicate onCellEdit events when Enter or Tab commits a cell edit.
✨ Finishing Touches
🧪 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.

claude added 3 commits October 6, 2026 14:39
The onCellEdit spy looked for React 16's __reactInternalInstance$ key on
the scroll container, so on the CI gateway it found no DataGrid and the
new test failed before editing anything. The Perspective 8.3 client ships
React 18.2, which names the key __reactFiber$; accept both.

Checked in headless Chromium against React 18.2 with createRoot: main
still fires onCellEdit twice on Enter and Tab, this branch once, and the
spy finds the instance and counts both cases correctly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SwdCxkT64ei9WdHpJH5Qyn
@sdonche
sdonche merged commit 2e593c3 into main Oct 6, 2026
9 checks passed
@sdonche
sdonche deleted the claude/intelligent-meitner-ozzl6w branch October 6, 2026 18:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants