LearnNewsExamplesServices
Frontmatter
id15195
titleAlign Grid focus ownership after the multi-body split
stateOpen
labels
bugairefactoringtestingregressiongrid
assignees[]
createdAtJul 15, 2026, 5:05 PM
updatedAtJul 15, 2026, 5:05 PM
githubUrlhttps://github.com/neomjs/neo/issues/15195
authorneo-gpt
commentsCount0
parentIssue9496
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]

Align Grid focus ownership after the multi-body split

Open Backlog/active-chunk-6 bugairefactoringtestingregressiongrid
neo-gpt
neo-gpt commented on Jul 15, 2026, 5:05 PM

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:

  1. Make grid.View the single logical focus/key registry for a multi-body Grid.
  2. Keep bodyStart, body, and bodyEnd as physical render/event delegates.
  3. Route pointer-origin row activation through the logical owner without displaying an accidental body-sized user-agent ring.
  4. Preserve an intentional, modality-aware visible focus indication for keyboard entry.
  5. Preserve the existing no-scroll focus behavior and single row-selection transition.
  6. 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

  • Clicking a row in bodyStart, center body, or bodyEnd resolves to one logical View-owned focus state.
  • No physical Body displays an accidental user-agent focus rectangle after pointer-origin activation.
  • A keyboard-origin focus path retains an intentional visible focus indication; the fix does not globally suppress focus outlines.
  • Row selection fires exactly once from all three body regions.
  • ArrowUp and ArrowDown continue to change logical selection after activation in any body.
  • Focus transfer preserves scroll position.
  • Unit coverage proves View is the sole focus/key registry and bodies remain delegates.
  • Whitebox E2E uses real pointer clicks and keyboard input, asserts document.activeElement, selection transitions, focus modality, and scroll stability.
  • Existing single-body Grid behavior remains unchanged.

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"