Context
Found while building Tab traversal for grid cell editing (#18861), which scrolls its target into sight through the same primitive the arrow keys use. Measured on dev@f44ddcf81e, fixture test/playwright/component/apps/grid-cell-editing, grid #grid-cell-editing-pooled (40 columns of 150px, 1400px viewport), with no editing involved.
The Problem
Click cell r3c3, press ArrowRight twelve times. The selection moves to c15; the grid does not scroll: .neo-grid-horizontal-scrollbar stays at scrollLeft: 0, the selected column leaves the mounted window, and no cell carries neo-selected any more. The keyboard walks off-screen. ArrowDown scrolls as it should (scrollTop 636 after thirty presses).
The Architectural Reality
grid.Container#scrollByColumns (src/grid/Container.mjs:1340) ends in Neo.main.DomAccess.scrollTo({direction: 'left', id: me.getVdomRoot().id, …}) — it scrolls the container's root node. That was the horizontal scroll owner when #8982 / #8999 fixed this path in February. Since the multi-body architecture (#9612) the owner is the grid.HorizontalScrollbar the container creates (:304); the root no longer scrolls, so the call lands nowhere. grid.View#scrollByRows was not affected.
With id: me.horizontalScrollbar.id, the same twelve presses give scrollLeft: 1000 and r3c15 selected and rendered; thirty ArrowDowns after it keep r33c15 selected and rendered.
Measured once the scroll moved again (2026-09-18), two more defects in the same method: at scrollLeft: 1000 the selected r3c15 sits at x 1282–1432, entirely under the locked-end body (1280–1430); the center body shows 182–1280.
- The target is read as
body.columnPositions.getAt(index + step) with the selection model's index, which counts columns.items. columnPositions holds the center columns only, so every locked-start column shifts the target by one: the method positioned c16 for a selection on c15.
- It aligns a right-moving target to
body.containerWidth, the whole container's width, not to what the center body shows — the container less the two locked regions, which grid.HorizontalScrollbar carries as startWidth / endWidth.
The Fix
scrollByColumns resolves the target column in columns, skips a locked one (always in sight), reads its position by dataField, and scrolls the horizontal scrollbar only when the column lies outside [scrollLeft, scrollLeft + visible width]. The stale visibleColumns bookkeeping goes: grid.Body#afterSetScrollLeft recomputes it on every scroll. One Brain-free e2e spec on the pooled fixture.
Acceptance Criteria
Out of Scope
grid.Body#updateMountedAndVisibleColumns also counts the columns under the locked-end body as visible (it uses the same containerWidth) — harmless for mounting, noted · Tab traversal (#18861), which inherits this.
Related
#8982 · #8999 (the earlier fix of this path) · #9612 (the multi-body change after which the root stopped scrolling) · #18861.
Live latest-open sweep: latest 20 open issues at 2026-09-18T12:26Z plus all-state searches for scrollByColumns and grid arrow key horizontal scroll — no open equivalent (#8982 and #8999 are closed and describe the pre-multi-body fix). A2A sweep at 12:20Z: no claim on this scope.
Origin Session ID: 323cb989-06a2-4b3f-8d70-912d67796c11
Retrieval Hint: "grid scrollByColumns horizontalScrollbar keyboard navigation ArrowRight does not scroll regression multi-body"
Context
Found while building Tab traversal for grid cell editing (#18861), which scrolls its target into sight through the same primitive the arrow keys use. Measured on
dev@f44ddcf81e, fixturetest/playwright/component/apps/grid-cell-editing, grid#grid-cell-editing-pooled(40 columns of 150px, 1400px viewport), with no editing involved.The Problem
Click cell
r3c3, press ArrowRight twelve times. The selection moves toc15; the grid does not scroll:.neo-grid-horizontal-scrollbarstays atscrollLeft: 0, the selected column leaves the mounted window, and no cell carriesneo-selectedany more. The keyboard walks off-screen. ArrowDown scrolls as it should (scrollTop636 after thirty presses).The Architectural Reality
grid.Container#scrollByColumns(src/grid/Container.mjs:1340) ends inNeo.main.DomAccess.scrollTo({direction: 'left', id: me.getVdomRoot().id, …})— it scrolls the container's root node. That was the horizontal scroll owner when #8982 / #8999 fixed this path in February. Since the multi-body architecture (#9612) the owner is thegrid.HorizontalScrollbarthe container creates (:304); the root no longer scrolls, so the call lands nowhere.grid.View#scrollByRowswas not affected.With
id: me.horizontalScrollbar.id, the same twelve presses givescrollLeft: 1000andr3c15selected and rendered; thirty ArrowDowns after it keepr33c15selected and rendered.Measured once the scroll moved again (2026-09-18), two more defects in the same method: at
scrollLeft: 1000the selectedr3c15sits at x 1282–1432, entirely under the locked-end body (1280–1430); the center body shows 182–1280.body.columnPositions.getAt(index + step)with the selection model's index, which countscolumns.items.columnPositionsholds the center columns only, so every locked-start column shifts the target by one: the method positionedc16for a selection onc15.body.containerWidth, the whole container's width, not to what the center body shows — the container less the two locked regions, whichgrid.HorizontalScrollbarcarries asstartWidth/endWidth.The Fix
scrollByColumnsresolves the target column incolumns, skips a locked one (always in sight), reads its position bydataField, and scrolls the horizontal scrollbar only when the column lies outside[scrollLeft, scrollLeft + visible width]. The stalevisibleColumnsbookkeeping goes:grid.Body#afterSetScrollLeftrecomputes it on every scroll. One Brain-free e2e spec on the pooled fixture.Acceptance Criteria
devfirst.Out of Scope
grid.Body#updateMountedAndVisibleColumnsalso counts the columns under the locked-end body as visible (it uses the samecontainerWidth) — harmless for mounting, noted · Tab traversal (#18861), which inherits this.Related
#8982 · #8999 (the earlier fix of this path) · #9612 (the multi-body change after which the root stopped scrolling) · #18861.
Live latest-open sweep: latest 20 open issues at 2026-09-18T12:26Z plus all-state searches for
scrollByColumnsandgrid arrow key horizontal scroll— no open equivalent (#8982 and #8999 are closed and describe the pre-multi-body fix). A2A sweep at 12:20Z: no claim on this scope.Origin Session ID: 323cb989-06a2-4b3f-8d70-912d67796c11
Retrieval Hint: "grid scrollByColumns horizontalScrollbar keyboard navigation ArrowRight does not scroll regression multi-body"