You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Part of the ADR-0004 vanilla-shell investment track (see docs/ADR-0004-ui-shell.md); scoped from the 2026-08-03 architecture review.
Goal
Replace six copy-pasted overlay lifecycles with one ~60-line SurfaceLifecycle helper, and give .main-row a shell-owned docked right slot. This is deliberate prep that delivers most of #488's skeleton without committing to #488's full product contract yet.
Context
.main-row (src/ui/app-shell.ts:188) currently composes only sidebar, sideHandle, queryHost, and dashboardHost — there is NO right-hand slot. Three surfaces that conceptually belong on the right side are each independently mounted position:fixed overlays on document.body:
the global close-overlay shortcut (shortcuts.ts:53)
Five focus-restore patterns exist, and the cell drawer and rows viewer have NO focus restore at all.
Three places hard-code .cd-backdrop knowledge outside results.ts's own drawer builder: results.ts:1183, results.ts:1153, and a comment at app.ts:2163–2166.
drawer.ts shares only the cheap part (markup + drag) with these overlays, none of the lifecycle; schema-detail.ts:311–341 rolled its own drag handling because attachDrawerResize wasn't general enough to reuse.
Width preferences cellDrawerPx and docPanePx should collapse into one rightInspectorPx, with a compatibility read order (already specified in #488's body under "Width and migration").
Deliverables
SurfaceLifecycle helper (new, ~60 lines): idempotent single-funnel close(), optional keyboard-owner acquisition, capture-phase Escape with an explicit escapePolicy: 'always' | 'focus-inside' | 'none', and a returnFocusTo contract borrowing dialog-shell.ts:41–56's element-or-resolver design (a resolver is called at close time so it can hand back whatever is on screen now, rather than a possibly-detached captured element).
A docked inspectorHost plus its resize handle, added as siblings of queryHost/dashboardHost in app-shell.ts's mainRow composition (app-shell.ts:188), a new 'rightInspector' axis in splitters.ts, folded state expressed as hidden (the same pattern showHost already uses, app-shell.ts:323–327).
Fold the cell/rows/reference content builders into the new slot, built on SurfaceLifecycle.
Delete isTopDrawer (results.ts:1182–1185), the .cd-backdrop DOM probes (results.ts:1153, :1183), the .cd-backdrop CSS, the 'docPane' splitter branch, and drawer.ts's stateKey plumbing that only existed to support the old per-surface persistence.
Caveat — do not delete, rewrite: the header comments of drawer.ts, doc-pane.ts:1–33, and results.ts:481–482 document the current three-surface split as a deliberate architectural decision from #60/#101/#166/#313. This issue reverses that decision, so those comment blocks need to be rewritten to explain the new shared-lifecycle model, not silently deleted — a future reader must not conclude the split is still intentional.
Expected net: ~250–300 lines removed, ~120 added. Escape policies 5 → 1. Focus-restore patterns 5 → 2 (SurfaceLifecycle's, and whatever dialog-shell.ts/popover.ts keep for their own reasons if not yet unified).
Tests
SurfaceLifecycle unit tests at 100/95/90/100: idempotent close (double-close doesn't double-fire teardown or throw), each escapePolicy value in isolation, returnFocusTo element vs. resolver vs. null, keyboard-owner acquisition/release.
rightInspectorPx read/write: compatibility read order across cellDrawerPx → docPanePx → rightInspectorPx, single canonical write.
Fold state renders hidden and consumes no layout width; open state reduces centre width (mirrors showHost coverage).
Regression coverage that cell drawer, rows viewer, and Reference each still open/close/resize/Escape/focus-restore correctly once rebuilt on the shared primitive.
Existing results.ts, doc-pane.ts, detached-view.ts test files updated in place rather than duplicated.
Acceptance criteria
One SurfaceLifecycle helper exists and is the sole implementation backing cell drawer, rows viewer, and Reference pane lifecycle.
.main-row has a real inspectorHost slot as a layout sibling of queryHost/dashboardHost, not a position:fixed overlay.
isTopDrawer, the .cd-backdrop probes/CSS, the 'docPane' splitter branch, and drawer.ts's stateKey plumbing are deleted.
cellDrawerPx/docPanePx collapse into rightInspectorPx with a documented compatibility read order and no double-write.
Escape policy count is 1 (or an explicitly justified small number backed by SurfaceLifecycle's escapePolicy parameter), down from 5.
drawer.ts, doc-pane.ts, results.ts header comments describing the old three-surface split are rewritten to reflect the shared-lifecycle model.
Part of the ADR-0004 vanilla-shell investment track (see
docs/ADR-0004-ui-shell.md); scoped from the 2026-08-03 architecture review.Goal
Replace six copy-pasted overlay lifecycles with one ~60-line
SurfaceLifecyclehelper, and give.main-rowa shell-owned docked right slot. This is deliberate prep that delivers most of #488's skeleton without committing to #488's full product contract yet.Context
.main-row(src/ui/app-shell.ts:188) currently composes onlysidebar,sideHandle,queryHost, anddashboardHost— there is NO right-hand slot. Three surfaces that conceptually belong on the right side are each independently mountedposition:fixedoverlays ondocument.body:src/ui/results.ts:1258src/ui/results.ts(near-identical drawer,:484–530)src/ui/doc-pane.ts:214src/ui/detached-view.ts:153The open/close/resize/Escape/focus lifecycle is duplicated roughly six times (~330 lines total):
results.ts:1187–1262results.ts:484–530(near line-for-line clone of the cell drawer)doc-pane.ts:139–217detached-view.ts:128–156dialog-shell.ts:84–200andpopover.ts:100–196— the two BEST implementations in the repo, reused by nobody on the right sideFive different Escape policies coexist:
isTopDrawerDOM-scan of.cd-backdrop(results.ts:1182–1185, used at:1195)panel.contains(activeElement)(doc-pane.ts:205–212)close-overlayshortcut (shortcuts.ts:53)Five focus-restore patterns exist, and the cell drawer and rows viewer have NO focus restore at all.
Three places hard-code
.cd-backdropknowledge outsideresults.ts's own drawer builder:results.ts:1183,results.ts:1153, and a comment atapp.ts:2163–2166.drawer.tsshares only the cheap part (markup + drag) with these overlays, none of the lifecycle;schema-detail.ts:311–341rolled its own drag handling becauseattachDrawerResizewasn't general enough to reuse.Width preferences
cellDrawerPxanddocPanePxshould collapse into onerightInspectorPx, with a compatibility read order (already specified in #488's body under "Width and migration").Deliverables
SurfaceLifecyclehelper (new, ~60 lines): idempotent single-funnelclose(), optional keyboard-owner acquisition, capture-phase Escape with an explicitescapePolicy: 'always' | 'focus-inside' | 'none', and areturnFocusTocontract borrowingdialog-shell.ts:41–56's element-or-resolver design (a resolver is called at close time so it can hand back whatever is on screen now, rather than a possibly-detached captured element).inspectorHostplus its resize handle, added as siblings ofqueryHost/dashboardHostinapp-shell.ts'smainRowcomposition (app-shell.ts:188), a new'rightInspector'axis insplitters.ts, folded state expressed ashidden(the same patternshowHostalready uses,app-shell.ts:323–327).SurfaceLifecycle.isTopDrawer(results.ts:1182–1185), the.cd-backdropDOM probes (results.ts:1153,:1183), the.cd-backdropCSS, the'docPane'splitter branch, anddrawer.ts'sstateKeyplumbing that only existed to support the old per-surface persistence.Caveat — do not delete, rewrite: the header comments of
drawer.ts,doc-pane.ts:1–33, andresults.ts:481–482document the current three-surface split as a deliberate architectural decision from #60/#101/#166/#313. This issue reverses that decision, so those comment blocks need to be rewritten to explain the new shared-lifecycle model, not silently deleted — a future reader must not conclude the split is still intentional.Expected net: ~250–300 lines removed, ~120 added. Escape policies 5 → 1. Focus-restore patterns 5 → 2 (SurfaceLifecycle's, and whatever
dialog-shell.ts/popover.tskeep for their own reasons if not yet unified).Tests
SurfaceLifecycleunit tests at 100/95/90/100: idempotent close (double-close doesn't double-fire teardown or throw), eachescapePolicyvalue in isolation,returnFocusToelement vs. resolver vs. null, keyboard-owner acquisition/release.rightInspectorPxread/write: compatibility read order acrosscellDrawerPx→docPanePx→rightInspectorPx, single canonical write.hiddenand consumes no layout width; open state reduces centre width (mirrorsshowHostcoverage).results.ts,doc-pane.ts,detached-view.tstest files updated in place rather than duplicated.Acceptance criteria
SurfaceLifecyclehelper exists and is the sole implementation backing cell drawer, rows viewer, and Reference pane lifecycle..main-rowhas a realinspectorHostslot as a layout sibling ofqueryHost/dashboardHost, not aposition:fixedoverlay.isTopDrawer, the.cd-backdropprobes/CSS, the'docPane'splitter branch, anddrawer.ts'sstateKeyplumbing are deleted.cellDrawerPx/docPanePxcollapse intorightInspectorPxwith a documented compatibility read order and no double-write.SurfaceLifecycle'sescapePolicyparameter), down from 5.drawer.ts,doc-pane.ts,results.tsheader comments describing the old three-surface split are rewritten to reflect the shared-lifecycle model.npm test(coverage gate) andtsc --noEmitpass.Non-goals
RightInspectorToolId, contextual auto-open triggers, chevron UI, mobile behavior changes. This issue is the shell-primitive substrate Unify cell detail, rows and documentation in a foldable desktop right inspector #488 will build on, not Unify cell detail, rows and documentation in a foldable desktop right inspector #488 itself.detached-view.ts) into the docked inspector — it serves a distinct full-screen purpose per Unify cell detail, rows and documentation in a foldable desktop right inspector #488's non-goals.Related
docs/ADR-0004-ui-shell.md)