🌐 US-Proxy
class="logged-out env-production page-responsive" style="word-wrap: break-word;" >
Skip to content

feat: support multiple terminal tabs on the agents right panel - #26089

Merged
ethanndickson merged 3 commits into
mainfrom
ethan/agents-multi-terminal-tabs
Jun 9, 2026
Merged

feat: support multiple terminal tabs on the agents right panel#26089
ethanndickson merged 3 commits into
mainfrom
ethan/agents-multi-terminal-tabs

Conversation

@ethanndickson

@ethanndickson ethanndickson commented Jun 5, 2026

Copy link
Copy Markdown
Member
  • Adds a dynamic terminal tab model to the Coder Agents right panel so users can open, switch between, and close multiple Web Terminal sessions for the same agent.
  • Each terminal tab generates its own UUID reconnect token and is persisted per agent in local storage, so tabs and their PTY sessions survive reloads.
  • New terminal tabs are auto-labelled (Terminal 2, Terminal 3, ...), filling the lowest free number.
  • The built-in Terminal tab is now closeable; closing it hides it and persists that choice per agent, and it can be restored from the add-tab control.
  • Only the active terminal (and a tab that is mid-activation) mounts the expensive xterm/WebSocket/PTY resources; a freshly hidden terminal stays warm for 30s to keep quick tab toggles instant, then detaches to bound resource usage instead of capping the tab count.
  • Inactive terminals stay laid out but hidden, and a newly opened terminal is not activated until it reports ready (or 100ms), avoiding the narrow refit and blank-frame flicker when switching between terminal canvases.
  • The right-panel tab bar scrolls horizontally with chevron controls when tabs overflow, keeping everything on a single row.
  • Per-agent right-panel tab and hidden-terminal state is cleared from local storage when a chat is archived or deleted.
  • Adds unit tests for the tab utilities, persistence helpers, and the warm/detach lifecycle hook, plus Storybook play-function coverage for the tab and terminal components.

Relates to CODAGT-346

ethanndickson commented Jun 5, 2026

Copy link
Copy Markdown
Member Author

@ethanndickson ethanndickson changed the title feat(site/src/pages/AgentsPage): add multiple terminal tabs to the right panel feat: support multiple terminal tabs on the agents right panel Jun 5, 2026
@linear-code

linear-code Bot commented Jun 5, 2026

Copy link
Copy Markdown

CODAGT-346

@ethanndickson
ethanndickson force-pushed the ethan/agents-multi-terminal-tabs branch from 7e52bf5 to debb4a0 Compare June 5, 2026 03:54
@ethanndickson

Copy link
Copy Markdown
Member Author

/coder-agents-review

@ethanndickson
ethanndickson marked this pull request as ready for review June 5, 2026 04:00
@coder-agents-review

coder-agents-review Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Chat: Review in progress | View chat
Requested: 2026-06-05 10:25 UTC by @ethanndickson
Spend: $66.70 / $100.00

deep-review v0.7.1 | Round 3 | c4792cf..1401838

Last posted: Round 3, 18 findings (4 P2, 10 P3, 4 Nit), COMMENT. Review

Finding inventory

Findings

