Skip to content
Unverified: This page has not yet been anchored to automated code tests.

GUI navigation

The operator console compresses legacy sidebar entries into nine surfaces plus a Settings system row. v2 (June 2026) adds a configurable dashboard, a persistent StatusBar, federated OmniSearch, and Chat-first composer placement (see operator console v2 plan).

SurfaceInner tabs (default first)
ChatFull-page sessions + embedded composer (no global dock)
AgentsDashboard · Agents (flow) · Routing (matrix) · Tasks
Runs & ApprovalsRuns · Approvals · Policies
WorkspaceRepository · Browser · Console · Quick Harness (composer redirect)
CommandsBrowse & Run · MCP Skills & Plugins
SearchUnified search (Memory as a scope chip)
KnowledgeResearch · Scientia · Review · Claims · Publications
ComputeModels · Mens · Populi · Oratio · Mesh
SettingsOrchestrator · Model routing · Mesh & peers · Signing · Secrets · Telemetry · Keybinds · Theme · Gamification (prefs) · Coverage (CI surface gaps)

Gamification: no longer an Agents inner tab. When gamification is enabled, open Achievements from the trophy control on the StatusBar (AchievementsDrawer). Preferences remain under Settings › Gamification. Legacy gamify view keys still deep-link for compatibility.

Legacy view keys (approvals, flow, catalog, …) still deep-link: they open the correct parent and inner tab.

Each leaf surface opens as its own workbench tab in the header stack. The sidebar opens the default child for a parent (for example Workspace → Console). Open tabs persist under vox_workbench_tabs.v1; the location hash #view=<key> stays in sync with the active tab.

┌ Sidebar ─┬─ TopHud ────────────────────────────────────────────────┐
│ ├─ WorkbenchTabBar (open leaf tabs; close non-pinned) │
│ ├─ StatusBar │
│ ├─ Active surface (`SurfaceScrollHost` — single scrollport) │
└──────────┴──────────────────────────────────────────────────────────┘
  • Pinned by default: Chat. Dashboard is opened on first run when no saved state exists.
  • Documentation: Help docs open as doc:<path> tabs via Omnibar (/help … queries) or read_doc_markdown IPC; in-app DocReader renders markdown.
  • Attention budget: shown only on the Chat tab (composer meter), not as a global strip.
  • Legacy inner tabs (ParentSurface / SubTabs) are retired for primary navigation — use workbench tabs and sidebar parents instead.

E2E visual audit: pnpm exec playwright test e2e/workbench-tabs.spec.ts e2e/screenshots.spec.ts --project=chromium.

Operator surfaces share a fixed header stack inside AppShell (sidebar left, chrome + surface right):

┌ Sidebar ─┬─ TopHud (KPI tiles, OpenRouter spend, ⌘K trigger) ─────────┐
│ ├─ WorkbenchTabBar (open surfaces) │
│ ├─ StatusBar (agents · queue · budget · mesh · model · live) │
│ ├─ Active surface (`SurfaceScrollHost`) │
└──────────┴────────────────────────────────────────────────────────────┘

See plan Appendix D for the Chat-first composer rationale. Shipped v2 has no global Loquela dock — the composer lives only on the Chat surface; Dashboard exposes an Open Chat CTA (data-testid="open-chat-cta").

SurfaceComposerExecution rail
ChatYes — Loquela embedded in ChatSurfaceYes — collapsible right rail
Dashboard (Agents › Dashboard)No — Open Chat CTA navigates to ChatNo
ConsoleNo — terminal is primaryOptional task strip only
Other surfacesNoNo

App.tsx keeps chatDocked = false always. Chat hosts the sole shell composer; Console and other surfaces do not show Loquela at the shell level.

Three columns on the Chat surface (each side rail is collapsible):

┌──────────┬─────────────────────────────┬─────────────────┐
│ Sessions │ Transcript + embedded │ Execution rail │
│ rail │ Loquela composer │ (tasks, KPIs) │
└──────────┴─────────────────────────────┴─────────────────┘
  • Sessions rail (ChatSessionRail) — session list and create; collapse persisted under gui.chat.sessions_collapsed.v1.
  • Center — transcript plus inline composer (not the global shell dock).
  • Execution rail (ChatExecutionRail) — live tasks, queue/agents/mesh KPIs, active model, OpenRouter spend; collapse persisted per session UI state.

Agents › Dashboard uses DashboardGrid with drag-and-drop reorder (@dnd-kit) and a Customize dashboard mode that opens the widget picker drawer. Layout is validated against contracts/gui/dashboard-layout.v1.yaml and persisted as gui.dashboard.layout.v1. Widget kinds include stream, agents, alerts, KPI sparklines, charts, queue depth, budget burn, mesh peers, model active, OpenRouter spend, and task summary.

A slim StatusBar sits below the breadcrumb in the header stack (always visible on operator surfaces; not hidden in production HUD modes). Clickable segments navigate to the related surface. OpenRouter spend and richer KPI sparklines live in TopHud tiles and the Chat execution rail, not in StatusBar segments.

