Context
Manual UI verification on 2026-07-15 exposed an intermittent browser focus rectangle after clicking a Grid row. The symptom is pointer-path dependent and appears after the multi-body ownership changes: a logical Grid can render start, center, and end bodies, but each physical body remains a focus target.
Live latest-open sweep: checked the latest 20 open issues plus targeted Grid/focus results at 2026-07-15T15:04:18Z; no equivalent leaf exists. The closest active scope is the keyboard-navigation epic #9496.
The Problem
Every row click currently focuses the physical grid.Body that received the event. Browser focus heuristics can therefore expose a user-agent outline around an individual body after pointer interaction. The result is intermittent across browsers and input modality, which makes the Grid look broken even though row selection still fires.
Hiding every body outline unconditionally would fix only the visible symptom. It would also erase the intentional focus indication keyboard users need and leave focus ownership split across three render delegates while selection ownership is already centralized.
The Architectural Reality
On current dev at 0ee3f3af:
src/grid/Body.mjs:240-241 gives every physical body tabIndex: '-1'.
src/grid/Body.mjs:1231-1235 calls focus(bodyId, false, true) on every row click before forwarding the row event.
src/grid/View.mjs:46-51 and src/grid/View.mjs:82-97 define grid.View as the single SelectionModel owner across all bodies.
resources/scss/src/grid/View.scss:9-10 suppresses the View outline, while the actually focused Body has no equivalent focus contract.
History confirms the ownership split. Commit 830bbb5 added Body focus with scroll prevention for keyboard navigation. Commit 05a9750 moved the old body-wrapper focus rule to View; 3b0d468 then removed the Body wrapper class. Finally, f69b56a centralized SelectionModel ownership in View while explicitly leaving keyboard migration for follow-up work.
The Fix
Align focus and key-navigation ownership with the existing View-owned SelectionModel contract:
- Make
grid.View the single logical focus/key registry for a multi-body Grid.
- Keep
bodyStart, body, and bodyEnd as physical render/event delegates.
- Route pointer-origin row activation through the logical owner without displaying an accidental body-sized user-agent ring.
- Preserve an intentional, modality-aware visible focus indication for keyboard entry.
- Preserve the existing no-scroll focus behavior and single row-selection transition.
- Add unit and whitebox E2E coverage for all three physical bodies and both pointer and keyboard modalities.
The implementation may retain a physical DOM focus anchor where browser mechanics require one, but the public contract and navigation state must remain View-owned and body-agnostic.
Contract Ledger
| Target surface |
Source of authority |
Proposed behavior |
Fallback |
Docs |
Evidence |
| Row pointer activation |
grid.Body.onRowClick() event delegation plus View-owned SelectionModel |
One logical focus owner; no body-sized UA ring; one selection transition |
None |
Update affected JSDoc |
Whitebox clicks in start, center, and end bodies |
| Grid keyboard entry |
grid.View focus/key ownership |
Intentional visible keyboard focus and working Up/Down navigation from every body |
None |
Update ownership comments/JSDoc |
Real keyboard-modality E2E with :focus-visible observation |
| Scroll stability |
Existing focus(..., preventScroll) behavior |
Focus transfer does not move the scroll position |
Preserve current behavior |
N/A |
Before/after scroll assertions |
Decision Record impact
None. This is a bounded regression leaf under the accepted multi-body Grid and keyboard-navigation direction.
Acceptance Criteria
Out of Scope
- Full CellModel Left/Right navigation across locked-body boundaries.
- Cross-window SubGrid navigation.
- Reworking row or cell selection semantics.
- General application theme focus styling.
Avoided Traps
- No unconditional
.neo-grid-body:focus { outline: none; } symptom patch.
- No independent KeyNavigation or SelectionModel clone per body.
- No Playwright
.focus() call as a substitute for the real pointer path.
- No body-specific branching that encodes start/center/end topology in the logical selection model.
Related
- Parent epic: #9496
- Multi-body architecture: #9486
- Closed focus-behavior precedent:
#8980
- Row/cell focus styling context:
#6323
Retrieval Hint: "grid Body pointer focus View SelectionModel multi-body focus-visible 830bbb5 05a9750 3b0d468 f69b56a"
Context
Manual UI verification on 2026-07-15 exposed an intermittent browser focus rectangle after clicking a Grid row. The symptom is pointer-path dependent and appears after the multi-body ownership changes: a logical Grid can render start, center, and end bodies, but each physical body remains a focus target.
Live latest-open sweep: checked the latest 20 open issues plus targeted Grid/focus results at 2026-07-15T15:04:18Z; no equivalent leaf exists. The closest active scope is the keyboard-navigation epic #9496.
The Problem
Every row click currently focuses the physical
grid.Bodythat received the event. Browser focus heuristics can therefore expose a user-agent outline around an individual body after pointer interaction. The result is intermittent across browsers and input modality, which makes the Grid look broken even though row selection still fires.Hiding every body outline unconditionally would fix only the visible symptom. It would also erase the intentional focus indication keyboard users need and leave focus ownership split across three render delegates while selection ownership is already centralized.
The Architectural Reality
On current
devat0ee3f3af:src/grid/Body.mjs:240-241gives every physical bodytabIndex: '-1'.src/grid/Body.mjs:1231-1235callsfocus(bodyId, false, true)on every row click before forwarding the row event.src/grid/View.mjs:46-51andsrc/grid/View.mjs:82-97definegrid.Viewas the single SelectionModel owner across all bodies.resources/scss/src/grid/View.scss:9-10suppresses the View outline, while the actually focused Body has no equivalent focus contract.History confirms the ownership split. Commit
830bbb5added Body focus with scroll prevention for keyboard navigation. Commit05a9750moved the old body-wrapper focus rule to View;3b0d468then removed the Body wrapper class. Finally,f69b56acentralized SelectionModel ownership in View while explicitly leaving keyboard migration for follow-up work.The Fix
Align focus and key-navigation ownership with the existing View-owned SelectionModel contract:
grid.Viewthe single logical focus/key registry for a multi-body Grid.bodyStart,body, andbodyEndas physical render/event delegates.The implementation may retain a physical DOM focus anchor where browser mechanics require one, but the public contract and navigation state must remain View-owned and body-agnostic.
Contract Ledger
grid.Body.onRowClick()event delegation plus View-owned SelectionModelgrid.Viewfocus/key ownership:focus-visibleobservationfocus(..., preventScroll)behaviorDecision Record impact
None. This is a bounded regression leaf under the accepted multi-body Grid and keyboard-navigation direction.
Acceptance Criteria
bodyStart, centerbody, orbodyEndresolves to one logical View-owned focus state.document.activeElement, selection transitions, focus modality, and scroll stability.Out of Scope
Avoided Traps
.neo-grid-body:focus { outline: none; }symptom patch..focus()call as a substitute for the real pointer path.Related
#8980#6323Retrieval Hint: "grid Body pointer focus View SelectionModel multi-body focus-visible 830bbb5 05a9750 3b0d468 f69b56a"