# Sev Status Location Summary Round Reviewer Posted
CRF-1 P3 Author fixed (20f5d3d) rightPanelTabStorage.ts:50 getPersistedDefaultTerminalHidden and savePersistedDefaultTerminalHidden have no unit tests R1 Netero Yes
CRF-2 P3 Author fixed (20f5d3d) rightPanelTabs.ts:22 sourceAppId field defined, validated, tested, but never read by logic; deduplication code promised by doc comment does not exist R1 Netero, Gon P2 Yes
CRF-3 P2 Author fixed (20f5d3d) AgentChatPageView.tsx:461 No upper bound on terminal tab count; each tab accumulates live WebGL renderer, PTY session, and WebSocket R1 Meruem P3, Mafuuu P3, Luffy P2, Nami P2 Yes
CRF-4 P2 Author fixed (20f5d3d) rightPanelTabStorage.ts:3 Per-chat localStorage keys for right panel tabs and default-terminal-hidden are never cleaned on chat archival or deletion R1 Hisoka P2, Luffy P3 Yes
CRF-5 P2 Author fixed (20f5d3d) SidebarTabView.stories.tsx:115 CloseableTabs story is visual-only; no play function verifies close interaction R1 Bisky Yes
CRF-6 P3 Author fixed (20f5d3d) AgentChatPageView.tsx:478 Stale render closure in handleAddTerminalTab and handleCloseTab can duplicate labels or resurrect closed tabs on rapid clicks R1 Kite Yes
CRF-7 P3 Author fixed (20f5d3d) rightPanelTabs.ts:103 createTerminalReconnectionToken reimplements uuid v4 which is already a project dependency used for the same purpose R1 Robin Yes
CRF-8 P3 Author fixed (20f5d3d) RightPanelAddTabMenu.tsx:5 Commit message describes a split add-tab control with menu items; component is a single button named Menu R1 Mafu-san P3, Gon Nit, Nami Nit, Zoro Nit Yes
CRF-9 P3 Author fixed (20f5d3d) rightPanelTabs.ts:48 isUserRightPanelTab accepts any non-empty string as reconnectionToken; backend requires UUID format R1 Knov Yes
CRF-10 P3 Author fixed (20f5d3d) SidebarTabView.tsx:25 SidebarTab.closeable is redundant with onClose and permits impossible state R1 Knov Yes
CRF-11 P3 Author fixed (20f5d3d) rightPanelTabs.test.ts:44 getNextTerminalTabLabel test missing empty-array base case R1 Bisky Yes
CRF-12 Nit Author fixed (20f5d3d) WorkspaceTerminal.tsx:144 Fit-guard comment references display:none but SidebarTabView now uses visibility:hidden R1 Meruem Yes
CRF-13 Nit Author fixed (20f5d3d) RightPanelAddTabMenu.tsx:13 Raw button bypasses project Button component R1 Zoro Yes
CRF-14 Nit Author fixed (20f5d3d) SidebarTabView.stories.tsx:159 MockAddTabControl duplicates RightPanelAddTabMenu markup R1 Robin Yes
CRF-15 Nit Dropped by orchestrator (split-button compound widget; Button abstraction does not fit) SidebarTabView.tsx:208 Close button uses raw button instead of project Button component R2 Netero No
CRF-16 P2 Author fixed (1401838) TerminalPanel.stories.tsx:34 TerminalPanel stories silently render empty containers because isHot is not in meta args; fix-chain regression from CRF-3 R2 Bisky Yes
CRF-17 P3 Author fixed (1401838) TerminalPanel.tsx:88 Hot/warm terminal lifecycle (CRF-3 resolution) has no test coverage for key transitions R2 Chopper Yes
CRF-18 Nit Author fixed (1401838) rightPanelTabs.test.ts:1 Missing explicit vitest imports; every sibling test uses explicit imports R2 Pariston Yes
CRF-19 P3 Open useTerminalWarmLifecycle.test.ts:25 All three tests start with isHot: true; cold-start transition (isHot: false to true) is untested R3 Bisky P3, Meruem P3, Chopper P3 Yes

Round log

Round 1

Panel. Netero: 0 P0-P2, 2 P3. Panel: 3 P2, 6 P3, 3 Nit. 14 total findings. Reviewed against 4627b01..debb4a0.

Round 2

Churn guard: PROCEED. 14/14 findings addressed. Panel: 1 P2, 1 P3, 1 Nit new. 1 Netero Nit dropped. CRF-15 dropped: close button is a split-button compound widget where Button abstraction does not fit. Reviewed against c4792cf..20f5d3d.

Round 3

Churn guard: PROCEED. 3/3 R2 findings addressed. Netero: no findings. Panel: 1 P3 new (convergent, 3/6 reviewers). Reviewed against c4792cf..1401838.

About deep-review

CRF = Coder Review Finding (P0-P4, Nit, Note)

Reviewer Focus
Bisky tests
Chopper ops/errors
Churn-guard change verification
Ging language modernization
Gon naming
Hisoka edge cases
Killua perf
Kite change integrity
Knov contracts
Knuckle SQL
Kurapika security
Law decomposition
Leorio docs
Luffy product
Mafu-san process
Mafuuu contracts
Melody dispatch/pairing
Meruem structural
Nami frontend
Netero mechanical checks
Pariston premise testing
Pen-botter product gaps
Razor verification
Robin duplication
Ryosuke Go arch
Takumi concurrency
Zoro shape

🤖 Managed by Coder Agents.

@ethanndickson
ethanndickson force-pushed the ethan/agents-multi-terminal-tabs branch from debb4a0 to 6589f87 Compare June 5, 2026 04:25

@coder-agents-review coder-agents-review Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The pending-tab promotion pattern is well-designed: connecting off screen, waiting for painted output or the fallback, then switching. The readiness signaling chain is correct through the useEffectEvent chain and the stale-closure guards on pendingTabId are sound. The invisible-absolute layout strategy correctly solves the canvas refit problem at the root (FitAddon dimensions) rather than patching symptoms. The type guard and storage validation are defensive and well-tested. Solid feature work.

