feat(orchestrator): add view-mode navigation, compact switcher, and tmux keybindings - #588
Conversation
Remove all Esc key bindings to avoid interrupting running agents. Use g to return to graph view, add Shift+Tab for prev agent, and route orchestrator attachment back to graph view automatically. Assistant-model: Claude Code
Use a dedicated subagents list (excluding orchestrator) for Tab/Shift+Tab cycling and breadcrumb position display, so attached-view navigation only rotates through actual sub-agents. Assistant-model: Claude Code
Introduce graph/attached view-mode tracking in PanelStore so the UI
layer can coordinate between the session graph overview and an
attached agent window.
- Add ViewMode type ("graph" | "attached") to panel types
- Add viewMode, activeAgentId state and setViewMode() to PanelStore
- Add getSubagents() (filters out orchestrator + pending) and
getActiveAgentIndex() helpers for Tab-cycling
- Full test coverage for new methods
Assistant-model: Claude Code
Add prefix-free tmux keybindings for workflow navigation: - Ctrl+G: jump back to graph window (window 0) from any agent - Ctrl+\: cycle to next agent window Also source-file tmux.conf after createSession so keybindings stay current in long-running sessions. Assistant-model: Claude Code
…igation Wire up the full graph-to-agent navigation flow: - Add CompactSwitcher overlay (/ key) with j/k selection, Enter to jump, number keys for direct access, Esc to close - Implement auto-reset: receiving any key while in attached mode automatically transitions back to graph mode - Poll tmux window index to detect Ctrl+G return from agent windows - Sync tmux status bar with attached-mode hints (agent name, index, Ctrl+G / Ctrl+\ shortcuts) - Simplify Statusline to graph-only; add "/" agents hint - Tab now attaches to first available subagent instead of spatial nav - Update statusline test for icon-based status display Assistant-model: Claude Code
Add compact-switcher.tsx and session-graph-panel.tsx to coveragePathIgnorePatterns to match the components introduced in the compact agent switcher feature. Assistant-model: Claude Code
There was a problem hiding this comment.
Pull request overview
Updates the orchestrator “graph” UX to match new workflow navigation designs by introducing an in-app compact agent switcher, tmux-level navigation bindings, and refreshed statusline behavior.
Changes:
- Redesign statusline to show status via icons and add new navigation hints (including
/for agents). - Add a compact agent switcher overlay (
/) plus attached/graph view mode tracking in the panel store. - Add tmux bindings and runtime syncing to support prefix-free navigation and attached-mode status hints.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/sdk/components/statusline.test.tsx | Updates expectation to match icon-based status display. |
| src/sdk/runtime/tmux.ts | Sources tmux config after session creation to refresh keybindings. |
| src/sdk/runtime/tmux.conf | Adds prefix-free workflow navigation bindings (Ctrl+G / Ctrl+\). |
| src/sdk/components/statusline.tsx | Updates statusline content to icon-based status + new hints. |
| src/sdk/components/session-graph-panel.tsx | Adds compact switcher, attached/graph mode handling, tmux status sync, and return-to-graph detection. |
| src/sdk/components/orchestrator-panel-types.ts | Introduces ViewMode type. |
| src/sdk/components/orchestrator-panel-store.ts | Adds viewMode/activeAgentId plus helper methods for subagents/active index. |
| src/sdk/components/orchestrator-panel-store.test.ts | Adds unit tests for the new store view-mode and helper APIs. |
| src/sdk/components/compact-switcher.tsx | New compact agent list overlay UI component. |
| research/designs/option3-refined-prototype.html | Adds a design prototype for the refined Option 3 workflow UI. |
| bunfig.toml | Excludes new UI components from coverage collection. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const paneId = tmux(args); | ||
| // Reload config into the running server so keybindings are always current | ||
| // (tmux only loads -f on first server start; source-file updates a running server). | ||
| tmuxRun(["source-file", CONFIG_PATH]); |
There was a problem hiding this comment.
tmuxRun(["source-file", CONFIG_PATH]) ignores the return value, so a failure to reload the config (bad path/permissions, missing file, tmux error) will be silently swallowed and keybindings/status settings may not match expectations. Consider using the throwing helper (tmuxExec) or explicitly checking result.ok and surfacing an error when sourcing fails.
| tmuxRun(["source-file", CONFIG_PATH]); | |
| tmux(["source-file", CONFIG_PATH]); |
| return; | ||
| } | ||
| if (key.name === "down" || key.name === "j") { | ||
| setSwitcherSel((s) => Math.min(store.sessions.length - 1, s + 1)); |
There was a problem hiding this comment.
When store.sessions.length is 0, Math.min(store.sessions.length - 1, s + 1) evaluates to -1, allowing switcherSel to become negative. This produces an invalid selection state (and makes Enter a no-op). Clamp the upper bound to at least 0 (e.g., Math.max(0, store.sessions.length - 1)) or early-return when there are no sessions.
| setSwitcherSel((s) => Math.min(store.sessions.length - 1, s + 1)); | |
| setSwitcherSel((s) => | |
| Math.min(Math.max(0, store.sessions.length - 1), s + 1), | |
| ); |
| useEffect(() => { | ||
| if (store.viewMode !== "attached") return; | ||
|
|
||
| const check = () => { | ||
| const result = tmuxRun([ | ||
| "display-message", "-t", tmuxSession, "-p", "#{window_index}", | ||
| ]); | ||
| if (result.ok && result.stdout.trim() === "0") { | ||
| store.setViewMode("graph"); | ||
| } | ||
| }; | ||
|
|
||
| const id = setInterval(check, 300); | ||
| return () => clearInterval(id); |
There was a problem hiding this comment.
The Ctrl+G return-to-graph detection polls tmux every 300ms using tmuxRun, which calls Bun.spawnSync each time. This can become a noticeable CPU/battery cost while attached. Consider reducing the polling frequency, or switching to a tmux hook-driven approach (e.g., a tmux client-* hook that triggers a lightweight signal/flag) to avoid tight synchronous polling.
| /** | ||
| * Return non-orchestrator agents that have started (not pending). | ||
| * These are the agents Tab/Shift+Tab cycles through. | ||
| */ | ||
| getSubagents(): SessionData[] { | ||
| return this.sessions.filter( | ||
| (s) => s.name !== "orchestrator" && s.status !== "pending", | ||
| ); |
There was a problem hiding this comment.
The doc comment says these are the agents that "Tab/Shift+Tab cycles through", but the updated keyboard handling in SessionGraphPanel no longer cycles with Tab (it attaches to the first subagent) and tmux-level navigation is bound to Ctrl+\. Please update the comment to match the actual navigation behavior so it doesn’t mislead future changes/tests.
Code Review — Workflow designs / compact switcherNice prototype work — the interaction model is well thought out, the store additions are clean, and the tests for Bugs / Correctness
Design / Risk
Test coverage
Small / nits
Positives
Happy to discuss any of these — the Tab/Shift+Tab and switcher-indexing inconsistencies feel like the most important to sort out before merging. |
Tab attach-to-first-subagent shortcut is no longer needed now that Ctrl+G and arrow-key navigation cover graph traversal. Update the getSubagents() docstring to reflect its remaining usage (tmux status bar) and drop the corresponding tests. Assistant-model: Claude Code
Code Review — #588Thanks for the thorough PR description and the accompanying HTML prototype — both made the review much easier. Overall the decomposition is clean (types → store methods → UI components) and the new store methods are well-tested. A handful of concerns below, grouped by severity. Potential bugs / correctness1. 2. Tmux format-string interpolation is not escaped ( const left = `... #[fg=#cdd6f4]${name} #[fg=#7f849c]${activeAgentIdx + 1}/${subagentCount}`;If an agent name contains 3. Pending agents are selectable in the compact switcher 4. Number shortcuts in the switcher include the orchestrator 5. Performance6. Tmux status-bar sync fires on every subagent count change ( 7. 300 ms polling of 8. UX / discoverability9. 10. Tests / coverage11. New files added to 12. `getSubagents` "excludes orchestrator" test is trivially satisfied Nits
Strengths worth keeping
Happy to iterate on any of the above — #1, #2, and #11 are the ones I'd treat as blocking before merge. |
…cher Number keys 1-9 conflicted with graph panel input handling; navigation is handled by the compact switcher UI instead. Assistant-model: Claude Code
Code Review —
|
Extract TMUX_DEFAULT_STATUS_* constants into tmux.ts so the restore logic in session-graph-panel stays in sync with tmux.conf without duplicating magic strings. Add escapeTmuxFormat() to prevent agent IDs containing '#' from being interpreted as tmux style/command directives. Assistant-model: Claude Code
…ride Clarify that Ctrl+G / Ctrl+\ bindings use the root table and only apply inside Atomic's isolated tmux server. Note that Ctrl+\ replaces the default SIGQUIT signal and document the workaround. Assistant-model: Claude Code
PR Review: view-mode navigation + compact agent switcherNice, well-scoped feature. The three-layer design (tmux bindings → React view-mode state → tmux status-bar mirror) is a sensible way to make navigation discoverable across the boundary. The tests for the new store methods are solid and the prototype HTML is a helpful reference. Flagging a handful of concerns worth addressing before merge. Correctness / bugs1. 2. CompactSwitcher lists every session incl. orchestrator and pending agents, but
Also: including 3. Pre-selection fallback lands on index 0 (orchestrator). 4. Prototype keymap documents Tab / number-shortcuts; the React handler doesn't implement them. The HTML prototype (line 331, 649) supports 5. Silent tmux failures in Performance6. 300ms subprocess polling is heavier than it looks.
These are more complex but eliminate the polling entirely and cut latency from up-to-300ms to immediate. 7. Status-bar sync fires 4 Design / consistency8. Duplicate restore logic. 9. Hardcoded hex colors in 10. Mode badge literal always says GRAPH. Tests11. New state methods are well-tested ( 12. Missing tests for the switcher keyboard loop and auto-reset behavior.
The existing 13. Security / tmux concerns14. 15. No escaping of Minor / nits
SummaryThe core approach is sound and the test additions for store state are high-quality. The main things I'd want resolved before merge: (1) switcher handling of pending/orchestrator rows (UX footgun), (2) the 300ms polling — either document it as a known cost or switch to a hook-driven approach, and (3) some coverage of the switcher keyboard loop in tests. Everything else is quality-of-life. |
Summary
Adds full keyboard navigation between the orchestrator graph view and attached agent views, a compact agent-switcher popup (
/), and prefix-free tmux keybindings (Ctrl+G,Ctrl+\) for navigating the multi-agent workflow from any window.Key Changes
View Mode State (
orchestrator-panel-types.ts,orchestrator-panel-store.ts)ViewModetype ("graph" | "attached") toorchestrator-panel-types.tsPanelStoregainsviewMode/activeAgentIdstate plussetViewMode(),getSubagents(), andgetActiveAgentIndex()methodsOrchestrator Navigation (
session-graph-panel.tsx)viewMode === "attached"(i.e. user returned to the orchestrator tmux window) automatically resets to graph modeCtrl+Ggraph-return (tmux swallows the key before React sees it)Enteron the orchestrator node stays in / returns to graph mode instead of switching windowsTab/Shift+Tabgraph-grid cycling (now handled at the tmux level)Compact Switcher (
compact-switcher.tsx)CompactSwitcherpopup component, opened with/from any view↑/↓orj/kto navigate,Enterto attach,Escto closeTmux Keybindings (
tmux.conf,tmux.ts)Ctrl+G— jump straight back to graph (window 0) from any agent window (prefix-free)Ctrl+\— cycle to the next agent window (prefix-free); note: overrides the default SIGQUIT signaltmux.tsnow runssource-fileafter session creation so keybindings are always current in a running serverTMUX_DEFAULT_STATUS_*constants andescapeTmuxFormat()to keep status-bar sync centralizedStatusline (
statusline.tsx)●/✓/✗/○) instead of a text label/agents hint to the navigation barTab/Shift+Tabhints (no longer applicable)Tests & Config
setViewMode,getSubagents,getActiveAgentIndexinorchestrator-panel-store.test.tsTab/Shift+Tabgraph-navigation tests fromsession-graph-panel.test.tsxstatusline.test.tsxto assert icon-based status rendering (●) instead of text labelcompact-switcher.tsx,session-graph-panel.tsx) from coverage inbunfig.tomlDesign Prototype
research/designs/option3-refined-prototype.html— interactive single-file prototype demonstrating the full keyboard UX (graph nav, attach, switcher, breadcrumb statusline)Notes
Ctrl+\overrides the default SIGQUIT signal. Agent CLIs in these panes will not receive SIGQUIT viaCtrl+\. The integrated agents (Claude Code, OpenCode, Copilot CLI) useCtrl+Cfor interrupts and are unaffected.-L atomic) and do not affect the user's regular tmux sessions.