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"
Terminal predicate: a grid with
cellEditing: trueedits 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 inNeo.gridextendsNeo.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.CellEditingis a 34-line subclass ofNeo.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 withcellEditing: trueand aCellModel: double-click, click+Enter and F2 mount no editor, and the double-click throwsCannot read properties of null (reading 'vdom'). The publicexamples/grid/cellEditingrenders no rows at all — itscountryRendererthrowsthis.getStateProvider is not a function.The mismatch is structural, not one bug: the inherited
mountEditorpasses a record into Grid'sgetCellId(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 acreateRowthatgrid.Bodydoes 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
grid.View:{recordId, dataField, editor, generation}. Physical row, cell and body ids are projections, never edit identity. Keys and focus return belong togrid.View; the edit target is independent of the selection model.{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.Neo.worker.App.getConfigs).buildScripts/util/e2eCiSelection.mjsdeselects every e2e spec whose source containsneuralLink.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
getCellIdshim, or hooks on the Table plugin (PR #18222): clears the first exception and makes the forbidden inheritance load-bearing.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:
up.ran with the column asthis, soexamples/grid/cellEditingrendered 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.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 ownmountedevent.Neo.main.DomAccessand 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 throughDC_kwDODSospM4BFZRg; no graduation marker (see Unresolved Liveness).claude:DC_kwDODSospM4BFYbO, STEP_BACKDC_kwDODSospM4BFZHw, criterion 4DC_kwDODSospM4BFZKXand criterion 5DC_kwDODSospM4BFZPgdischarged; no graduation marker.DC_kwDODSospM4BFYb5andDC_kwDODSospM4BFYrO; no graduation marker.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.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:
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 openepiccarries aTerminal predicate:line, and none of the open grid epics (#9486, #9492, #9496, #9404) owns cell editing. Structure map: N/A —ai:structure-mapis Brain-hosted, and this is Enginesrc/gridwork.Origin Session ID: a758f0e1-0dc1-4e4e-a975-4824a9319487
Retrieval Hint: "grid-native cell editing session render-time projection pooled rows columns D17832 steward"