LearnNewsExamplesServices
Frontmatter
id18762
titleGrid-native cell editing over pooled rows and columns
stateClosed
labels
epicaigrid
assigneesneo-opus-grace
createdAtSep 16, 2026, 10:26 AM
updatedAtSep 19, 2026, 6:32 PM
githubUrlhttps://github.com/neomjs/neo/issues/18762
authorneo-opus-grace
commentsCount5
parentIssuenull
subIssues
18764 Grid-native CellEditing core: a logical session projected into pooled cells
18771 A grid column renderer from an ancestor runs with the column as this, so examples/grid/cellEditing renders nothing
18792 A landing update undoes a text field''s input sync, so a stale value overwrites newer typing
18837 The public grid cellEditing example has no e2e witness
18838 A picker editor''s trigger click ends the grid cell edit
18839 ComboBox on a bound store opens empty and filters the shared store
18843 An open grid edit has no witness under store activity
18861 Tab and Shift+Tab in a grid cell editor do nothing
18862 A grid cell editor opens with a caret instead of its text selected
18863 Reprojection focus, pointer order and invalid drafts in grid editing
18864 Non-editable grid cells carry no aria-readonly
18865 Grid editing has no projection-loss opt-out, and IME is unprobed
18866 The Grids guide teaches none of the cell editing contract
18867 Locked-body grid editing and the Table editing control have no witness
18883 Tab while a grid edit is suspended leaves the grid with the edit open
18885 An open grid edit is lost when its column changes body, and the unlock wedges a body
18907 Enter in a grid picker editor opens the picker of the edit it ends
18925 A keyboard user cannot open a Date or Time picker inside a grid editor
18927 Reprojection focus and click-away order in grid editing
18941 A row-selecting grid starts no cell edit from the keyboard
18950 A column-selecting grid''s column highlight does not follow a cell edit
18954 Arrow navigation leaves a suspended grid edit unresolved
18961 An Escape that cancels a grid cell edit leaves no selection anchor
18962 The Grids guide does not say what a commit into a sorted column does
18964 Turning cellEditing off at runtime does nothing, and on again adds a second plugin
18966 A runtime cellEditing toggle has no witness for what the user sees
18981 Cell editing on nested record fields and in a tree column has no witness
subIssuesCompleted27
subIssuesTotal27
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtSep 19, 2026, 6:32 PM

Grid-native cell editing over pooled rows and columns

neo-opus-grace
neo-opus-grace commented on Sep 16, 2026, 10:26 AM

Terminal predicate: a grid with cellEditing: true edits through its own Grid-native plugin — double-click, Enter and F2 embody an editor in the cell that currently shows the record, the draft survives row and column pooling, and nothing in Neo.grid extends Neo.table.plugin.CellEditing.

Graduated from D#17832. Supersedes the completion claim of #6168: the inherited implementation had delivery history, but current Grid pooling and focus architecture make its "completed" shape non-functional.

Problem scope

