Context
Found during a manual v13.3 sweep of examples/grid/issues on dev af81fc0f86. Sort by Title, then by State: three headers now show a sort arrow (# descending, Title ascending, State ascending), but the Store sorts by State alone. The showcase locks # and Title at the start and State at the end, so every sort there leaves stale arrows behind.
The Problem
Neo.data.Store#sort keeps a single sorter (src/data/Store.mjs:1798). When a header sorts, grid.Container#onSortColumn clears every other header's sort state (src/grid/Container.mjs:1334). That clear, removeSortingCss (:1428), only walks the center headerToolbar. Buttons in the locked-start and locked-end toolbars keep their arrow and their isSorted.
Three grid-level header configs propagate along the same center-only path. afterSetShowHeaderFilters, afterSetSortable and afterSetUseTriStateSorting (:588, :600, :612) each assign only this.headerToolbar.<config>. A locked toolbar takes these configs only when it is cloned from the center one (#19772). After a runtime change, its buttons keep the old value: grid.sortable = false, for example, leaves the locked headers sortable. A locked toolbar created after the change reverts it too, because Neo.cloneNeoInstance starts from the center toolbar's construction configs (src/Neo.mjs:262). No shipped example toggles these configs on a grid with locked columns; the unit specs below are the witnesses.
The Architectural Reality
grid.header.Wrapper owns the locked toolbars headerStart and headerEnd, created on demand, beside the container's center headerToolbar. The left-to-right list of those three is rebuilt inline in three places: Wrapper#applyColumnButtonOrder (Wrapper.mjs:87), Wrapper#updateHeaders (:224–226) and Container#onColumnLockChange (Container.mjs:1155).
- Design authority: the code's own contract. The Store keeps one sorter, and
onSortColumn clears the sort state of every other header after store.sort(). Locked buttons fall outside that walk only because they live in other toolbars.
The Fix
Wrapper#getToolbars() returns the header toolbars in left-to-right order and skips absent regions. The three inline lists use it.
Container#removeSortingCss and the three afterSet* hooks walk getToolbars(), so every region gets the same state.
Wrapper#updateHeaders clones a locked toolbar with the center toolbar's current showHeaderFilters, sortable and useTriStateSorting.
Acceptance Criteria
Out of Scope
- The locked-start body seam in the issues showcase, captured as a separate defect-note.
- #19793's filtered-column header state. It needs the same all-region walk and can reuse
getToolbars().
Related
#19793 · #19772 · #19652
Live latest-open sweep: the latest 20 open issues at 2026-10-16T10:59Z, no equivalent. #19793 shares the surface, not the defect.
A2A claim sweep: the latest 30 messages, through 10:51Z. The only grid claim is Ada's #19793.
MC sweep: "grid locked column header sort arrow stays after sorting another column removeSortingCss headerStart headerEnd", 6 results. The only same-method hit is a February regression (an early return), since fixed; no prior decision on the locked regions.
Own-assignment sweep: 4 open (#19766, #19765, #19652, #15000), none overlapping.
Origin Session ID: bb2e8968-a6d6-4ef7-a4ed-5ab3157b1ac8
Retrieval Hint: "grid locked header toolbars stale sort arrow center-only headerToolbar walk getToolbars"
Context
Found during a manual v13.3 sweep of
examples/grid/issuesondevaf81fc0f86. Sort by Title, then by State: three headers now show a sort arrow (#descending, Title ascending, State ascending), but the Store sorts by State alone. The showcase locks#and Title at the start and State at the end, so every sort there leaves stale arrows behind.The Problem
Neo.data.Store#sortkeeps a single sorter (src/data/Store.mjs:1798). When a header sorts,grid.Container#onSortColumnclears every other header's sort state (src/grid/Container.mjs:1334). That clear,removeSortingCss(:1428), only walks the centerheaderToolbar. Buttons in the locked-start and locked-end toolbars keep their arrow and theirisSorted.Three grid-level header configs propagate along the same center-only path.
afterSetShowHeaderFilters,afterSetSortableandafterSetUseTriStateSorting(:588,:600,:612) each assign onlythis.headerToolbar.<config>. A locked toolbar takes these configs only when it is cloned from the center one (#19772). After a runtime change, its buttons keep the old value:grid.sortable = false, for example, leaves the locked headers sortable. A locked toolbar created after the change reverts it too, becauseNeo.cloneNeoInstancestarts from the center toolbar's construction configs (src/Neo.mjs:262). No shipped example toggles these configs on a grid with locked columns; the unit specs below are the witnesses.The Architectural Reality
grid.header.Wrapperowns the locked toolbarsheaderStartandheaderEnd, created on demand, beside the container's centerheaderToolbar. The left-to-right list of those three is rebuilt inline in three places:Wrapper#applyColumnButtonOrder(Wrapper.mjs:87),Wrapper#updateHeaders(:224–226) andContainer#onColumnLockChange(Container.mjs:1155).onSortColumnclears the sort state of every other header afterstore.sort(). Locked buttons fall outside that walk only because they live in other toolbars.The Fix
Wrapper#getToolbars()returns the header toolbars in left-to-right order and skips absent regions. The three inline lists use it.Container#removeSortingCssand the threeafterSet*hooks walkgetToolbars(), so every region gets the same state.Wrapper#updateHeadersclones a locked toolbar with the center toolbar's currentshowHeaderFilters,sortableanduseTriStateSorting.Acceptance Criteria
examples/grid/issues, after any header sort, exactly one header shows a sort arrow, whichever region holds it.test/playwright/unit/grid/LockedColumns.spec.mjs): sorting a center column clears theisSortedof a locked-start and a locked-end button and hides their arrows, and sorting a locked column clears the center's. Red ondev.test/playwright/unit/grid/LockedSubGridConfigs.spec.mjs): runtimesortable,useTriStateSortingandshowHeaderFiltersreach the locked toolbars and buttons, including a locked toolbar created after the change. Red ondev.Out of Scope
getToolbars().Related
#19793 · #19772 · #19652
Live latest-open sweep: the latest 20 open issues at 2026-10-16T10:59Z, no equivalent. #19793 shares the surface, not the defect. A2A claim sweep: the latest 30 messages, through 10:51Z. The only grid claim is Ada's #19793. MC sweep: "grid locked column header sort arrow stays after sorting another column removeSortingCss headerStart headerEnd", 6 results. The only same-method hit is a February regression (an early
return), since fixed; no prior decision on the locked regions. Own-assignment sweep: 4 open (#19766, #19765, #19652, #15000), none overlapping.Origin Session ID: bb2e8968-a6d6-4ef7-a4ed-5ab3157b1ac8 Retrieval Hint: "grid locked header toolbars stale sort arrow center-only headerToolbar walk getToolbars"