Skip to content

Unify cell detail, rows and documentation in a foldable desktop right inspector #488

Description

@BorisTyshkevich

Depends on: #425

Coordinate with: #487 so both docked sides share one centre-width policy and resize lifecycle.

Goal

Replace the current collection of unrelated desktop right-side overlays and panes with one foldable, shell-owned right inspector for contextual content:

  • cell detail;
  • rows/detail viewer content;
  • function, aggregate-function, data-type, and other Reference documentation;
  • future contextual diagnostic/detail tools that belong in the same right-side surface.

On desktop, the inspector is docked beside the Query/Dashboard surface, consumes normal layout width, and pushes the centre surface left. It never covers the editor or Dashboard with a backdrop.

Mobile keeps its existing mobile/full-screen/modal presentation where needed and is not redesigned by this issue.

Status

Architecture is decided — vanilla rendering per docs/ADR-0004-ui-shell.md (#577/#578 closed). Implementation is routed through #586, which already delivers most of this issue's shell skeleton: the docked inspectorHost slot in .main-row, the shared SurfaceLifecycle open/close/resize/Escape helper, and the rightInspectorPx width-preference migration. This issue is the product contract that host carries — tool registry, per-tool validity/generation rules, tool navigation, accessibility, and mobile behavior — which #586 deliberately does not define (see #586's non-goals: tool registry/tabs, contextual auto-open triggers, chevron UI).

Final desktop shell geometry

The application header remains full width above the shell.

[right inspector folded]
[left navigation] [main Query/Dashboard surface] [chevron]

[right inspector open]
[left navigation] [main Query/Dashboard surface] [chevron] [right inspector]

[left focused drawer and inspector open]
[rail] [left focused drawer] [main surface] [chevron] [right inspector]

On desktop: exactly one right-inspector host exists per application shell/document; no inspector tool creates a backdrop, covers the centre surface, or traps focus; only one inspector tool is visible at a time; valid inactive tool state may remain cached for the session.

Fold control

Use one narrow vertical chevron attached to the inspector's left boundary.

folded: chevron points left, meaning open
open:   chevron points right, meaning fold closed

The chevron remains visible at the right edge when folded and moves with the inspector's left edge when open. Toggling it does not discard valid active-tool state; reopening restores the most recently active still-valid tool; contextual triggers still open and activate the appropriate tool automatically. Do not add redundant top-bar open controls solely for panel visibility, and remove redundant per-tool desktop X controls where the panel-level fold control replaces them. The accessible label reflects the current action, such as Open details panel or Close details panel.

Tool registry and ownership

Use a narrow typed registry, initially:

type RightInspectorToolId =
  | 'cell'
  | 'rows'
  | 'reference';

Conceptual shell-facing operations:

interface OpenRightInspectorRequest<T = unknown> {
  tool: RightInspectorToolId;
  payload: T;
  initiator?: Element | null;
}

openRightInspector(request: OpenRightInspectorRequest): void;
activateRightInspectorTool(tool: RightInspectorToolId): void;
foldRightInspector(): void;
unfoldRightInspector(): void;
closeRightInspectorTool(tool: RightInspectorToolId): void;

The exact API may evolve as it lands on #586's SurfaceLifecycle + docked slot, but the ownership boundary remains:

Shell owns: the single inspector host (#586: inspectorHost); fold/open geometry (#586: hidden-state + resize handle); active-tool selection; tool selector chrome; width preference and resize separator (#586: rightInspectorPx + compatibility migration); interaction with left navigation and the centre minimum; high-level teardown on workspace replacement and sign-out.

Tool controllers own: typed payloads; content/domain rendering state; result or catalog validity; async generation tokens; tool-specific back stacks, embedded viewers, and actions; initiator metadata used for safe focus restoration.

Feature call sites request a tool through the shell-facing API. They must not append independent desktop panels/backdrops or write shell widths.

Each inspector subtree has one owner. Do not leave parallel independent inspector implementations active behind different triggers.

Tool navigation

When more than one valid tool has session content, show compact accessible tabs or an equivalent selector:

Cell | Rows | Reference

Rules: only tools with live/available content appear; opening a tool unfolds and activates it; a new Cell request replaces the Cell payload rather than adding another Cell tab; a new Reference target replaces the current target while preserving Reference's own valid back stack semantics; switching tools does not destroy valid inactive tool state; closing one tool activates the most recently used valid remaining tool; if no valid tool remains, fold the inspector; the fold chevron remains panel-level chrome, not a tool tab.

Cell tool

Clicking a supported result cell opens/unfolds Cell, preserving formatting, copy, and structured-value rendering. Bind validity to a stable result-generation token plus tab/query identity: result replacement, rerun, tab closure, workspace switch, or sign-out invalidates stale Cell state, and stale asynchronous formatting/render work cannot repaint a newer payload. No desktop backdrop or focus trap; opening Cell does not unnecessarily steal editor selection; folding/reopening preserves Cell only while its origin remains valid. A stale cell must never appear as though it belongs to the current result.

Rows/detail tool

Migrate passive rows/detail consumers that currently share cell-drawer chrome, preserving table/detail rendering, paging, copy, and result semantics. Bind validity to the originating result generation: result replacement, tab closure, workspace switch, or sign-out invalidates it. One Rows tool instance exists per shell and a new request replaces its payload. Keep truly modal decisions modal; move only passive inspection content into the inspector.

Reference tool

Preserve the existing mature behavior: loading, found, missing, unavailable, and Retry states; related/alias navigation and disambiguation; a bounded back stack; safe Markdown rendering; embedded code-viewer lifecycle; stale lookup generation protection; catalog capability semantics; contextual opening from F1, hover, completion, schema references, and future Reference actions.

Reference validity differs from result-bound tools: Query-tab switching does not invalidate it; Query/Dashboard switching may preserve it; workspace changes preserve it only when connection/catalog identity and target remain valid; connection change, catalog invalidation, or sign-out closes it and destroys embedded viewers; Reference back-stack state remains owned by the Reference controller, not by the shell.

Width and preference migration

#586 delivers this migration as shared shell substrate: the inspectorHost slot, its resize handle, and the canonical rightInspectorPx write.

Suggested bounds remain subject to real-browser validation:

const RIGHT_INSPECTOR_DEFAULT_PX = 480;
const RIGHT_INSPECTOR_MIN_PX = 320;
const RIGHT_INSPECTOR_MAX_VIEWPORT_RATIO = 0.55;
const MAIN_SURFACE_MIN_PX = 640;

Final clamping must account for viewport width, #487 left-navigation presentation, active centre surface, and the useful centre minimum. A temporary viewport clamp must not destroy the saved preferred inspector width.

Replace separate browser preferences (cellDrawerPx, docPanePx) with rightInspectorPx. Compatibility read order: valid rightInspectorPx, then valid docPanePx, then valid cellDrawerPx, then default. Write only rightInspectorPx after migration. Width is a browser preference and must not enter workspace storage, portable bundles, or share links. Fold state and active payload are session state.

The left-edge separator must support pointer and keyboard resizing and integrate with #586's shell geometry. Do not use arbitrary delayed resize events.

Surface, workspace, and teardown behavior

Query -> Dashboard: preserve valid Reference; invalidate or hide Cell/Rows according to result ownership; never destroy Query merely to show/hide the inspector; reflow Dashboard when the inspector opens, folds, or resizes.

Dashboard -> Query: preserve valid Reference; restore Cell/Rows only if the same originating result is still valid; reflow CodeMirror/results through normal layout hooks.

Workspace switch: invalidate all result-bound tools; clear initiators belonging to the old workspace/shell; preserve Reference only when explicitly safe; stale callbacks from the previous workspace cannot repaint the inspector.

Sign-out/auth loss: invalidate every tool; destroy embedded viewers and listeners; cancel active resize and pending structural transition work; remove/fold inspector content before Login; late callbacks cannot remount it.

Focus and Escape contract

The desktop inspector is non-modal.

  • focus moves normally between inspector, editor, and results; there is no focus trap;
  • Escape while focus is inside the inspector folds it, consumed so it does not also cancel a query; Escape outside keeps existing global semantics;
  • after a fold/unfold/tool-switch completes, focus settles on a visible, sensible control — the chevron when folding, the newly active tool's content/tab when unfolding or switching, or the initiator when a tool activated in response to one;
  • the chevron, tabs, and separator remain keyboard reachable with visible focus;
  • transient intermediate frames (mid-resize, mid-fold) are unspecified — no requirement to preserve focus mid-gesture; a transient focus on <body> during a drag is acceptable;
  • teardown (workspace switch, sign-out, unmount) must dispose pending focus/transition work — the only disposal rule.

The implementation builds on #586's SurfaceLifecycle (escapePolicy, returnFocusTo) rather than a bespoke per-tool mechanism.

Accessibility

Inspector host:

role="complementary"
aria-label="Details and reference"

Tool selector: proper tabs/tablist semantics when tabs are used; active state is announced; unavailable tools are absent rather than disabled placeholders.

Resize separator:

role="separator"
aria-orientation="vertical"
aria-valuemin / aria-valuemax / aria-valuenow

Keys: Left/Right Arrow resize by a small step; Shift+Left/Right resize by a larger step; Home uses minimum width; End uses the current maximum width.

Chevron direction, visible state, and accessible label remain synchronized.

Mobile

At the existing mobile breakpoint: do not apply desktop docked geometry; retain existing mobile/full-screen/modal cell and detail behavior where required; Reference may use the established mobile pane pattern; reuse domain/tool controllers where practical without forcing one desktop/mobile renderer; preserve desktop width preference without narrowing mobile permanently.

Main-surface reflow

Opening, folding, tool replacement, or resizing must correctly reflow CodeMirror/editor measurement, result columns and virtualized rows, editor/results split layout, Dashboard grids/flow layouts/charts/sticky bars, and detached/fullscreen views where applicable. Presentation changes must not cancel a running query or Dashboard execution.

Implementation sequence

Building on #586's docked host and SurfaceLifecycle:

  1. Move Reference into the host while preserving its mature behavior.
  2. Move Cell and remove its desktop backdrop/focus trap.
  3. Move Rows/detail consumers.
  4. Remove obsolete independent desktop insertion, width writes, backdrops, close controls, and renderer/lifecycle paths not already deleted by refactor(shell): one SurfaceLifecycle primitive and a docked right-inspector slot #586.
  5. Keep mobile-specific composition behind the existing breakpoint.
  6. Verify simultaneous left/right docking and Query/Dashboard behavior.
  7. Record production LOC removed/added and bundle delta in the PR description.

Tests

Shell and fold state: one inspector host per shell/document; folded state leaves only the chevron and consumes no panel width; open state reduces centre width; chevron direction and label are correct; folding preserves valid tool state; no desktop backdrop or focus trap; left and right panels coexist within centre-width limits.

Tool selection and validity: Cell/Rows/Reference open, unfold, and activate correctly; replacing one payload does not destroy other valid tool state; closing one activates the most recent valid remaining tool; no valid tools folds the inspector; duplicate independent panels cannot be created; result rerun/tab/workspace removal invalidates Cell/Rows; stale async work cannot repaint; Reference back stack, aliases, disambiguation, Retry, and embedded-viewer disposal remain correct.

Width and migration: rightInspectorPx restores and clamps; compatibility reads docPanePx, then cellDrawerPx; only the unified key is written; viewport clamping does not overwrite preferred width; pointer/keyboard resize behavior matches; cancellation, blur, remount, and sign-out leave no listeners or stale work.

Focus, accessibility, and reflow: Escape inside folds only the inspector and does not cancel a query; safe initiator restoration and fallbacks are proven in Chromium and WebKit (final settled state only); complementary, tabs, separator, and chevron semantics are correct; editor/results/Dashboard reflow correctly; opening/folding never cancels execution; Query state survives Dashboard round trips; no mobile, detached, or fullscreen regression.

Implementation notes: mid-resize/mid-fold focus racing is guidance from #586's SurfaceLifecycle, not an acceptance gate here.

Acceptance criteria

  1. Desktop has one foldable right inspector for Cell, Rows/detail, and Reference.
  2. The inspector is docked, consumes layout width, and never overlays the desktop editor/Dashboard.
  3. One chevron controls the panel and redundant desktop panel controls are removed.
  4. Contextual triggers automatically open the correct tool.
  5. Result-bound tools cannot display stale content.
  6. Reference retains navigation, async safety, and embedded-viewer lifecycle.
  7. Width is unified into one browser preference with deterministic migration.
  8. Left navigation, Query, and Dashboard share a coherent width/reflow policy.
  9. Mobile behavior remains appropriate and is not permanently narrowed.
  10. Focus and Escape behavior are safe, non-modal, and proven in Chromium and WebKit once a transition completes.
  11. Workspace switch and sign-out cannot leak tools, listeners, initiators, or stale callbacks.

Non-goals

  • Moving unrelated modal confirmations into the inspector.
  • Showing several inspector tools side by side.
  • Persisting inspector payloads in workspace data.
  • Redesigning Cell or Reference content beyond host integration.
  • Replacing detached/fullscreen views serving a distinct purpose.
  • Redesigning mobile navigation.
  • Requirements on transient mid-gesture focus behavior.

Inherited from #586 (phase 1 of #593) — two deliberate deferrals

#586 delivered this issue's substrate (the SurfaceLifecycle primitive and the docked
inspectorHost slot). Two focus/modality questions surfaced in review of PR #596 and were
deliberately deferred to this issue rather than settled there, because both need this issue's
fuller focus contract to resolve safely. Acceptance criteria 9 and 10 above already cover them;
this records the concrete starting state so it is not re-derived.

  1. Docked Cell and Rows use escapePolicy: 'always' while non-modal.
    src/ui/results.ts:506 and :1305. Because refactor(shell): one SurfaceLifecycle primitive and a docked right-inspector slot #586 deliberately dropped keyboard-owner
    acquisition on the docked path (the pre-refactor(shell): one SurfaceLifecycle primitive and a docked right-inspector slot #586 modal cell drawer disabled every app shortcut
    while open — src/ui/shortcuts.ts bails when ownsKeyboard(app)), Escape is captured by the
    inspector even when focus is in the editor or results, so it cannot reach the global
    running-query cancel action. tests/unit/results.test.ts currently locks that in.
    Reference already uses 'focus-inside' (src/ui/doc-pane.ts:204).

    Switching Cell/Rows to 'focus-inside' is the likely right answer, but it requires
    auto-focus-into-panel-on-open as well — otherwise "click a cell, immediately press Escape"
    stops working, since focus is still outside the panel. That auto-focus behaviour is part of
    this issue's contract, not refactor(shell): one SurfaceLifecycle primitive and a docked right-inspector slot #586's, which is why it was deferred.

  2. Mobile is full-screen but non-modal. src/styles.css:3339 turns .inspector-host into
    position: fixed; inset: 0; z-index: 60; width: 100vw below the mobile breakpoint, while the
    same docked code path acquires no keyboard owner. So a visually full-screen panel leaves
    hardware-keyboard application shortcuts live behind it. Pre-refactor(shell): one SurfaceLifecycle primitive and a docked right-inspector slot #586 the mobile cell drawer was
    full-screen and modal, so this is a real mobile behaviour change that shipped with refactor(shell): one SurfaceLifecycle primitive and a docked right-inspector slot #586
    even though mobile behaviour was one of its explicit non-goals.

    Resolve either by restoring modal ownership for the full-screen presentation, or by making
    the lifecycle presentation-aware (one policy for the desktop dock, another for the mobile
    full-screen surface) — the latter is probably the cleaner primitive extension, and
    SurfaceLifecycle's escapePolicy parameter is the natural place for it.

Both were reviewed and consciously accepted as #586's shipped state by the repo owner at that
PR's merge gate; neither is an oversight to rediscover.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions