Skip to content

feat: cycle through custom tabs with Ctrl+Tab#51

Merged
Charlie85270 merged 1 commit into
Charlie85270:mainfrom
SaaSpasse:feat/ctrl-tab-cycle-custom-tabs
May 5, 2026
Merged

feat: cycle through custom tabs with Ctrl+Tab#51
Charlie85270 merged 1 commit into
Charlie85270:mainfrom
SaaSpasse:feat/ctrl-tab-cycle-custom-tabs

Conversation

@SaaSpasse

@SaaSpasse SaaSpasse commented Apr 16, 2026

Copy link
Copy Markdown
Contributor

What

Adds two keyboard shortcuts to the terminals dashboard:

Shortcut Action
Ctrl+Tab Switch to the next custom tab (wraps)
Ctrl+Shift+Tab Switch to the previous custom tab (wraps)

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-9 shortcut (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

  • Per-tab focus memory is held in a useRef<Map<tabId, agentId>> and pruned by a useEffect when tabs are deleted (mirrors the cleanup useTabManager already does for stale agent IDs and tab layouts).
  • Async focus restoration: switching tabs re-mounts terminals (registerContainer disposes the old xterm and asynchronously inits a new one). A naive synchronous focusTerminal() would no-op because the entry isn't in terminalsRef yet. So the focus target is stashed in a pendingFocusRef and consumed by the existing onTerminalReady callback once the new terminal finishes init. A best-effort inline focusTerminal() is also called for the case where the destination terminal happens to already be live (fast cycling back to a recent tab).
  • Circular dep: handleTerminalReady is created before multiTerminal, so focusTerminal is exposed via a focusTerminalRef updated post-creation — same pattern useMultiTerminal.ts uses internally for onTerminalReady.
  • No-op below 2 tabs — single tab does nothing, no crash.
  • Stale agent guard — if the remembered agent was removed from the tab, falls back to the first agent.

Diff scope

  • src/components/TerminalsView/hooks/useTerminalKeyboard.ts — adds the onCycleTab prop + Ctrl+Tab handler.
  • src/components/TerminalsView/index.tsx — adds the refs, the cycle handler, and rewires useTerminalKeyboard to use the named handleFocusPanel callback (which now also tracks per-tab focus). The useTerminalKeyboard call 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

  • Create 2+ custom tabs with at least one agent each
  • Press Ctrl+Tab → cycles to next tab, terminal is auto-focused, typing goes straight into the agent
  • Press Ctrl+Shift+Tab → cycles backward, wraps from first to last
  • Focus a specific agent in tab A, switch to tab B, switch back → focus restored to that specific agent
  • Delete a tab → no leak in the per-tab focus map
  • Single custom tab → Ctrl+Tab is a no-op (no error)
  • Empty custom tab → switch happens, no focus call

Summary by CodeRabbit

  • New Features
    • Added keyboard shortcuts to cycle through tabs: press Ctrl+Tab to move to the next tab or Ctrl+Shift+Tab for the previous tab.
    • Improved tab switching experience with automatic focus restoration to the last active terminal within each tab.

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.
@vercel

vercel Bot commented Apr 16, 2026

Copy link
Copy Markdown

@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.

@coderabbitai

coderabbitai Bot commented Apr 16, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

These changes implement keyboard-driven terminal tab cycling functionality. The useTerminalKeyboard hook now detects Ctrl+Tab and Ctrl+Shift+Tab key combinations and invokes a callback, while TerminalsView manages focus state and tab selection logic with deferred focusing during async terminal initialization.

Changes

Cohort / File(s) Summary
Keyboard Hook Extension
src/components/TerminalsView/hooks/useTerminalKeyboard.ts
Added onCycleTab callback parameter to detect Ctrl+Tab and Ctrl+Shift+Tab key combinations, passing 'next' or 'prev' direction signals to the callback.
Tab Cycling State & Logic
src/components/TerminalsView/index.tsx
Introduced lastFocusedByTabRef to persist focus per custom tab, added pendingFocusRef and focusTerminalRef for deferred focus restoration across async terminal re-mounts. Implemented handleCycleTab to navigate tabs and restore remembered agent focus, updated handleTerminalReady to apply pending focus, and added cleanup effect to prune stale tab references.

Sequence Diagram

sequenceDiagram
    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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 Hop, hop, tab to tab with Ctrl's sweet song,
Remembering where each agent belongs!
Deferred focus dances through async terrain,
While refs keep the memory, cycle through the plane. 🎹✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title 'feat: cycle through custom tabs with Ctrl+Tab' accurately and concisely describes the main feature added—enabling users to navigate between custom tabs using the Ctrl+Tab keyboard shortcut.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/components/TerminalsView/index.tsx (1)

157-158: Consider moving ref assignment to a useEffect for concurrent safety.

Assigning to ref.current during 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 and focusTerminal is 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 useLayoutEffect to 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

📥 Commits

Reviewing files that changed from the base of the PR and between 7b2aa4a and 1b1669e.

📒 Files selected for processing (2)
  • src/components/TerminalsView/hooks/useTerminalKeyboard.ts
  • src/components/TerminalsView/index.tsx

@Charlie85270 Charlie85270 merged commit c198ba3 into Charlie85270:main May 5, 2026
2 of 3 checks passed
Charlie85270 added a commit that referenced this pull request May 5, 2026
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.
Charlie85270 added a commit that referenced this pull request May 5, 2026
…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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants