Skip to content

Add a drag-foldable desktop left navigation with rail and focused drawers #487

Description

@BorisTyshkevich

Depends on: #425, #426

Coordinate with: #427, which renames the lower Queries projection to Library and filters out Dashboard-owned queries.

Goal

Make the desktop left navigation foldable without adding collapse/expand buttons.

The user controls the navigation width by dragging its vertical frame border. A sufficiently narrow drag converts the current two-panel sidebar into a compact icon rail. Dragging the rail or its focused drawer wider converts it back into the normal two-panel sidebar.

The compact state must remain fully navigable: clicking a rail icon opens that section as a docked, full-height focused drawer beside the rail. The drawer consumes layout width and pushes the SQL/Dashboard work surface inward; it must not float over or obscure the work surface.

Mobile already uses a different navigation model and is explicitly out of scope.

Desktop shell geometry

The application header owns the complete top row across the viewport.

Below the header, the horizontal shell is:

wide state
  [two-panel sidebar] [main Query/Dashboard surface] [optional right inspector]

rail state, drawer closed
  [rail] [main Query/Dashboard surface] [optional right inspector]

rail state, focused drawer open
  [rail] [focused left drawer] [main Query/Dashboard surface] [optional right inspector]

The rail, sidebar, focused drawer, main surface and right inspector all begin below the header.

The left navigation must participate in normal layout. No left-navigation state uses an overlay or backdrop on desktop.

Two presentation states

Wide state

The compact rail is absent.

Show the established two stacked navigation panes:

upper pane
  Databases | Dashboards

lower pane
  Queries | History

After #427, the lower pane becomes:

Library | History

Requirements:

  • preserve the existing upper/lower pane switchers;
  • preserve the horizontal divider between the two panes;
  • preserve search, expansion, scroll and lazy-loaded schema state;
  • preserve the existing sidebar-width preference range unless a new central layout constraint requires a narrower maximum;
  • the vertical frame border at the sidebar's right edge is the only primary wide/rail control;
  • do not add chevron, hamburger, pin or >|< buttons for folding the left navigation.

Rail state

Show a narrow vertical rail below the header.

Recommended visual width:

48 px

The rail contains four section launchers:

Section Icon concept Accessible label
Databases database cylinder Open Databases navigation
Dashboards dashboard/grid Open Dashboards navigation
Queries/Library bookmark or saved document Open Queries navigation before #427, Open Library navigation after #427
History clock/history Open query History

Requirements:

  • use existing icon primitives where possible;
  • show tooltips with section names;
  • show hover, keyboard-focus and active-drawer states distinctly;
  • keep every rail icon visible when a focused drawer is open;
  • the rail width must remain fixed and must not accidentally stretch to the drawer width;
  • the rail has no internal text labels in its normal state;
  • the rail begins below the full-width application header.

Focused drawer behaviour

Clicking a rail section opens that section as a docked drawer immediately to the right of the rail.

Examples:

[rail: Databases active] [Databases drawer] [SQL editor]
[rail: Dashboards active] [Dashboards drawer] [Dashboard surface]
[rail: Library active]   [Library drawer]   [SQL editor]

The focused drawer:

  • uses the remaining vertical height below the header;
  • consumes normal layout width;
  • pushes the Query/Dashboard surface instead of covering it;
  • shows exactly one section;
  • contains that section's title, search and content;
  • does not repeat Databases | Dashboards or Queries/Library | History switcher buttons;
  • remains open after selecting a database, table, Dashboard, query or history item;
  • retains the section's expansion, search and scroll state;
  • uses the same application navigation commands as the wide sidebar;
  • never executes hidden Dashboards merely because the Dashboards section is visible.

Clicking the active rail icon closes the focused drawer and leaves the rail visible.

Clicking a different rail icon switches the drawer content in place without first closing the drawer.

Escape may close the focused drawer when focus is inside it, but must not also cancel a running query or trigger another global Escape action.

Section-specific behaviour

Databases

