Context
#9872 (Grid Multi-Body: 3-Tier Component Orchestration) identifies the "God Object" anti-pattern: Neo.grid.Container manually instantiates and orchestrates the split header toolbars (headerStart, headerToolbar, headerEnd) inline, mixing header concerns into the macro layout layer and forcing VDOM/selection-model thrash across unrelated domains. This ticket is the Tier-2 slice of that refactor — extract the header region into a dedicated orchestrator, mirroring how Neo.grid.View already owns the body region.
Live latest-open sweep: checked latest 20 open issues at 2026-06-09T00:09:51Z; no equivalent found (#9872 is the parent feature ticket, #9486 the epic, #9491/#9611 distinct multi-body siblings).
The Problem
All header-toolbar lifecycle + orchestration currently lives in grid.Container:
createOrUpdateSubGrids() creates/destroys headerStart/headerEnd (interleaved with the body sub-grids) and assembles them into a generic BaseContainer header wrapper.
onColumnsMutate() re-homes and re-orders the column header buttons across the three toolbars.
getButton() resolves a header button by dataField across the three toolbars.
The header wrapper is a bare container.Base with no header-specific identity, so header logic has no natural home and bleeds into the container.
The Architectural Reality
grid.View (src/grid/View.mjs) is already the dedicated body orchestrator (owns bodyStart/body/bodyEnd synchronization via syncBodies). The header side has no equivalent — it is a bare BaseContainer driven externally by Container.
- Every
headerStart/headerEnd reference is confined to three Container methods (createOrUpdateSubGrids, onColumnsMutate, getButton) — verified zero references elsewhere in src/ or test/.
neo-header-wrapper has no CSS-selector dependency (only the inline cls), so a dedicated class can preserve it as a baseCls with no style regression.
The Fix
Introduce src/grid/header/Wrapper.mjs — Neo.grid.header.Wrapper extends Neo.container.Base (ntype: 'grid-header-wrapper'), the header-side counterpart to grid.View. It owns:
headerStart / headerEnd configs + their create/destroy lifecycle.
updateHeaders(lockedStartButtons, centerButtons, lockedEndButtons) — centre-toolbar refresh + locked-toolbar lifecycle + left-to-right assembly.
applyColumnButtonOrder(lockedStartColumns, centerColumns, lockedEndColumns) — column-button re-homing/re-ordering.
getButton(dataField) — header-button lookup.
grid.Container becomes a thin delegator: createOrUpdateSubGrids calls headerWrapper.updateHeaders(...) (retaining only body creation), onColumnsMutate calls headerWrapper.applyColumnButtonOrder(...), getButton delegates. The headerStart/headerEnd configs move off Container entirely.
Decision Record impact
none — grid header-component orchestration is not ADR-governed (the multi-body SelectionModel design-lock is #9492, a design comment, not an ADR; unaffected here).
Acceptance Criteria
Out of Scope
- Tier-1 (
grid.Container → pure macro layout coordinator; drop body instantiation): separate follow-up.
- Tier-3 (body creation/lifecycle →
grid.View): separate follow-up; overlaps #12758's in-review createOrUpdateSubGrids changes, so sequenced after that merges.
- Full migration of the centre
headerToolbar reactive config off Container (it remains a Container config; the Wrapper places it).
Related
- Parent: #9872 (3-Tier Orchestration); epic #9486.
- #12758 (View-owned SelectionModel, in review) — touches the same
createOrUpdateSubGrids; this work's header/body split is sequenced to rebase cleanly after it merges.
- Siblings: #9491 (SortZone DnD), #9611 (native scrollbar).
Retrieval Hint: "grid.header.Wrapper Tier-2 extraction from Container God-Object"; branch claude/9872-header-wrapper.
Authored by Claude Opus 4.8 (Claude Code)
Context
#9872(Grid Multi-Body: 3-Tier Component Orchestration) identifies the "God Object" anti-pattern:Neo.grid.Containermanually instantiates and orchestrates the split header toolbars (headerStart,headerToolbar,headerEnd) inline, mixing header concerns into the macro layout layer and forcing VDOM/selection-model thrash across unrelated domains. This ticket is the Tier-2 slice of that refactor — extract the header region into a dedicated orchestrator, mirroring howNeo.grid.Viewalready owns the body region.Live latest-open sweep: checked latest 20 open issues at 2026-06-09T00:09:51Z; no equivalent found (
#9872is the parent feature ticket,#9486the epic,#9491/#9611distinct multi-body siblings).The Problem
All header-toolbar lifecycle + orchestration currently lives in
grid.Container:createOrUpdateSubGrids()creates/destroysheaderStart/headerEnd(interleaved with the body sub-grids) and assembles them into a genericBaseContainerheader wrapper.onColumnsMutate()re-homes and re-orders the column header buttons across the three toolbars.getButton()resolves a header button bydataFieldacross the three toolbars.The header wrapper is a bare
container.Basewith no header-specific identity, so header logic has no natural home and bleeds into the container.The Architectural Reality
grid.View(src/grid/View.mjs) is already the dedicated body orchestrator (ownsbodyStart/body/bodyEndsynchronization viasyncBodies). The header side has no equivalent — it is a bareBaseContainerdriven externally byContainer.headerStart/headerEndreference is confined to threeContainermethods (createOrUpdateSubGrids,onColumnsMutate,getButton) — verified zero references elsewhere insrc/ortest/.neo-header-wrapperhas no CSS-selector dependency (only the inlinecls), so a dedicated class can preserve it as abaseClswith no style regression.The Fix
Introduce
src/grid/header/Wrapper.mjs—Neo.grid.header.Wrapper extends Neo.container.Base(ntype: 'grid-header-wrapper'), the header-side counterpart togrid.View. It owns:headerStart/headerEndconfigs + their create/destroy lifecycle.updateHeaders(lockedStartButtons, centerButtons, lockedEndButtons)— centre-toolbar refresh + locked-toolbar lifecycle + left-to-right assembly.applyColumnButtonOrder(lockedStartColumns, centerColumns, lockedEndColumns)— column-button re-homing/re-ordering.getButton(dataField)— header-button lookup.grid.Containerbecomes a thin delegator:createOrUpdateSubGridscallsheaderWrapper.updateHeaders(...)(retaining only body creation),onColumnsMutatecallsheaderWrapper.applyColumnButtonOrder(...),getButtondelegates. TheheaderStart/headerEndconfigs move offContainerentirely.Decision Record impact
none— grid header-component orchestration is not ADR-governed (the multi-body SelectionModel design-lock is#9492, a design comment, not an ADR; unaffected here).Acceptance Criteria
src/grid/header/Wrapper.mjsexists as a dedicatedcontainer.Basesubclass owningheaderStart/headerEnd+ the three header-orchestration methods.grid.Containerno longer referencesheaderStart/headerEnddirectly (delegates viaheaderWrapper).grid.header.Wrapperregistered insrc/grid/header/_export.mjs.neo-header-wrappercls preserved (no style regression).GridScrollProfilepasses.Out of Scope
grid.Container→ pure macro layout coordinator; drop body instantiation): separate follow-up.grid.View): separate follow-up; overlaps#12758's in-reviewcreateOrUpdateSubGridschanges, so sequenced after that merges.headerToolbarreactive config offContainer(it remains aContainerconfig; the Wrapper places it).Related
createOrUpdateSubGrids; this work's header/body split is sequenced to rebase cleanly after it merges.Retrieval Hint: "grid.header.Wrapper Tier-2 extraction from Container God-Object"; branch
claude/9872-header-wrapper.Authored by Claude Opus 4.8 (Claude Code)