SegmentNavigates toData
AgentsAgentsActive agent count
QueueRuns & ApprovalsQueue depth
BudgetSettingsSpend vs cap
MeshComputePeer count
ModelCompute › ModelsActive routing label
Freshness (right)—Live / Poll / Offline from orchestrator events
Trophy (when gamify on)Achievements drawerXP / badges

Full HUD mode (TopHud) adds configurable KPI tiles (including OpenRouter spend) above the StatusBar; cycle with ⌘/Ctrl+Shift+H.

Three entry points share one federated index (useFederatedSearchIndex + backend vox_search_query):

EntryLocationBehavior
⌘/Ctrl+KOmnibarPrimary omni palette, fed by the capability index. Prefix modes: > commands, @ agents, / docs and skills, # crates (#L3 lists the layer-3 crates). The top row is the Best match. MCP tools and crates appear under Tools & crates.
TopHud trigger“Search or jump…” chipOpens the same palette (slim and full HUD modes)
Sidebar filterCollapsible filter fieldNarrows top-level nav and matching child tabs client-side

Palette sections rank surfaces, settings, docs, policies, commands, actions, skills, and backend corpora. Each section shows a few rows with an “N of M” count when more match; keep typing, or use > (commands) or # (crates), to narrow. Each row carries a kind icon and chips for the interfaces it is reachable through (CLI, GUI, MCP), its domain, and its keybind chord when it has one.

  • Surfaces navigate to the surface.
  • A CLI verb runs directly only when it is classified read-only and needs no arguments. A toast shows the exit status, and View full output opens the complete stdout and stderr. Every other verb opens its Catalog form prefilled, with an explicit Run button.
  • Commands the capability index classifies as destructive ask for confirmation, and the backend refuses to run them without it, including under their clap aliases (for example vox models preferences reset). This covers only CLI rows classified destructive: a command with no side_effect_class is not gated by the backend, and from the palette it opens its form instead of running directly. The still-unclassified commands are listed in contracts/capabilities/side-effect-ratchet.v1.json, which can only shrink. MCP tools are not covered by this gate.
  • MCP tools, crates, and skills that are not installed open an explain card with the same groups as vox explain.
  • Each CLI row shows what Enter will do (runs, or opens the form).

Search scopes (OmniSearch vs Search surface)

Section titled “Search scopes (OmniSearch vs Search surface)”

The ⌘/Ctrl+K palette and the Search surface share searchController.ts scope chips, but not every chip maps to a vox-search backend corpus. Surface and command rows come from contracts/capabilities/capability-index.v1.json through the generated capabilityIndex.generated.ts module, which the palette loads on first open. SSOT for index kinds and the source of each kind: contracts/gui/omnisearch-index.v1.yaml.

User scopeWhere results come from (v1)Backend (vox_search_query / search.rs)
Coderepo, symbol corporaYes
Docschunk, knowledge corporaYes
ChatsGUI message LIKE searchYes (special-case in search.rs)
Memorymemory corpusYes
Webweb corpusYes
CommandsCLI catalog (get_command_catalog)Client merge in Search surface; no SearchCorpus variant
SettingsSETTINGS_INDEX (settingsIndex.ts + config-gui-codegen)No — client-merged in Search surface (same pattern as Commands)

Settings search is client-federated only in v1. Rows are built at compile time from settingsIndex.ts and crates/vox-gui/ui/src/config/generatedSettingsIndex.ts (via vox ci config-gui-codegen). useFederatedSearchIndex and federatedSearchIndex.ts index them for the palette and sidebar filter. There is no settings scope in search.rs scope_to_corpus and no SearchCorpus::Settings variant — a backend settings corpus is explicitly out of scope for v1 (see plan task 3.4).

The Search surface Settings chip merges SETTINGS_INDEX client-side via filterSettingsIndexHits (mirroring Commands + get_command_catalog). Selecting a hit seeds vox_settings_seed for the Settings surface. ⌘/Ctrl+K also indexes settings via the federated palette.

KeyAction
⌘/Ctrl+KQuick search palette (OmniSearch)
? (outside text fields) or ⌘/Ctrl+/ (Mod+/)Keyboard shortcut sheet, generated from the keybind registry with your current rebinds
⌘/Ctrl+BCycle sidebar width (rail → default → wide)
⌘/Ctrl+Shift+HCycle HUD (full → slim → hidden)
⌘/Ctrl+\Split panel horizontally (dockview; focus must be in DockShell)
⌘/Ctrl+WClose focused dockview panel (when more than one panel exists)

Every keybind is also a ⌘K action, labelled with its current chord; selecting it runs the action.

Dockview split/close is implemented in DockShell (dockShellKeybindingForEvent).

Surface hierarchy is defined in contracts/gui/surface-registry.v1.yaml and regenerated with vox ci gui-surface-registry --write. Shell persistence keys live in contracts/gui/shell-persistence.v1.yaml; dashboard widgets in contracts/gui/dashboard-layout.v1.yaml; federated OmniSearch kinds in contracts/gui/omnisearch-index.v1.yaml.

Terminal window
vox ci gui-surface-registry --write
vox ci config-gui-codegen --write
cd crates/vox-gui/ui && pnpm install && pnpm build
cargo run -p vox-gui
vox ci gui-smoke