Context
Reported by the operator against apps/devindex: scrolling into new row ranges leaves the User and Impact columns showing the values from the first page. Reproduced in the GPU e2e harness on 2026-08-19.
Live latest-open sweep: latest 20 open issues checked at 20:56Z, no equivalent. A2A claim sweep over the last 12 messages: no overlapping lane.
The Problem
One row, read at three scroll positions, in the same run:
| position |
rank |
total |
login |
| page 1 |
1 |
19,876,461 |
laciferin2024Hiro |
| +100 rows |
105 |
214,342 |
laciferin2024Hiro |
| bottom |
2263 |
37,236 |
laciferin2024Hiro |
rank and total track the scroll exactly. login never changes. Rows 2–5 behave identically — the same five logins appear at rank 1, rank 105 and rank 2263.
The control is inside the row. Because rank and total update in the very same recycled row, this cannot be a scroll that did not happen or a store that did not page. Only some cells are stale.
The split is by column KIND, not by column. rank and total are plain/renderer columns and are correct. login is type: 'githubUser', which extends Neo.grid.column.Component. The operator also names Impact (type: 'component'); the sparkline, iconLink and countryFlag columns share the same base and should be assumed affected until measured.
Reproduction: load /apps/devindex/, read the first rows, wheel-scroll over .neo-grid-view (native scrollTop assignment is inert — the grid virtualizes by transform), read again. A diagnostic spec was used and deliberately not committed; the app is leaving this repository and its tests go with it.
The Architectural Reality
src/grid/column/Component.mjs#cellRenderer — produces or recycles the cell component. It opens with a short-circuit that returns the pooled component untouched when component[recordProperty] === record && component.lastRecordVersion === record.version. The reuse branch below it deletes className/module/ntype, maps undefined to null and calls component.set(...).
src/grid/Body.mjs — owns row pooling and the mounted range.
src/grid/column/GitHubUser.mjs, Sparkline.mjs, IconLink.mjs, CountryFlag.mjs, Progress.mjs — all extend the component column and inherit the path.
- Plain and renderer columns take a different, string-producing path and are correct, which is what makes the failure invisible in any grid without a component column.
Not yet established: whether the short-circuit fires wrongly, whether cellRenderer is reached at all on recycle, or whether the row hands the wrong component instance. The measurement above locates the class; it does not name the line. That work belongs to whoever takes this.
The Fix
Determine which of the three candidates above holds before changing anything, then make component-backed cells re-bind to the row's current record on recycle.
Note that useBindings is not the lever: Component.mjs documents those bindings as evaluated exactly once at instantiation and explicitly forbids reading record data through them, so enabling it would freeze values rather than refresh them.
Test Coverage Gap
No spec at any level exercised a component column under recycling, and none resized one either — test/playwright/unit/grid/ComponentColumnResizeSync.spec.mjs was added today to close the resize half. The scroll half is still open, and it is why this reached an operator: ColumnResizeCellSync and HeaderResizeRectSync both drive plain-column grids only.
Acceptance Criteria
Out of Scope
- Column resize on component columns — measured working in the GPU harness across six columns, and now unit-covered
apps/devindex itself; the app is leaving this repository
- The
neomjs/devindex repository's own grid faults, which trace to it running neo.mjs@13.1.0 without either resize fix
Related
- neomjs/neo#17289, neomjs/neo#17327 — the two resize defects, both verified only on plain-column grids
- neomjs/neo#17375 — the DevIndex extraction that surfaced this
Origin Session ID: 44746e37-a5f9-44c4-8c9d-f664247f0e38
Context
Reported by the operator against
apps/devindex: scrolling into new row ranges leaves the User and Impact columns showing the values from the first page. Reproduced in the GPU e2e harness on 2026-08-19.Live latest-open sweep: latest 20 open issues checked at 20:56Z, no equivalent. A2A claim sweep over the last 12 messages: no overlapping lane.
The Problem
One row, read at three scroll positions, in the same run:
ranktotalloginlaciferin2024Hirolaciferin2024Hirolaciferin2024Hirorankandtotaltrack the scroll exactly.loginnever changes. Rows 2–5 behave identically — the same five logins appear at rank 1, rank 105 and rank 2263.The control is inside the row. Because
rankandtotalupdate in the very same recycled row, this cannot be a scroll that did not happen or a store that did not page. Only some cells are stale.The split is by column KIND, not by column.
rankandtotalare plain/renderer columns and are correct.loginistype: 'githubUser', which extendsNeo.grid.column.Component. The operator also names Impact (type: 'component'); the sparkline, iconLink and countryFlag columns share the same base and should be assumed affected until measured.Reproduction: load
/apps/devindex/, read the first rows, wheel-scroll over.neo-grid-view(nativescrollTopassignment is inert — the grid virtualizes by transform), read again. A diagnostic spec was used and deliberately not committed; the app is leaving this repository and its tests go with it.The Architectural Reality
src/grid/column/Component.mjs#cellRenderer— produces or recycles the cell component. It opens with a short-circuit that returns the pooled component untouched whencomponent[recordProperty] === record && component.lastRecordVersion === record.version. The reuse branch below it deletesclassName/module/ntype, mapsundefinedtonulland callscomponent.set(...).src/grid/Body.mjs— owns row pooling and the mounted range.src/grid/column/GitHubUser.mjs,Sparkline.mjs,IconLink.mjs,CountryFlag.mjs,Progress.mjs— all extend the component column and inherit the path.Not yet established: whether the short-circuit fires wrongly, whether
cellRendereris reached at all on recycle, or whether the row hands the wrong component instance. The measurement above locates the class; it does not name the line. That work belongs to whoever takes this.The Fix
Determine which of the three candidates above holds before changing anything, then make component-backed cells re-bind to the row's current record on recycle.
Note that
useBindingsis not the lever:Component.mjsdocuments those bindings as evaluated exactly once at instantiation and explicitly forbids reading record data through them, so enabling it would freeze values rather than refresh them.Test Coverage Gap
No spec at any level exercised a component column under recycling, and none resized one either —
test/playwright/unit/grid/ComponentColumnResizeSync.spec.mjswas added today to close the resize half. The scroll half is still open, and it is why this reached an operator:ColumnResizeCellSyncandHeaderResizeRectSyncboth drive plain-column grids only.Acceptance Criteria
useBindingsanywhereapps/devindexOut of Scope
apps/devindexitself; the app is leaving this repositoryneomjs/devindexrepository's own grid faults, which trace to it runningneo.mjs@13.1.0without either resize fixRelated
Origin Session ID: 44746e37-a5f9-44c4-8c9d-f664247f0e38