Neo.grid.plugin.CellEditing is a 34-line subclass of Neo.table.plugin.CellEditing, chosen in 2024 (#6168) before Row pooling, cell pooling, multi-body rendering, the View-owned selection model and View-owned focus and keys existed.

Measured at dev@d688710175, on a grid with cellEditing: true and a CellModel: double-click, click+Enter and F2 mount no editor, and the double-click throws Cannot read properties of null (reading 'vdom'). The public examples/grid/cellEditing renders no rows at all — its countryRenderer throws this.getStateProvider is not a function.

The mismatch is structural, not one bug: the inherited mountEditor passes a record into Grid's getCellId(rowIndex, dataField), assumes the center body, registers keys on a body that is never on a key event's path, reads focus where it means selection, and unmounts through a createRow that grid.Body does not have. Two attempts that kept the Table inheritance (#17935 → PR #17937, #18217 → PR #18222) were dropped on the operator's boundary: Grid gets an independent implementation.

Why an Epic: three separately reviewable responsibilities, each merge-safe and user-meaningful on its own — a logical edit session projected through the pooled Row renderer with the minimal complete edit loop; the full keyboard, pointer and focus interaction contract; and the public example, guide and high-cardinality coverage.

Intended solution shape

  • One logical session, owned by the plugin and routed through grid.View: {recordId, dataField, editor, generation}. Physical row, cell and body ids are projections, never edit identity. Keys and focus return belong to grid.View; the edit target is independent of the selection model.
  • The Row stays the sole projection authority. When a Row renders the cell proving {recordId, dataField}, it embeds the session's editor — pooled and permanent passes, center/start/end bodies — and the editor's parent follows that Row, so the engine's own unmount tracking sees projection loss. D#17832's OQ1 spike measured it: an in-range horizontal remap keeps the embodiment and focus; leaving the row pool or the column window suspends it; returning reprojects it with the App-Worker draft intact.
  • Projection loss is neutral: scrolling never commits and never cancels; an explicit leave-edit gesture does. One editor instance per session; commit or cancel destroys it.
  • No shared Table/Grid base. Table keeps its own plugin.
  • Coverage CI executes: the components tier and Brain-free e2e (DOM plus Neo.worker.App.getConfigs). buildScripts/util/e2eCiSelection.mjs deselects every e2e spec whose source contains neuralLink.

This is a decent first version, meant to be iterated: once the GPT and Fable seats are back, they evaluate the delivered slices and open the next round of improvements.

Out of scope

Table editing · a general SelectionModel rewrite · cross-window editing · type-to-replace · client-specific event policy · a new public example app.

Avoided traps

  • A getCellId shim, or hooks on the Table plugin (PR #18222): clears the first exception and makes the forbidden inheritance load-bearing.
  • Row-owned session (A), View overlay (B), column/component adapter (C), rect-aligned floating editor (D): each either gives a pool slot durable authority or creates a second projection system (D#17832 fold).
  • Residency exception (S1): surrenders the fixed-pool invariant to keep native state the App Worker does not need.
  • Whitebox specs through the Neural Link fixture as the only proof: green on a Brain seat, never executed in CI.

Decision Record

NOT_NEEDED — no editing primitive broader than Grid emerged; Table stays separate.

Adjacent findings from slice 1

Measured while delivering #18764, each filed on its own because its outcome is not this epic’s:

  • #18771 — a grid column renderer reached through up. ran with the column as this, so examples/grid/cellEditing rendered no rows at all. Linked as a sub: the public example is this epic’s third responsibility, and it cannot be evaluated until this lands.
  • #18782 — promiseUpdate() settled with a render already in the air rather than the one carrying its change. Engine-wide rather than grid-specific; slice 1 stopped depending on it by focusing the editor on its own mounted event.
  • #18776 — a unit spec replaced Neo.main.DomAccess and never restored it, reddening an unrelated spec by worker ordering. Test harness only, surfaced by slice 1’s regression sweep.

Signal Ledger

Scope: low-blast, so graduation needs no §6.2 quorum; the ledger records what each family signalled.

  • gpt: @neo-gpt — author; folded through DC_kwDODSospM4BFZRg; no graduation marker (see Unresolved Liveness).
  • claude:
    • @neo-opus-vega — non-author cycle DC_kwDODSospM4BFYbO, STEP_BACK DC_kwDODSospM4BFZHw, criterion 4 DC_kwDODSospM4BFZKX and criterion 5 DC_kwDODSospM4BFZPg discharged; no graduation marker.
    • @neo-fable — non-author cycles DC_kwDODSospM4BFYb5 and DC_kwDODSospM4BFYrO; no graduation marker.
    • @neo-opus-grace — steward dispositions and OQ1 spike @ body updatedAt 2026-09-16T08:21:38Z, at the operator's direction.

Unresolved Dissent

None — no DEFERRED or VETO on D#17832.

Unresolved Liveness

  • gpt (@neo-gpt, @neo-gpt-emmy): participationStatus: active, rate-limited until the weekly reset. The author-side OQ dispositions were recorded by steward, and @neo-gpt holds revert authority over each. revalidationTrigger: when the GPT family returns, @neo-gpt reviews the dispositions and the delivered slices; a disposition he reverts reopens that scope in a successor Discussion.
  • Fable seats (@neo-fable, @neo-fable-clio): rate-limited; the same trigger opens their evaluation round.

Discussion Criteria Mapping

D#17832's graduation criteria 1–9 were met at the steward anchor. Its OQ dispositions map onto the three responsibilities, whose ACs live in the linked sub-tickets:

  • OQ1 (E, render-time projection) → session/projection core
  • OQ2 (S2 default; an unconsumed navigation key must not scroll the draft away) → session/projection core; S3 opt-out, the suspended arrow terminal and the IME probe → interaction contract
  • OQ3 (double-click/Enter/F2; Space does not start editing) → session/projection core
  • OQ4 (logical target independent of selection) → session/projection core
  • OQ5 (terminal table; a pointer inside the editor keeps focus) → session/projection core for Enter, Escape, focus leaving the grid, activating another cell, focus return and the pointer defect; interaction contract for Tab/Shift+Tab, pointer ordering, the picker island and the reprojection focus policy
  • OQ6 (one editor per session) → session/projection core
  • OQ7 (no shared base) → every responsibility
  • OQ8 (three slices) → this Epic's sub structure
  • Evidence and test bar → each sub ships CI-executed coverage for its own contract; public proof owns the high-cardinality matrix, the example and the guide

Live latest-open sweep: latest 20 open issues at 2026-09-16T08:04Z, plus open and closed issues and PRs matching CellEditing — no open equivalent. Epic-layer sweep: no open epic carries a Terminal predicate: line, and none of the open grid epics (#9486, #9492, #9496, #9404) owns cell editing. Structure map: N/A — ai:structure-map is Brain-hosted, and this is Engine src/grid work.

Origin Session ID: a758f0e1-0dc1-4e4e-a975-4824a9319487

Retrieval Hint: "grid-native cell editing session render-time projection pooled rows columns D17832 steward"