Show the existing schema search and database/table/column tree with existing click, double-click, Shift-click, drag and documentation actions unchanged.

Dashboards

Show the Dashboard hierarchy from #426 with its search, expansion, keyboard and navigation semantics unchanged.

Queries / Library

Before #427, show the existing Queries collection.

After #427, show only the zero-owner Library projection and update the rail label/tooltip to Library. The foldable shell must not introduce a separate data projection or duplicate ownership logic.

History

Show the existing history search/list and opening behaviour unchanged.

Drag-controlled state transitions

Use the vertical navigation frame border as a resize separator.

The transition must feel deliberate and must not flicker near a single threshold. Use named constants and hysteresis rather than deriving state directly from every pointer pixel.

Recommended starting values, adjustable only through real-browser layout verification:

const LEFT_RAIL_PX = 48;
const LEFT_FOLD_THRESHOLD_PX = 140;
const LEFT_WIDE_THRESHOLD_PX = 260;
const LEFT_PANEL_MIN_PX = 180;
const LEFT_PANEL_MAX_PX = 420;

Wide -> rail

While dragging the wide sidebar border left:

  • resize normally within the wide range;
  • once the proposed width passes the fold threshold, commit rail mode;
  • remember the last useful wide width;
  • remove the wide two-pane sidebar from layout and expose the fixed rail;
  • do not leave a partially clipped wide sidebar.

Rail -> wide

While dragging the rail's right frame border to the right:

  • show deterministic resize feedback;
  • once the drag passes the wide threshold, restore wide mode;
  • restore the last useful wide width, or the threshold width when no prior width exists;
  • the rail disappears completely in wide mode.

Focused drawer resize

When a focused drawer is open, its right border is the active navigation resize separator.

  • dragging resizes the focused drawer within the desktop bounds;
  • dragging sufficiently narrow folds the focused drawer closed, leaving the rail;
  • dragging beyond the wide threshold converts the navigation to the full two-pane wide sidebar;
  • conversion must preserve the currently selected upper/lower section and all section UI state.

Do not rely on CSS width alone to decide the semantic mode after every repaint. The mode is explicit application-shell state.

Layout state

Suggested shell state:

type LeftNavigationMode = 'wide' | 'rail';
type LeftNavigationSection = 'databases' | 'dashboards' | 'library' | 'history';

interface DesktopLeftNavigationState {
  mode: LeftNavigationMode;
  wideWidthPx: number;
  focusedDrawerWidthPx: number;
  focusedSection: LeftNavigationSection | null;
}

Persistence:

  • mode, wideWidthPx and focusedDrawerWidthPx are browser preferences;
  • focusedSection is session UI state and need not reopen automatically after reload;
  • none of this state belongs in StoredWorkspaceV3, Dashboard documents or query specs;
  • selected Databases/Dashboards and Queries/Library/History roles continue to use their existing UI-state ownership.

The persisted preference decoder must clamp invalid or obsolete values safely.

Shared navigation state

Wide and focused presentations are two views over the same navigation state.

Preserve across transitions:

  • schema search text;
  • schema database/table expansion;
  • loaded-column cache;
  • schema scroll position;
  • Dashboard search and expansion;
  • Dashboard keyboard/current-row state;
  • Queries/Library search and editing state where safe;
  • History search;
  • upper/lower split percentage;
  • current application surface and editor/result state.

Do not create separate competing expansion/search stores for wide and focused presentations.

A transition may move DOM focus to the corresponding control in the new presentation, but it must not silently reset the user's state.

Interaction with drag-and-drop assignment

The implementation must remain compatible with #428.

When a Library-query drag is active in rail mode:

  • the Dashboards rail icon can be exposed as a valid hover launcher;
  • bounded hover may open the docked Dashboards focused drawer;
  • the user can then drop on Dashboard, Panels or Filters targets;
  • leaving/cancelling the drag clears temporary hover state;
  • this must never turn the drawer into an overlay.

This integration may be completed by #428 if #428 has not yet landed, but the left-navigation API must provide a deterministic openFocusedSection('dashboards') seam.

