Context
Leaf of #18762, from the Fable evaluation round its Unresolved Liveness names. Measured on examples/grid/cellEditing at dev@3f859a4803 with a real pointer and keyboard. Text and number editors complete the whole loop; the date editor does not survive a pointer on its trigger.
Owner: @neo-fable (self-assigned 2026-09-18).
The Problem
Observed
Double-click a Random Date cell → the DateField editor mounts and takes focus. Click its picker trigger → the editor is destroyed, no picker stays, document.activeElement is body. A date can only be typed.
Measured mechanism (worker-side focus trace + a main-thread focusin/focusout/mutation trace)
The trigger click opens the picker, and the DateSelector takes focus. manager.Focus#focusMove reports the move as a leave for [datefield, row, body, view] and then an enter for [dateselector, picker]. The plugin commits on the editor's focusLeave, so completeEdit() destroys the editor and the picker with it, and focus falls to body.
A second defect sits behind the first. Once the session survives a picker visit, the editor's last focus enter came from the picker, so component.Base#revertFocus hands focus to a component that dies with the editor: ending the edit leaves focus on body instead of grid.View.
Corrected 2026-09-18: the first filing also listed "a ComboBox trigger click does not open the list". Under Playwright it does — 243 items, the input keeps focus, an item click selects, the session stays. That sighting was an artifact of the preview browser's click; the ComboBox keeps focus in its input, so it never crosses the island boundary. It stays here as a witness arm only.
The Architectural Reality
src/grid/plugin/CellEditing.mjs — editor.on('focusLeave', …) commits whenever the session is embodied.
src/manager/Focus.mjs — focusMove delivers the leave set before the enter set, synchronously; setComponentFocus writes containsFocus before it fires the events.
src/form/field/Picker.mjs — the picker container is parentId: 'document.body' with NO parentComponent. ComponentManager.getParentPath — the path manager.Focus works on — walks component.parent (parentComponent, else parentId), so the picker is a branch of its own, outside the editor's path. That is the root cause; button.Base, the tab overflow control and the grid component column give their floating parts parentComponent.
src/component/Base.mjs — revertFocus() reads focusEnterData.relatedTarget, overwritten on every focus enter.
- D#17832 OQ5, resolved and assigned to the interaction-contract responsibility, never filed as a leaf: "Focus leaves the editor/picker island → commit-if-valid", "Escape with picker open → close picker only".
The Fix
Revised 2026-09-18, on @tobiu's question on the PR (would editors carrying parent ids simplify it): in src/form/field/Picker.mjs, createPicker gives the picker parentComponent: me. Focus moving between input and picker is then a move inside the field — no focusLeave for the field, the Row or the grid, and focusEnterData keeps its origin — so the grid plugin needs no change, and the field's two scans of the focus payload (for the picker's id, and for its own) are deleted. Every picker field gains it, not only grid editors.
First prescription, built and approved before the question: a plugin-side island — the leave terminal deferred by a microtask and asking containsFocus on editor and picker, a listener on the picker, and a restored focus origin on the session. It worked around a picker that was nobody's child.
Acceptance Criteria
Out of Scope
Tab/Shift+Tab traversal, select-on-activation, S3 opt-out, the IME probe (remaining interaction-contract scope, #18861–#18865) · ComboBox on a bound store (#18839) · whether a picker field should open its picker on Enter while it is a grid editor — the picker still flashes into the DOM for ~9 ms on an Enter-commit.
Avoided Traps
- A timer, or a plugin-side "picker is open" flag, to skip the commit: the focus manager already knows where focus is; a second notion of "inside" drifts from it.
- Reading the leave event's
oldPath: for a focus move it carries the path focus went TO, for a plain leave the path it came FROM — one name, two meanings. containsFocus after the move has landed means one thing.
- Parenting the picker into the Row: the body clips it, and it dies with the pool slot.
Related
Parent #18762 · D#17832 (OQ5 terminal table) · #18764 · #18837 / PR #18842 (the spec these arms land in) · #17825 (outside-pointer dismissal for floating pickers, the neighbouring contract).
Live latest-open sweep: latest 20 open issues at 2026-09-18T10:27Z plus all-state searches for CellEditing / cell editing / picker — no equivalent. A2A sweep (latest 12, all read-states): no claim on this scope. Memory sweep: one hit, my own measurement checkpoint.
Origin Session ID: 323cb989-06a2-4b3f-8d70-912d67796c11
Retrieval Hint: "grid cell editing picker island focusLeave containsFocus DateField trigger destroys editor focus origin"
Context
Leaf of #18762, from the Fable evaluation round its Unresolved Liveness names. Measured on
examples/grid/cellEditingatdev@3f859a4803with a real pointer and keyboard. Text and number editors complete the whole loop; the date editor does not survive a pointer on its trigger.Owner: @neo-fable (self-assigned 2026-09-18).
The Problem
Observed
Double-click a
Random Datecell → the DateField editor mounts and takes focus. Click its picker trigger → the editor is destroyed, no picker stays,document.activeElementisbody. A date can only be typed.Measured mechanism (worker-side focus trace + a main-thread
focusin/focusout/mutation trace)The trigger click opens the picker, and the DateSelector takes focus.
manager.Focus#focusMovereports the move as a leave for[datefield, row, body, view]and then an enter for[dateselector, picker]. The plugin commits on the editor'sfocusLeave, socompleteEdit()destroys the editor and the picker with it, and focus falls tobody.A second defect sits behind the first. Once the session survives a picker visit, the editor's last focus enter came from the picker, so
component.Base#revertFocushands focus to a component that dies with the editor: ending the edit leaves focus onbodyinstead ofgrid.View.Corrected 2026-09-18: the first filing also listed "a ComboBox trigger click does not open the list". Under Playwright it does — 243 items, the input keeps focus, an item click selects, the session stays. That sighting was an artifact of the preview browser's click; the ComboBox keeps focus in its input, so it never crosses the island boundary. It stays here as a witness arm only.
The Architectural Reality
src/grid/plugin/CellEditing.mjs—editor.on('focusLeave', …)commits whenever the session is embodied.src/manager/Focus.mjs—focusMovedelivers the leave set before the enter set, synchronously;setComponentFocuswritescontainsFocusbefore it fires the events.src/form/field/Picker.mjs— the picker container isparentId: 'document.body'with NOparentComponent.ComponentManager.getParentPath— the pathmanager.Focusworks on — walkscomponent.parent(parentComponent, elseparentId), so the picker is a branch of its own, outside the editor's path. That is the root cause;button.Base, the tab overflow control and the grid component column give their floating partsparentComponent.src/component/Base.mjs—revertFocus()readsfocusEnterData.relatedTarget, overwritten on every focus enter.The Fix
Revised 2026-09-18, on @tobiu's question on the PR (would editors carrying parent ids simplify it): in
src/form/field/Picker.mjs,createPickergives the pickerparentComponent: me. Focus moving between input and picker is then a move inside the field — nofocusLeavefor the field, the Row or the grid, andfocusEnterDatakeeps its origin — so the grid plugin needs no change, and the field's two scans of the focus payload (for the picker's id, and for its own) are deleted. Every picker field gains it, not only grid editors.First prescription, built and approved before the question: a plugin-side island — the leave terminal deferred by a microtask and askingIt worked around a picker that was nobody's child.containsFocuson editor and picker, a listener on the picker, and a restored focus origin on the session.Acceptance Criteria
grid.View.Witness: a ComboBox editor's trigger opens its list inside the session, and Escape closes the list before it cancels.Moved to #18839: any arm opening the Country editor can red on that ticket's renderer error (reading 'name'of null, 1 of 4 runs), so the witness lands with its fix.devfirst; each half of the fix killed by a named mutation.grid.View(added 2026-09-18, once #18850's engine guard was ondev: the flow needs both fixes).Out of Scope
Tab/Shift+Tab traversal, select-on-activation, S3 opt-out, the IME probe (remaining interaction-contract scope, #18861–#18865) · ComboBox on a bound store (#18839) · whether a picker field should open its picker on Enter while it is a grid editor — the picker still flashes into the DOM for ~9 ms on an Enter-commit.
Avoided Traps
oldPath: for a focus move it carries the path focus went TO, for a plain leave the path it came FROM — one name, two meanings.containsFocusafter the move has landed means one thing.Related
Parent #18762 · D#17832 (OQ5 terminal table) · #18764 · #18837 / PR #18842 (the spec these arms land in) · #17825 (outside-pointer dismissal for floating pickers, the neighbouring contract).
Live latest-open sweep: latest 20 open issues at 2026-09-18T10:27Z plus all-state searches for
CellEditing/cell editing/picker— no equivalent. A2A sweep (latest 12, all read-states): no claim on this scope. Memory sweep: one hit, my own measurement checkpoint.Origin Session ID: 323cb989-06a2-4b3f-8d70-912d67796c11
Retrieval Hint: "grid cell editing picker island focusLeave containsFocus DateField trigger destroys editor focus origin"