3 P2, 8 P3, 3 Nit.

Hisoka, on what happens when someone opens their fifteenth terminal: "What happens when someone opens their fifteenth terminal and types fast in one of them, triggering re-renders on every keystroke? Shall I show you? ♥"

The tab overflow strategy changed from horizontal scroll (chevron buttons, useTabScroll hook) to flex-wrap. With many open tabs the tab bar now grows vertically, eating into terminal panel space. The previous horizontal scroll kept the tab bar compact at one row. This is a UX tradeoff bundled into the feature without mention in the PR description.

🤖 This review was automatically generated with Coder Agents.

Comment thread site/src/pages/AgentsPage/AgentChatPageView.tsx
Comment thread site/src/pages/AgentsPage/utils/rightPanelTabStorage.ts
Comment thread site/src/pages/AgentsPage/AgentChatPageView.tsx Outdated
Comment thread site/src/pages/AgentsPage/utils/rightPanelTabs.ts Outdated
Comment thread site/src/pages/AgentsPage/utils/rightPanelTabs.ts Outdated
Comment thread site/src/pages/AgentsPage/utils/rightPanelTabStorage.ts
Comment thread site/src/modules/terminal/WorkspaceTerminal.tsx Outdated
Comment thread site/src/pages/AgentsPage/components/RightPanel/RightPanelAddTabMenu.tsx Outdated
Comment thread site/src/pages/AgentsPage/components/ChatsSidebar/tabs/SidebarTabView.stories.tsx Outdated
@ethanndickson
ethanndickson force-pushed the ethan/agents-multi-terminal-tabs branch from 6589f87 to 20f5d3d Compare June 5, 2026 07:49
@ethanndickson

Copy link
Copy Markdown
Member Author

/coder-agents-review

@coder-agents-review coder-agents-review Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All 14 R1 findings addressed. The hot/warm/detach lifecycle for CRF-3 is a well-calibrated alternative to a hard tab cap: it bounds the expensive resources (xterm, WebSocket, PTY) while keeping lightweight tab descriptors intact for later reattachment. The stale-closure fixes (CRF-6) use correct callback-form setState. The localStorage cleanup (CRF-4) is wired symmetrically with the existing sidebar tab cleanup. The play function on CloseableTabs (CRF-5) verifies close on active and inactive tabs.

1 P2, 1 P3, 1 Nit.

Hisoka traced rapid double-click, close-while-pending, warm-to-cold-to-hot cycles, built-in terminal restore, and useEffectEvent closure chains: "Boring code gets silence. Nothing here broke under inspection. ♠"

Pariston noted a redundant key={agentId} on AgentChatPageView (the parent KeyedAgentChatPage already applies the same key). Harmless but worth knowing.

Hisoka also flagged that isVisible passed to WorkspaceTerminal is a tautology in the new code: the component is conditionally rendered on the same boolean (shouldMountTerminal), so isVisible is always true at mount. Not harmful, not this PR's concern.


site/src/pages/AgentsPage/components/TerminalPanel.stories.tsx:34

P2 [CRF-16] Every TerminalPanel story except AgentUnavailable silently renders an empty terminal container.

The hot/warm lifecycle gate (line 166: {shouldMountTerminal && (<WorkspaceTerminal .../>)}) requires isHot to be truthy, but no story sets isHot in its args. Boolean(undefined) is false, isWarm initializes as useState(false), so shouldMountTerminal is false and WorkspaceTerminal never mounts.

Before this PR, TerminalPanel rendered <WorkspaceTerminal> unconditionally. Now Connected, StartingAgent, StartError, and Disconnected all render a TerminalPanel shell with no terminal inside. Since chromatic: { disableSnapshot: true } is set on the meta, no visual regression catches this.

Fix: add isHot: true to the story meta args.

(Bisky)

🤖

🤖 This review was automatically generated with Coder Agents.

Comment thread site/src/pages/AgentsPage/components/TerminalPanel.tsx
Comment thread site/src/pages/AgentsPage/utils/rightPanelTabs.test.ts
@ethanndickson
ethanndickson force-pushed the ethan/agents-multi-terminal-tabs branch from 20f5d3d to 1401838 Compare June 5, 2026 10:21
@ethanndickson

Copy link
Copy Markdown
Member Author

/coder-agents-review

