@burdenoff/microfe-vibecontrols 2026.529.6 → 2026.529.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/sessions/TerminalIframe.js +12 -12
- package/dist/components/sessions/TerminalIframe.js.map +1 -1
- package/dist/components/sessions/TerminalPanel.js +1 -0
- package/dist/components/sessions/TerminalPanel.js.map +1 -1
- package/dist/generated/wspace-operations.js +780 -748
- package/dist/generated/wspace-operations.js.map +1 -1
- package/dist/generated/wspace-types.js.map +1 -1
- package/dist/index.js +10 -10
- package/dist/pages/SessionsPage.js +502 -481
- package/dist/pages/SessionsPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -2,25 +2,25 @@ import { useTerminalCapabilityUrl as e } from "../../hooks/useTerminalCapability
|
|
|
2
2
|
import { forwardRef as t, useEffect as n } from "react";
|
|
3
3
|
import { Fragment as r, jsx as i } from "react/jsx-runtime";
|
|
4
4
|
//#region src/components/sessions/TerminalIframe.tsx
|
|
5
|
-
var a = t(function({ sessionId: t, agentId: a,
|
|
6
|
-
let { url:
|
|
5
|
+
var a = t(function({ sessionId: t, agentId: a, profile: o, baseTtydUrl: s, title: c, className: l, allow: u, onLoad: d, onError: f, fallback: p }, m) {
|
|
6
|
+
let { url: h, remint: g } = e(a, t, s, o || "default");
|
|
7
7
|
return n(() => {
|
|
8
8
|
function e(e) {
|
|
9
9
|
let n = e?.data;
|
|
10
|
-
!n || typeof n != "object" || n.type === "vt-token-request" && (typeof n.sid != "string" || n.sid !== t ||
|
|
10
|
+
!n || typeof n != "object" || n.type === "vt-token-request" && (typeof n.sid != "string" || n.sid !== t || g());
|
|
11
11
|
}
|
|
12
12
|
return window.addEventListener("message", e), () => window.removeEventListener("message", e);
|
|
13
|
-
}, [t,
|
|
14
|
-
ref:
|
|
15
|
-
src:
|
|
16
|
-
title:
|
|
17
|
-
className:
|
|
18
|
-
allow:
|
|
19
|
-
onLoad:
|
|
13
|
+
}, [t, g]), h ? /* @__PURE__ */ i("iframe", {
|
|
14
|
+
ref: m,
|
|
15
|
+
src: h,
|
|
16
|
+
title: c,
|
|
17
|
+
className: l,
|
|
18
|
+
allow: u,
|
|
19
|
+
onLoad: d,
|
|
20
20
|
onError: () => {
|
|
21
|
-
|
|
21
|
+
g(), f?.();
|
|
22
22
|
}
|
|
23
|
-
}) : /* @__PURE__ */ i(r, { children:
|
|
23
|
+
}) : /* @__PURE__ */ i(r, { children: p ?? null });
|
|
24
24
|
});
|
|
25
25
|
//#endregion
|
|
26
26
|
export { a as TerminalIframe };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TerminalIframe.js","names":[],"sources":["../../../src/components/sessions/TerminalIframe.tsx"],"sourcesContent":["/**\n * TerminalIframe — renders the ttyd iframe with an F3-compliant\n * capability token appended as a `#vt=<token>` URL fragment.\n *\n * Each tab in the sessions terminal panel needs its own capability mint\n * (the iframe-token is scoped to `/terminal/<sessionId>`). React hooks\n * can't be called in a `tabs.map(...)` loop, so the iframe rendering is\n * extracted here and the hook runs per component instance.\n *\n * Stale browser tabs and reopened sessions hit a 10-minute cookie TTL\n * inside the agent; `onError` triggers `remint()` which re-fetches a\n * fresh token and re-iframes.\n */\nimport { type ReactNode, forwardRef, useEffect } from 'react';\n\nimport { useTerminalCapabilityUrl } from '@/hooks/useTerminalCapabilityUrl';\n\nexport interface TerminalIframeProps {\n /** Session id — used as resourceId for the capability mint. */\n sessionId: string;\n /** Agent id owning this session — required for the mint mutation. */\n agentId: string | null | undefined;\n /** Bare ttyd tunnel URL from `session.ttydUrl`. Hook strips any legacy ?apiKey=. */\n baseTtydUrl: string | null | undefined;\n /** Title for accessibility. */\n title: string;\n /** Optional className for the iframe. */\n className?: string;\n /** Permissions-Policy allow attribute (clipboard). */\n allow?: string;\n /** Called on iframe load. Use to flip connection status to connected. */\n onLoad?: () => void;\n /** Called on iframe error. We re-mint internally + bubble the event. */\n onError?: () => void;\n /** Slot for fallback content when no URL is available yet. */\n fallback?: ReactNode;\n}\n\nexport const TerminalIframe = forwardRef<HTMLIFrameElement, TerminalIframeProps>(\n function TerminalIframe(\n {
|
|
1
|
+
{"version":3,"file":"TerminalIframe.js","names":[],"sources":["../../../src/components/sessions/TerminalIframe.tsx"],"sourcesContent":["/**\n * TerminalIframe — renders the ttyd iframe with an F3-compliant\n * capability token appended as a `#vt=<token>` URL fragment.\n *\n * Each tab in the sessions terminal panel needs its own capability mint\n * (the iframe-token is scoped to `/terminal/<sessionId>`). React hooks\n * can't be called in a `tabs.map(...)` loop, so the iframe rendering is\n * extracted here and the hook runs per component instance.\n *\n * Stale browser tabs and reopened sessions hit a 10-minute cookie TTL\n * inside the agent; `onError` triggers `remint()` which re-fetches a\n * fresh token and re-iframes.\n */\nimport { type ReactNode, forwardRef, useEffect } from 'react';\n\nimport { useTerminalCapabilityUrl } from '@/hooks/useTerminalCapabilityUrl';\n\nexport interface TerminalIframeProps {\n /** Session id — used as resourceId for the capability mint. */\n sessionId: string;\n /** Agent id owning this session — required for the mint mutation. */\n agentId: string | null | undefined;\n /**\n * Agent profile this session runs against. Persisted as\n * Session.profile at create-time (default: \"default\"). Threaded\n * into mintAgentCapability so the svc proxies the iframe-token\n * mint to /api/profiles/<profile>/agent/iframe-token instead of\n * defaulting unconditionally — required when an operator runs\n * multiple profiles on one agent and the session targets one.\n */\n profile?: string | null;\n /** Bare ttyd tunnel URL from `session.ttydUrl`. Hook strips any legacy ?apiKey=. */\n baseTtydUrl: string | null | undefined;\n /** Title for accessibility. */\n title: string;\n /** Optional className for the iframe. */\n className?: string;\n /** Permissions-Policy allow attribute (clipboard). */\n allow?: string;\n /** Called on iframe load. Use to flip connection status to connected. */\n onLoad?: () => void;\n /** Called on iframe error. We re-mint internally + bubble the event. */\n onError?: () => void;\n /** Slot for fallback content when no URL is available yet. */\n fallback?: ReactNode;\n}\n\nexport const TerminalIframe = forwardRef<HTMLIFrameElement, TerminalIframeProps>(\n function TerminalIframe(\n {\n sessionId,\n agentId,\n profile,\n baseTtydUrl,\n title,\n className,\n allow,\n onLoad,\n onError,\n fallback,\n },\n ref\n ) {\n const { url, remint } = useTerminalCapabilityUrl(\n agentId,\n sessionId,\n baseTtydUrl,\n profile || 'default'\n );\n\n // F3 cookie-shim postMessage bridge.\n //\n // When the agent's bootstrap HTML rejects the iframe-token with\n // `code: 'expired'` or `code: 'replayed'`, it posts a\n // `{type: 'vt-token-request', sid}` message to its parent window\n // (this microfe) asking for a fresh mint. We listen for it and\n // call `remint()` so `useTerminalCapabilityUrl` issues a new\n // capability and the iframe src updates with a fresh `#vt=…`.\n //\n // This unblocks two real-world cases:\n // 1. React Strict Mode double-renders the iframe in dev, so the\n // first token's JTI gets consumed before the visible iframe\n // navigates — second navigation hits `replayed`.\n // 2. Operator leaves the tab idle past the 60s mint TTL — next\n // navigation hits `expired`.\n // Without the bridge the bootstrap dead-ends on \"Terminal\n // authentication failed\" until the operator manually reloads.\n useEffect(() => {\n function onMsg(ev: MessageEvent) {\n const data = ev?.data;\n if (!data || typeof data !== 'object') return;\n if (data.type !== 'vt-token-request') return;\n if (typeof data.sid !== 'string' || data.sid !== sessionId) return;\n remint();\n }\n window.addEventListener('message', onMsg);\n return () => window.removeEventListener('message', onMsg);\n }, [sessionId, remint]);\n\n if (!url) {\n return <>{fallback ?? null}</>;\n }\n\n return (\n <iframe\n ref={ref}\n src={url}\n title={title}\n className={className}\n allow={allow}\n onLoad={onLoad}\n onError={() => {\n // Re-mint on any iframe error — covers both 401 (cookie expired\n // mid-session) and network blips.\n remint();\n onError?.();\n }}\n />\n );\n }\n);\n"],"mappings":";;;;AA+CA,IAAa,IAAiB,EAC5B,SACE,EACE,cACA,YACA,YACA,gBACA,UACA,cACA,UACA,WACA,YACA,eAEF,GACA;CACA,IAAM,EAAE,QAAK,cAAW,EACtB,GACA,GACA,GACA,KAAW,UACZ;AAmCD,QAhBA,QAAgB;EACd,SAAS,EAAM,GAAkB;GAC/B,IAAM,IAAO,GAAI;AACb,IAAC,KAAQ,OAAO,KAAS,YACzB,EAAK,SAAS,uBACd,OAAO,EAAK,OAAQ,YAAY,EAAK,QAAQ,KACjD,GAAQ;;AAGV,SADA,OAAO,iBAAiB,WAAW,EAAM,QAC5B,OAAO,oBAAoB,WAAW,EAAM;IACxD,CAAC,GAAW,EAAO,CAAC,EAElB,IAKH,kBAAC,UAAD;EACO;EACL,KAAK;EACE;EACI;EACJ;EACC;EACR,eAAe;AAIb,GADA,GAAQ,EACR,KAAW;;EAEb,CAAA,GAjBK,kBAAA,GAAA,EAAA,UAAG,KAAY,MAAQ,CAAA;EAoBnC"}
|
|
@@ -288,6 +288,7 @@ function h({ tabs: h, activeTabId: me, onTabSelect: he, onTabClose: ge, onOpenEx
|
|
|
288
288
|
},
|
|
289
289
|
sessionId: e.sessionId,
|
|
290
290
|
agentId: e.agentId ?? null,
|
|
291
|
+
profile: e.profile ?? null,
|
|
291
292
|
baseTtydUrl: e.terminalUrl,
|
|
292
293
|
title: `Terminal - ${e.name}`,
|
|
293
294
|
className: "w-full h-full border-0",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TerminalPanel.js","names":[],"sources":["../../../src/components/sessions/TerminalPanel.tsx"],"sourcesContent":["import { useState, useCallback, useRef, useEffect, type ReactNode } from 'react';\nimport { createPortal } from 'react-dom';\nimport { useTr } from '../../shared/hooks/useTr';\nimport {\n Terminal,\n X,\n Maximize2,\n Minimize2,\n ExternalLink,\n Plus,\n ChevronUp,\n ChevronDown,\n Loader2,\n AlertCircle,\n Edit3,\n RotateCcw,\n Square,\n Search,\n Wifi,\n WifiOff,\n GripVertical,\n Expand,\n Shrink,\n} from 'lucide-react';\nimport type { Session } from './types';\nimport { TerminalIframe } from './TerminalIframe';\n\n/**\n * A single terminal tab representing an open session.\n */\nexport interface TerminalTab {\n /** The session ID */\n sessionId: string;\n /** Display name (session name) */\n name: string;\n /** The URL to load in the iframe - either ttydUrl or agent tunnelUrl */\n terminalUrl: string | null;\n /**\n * Agent id owning the session — required so TerminalIframe can mint\n * an F3 capability token. Optional for tabs whose owning agent is\n * unknown (rare; legacy sessions get null).\n */\n agentId?: string | null;\n /** Whether ttyd is being started for this session */\n loading?: boolean;\n /** Error message if starting failed */\n error?: string | null;\n}\n\ninterface ContextMenuState {\n visible: boolean;\n x: number;\n y: number;\n sessionId: string | null;\n}\n\ntype ConnectionStatus = 'connecting' | 'connected' | 'error';\n\ninterface TerminalPanelProps {\n /** The currently open terminal tabs */\n tabs: TerminalTab[];\n /** The active tab session ID */\n activeTabId: string | null;\n /** Called when user clicks a tab */\n onTabSelect: (sessionId: string) => void;\n /** Called when user closes a tab */\n onTabClose: (sessionId: string) => void;\n /** Called when user wants to open a session in a new browser tab */\n onOpenExternal: (sessionId: string) => void;\n /** Called when panel is collapsed/expanded */\n onToggleCollapse?: () => void;\n /** Whether the panel is collapsed */\n collapsed?: boolean;\n /** Panel height class (e.g., 'h-96', 'h-[500px]') */\n heightClass?: string;\n /** All available sessions for the \"+\" button */\n sessions?: Session[];\n /** Called when user clicks \"+\" to open a new terminal for a session */\n onOpenTerminal?: (session: Session) => void;\n /** Called when user clicks retry on a failed terminal tab */\n onRetryTerminal?: (session: Session) => void;\n /** Called when user renames a session tab */\n onRenameSession?: (sessionId: string, newName: string) => void;\n /** Called when user stops a terminal */\n onStopTerminal?: (sessionId: string) => void;\n /** Called when user restarts a terminal */\n onRestartTerminal?: (sessionId: string) => void;\n /** Called when user stops the entire session */\n onStopSession?: (sessionId: string) => void;\n /** Called to create a new session (for \"+\" tab in terminal-first mode) */\n onCreateSession?: () => void;\n /** Whether this is the full-screen terminal-first mode */\n fullScreen?: boolean;\n /** Whether the immersive fullscreen overlay is active */\n isImmersiveFullscreen?: boolean;\n /** Called when user clicks the immersive fullscreen button */\n onToggleImmersiveFullscreen?: () => void;\n /** Context switcher component to render in immersive fullscreen header */\n contextSwitcher?: ReactNode;\n}\n\n/**\n * Tabbed terminal panel - like VS Code's integrated terminal.\n * Each tab is an iframe loading a ttyd web terminal via the agent's tunnel.\n * Supports context menu with rename, close, open in new tab, restart, stop.\n * Features: reconnect on failure, connection status indicators, drag-to-resize.\n */\nexport function TerminalPanel({\n tabs,\n activeTabId,\n onTabSelect,\n onTabClose,\n onOpenExternal,\n onToggleCollapse,\n collapsed = false,\n heightClass = 'h-[420px]',\n sessions,\n onOpenTerminal,\n onRetryTerminal,\n onRenameSession,\n onStopTerminal,\n onRestartTerminal,\n onStopSession,\n onCreateSession,\n fullScreen = false,\n isImmersiveFullscreen = false,\n onToggleImmersiveFullscreen,\n contextSwitcher,\n}: TerminalPanelProps) {\n const tr = useTr();\n const [maximized, setMaximized] = useState(false);\n const [showSessionPicker, setShowSessionPicker] = useState(false);\n const [sessionSearch, setSessionSearch] = useState('');\n const [contextMenu, setContextMenu] = useState<ContextMenuState>({\n visible: false,\n x: 0,\n y: 0,\n sessionId: null,\n });\n const [renamingTabId, setRenamingTabId] = useState<string | null>(null);\n const [renameValue, setRenameValue] = useState('');\n // Track iframe connection status per tab\n const [connectionStatus, setConnectionStatus] = useState<Record<string, ConnectionStatus>>({});\n const [connectionIssues, setConnectionIssues] = useState<Record<string, string | undefined>>({});\n // Drag-to-resize state\n const [resizeHeight, setResizeHeight] = useState<number | null>(null);\n const resizingRef = useRef(false);\n const startYRef = useRef(0);\n const startHeightRef = useRef(0);\n const panelRef = useRef<HTMLDivElement>(null);\n\n const renameInputRef = useRef<HTMLInputElement>(null);\n const contextMenuRef = useRef<HTMLDivElement>(null);\n const plusBtnRef = useRef<HTMLButtonElement>(null);\n const sessionPickerRef = useRef<HTMLDivElement>(null);\n const sessionSearchRef = useRef<HTMLInputElement>(null);\n // Track iframe refs for reconnect\n const iframeRefs = useRef<Record<string, HTMLIFrameElement | null>>({});\n // Auto-reconnect: track retry timers and attempt counts per session\n const autoReconnectTimers = useRef<Record<string, ReturnType<typeof setTimeout>>>({});\n const autoReconnectAttempts = useRef<Record<string, number>>({});\n const connectionTimeoutTimers = useRef<Record<string, ReturnType<typeof setTimeout>>>({});\n\n const activeTab = tabs.find((t) => t.sessionId === activeTabId) || tabs[0] || null;\n\n const setConnectionStatusForTab = useCallback((sessionId: string, status: ConnectionStatus) => {\n setConnectionStatus((prev) =>\n prev[sessionId] === status ? prev : { ...prev, [sessionId]: status }\n );\n }, []);\n\n const setConnectionIssueForTab = useCallback((sessionId: string, issue: string | undefined) => {\n setConnectionIssues((prev) =>\n prev[sessionId] === issue ? prev : { ...prev, [sessionId]: issue }\n );\n }, []);\n\n const getTerminalUrlIssue = useCallback(\n (terminalUrl: string | null | undefined): string | null => {\n if (!terminalUrl) return null;\n\n try {\n const pageProtocol = typeof window !== 'undefined' ? window.location.protocol : 'https:';\n const terminalProtocol = new URL(terminalUrl).protocol;\n if (pageProtocol === 'https:' && terminalProtocol !== 'https:') {\n return tr(\n 'vibecontrols.sessions.terminal.mixedContentBlocked',\n 'This terminal URL is not HTTPS, so the browser blocked it on this secure page.'\n );\n }\n\n return null;\n } catch {\n return tr(\n 'vibecontrols.sessions.terminal.invalidUrl',\n 'The terminal URL is invalid. Restart the terminal to generate a new URL.'\n );\n }\n },\n [tr]\n );\n\n const handleMaximize = useCallback(() => {\n setMaximized((prev) => !prev);\n setResizeHeight(null);\n }, []);\n\n // Sessions not already opened in tabs\n const availableSessions = (sessions ?? []).filter((s) => !tabs.some((t) => t.sessionId === s.id));\n\n // Filtered by search term\n const filteredPickerSessions = sessionSearch.trim()\n ? availableSessions.filter((s) =>\n s.name.toLowerCase().includes(sessionSearch.trim().toLowerCase())\n )\n : availableSessions;\n\n // Track connection status via iframe load/error events\n const handleIframeLoad = useCallback(\n (sessionId: string) => {\n if (connectionTimeoutTimers.current[sessionId]) {\n clearTimeout(connectionTimeoutTimers.current[sessionId]);\n delete connectionTimeoutTimers.current[sessionId];\n }\n setConnectionStatusForTab(sessionId, 'connected');\n setConnectionIssueForTab(sessionId, undefined);\n },\n [setConnectionIssueForTab, setConnectionStatusForTab]\n );\n\n const handleIframeError = useCallback(\n (sessionId: string) => {\n if (connectionTimeoutTimers.current[sessionId]) {\n clearTimeout(connectionTimeoutTimers.current[sessionId]);\n delete connectionTimeoutTimers.current[sessionId];\n }\n setConnectionStatusForTab(sessionId, 'error');\n },\n [setConnectionStatusForTab]\n );\n\n // Set connecting status when a tab starts loading\n useEffect(() => {\n tabs.forEach((tab) => {\n if (!tab.terminalUrl || tab.loading || tab.error) {\n return;\n }\n\n const terminalUrlIssue = getTerminalUrlIssue(tab.terminalUrl);\n if (terminalUrlIssue) {\n setConnectionStatusForTab(tab.sessionId, 'error');\n setConnectionIssueForTab(tab.sessionId, terminalUrlIssue);\n return;\n }\n\n if (!connectionStatus[tab.sessionId]) {\n setConnectionStatusForTab(tab.sessionId, 'connecting');\n }\n\n if (\n connectionStatus[tab.sessionId] === 'connecting' &&\n !connectionTimeoutTimers.current[tab.sessionId]\n ) {\n connectionTimeoutTimers.current[tab.sessionId] = setTimeout(() => {\n delete connectionTimeoutTimers.current[tab.sessionId];\n setConnectionStatusForTab(tab.sessionId, 'error');\n setConnectionIssueForTab(\n tab.sessionId,\n tr(\n 'vibecontrols.sessions.terminal.connectionTimedOut',\n 'Timed out waiting for the terminal to connect. Check the agent tunnel and retry.'\n )\n );\n }, 12_000);\n }\n });\n\n return () => {\n for (const timerId of Object.values(connectionTimeoutTimers.current)) {\n clearTimeout(timerId);\n }\n connectionTimeoutTimers.current = {};\n };\n }, [\n connectionStatus,\n getTerminalUrlIssue,\n setConnectionIssueForTab,\n setConnectionStatusForTab,\n tabs,\n tr,\n ]);\n\n // Clear auto-reconnect timer for a session\n const clearAutoReconnect = useCallback((sessionId: string) => {\n if (autoReconnectTimers.current[sessionId]) {\n clearTimeout(autoReconnectTimers.current[sessionId]);\n delete autoReconnectTimers.current[sessionId];\n }\n delete autoReconnectAttempts.current[sessionId];\n }, []);\n\n // Reconnect handler — reload the iframe\n const handleReconnect = useCallback(\n (sessionId: string) => {\n const iframe = iframeRefs.current[sessionId];\n if (iframe) {\n clearAutoReconnect(sessionId);\n setConnectionIssues((prev) => ({ ...prev, [sessionId]: undefined }));\n setConnectionStatus((prev) => ({ ...prev, [sessionId]: 'connecting' }));\n // Force reload by briefly clearing src then setting it back\n const src = iframe.src;\n iframe.src = 'about:blank';\n requestAnimationFrame(() => {\n iframe.src = src;\n });\n }\n },\n [clearAutoReconnect]\n );\n\n // Auto-reconnect: when a tab enters error state, schedule automatic retry\n // Uses exponential backoff: 2s, 4s, 8s, 16s, 30s (capped), up to 15 attempts\n const MAX_AUTO_RECONNECT_ATTEMPTS = 15;\n const BASE_RECONNECT_DELAY_MS = 2_000;\n const MAX_RECONNECT_DELAY_MS = 30_000;\n\n useEffect(() => {\n for (const tab of tabs) {\n const status = connectionStatus[tab.sessionId];\n\n if (status === 'error' && tab.terminalUrl && !tab.error && !connectionIssues[tab.sessionId]) {\n // Terminal iframe lost connection (agent restart, network blip) — auto-retry\n if (autoReconnectTimers.current[tab.sessionId]) continue; // already scheduled\n\n const attempt = autoReconnectAttempts.current[tab.sessionId] ?? 0;\n if (attempt >= MAX_AUTO_RECONNECT_ATTEMPTS) continue; // give up after max attempts\n\n const delay = Math.min(\n BASE_RECONNECT_DELAY_MS * Math.pow(2, attempt),\n MAX_RECONNECT_DELAY_MS\n );\n\n autoReconnectTimers.current[tab.sessionId] = setTimeout(() => {\n delete autoReconnectTimers.current[tab.sessionId];\n autoReconnectAttempts.current[tab.sessionId] = attempt + 1;\n\n const iframe = iframeRefs.current[tab.sessionId];\n if (!iframe) return;\n\n // Probe the agent health before reloading the iframe\n // Extract the agent base URL from the terminal URL\n const terminalUrl = tab.terminalUrl!;\n const urlObj = new URL(terminalUrl);\n const healthUrl = `${urlObj.origin}/health`;\n\n fetch(healthUrl, { signal: AbortSignal.timeout(5_000) })\n .then((res) => {\n if (res.ok) {\n // Agent is back — reload the iframe\n setConnectionStatus((prev) => ({\n ...prev,\n [tab.sessionId]: 'connecting',\n }));\n const src = iframe.src;\n iframe.src = 'about:blank';\n requestAnimationFrame(() => {\n iframe.src = src;\n });\n }\n // If not ok, the next interval tick will schedule another attempt\n })\n .catch(() => {\n // Agent still down — will retry on next tick\n });\n }, delay);\n }\n\n // Reset attempts on successful connection\n if (status === 'connected') {\n clearAutoReconnect(tab.sessionId);\n }\n }\n\n // Capture ref for cleanup (react-hooks/exhaustive-deps)\n const timers = autoReconnectTimers.current;\n return () => {\n for (const timerId of Object.values(timers)) {\n clearTimeout(timerId);\n }\n };\n }, [tabs, connectionIssues, connectionStatus, clearAutoReconnect]);\n\n // Drag-to-resize handlers (only for non-fullscreen, non-maximized)\n const handleResizeStart = useCallback(\n (e: React.MouseEvent) => {\n if (fullScreen || maximized) return;\n e.preventDefault();\n resizingRef.current = true;\n startYRef.current = e.clientY;\n startHeightRef.current = panelRef.current?.offsetHeight || 420;\n document.body.style.cursor = 'ns-resize';\n document.body.style.userSelect = 'none';\n },\n [fullScreen, maximized]\n );\n\n useEffect(() => {\n const handleMouseMove = (e: MouseEvent) => {\n if (!resizingRef.current) return;\n const delta = startYRef.current - e.clientY;\n const newHeight = Math.max(\n 200,\n Math.min(startHeightRef.current + delta, window.innerHeight - 150)\n );\n setResizeHeight(newHeight);\n };\n const handleMouseUp = () => {\n if (resizingRef.current) {\n resizingRef.current = false;\n document.body.style.cursor = '';\n document.body.style.userSelect = '';\n }\n };\n document.addEventListener('mousemove', handleMouseMove);\n document.addEventListener('mouseup', handleMouseUp);\n return () => {\n document.removeEventListener('mousemove', handleMouseMove);\n document.removeEventListener('mouseup', handleMouseUp);\n };\n }, []);\n\n // Context menu handler\n const handleContextMenu = useCallback((e: React.MouseEvent, sessionId: string) => {\n e.preventDefault();\n e.stopPropagation();\n setContextMenu({\n visible: true,\n x: e.clientX,\n y: e.clientY,\n sessionId,\n });\n }, []);\n\n // Close context menu on outside click\n useEffect(() => {\n const handleClick = (e: MouseEvent) => {\n if (contextMenuRef.current && !contextMenuRef.current.contains(e.target as Node)) {\n setContextMenu((prev) => ({ ...prev, visible: false }));\n }\n };\n if (contextMenu.visible) {\n document.addEventListener('mousedown', handleClick);\n return () => document.removeEventListener('mousedown', handleClick);\n }\n }, [contextMenu.visible]);\n\n // Close session picker on outside click\n useEffect(() => {\n const handleClick = (e: MouseEvent) => {\n const target = e.target as Node;\n if (\n sessionPickerRef.current &&\n !sessionPickerRef.current.contains(target) &&\n plusBtnRef.current &&\n !plusBtnRef.current.contains(target)\n ) {\n setShowSessionPicker(false);\n setSessionSearch('');\n }\n };\n if (showSessionPicker) {\n document.addEventListener('mousedown', handleClick);\n return () => document.removeEventListener('mousedown', handleClick);\n }\n }, [showSessionPicker]);\n\n // Focus search input when session picker opens\n useEffect(() => {\n if (showSessionPicker && sessionSearchRef.current) {\n requestAnimationFrame(() => sessionSearchRef.current?.focus());\n }\n }, [showSessionPicker]);\n\n // Focus rename input when renaming starts\n useEffect(() => {\n if (renamingTabId && renameInputRef.current) {\n renameInputRef.current.focus();\n renameInputRef.current.select();\n }\n }, [renamingTabId]);\n\n // ESC key to exit immersive fullscreen\n useEffect(() => {\n if (!isImmersiveFullscreen || !onToggleImmersiveFullscreen) return;\n\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n // Don't prevent default - let browser handle native ESC behaviors\n onToggleImmersiveFullscreen();\n }\n };\n\n document.addEventListener('keydown', handleKeyDown);\n return () => document.removeEventListener('keydown', handleKeyDown);\n }, [isImmersiveFullscreen, onToggleImmersiveFullscreen]);\n\n const handleStartRename = useCallback(\n (sessionId: string) => {\n const tab = tabs.find((t) => t.sessionId === sessionId);\n if (tab) {\n setRenamingTabId(sessionId);\n setRenameValue(tab.name);\n }\n setContextMenu((prev) => ({ ...prev, visible: false }));\n },\n [tabs]\n );\n\n const handleFinishRename = useCallback(() => {\n if (renamingTabId && renameValue.trim() && onRenameSession) {\n const tab = tabs.find((t) => t.sessionId === renamingTabId);\n if (tab && renameValue.trim() !== tab.name) {\n onRenameSession(renamingTabId, renameValue.trim());\n }\n }\n setRenamingTabId(null);\n setRenameValue('');\n }, [renamingTabId, renameValue, onRenameSession, tabs]);\n\n const handleRenameKeyDown = useCallback(\n (e: React.KeyboardEvent) => {\n if (e.key === 'Enter') {\n handleFinishRename();\n } else if (e.key === 'Escape') {\n setRenamingTabId(null);\n setRenameValue('');\n }\n },\n [handleFinishRename]\n );\n\n if (tabs.length === 0 && !collapsed && !fullScreen) {\n return null;\n }\n\n // Determine panel height\n const panelHeight = fullScreen\n ? 'h-full'\n : maximized\n ? 'h-[calc(100vh-120px)]'\n : resizeHeight\n ? undefined // use inline style instead\n : heightClass;\n\n const panelStyle =\n !fullScreen && !maximized && resizeHeight ? { height: `${resizeHeight}px` } : undefined;\n\n const contextTab = contextMenu.sessionId\n ? tabs.find((t) => t.sessionId === contextMenu.sessionId)\n : null;\n\n // Helper to render connection status indicator\n const renderStatusIndicator = (tab: TerminalTab) => {\n if (tab.loading) {\n return <Loader2 className=\"size-3 animate-spin shrink-0\" />;\n }\n if (tab.error) {\n return <AlertCircle className=\"size-3 text-status-error-text shrink-0\" />;\n }\n const status = connectionStatus[tab.sessionId];\n if (status === 'connected') {\n return <Wifi className=\"size-3 text-status-success-text shrink-0\" />;\n }\n if (status === 'error') {\n return <WifiOff className=\"size-3 text-status-error-text shrink-0\" />;\n }\n if (status === 'connecting') {\n return <Loader2 className=\"size-3 animate-spin text-status-warning-text shrink-0\" />;\n }\n return <Terminal className=\"size-3 shrink-0\" />;\n };\n\n // Render the immersive fullscreen overlay\n const renderImmersiveFullscreen = () => {\n if (!isImmersiveFullscreen) return null;\n\n return createPortal(\n <div className=\"fixed inset-0 z-[9999] bg-bg-surface flex flex-col\">\n {/* Immersive Fullscreen Header - minimal with context switcher */}\n <div className=\"flex items-center justify-between px-3 py-2 bg-bg-sunken border-b border-border-default shrink-0\">\n {/* Context Switcher */}\n <div className=\"flex items-center gap-4\">{contextSwitcher}</div>\n\n {/* Actions */}\n <div className=\"flex items-center gap-2\">\n {/* Reconnect button for disconnected terminals */}\n {activeTab && connectionStatus[activeTab.sessionId] === 'error' && (\n <button\n type=\"button\"\n onClick={() => handleReconnect(activeTab.sessionId)}\n className=\"p-1.5 text-status-warning-text hover:text-status-warning-text/80 transition-colors\"\n title=\"Reconnect terminal\"\n >\n <RotateCcw className=\"size-4\" />\n </button>\n )}\n {activeTab?.terminalUrl && (\n <button\n type=\"button\"\n onClick={() => activeTab && onOpenExternal(activeTab.sessionId)}\n className=\"p-1.5 text-text-secondary hover:text-text-primary transition-colors\"\n title=\"Open in new tab\"\n >\n <ExternalLink className=\"size-4\" />\n </button>\n )}\n <button\n type=\"button\"\n onClick={onToggleImmersiveFullscreen}\n className=\"p-1.5 text-text-secondary hover:text-text-primary transition-colors\"\n title=\"Exit fullscreen (ESC)\"\n >\n <Shrink className=\"size-4\" />\n </button>\n </div>\n </div>\n\n {/* Tab Bar */}\n <div className=\"flex items-center bg-bg-sunken border-b border-border-default shrink-0\">\n {/* Tab list */}\n <div className=\"flex items-center overflow-x-auto flex-1 min-w-0\">\n {tabs.map((tab) => (\n <div\n role=\"button\"\n tabIndex={0}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n (() => onTabSelect(tab.sessionId))();\n }\n }}\n key={tab.sessionId}\n className={`flex items-center gap-1.5 px-3 py-1.5 text-xs border-r border-border-subtle cursor-pointer transition-colors min-w-0 max-w-[180px] group ${\n tab.sessionId === activeTab?.sessionId\n ? 'bg-bg-surface text-text-primary border-b-2 border-b-action-primary-bg'\n : 'text-text-secondary hover:text-text-primary hover:bg-bg-surface/50'\n }`}\n onClick={() => onTabSelect(tab.sessionId)}\n onContextMenu={(e) => handleContextMenu(e, tab.sessionId)}\n onDoubleClick={() => handleStartRename(tab.sessionId)}\n >\n {renderStatusIndicator(tab)}\n {renamingTabId === tab.sessionId ? (\n <input\n aria-label=\"control\"\n ref={renameInputRef}\n value={renameValue}\n onChange={(e) => setRenameValue(e.target.value)}\n onBlur={handleFinishRename}\n onKeyDown={handleRenameKeyDown}\n onClick={(e) => e.stopPropagation()}\n className=\"bg-bg-sunken border border-border-strong rounded px-1 py-0 text-xs text-text-primary w-[100px] outline-none\"\n />\n ) : (\n <span className=\"truncate\">{tab.name}</span>\n )}\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onTabClose(tab.sessionId);\n }}\n className=\"ml-auto opacity-0 group-hover:opacity-100 hover:text-status-error-text transition-opacity shrink-0\"\n title=\"Close terminal\"\n >\n <X className=\"size-3\" />\n </button>\n </div>\n ))}\n </div>\n\n {/* Add tab \"+\" button */}\n <button\n type=\"button\"\n ref={plusBtnRef}\n onClick={() => {\n if (onCreateSession || availableSessions.length > 0) {\n setShowSessionPicker((prev) => !prev);\n setSessionSearch('');\n }\n }}\n className=\"flex items-center px-2.5 py-1.5 text-text-secondary hover:text-text-primary transition-colors shrink-0 border-r border-border-subtle\"\n title=\"New session or open existing\"\n >\n <Plus className=\"size-3.5\" />\n </button>\n </div>\n\n {/* Terminal Content - takes remaining space */}\n <div className=\"flex-1 bg-bg-sunken text-text-primary relative overflow-hidden\">\n {activeTab ? (\n tabs.map((tab) => {\n const status = connectionStatus[tab.sessionId];\n const isConnecting = status === 'connecting' || tab.loading;\n const terminalUrlIssue = getTerminalUrlIssue(tab.terminalUrl);\n const issue = terminalUrlIssue ?? connectionIssues[tab.sessionId];\n const showIframe = !!tab.terminalUrl && !tab.error && !terminalUrlIssue;\n const showConnectionIssue = !!terminalUrlIssue || (showIframe && status === 'error');\n\n return (\n <div\n key={tab.sessionId}\n className={`absolute inset-0 ${tab.sessionId === activeTab.sessionId ? 'block' : 'hidden'}`}\n >\n {/* Always render iframe if URL exists (keeps terminal state) */}\n {showIframe && tab.terminalUrl && (\n <TerminalIframe\n ref={(el) => {\n iframeRefs.current[tab.sessionId] = el;\n }}\n sessionId={tab.sessionId}\n agentId={tab.agentId ?? null}\n baseTtydUrl={tab.terminalUrl}\n title={`Terminal - ${tab.name}`}\n className=\"w-full h-full border-0\"\n allow=\"clipboard-read *; clipboard-write *\"\n onLoad={() => handleIframeLoad(tab.sessionId)}\n onError={() => handleIframeError(tab.sessionId)}\n />\n )}\n\n {/* Loading/Connecting overlay */}\n {showIframe && isConnecting && (\n <div className=\"absolute inset-0 bg-bg-overlay backdrop-blur-[2px] flex items-center justify-center pointer-events-none z-10\">\n <div className=\"flex items-center gap-3 px-4 py-3 bg-bg-surface/95 rounded-lg border border-border-default shadow-lg\">\n <Loader2 className=\"size-5 text-status-warning-text animate-spin\" />\n <div>\n <p className=\"text-text-primary text-sm font-medium\">\n {tab.loading\n ? tr(\n 'vibecontrols.sessions.terminal.startingTerminal',\n 'Starting terminal...'\n )\n : tr('vibecontrols.sessions.terminal.connecting', 'Connecting...')}\n </p>\n <p className=\"text-text-secondary text-xs\">\n {tab.loading\n ? tr(\n 'vibecontrols.sessions.terminal.settingUpTmux',\n 'Setting up tmux + ttyd'\n )\n : tr(\n 'vibecontrols.sessions.terminal.establishingConnection',\n 'Establishing connection'\n )}\n </p>\n </div>\n </div>\n </div>\n )}\n\n {/* Disconnected overlay - auto-reconnect is active in background */}\n {showConnectionIssue && !tab.error && (\n <div className=\"absolute inset-0 bg-bg-overlay backdrop-blur-[2px] flex items-center justify-center z-10\">\n <div className=\"flex flex-col items-center gap-3 px-6 py-4 bg-bg-surface/95 rounded-lg border border-border-default shadow-lg\">\n <WifiOff className=\"size-6 text-status-error-text\" />\n <div className=\"text-center\">\n <p className=\"text-text-primary text-sm font-medium\">\n {tr('vibecontrols.sessions.terminal.connectionLost', 'Connection Lost')}\n </p>\n <p className=\"text-text-secondary text-xs mt-1\">\n {issue\n ? issue\n : (autoReconnectAttempts.current[tab.sessionId] ?? 0) <\n MAX_AUTO_RECONNECT_ATTEMPTS\n ? tr(\n 'vibecontrols.sessions.terminal.reconnectingAutomatically',\n 'Reconnecting automatically...'\n )\n : tr(\n 'vibecontrols.sessions.terminal.connectionInterrupted',\n 'Terminal connection was interrupted'\n )}\n </p>\n </div>\n {!issue &&\n (autoReconnectAttempts.current[tab.sessionId] ?? 0) <\n MAX_AUTO_RECONNECT_ATTEMPTS && (\n <Loader2 className=\"size-4 text-text-tertiary animate-spin\" />\n )}\n <button\n type=\"button\"\n onClick={() => handleReconnect(tab.sessionId)}\n className=\"px-4 py-2 bg-bg-sunken text-text-primary text-sm rounded-md hover:bg-bg-sunken/80 transition-colors inline-flex items-center gap-2\"\n >\n <RotateCcw className=\"size-3.5\" />\n {tr('vibecontrols.sessions.terminal.reconnectNow', 'Reconnect Now')}\n </button>\n </div>\n </div>\n )}\n\n {/* No URL yet */}\n {!tab.terminalUrl && tab.loading && (\n <div className=\"flex items-center justify-center h-full\">\n <div className=\"text-center\">\n <Loader2 className=\"size-8 text-text-tertiary animate-spin mx-auto mb-3\" />\n <p className=\"text-text-tertiary text-sm\">\n Starting terminal for {tab.name}...\n </p>\n <p className=\"text-text-secondary text-xs mt-1\">\n Setting up tmux + ttyd terminal\n </p>\n </div>\n </div>\n )}\n\n {/* Error state */}\n {tab.error && (\n <div className=\"flex items-center justify-center h-full\">\n <div className=\"text-center max-w-md\">\n <AlertCircle className=\"size-8 text-status-error-text mx-auto mb-3\" />\n <p className=\"text-status-error-text text-sm font-medium\">\n {tr(\n 'vibecontrols.sessions.terminal.failedToStart',\n 'Failed to start terminal'\n )}\n </p>\n <p className=\"text-text-tertiary text-xs mt-2\">{tab.error}</p>\n <div className=\"flex items-center justify-center gap-3 mt-4\">\n {onRetryTerminal && sessions && (\n <button\n type=\"button\"\n onClick={() => {\n const session = sessions.find((s) => s.id === tab.sessionId);\n if (session) onRetryTerminal(session);\n }}\n className=\"px-4 py-2 bg-bg-sunken text-text-primary text-sm rounded-md hover:bg-bg-sunken/80 transition-colors inline-flex items-center gap-2\"\n >\n <RotateCcw className=\"size-3.5\" />\n {tr('vibecontrols.sessions.terminal.retry', 'Retry')}\n </button>\n )}\n </div>\n </div>\n </div>\n )}\n\n {/* No terminal URL and not loading — actionable CTA */}\n {!tab.terminalUrl && !tab.loading && !tab.error && (\n <div className=\"flex items-center justify-center h-full\">\n <div className=\"text-center max-w-sm space-y-3\">\n <Terminal className=\"size-8 text-text-secondary mx-auto\" />\n <p className=\"text-text-primary text-sm font-medium\">\n {tr(\n 'vibecontrols.sessions.terminal.terminalNotStarted',\n 'Terminal not started yet'\n )}\n </p>\n <p className=\"text-text-secondary text-xs\">\n {tr(\n 'vibecontrols.sessions.terminal.startTerminalHint',\n 'Click below to start a ttyd terminal on the agent for this session.'\n )}\n </p>\n {onRetryTerminal && sessions && (\n <button\n type=\"button\"\n onClick={() => {\n const session = sessions.find((s) => s.id === tab.sessionId);\n if (session) onRetryTerminal(session);\n }}\n className=\"px-4 py-2 bg-accent-primary text-white text-sm rounded-md hover:bg-accent-primary/90 transition-colors inline-flex items-center gap-2\"\n >\n <Terminal className=\"size-3.5\" />\n {tr('vibecontrols.sessions.terminal.startTerminal', 'Start terminal')}\n </button>\n )}\n </div>\n </div>\n )}\n </div>\n );\n })\n ) : (\n /* Empty state for fullscreen with no tabs */\n <div className=\"flex items-center justify-center h-full\">\n <div className=\"text-center max-w-md\">\n <Terminal className=\"size-12 text-text-secondary mx-auto mb-4\" />\n <p className=\"text-text-secondary text-base font-medium mb-2\">\n {tr('vibecontrols.sessions.terminal.noTerminalsOpen', 'No terminals open')}\n </p>\n <p className=\"text-text-secondary text-sm mb-4\">\n {tr(\n 'vibecontrols.sessions.terminal.clickPlusTab',\n 'Click the + tab above to create a new session or open an existing one'\n )}\n </p>\n {onCreateSession && (\n <button\n type=\"button\"\n onClick={onCreateSession}\n className=\"px-4 py-2 bg-bg-sunken text-text-primary text-sm rounded-md hover:bg-bg-sunken/80 transition-colors inline-flex items-center gap-2\"\n >\n <Plus className=\"size-4\" />\n {tr('vibecontrols.sessions.terminal.newSession', 'New Session')}\n </button>\n )}\n </div>\n </div>\n )}\n </div>\n\n {/* Session Picker Dropdown (for immersive fullscreen) */}\n {showSessionPicker &&\n (onCreateSession || availableSessions.length > 0) &&\n plusBtnRef.current && (\n <div\n ref={sessionPickerRef}\n className=\"fixed bg-bg-elevated border border-border-default rounded-md shadow-xl z-[10000] w-[280px]\"\n style={{\n top: plusBtnRef.current.getBoundingClientRect().bottom + 4,\n left: Math.min(\n plusBtnRef.current.getBoundingClientRect().left,\n window.innerWidth - 290\n ),\n }}\n >\n {/* Search box */}\n <div className=\"p-2 border-b border-border-subtle\">\n <div className=\"flex items-center gap-2 px-2 py-1.5 bg-bg-sunken rounded border border-border-subtle focus-within:border-border-strong\">\n <Search className=\"size-3.5 text-text-muted shrink-0\" />\n <input\n aria-label=\"Search sessions...\"\n ref={sessionSearchRef}\n type=\"text\"\n value={sessionSearch}\n onChange={(e) => setSessionSearch(e.target.value)}\n placeholder=\"Search sessions...\"\n className=\"w-full bg-bg-surface/0 text-sm text-text-primary outline-none placeholder:text-text-placeholder\"\n />\n {sessionSearch && (\n <button\n type=\"button\"\n onClick={() => setSessionSearch('')}\n className=\"text-text-muted hover:text-text-primary shrink-0\"\n >\n <X className=\"size-3\" />\n </button>\n )}\n </div>\n </div>\n\n {/* New Session action */}\n {onCreateSession && (\n <button\n type=\"button\"\n onClick={() => {\n onCreateSession();\n setShowSessionPicker(false);\n setSessionSearch('');\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors text-left font-medium\"\n >\n <Plus className=\"size-3.5 text-text-secondary\" />\n <span>{tr('vibecontrols.sessions.terminal.newSession', 'New Session')}</span>\n </button>\n )}\n\n {/* Open existing section */}\n {availableSessions.length > 0 && (\n <>\n <div className=\"border-t border-border-subtle\" />\n <div className=\"px-3 pt-2 pb-1\">\n <span className=\"text-[10px] font-medium uppercase tracking-wider text-text-muted\">\n Open existing ({availableSessions.length})\n </span>\n </div>\n <div className=\"max-h-48 overflow-y-auto pb-1\">\n {filteredPickerSessions.length > 0 ? (\n filteredPickerSessions.map((s) => (\n <button\n type=\"button\"\n key={s.id}\n onClick={() => {\n onOpenTerminal?.(s);\n setShowSessionPicker(false);\n setSessionSearch('');\n }}\n className=\"flex items-center gap-2 w-full px-3 py-1.5 text-sm text-text-primary hover:bg-bg-sunken transition-colors text-left\"\n >\n <Terminal className=\"size-3.5 text-text-secondary shrink-0\" />\n <span className=\"truncate\">{s.name}</span>\n <span\n className={`ml-auto text-[10px] px-1.5 py-0.5 rounded shrink-0 ${\n s.status === 'RUNNING'\n ? 'bg-status-success-text/10 text-status-success-text'\n : s.status === 'STOPPED' || s.status === 'TERMINATED'\n ? 'bg-text-secondary/10 text-text-tertiary'\n : 'bg-status-warning-text/10 text-status-warning-text'\n }`}\n >\n {s.status.toLowerCase()}\n </span>\n </button>\n ))\n ) : (\n <div className=\"px-3 py-2 text-xs text-text-muted\">\n No sessions matching “{sessionSearch}”\n </div>\n )}\n </div>\n </>\n )}\n </div>\n )}\n\n {/* Context Menu (for immersive fullscreen) */}\n {contextMenu.visible && contextTab && (\n <div\n ref={contextMenuRef}\n className=\"fixed bg-bg-elevated border border-border-default rounded-lg shadow-lg z-[10000] min-w-[180px] py-1\"\n style={{ top: contextMenu.y, left: contextMenu.x }}\n >\n {onRenameSession && (\n <button\n type=\"button\"\n onClick={() => handleStartRename(contextTab.sessionId)}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n <Edit3 className=\"size-3.5\" />\n {tr('vibecontrols.sessions.terminal.rename', 'Rename')}\n </button>\n )}\n {contextTab.terminalUrl && (\n <button\n type=\"button\"\n onClick={() => {\n onOpenExternal(contextTab.sessionId);\n setContextMenu((prev) => ({ ...prev, visible: false }));\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n <ExternalLink className=\"size-3.5\" />\n {tr('vibecontrols.sessions.terminal.openInNewTab', 'Open in New Tab')}\n </button>\n )}\n {contextTab.terminalUrl && connectionStatus[contextTab.sessionId] === 'error' && (\n <button\n type=\"button\"\n onClick={() => {\n handleReconnect(contextTab.sessionId);\n setContextMenu((prev) => ({ ...prev, visible: false }));\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-status-warning-text hover:bg-bg-sunken transition-colors\"\n >\n <RotateCcw className=\"size-3.5\" />\n {tr('vibecontrols.sessions.terminal.reconnect', 'Reconnect')}\n </button>\n )}\n {onRestartTerminal && (\n <button\n type=\"button\"\n onClick={() => {\n onRestartTerminal(contextTab.sessionId);\n setContextMenu((prev) => ({ ...prev, visible: false }));\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n <RotateCcw className=\"size-3.5\" />\n {tr('vibecontrols.sessions.terminal.restartTerminal', 'Restart Terminal')}\n </button>\n )}\n {onStopTerminal && (\n <button\n type=\"button\"\n onClick={() => {\n onStopTerminal(contextTab.sessionId);\n setContextMenu((prev) => ({ ...prev, visible: false }));\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n <Square className=\"size-3.5\" />\n {tr('vibecontrols.sessions.terminal.stopTerminal', 'Stop Terminal')}\n </button>\n )}\n {onStopSession && (\n <button\n type=\"button\"\n onClick={() => {\n onStopSession(contextTab.sessionId);\n setContextMenu((prev) => ({ ...prev, visible: false }));\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-status-warning-text hover:bg-bg-sunken transition-colors\"\n >\n <Square className=\"size-3.5\" />\n {tr('vibecontrols.sessions.terminal.stopSession', 'Stop Session')}\n </button>\n )}\n <div className=\"border-t border-border-subtle my-1\" />\n <button\n type=\"button\"\n onClick={() => {\n onTabClose(contextTab.sessionId);\n setContextMenu((prev) => ({ ...prev, visible: false }));\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-status-error-text hover:bg-bg-sunken transition-colors\"\n >\n <X className=\"size-3.5\" />\n {tr('vibecontrols.sessions.terminal.closeTab', 'Close Tab')}\n </button>\n </div>\n )}\n </div>,\n document.body\n );\n };\n\n // When in immersive fullscreen, render the portal and hide the normal panel\n if (isImmersiveFullscreen) {\n return renderImmersiveFullscreen();\n }\n\n return (\n <div\n ref={panelRef}\n className={`${fullScreen ? 'flex flex-col h-full' : ''} border-t border-border-default bg-bg-surface ${collapsed && tabs.length === 0 && !fullScreen ? 'hidden' : ''}`}\n >\n {/* Drag-to-resize handle (top edge of terminal panel) */}\n {!fullScreen && !collapsed && tabs.length > 0 && (\n <div\n onMouseDown={handleResizeStart}\n className=\"h-1 cursor-ns-resize hover:bg-action-primary-bg/30 transition-colors flex items-center justify-center group\"\n title=\"Drag to resize\"\n >\n <GripVertical className=\"w-4 h-3 text-text-muted opacity-0 group-hover:opacity-100 transition-opacity rotate-90\" />\n </div>\n )}\n\n {/* Tab Bar */}\n <div className=\"flex items-center bg-bg-sunken border-b border-border-default\">\n {/* Collapse toggle — only in non-fullscreen mode */}\n {!fullScreen && (\n <button\n type=\"button\"\n onClick={onToggleCollapse}\n className=\"flex items-center gap-1 px-3 py-2 text-text-secondary hover:text-text-primary transition-colors text-xs font-medium uppercase tracking-wide\"\n title={collapsed ? 'Expand terminal' : 'Collapse terminal'}\n >\n <Terminal className=\"size-3.5\" />\n Terminal\n {collapsed ? <ChevronUp className=\"size-3\" /> : <ChevronDown className=\"size-3\" />}\n </button>\n )}\n\n {/* Tab list */}\n {(!collapsed || fullScreen) && (\n <div\n className={`flex items-center overflow-x-auto ${fullScreen ? 'flex-1' : 'flex-1'} min-w-0`}\n >\n {tabs.map((tab) => (\n <div\n role=\"button\"\n tabIndex={0}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n (() => onTabSelect(tab.sessionId))();\n }\n }}\n key={tab.sessionId}\n className={`flex items-center gap-1.5 px-3 py-1.5 text-xs border-r border-border-subtle cursor-pointer transition-colors min-w-0 max-w-[180px] group ${\n tab.sessionId === activeTab?.sessionId\n ? 'bg-bg-surface text-text-primary border-b-2 border-b-action-primary-bg'\n : 'text-text-secondary hover:text-text-primary hover:bg-bg-surface/50'\n }`}\n onClick={() => onTabSelect(tab.sessionId)}\n onContextMenu={(e) => handleContextMenu(e, tab.sessionId)}\n onDoubleClick={() => handleStartRename(tab.sessionId)}\n >\n {renderStatusIndicator(tab)}\n {renamingTabId === tab.sessionId ? (\n <input\n aria-label=\"control\"\n ref={renameInputRef}\n value={renameValue}\n onChange={(e) => setRenameValue(e.target.value)}\n onBlur={handleFinishRename}\n onKeyDown={handleRenameKeyDown}\n onClick={(e) => e.stopPropagation()}\n className=\"bg-bg-sunken border border-border-strong rounded px-1 py-0 text-xs text-text-primary w-[100px] outline-none\"\n />\n ) : (\n <span className=\"truncate\">{tab.name}</span>\n )}\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onTabClose(tab.sessionId);\n }}\n className=\"ml-auto opacity-0 group-hover:opacity-100 hover:text-status-error-text transition-opacity shrink-0\"\n title=\"Close terminal\"\n >\n <X className=\"size-3\" />\n </button>\n </div>\n ))}\n </div>\n )}\n\n {/* Add tab \"+\" button — outside the overflow container so the dropdown isn't clipped */}\n {(!collapsed || fullScreen) && (\n <button\n type=\"button\"\n ref={plusBtnRef}\n onClick={() => {\n if (onCreateSession || availableSessions.length > 0) {\n setShowSessionPicker((prev) => !prev);\n setSessionSearch('');\n }\n }}\n className=\"flex items-center px-2.5 py-1.5 text-text-secondary hover:text-text-primary transition-colors shrink-0 border-r border-border-subtle\"\n title=\"New session or open existing\"\n >\n <Plus className=\"size-3.5\" />\n </button>\n )}\n\n {/* Panel actions */}\n {(!collapsed || fullScreen) && tabs.length > 0 && (\n <div className=\"flex items-center gap-1 px-2 shrink-0\">\n {/* Reconnect button for disconnected terminals */}\n {activeTab && connectionStatus[activeTab.sessionId] === 'error' && (\n <button\n type=\"button\"\n onClick={() => handleReconnect(activeTab.sessionId)}\n className=\"p-1 text-status-warning-text hover:text-status-warning-text/80 transition-colors\"\n title=\"Reconnect terminal\"\n >\n <RotateCcw className=\"size-3.5\" />\n </button>\n )}\n {activeTab?.terminalUrl && (\n <button\n type=\"button\"\n onClick={() => activeTab && onOpenExternal(activeTab.sessionId)}\n className=\"p-1 text-text-secondary hover:text-text-primary transition-colors\"\n title=\"Open in new tab\"\n >\n <ExternalLink className=\"size-3.5\" />\n </button>\n )}\n {/* Immersive fullscreen toggle */}\n {onToggleImmersiveFullscreen && (\n <button\n type=\"button\"\n onClick={onToggleImmersiveFullscreen}\n className=\"p-1 text-text-secondary hover:text-text-primary transition-colors\"\n title={isImmersiveFullscreen ? 'Exit fullscreen' : 'Enter fullscreen'}\n >\n {isImmersiveFullscreen ? (\n <Shrink className=\"size-3.5\" />\n ) : (\n <Expand className=\"size-3.5\" />\n )}\n </button>\n )}\n {!fullScreen && !isImmersiveFullscreen && (\n <button\n type=\"button\"\n onClick={handleMaximize}\n className=\"p-1 text-text-secondary hover:text-text-primary transition-colors\"\n title={maximized ? 'Restore size' : 'Maximize'}\n >\n {maximized ? (\n <Minimize2 className=\"size-3.5\" />\n ) : (\n <Maximize2 className=\"size-3.5\" />\n )}\n </button>\n )}\n </div>\n )}\n </div>\n\n {/* Terminal Content */}\n {(!collapsed || fullScreen) && activeTab && (\n <div\n className={`${panelHeight || ''} bg-bg-sunken text-text-primary relative ${fullScreen ? 'flex-1' : ''}`}\n style={panelStyle}\n >\n {/* Render all tab iframes but only show the active one */}\n {tabs.map((tab) => {\n const status = connectionStatus[tab.sessionId];\n const isConnecting = status === 'connecting' || tab.loading;\n const terminalUrlIssue = getTerminalUrlIssue(tab.terminalUrl);\n const issue = terminalUrlIssue ?? connectionIssues[tab.sessionId];\n const showIframe = !!tab.terminalUrl && !tab.error && !terminalUrlIssue;\n const showConnectionIssue = !!terminalUrlIssue || (showIframe && status === 'error');\n\n return (\n <div\n key={tab.sessionId}\n className={`absolute inset-0 ${tab.sessionId === activeTab.sessionId ? 'block' : 'hidden'}`}\n >\n {/* Always render iframe if URL exists (keeps terminal state) */}\n {showIframe && tab.terminalUrl && (\n <TerminalIframe\n ref={(el) => {\n iframeRefs.current[tab.sessionId] = el;\n }}\n sessionId={tab.sessionId}\n agentId={tab.agentId ?? null}\n baseTtydUrl={tab.terminalUrl}\n title={`Terminal - ${tab.name}`}\n className=\"w-full h-full border-0\"\n allow=\"clipboard-read *; clipboard-write *\"\n onLoad={() => handleIframeLoad(tab.sessionId)}\n onError={() => handleIframeError(tab.sessionId)}\n />\n )}\n\n {/* Loading/Connecting overlay - shows on top of terminal without destroying it */}\n {showIframe && isConnecting && (\n <div className=\"absolute inset-0 bg-bg-overlay backdrop-blur-[2px] flex items-center justify-center pointer-events-none z-10\">\n <div className=\"flex items-center gap-3 px-4 py-3 bg-bg-surface/95 rounded-lg border border-border-default shadow-lg\">\n <Loader2 className=\"size-5 text-status-warning-text animate-spin\" />\n <div>\n <p className=\"text-text-primary text-sm font-medium\">\n {tab.loading\n ? tr(\n 'vibecontrols.sessions.terminal.startingTerminal',\n 'Starting terminal...'\n )\n : tr('vibecontrols.sessions.terminal.connecting', 'Connecting...')}\n </p>\n <p className=\"text-text-secondary text-xs\">\n {tab.loading\n ? tr(\n 'vibecontrols.sessions.terminal.settingUpTmux',\n 'Setting up tmux + ttyd'\n )\n : tr(\n 'vibecontrols.sessions.terminal.establishingConnection',\n 'Establishing connection'\n )}\n </p>\n </div>\n </div>\n </div>\n )}\n\n {/* Disconnected overlay - auto-reconnect is active in background */}\n {showConnectionIssue && !tab.error && (\n <div className=\"absolute inset-0 bg-bg-overlay backdrop-blur-[2px] flex items-center justify-center z-10\">\n <div className=\"flex flex-col items-center gap-3 px-6 py-4 bg-bg-surface/95 rounded-lg border border-border-default shadow-lg\">\n <WifiOff className=\"size-6 text-status-error-text\" />\n <div className=\"text-center\">\n <p className=\"text-text-primary text-sm font-medium\">\n {tr('vibecontrols.sessions.terminal.connectionLost', 'Connection Lost')}\n </p>\n <p className=\"text-text-secondary text-xs mt-1\">\n {issue\n ? issue\n : (autoReconnectAttempts.current[tab.sessionId] ?? 0) <\n MAX_AUTO_RECONNECT_ATTEMPTS\n ? tr(\n 'vibecontrols.sessions.terminal.reconnectingAutomatically',\n 'Reconnecting automatically...'\n )\n : tr(\n 'vibecontrols.sessions.terminal.connectionInterrupted',\n 'Terminal connection was interrupted'\n )}\n </p>\n </div>\n {!issue &&\n (autoReconnectAttempts.current[tab.sessionId] ?? 0) <\n MAX_AUTO_RECONNECT_ATTEMPTS && (\n <Loader2 className=\"size-4 text-text-tertiary animate-spin\" />\n )}\n <button\n type=\"button\"\n onClick={() => handleReconnect(tab.sessionId)}\n className=\"px-4 py-2 bg-bg-sunken text-text-primary text-sm rounded-md hover:bg-bg-sunken/80 transition-colors inline-flex items-center gap-2\"\n >\n <RotateCcw className=\"size-3.5\" />\n {tr('vibecontrols.sessions.terminal.reconnectNow', 'Reconnect Now')}\n </button>\n </div>\n </div>\n )}\n\n {/* No URL yet - initial loading state (no iframe to show behind) */}\n {!tab.terminalUrl && tab.loading && (\n <div className=\"flex items-center justify-center h-full\">\n <div className=\"text-center\">\n <Loader2 className=\"size-8 text-text-tertiary animate-spin mx-auto mb-3\" />\n <p className=\"text-text-tertiary text-sm\">\n Starting terminal for {tab.name}...\n </p>\n <p className=\"text-text-secondary text-xs mt-1\">\n Setting up tmux + ttyd terminal\n </p>\n </div>\n </div>\n )}\n\n {/* Error state - terminal failed to start */}\n {tab.error && (\n <div className=\"flex items-center justify-center h-full\">\n <div className=\"text-center max-w-md\">\n <AlertCircle className=\"size-8 text-status-error-text mx-auto mb-3\" />\n <p className=\"text-status-error-text text-sm font-medium\">\n {tr(\n 'vibecontrols.sessions.terminal.failedToStart',\n 'Failed to start terminal'\n )}\n </p>\n <p className=\"text-text-tertiary text-xs mt-2\">{tab.error}</p>\n <div className=\"flex items-center justify-center gap-3 mt-4\">\n {onRetryTerminal && sessions && (\n <button\n type=\"button\"\n onClick={() => {\n const session = sessions.find((s) => s.id === tab.sessionId);\n if (session) onRetryTerminal(session);\n }}\n className=\"px-4 py-2 bg-bg-sunken text-text-primary text-sm rounded-md hover:bg-bg-sunken/80 transition-colors inline-flex items-center gap-2\"\n >\n <RotateCcw className=\"size-3.5\" />\n {tr('vibecontrols.sessions.terminal.retry', 'Retry')}\n </button>\n )}\n </div>\n </div>\n </div>\n )}\n\n {/* No terminal URL and not loading - waiting for agent */}\n {!tab.terminalUrl && !tab.loading && !tab.error && (\n <div className=\"flex items-center justify-center h-full\">\n <div className=\"text-center\">\n <Terminal className=\"size-8 text-text-secondary mx-auto mb-3\" />\n <p className=\"text-text-tertiary text-sm\">\n {tr(\n 'vibecontrols.sessions.terminal.noTerminalUrl',\n 'No terminal URL available'\n )}\n </p>\n <p className=\"text-text-secondary text-xs mt-1\">\n {tr(\n 'vibecontrols.sessions.terminal.agentNeedsTtyd',\n 'The agent needs to start ttyd for this session'\n )}\n </p>\n </div>\n </div>\n )}\n </div>\n );\n })}\n </div>\n )}\n\n {/* Empty state for terminal-first mode with no tabs */}\n {(!collapsed || fullScreen) && tabs.length === 0 && fullScreen && (\n <div className=\"flex-1 bg-bg-sunken text-text-primary flex items-center justify-center\">\n <div className=\"text-center max-w-md\">\n <Terminal className=\"size-12 text-text-secondary mx-auto mb-4\" />\n <p className=\"text-text-secondary text-base font-medium mb-2\">\n {tr('vibecontrols.sessions.terminal.noTerminalsOpen', 'No terminals open')}\n </p>\n <p className=\"text-text-secondary text-sm mb-4\">\n {tr(\n 'vibecontrols.sessions.terminal.clickPlusTab',\n 'Click the + tab above to create a new session or open an existing one'\n )}\n </p>\n <div className=\"flex items-center justify-center gap-2 flex-wrap\">\n {onCreateSession && (\n <button\n type=\"button\"\n onClick={onCreateSession}\n className=\"px-4 py-2 bg-bg-sunken text-text-primary text-sm rounded-md hover:bg-bg-sunken/80 transition-colors inline-flex items-center gap-2\"\n >\n <Plus className=\"size-4\" />\n {tr('vibecontrols.sessions.terminal.newSession', 'New Session')}\n </button>\n )}\n </div>\n {availableSessions.length > 0 && (\n <div className=\"mt-6\">\n <p className=\"text-text-secondary text-xs uppercase tracking-wider mb-3\">\n Or open an existing session\n </p>\n <div className=\"flex flex-wrap justify-center gap-2\">\n {availableSessions.slice(0, 6).map((s) => (\n <button\n type=\"button\"\n key={s.id}\n onClick={() => onOpenTerminal?.(s)}\n className=\"flex items-center gap-1.5 px-3 py-1.5 bg-bg-canvas text-text-secondary text-xs rounded-md hover:bg-bg-sunken transition-colors border border-border-default\"\n >\n <Terminal className=\"size-3\" />\n <span className=\"truncate max-w-[120px]\">{s.name}</span>\n <span\n className={`size-1.5 rounded-full ${\n s.status === 'RUNNING' ? 'bg-status-success-text' : 'bg-text-secondary'\n }`}\n />\n </button>\n ))}\n {availableSessions.length > 6 && (\n <button\n type=\"button\"\n onClick={() => {\n setShowSessionPicker(true);\n setSessionSearch('');\n }}\n className=\"text-text-tertiary text-xs self-center hover:text-text-primary transition-colors cursor-pointer\"\n >\n +{availableSessions.length - 6} more\n </button>\n )}\n </div>\n </div>\n )}\n </div>\n </div>\n )}\n\n {/* Session Picker Dropdown (fixed position to escape overflow clipping) */}\n {showSessionPicker &&\n (onCreateSession || availableSessions.length > 0) &&\n plusBtnRef.current && (\n <div\n ref={sessionPickerRef}\n className=\"fixed bg-bg-elevated border border-border-default rounded-md shadow-xl z-[100] w-[280px]\"\n style={{\n top: plusBtnRef.current.getBoundingClientRect().bottom + 4,\n left: Math.min(\n plusBtnRef.current.getBoundingClientRect().left,\n window.innerWidth - 290\n ),\n }}\n >\n {/* Search box */}\n <div className=\"p-2 border-b border-border-subtle\">\n <div className=\"flex items-center gap-2 px-2 py-1.5 bg-bg-sunken rounded border border-border-subtle focus-within:border-border-strong\">\n <Search className=\"size-3.5 text-text-muted shrink-0\" />\n <input\n aria-label=\"Search sessions...\"\n ref={sessionSearchRef}\n type=\"text\"\n value={sessionSearch}\n onChange={(e) => setSessionSearch(e.target.value)}\n placeholder=\"Search sessions...\"\n className=\"w-full bg-bg-surface/0 text-sm text-text-primary outline-none placeholder:text-text-placeholder\"\n />\n {sessionSearch && (\n <button\n type=\"button\"\n onClick={() => setSessionSearch('')}\n className=\"text-text-muted hover:text-text-primary shrink-0\"\n >\n <X className=\"size-3\" />\n </button>\n )}\n </div>\n </div>\n\n {/* {tr('vibecontrols.sessions.terminal.newSession', 'New Session')} action */}\n {onCreateSession && (\n <button\n type=\"button\"\n onClick={() => {\n onCreateSession();\n setShowSessionPicker(false);\n setSessionSearch('');\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors text-left font-medium\"\n >\n <Plus className=\"size-3.5 text-text-secondary\" />\n <span>{tr('vibecontrols.sessions.terminal.newSession', 'New Session')}</span>\n </button>\n )}\n\n {/* Divider + Open existing section */}\n {availableSessions.length > 0 && (\n <>\n <div className=\"border-t border-border-subtle\" />\n <div className=\"px-3 pt-2 pb-1\">\n <span className=\"text-[10px] font-medium uppercase tracking-wider text-text-muted\">\n Open existing ({availableSessions.length})\n </span>\n </div>\n <div className=\"max-h-48 overflow-y-auto pb-1\">\n {filteredPickerSessions.length > 0 ? (\n filteredPickerSessions.map((s) => (\n <button\n type=\"button\"\n key={s.id}\n onClick={() => {\n onOpenTerminal?.(s);\n setShowSessionPicker(false);\n setSessionSearch('');\n }}\n className=\"flex items-center gap-2 w-full px-3 py-1.5 text-sm text-text-primary hover:bg-bg-sunken transition-colors text-left\"\n >\n <Terminal className=\"size-3.5 text-text-secondary shrink-0\" />\n <span className=\"truncate\">{s.name}</span>\n <span\n className={`ml-auto text-[10px] px-1.5 py-0.5 rounded shrink-0 ${\n s.status === 'RUNNING'\n ? 'bg-status-success-text/10 text-status-success-text'\n : s.status === 'STOPPED' || s.status === 'TERMINATED'\n ? 'bg-text-secondary/10 text-text-tertiary'\n : 'bg-status-warning-text/10 text-status-warning-text'\n }`}\n >\n {s.status.toLowerCase()}\n </span>\n </button>\n ))\n ) : (\n <div className=\"px-3 py-2 text-xs text-text-muted\">\n No sessions matching “{sessionSearch}”\n </div>\n )}\n </div>\n </>\n )}\n </div>\n )}\n\n {/* Context Menu */}\n {contextMenu.visible && contextTab && (\n <div\n ref={contextMenuRef}\n className=\"fixed bg-bg-elevated border border-border-default rounded-lg shadow-lg z-[100] min-w-[180px] py-1\"\n style={{ top: contextMenu.y, left: contextMenu.x }}\n >\n {onRenameSession && (\n <button\n type=\"button\"\n onClick={() => handleStartRename(contextTab.sessionId)}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n <Edit3 className=\"size-3.5\" />\n Rename\n </button>\n )}\n {contextTab.terminalUrl && (\n <button\n type=\"button\"\n onClick={() => {\n onOpenExternal(contextTab.sessionId);\n setContextMenu((prev) => ({ ...prev, visible: false }));\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n <ExternalLink className=\"size-3.5\" />\n {tr('vibecontrols.sessions.terminal.openInNewTab', 'Open in New Tab')}\n </button>\n )}\n {/* Reconnect option in context menu */}\n {contextTab.terminalUrl && connectionStatus[contextTab.sessionId] === 'error' && (\n <button\n type=\"button\"\n onClick={() => {\n handleReconnect(contextTab.sessionId);\n setContextMenu((prev) => ({ ...prev, visible: false }));\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-status-warning-text hover:bg-bg-sunken transition-colors\"\n >\n <RotateCcw className=\"size-3.5\" />\n Reconnect\n </button>\n )}\n {onRestartTerminal && (\n <button\n type=\"button\"\n onClick={() => {\n onRestartTerminal(contextTab.sessionId);\n setContextMenu((prev) => ({ ...prev, visible: false }));\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n <RotateCcw className=\"size-3.5\" />\n Restart Terminal\n </button>\n )}\n {onStopTerminal && (\n <button\n type=\"button\"\n onClick={() => {\n onStopTerminal(contextTab.sessionId);\n setContextMenu((prev) => ({ ...prev, visible: false }));\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n <Square className=\"size-3.5\" />\n Stop Terminal\n </button>\n )}\n {onStopSession && (\n <button\n type=\"button\"\n onClick={() => {\n onStopSession(contextTab.sessionId);\n setContextMenu((prev) => ({ ...prev, visible: false }));\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-status-warning-text hover:bg-bg-sunken transition-colors\"\n >\n <Square className=\"size-3.5\" />\n {tr('vibecontrols.sessions.terminal.stopSession', 'Stop Session')}\n </button>\n )}\n <div className=\"border-t border-border-subtle my-1\" />\n <button\n type=\"button\"\n onClick={() => {\n onTabClose(contextTab.sessionId);\n setContextMenu((prev) => ({ ...prev, visible: false }));\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-status-error-text hover:bg-bg-sunken transition-colors\"\n >\n <X className=\"size-3.5\" />\n {tr('vibecontrols.sessions.terminal.closeTab', 'Close Tab')}\n </button>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TerminalPanel.js","names":[],"sources":["../../../src/components/sessions/TerminalPanel.tsx"],"sourcesContent":["import { useState, useCallback, useRef, useEffect, type ReactNode } from 'react';\nimport { createPortal } from 'react-dom';\nimport { useTr } from '../../shared/hooks/useTr';\nimport {\n Terminal,\n X,\n Maximize2,\n Minimize2,\n ExternalLink,\n Plus,\n ChevronUp,\n ChevronDown,\n Loader2,\n AlertCircle,\n Edit3,\n RotateCcw,\n Square,\n Search,\n Wifi,\n WifiOff,\n GripVertical,\n Expand,\n Shrink,\n} from 'lucide-react';\nimport type { Session } from './types';\nimport { TerminalIframe } from './TerminalIframe';\n\n/**\n * A single terminal tab representing an open session.\n */\nexport interface TerminalTab {\n /** The session ID */\n sessionId: string;\n /** Display name (session name) */\n name: string;\n /** The URL to load in the iframe - either ttydUrl or agent tunnelUrl */\n terminalUrl: string | null;\n /**\n * Agent id owning the session — required so TerminalIframe can mint\n * an F3 capability token. Optional for tabs whose owning agent is\n * unknown (rare; legacy sessions get null).\n */\n agentId?: string | null;\n /**\n * Agent profile this session was created against. Threaded into\n * TerminalIframe → useTerminalCapabilityUrl so the\n * mintAgentCapability mutation targets `/api/profiles/<profile>/...`\n * on the agent. Falls back to \"default\" if absent (legacy tabs).\n */\n profile?: string | null;\n /** Whether ttyd is being started for this session */\n loading?: boolean;\n /** Error message if starting failed */\n error?: string | null;\n}\n\ninterface ContextMenuState {\n visible: boolean;\n x: number;\n y: number;\n sessionId: string | null;\n}\n\ntype ConnectionStatus = 'connecting' | 'connected' | 'error';\n\ninterface TerminalPanelProps {\n /** The currently open terminal tabs */\n tabs: TerminalTab[];\n /** The active tab session ID */\n activeTabId: string | null;\n /** Called when user clicks a tab */\n onTabSelect: (sessionId: string) => void;\n /** Called when user closes a tab */\n onTabClose: (sessionId: string) => void;\n /** Called when user wants to open a session in a new browser tab */\n onOpenExternal: (sessionId: string) => void;\n /** Called when panel is collapsed/expanded */\n onToggleCollapse?: () => void;\n /** Whether the panel is collapsed */\n collapsed?: boolean;\n /** Panel height class (e.g., 'h-96', 'h-[500px]') */\n heightClass?: string;\n /** All available sessions for the \"+\" button */\n sessions?: Session[];\n /** Called when user clicks \"+\" to open a new terminal for a session */\n onOpenTerminal?: (session: Session) => void;\n /** Called when user clicks retry on a failed terminal tab */\n onRetryTerminal?: (session: Session) => void;\n /** Called when user renames a session tab */\n onRenameSession?: (sessionId: string, newName: string) => void;\n /** Called when user stops a terminal */\n onStopTerminal?: (sessionId: string) => void;\n /** Called when user restarts a terminal */\n onRestartTerminal?: (sessionId: string) => void;\n /** Called when user stops the entire session */\n onStopSession?: (sessionId: string) => void;\n /** Called to create a new session (for \"+\" tab in terminal-first mode) */\n onCreateSession?: () => void;\n /** Whether this is the full-screen terminal-first mode */\n fullScreen?: boolean;\n /** Whether the immersive fullscreen overlay is active */\n isImmersiveFullscreen?: boolean;\n /** Called when user clicks the immersive fullscreen button */\n onToggleImmersiveFullscreen?: () => void;\n /** Context switcher component to render in immersive fullscreen header */\n contextSwitcher?: ReactNode;\n}\n\n/**\n * Tabbed terminal panel - like VS Code's integrated terminal.\n * Each tab is an iframe loading a ttyd web terminal via the agent's tunnel.\n * Supports context menu with rename, close, open in new tab, restart, stop.\n * Features: reconnect on failure, connection status indicators, drag-to-resize.\n */\nexport function TerminalPanel({\n tabs,\n activeTabId,\n onTabSelect,\n onTabClose,\n onOpenExternal,\n onToggleCollapse,\n collapsed = false,\n heightClass = 'h-[420px]',\n sessions,\n onOpenTerminal,\n onRetryTerminal,\n onRenameSession,\n onStopTerminal,\n onRestartTerminal,\n onStopSession,\n onCreateSession,\n fullScreen = false,\n isImmersiveFullscreen = false,\n onToggleImmersiveFullscreen,\n contextSwitcher,\n}: TerminalPanelProps) {\n const tr = useTr();\n const [maximized, setMaximized] = useState(false);\n const [showSessionPicker, setShowSessionPicker] = useState(false);\n const [sessionSearch, setSessionSearch] = useState('');\n const [contextMenu, setContextMenu] = useState<ContextMenuState>({\n visible: false,\n x: 0,\n y: 0,\n sessionId: null,\n });\n const [renamingTabId, setRenamingTabId] = useState<string | null>(null);\n const [renameValue, setRenameValue] = useState('');\n // Track iframe connection status per tab\n const [connectionStatus, setConnectionStatus] = useState<Record<string, ConnectionStatus>>({});\n const [connectionIssues, setConnectionIssues] = useState<Record<string, string | undefined>>({});\n // Drag-to-resize state\n const [resizeHeight, setResizeHeight] = useState<number | null>(null);\n const resizingRef = useRef(false);\n const startYRef = useRef(0);\n const startHeightRef = useRef(0);\n const panelRef = useRef<HTMLDivElement>(null);\n\n const renameInputRef = useRef<HTMLInputElement>(null);\n const contextMenuRef = useRef<HTMLDivElement>(null);\n const plusBtnRef = useRef<HTMLButtonElement>(null);\n const sessionPickerRef = useRef<HTMLDivElement>(null);\n const sessionSearchRef = useRef<HTMLInputElement>(null);\n // Track iframe refs for reconnect\n const iframeRefs = useRef<Record<string, HTMLIFrameElement | null>>({});\n // Auto-reconnect: track retry timers and attempt counts per session\n const autoReconnectTimers = useRef<Record<string, ReturnType<typeof setTimeout>>>({});\n const autoReconnectAttempts = useRef<Record<string, number>>({});\n const connectionTimeoutTimers = useRef<Record<string, ReturnType<typeof setTimeout>>>({});\n\n const activeTab = tabs.find((t) => t.sessionId === activeTabId) || tabs[0] || null;\n\n const setConnectionStatusForTab = useCallback((sessionId: string, status: ConnectionStatus) => {\n setConnectionStatus((prev) =>\n prev[sessionId] === status ? prev : { ...prev, [sessionId]: status }\n );\n }, []);\n\n const setConnectionIssueForTab = useCallback((sessionId: string, issue: string | undefined) => {\n setConnectionIssues((prev) =>\n prev[sessionId] === issue ? prev : { ...prev, [sessionId]: issue }\n );\n }, []);\n\n const getTerminalUrlIssue = useCallback(\n (terminalUrl: string | null | undefined): string | null => {\n if (!terminalUrl) return null;\n\n try {\n const pageProtocol = typeof window !== 'undefined' ? window.location.protocol : 'https:';\n const terminalProtocol = new URL(terminalUrl).protocol;\n if (pageProtocol === 'https:' && terminalProtocol !== 'https:') {\n return tr(\n 'vibecontrols.sessions.terminal.mixedContentBlocked',\n 'This terminal URL is not HTTPS, so the browser blocked it on this secure page.'\n );\n }\n\n return null;\n } catch {\n return tr(\n 'vibecontrols.sessions.terminal.invalidUrl',\n 'The terminal URL is invalid. Restart the terminal to generate a new URL.'\n );\n }\n },\n [tr]\n );\n\n const handleMaximize = useCallback(() => {\n setMaximized((prev) => !prev);\n setResizeHeight(null);\n }, []);\n\n // Sessions not already opened in tabs\n const availableSessions = (sessions ?? []).filter((s) => !tabs.some((t) => t.sessionId === s.id));\n\n // Filtered by search term\n const filteredPickerSessions = sessionSearch.trim()\n ? availableSessions.filter((s) =>\n s.name.toLowerCase().includes(sessionSearch.trim().toLowerCase())\n )\n : availableSessions;\n\n // Track connection status via iframe load/error events\n const handleIframeLoad = useCallback(\n (sessionId: string) => {\n if (connectionTimeoutTimers.current[sessionId]) {\n clearTimeout(connectionTimeoutTimers.current[sessionId]);\n delete connectionTimeoutTimers.current[sessionId];\n }\n setConnectionStatusForTab(sessionId, 'connected');\n setConnectionIssueForTab(sessionId, undefined);\n },\n [setConnectionIssueForTab, setConnectionStatusForTab]\n );\n\n const handleIframeError = useCallback(\n (sessionId: string) => {\n if (connectionTimeoutTimers.current[sessionId]) {\n clearTimeout(connectionTimeoutTimers.current[sessionId]);\n delete connectionTimeoutTimers.current[sessionId];\n }\n setConnectionStatusForTab(sessionId, 'error');\n },\n [setConnectionStatusForTab]\n );\n\n // Set connecting status when a tab starts loading\n useEffect(() => {\n tabs.forEach((tab) => {\n if (!tab.terminalUrl || tab.loading || tab.error) {\n return;\n }\n\n const terminalUrlIssue = getTerminalUrlIssue(tab.terminalUrl);\n if (terminalUrlIssue) {\n setConnectionStatusForTab(tab.sessionId, 'error');\n setConnectionIssueForTab(tab.sessionId, terminalUrlIssue);\n return;\n }\n\n if (!connectionStatus[tab.sessionId]) {\n setConnectionStatusForTab(tab.sessionId, 'connecting');\n }\n\n if (\n connectionStatus[tab.sessionId] === 'connecting' &&\n !connectionTimeoutTimers.current[tab.sessionId]\n ) {\n connectionTimeoutTimers.current[tab.sessionId] = setTimeout(() => {\n delete connectionTimeoutTimers.current[tab.sessionId];\n setConnectionStatusForTab(tab.sessionId, 'error');\n setConnectionIssueForTab(\n tab.sessionId,\n tr(\n 'vibecontrols.sessions.terminal.connectionTimedOut',\n 'Timed out waiting for the terminal to connect. Check the agent tunnel and retry.'\n )\n );\n }, 12_000);\n }\n });\n\n return () => {\n for (const timerId of Object.values(connectionTimeoutTimers.current)) {\n clearTimeout(timerId);\n }\n connectionTimeoutTimers.current = {};\n };\n }, [\n connectionStatus,\n getTerminalUrlIssue,\n setConnectionIssueForTab,\n setConnectionStatusForTab,\n tabs,\n tr,\n ]);\n\n // Clear auto-reconnect timer for a session\n const clearAutoReconnect = useCallback((sessionId: string) => {\n if (autoReconnectTimers.current[sessionId]) {\n clearTimeout(autoReconnectTimers.current[sessionId]);\n delete autoReconnectTimers.current[sessionId];\n }\n delete autoReconnectAttempts.current[sessionId];\n }, []);\n\n // Reconnect handler — reload the iframe\n const handleReconnect = useCallback(\n (sessionId: string) => {\n const iframe = iframeRefs.current[sessionId];\n if (iframe) {\n clearAutoReconnect(sessionId);\n setConnectionIssues((prev) => ({ ...prev, [sessionId]: undefined }));\n setConnectionStatus((prev) => ({ ...prev, [sessionId]: 'connecting' }));\n // Force reload by briefly clearing src then setting it back\n const src = iframe.src;\n iframe.src = 'about:blank';\n requestAnimationFrame(() => {\n iframe.src = src;\n });\n }\n },\n [clearAutoReconnect]\n );\n\n // Auto-reconnect: when a tab enters error state, schedule automatic retry\n // Uses exponential backoff: 2s, 4s, 8s, 16s, 30s (capped), up to 15 attempts\n const MAX_AUTO_RECONNECT_ATTEMPTS = 15;\n const BASE_RECONNECT_DELAY_MS = 2_000;\n const MAX_RECONNECT_DELAY_MS = 30_000;\n\n useEffect(() => {\n for (const tab of tabs) {\n const status = connectionStatus[tab.sessionId];\n\n if (status === 'error' && tab.terminalUrl && !tab.error && !connectionIssues[tab.sessionId]) {\n // Terminal iframe lost connection (agent restart, network blip) — auto-retry\n if (autoReconnectTimers.current[tab.sessionId]) continue; // already scheduled\n\n const attempt = autoReconnectAttempts.current[tab.sessionId] ?? 0;\n if (attempt >= MAX_AUTO_RECONNECT_ATTEMPTS) continue; // give up after max attempts\n\n const delay = Math.min(\n BASE_RECONNECT_DELAY_MS * Math.pow(2, attempt),\n MAX_RECONNECT_DELAY_MS\n );\n\n autoReconnectTimers.current[tab.sessionId] = setTimeout(() => {\n delete autoReconnectTimers.current[tab.sessionId];\n autoReconnectAttempts.current[tab.sessionId] = attempt + 1;\n\n const iframe = iframeRefs.current[tab.sessionId];\n if (!iframe) return;\n\n // Probe the agent health before reloading the iframe\n // Extract the agent base URL from the terminal URL\n const terminalUrl = tab.terminalUrl!;\n const urlObj = new URL(terminalUrl);\n const healthUrl = `${urlObj.origin}/health`;\n\n fetch(healthUrl, { signal: AbortSignal.timeout(5_000) })\n .then((res) => {\n if (res.ok) {\n // Agent is back — reload the iframe\n setConnectionStatus((prev) => ({\n ...prev,\n [tab.sessionId]: 'connecting',\n }));\n const src = iframe.src;\n iframe.src = 'about:blank';\n requestAnimationFrame(() => {\n iframe.src = src;\n });\n }\n // If not ok, the next interval tick will schedule another attempt\n })\n .catch(() => {\n // Agent still down — will retry on next tick\n });\n }, delay);\n }\n\n // Reset attempts on successful connection\n if (status === 'connected') {\n clearAutoReconnect(tab.sessionId);\n }\n }\n\n // Capture ref for cleanup (react-hooks/exhaustive-deps)\n const timers = autoReconnectTimers.current;\n return () => {\n for (const timerId of Object.values(timers)) {\n clearTimeout(timerId);\n }\n };\n }, [tabs, connectionIssues, connectionStatus, clearAutoReconnect]);\n\n // Drag-to-resize handlers (only for non-fullscreen, non-maximized)\n const handleResizeStart = useCallback(\n (e: React.MouseEvent) => {\n if (fullScreen || maximized) return;\n e.preventDefault();\n resizingRef.current = true;\n startYRef.current = e.clientY;\n startHeightRef.current = panelRef.current?.offsetHeight || 420;\n document.body.style.cursor = 'ns-resize';\n document.body.style.userSelect = 'none';\n },\n [fullScreen, maximized]\n );\n\n useEffect(() => {\n const handleMouseMove = (e: MouseEvent) => {\n if (!resizingRef.current) return;\n const delta = startYRef.current - e.clientY;\n const newHeight = Math.max(\n 200,\n Math.min(startHeightRef.current + delta, window.innerHeight - 150)\n );\n setResizeHeight(newHeight);\n };\n const handleMouseUp = () => {\n if (resizingRef.current) {\n resizingRef.current = false;\n document.body.style.cursor = '';\n document.body.style.userSelect = '';\n }\n };\n document.addEventListener('mousemove', handleMouseMove);\n document.addEventListener('mouseup', handleMouseUp);\n return () => {\n document.removeEventListener('mousemove', handleMouseMove);\n document.removeEventListener('mouseup', handleMouseUp);\n };\n }, []);\n\n // Context menu handler\n const handleContextMenu = useCallback((e: React.MouseEvent, sessionId: string) => {\n e.preventDefault();\n e.stopPropagation();\n setContextMenu({\n visible: true,\n x: e.clientX,\n y: e.clientY,\n sessionId,\n });\n }, []);\n\n // Close context menu on outside click\n useEffect(() => {\n const handleClick = (e: MouseEvent) => {\n if (contextMenuRef.current && !contextMenuRef.current.contains(e.target as Node)) {\n setContextMenu((prev) => ({ ...prev, visible: false }));\n }\n };\n if (contextMenu.visible) {\n document.addEventListener('mousedown', handleClick);\n return () => document.removeEventListener('mousedown', handleClick);\n }\n }, [contextMenu.visible]);\n\n // Close session picker on outside click\n useEffect(() => {\n const handleClick = (e: MouseEvent) => {\n const target = e.target as Node;\n if (\n sessionPickerRef.current &&\n !sessionPickerRef.current.contains(target) &&\n plusBtnRef.current &&\n !plusBtnRef.current.contains(target)\n ) {\n setShowSessionPicker(false);\n setSessionSearch('');\n }\n };\n if (showSessionPicker) {\n document.addEventListener('mousedown', handleClick);\n return () => document.removeEventListener('mousedown', handleClick);\n }\n }, [showSessionPicker]);\n\n // Focus search input when session picker opens\n useEffect(() => {\n if (showSessionPicker && sessionSearchRef.current) {\n requestAnimationFrame(() => sessionSearchRef.current?.focus());\n }\n }, [showSessionPicker]);\n\n // Focus rename input when renaming starts\n useEffect(() => {\n if (renamingTabId && renameInputRef.current) {\n renameInputRef.current.focus();\n renameInputRef.current.select();\n }\n }, [renamingTabId]);\n\n // ESC key to exit immersive fullscreen\n useEffect(() => {\n if (!isImmersiveFullscreen || !onToggleImmersiveFullscreen) return;\n\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n // Don't prevent default - let browser handle native ESC behaviors\n onToggleImmersiveFullscreen();\n }\n };\n\n document.addEventListener('keydown', handleKeyDown);\n return () => document.removeEventListener('keydown', handleKeyDown);\n }, [isImmersiveFullscreen, onToggleImmersiveFullscreen]);\n\n const handleStartRename = useCallback(\n (sessionId: string) => {\n const tab = tabs.find((t) => t.sessionId === sessionId);\n if (tab) {\n setRenamingTabId(sessionId);\n setRenameValue(tab.name);\n }\n setContextMenu((prev) => ({ ...prev, visible: false }));\n },\n [tabs]\n );\n\n const handleFinishRename = useCallback(() => {\n if (renamingTabId && renameValue.trim() && onRenameSession) {\n const tab = tabs.find((t) => t.sessionId === renamingTabId);\n if (tab && renameValue.trim() !== tab.name) {\n onRenameSession(renamingTabId, renameValue.trim());\n }\n }\n setRenamingTabId(null);\n setRenameValue('');\n }, [renamingTabId, renameValue, onRenameSession, tabs]);\n\n const handleRenameKeyDown = useCallback(\n (e: React.KeyboardEvent) => {\n if (e.key === 'Enter') {\n handleFinishRename();\n } else if (e.key === 'Escape') {\n setRenamingTabId(null);\n setRenameValue('');\n }\n },\n [handleFinishRename]\n );\n\n if (tabs.length === 0 && !collapsed && !fullScreen) {\n return null;\n }\n\n // Determine panel height\n const panelHeight = fullScreen\n ? 'h-full'\n : maximized\n ? 'h-[calc(100vh-120px)]'\n : resizeHeight\n ? undefined // use inline style instead\n : heightClass;\n\n const panelStyle =\n !fullScreen && !maximized && resizeHeight ? { height: `${resizeHeight}px` } : undefined;\n\n const contextTab = contextMenu.sessionId\n ? tabs.find((t) => t.sessionId === contextMenu.sessionId)\n : null;\n\n // Helper to render connection status indicator\n const renderStatusIndicator = (tab: TerminalTab) => {\n if (tab.loading) {\n return <Loader2 className=\"size-3 animate-spin shrink-0\" />;\n }\n if (tab.error) {\n return <AlertCircle className=\"size-3 text-status-error-text shrink-0\" />;\n }\n const status = connectionStatus[tab.sessionId];\n if (status === 'connected') {\n return <Wifi className=\"size-3 text-status-success-text shrink-0\" />;\n }\n if (status === 'error') {\n return <WifiOff className=\"size-3 text-status-error-text shrink-0\" />;\n }\n if (status === 'connecting') {\n return <Loader2 className=\"size-3 animate-spin text-status-warning-text shrink-0\" />;\n }\n return <Terminal className=\"size-3 shrink-0\" />;\n };\n\n // Render the immersive fullscreen overlay\n const renderImmersiveFullscreen = () => {\n if (!isImmersiveFullscreen) return null;\n\n return createPortal(\n <div className=\"fixed inset-0 z-[9999] bg-bg-surface flex flex-col\">\n {/* Immersive Fullscreen Header - minimal with context switcher */}\n <div className=\"flex items-center justify-between px-3 py-2 bg-bg-sunken border-b border-border-default shrink-0\">\n {/* Context Switcher */}\n <div className=\"flex items-center gap-4\">{contextSwitcher}</div>\n\n {/* Actions */}\n <div className=\"flex items-center gap-2\">\n {/* Reconnect button for disconnected terminals */}\n {activeTab && connectionStatus[activeTab.sessionId] === 'error' && (\n <button\n type=\"button\"\n onClick={() => handleReconnect(activeTab.sessionId)}\n className=\"p-1.5 text-status-warning-text hover:text-status-warning-text/80 transition-colors\"\n title=\"Reconnect terminal\"\n >\n <RotateCcw className=\"size-4\" />\n </button>\n )}\n {activeTab?.terminalUrl && (\n <button\n type=\"button\"\n onClick={() => activeTab && onOpenExternal(activeTab.sessionId)}\n className=\"p-1.5 text-text-secondary hover:text-text-primary transition-colors\"\n title=\"Open in new tab\"\n >\n <ExternalLink className=\"size-4\" />\n </button>\n )}\n <button\n type=\"button\"\n onClick={onToggleImmersiveFullscreen}\n className=\"p-1.5 text-text-secondary hover:text-text-primary transition-colors\"\n title=\"Exit fullscreen (ESC)\"\n >\n <Shrink className=\"size-4\" />\n </button>\n </div>\n </div>\n\n {/* Tab Bar */}\n <div className=\"flex items-center bg-bg-sunken border-b border-border-default shrink-0\">\n {/* Tab list */}\n <div className=\"flex items-center overflow-x-auto flex-1 min-w-0\">\n {tabs.map((tab) => (\n <div\n role=\"button\"\n tabIndex={0}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n (() => onTabSelect(tab.sessionId))();\n }\n }}\n key={tab.sessionId}\n className={`flex items-center gap-1.5 px-3 py-1.5 text-xs border-r border-border-subtle cursor-pointer transition-colors min-w-0 max-w-[180px] group ${\n tab.sessionId === activeTab?.sessionId\n ? 'bg-bg-surface text-text-primary border-b-2 border-b-action-primary-bg'\n : 'text-text-secondary hover:text-text-primary hover:bg-bg-surface/50'\n }`}\n onClick={() => onTabSelect(tab.sessionId)}\n onContextMenu={(e) => handleContextMenu(e, tab.sessionId)}\n onDoubleClick={() => handleStartRename(tab.sessionId)}\n >\n {renderStatusIndicator(tab)}\n {renamingTabId === tab.sessionId ? (\n <input\n aria-label=\"control\"\n ref={renameInputRef}\n value={renameValue}\n onChange={(e) => setRenameValue(e.target.value)}\n onBlur={handleFinishRename}\n onKeyDown={handleRenameKeyDown}\n onClick={(e) => e.stopPropagation()}\n className=\"bg-bg-sunken border border-border-strong rounded px-1 py-0 text-xs text-text-primary w-[100px] outline-none\"\n />\n ) : (\n <span className=\"truncate\">{tab.name}</span>\n )}\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onTabClose(tab.sessionId);\n }}\n className=\"ml-auto opacity-0 group-hover:opacity-100 hover:text-status-error-text transition-opacity shrink-0\"\n title=\"Close terminal\"\n >\n <X className=\"size-3\" />\n </button>\n </div>\n ))}\n </div>\n\n {/* Add tab \"+\" button */}\n <button\n type=\"button\"\n ref={plusBtnRef}\n onClick={() => {\n if (onCreateSession || availableSessions.length > 0) {\n setShowSessionPicker((prev) => !prev);\n setSessionSearch('');\n }\n }}\n className=\"flex items-center px-2.5 py-1.5 text-text-secondary hover:text-text-primary transition-colors shrink-0 border-r border-border-subtle\"\n title=\"New session or open existing\"\n >\n <Plus className=\"size-3.5\" />\n </button>\n </div>\n\n {/* Terminal Content - takes remaining space */}\n <div className=\"flex-1 bg-bg-sunken text-text-primary relative overflow-hidden\">\n {activeTab ? (\n tabs.map((tab) => {\n const status = connectionStatus[tab.sessionId];\n const isConnecting = status === 'connecting' || tab.loading;\n const terminalUrlIssue = getTerminalUrlIssue(tab.terminalUrl);\n const issue = terminalUrlIssue ?? connectionIssues[tab.sessionId];\n const showIframe = !!tab.terminalUrl && !tab.error && !terminalUrlIssue;\n const showConnectionIssue = !!terminalUrlIssue || (showIframe && status === 'error');\n\n return (\n <div\n key={tab.sessionId}\n className={`absolute inset-0 ${tab.sessionId === activeTab.sessionId ? 'block' : 'hidden'}`}\n >\n {/* Always render iframe if URL exists (keeps terminal state) */}\n {showIframe && tab.terminalUrl && (\n <TerminalIframe\n ref={(el) => {\n iframeRefs.current[tab.sessionId] = el;\n }}\n sessionId={tab.sessionId}\n agentId={tab.agentId ?? null}\n profile={tab.profile ?? null}\n baseTtydUrl={tab.terminalUrl}\n title={`Terminal - ${tab.name}`}\n className=\"w-full h-full border-0\"\n allow=\"clipboard-read *; clipboard-write *\"\n onLoad={() => handleIframeLoad(tab.sessionId)}\n onError={() => handleIframeError(tab.sessionId)}\n />\n )}\n\n {/* Loading/Connecting overlay */}\n {showIframe && isConnecting && (\n <div className=\"absolute inset-0 bg-bg-overlay backdrop-blur-[2px] flex items-center justify-center pointer-events-none z-10\">\n <div className=\"flex items-center gap-3 px-4 py-3 bg-bg-surface/95 rounded-lg border border-border-default shadow-lg\">\n <Loader2 className=\"size-5 text-status-warning-text animate-spin\" />\n <div>\n <p className=\"text-text-primary text-sm font-medium\">\n {tab.loading\n ? tr(\n 'vibecontrols.sessions.terminal.startingTerminal',\n 'Starting terminal...'\n )\n : tr('vibecontrols.sessions.terminal.connecting', 'Connecting...')}\n </p>\n <p className=\"text-text-secondary text-xs\">\n {tab.loading\n ? tr(\n 'vibecontrols.sessions.terminal.settingUpTmux',\n 'Setting up tmux + ttyd'\n )\n : tr(\n 'vibecontrols.sessions.terminal.establishingConnection',\n 'Establishing connection'\n )}\n </p>\n </div>\n </div>\n </div>\n )}\n\n {/* Disconnected overlay - auto-reconnect is active in background */}\n {showConnectionIssue && !tab.error && (\n <div className=\"absolute inset-0 bg-bg-overlay backdrop-blur-[2px] flex items-center justify-center z-10\">\n <div className=\"flex flex-col items-center gap-3 px-6 py-4 bg-bg-surface/95 rounded-lg border border-border-default shadow-lg\">\n <WifiOff className=\"size-6 text-status-error-text\" />\n <div className=\"text-center\">\n <p className=\"text-text-primary text-sm font-medium\">\n {tr('vibecontrols.sessions.terminal.connectionLost', 'Connection Lost')}\n </p>\n <p className=\"text-text-secondary text-xs mt-1\">\n {issue\n ? issue\n : (autoReconnectAttempts.current[tab.sessionId] ?? 0) <\n MAX_AUTO_RECONNECT_ATTEMPTS\n ? tr(\n 'vibecontrols.sessions.terminal.reconnectingAutomatically',\n 'Reconnecting automatically...'\n )\n : tr(\n 'vibecontrols.sessions.terminal.connectionInterrupted',\n 'Terminal connection was interrupted'\n )}\n </p>\n </div>\n {!issue &&\n (autoReconnectAttempts.current[tab.sessionId] ?? 0) <\n MAX_AUTO_RECONNECT_ATTEMPTS && (\n <Loader2 className=\"size-4 text-text-tertiary animate-spin\" />\n )}\n <button\n type=\"button\"\n onClick={() => handleReconnect(tab.sessionId)}\n className=\"px-4 py-2 bg-bg-sunken text-text-primary text-sm rounded-md hover:bg-bg-sunken/80 transition-colors inline-flex items-center gap-2\"\n >\n <RotateCcw className=\"size-3.5\" />\n {tr('vibecontrols.sessions.terminal.reconnectNow', 'Reconnect Now')}\n </button>\n </div>\n </div>\n )}\n\n {/* No URL yet */}\n {!tab.terminalUrl && tab.loading && (\n <div className=\"flex items-center justify-center h-full\">\n <div className=\"text-center\">\n <Loader2 className=\"size-8 text-text-tertiary animate-spin mx-auto mb-3\" />\n <p className=\"text-text-tertiary text-sm\">\n Starting terminal for {tab.name}...\n </p>\n <p className=\"text-text-secondary text-xs mt-1\">\n Setting up tmux + ttyd terminal\n </p>\n </div>\n </div>\n )}\n\n {/* Error state */}\n {tab.error && (\n <div className=\"flex items-center justify-center h-full\">\n <div className=\"text-center max-w-md\">\n <AlertCircle className=\"size-8 text-status-error-text mx-auto mb-3\" />\n <p className=\"text-status-error-text text-sm font-medium\">\n {tr(\n 'vibecontrols.sessions.terminal.failedToStart',\n 'Failed to start terminal'\n )}\n </p>\n <p className=\"text-text-tertiary text-xs mt-2\">{tab.error}</p>\n <div className=\"flex items-center justify-center gap-3 mt-4\">\n {onRetryTerminal && sessions && (\n <button\n type=\"button\"\n onClick={() => {\n const session = sessions.find((s) => s.id === tab.sessionId);\n if (session) onRetryTerminal(session);\n }}\n className=\"px-4 py-2 bg-bg-sunken text-text-primary text-sm rounded-md hover:bg-bg-sunken/80 transition-colors inline-flex items-center gap-2\"\n >\n <RotateCcw className=\"size-3.5\" />\n {tr('vibecontrols.sessions.terminal.retry', 'Retry')}\n </button>\n )}\n </div>\n </div>\n </div>\n )}\n\n {/* No terminal URL and not loading — actionable CTA */}\n {!tab.terminalUrl && !tab.loading && !tab.error && (\n <div className=\"flex items-center justify-center h-full\">\n <div className=\"text-center max-w-sm space-y-3\">\n <Terminal className=\"size-8 text-text-secondary mx-auto\" />\n <p className=\"text-text-primary text-sm font-medium\">\n {tr(\n 'vibecontrols.sessions.terminal.terminalNotStarted',\n 'Terminal not started yet'\n )}\n </p>\n <p className=\"text-text-secondary text-xs\">\n {tr(\n 'vibecontrols.sessions.terminal.startTerminalHint',\n 'Click below to start a ttyd terminal on the agent for this session.'\n )}\n </p>\n {onRetryTerminal && sessions && (\n <button\n type=\"button\"\n onClick={() => {\n const session = sessions.find((s) => s.id === tab.sessionId);\n if (session) onRetryTerminal(session);\n }}\n className=\"px-4 py-2 bg-accent-primary text-white text-sm rounded-md hover:bg-accent-primary/90 transition-colors inline-flex items-center gap-2\"\n >\n <Terminal className=\"size-3.5\" />\n {tr('vibecontrols.sessions.terminal.startTerminal', 'Start terminal')}\n </button>\n )}\n </div>\n </div>\n )}\n </div>\n );\n })\n ) : (\n /* Empty state for fullscreen with no tabs */\n <div className=\"flex items-center justify-center h-full\">\n <div className=\"text-center max-w-md\">\n <Terminal className=\"size-12 text-text-secondary mx-auto mb-4\" />\n <p className=\"text-text-secondary text-base font-medium mb-2\">\n {tr('vibecontrols.sessions.terminal.noTerminalsOpen', 'No terminals open')}\n </p>\n <p className=\"text-text-secondary text-sm mb-4\">\n {tr(\n 'vibecontrols.sessions.terminal.clickPlusTab',\n 'Click the + tab above to create a new session or open an existing one'\n )}\n </p>\n {onCreateSession && (\n <button\n type=\"button\"\n onClick={onCreateSession}\n className=\"px-4 py-2 bg-bg-sunken text-text-primary text-sm rounded-md hover:bg-bg-sunken/80 transition-colors inline-flex items-center gap-2\"\n >\n <Plus className=\"size-4\" />\n {tr('vibecontrols.sessions.terminal.newSession', 'New Session')}\n </button>\n )}\n </div>\n </div>\n )}\n </div>\n\n {/* Session Picker Dropdown (for immersive fullscreen) */}\n {showSessionPicker &&\n (onCreateSession || availableSessions.length > 0) &&\n plusBtnRef.current && (\n <div\n ref={sessionPickerRef}\n className=\"fixed bg-bg-elevated border border-border-default rounded-md shadow-xl z-[10000] w-[280px]\"\n style={{\n top: plusBtnRef.current.getBoundingClientRect().bottom + 4,\n left: Math.min(\n plusBtnRef.current.getBoundingClientRect().left,\n window.innerWidth - 290\n ),\n }}\n >\n {/* Search box */}\n <div className=\"p-2 border-b border-border-subtle\">\n <div className=\"flex items-center gap-2 px-2 py-1.5 bg-bg-sunken rounded border border-border-subtle focus-within:border-border-strong\">\n <Search className=\"size-3.5 text-text-muted shrink-0\" />\n <input\n aria-label=\"Search sessions...\"\n ref={sessionSearchRef}\n type=\"text\"\n value={sessionSearch}\n onChange={(e) => setSessionSearch(e.target.value)}\n placeholder=\"Search sessions...\"\n className=\"w-full bg-bg-surface/0 text-sm text-text-primary outline-none placeholder:text-text-placeholder\"\n />\n {sessionSearch && (\n <button\n type=\"button\"\n onClick={() => setSessionSearch('')}\n className=\"text-text-muted hover:text-text-primary shrink-0\"\n >\n <X className=\"size-3\" />\n </button>\n )}\n </div>\n </div>\n\n {/* New Session action */}\n {onCreateSession && (\n <button\n type=\"button\"\n onClick={() => {\n onCreateSession();\n setShowSessionPicker(false);\n setSessionSearch('');\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors text-left font-medium\"\n >\n <Plus className=\"size-3.5 text-text-secondary\" />\n <span>{tr('vibecontrols.sessions.terminal.newSession', 'New Session')}</span>\n </button>\n )}\n\n {/* Open existing section */}\n {availableSessions.length > 0 && (\n <>\n <div className=\"border-t border-border-subtle\" />\n <div className=\"px-3 pt-2 pb-1\">\n <span className=\"text-[10px] font-medium uppercase tracking-wider text-text-muted\">\n Open existing ({availableSessions.length})\n </span>\n </div>\n <div className=\"max-h-48 overflow-y-auto pb-1\">\n {filteredPickerSessions.length > 0 ? (\n filteredPickerSessions.map((s) => (\n <button\n type=\"button\"\n key={s.id}\n onClick={() => {\n onOpenTerminal?.(s);\n setShowSessionPicker(false);\n setSessionSearch('');\n }}\n className=\"flex items-center gap-2 w-full px-3 py-1.5 text-sm text-text-primary hover:bg-bg-sunken transition-colors text-left\"\n >\n <Terminal className=\"size-3.5 text-text-secondary shrink-0\" />\n <span className=\"truncate\">{s.name}</span>\n <span\n className={`ml-auto text-[10px] px-1.5 py-0.5 rounded shrink-0 ${\n s.status === 'RUNNING'\n ? 'bg-status-success-text/10 text-status-success-text'\n : s.status === 'STOPPED' || s.status === 'TERMINATED'\n ? 'bg-text-secondary/10 text-text-tertiary'\n : 'bg-status-warning-text/10 text-status-warning-text'\n }`}\n >\n {s.status.toLowerCase()}\n </span>\n </button>\n ))\n ) : (\n <div className=\"px-3 py-2 text-xs text-text-muted\">\n No sessions matching “{sessionSearch}”\n </div>\n )}\n </div>\n </>\n )}\n </div>\n )}\n\n {/* Context Menu (for immersive fullscreen) */}\n {contextMenu.visible && contextTab && (\n <div\n ref={contextMenuRef}\n className=\"fixed bg-bg-elevated border border-border-default rounded-lg shadow-lg z-[10000] min-w-[180px] py-1\"\n style={{ top: contextMenu.y, left: contextMenu.x }}\n >\n {onRenameSession && (\n <button\n type=\"button\"\n onClick={() => handleStartRename(contextTab.sessionId)}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n <Edit3 className=\"size-3.5\" />\n {tr('vibecontrols.sessions.terminal.rename', 'Rename')}\n </button>\n )}\n {contextTab.terminalUrl && (\n <button\n type=\"button\"\n onClick={() => {\n onOpenExternal(contextTab.sessionId);\n setContextMenu((prev) => ({ ...prev, visible: false }));\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n <ExternalLink className=\"size-3.5\" />\n {tr('vibecontrols.sessions.terminal.openInNewTab', 'Open in New Tab')}\n </button>\n )}\n {contextTab.terminalUrl && connectionStatus[contextTab.sessionId] === 'error' && (\n <button\n type=\"button\"\n onClick={() => {\n handleReconnect(contextTab.sessionId);\n setContextMenu((prev) => ({ ...prev, visible: false }));\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-status-warning-text hover:bg-bg-sunken transition-colors\"\n >\n <RotateCcw className=\"size-3.5\" />\n {tr('vibecontrols.sessions.terminal.reconnect', 'Reconnect')}\n </button>\n )}\n {onRestartTerminal && (\n <button\n type=\"button\"\n onClick={() => {\n onRestartTerminal(contextTab.sessionId);\n setContextMenu((prev) => ({ ...prev, visible: false }));\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n <RotateCcw className=\"size-3.5\" />\n {tr('vibecontrols.sessions.terminal.restartTerminal', 'Restart Terminal')}\n </button>\n )}\n {onStopTerminal && (\n <button\n type=\"button\"\n onClick={() => {\n onStopTerminal(contextTab.sessionId);\n setContextMenu((prev) => ({ ...prev, visible: false }));\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n <Square className=\"size-3.5\" />\n {tr('vibecontrols.sessions.terminal.stopTerminal', 'Stop Terminal')}\n </button>\n )}\n {onStopSession && (\n <button\n type=\"button\"\n onClick={() => {\n onStopSession(contextTab.sessionId);\n setContextMenu((prev) => ({ ...prev, visible: false }));\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-status-warning-text hover:bg-bg-sunken transition-colors\"\n >\n <Square className=\"size-3.5\" />\n {tr('vibecontrols.sessions.terminal.stopSession', 'Stop Session')}\n </button>\n )}\n <div className=\"border-t border-border-subtle my-1\" />\n <button\n type=\"button\"\n onClick={() => {\n onTabClose(contextTab.sessionId);\n setContextMenu((prev) => ({ ...prev, visible: false }));\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-status-error-text hover:bg-bg-sunken transition-colors\"\n >\n <X className=\"size-3.5\" />\n {tr('vibecontrols.sessions.terminal.closeTab', 'Close Tab')}\n </button>\n </div>\n )}\n </div>,\n document.body\n );\n };\n\n // When in immersive fullscreen, render the portal and hide the normal panel\n if (isImmersiveFullscreen) {\n return renderImmersiveFullscreen();\n }\n\n return (\n <div\n ref={panelRef}\n className={`${fullScreen ? 'flex flex-col h-full' : ''} border-t border-border-default bg-bg-surface ${collapsed && tabs.length === 0 && !fullScreen ? 'hidden' : ''}`}\n >\n {/* Drag-to-resize handle (top edge of terminal panel) */}\n {!fullScreen && !collapsed && tabs.length > 0 && (\n <div\n onMouseDown={handleResizeStart}\n className=\"h-1 cursor-ns-resize hover:bg-action-primary-bg/30 transition-colors flex items-center justify-center group\"\n title=\"Drag to resize\"\n >\n <GripVertical className=\"w-4 h-3 text-text-muted opacity-0 group-hover:opacity-100 transition-opacity rotate-90\" />\n </div>\n )}\n\n {/* Tab Bar */}\n <div className=\"flex items-center bg-bg-sunken border-b border-border-default\">\n {/* Collapse toggle — only in non-fullscreen mode */}\n {!fullScreen && (\n <button\n type=\"button\"\n onClick={onToggleCollapse}\n className=\"flex items-center gap-1 px-3 py-2 text-text-secondary hover:text-text-primary transition-colors text-xs font-medium uppercase tracking-wide\"\n title={collapsed ? 'Expand terminal' : 'Collapse terminal'}\n >\n <Terminal className=\"size-3.5\" />\n Terminal\n {collapsed ? <ChevronUp className=\"size-3\" /> : <ChevronDown className=\"size-3\" />}\n </button>\n )}\n\n {/* Tab list */}\n {(!collapsed || fullScreen) && (\n <div\n className={`flex items-center overflow-x-auto ${fullScreen ? 'flex-1' : 'flex-1'} min-w-0`}\n >\n {tabs.map((tab) => (\n <div\n role=\"button\"\n tabIndex={0}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n (() => onTabSelect(tab.sessionId))();\n }\n }}\n key={tab.sessionId}\n className={`flex items-center gap-1.5 px-3 py-1.5 text-xs border-r border-border-subtle cursor-pointer transition-colors min-w-0 max-w-[180px] group ${\n tab.sessionId === activeTab?.sessionId\n ? 'bg-bg-surface text-text-primary border-b-2 border-b-action-primary-bg'\n : 'text-text-secondary hover:text-text-primary hover:bg-bg-surface/50'\n }`}\n onClick={() => onTabSelect(tab.sessionId)}\n onContextMenu={(e) => handleContextMenu(e, tab.sessionId)}\n onDoubleClick={() => handleStartRename(tab.sessionId)}\n >\n {renderStatusIndicator(tab)}\n {renamingTabId === tab.sessionId ? (\n <input\n aria-label=\"control\"\n ref={renameInputRef}\n value={renameValue}\n onChange={(e) => setRenameValue(e.target.value)}\n onBlur={handleFinishRename}\n onKeyDown={handleRenameKeyDown}\n onClick={(e) => e.stopPropagation()}\n className=\"bg-bg-sunken border border-border-strong rounded px-1 py-0 text-xs text-text-primary w-[100px] outline-none\"\n />\n ) : (\n <span className=\"truncate\">{tab.name}</span>\n )}\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onTabClose(tab.sessionId);\n }}\n className=\"ml-auto opacity-0 group-hover:opacity-100 hover:text-status-error-text transition-opacity shrink-0\"\n title=\"Close terminal\"\n >\n <X className=\"size-3\" />\n </button>\n </div>\n ))}\n </div>\n )}\n\n {/* Add tab \"+\" button — outside the overflow container so the dropdown isn't clipped */}\n {(!collapsed || fullScreen) && (\n <button\n type=\"button\"\n ref={plusBtnRef}\n onClick={() => {\n if (onCreateSession || availableSessions.length > 0) {\n setShowSessionPicker((prev) => !prev);\n setSessionSearch('');\n }\n }}\n className=\"flex items-center px-2.5 py-1.5 text-text-secondary hover:text-text-primary transition-colors shrink-0 border-r border-border-subtle\"\n title=\"New session or open existing\"\n >\n <Plus className=\"size-3.5\" />\n </button>\n )}\n\n {/* Panel actions */}\n {(!collapsed || fullScreen) && tabs.length > 0 && (\n <div className=\"flex items-center gap-1 px-2 shrink-0\">\n {/* Reconnect button for disconnected terminals */}\n {activeTab && connectionStatus[activeTab.sessionId] === 'error' && (\n <button\n type=\"button\"\n onClick={() => handleReconnect(activeTab.sessionId)}\n className=\"p-1 text-status-warning-text hover:text-status-warning-text/80 transition-colors\"\n title=\"Reconnect terminal\"\n >\n <RotateCcw className=\"size-3.5\" />\n </button>\n )}\n {activeTab?.terminalUrl && (\n <button\n type=\"button\"\n onClick={() => activeTab && onOpenExternal(activeTab.sessionId)}\n className=\"p-1 text-text-secondary hover:text-text-primary transition-colors\"\n title=\"Open in new tab\"\n >\n <ExternalLink className=\"size-3.5\" />\n </button>\n )}\n {/* Immersive fullscreen toggle */}\n {onToggleImmersiveFullscreen && (\n <button\n type=\"button\"\n onClick={onToggleImmersiveFullscreen}\n className=\"p-1 text-text-secondary hover:text-text-primary transition-colors\"\n title={isImmersiveFullscreen ? 'Exit fullscreen' : 'Enter fullscreen'}\n >\n {isImmersiveFullscreen ? (\n <Shrink className=\"size-3.5\" />\n ) : (\n <Expand className=\"size-3.5\" />\n )}\n </button>\n )}\n {!fullScreen && !isImmersiveFullscreen && (\n <button\n type=\"button\"\n onClick={handleMaximize}\n className=\"p-1 text-text-secondary hover:text-text-primary transition-colors\"\n title={maximized ? 'Restore size' : 'Maximize'}\n >\n {maximized ? (\n <Minimize2 className=\"size-3.5\" />\n ) : (\n <Maximize2 className=\"size-3.5\" />\n )}\n </button>\n )}\n </div>\n )}\n </div>\n\n {/* Terminal Content */}\n {(!collapsed || fullScreen) && activeTab && (\n <div\n className={`${panelHeight || ''} bg-bg-sunken text-text-primary relative ${fullScreen ? 'flex-1' : ''}`}\n style={panelStyle}\n >\n {/* Render all tab iframes but only show the active one */}\n {tabs.map((tab) => {\n const status = connectionStatus[tab.sessionId];\n const isConnecting = status === 'connecting' || tab.loading;\n const terminalUrlIssue = getTerminalUrlIssue(tab.terminalUrl);\n const issue = terminalUrlIssue ?? connectionIssues[tab.sessionId];\n const showIframe = !!tab.terminalUrl && !tab.error && !terminalUrlIssue;\n const showConnectionIssue = !!terminalUrlIssue || (showIframe && status === 'error');\n\n return (\n <div\n key={tab.sessionId}\n className={`absolute inset-0 ${tab.sessionId === activeTab.sessionId ? 'block' : 'hidden'}`}\n >\n {/* Always render iframe if URL exists (keeps terminal state) */}\n {showIframe && tab.terminalUrl && (\n <TerminalIframe\n ref={(el) => {\n iframeRefs.current[tab.sessionId] = el;\n }}\n sessionId={tab.sessionId}\n agentId={tab.agentId ?? null}\n baseTtydUrl={tab.terminalUrl}\n title={`Terminal - ${tab.name}`}\n className=\"w-full h-full border-0\"\n allow=\"clipboard-read *; clipboard-write *\"\n onLoad={() => handleIframeLoad(tab.sessionId)}\n onError={() => handleIframeError(tab.sessionId)}\n />\n )}\n\n {/* Loading/Connecting overlay - shows on top of terminal without destroying it */}\n {showIframe && isConnecting && (\n <div className=\"absolute inset-0 bg-bg-overlay backdrop-blur-[2px] flex items-center justify-center pointer-events-none z-10\">\n <div className=\"flex items-center gap-3 px-4 py-3 bg-bg-surface/95 rounded-lg border border-border-default shadow-lg\">\n <Loader2 className=\"size-5 text-status-warning-text animate-spin\" />\n <div>\n <p className=\"text-text-primary text-sm font-medium\">\n {tab.loading\n ? tr(\n 'vibecontrols.sessions.terminal.startingTerminal',\n 'Starting terminal...'\n )\n : tr('vibecontrols.sessions.terminal.connecting', 'Connecting...')}\n </p>\n <p className=\"text-text-secondary text-xs\">\n {tab.loading\n ? tr(\n 'vibecontrols.sessions.terminal.settingUpTmux',\n 'Setting up tmux + ttyd'\n )\n : tr(\n 'vibecontrols.sessions.terminal.establishingConnection',\n 'Establishing connection'\n )}\n </p>\n </div>\n </div>\n </div>\n )}\n\n {/* Disconnected overlay - auto-reconnect is active in background */}\n {showConnectionIssue && !tab.error && (\n <div className=\"absolute inset-0 bg-bg-overlay backdrop-blur-[2px] flex items-center justify-center z-10\">\n <div className=\"flex flex-col items-center gap-3 px-6 py-4 bg-bg-surface/95 rounded-lg border border-border-default shadow-lg\">\n <WifiOff className=\"size-6 text-status-error-text\" />\n <div className=\"text-center\">\n <p className=\"text-text-primary text-sm font-medium\">\n {tr('vibecontrols.sessions.terminal.connectionLost', 'Connection Lost')}\n </p>\n <p className=\"text-text-secondary text-xs mt-1\">\n {issue\n ? issue\n : (autoReconnectAttempts.current[tab.sessionId] ?? 0) <\n MAX_AUTO_RECONNECT_ATTEMPTS\n ? tr(\n 'vibecontrols.sessions.terminal.reconnectingAutomatically',\n 'Reconnecting automatically...'\n )\n : tr(\n 'vibecontrols.sessions.terminal.connectionInterrupted',\n 'Terminal connection was interrupted'\n )}\n </p>\n </div>\n {!issue &&\n (autoReconnectAttempts.current[tab.sessionId] ?? 0) <\n MAX_AUTO_RECONNECT_ATTEMPTS && (\n <Loader2 className=\"size-4 text-text-tertiary animate-spin\" />\n )}\n <button\n type=\"button\"\n onClick={() => handleReconnect(tab.sessionId)}\n className=\"px-4 py-2 bg-bg-sunken text-text-primary text-sm rounded-md hover:bg-bg-sunken/80 transition-colors inline-flex items-center gap-2\"\n >\n <RotateCcw className=\"size-3.5\" />\n {tr('vibecontrols.sessions.terminal.reconnectNow', 'Reconnect Now')}\n </button>\n </div>\n </div>\n )}\n\n {/* No URL yet - initial loading state (no iframe to show behind) */}\n {!tab.terminalUrl && tab.loading && (\n <div className=\"flex items-center justify-center h-full\">\n <div className=\"text-center\">\n <Loader2 className=\"size-8 text-text-tertiary animate-spin mx-auto mb-3\" />\n <p className=\"text-text-tertiary text-sm\">\n Starting terminal for {tab.name}...\n </p>\n <p className=\"text-text-secondary text-xs mt-1\">\n Setting up tmux + ttyd terminal\n </p>\n </div>\n </div>\n )}\n\n {/* Error state - terminal failed to start */}\n {tab.error && (\n <div className=\"flex items-center justify-center h-full\">\n <div className=\"text-center max-w-md\">\n <AlertCircle className=\"size-8 text-status-error-text mx-auto mb-3\" />\n <p className=\"text-status-error-text text-sm font-medium\">\n {tr(\n 'vibecontrols.sessions.terminal.failedToStart',\n 'Failed to start terminal'\n )}\n </p>\n <p className=\"text-text-tertiary text-xs mt-2\">{tab.error}</p>\n <div className=\"flex items-center justify-center gap-3 mt-4\">\n {onRetryTerminal && sessions && (\n <button\n type=\"button\"\n onClick={() => {\n const session = sessions.find((s) => s.id === tab.sessionId);\n if (session) onRetryTerminal(session);\n }}\n className=\"px-4 py-2 bg-bg-sunken text-text-primary text-sm rounded-md hover:bg-bg-sunken/80 transition-colors inline-flex items-center gap-2\"\n >\n <RotateCcw className=\"size-3.5\" />\n {tr('vibecontrols.sessions.terminal.retry', 'Retry')}\n </button>\n )}\n </div>\n </div>\n </div>\n )}\n\n {/* No terminal URL and not loading - waiting for agent */}\n {!tab.terminalUrl && !tab.loading && !tab.error && (\n <div className=\"flex items-center justify-center h-full\">\n <div className=\"text-center\">\n <Terminal className=\"size-8 text-text-secondary mx-auto mb-3\" />\n <p className=\"text-text-tertiary text-sm\">\n {tr(\n 'vibecontrols.sessions.terminal.noTerminalUrl',\n 'No terminal URL available'\n )}\n </p>\n <p className=\"text-text-secondary text-xs mt-1\">\n {tr(\n 'vibecontrols.sessions.terminal.agentNeedsTtyd',\n 'The agent needs to start ttyd for this session'\n )}\n </p>\n </div>\n </div>\n )}\n </div>\n );\n })}\n </div>\n )}\n\n {/* Empty state for terminal-first mode with no tabs */}\n {(!collapsed || fullScreen) && tabs.length === 0 && fullScreen && (\n <div className=\"flex-1 bg-bg-sunken text-text-primary flex items-center justify-center\">\n <div className=\"text-center max-w-md\">\n <Terminal className=\"size-12 text-text-secondary mx-auto mb-4\" />\n <p className=\"text-text-secondary text-base font-medium mb-2\">\n {tr('vibecontrols.sessions.terminal.noTerminalsOpen', 'No terminals open')}\n </p>\n <p className=\"text-text-secondary text-sm mb-4\">\n {tr(\n 'vibecontrols.sessions.terminal.clickPlusTab',\n 'Click the + tab above to create a new session or open an existing one'\n )}\n </p>\n <div className=\"flex items-center justify-center gap-2 flex-wrap\">\n {onCreateSession && (\n <button\n type=\"button\"\n onClick={onCreateSession}\n className=\"px-4 py-2 bg-bg-sunken text-text-primary text-sm rounded-md hover:bg-bg-sunken/80 transition-colors inline-flex items-center gap-2\"\n >\n <Plus className=\"size-4\" />\n {tr('vibecontrols.sessions.terminal.newSession', 'New Session')}\n </button>\n )}\n </div>\n {availableSessions.length > 0 && (\n <div className=\"mt-6\">\n <p className=\"text-text-secondary text-xs uppercase tracking-wider mb-3\">\n Or open an existing session\n </p>\n <div className=\"flex flex-wrap justify-center gap-2\">\n {availableSessions.slice(0, 6).map((s) => (\n <button\n type=\"button\"\n key={s.id}\n onClick={() => onOpenTerminal?.(s)}\n className=\"flex items-center gap-1.5 px-3 py-1.5 bg-bg-canvas text-text-secondary text-xs rounded-md hover:bg-bg-sunken transition-colors border border-border-default\"\n >\n <Terminal className=\"size-3\" />\n <span className=\"truncate max-w-[120px]\">{s.name}</span>\n <span\n className={`size-1.5 rounded-full ${\n s.status === 'RUNNING' ? 'bg-status-success-text' : 'bg-text-secondary'\n }`}\n />\n </button>\n ))}\n {availableSessions.length > 6 && (\n <button\n type=\"button\"\n onClick={() => {\n setShowSessionPicker(true);\n setSessionSearch('');\n }}\n className=\"text-text-tertiary text-xs self-center hover:text-text-primary transition-colors cursor-pointer\"\n >\n +{availableSessions.length - 6} more\n </button>\n )}\n </div>\n </div>\n )}\n </div>\n </div>\n )}\n\n {/* Session Picker Dropdown (fixed position to escape overflow clipping) */}\n {showSessionPicker &&\n (onCreateSession || availableSessions.length > 0) &&\n plusBtnRef.current && (\n <div\n ref={sessionPickerRef}\n className=\"fixed bg-bg-elevated border border-border-default rounded-md shadow-xl z-[100] w-[280px]\"\n style={{\n top: plusBtnRef.current.getBoundingClientRect().bottom + 4,\n left: Math.min(\n plusBtnRef.current.getBoundingClientRect().left,\n window.innerWidth - 290\n ),\n }}\n >\n {/* Search box */}\n <div className=\"p-2 border-b border-border-subtle\">\n <div className=\"flex items-center gap-2 px-2 py-1.5 bg-bg-sunken rounded border border-border-subtle focus-within:border-border-strong\">\n <Search className=\"size-3.5 text-text-muted shrink-0\" />\n <input\n aria-label=\"Search sessions...\"\n ref={sessionSearchRef}\n type=\"text\"\n value={sessionSearch}\n onChange={(e) => setSessionSearch(e.target.value)}\n placeholder=\"Search sessions...\"\n className=\"w-full bg-bg-surface/0 text-sm text-text-primary outline-none placeholder:text-text-placeholder\"\n />\n {sessionSearch && (\n <button\n type=\"button\"\n onClick={() => setSessionSearch('')}\n className=\"text-text-muted hover:text-text-primary shrink-0\"\n >\n <X className=\"size-3\" />\n </button>\n )}\n </div>\n </div>\n\n {/* {tr('vibecontrols.sessions.terminal.newSession', 'New Session')} action */}\n {onCreateSession && (\n <button\n type=\"button\"\n onClick={() => {\n onCreateSession();\n setShowSessionPicker(false);\n setSessionSearch('');\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors text-left font-medium\"\n >\n <Plus className=\"size-3.5 text-text-secondary\" />\n <span>{tr('vibecontrols.sessions.terminal.newSession', 'New Session')}</span>\n </button>\n )}\n\n {/* Divider + Open existing section */}\n {availableSessions.length > 0 && (\n <>\n <div className=\"border-t border-border-subtle\" />\n <div className=\"px-3 pt-2 pb-1\">\n <span className=\"text-[10px] font-medium uppercase tracking-wider text-text-muted\">\n Open existing ({availableSessions.length})\n </span>\n </div>\n <div className=\"max-h-48 overflow-y-auto pb-1\">\n {filteredPickerSessions.length > 0 ? (\n filteredPickerSessions.map((s) => (\n <button\n type=\"button\"\n key={s.id}\n onClick={() => {\n onOpenTerminal?.(s);\n setShowSessionPicker(false);\n setSessionSearch('');\n }}\n className=\"flex items-center gap-2 w-full px-3 py-1.5 text-sm text-text-primary hover:bg-bg-sunken transition-colors text-left\"\n >\n <Terminal className=\"size-3.5 text-text-secondary shrink-0\" />\n <span className=\"truncate\">{s.name}</span>\n <span\n className={`ml-auto text-[10px] px-1.5 py-0.5 rounded shrink-0 ${\n s.status === 'RUNNING'\n ? 'bg-status-success-text/10 text-status-success-text'\n : s.status === 'STOPPED' || s.status === 'TERMINATED'\n ? 'bg-text-secondary/10 text-text-tertiary'\n : 'bg-status-warning-text/10 text-status-warning-text'\n }`}\n >\n {s.status.toLowerCase()}\n </span>\n </button>\n ))\n ) : (\n <div className=\"px-3 py-2 text-xs text-text-muted\">\n No sessions matching “{sessionSearch}”\n </div>\n )}\n </div>\n </>\n )}\n </div>\n )}\n\n {/* Context Menu */}\n {contextMenu.visible && contextTab && (\n <div\n ref={contextMenuRef}\n className=\"fixed bg-bg-elevated border border-border-default rounded-lg shadow-lg z-[100] min-w-[180px] py-1\"\n style={{ top: contextMenu.y, left: contextMenu.x }}\n >\n {onRenameSession && (\n <button\n type=\"button\"\n onClick={() => handleStartRename(contextTab.sessionId)}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n <Edit3 className=\"size-3.5\" />\n Rename\n </button>\n )}\n {contextTab.terminalUrl && (\n <button\n type=\"button\"\n onClick={() => {\n onOpenExternal(contextTab.sessionId);\n setContextMenu((prev) => ({ ...prev, visible: false }));\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n <ExternalLink className=\"size-3.5\" />\n {tr('vibecontrols.sessions.terminal.openInNewTab', 'Open in New Tab')}\n </button>\n )}\n {/* Reconnect option in context menu */}\n {contextTab.terminalUrl && connectionStatus[contextTab.sessionId] === 'error' && (\n <button\n type=\"button\"\n onClick={() => {\n handleReconnect(contextTab.sessionId);\n setContextMenu((prev) => ({ ...prev, visible: false }));\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-status-warning-text hover:bg-bg-sunken transition-colors\"\n >\n <RotateCcw className=\"size-3.5\" />\n Reconnect\n </button>\n )}\n {onRestartTerminal && (\n <button\n type=\"button\"\n onClick={() => {\n onRestartTerminal(contextTab.sessionId);\n setContextMenu((prev) => ({ ...prev, visible: false }));\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n <RotateCcw className=\"size-3.5\" />\n Restart Terminal\n </button>\n )}\n {onStopTerminal && (\n <button\n type=\"button\"\n onClick={() => {\n onStopTerminal(contextTab.sessionId);\n setContextMenu((prev) => ({ ...prev, visible: false }));\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n <Square className=\"size-3.5\" />\n Stop Terminal\n </button>\n )}\n {onStopSession && (\n <button\n type=\"button\"\n onClick={() => {\n onStopSession(contextTab.sessionId);\n setContextMenu((prev) => ({ ...prev, visible: false }));\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-status-warning-text hover:bg-bg-sunken transition-colors\"\n >\n <Square className=\"size-3.5\" />\n {tr('vibecontrols.sessions.terminal.stopSession', 'Stop Session')}\n </button>\n )}\n <div className=\"border-t border-border-subtle my-1\" />\n <button\n type=\"button\"\n onClick={() => {\n onTabClose(contextTab.sessionId);\n setContextMenu((prev) => ({ ...prev, visible: false }));\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-status-error-text hover:bg-bg-sunken transition-colors\"\n >\n <X className=\"size-3.5\" />\n {tr('vibecontrols.sessions.terminal.closeTab', 'Close Tab')}\n </button>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|