feat: cycle through custom tabs with Ctrl+Tab#51
Conversation
Adds Ctrl+Tab / Ctrl+Shift+Tab keyboard shortcuts to cycle through custom tabs in the terminals dashboard, browser-tab style. After cycling, focus is automatically restored to the last focused agent terminal in the destination tab — no more clicking into the xterm to start typing. The per-tab focus memory is held in a ref and pruned when tabs are deleted. Because tab switches re-mount terminals (registerContainer disposes the old xterm and asynchronously initializes a new one), the focus call is stashed in a pending ref and consumed by the existing onTerminalReady callback once the new terminal is ready. The cycle is a no-op when fewer than two custom tabs exist.
|
@SaaSpasse is attempting to deploy a commit to the Charlie Rabiller's projects Team on Vercel. A member of the Team first needs to authorize it. |
📝 WalkthroughWalkthroughThese changes implement keyboard-driven terminal tab cycling functionality. The Changes
Sequence DiagramsequenceDiagram
participant User as User
participant Hook as useTerminalKeyboard Hook
participant Handler as handleCycleTab Handler
participant TabMgr as tabManager
participant Terminal as Terminal Component
participant Mgr as multiTerminal
User->>Hook: Presses Ctrl+Tab
Hook->>Hook: Detects key combination
Hook->>Handler: Invokes onCycleTab('next')
Handler->>TabMgr: Cycles through customTabs
Handler->>Handler: Resolves remembered agent for destination tab
Handler->>Handler: Stores in pendingFocusRef
Handler->>Handler: Sets active custom tab
Handler->>Mgr: Attempts immediate focus
Handler->>Terminal: Terminal re-mounts if needed
Terminal->>Handler: Signals terminal ready (handleTerminalReady)
Handler->>Handler: Consumes pendingFocusRef
Handler->>Mgr: Invokes focusTerminal with destination agent
Mgr->>User: Focus applied to target terminal
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/components/TerminalsView/index.tsx (1)
157-158: Consider moving ref assignment to auseEffectfor concurrent safety.Assigning to
ref.currentduring render is a side effect that could behave unexpectedly if React discards and re-runs the render (concurrent features). In practice this works because terminal initialization is async andfocusTerminalis stable, but moving to a layout effect would be more idiomatic:♻️ Suggested refactor
- // Expose focusTerminal to handleTerminalReady via a ref to break the cycle. - focusTerminalRef.current = multiTerminal.focusTerminal; + // Expose focusTerminal to handleTerminalReady via a ref to break the cycle. + useLayoutEffect(() => { + focusTerminalRef.current = multiTerminal.focusTerminal; + }, [multiTerminal.focusTerminal]);You'll need to add
useLayoutEffectto the imports on line 3.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/TerminalsView/index.tsx` around lines 157 - 158, Move the assignment of focusTerminalRef.current = multiTerminal.focusTerminal out of render and into a useLayoutEffect: import useLayoutEffect alongside other React hooks, create a useLayoutEffect that sets focusTerminalRef.current = multiTerminal.focusTerminal (and cleans up if needed), and include multiTerminal.focusTerminal and focusTerminalRef in the effect dependencies so the ref assignment runs safely under concurrent rendering; this keeps the render side-effect free and preserves the existing behavior used by handleTerminalReady and multiTerminal.focusTerminal.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/components/TerminalsView/index.tsx`:
- Around line 157-158: Move the assignment of focusTerminalRef.current =
multiTerminal.focusTerminal out of render and into a useLayoutEffect: import
useLayoutEffect alongside other React hooks, create a useLayoutEffect that sets
focusTerminalRef.current = multiTerminal.focusTerminal (and cleans up if
needed), and include multiTerminal.focusTerminal and focusTerminalRef in the
effect dependencies so the ref assignment runs safely under concurrent
rendering; this keeps the render side-effect free and preserves the existing
behavior used by handleTerminalReady and multiTerminal.focusTerminal.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: c2061e16-964f-4552-9146-c6adbd1b348d
📒 Files selected for processing (2)
src/components/TerminalsView/hooks/useTerminalKeyboard.tssrc/components/TerminalsView/index.tsx
Add the keyboard-shortcut feature from #51 (already merged) to the v1.2.8 What's New entry so users of this release see it alongside the templates work.
…overrides (#54) * feat: agent templates with built-in roles, import/export, and prompt overrides Add a Templates page with 9 built-in role templates (Frontend / Backend Engineer, Security, Code Reviewer, Tester, Refactor Specialist, Docs Writer, DevOps, Product Designer), each pre-wired with a curated set of marketplace skills and a production-grade system prompt grounded in Cursor / Cline / Roo Code / Anthropic Agent Skills patterns. Use a template → pick a project → agent is created and auto-started. Built-ins are editable; edits are stored as overrides keyed by built-in ID so the original keeps its slot and can be reset to defaults. Templates can be exported as .json and re-imported, single or in batches, for sharing across machines. Other quality-of-life changes that landed alongside: - Agents list sorts by created-at desc by default; createdAt is set on creation and backfilled from lastActivity for legacy agents. - "Save as template" action on each agent card. - Clickable "+" badges on missing skills install via the existing TerminalDialog flow and refresh the badge live on close. - Changelog entry under v1.2.8. * docs: note Ctrl+Tab cycling in v1.2.8 changelog Add the keyboard-shortcut feature from #51 (already merged) to the v1.2.8 What's New entry so users of this release see it alongside the templates work. * fix: keep template dialog usable after a failed onSubmit Wrap the onSubmit/onImport await in try/finally so a thrown IPC error or rejected promise can't strand the submit button in its disabled spinner state. The user can now read the error and retry instead of having to close and reopen the dialog.
What
Adds two keyboard shortcuts to the terminals dashboard:
After switching, focus is automatically restored to the last focused agent terminal in the destination tab — so you can immediately start typing without clicking into the xterm. If no agent has been focused in that tab yet, focus goes to the first agent.
This complements the existing
Ctrl+1-9shortcut (focus an agent inside the current tab) and gives the dashboard a browser-tab-style navigation flow.Why
This was discussed on Slack with @Charlie85270 — the request was muscle-memory parity with browser tab cycling, plus solving the existing pain point where switching panels required a manual click before typing.
Implementation notes
useRef<Map<tabId, agentId>>and pruned by auseEffectwhen tabs are deleted (mirrors the cleanupuseTabManageralready does for stale agent IDs and tab layouts).registerContainerdisposes the old xterm and asynchronously inits a new one). A naive synchronousfocusTerminal()would no-op because the entry isn't interminalsRefyet. So the focus target is stashed in apendingFocusRefand consumed by the existingonTerminalReadycallback once the new terminal finishes init. A best-effort inlinefocusTerminal()is also called for the case where the destination terminal happens to already be live (fast cycling back to a recent tab).handleTerminalReadyis created beforemultiTerminal, sofocusTerminalis exposed via afocusTerminalRefupdated post-creation — same patternuseMultiTerminal.tsuses internally foronTerminalReady.Diff scope
src/components/TerminalsView/hooks/useTerminalKeyboard.ts— adds theonCycleTabprop +Ctrl+Tabhandler.src/components/TerminalsView/index.tsx— adds the refs, the cycle handler, and rewiresuseTerminalKeyboardto use the namedhandleFocusPanelcallback (which now also tracks per-tab focus). TheuseTerminalKeyboardcall had to move below the handler declarations to avoid TDZ on the new dependency.No new dependencies, no IPC or Electron main-process changes, no schema changes.
Test plan
Ctrl+Tab→ cycles to next tab, terminal is auto-focused, typing goes straight into the agentCtrl+Shift+Tab→ cycles backward, wraps from first to lastCtrl+Tabis a no-op (no error)Summary by CodeRabbit
Ctrl+Tabto move to the next tab orCtrl+Shift+Tabfor the previous tab.