Context
Column drag-to-reorder in the grid header broke after the multi-body flattened-DOM refactor (#9633 flattened DOM architecture + #9634 remove redundant grid wrapper node). Neo.draggable.grid.header.toolbar.SortZone was written when the grid had a single contiguous header toolbar and a single grid.body; the multi-body split (locked-start / center / locked-end bodies, each paired with its own header toolbar under a headerWrapper) invalidated those assumptions.
This is the urgent, release-blocking regression half of #9491 (the full multi-body SortZone overhaul). Per grid-finish coordination with @neo-opus-grace (grid-lead, operator-directed), the within-region regression fix is split out here so #9491 stays open for the cross-toolbar enhancement (which depends on the View-owned SM + #9872 redistribution/render).
Live latest-open sweep: checked the latest 20 open issues on 2026-06-08; no equivalent regression ticket found.
The Problem
SortZone resolved its grid Container via me.owner.parent and its cells via grid.body (the center body):
- After the split, the header toolbars are children of the
headerWrapper, so me.owner.parent is the headerWrapper — not the grid Container. Grid / columns / getDomRect access broke even in the common (no-locked-columns) case.
grid.body is only the center body. Locked-column cells live in bodyStart / bodyEnd, so grid.body.getColumnCells(dataField) returned empty for locked columns → wrong / empty drag proxy and broken cell hide / move.
Root cause confirmed statically: aria-colindex is assigned per-toolbar (src/grid/header/Toolbar.mjs:194) and columnPositions is per-Body, so a per-toolbar index lines up with its region body — the break was purely the single-body / owner.parent assumption. Live break-confirm + fix-verify is routed to @tobiu (headless DevIndex bootstrap is flaky for the agents; grid e2e is not CI-gated).
The Architectural Reality
src/draggable/grid/header/toolbar/SortZone.mjs — createDragProxy, onDragStart, switchItems, onDragEnd, moveTo all read me.owner.parent (grid) and grid.body (center body).
src/grid/Container.mjs (~823-906) creates headerStart (layoutLock: 'start') ↔ bodyStart, headerToolbar ↔ body, headerEnd (layoutLock: 'end') ↔ bodyEnd. Toolbars carry gridContainer + layoutLock configs (src/grid/header/Toolbar.mjs:29 / :43), and Toolbar.mjs:254-256 already resolves its region body via gridContainer + layoutLock.
- The body handles (
bodyStart / body / bodyEnd) stay on grid.Container as the durable contract (survives the #9872 Container→View ownership move; per @neo-opus-grace).
The Fix
In SortZone.mjs:
- Add a
gridBody getter resolving the region body via me.owner.gridContainer + me.owner.layoutLock ('start'→bodyStart, 'end'→bodyEnd, else→body), mirroring grid.header.Toolbar.
- Replace
me.owner.parent → me.owner.gridContainer (Container access) and center-only grid.body → me.gridBody across createDragProxy / onDragStart / switchItems / onDragEnd / moveTo.
- Add a CI-gated unit test for the
gridBody region resolution.
Acceptance Criteria
Out of Scope
- Cross-toolbar drag (center→start re-lock) and the redistribution / newly-locked-body render on drop-to-lock — owned by @neo-opus-grace's View-owned SM + #9872 (the #9491 enhancement).
- Locked-region column-index remap (toolbar-local ↔ global
columns index) in moveTo / onDragEnd — folds into the shared global↔region-local column-index map @neo-opus-grace is building for SM keynav (per grid-finish convergence); the no-locked common case is already correct.
Related
- #9491 — full multi-body SortZone overhaul (stays open for cross-toolbar)
- #9486 — Grid Multi-Body epic
- #9633 / #9634 — the flattened-DOM refactor that introduced the regression
Origin Session ID: 31325e4f-0fdf-4627-96ed-7983dd1b1002
Retrieval Hint: "multi-body grid SortZone column drag regression gridBody layoutLock"
Context
Column drag-to-reorder in the grid header broke after the multi-body flattened-DOM refactor (
#9633flattened DOM architecture +#9634remove redundant grid wrapper node).Neo.draggable.grid.header.toolbar.SortZonewas written when the grid had a single contiguous header toolbar and a singlegrid.body; the multi-body split (locked-start / center / locked-end bodies, each paired with its own header toolbar under aheaderWrapper) invalidated those assumptions.This is the urgent, release-blocking regression half of #9491 (the full multi-body SortZone overhaul). Per grid-finish coordination with @neo-opus-grace (grid-lead, operator-directed), the within-region regression fix is split out here so #9491 stays open for the cross-toolbar enhancement (which depends on the View-owned SM + #9872 redistribution/render).
Live latest-open sweep: checked the latest 20 open issues on 2026-06-08; no equivalent regression ticket found.
The Problem
SortZoneresolved its grid Container viame.owner.parentand its cells viagrid.body(the center body):headerWrapper, some.owner.parentis theheaderWrapper— not the grid Container. Grid /columns/getDomRectaccess broke even in the common (no-locked-columns) case.grid.bodyis only the center body. Locked-column cells live inbodyStart/bodyEnd, sogrid.body.getColumnCells(dataField)returned empty for locked columns → wrong / empty drag proxy and broken cell hide / move.Root cause confirmed statically:
aria-colindexis assigned per-toolbar (src/grid/header/Toolbar.mjs:194) andcolumnPositionsis per-Body, so a per-toolbar index lines up with its region body — the break was purely the single-body /owner.parentassumption. Live break-confirm + fix-verify is routed to @tobiu (headless DevIndex bootstrap is flaky for the agents; grid e2e is not CI-gated).The Architectural Reality
src/draggable/grid/header/toolbar/SortZone.mjs—createDragProxy,onDragStart,switchItems,onDragEnd,moveToall readme.owner.parent(grid) andgrid.body(center body).src/grid/Container.mjs(~823-906) createsheaderStart(layoutLock: 'start') ↔bodyStart,headerToolbar↔body,headerEnd(layoutLock: 'end') ↔bodyEnd. Toolbars carrygridContainer+layoutLockconfigs (src/grid/header/Toolbar.mjs:29/:43), andToolbar.mjs:254-256already resolves its region body viagridContainer+layoutLock.bodyStart/body/bodyEnd) stay ongrid.Containeras the durable contract (survives the #9872 Container→View ownership move; per @neo-opus-grace).The Fix
In
SortZone.mjs:gridBodygetter resolving the region body viame.owner.gridContainer+me.owner.layoutLock('start'→bodyStart,'end'→bodyEnd, else→body), mirroringgrid.header.Toolbar.me.owner.parent→me.owner.gridContainer(Container access) and center-onlygrid.body→me.gridBodyacrosscreateDragProxy/onDragStart/switchItems/onDragEnd/moveTo.gridBodyregion resolution.Acceptance Criteria
SortZoneresolves the grid Container viagridContainerand the region body vialayoutLock— noowner.parent/ hardcoded centergrid.body.gridBodyresolution for start / end / center regions and runs in the CIunitsuite.Out of Scope
columnsindex) inmoveTo/onDragEnd— folds into the shared global↔region-local column-index map @neo-opus-grace is building for SM keynav (per grid-finish convergence); the no-locked common case is already correct.Related
Origin Session ID: 31325e4f-0fdf-4627-96ed-7983dd1b1002 Retrieval Hint: "multi-body grid SortZone column drag regression gridBody layoutLock"