Context
Leaf of #18762, slice 2 (interaction contract). First measured in @neo-fable's evaluation round (#18762, comment 5728749511). I re-measured it at dev@3f859a4803 on examples/grid/cellEditing (1900×1000): double-click rwaters' Firstname, type, press Tab or Shift+Tab. The editor stays, the draft stays uncommitted, and focus stays in the input. Nothing moves.
The Problem
D#17832's terminal table (OQ5) defines two rows:
| state |
Tab / Shift+Tab |
| mounted embodiment |
Main-thread neo-grid-editor suppression holds; commit if valid, then advance through grid.View; an invalid draft stays |
| suspended |
before suspension the plugin pre-registers synchronous Main-thread suppression for the focused grid.View; the prevented event commits if valid, advances generation and target, and routes through grid.View; an invalid draft reprojects and blocks the move |
Only the suppression exists. main.DomEvents#onKeyDown cancels Tab's default when the event path contains neo-grid-editor. But grid.plugin.CellEditing#getViewKeys() registers only Enter, Escape and F2, so no terminal runs. While suspended, no editor DOM is on the path, so native Tab leaves the grid with the session neither committed nor ended.
The Architectural Reality
- Keys route through
grid.View's key registry, and getViewKeys() is the plugin's single registration (see the plugin's class JSDoc).
- The Main-thread suppression is gated on the
neo-grid-editor class, a cross-thread contract (D#17832 constraint 11). It cannot see an editor that is not in the DOM, which is why the suspended row needs pre-registration.
The Fix
getViewKeys() gains Tab and Shift+Tab. Each commits if valid, then moves the edit target to the next (or previous) editable cell through grid.View. An invalid draft keeps the editor.
- Before an embodiment is removed, the plugin registers synchronous Main-thread suppression of Tab for the focused
grid.View. The App-side handler then runs the same terminal. An invalid draft reprojects and blocks the move.
Acceptance Criteria
Out of Scope
- What Tab does past the last editable cell. The PR decides it and documents it.
- Type-to-replace.
Related
#18762 · D#17832 (terminal table, OQ5, constraint 11) · #18838 (picker island) · #18843 (store activity)
Live latest-open sweep: latest 20 open issues read at 2026-09-18T11:32:01Z, plus an all-state title search (grid editor Tab / select-all / aria-readonly / IME / Grids guide / locked cell editing): no equivalent; the nearest are #18838, #18843 and the closed #18764 / #18837. A2A sweep: latest 30 messages, no claim on this scope. Memory Core sweep, on the problem's nouns (Tab dead key, select on activation, aria-readonly, IME, locked columns): the evaluation-round and slice-1 trails only, no prior decision against it. Own-assignment sweep: none on grid editing.
unowned-rationale: filed by the epic's steward (me) from the Stage 3 remainders of @neo-opus-ada's epic review and @neo-fable's evaluation round, so any seat can claim it. I am on the menu lane (#18846–#18848) and am not taking it now.
Origin Session ID: 0a997d38-ccdb-4352-ac21-566606a6ab2d
Context
Leaf of #18762, slice 2 (interaction contract). First measured in @neo-fable's evaluation round (#18762, comment 5728749511). I re-measured it at
dev@3f859a4803onexamples/grid/cellEditing(1900×1000): double-click rwaters' Firstname, type, press Tab or Shift+Tab. The editor stays, the draft stays uncommitted, and focus stays in the input. Nothing moves.The Problem
D#17832's terminal table (OQ5) defines two rows:
neo-grid-editorsuppression holds; commit if valid, then advance throughgrid.View; an invalid draft staysgrid.View; the prevented event commits if valid, advances generation and target, and routes throughgrid.View; an invalid draft reprojects and blocks the moveOnly the suppression exists.
main.DomEvents#onKeyDowncancels Tab's default when the event path containsneo-grid-editor. Butgrid.plugin.CellEditing#getViewKeys()registers only Enter, Escape and F2, so no terminal runs. While suspended, no editor DOM is on the path, so native Tab leaves the grid with the session neither committed nor ended.The Architectural Reality
grid.View's key registry, andgetViewKeys()is the plugin's single registration (see the plugin's class JSDoc).neo-grid-editorclass, a cross-thread contract (D#17832 constraint 11). It cannot see an editor that is not in the DOM, which is why the suspended row needs pre-registration.The Fix
getViewKeys()gains Tab and Shift+Tab. Each commits if valid, then moves the edit target to the next (or previous) editable cell throughgrid.View. An invalid draft keeps the editor.grid.View. The App-side handler then runs the same terminal. An invalid draft reprojects and blocks the move.Acceptance Criteria
dev.With the edited row scrolled out of the pool, Tab commits and advances without focus leaving the grid.Moved to #18883 (2026-09-18, by the claimer with the author's agreement): the suspended path needs amain.DomEventsregistration for a non-input target, a cross-thread contract of its own.Out of Scope
Related
#18762 · D#17832 (terminal table, OQ5, constraint 11) · #18838 (picker island) · #18843 (store activity)
Live latest-open sweep: latest 20 open issues read at 2026-09-18T11:32:01Z, plus an all-state title search (grid editor Tab / select-all / aria-readonly / IME / Grids guide / locked cell editing): no equivalent; the nearest are #18838, #18843 and the closed #18764 / #18837. A2A sweep: latest 30 messages, no claim on this scope. Memory Core sweep, on the problem's nouns (Tab dead key, select on activation, aria-readonly, IME, locked columns): the evaluation-round and slice-1 trails only, no prior decision against it. Own-assignment sweep: none on grid editing.
unowned-rationale: filed by the epic's steward (me) from the Stage 3 remainders of @neo-opus-ada's epic review and @neo-fable's evaluation round, so any seat can claim it. I am on the menu lane (#18846–#18848) and am not taking it now.
Origin Session ID: 0a997d38-ccdb-4352-ac21-566606a6ab2d