@coder-agents-review coder-agents-review Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All 17 findings across R1 and R2 resolved. The useTerminalWarmLifecycle extraction (CRF-17) is clean: the hook owns the timer and state, TerminalPanel consumes it, and three tests cover the key transitions. The isHot: true fix for the stories (CRF-16) is correct and minimal. Netero found no mechanical issues.

0 P2, 1 P3, 0 Nit.

Hisoka traced rapid-create, close-while-pending, manual-switch-cancels-pending, warm lifecycle, visibility tautology, key={agentId} boundary, and built-in terminal close vs user-tab close: "Three rounds in, seventeen findings addressed, and the code held up. ♦"

Chopper noted the CloseableTabs story marks the built-in Terminal as non-closeable, diverging from the real component where it is closeable. The close mechanism is exercised on user-created tabs, so the coverage gap is cosmetic.

Multiple reviewers noted isVisible={shouldMountTerminal} is a tautology (always true when WorkspaceTerminal is rendered). Harmless; the prop communicates intent and WorkspaceTerminal uses it independently in other contexts.


site/src/pages/AgentsPage/hooks/useTerminalWarmLifecycle.test.ts:25

P3 [CRF-19] All three tests start with { isHot: true }. The cold-start transition (isHot: falsetrue) is the primary mount path for terminal tabs restored from localStorage when another tab is active.

The hook initializes isWarm from Boolean(isHot) (line 28), so starting cold means shouldMountTerminal is false until isHot goes true. A change to the initialization or the effect's early-return guard could break first-mount silently.

it("mounts the terminal when a cold tab becomes hot", () => {
  const { result, rerender } = renderHook(
    ({ isHot }) => useTerminalWarmLifecycle({ isHot, detachDelayMs: TERMINAL_IDLE_DETACH_MS }),
    { initialProps: { isHot: false } },
  );
  expect(result.current.shouldMountTerminal).toBe(false);
  rerender({ isHot: true });
  expect(result.current.shouldMountTerminal).toBe(true);
});

(Bisky P3, Meruem P3, Chopper P3)

🤖

🤖 This review was automatically generated with Coder Agents.

@ethanndickson
ethanndickson force-pushed the ethan/agents-multi-terminal-tabs branch from 1401838 to a055112 Compare June 9, 2026 03:10
@ethanndickson

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a055112e02

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread site/src/pages/AgentsPage/AgentChatPageView.tsx
@ethanndickson
ethanndickson force-pushed the ethan/agents-multi-terminal-tabs branch 2 times, most recently from e7f2e8b to 3171944 Compare June 9, 2026 05:29

@DanielleMaywood DanielleMaywood left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The comments are wayyyyy too verbose. Lots of them are just re-iterating the implementation and add little/no value.

Comment thread site/src/pages/AgentsPage/AgentChatPageView.tsx Outdated
…ght panel

Replace the single fixed Terminal slot in the Coder Agents right panel with a
dynamic, per-agent set of terminal tabs. Tabs are added through a split add-tab
control (RightPanelAddTabMenu) whose primary action and "New Terminal" menu item
open a new terminal, and their open/closed state persists per agent in
localStorage. The built-in Terminal becomes closeable; closing it persists a
hidden flag, and reopening restores it.

Each terminal tab carries its own UUID PTY reconnect token so multiple PTYs stay
attached across reloads, and TerminalPanel takes the reconnect token explicitly
instead of deriving it from the chat id.

Fix two terminal rendering glitches that surface with multiple terminal tabs:

- Defer activation of a freshly opened terminal until its prompt paints, so a
  new tab no longer flashes an empty panel during connection latency.
- Keep inactive panels laid out with visibility:hidden instead of display:none,
  and skip FitAddon fitting while a terminal has no layout box. A display:none
  terminal otherwise refit to its minimum column count (FitAddon reads a "100%"
  computed width and parseInt clamps it), which both wrapped the prompt narrow
  on the next switch and reflowed the PTY. visibility:hidden also preserves the
  canvas renderer's painted pixels, so switching between terminals no longer
  flickers blank for a frame.
@ethanndickson
ethanndickson force-pushed the ethan/agents-multi-terminal-tabs branch from 3171944 to 912a4e1 Compare June 9, 2026 12:55
@ethanndickson
ethanndickson merged commit db00007 into main Jun 9, 2026
30 checks passed
@ethanndickson
ethanndickson deleted the ethan/agents-multi-terminal-tabs branch June 9, 2026 14:08
@github-actions github-actions Bot locked and limited conversation to collaborators Jun 9, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants