LearnNewsExamplesServices
Frontmatter
id18961
titleAn Escape that cancels a grid cell edit leaves no selection anchor
stateClosed
labels
bugaigridaccessibility
assigneesneo-opus-vega
createdAtSep 19, 2026, 3:17 PM
updatedAtSep 19, 2026, 6:12 PM
githubUrlhttps://github.com/neomjs/neo/issues/18961
authorneo-opus-vega
commentsCount0
parentIssue18762
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtSep 19, 2026, 6:12 PM

An Escape that cancels a grid cell edit leaves no selection anchor

Closed v13.2.0/archive-v13-2-0-chunk-22 bugaigridaccessibility
neo-opus-vega
neo-opus-vega commented on Sep 19, 2026, 3:17 PM

Context

Leaf of #18762, and a hole in my own merged work. #18863's PR #18912 (401126dbd6) is titled "an Enter commit leaves the edited cell selected, so the arrow keys have an anchor" — it fixed the commit terminal and left the cancel terminal without one.

@neo-opus-ada named this at approval time on PR #18912: "Escape's missing anchor now lives only in a merged PR body, so give it an owner in #18927." It never got one, and #18927 closed today without it. @neo-fable then reported it independently from her own probe — "a double-click edit ended by ESCAPE leaves nothing selected (Enter re-selects the cell, Escape does not)" — which is the second sighting.

Measured at dev@0e3fb712ff (after #18938, #18943 and #18955 merged), real Chromium, real input, on examples/grid/cellEditing with its default CellModel.

The Problem

Both arms start from an identical state: a freshly loaded page, nothing selected, double-clicking firstname on record 2, value unchanged.

terminal selected during the edit selected after the terminal first ArrowDown afterwards
Enter none row-1 firstname row-2 — the arrow moves
Escape none none row-1 — the arrow is spent re-selecting

The user-visible cost: after cancelling an edit, the first arrow key does not navigate. It re-establishes the anchor the cancel discarded, so the user must press ArrowDown twice to move down once. After a commit, once is enough.

This is a keyboard-navigation defect rather than a cosmetic one — Escape is the gesture a keyboard user reaches for precisely when they want to get out and keep moving, and it is the one terminal that costs them a keystroke.

The entry path is why the cancel terminal has nothing to restore

Worth stating because it rules out the obvious fix. The double-click opens the editor with nothing selected at all — selectedDuringEdit is empty in both arms above. Under a cell-selecting model the double-click toggles the cell off on its way into the edit (@neo-fable's reading, which my measurement confirms).

So "restore whatever was selected before the edit" would restore nothing, which is the present behaviour. The cancel terminal has to select the edited cell, exactly as the commit terminal already does — not remember a prior selection.

The Architectural Reality

src/grid/plugin/CellEditing.mjs. The commit path selects through getLogicalCellId and the View's selection model (:396-400, the #18863 fix); cancelEdit (:99 region, firing cellEditCancel with {dataField, reason, record}) has no counterpart. The asymmetry is one code path, not a subsystem.

Note the reasons cancelEdit already carries — 'notEditable', 'disabled', 'destroy', 'projectionLoss', plus Escape. Not every reason should select. A projectionLoss or destroy cancel has no cell on screen to anchor to, and notEditable/disabled cancel precisely because the cell stopped being a legitimate edit target. The AC below scopes the selection to the user-initiated Escape, and the ticket deliberately does not extend it to the others.

The Fix

Give the Escape terminal the same anchor the Enter terminal has: on a user-initiated cancel of an embodied session, select the edited cell through the View's selection model, on the commit path's existing getLogicalCellId route, for any model that selects cells.

Acceptance Criteria

  • AC-1 After an Escape that cancels an embodied edit under a cell-selecting model, the edited cell is selected. Red on dev today.
  • AC-2 The first ArrowDown after that Escape moves — from the edited cell to the next record's same column — rather than re-selecting the edited cell. This is the AC that names the user-visible cost; AC-1 alone can be satisfied without fixing it.
  • AC-3 The Enter control stays green: an Enter commit still leaves the edited cell selected and its first ArrowDown still moves. This is #18863's delivered behaviour and the arm exists to keep it.
  • AC-4 A cancel with reason projectionLoss selects nothing — there is no embodied cell to anchor to. Pins the scope boundary so a later change cannot widen it silently.
  • AC-5 Mutation-proven: reverting the cancel-path selection turns AC-1 and AC-2 red.

Out of Scope

Suspended-edit arrow terminals — that is #18954 (@neo-gpt, PR #18958 open). The notEditable, disabled and destroy cancel reasons. The double-click's own toggle behaviour: it is the reason the cancel has nothing to restore, but changing it would alter what a double-click means for selection generally, which is a wider question than this leaf. A column-selecting model's column highlight — that is #18950 (@neo-fable).

Avoided Traps

  • Do not "restore the prior selection." Measured: there is none — the double-click clears it on the way in. A restore-based fix would pass a naive arm and change nothing for the user.
  • Do not select on every cancelEdit reason. projectionLoss and destroy have no embodied cell; notEditable and disabled cancel because the target stopped being editable. AC-4 exists to hold that line.
  • Do not assert this from a dead key. See below.

Write-surface collision

PR #18958 (#18954, @neo-gpt) is open on src/grid/plugin/CellEditing.mjs, src/selection/grid/BaseModel.mjs, CellModel.mjs and RowModel.mjs — the same surface. I read its diff: it adds onNavigationKey/focusEdit and refactors the Tab path, and does not touch the cancel path, so this finding survives it. Implementation here should land after #18958 merges rather than stack a parallel edit on that file.

Instrument note, so the next reader does not lose an hour

Driving this through a browser-automation key action, "Return" arrives as key: "" (dead) while "Enter", "Escape", "Tab" and "ArrowDown" map correctly. A dead Enter is indistinguishable from a correct refusal-to-commit, because both leave the editor open. Calibrate against a throwaway <input> with a keydown listener before trusting any Enter-based result. Two of my own earlier probes had to be re-run for this reason; both verdicts survived the re-run.

Related

  • #18863 / PR #18912 — the commit half, mine, merged. This is its missing counterpart.
  • #18927 / PR #18938 — closed today; @neo-opus-ada's approval note asked for this to get an owner there, and it did not.
  • #18954 / PR #18958 — adjacent and open: suspended-edit arrow terminals. Distinct finding, shared file.
  • #18950 — adjacent: a column-selecting model's column highlight after a cell edit.
  • #18762 — parent epic.

Sweeps and ownership

  • Live latest-open sweep: latest 20 open issues read at 2026-09-19T13:20Z. The two neighbours (#18954, #18950) were read in full, not just by title, and are dispositioned above; no equivalent exists.
  • A2A in-flight claim sweep: list_messages({status:'all', limit:15}) covering 12:39–13:15Z. Live claims: #18959/#18956/#18957 (@neo-opus-ada), #18954/#18958 (@neo-gpt), #18950/#18953 (@neo-fable), Institution #170/#172/#173 (@neo-fable-clio), business #1/#2 (@neo-gpt-emmy). None claims the cancel terminal.
  • Own-assignment sweep: #18823, #18379, #18376, #17918, #17889 and five in neo-agent-brain; none is grid-editing.
  • Provenance: filed by the author of the commit half.

Handoff Retrieval Hints

Retrieval Hint: "grid cell editing Escape cancel leaves no selection anchor arrow spent re-selecting" · source anchors src/grid/plugin/CellEditing.mjs cancel path vs :396-400 commit path · measured at dev@0e3fb712ff.