Main-surface resizing

Every width or mode change must notify/reflow the active centre surface correctly:

  • CodeMirror/editor geometry;
  • result table and virtualised rows;
  • Query editor/results splitter;
  • Dashboard layout engines and charts;
  • sticky Dashboard/filter/toolbars;
  • optional right inspector.

Use existing resize observers/layout hooks rather than arbitrary delayed resize events.

Left and right docked panels may be open simultaneously. Clamp widths so the centre retains a documented minimum usable width.

Keyboard and accessibility

The vertical frame border is a keyboard-operable separator:

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

Keys:

  • Left/Right Arrow resize by a small step;
  • Shift+Left/Right resize by a larger step;
  • Home folds to rail;
  • End restores wide mode;
  • announce semantic mode changes through an appropriate live/status mechanism without excessive chatter.

Rail buttons must expose aria-expanded and aria-controls for the focused drawer.

Closing a focused drawer returns focus to its rail icon. Converting to wide mode restores focus to the corresponding section tab/search/tree when possible.

Mobile

At the existing mobile breakpoint:

  • do not render the desktop rail or desktop focused drawer;
  • retain the established mobile segmented and bottom navigation;
  • ignore desktop folding preferences for effective mobile layout;
  • preserve those preferences for the next desktop session.

Suggested code boundaries

App shell layout controller

Owns:

  • explicit wide/rail mode;
  • widths and thresholds;
  • vertical resize separator;
  • focused drawer host;
  • preference persistence;
  • centre-width clamping and resize notifications.

Navigation section registry

Maps rail sections to existing views and labels without duplicating their domain state.

Existing navigation views

Continue to own their tree/list rendering, search and application actions. They must not manipulate shell widths directly.

Tests

State and preferences

  • fresh desktop session starts from the documented default;
  • valid mode/width preferences restore;
  • invalid widths clamp safely;
  • left state is never written to workspace JSON;
  • mobile ignores desktop effective state while preserving preferences.

Wide state

  • rail is absent;
  • upper and lower panes render together;
  • switchers and horizontal splitter remain;
  • existing navigation behaviour is unchanged;
  • dragging left below the fold threshold enters rail mode once.

Rail state

  • rail starts below the full-width header;
  • exactly four section icons remain visible;
  • icons remain visible while a drawer is open;
  • active icon is marked;
  • clicking active icon closes the drawer;
  • clicking another icon switches content;
  • rail never expands to drawer width.

Focused drawer

  • drawer is docked and reduces centre width;
  • no overlay/backdrop is created;
  • no redundant role switchers render inside it;
  • selected resources do not auto-close it;
  • search/expansion/scroll state survives section and mode changes;
  • Escape and focus restoration are correct.

Drag transitions

  • wide -> rail uses hysteresis without oscillation;
  • rail -> wide restores the prior width;
  • focused drawer can fold closed or expand to wide;
  • pointer cancellation and window blur terminate drag safely;
  • keyboard separator operations match pointer transitions.

Reflow and regressions

  • editor, results and Dashboard resize correctly;
  • a running query is not cancelled by folding navigation;
  • Query/Dashboard surface state is preserved;
  • right inspector can coexist;
  • database tree and Dashboard tree gestures are unchanged;
  • Add Library drag assignment to Dashboard panels and variables #428 hover-opening can target the docked Dashboards drawer;
  • no mobile regression.

Acceptance criteria

  • Desktop users can fold the left sidebar into a compact rail solely by dragging the vertical frame border.
  • Dragging wider restores the normal two-pane sidebar without a separate expand button.
  • Rail icons open Databases, Dashboards, Queries/Library or History as full-height docked drawers.
  • Focused drawers push the work surface and never float over it.
  • The rail remains visible and correctly sized while a drawer is open.
  • Wide and focused presentations share and preserve all navigation state.
  • Header, main surface and optional right inspector resize correctly.
  • Mobile behaviour is unchanged.

Non-goals

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