@burdenoff/microfe-vibecontrols 2026.608.2 → 2026.608.3

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.
@@ -1 +1 @@
1
- {"version":3,"file":"TerminalPanel.js","names":[],"sources":["../../../src/components/sessions/TerminalPanel.tsx"],"sourcesContent":["import { useState, useCallback, useRef, useEffect, useMemo, 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 ScrollText,\n Pin,\n PinOff,\n MonitorSmartphone,\n Info,\n Server,\n Share2,\n} from 'lucide-react';\nimport type { Session } from './types';\nimport { TerminalIframe } from './TerminalIframe';\nimport { ConnectionLogPanel } from '@/components/agents/ConnectionLogPanel';\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 /** Owning agent's display name — shown as a tab tooltip/badge. */\n agentName?: string | null;\n /** Target machine name the session runs on — shown as a tab tooltip/badge. */\n targetName?: string | null;\n /** Target machine kind (DIRECT/SSH/...) for the tab badge. */\n targetType?: string | null;\n /** Pinned tabs sort to the front and survive \"close others\". */\n pinned?: boolean;\n}\n\ninterface ContextMenuState {\n visible: boolean;\n x: number;\n y: number;\n sessionId: string | null;\n}\n\ninterface TerminalTabItemProps {\n tab: TerminalTab;\n index: number;\n isActive: boolean;\n isDragging: boolean;\n statusIndicator: ReactNode;\n isRenaming: boolean;\n renameValue: string;\n renameInputRef: React.RefObject<HTMLInputElement | null>;\n onRenameChange: (value: string) => void;\n onRenameBlur: () => void;\n onRenameKeyDown: (e: React.KeyboardEvent) => void;\n onSelect: () => void;\n onClose: () => void;\n onContextMenu: (e: React.MouseEvent) => void;\n onDoubleClick: () => void;\n onDragStart: (e: React.DragEvent) => void;\n onDragOver: (e: React.DragEvent) => void;\n onDrop: (e: React.DragEvent) => void;\n onDragEnd: () => void;\n closeLabel: string;\n}\n\n/**\n * A single terminal tab. Extracted so the immersive and docked tab bars\n * render identical behaviour (drag-reorder, middle-click close, pin, the\n * agent/target context badge) from one place.\n */\nfunction TerminalTabItem({\n tab,\n index,\n isActive,\n isDragging,\n statusIndicator,\n isRenaming,\n renameValue,\n renameInputRef,\n onRenameChange,\n onRenameBlur,\n onRenameKeyDown,\n onSelect,\n onClose,\n onContextMenu,\n onDoubleClick,\n onDragStart,\n onDragOver,\n onDrop,\n onDragEnd,\n closeLabel,\n}: TerminalTabItemProps) {\n const tooltip = [\n tab.name,\n tab.agentName ? `agent: ${tab.agentName}` : null,\n tab.targetName\n ? `target: ${tab.targetName}${tab.targetType ? ` (${tab.targetType})` : ''}`\n : null,\n ]\n .filter(Boolean)\n .join('\\n');\n\n return (\n <div\n role=\"button\"\n tabIndex={0}\n draggable={!isRenaming}\n title={tooltip}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n onSelect();\n }\n }}\n onDragStart={onDragStart}\n onDragOver={onDragOver}\n onDrop={onDrop}\n onDragEnd={onDragEnd}\n data-testid=\"terminal-tab\"\n data-session-id={tab.sessionId}\n data-tab-index={index}\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-[200px] group ${\n isActive\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 } ${isDragging ? 'opacity-50' : ''}`}\n onClick={onSelect}\n onAuxClick={(e) => {\n // Middle-click closes the tab.\n if (e.button === 1) {\n e.preventDefault();\n onClose();\n }\n }}\n onContextMenu={onContextMenu}\n onDoubleClick={onDoubleClick}\n >\n {tab.pinned && <Pin className=\"size-2.5 text-action-primary-text shrink-0\" aria-hidden />}\n {statusIndicator}\n {isRenaming ? (\n <input\n aria-label=\"control\"\n ref={renameInputRef}\n value={renameValue}\n onChange={(e) => onRenameChange(e.target.value)}\n onBlur={onRenameBlur}\n onKeyDown={onRenameKeyDown}\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 {tab.targetName && (\n <MonitorSmartphone\n className=\"size-2.5 text-text-muted shrink-0 hidden sm:inline\"\n aria-hidden\n />\n )}\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onClose();\n }}\n className={`ml-auto hover:text-status-error-text transition-opacity shrink-0 ${\n tab.pinned ? 'opacity-0 group-hover:opacity-100' : 'opacity-0 group-hover:opacity-100'\n }`}\n title={closeLabel}\n aria-label={closeLabel}\n >\n <X className=\"size-3\" />\n </button>\n </div>\n );\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 /**\n * Reconnect to the still-running agent session without stopping it.\n * The parent re-issues the idempotent startSessionTerminal (reattach)\n * and refreshes the tab URL; the panel additionally remounts the iframe\n * with a fresh capability token. This is the fix for \"terminal idles\n * out and the only way back was a full browser refresh\".\n */\n onReattachTerminal?: (sessionId: string) => void;\n /** Reorder tabs (drag-and-drop). Receives the new session-id order. */\n onReorderTabs?: (orderedSessionIds: string[]) => void;\n /** Toggle a tab's pinned state. */\n onTogglePin?: (sessionId: string) => void;\n /** Open the session info panel for a tab (terminal-view feature parity). */\n onShowSessionInfo?: (sessionId: string) => void;\n /** Navigate to a tab's agent detail page. */\n onViewAgent?: (sessionId: string) => void;\n /** Navigate to a tab's target detail page. */\n onViewTarget?: (sessionId: string) => void;\n /** Share / offer a session to a teammate. */\n onShareSession?: (sessionId: string) => void;\n /** Called when user stops the entire session */\n onStopSession?: (sessionId: string) => void;\n /** Called to create a new session *with options* (opens the full form). */\n onCreateSession?: () => void;\n /**\n * One-click quick-create: the primary \"+\" remembers the last-used\n * agent/vibe/type and spawns a terminal with an auto-generated name.\n * When set, the \"+\" quick-creates and the adjacent caret opens the menu\n * (options form / open existing). Falls back to `onCreateSession`.\n */\n onQuickCreate?: () => void;\n /**\n * Increment to request the panel start renaming its active tab. Driven by\n * the page-level F2 shortcut, since rename state lives inside this panel.\n */\n renameActiveTabNonce?: number;\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 onReattachTerminal,\n onReorderTabs,\n onTogglePin,\n onShowSessionInfo,\n onViewAgent,\n onViewTarget,\n onShareSession,\n onStopSession,\n onCreateSession,\n onQuickCreate,\n renameActiveTabNonce,\n fullScreen = false,\n isImmersiveFullscreen = false,\n onToggleImmersiveFullscreen,\n contextSwitcher,\n}: TerminalPanelProps) {\n const tr = useTr();\n const [maximized, setMaximized] = useState(false);\n const [showConnectionLog, setShowConnectionLog] = 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 // Sessions whose stop request is currently in flight. The UI flips to a\n // spinner + disabled state so a second click can't fire and the user gets\n // immediate feedback that the click registered. We clear an entry when\n // the upstream session list reports STOPPED/TERMINATED, or after 8s as a\n // fallback if the mutation hangs.\n const [stoppingSessionIds, setStoppingSessionIds] = useState<Set<string>>(() => new Set());\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 // Per-session iframe remount nonce. Bumped on (re)connect so the iframe\n // fully remounts → useTerminalCapabilityUrl mints a brand-new token, so a\n // stale/expired token can never block the reload.\n const [reloadNonce, setReloadNonce] = useState<Record<string, number>>({});\n // Liveness-probe consecutive-failure counters per session.\n const livenessFailures = useRef<Record<string, number>>({});\n // Drag-to-reorder: index of the tab currently being dragged.\n const [dragIndex, setDragIndex] = useState<number | null>(null);\n\n const bumpReload = useCallback((sessionId: string) => {\n setReloadNonce((prev) => ({ ...prev, [sessionId]: (prev[sessionId] ?? 0) + 1 }));\n }, []);\n\n // Pinned tabs render first; order is otherwise stable (drag order). Each\n // tab is enriched with its agent/target names from the live sessions list\n // so the tab tooltip/badge shows which machine it runs on without the\n // parent having to thread that metadata through every tab-creation site.\n const displayTabs = [...tabs]\n .sort((a, b) => (b.pinned ? 1 : 0) - (a.pinned ? 1 : 0))\n .map((tab) => {\n const session = sessions?.find((s) => s.id === tab.sessionId);\n return {\n ...tab,\n agentName: tab.agentName ?? session?.agent?.name ?? null,\n targetName: tab.targetName ?? session?.agent?.target?.name ?? null,\n targetType: tab.targetType ?? session?.agent?.target?.type ?? null,\n };\n });\n\n const activeTab = tabs.find((t) => t.sessionId === activeTabId) || tabs[0] || null;\n\n // Live status lookup, keyed by session id, from the upstream sessions list.\n // Used to drive the \"Session stopped\" overlay so a stopped session gives the\n // operator clear feedback instead of leaving a frozen terminal iframe (which\n // otherwise gets mislabelled \"Connection Lost — reconnecting…\").\n const sessionStatusById = useMemo(() => {\n const map = new Map<string, string>();\n (sessions ?? []).forEach((s) => map.set(s.id, s.status));\n return map;\n }, [sessions]);\n\n const isSessionEnded = useCallback(\n (sessionId: string): boolean => {\n const status = sessionStatusById.get(sessionId);\n return status === 'STOPPED' || status === 'TERMINATED' || status === 'ERROR';\n },\n [sessionStatusById]\n );\n\n // Wrap onStopSession so the click instantly enters a pending state. The\n // caller's mutation may take 1–3s to round-trip; without this the button\n // looks unresponsive and operators tend to click again, which we then\n // have to ignore via the disabled state.\n const handleStopSessionClick = useCallback(\n (sessionId: string) => {\n if (!onStopSession) return;\n setStoppingSessionIds((prev) => {\n if (prev.has(sessionId)) return prev;\n const next = new Set(prev);\n next.add(sessionId);\n return next;\n });\n try {\n onStopSession(sessionId);\n } catch {\n setStoppingSessionIds((prev) => {\n if (!prev.has(sessionId)) return prev;\n const next = new Set(prev);\n next.delete(sessionId);\n return next;\n });\n }\n // Safety fallback so a hung mutation doesn't leave the button stuck.\n window.setTimeout(() => {\n setStoppingSessionIds((prev) => {\n if (!prev.has(sessionId)) return prev;\n const next = new Set(prev);\n next.delete(sessionId);\n return next;\n });\n }, 8000);\n },\n [onStopSession]\n );\n\n // Clear pending state as soon as the upstream sessions list reports the\n // session as STOPPED/TERMINATED. This is the success signal; the 8s\n // fallback above only kicks in if the round-trip silently fails.\n useEffect(() => {\n if (!sessions || stoppingSessionIds.size === 0) return;\n const terminal = new Set(\n sessions\n .filter((s) => s.status === 'STOPPED' || s.status === 'TERMINATED' || s.status === 'ERROR')\n .map((s) => s.id)\n );\n let changed = false;\n const next = new Set(stoppingSessionIds);\n stoppingSessionIds.forEach((id) => {\n if (terminal.has(id)) {\n next.delete(id);\n changed = true;\n }\n });\n if (changed) setStoppingSessionIds(next);\n }, [sessions, stoppingSessionIds]);\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 — reattach to the still-running agent session.\n //\n // Previously this just reloaded the iframe's existing src, which carried\n // a possibly-expired 60s capability token, so an idled-out terminal often\n // dead-ended until a full browser refresh. Now we (1) ask the parent to\n // re-issue the idempotent startSessionTerminal (reattach to the live\n // session / refresh ttydUrl, never stop+start), and (2) bump the remount\n // nonce so TerminalIframe mounts fresh and mints a brand-new token.\n const handleReconnect = useCallback(\n (sessionId: string) => {\n clearAutoReconnect(sessionId);\n livenessFailures.current[sessionId] = 0;\n setConnectionIssues((prev) => ({ ...prev, [sessionId]: undefined }));\n setConnectionStatus((prev) => ({ ...prev, [sessionId]: 'connecting' }));\n bumpReload(sessionId);\n onReattachTerminal?.(sessionId);\n },\n [clearAutoReconnect, bumpReload, onReattachTerminal]\n );\n\n // Drag-to-reorder handlers (mirrors AgentManagerTabBar). Operates on the\n // displayed order and emits the new session-id order to the parent.\n const handleTabDragStart = useCallback((e: React.DragEvent, index: number) => {\n setDragIndex(index);\n e.dataTransfer.effectAllowed = 'move';\n e.dataTransfer.setData('text/plain', String(index));\n }, []);\n\n const handleTabDragOver = useCallback((e: React.DragEvent) => {\n e.preventDefault();\n e.dataTransfer.dropEffect = 'move';\n }, []);\n\n const handleTabDrop = useCallback(\n (e: React.DragEvent, dropIndex: number) => {\n e.preventDefault();\n if (dragIndex === null || dragIndex === dropIndex || !onReorderTabs) {\n setDragIndex(null);\n return;\n }\n const reordered = [...displayTabs];\n const [moved] = reordered.splice(dragIndex, 1);\n reordered.splice(dropIndex, 0, moved);\n onReorderTabs(reordered.map((t) => t.sessionId));\n setDragIndex(null);\n },\n [dragIndex, displayTabs, onReorderTabs]\n );\n\n const handleTabDragEnd = useCallback(() => setDragIndex(null), []);\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 // A session the operator (or the agent) stopped is gone for good — don't\n // hammer /health trying to reconnect a dead terminal, and let the\n // \"Session stopped\" overlay own the UI instead of \"Reconnecting…\".\n if (isSessionEnded(tab.sessionId)) {\n clearAutoReconnect(tab.sessionId);\n continue;\n }\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 // Probe the agent health before remounting the iframe.\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 — remount the iframe with a fresh token.\n setConnectionStatus((prev) => ({\n ...prev,\n [tab.sessionId]: 'connecting',\n }));\n bumpReload(tab.sessionId);\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, bumpReload, isSessionEnded]);\n\n // ── Liveness heartbeat ───────────────────────────────────────────────\n // ttyd's websocket can drop after idle without the iframe firing an\n // `error` event, so React keeps thinking the tab is \"connected\" and the\n // operator is stranded on ttyd's internal \"connection closed\" screen\n // with no app-level reconnect. Poll the active connected tab's agent\n // `/health` every 20s; after two consecutive failures, flip the tab to\n // `error` which surfaces the (manual + auto) reconnect path. Conservative\n // by design — a single transient failure never trips it.\n useEffect(() => {\n if (!activeTab || !activeTab.terminalUrl || activeTab.loading || activeTab.error) return;\n if (connectionStatus[activeTab.sessionId] !== 'connected') return;\n // Stopped sessions are intentionally dead — skip the heartbeat so we don't\n // flip a stopped tab into the \"error → reconnect\" path.\n if (isSessionEnded(activeTab.sessionId)) return;\n\n let origin: string;\n try {\n origin = new URL(activeTab.terminalUrl).origin;\n } catch {\n return;\n }\n\n const sessionId = activeTab.sessionId;\n const interval = setInterval(() => {\n fetch(`${origin}/health`, { signal: AbortSignal.timeout(5_000) })\n .then((res) => {\n if (res.ok) {\n livenessFailures.current[sessionId] = 0;\n } else {\n throw new Error('unhealthy');\n }\n })\n .catch(() => {\n const next = (livenessFailures.current[sessionId] ?? 0) + 1;\n livenessFailures.current[sessionId] = next;\n if (next >= 2) {\n setConnectionStatusForTab(sessionId, 'error');\n }\n });\n }, 20_000);\n\n return () => clearInterval(interval);\n }, [activeTab, connectionStatus, setConnectionStatusForTab, isSessionEnded]);\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 // F2 bridge: the page bumps `renameActiveTabNonce` to start renaming the\n // active tab. Guard on the last-seen nonce so merely switching the active\n // tab (which is also a dep) can never spuriously trigger a rename.\n const lastRenameNonce = useRef(renameActiveTabNonce ?? 0);\n useEffect(() => {\n if (renameActiveTabNonce === undefined) return;\n if (renameActiveTabNonce === lastRenameNonce.current) return;\n lastRenameNonce.current = renameActiveTabNonce;\n if (activeTab) handleStartRename(activeTab.sessionId);\n }, [renameActiveTabNonce, activeTab, handleStartRename]);\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 // Full session row for the context-menu target — used to gate the\n // View agent / View target entries on what the session actually has.\n const contextSession = contextMenu.sessionId\n ? (sessions?.find((s) => s.id === contextMenu.sessionId) ?? null)\n : null;\n\n // Session-level context-menu entries (info / view agent / view target /\n // share) — rendered identically in both the docked and immersive menus so\n // terminal view reaches feature parity with grid/list.\n const renderSessionContextActions = (sessionId: string) => (\n <>\n {onShowSessionInfo && (\n <button\n type=\"button\"\n onClick={() => {\n onShowSessionInfo(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 <Info className=\"size-3.5\" />\n {tr('vibecontrols.sessions.terminal.sessionInfo', 'Session info')}\n </button>\n )}\n {onViewAgent && contextSession?.agent && (\n <button\n type=\"button\"\n onClick={() => {\n onViewAgent(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 <Server className=\"size-3.5\" />\n {tr('vibecontrols.sessions.terminal.viewAgent', 'View agent')}\n </button>\n )}\n {onViewTarget && contextSession?.agent?.target && (\n <button\n type=\"button\"\n onClick={() => {\n onViewTarget(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 <MonitorSmartphone className=\"size-3.5\" />\n {tr('vibecontrols.sessions.terminal.viewTarget', 'View target')}\n </button>\n )}\n {onShareSession && (\n <button\n type=\"button\"\n onClick={() => {\n onShareSession(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 <Share2 className=\"size-3.5\" />\n {tr('vibecontrols.sessions.terminal.shareSession', 'Share / offer session')}\n </button>\n )}\n {(onShowSessionInfo || onViewAgent || onViewTarget || onShareSession) && (\n <div className=\"border-t border-border-subtle my-1\" />\n )}\n </>\n );\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 — always available for a live terminal so an\n idled-out ttyd session can be reattached without a refresh.\n Highlighted when the connection is known-broken. */}\n {activeTab?.terminalUrl && (\n <button\n type=\"button\"\n onClick={() => handleReconnect(activeTab.sessionId)}\n className={`p-1.5 transition-colors ${\n connectionStatus[activeTab.sessionId] === 'error'\n ? 'text-status-warning-text hover:text-status-warning-text/80'\n : 'text-text-secondary hover:text-text-primary'\n }`}\n title={tr('vibecontrols.sessions.terminal.reconnect', 'Reconnect')}\n aria-label={tr('vibecontrols.sessions.terminal.reconnect', 'Reconnect')}\n data-testid=\"terminal-reconnect\"\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={tr(\n 'vibecontrols.sessions.terminal.openInNewTabShortcut',\n 'Open in new tab (⌘/Ctrl+O)'\n )}\n >\n <ExternalLink className=\"size-4\" />\n </button>\n )}\n {/* Stop Session — also exposed in the immersive header so\n operators can shut down the underlying session without\n having to leave fullscreen first. */}\n {activeTab && onStopSession && (\n <button\n type=\"button\"\n onClick={() => handleStopSessionClick(activeTab.sessionId)}\n disabled={stoppingSessionIds.has(activeTab.sessionId)}\n className=\"inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md border border-status-error-bg/40 bg-status-error-bg/10 text-status-error-text hover:bg-status-error-bg/20 hover:border-status-error-bg/60 active:scale-95 transition-all disabled:opacity-60 disabled:cursor-not-allowed\"\n title={tr('vibecontrols.sessions.terminal.stopSession', 'Stop Session')}\n aria-label={tr('vibecontrols.sessions.terminal.stopSession', 'Stop Session')}\n aria-busy={stoppingSessionIds.has(activeTab.sessionId)}\n >\n {stoppingSessionIds.has(activeTab.sessionId) ? (\n <Loader2 className=\"size-3.5 animate-spin\" />\n ) : (\n <Square className=\"size-3.5 fill-current\" />\n )}\n <span className=\"text-xs font-medium\">\n {stoppingSessionIds.has(activeTab.sessionId)\n ? tr('vibecontrols.sessions.terminal.stoppingSession', 'Stopping…')\n : tr('vibecontrols.sessions.terminal.stopSession', 'Stop')}\n </span>\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 {displayTabs.map((tab, index) => (\n <TerminalTabItem\n key={tab.sessionId}\n tab={tab}\n index={index}\n isActive={tab.sessionId === activeTab?.sessionId}\n isDragging={dragIndex === index}\n statusIndicator={renderStatusIndicator(tab)}\n isRenaming={renamingTabId === tab.sessionId}\n renameValue={renameValue}\n renameInputRef={renameInputRef}\n onRenameChange={setRenameValue}\n onRenameBlur={handleFinishRename}\n onRenameKeyDown={handleRenameKeyDown}\n onSelect={() => onTabSelect(tab.sessionId)}\n onClose={() => onTabClose(tab.sessionId)}\n onContextMenu={(e) => handleContextMenu(e, tab.sessionId)}\n onDoubleClick={() => handleStartRename(tab.sessionId)}\n onDragStart={(e) => handleTabDragStart(e, index)}\n onDragOver={handleTabDragOver}\n onDrop={(e) => handleTabDrop(e, index)}\n onDragEnd={handleTabDragEnd}\n closeLabel={tr('vibecontrols.sessions.terminal.closeTab', 'Close terminal')}\n />\n ))}\n </div>\n\n {/* Add tab: split \"+\" (one-click quick-create) + caret (menu) */}\n <div className=\"flex items-center shrink-0 border-r border-border-subtle\">\n <button\n type=\"button\"\n onClick={() => {\n if (onQuickCreate) onQuickCreate();\n else if (onCreateSession || availableSessions.length > 0) {\n setShowSessionPicker((prev) => !prev);\n setSessionSearch('');\n }\n }}\n className=\"flex items-center pl-2.5 pr-1.5 py-1.5 text-text-secondary hover:text-text-primary transition-colors\"\n title={\n onQuickCreate\n ? tr('vibecontrols.sessions.terminal.quickNewSession', 'New terminal (⌘/Ctrl+T)')\n : tr('vibecontrols.sessions.terminal.newOrOpen', 'New session or open existing')\n }\n data-testid=\"terminal-quick-create\"\n >\n <Plus className=\"size-3.5\" />\n </button>\n <button\n type=\"button\"\n ref={plusBtnRef}\n onClick={() => {\n setShowSessionPicker((prev) => !prev);\n setSessionSearch('');\n }}\n className=\"flex items-center pr-2 pl-0.5 py-1.5 text-text-secondary hover:text-text-primary transition-colors\"\n title={tr(\n 'vibecontrols.sessions.terminal.moreSessionOptions',\n 'New session with options / open existing'\n )}\n aria-haspopup=\"menu\"\n aria-expanded={showSessionPicker}\n data-testid=\"terminal-session-menu\"\n >\n <ChevronDown className=\"size-3\" />\n </button>\n </div>\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 // A stopped/terminated session takes precedence over every\n // connection overlay: the terminal is gone on purpose, so we\n // show an explicit \"Session stopped\" state rather than the\n // misleading \"Connection Lost — reconnecting…\" spinner.\n const sessionEnded = isSessionEnded(tab.sessionId);\n const endedStatus = sessionStatusById.get(tab.sessionId);\n const showConnectionIssue =\n !sessionEnded && (!!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 key={`tif-${tab.sessionId}-${reloadNonce[tab.sessionId] ?? 0}`}\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 && !sessionEnded && (\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.settingUpWebTerminal',\n 'Setting up session + web terminal'\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 {/* Session stopped overlay — definitive feedback that a\n Stop Session / Stop Terminal action took effect. Covers\n the (now dead) iframe so the operator isn't left staring\n at a frozen terminal wondering whether the stop worked. */}\n {sessionEnded && (\n <div\n className=\"absolute inset-0 bg-bg-overlay backdrop-blur-[2px] flex items-center justify-center z-20\"\n data-testid=\"terminal-session-stopped\"\n >\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 <Square className=\"size-6 text-text-secondary\" />\n <div className=\"text-center\">\n <p className=\"text-text-primary text-sm font-medium\">\n {endedStatus === 'ERROR'\n ? tr('vibecontrols.sessions.terminal.sessionErrored', 'Session ended')\n : tr(\n 'vibecontrols.sessions.terminal.sessionStopped',\n 'Session stopped'\n )}\n </p>\n <p className=\"text-text-secondary text-xs mt-1\">\n {tr(\n 'vibecontrols.sessions.terminal.sessionStoppedHint',\n 'This terminal is no longer running.'\n )}\n </p>\n </div>\n <div className=\"flex items-center gap-2\">\n {onRestartTerminal && (\n <button\n type=\"button\"\n onClick={() => onRestartTerminal(tab.sessionId)}\n className=\"px-3 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.restartSession', 'Restart')}\n </button>\n )}\n <button\n type=\"button\"\n onClick={() => onTabClose(tab.sessionId)}\n className=\"px-3 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 <X className=\"size-3.5\" />\n {tr('vibecontrols.sessions.terminal.closeTab', 'Close')}\n </button>\n </div>\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 session + web 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 {(onQuickCreate || onCreateSession) && (\n <button\n type=\"button\"\n onClick={() => (onQuickCreate ?? 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>\n {tr(\n 'vibecontrols.sessions.terminal.newSessionWithOptions',\n 'New session with options…'\n )}\n </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 &ldquo;{sessionSearch}&rdquo;\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 <span className=\"ml-auto text-[10px] text-text-tertiary font-mono\">F2</span>\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 {renderSessionContextActions(contextTab.sessionId)}\n {onTogglePin && (\n <button\n type=\"button\"\n onClick={() => {\n onTogglePin(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 {contextTab.pinned ? <PinOff className=\"size-3.5\" /> : <Pin className=\"size-3.5\" />}\n {contextTab.pinned\n ? tr('vibecontrols.sessions.terminal.unpinTab', 'Unpin Tab')\n : tr('vibecontrols.sessions.terminal.pinTab', 'Pin Tab')}\n </button>\n )}\n {contextTab.terminalUrl && (\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 hover:bg-bg-sunken transition-colors ${\n connectionStatus[contextTab.sessionId] === 'error'\n ? 'text-status-warning-text'\n : 'text-text-primary'\n }`}\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 {displayTabs.map((tab, index) => (\n <TerminalTabItem\n key={tab.sessionId}\n tab={tab}\n index={index}\n isActive={tab.sessionId === activeTab?.sessionId}\n isDragging={dragIndex === index}\n statusIndicator={renderStatusIndicator(tab)}\n isRenaming={renamingTabId === tab.sessionId}\n renameValue={renameValue}\n renameInputRef={renameInputRef}\n onRenameChange={setRenameValue}\n onRenameBlur={handleFinishRename}\n onRenameKeyDown={handleRenameKeyDown}\n onSelect={() => onTabSelect(tab.sessionId)}\n onClose={() => onTabClose(tab.sessionId)}\n onContextMenu={(e) => handleContextMenu(e, tab.sessionId)}\n onDoubleClick={() => handleStartRename(tab.sessionId)}\n onDragStart={(e) => handleTabDragStart(e, index)}\n onDragOver={handleTabDragOver}\n onDrop={(e) => handleTabDrop(e, index)}\n onDragEnd={handleTabDragEnd}\n closeLabel={tr('vibecontrols.sessions.terminal.closeTab', 'Close terminal')}\n />\n ))}\n </div>\n )}\n\n {/* Add tab: split \"+\" (quick-create) + caret (menu) — outside the\n overflow container so the dropdown isn't clipped */}\n {(!collapsed || fullScreen) && (\n <div className=\"flex items-center shrink-0 border-r border-border-subtle\">\n <button\n type=\"button\"\n onClick={() => {\n if (onQuickCreate) onQuickCreate();\n else if (onCreateSession || availableSessions.length > 0) {\n setShowSessionPicker((prev) => !prev);\n setSessionSearch('');\n }\n }}\n className=\"flex items-center pl-2.5 pr-1.5 py-1.5 text-text-secondary hover:text-text-primary transition-colors\"\n title={\n onQuickCreate\n ? tr('vibecontrols.sessions.terminal.quickNewSession', 'New terminal (⌘/Ctrl+T)')\n : tr('vibecontrols.sessions.terminal.newOrOpen', 'New session or open existing')\n }\n data-testid=\"terminal-quick-create\"\n >\n <Plus className=\"size-3.5\" />\n </button>\n <button\n type=\"button\"\n ref={plusBtnRef}\n onClick={() => {\n setShowSessionPicker((prev) => !prev);\n setSessionSearch('');\n }}\n className=\"flex items-center pr-2 pl-0.5 py-1.5 text-text-secondary hover:text-text-primary transition-colors\"\n title={tr(\n 'vibecontrols.sessions.terminal.moreSessionOptions',\n 'New session with options / open existing'\n )}\n aria-haspopup=\"menu\"\n aria-expanded={showSessionPicker}\n data-testid=\"terminal-session-menu\"\n >\n <ChevronDown className=\"size-3\" />\n </button>\n </div>\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 — always available for a live terminal so an\n idled-out ttyd session can be reattached without a refresh.\n Highlighted when the connection is known-broken. */}\n {activeTab?.terminalUrl && (\n <button\n type=\"button\"\n onClick={() => handleReconnect(activeTab.sessionId)}\n className={`p-1 transition-colors ${\n connectionStatus[activeTab.sessionId] === 'error'\n ? 'text-status-warning-text hover:text-status-warning-text/80'\n : 'text-text-secondary hover:text-text-primary'\n }`}\n title={tr('vibecontrols.sessions.terminal.reconnect', 'Reconnect')}\n aria-label={tr('vibecontrols.sessions.terminal.reconnect', 'Reconnect')}\n data-testid=\"terminal-reconnect\"\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={tr(\n 'vibecontrols.sessions.terminal.openInNewTabShortcut',\n 'Open in new tab (⌘/Ctrl+O)'\n )}\n >\n <ExternalLink className=\"size-3.5\" />\n </button>\n )}\n {/* Stop Session — visible alongside the existing terminal\n actions so operators don't have to right-click the tab\n to discover the context menu. Mirrors the Stop Session\n entry there: kills the underlying session (not just\n ttyd) and surfaces quota errors via the shared modal. */}\n {activeTab && onStopSession && (\n <button\n type=\"button\"\n onClick={() => handleStopSessionClick(activeTab.sessionId)}\n disabled={stoppingSessionIds.has(activeTab.sessionId)}\n className=\"inline-flex items-center gap-1 px-2 py-0.5 rounded-md border border-status-error-bg/40 bg-status-error-bg/10 text-status-error-text hover:bg-status-error-bg/20 hover:border-status-error-bg/60 active:scale-95 transition-all disabled:opacity-60 disabled:cursor-not-allowed\"\n title={tr('vibecontrols.sessions.terminal.stopSession', 'Stop Session')}\n aria-label={tr('vibecontrols.sessions.terminal.stopSession', 'Stop Session')}\n aria-busy={stoppingSessionIds.has(activeTab.sessionId)}\n >\n {stoppingSessionIds.has(activeTab.sessionId) ? (\n <Loader2 className=\"size-3 animate-spin\" />\n ) : (\n <Square className=\"size-3 fill-current\" />\n )}\n <span className=\"text-[11px] font-medium\">\n {stoppingSessionIds.has(activeTab.sessionId)\n ? tr('vibecontrols.sessions.terminal.stoppingSession', 'Stopping…')\n : tr('vibecontrols.sessions.terminal.stopSession', 'Stop')}\n </span>\n </button>\n )}\n {/* Connection-log toggle — surfaces the same step-by-step\n timeline operators can see on the AgentDetails page,\n scoped to the active session. Required for the\n \"Connect fails — why?\" debugging path that previously\n forced operators to dig through the agent's local\n log file. */}\n {activeTab && (\n <button\n type=\"button\"\n onClick={() => setShowConnectionLog((v) => !v)}\n className={`p-1 transition-colors ${\n showConnectionLog\n ? 'text-action-primary-text bg-action-primary-bg rounded'\n : 'text-text-secondary hover:text-text-primary'\n }`}\n title={tr('vibecontrols.sessions.terminal.connectionLog', 'Connection Log')}\n >\n <ScrollText 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 {/* Connection-log slide-over for the active session. Renders\n ABOVE the terminal iframe so operators can read the\n step-by-step trace without losing their place. */}\n {showConnectionLog && activeTab && (\n <div className=\"absolute inset-0 z-30 bg-bg-surface flex flex-col\">\n <div className=\"flex items-center justify-between px-4 py-2 border-b border-border-default\">\n <div className=\"flex items-center gap-2 text-sm font-semibold text-text-primary\">\n <ScrollText className=\"size-4\" />\n {tr('vibecontrols.sessions.terminal.connectionLog', 'Connection Log')}\n <span className=\"text-text-tertiary font-normal\">· {activeTab.name}</span>\n </div>\n <button\n type=\"button\"\n onClick={() => setShowConnectionLog(false)}\n className=\"p-1 text-text-secondary hover:text-text-primary\"\n title={tr('common.close', 'Close')}\n >\n <X className=\"size-4\" />\n </button>\n </div>\n <div className=\"flex-1 overflow-hidden\">\n <ConnectionLogPanel sessionId={activeTab.sessionId} sessionName={activeTab.name} />\n </div>\n </div>\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 // A stopped/terminated session takes precedence over every\n // connection overlay (see the immersive view for rationale).\n const sessionEnded = isSessionEnded(tab.sessionId);\n const endedStatus = sessionStatusById.get(tab.sessionId);\n const showConnectionIssue =\n !sessionEnded && (!!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 key={`tif-${tab.sessionId}-${reloadNonce[tab.sessionId] ?? 0}`}\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 - shows on top of terminal without destroying it */}\n {showIframe && isConnecting && !sessionEnded && (\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.settingUpWebTerminal',\n 'Setting up session + web terminal'\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 <div className=\"flex items-center justify-center gap-2 flex-wrap\">\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 {/* Connection-log shortcut — same affordance the\n agent status badge gets so users in either\n surface can land on the timeline of what the\n svc actually tried. Anchor so Ctrl-click opens\n in a new tab. */}\n <a\n href={`/vibecontrols/sessions/${tab.sessionId}#connection-log`}\n className=\"px-4 py-2 text-action-primary-text bg-action-primary-bg text-sm rounded-md hover:bg-action-primary-bg-hover transition-colors inline-flex items-center gap-2\"\n >\n <ScrollText className=\"size-3.5\" />\n {tr(\n 'vibecontrols.sessions.terminal.seeConnectionLog',\n 'See connection log'\n )}\n </a>\n </div>\n </div>\n </div>\n )}\n\n {/* Session stopped overlay — definitive feedback that a\n Stop Session / Stop Terminal action took effect, instead of\n leaving the operator on a frozen terminal iframe. */}\n {sessionEnded && (\n <div\n className=\"absolute inset-0 bg-bg-overlay backdrop-blur-[2px] flex items-center justify-center z-20\"\n data-testid=\"terminal-session-stopped\"\n >\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 <Square className=\"size-6 text-text-secondary\" />\n <div className=\"text-center\">\n <p className=\"text-text-primary text-sm font-medium\">\n {endedStatus === 'ERROR'\n ? tr('vibecontrols.sessions.terminal.sessionErrored', 'Session ended')\n : tr(\n 'vibecontrols.sessions.terminal.sessionStopped',\n 'Session stopped'\n )}\n </p>\n <p className=\"text-text-secondary text-xs mt-1\">\n {tr(\n 'vibecontrols.sessions.terminal.sessionStoppedHint',\n 'This terminal is no longer running.'\n )}\n </p>\n </div>\n <div className=\"flex items-center gap-2\">\n {onRestartTerminal && (\n <button\n type=\"button\"\n onClick={() => onRestartTerminal(tab.sessionId)}\n className=\"px-3 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.restartSession', 'Restart')}\n </button>\n )}\n <button\n type=\"button\"\n onClick={() => onTabClose(tab.sessionId)}\n className=\"px-3 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 <X className=\"size-3.5\" />\n {tr('vibecontrols.sessions.terminal.closeTab', 'Close')}\n </button>\n </div>\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 session + web 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 <p className=\"text-text-secondary text-xs mt-3\">\n {tr(\n 'vibecontrols.sessions.terminal.seeConnectionLogPrompt',\n 'See the connection log for the step-by-step timeline of probe / start / mint / exchange events.'\n )}\n </p>\n <div className=\"flex items-center justify-center gap-3 mt-4 flex-wrap\">\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 <a\n href={`/vibecontrols/sessions/${tab.sessionId}#connection-log`}\n className=\"px-4 py-2 text-action-primary-text bg-action-primary-bg text-sm rounded-md hover:bg-action-primary-bg-hover transition-colors inline-flex items-center gap-2\"\n >\n <ScrollText className=\"size-3.5\" />\n {tr(\n 'vibecontrols.sessions.terminal.seeConnectionLog',\n 'See connection log'\n )}\n </a>\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 {(onQuickCreate || onCreateSession) && (\n <button\n type=\"button\"\n onClick={() => (onQuickCreate ?? 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-col items-stretch gap-2 max-w-md mx-auto\">\n {availableSessions.slice(0, 6).map((s) => (\n // Compound row: clicking the body opens the session;\n // the trailing Stop button kills the session without\n // having to switch out of terminal-first view first.\n // Previously the empty-state offered only \"open\" — an\n // operator who closed every tab while sessions were\n // still running had no way to shut them down without\n // going to Grid/List, which broke the terminal-first\n // promise.\n <div\n key={s.id}\n className=\"flex items-center gap-2 px-3 py-2 bg-bg-canvas text-text-secondary text-xs rounded-md border border-border-default\"\n >\n <button\n type=\"button\"\n onClick={() => onOpenTerminal?.(s)}\n className=\"flex items-center gap-1.5 flex-1 min-w-0 text-left hover:text-text-primary transition-colors\"\n title={`Open ${s.name}`}\n >\n <Terminal className=\"size-3 shrink-0\" />\n <span className=\"truncate\">{s.name}</span>\n <span\n className={`size-1.5 rounded-full shrink-0 ${\n s.status === 'RUNNING' ? 'bg-status-success-text' : 'bg-text-secondary'\n }`}\n title={s.status}\n />\n </button>\n {onStopSession && s.status === 'RUNNING' && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n handleStopSessionClick(s.id);\n }}\n disabled={stoppingSessionIds.has(s.id)}\n className=\"inline-flex items-center justify-center size-6 rounded-md border border-status-error-bg/40 bg-status-error-bg/10 text-status-error-text hover:bg-status-error-bg/20 hover:border-status-error-bg/60 active:scale-95 transition-all shrink-0 disabled:opacity-60 disabled:cursor-not-allowed\"\n title={tr('vibecontrols.sessions.terminal.stopSession', 'Stop Session')}\n aria-label={tr(\n 'vibecontrols.sessions.terminal.stopSession',\n 'Stop Session'\n )}\n aria-busy={stoppingSessionIds.has(s.id)}\n >\n {stoppingSessionIds.has(s.id) ? (\n <Loader2 className=\"size-3 animate-spin\" />\n ) : (\n <Square className=\"size-3 fill-current\" />\n )}\n </button>\n )}\n </div>\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>\n {tr(\n 'vibecontrols.sessions.terminal.newSessionWithOptions',\n 'New session with options…'\n )}\n </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 &ldquo;{sessionSearch}&rdquo;\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 {renderSessionContextActions(contextTab.sessionId)}\n {onTogglePin && (\n <button\n type=\"button\"\n onClick={() => {\n onTogglePin(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 {contextTab.pinned ? <PinOff className=\"size-3.5\" /> : <Pin className=\"size-3.5\" />}\n {contextTab.pinned\n ? tr('vibecontrols.sessions.terminal.unpinTab', 'Unpin Tab')\n : tr('vibecontrols.sessions.terminal.pinTab', 'Pin Tab')}\n </button>\n )}\n {/* Reconnect — always available for a live terminal (reattach to\n the running session), highlighted when the connection broke. */}\n {contextTab.terminalUrl && (\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 hover:bg-bg-sunken transition-colors ${\n connectionStatus[contextTab.sessionId] === 'error'\n ? 'text-status-warning-text'\n : 'text-text-primary'\n }`}\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 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 handleStopSessionClick(contextTab.sessionId);\n setContextMenu((prev) => ({ ...prev, visible: false }));\n }}\n disabled={stoppingSessionIds.has(contextTab.sessionId)}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-status-error-text hover:bg-status-error-bg/15 transition-colors disabled:opacity-60 disabled:cursor-not-allowed\"\n aria-busy={stoppingSessionIds.has(contextTab.sessionId)}\n >\n {stoppingSessionIds.has(contextTab.sessionId) ? (\n <Loader2 className=\"size-3.5 animate-spin\" />\n ) : (\n <Square className=\"size-3.5 fill-current\" />\n )}\n {stoppingSessionIds.has(contextTab.sessionId)\n ? tr('vibecontrols.sessions.terminal.stoppingSession', 'Stopping…')\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, useMemo, 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 ScrollText,\n Pin,\n PinOff,\n MonitorSmartphone,\n Info,\n Server,\n Share2,\n} from 'lucide-react';\nimport type { Session } from './types';\nimport { TerminalIframe } from './TerminalIframe';\nimport { ConnectionLogPanel } from '@/components/agents/ConnectionLogPanel';\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 /** Owning agent's display name — shown as a tab tooltip/badge. */\n agentName?: string | null;\n /** Target machine name the session runs on — shown as a tab tooltip/badge. */\n targetName?: string | null;\n /** Target machine kind (DIRECT/SSH/...) for the tab badge. */\n targetType?: string | null;\n /** Pinned tabs sort to the front and survive \"close others\". */\n pinned?: boolean;\n}\n\ninterface ContextMenuState {\n visible: boolean;\n x: number;\n y: number;\n sessionId: string | null;\n}\n\ninterface TerminalTabItemProps {\n tab: TerminalTab;\n index: number;\n isActive: boolean;\n isDragging: boolean;\n statusIndicator: ReactNode;\n isRenaming: boolean;\n renameValue: string;\n renameInputRef: React.RefObject<HTMLInputElement | null>;\n onRenameChange: (value: string) => void;\n onRenameBlur: () => void;\n onRenameKeyDown: (e: React.KeyboardEvent) => void;\n onSelect: () => void;\n onClose: () => void;\n onContextMenu: (e: React.MouseEvent) => void;\n onDoubleClick: () => void;\n onDragStart: (e: React.DragEvent) => void;\n onDragOver: (e: React.DragEvent) => void;\n onDrop: (e: React.DragEvent) => void;\n onDragEnd: () => void;\n closeLabel: string;\n}\n\n/**\n * A single terminal tab. Extracted so the immersive and docked tab bars\n * render identical behaviour (drag-reorder, middle-click close, pin, the\n * agent/target context badge) from one place.\n */\nfunction TerminalTabItem({\n tab,\n index,\n isActive,\n isDragging,\n statusIndicator,\n isRenaming,\n renameValue,\n renameInputRef,\n onRenameChange,\n onRenameBlur,\n onRenameKeyDown,\n onSelect,\n onClose,\n onContextMenu,\n onDoubleClick,\n onDragStart,\n onDragOver,\n onDrop,\n onDragEnd,\n closeLabel,\n}: TerminalTabItemProps) {\n const tooltip = [\n tab.name,\n tab.agentName ? `agent: ${tab.agentName}` : null,\n tab.targetName\n ? `target: ${tab.targetName}${tab.targetType ? ` (${tab.targetType})` : ''}`\n : null,\n ]\n .filter(Boolean)\n .join('\\n');\n\n return (\n <div\n role=\"button\"\n tabIndex={0}\n draggable={!isRenaming}\n title={tooltip}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n onSelect();\n }\n }}\n onDragStart={onDragStart}\n onDragOver={onDragOver}\n onDrop={onDrop}\n onDragEnd={onDragEnd}\n data-testid=\"terminal-tab\"\n data-session-id={tab.sessionId}\n data-tab-index={index}\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-[200px] group ${\n isActive\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 } ${isDragging ? 'opacity-50' : ''}`}\n onClick={onSelect}\n onAuxClick={(e) => {\n // Middle-click closes the tab.\n if (e.button === 1) {\n e.preventDefault();\n onClose();\n }\n }}\n onContextMenu={onContextMenu}\n onDoubleClick={onDoubleClick}\n >\n {tab.pinned && <Pin className=\"size-2.5 text-action-primary-text shrink-0\" aria-hidden />}\n {statusIndicator}\n {isRenaming ? (\n <input\n aria-label=\"control\"\n ref={renameInputRef}\n value={renameValue}\n onChange={(e) => onRenameChange(e.target.value)}\n onBlur={onRenameBlur}\n onKeyDown={onRenameKeyDown}\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 {tab.targetName && (\n <MonitorSmartphone\n className=\"size-2.5 text-text-muted shrink-0 hidden sm:inline\"\n aria-hidden\n />\n )}\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onClose();\n }}\n className={`ml-auto hover:text-status-error-text transition-opacity shrink-0 ${\n tab.pinned ? 'opacity-0 group-hover:opacity-100' : 'opacity-0 group-hover:opacity-100'\n }`}\n title={closeLabel}\n aria-label={closeLabel}\n >\n <X className=\"size-3\" />\n </button>\n </div>\n );\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 /**\n * Reconnect to the still-running agent session without stopping it.\n * The parent re-issues the idempotent startSessionTerminal (reattach)\n * and refreshes the tab URL; the panel additionally remounts the iframe\n * with a fresh capability token. This is the fix for \"terminal idles\n * out and the only way back was a full browser refresh\".\n */\n onReattachTerminal?: (sessionId: string) => void;\n /** Reorder tabs (drag-and-drop). Receives the new session-id order. */\n onReorderTabs?: (orderedSessionIds: string[]) => void;\n /** Toggle a tab's pinned state. */\n onTogglePin?: (sessionId: string) => void;\n /** Open the session info panel for a tab (terminal-view feature parity). */\n onShowSessionInfo?: (sessionId: string) => void;\n /** Navigate to a tab's agent detail page. */\n onViewAgent?: (sessionId: string) => void;\n /** Navigate to a tab's target detail page. */\n onViewTarget?: (sessionId: string) => void;\n /** Share / offer a session to a teammate. */\n onShareSession?: (sessionId: string) => void;\n /** Called when user stops the entire session */\n onStopSession?: (sessionId: string) => void;\n /** Called to create a new session *with options* (opens the full form). */\n onCreateSession?: () => void;\n /**\n * One-click quick-create: the primary \"+\" remembers the last-used\n * agent/vibe/type and spawns a terminal with an auto-generated name.\n * When set, the \"+\" quick-creates and the adjacent caret opens the menu\n * (options form / open existing). Falls back to `onCreateSession`.\n */\n onQuickCreate?: () => void;\n /**\n * Increment to request the panel start renaming its active tab. Driven by\n * the page-level F2 shortcut, since rename state lives inside this panel.\n */\n renameActiveTabNonce?: number;\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 onReattachTerminal,\n onReorderTabs,\n onTogglePin,\n onShowSessionInfo,\n onViewAgent,\n onViewTarget,\n onShareSession,\n onStopSession,\n onCreateSession,\n onQuickCreate,\n renameActiveTabNonce,\n fullScreen = false,\n isImmersiveFullscreen = false,\n onToggleImmersiveFullscreen,\n contextSwitcher,\n}: TerminalPanelProps) {\n const tr = useTr();\n const [maximized, setMaximized] = useState(false);\n const [showConnectionLog, setShowConnectionLog] = 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 // Sessions whose stop request is currently in flight. The UI flips to a\n // spinner + disabled state so a second click can't fire and the user gets\n // immediate feedback that the click registered. We clear an entry when\n // the upstream session list reports STOPPED/TERMINATED, or after 8s as a\n // fallback if the mutation hangs.\n const [stoppingSessionIds, setStoppingSessionIds] = useState<Set<string>>(() => new Set());\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 // Per-session iframe remount nonce. Bumped on (re)connect so the iframe\n // fully remounts → useTerminalCapabilityUrl mints a brand-new token, so a\n // stale/expired token can never block the reload.\n const [reloadNonce, setReloadNonce] = useState<Record<string, number>>({});\n // Liveness-probe consecutive-failure counters per session.\n const livenessFailures = useRef<Record<string, number>>({});\n // Drag-to-reorder: index of the tab currently being dragged.\n const [dragIndex, setDragIndex] = useState<number | null>(null);\n\n const bumpReload = useCallback((sessionId: string) => {\n setReloadNonce((prev) => ({ ...prev, [sessionId]: (prev[sessionId] ?? 0) + 1 }));\n }, []);\n\n // Pinned tabs render first; order is otherwise stable (drag order). Each\n // tab is enriched with its agent/target names from the live sessions list\n // so the tab tooltip/badge shows which machine it runs on without the\n // parent having to thread that metadata through every tab-creation site.\n const displayTabs = [...tabs]\n .sort((a, b) => (b.pinned ? 1 : 0) - (a.pinned ? 1 : 0))\n .map((tab) => {\n const session = sessions?.find((s) => s.id === tab.sessionId);\n return {\n ...tab,\n agentName: tab.agentName ?? session?.agent?.name ?? null,\n targetName: tab.targetName ?? session?.agent?.target?.name ?? null,\n targetType: tab.targetType ?? session?.agent?.target?.type ?? null,\n };\n });\n\n const activeTab = tabs.find((t) => t.sessionId === activeTabId) || tabs[0] || null;\n\n // Live status lookup, keyed by session id, from the upstream sessions list.\n // Used to drive the \"Session stopped\" overlay so a stopped session gives the\n // operator clear feedback instead of leaving a frozen terminal iframe (which\n // otherwise gets mislabelled \"Connection Lost — reconnecting…\").\n const sessionStatusById = useMemo(() => {\n const map = new Map<string, string>();\n (sessions ?? []).forEach((s) => map.set(s.id, s.status));\n return map;\n }, [sessions]);\n\n const isSessionEnded = useCallback(\n (sessionId: string): boolean => {\n const status = sessionStatusById.get(sessionId);\n return status === 'STOPPED' || status === 'TERMINATED' || status === 'ERROR';\n },\n [sessionStatusById]\n );\n\n // Wrap onStopSession so the click instantly enters a pending state. The\n // caller's mutation may take 1–3s to round-trip; without this the button\n // looks unresponsive and operators tend to click again, which we then\n // have to ignore via the disabled state.\n const handleStopSessionClick = useCallback(\n (sessionId: string) => {\n if (!onStopSession) return;\n setStoppingSessionIds((prev) => {\n if (prev.has(sessionId)) return prev;\n const next = new Set(prev);\n next.add(sessionId);\n return next;\n });\n try {\n onStopSession(sessionId);\n } catch {\n setStoppingSessionIds((prev) => {\n if (!prev.has(sessionId)) return prev;\n const next = new Set(prev);\n next.delete(sessionId);\n return next;\n });\n }\n // Safety fallback so a hung mutation doesn't leave the button stuck.\n window.setTimeout(() => {\n setStoppingSessionIds((prev) => {\n if (!prev.has(sessionId)) return prev;\n const next = new Set(prev);\n next.delete(sessionId);\n return next;\n });\n }, 8000);\n },\n [onStopSession]\n );\n\n // Clear pending state as soon as the upstream sessions list reports the\n // session as STOPPED/TERMINATED. This is the success signal; the 8s\n // fallback above only kicks in if the round-trip silently fails.\n useEffect(() => {\n if (!sessions || stoppingSessionIds.size === 0) return;\n const terminal = new Set(\n sessions\n .filter((s) => s.status === 'STOPPED' || s.status === 'TERMINATED' || s.status === 'ERROR')\n .map((s) => s.id)\n );\n let changed = false;\n const next = new Set(stoppingSessionIds);\n stoppingSessionIds.forEach((id) => {\n if (terminal.has(id)) {\n next.delete(id);\n changed = true;\n }\n });\n if (changed) setStoppingSessionIds(next);\n }, [sessions, stoppingSessionIds]);\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 — reattach to the still-running agent session.\n //\n // Previously this just reloaded the iframe's existing src, which carried\n // a possibly-expired 60s capability token, so an idled-out terminal often\n // dead-ended until a full browser refresh. Now we (1) ask the parent to\n // re-issue the idempotent startSessionTerminal (reattach to the live\n // session / refresh ttydUrl, never stop+start), and (2) bump the remount\n // nonce so TerminalIframe mounts fresh and mints a brand-new token.\n const handleReconnect = useCallback(\n (sessionId: string) => {\n clearAutoReconnect(sessionId);\n livenessFailures.current[sessionId] = 0;\n setConnectionIssues((prev) => ({ ...prev, [sessionId]: undefined }));\n setConnectionStatus((prev) => ({ ...prev, [sessionId]: 'connecting' }));\n bumpReload(sessionId);\n onReattachTerminal?.(sessionId);\n },\n [clearAutoReconnect, bumpReload, onReattachTerminal]\n );\n\n // Drag-to-reorder handlers (mirrors AgentManagerTabBar). Operates on the\n // displayed order and emits the new session-id order to the parent.\n const handleTabDragStart = useCallback((e: React.DragEvent, index: number) => {\n setDragIndex(index);\n e.dataTransfer.effectAllowed = 'move';\n e.dataTransfer.setData('text/plain', String(index));\n }, []);\n\n const handleTabDragOver = useCallback((e: React.DragEvent) => {\n e.preventDefault();\n e.dataTransfer.dropEffect = 'move';\n }, []);\n\n const handleTabDrop = useCallback(\n (e: React.DragEvent, dropIndex: number) => {\n e.preventDefault();\n if (dragIndex === null || dragIndex === dropIndex || !onReorderTabs) {\n setDragIndex(null);\n return;\n }\n const reordered = [...displayTabs];\n const [moved] = reordered.splice(dragIndex, 1);\n reordered.splice(dropIndex, 0, moved);\n onReorderTabs(reordered.map((t) => t.sessionId));\n setDragIndex(null);\n },\n [dragIndex, displayTabs, onReorderTabs]\n );\n\n const handleTabDragEnd = useCallback(() => setDragIndex(null), []);\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 // A session the operator (or the agent) stopped is gone for good — don't\n // hammer /health trying to reconnect a dead terminal, and let the\n // \"Session stopped\" overlay own the UI instead of \"Reconnecting…\".\n if (isSessionEnded(tab.sessionId)) {\n clearAutoReconnect(tab.sessionId);\n continue;\n }\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 // Probe the agent health before remounting the iframe.\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 — remount the iframe with a fresh token.\n setConnectionStatus((prev) => ({\n ...prev,\n [tab.sessionId]: 'connecting',\n }));\n bumpReload(tab.sessionId);\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, bumpReload, isSessionEnded]);\n\n // ── Liveness heartbeat ───────────────────────────────────────────────\n // ttyd's websocket can drop after idle without the iframe firing an\n // `error` event, so React keeps thinking the tab is \"connected\" and the\n // operator is stranded on ttyd's internal \"connection closed\" screen\n // with no app-level reconnect. Poll the active connected tab's agent\n // `/health` every 20s; after two consecutive failures, flip the tab to\n // `error` which surfaces the (manual + auto) reconnect path. Conservative\n // by design — a single transient failure never trips it.\n useEffect(() => {\n if (!activeTab || !activeTab.terminalUrl || activeTab.loading || activeTab.error) return;\n if (connectionStatus[activeTab.sessionId] !== 'connected') return;\n // Stopped sessions are intentionally dead — skip the heartbeat so we don't\n // flip a stopped tab into the \"error → reconnect\" path.\n if (isSessionEnded(activeTab.sessionId)) return;\n\n let origin: string;\n try {\n origin = new URL(activeTab.terminalUrl).origin;\n } catch {\n return;\n }\n\n const sessionId = activeTab.sessionId;\n const interval = setInterval(() => {\n fetch(`${origin}/health`, { signal: AbortSignal.timeout(5_000) })\n .then((res) => {\n if (res.ok) {\n livenessFailures.current[sessionId] = 0;\n } else {\n throw new Error('unhealthy');\n }\n })\n .catch(() => {\n const next = (livenessFailures.current[sessionId] ?? 0) + 1;\n livenessFailures.current[sessionId] = next;\n if (next >= 2) {\n setConnectionStatusForTab(sessionId, 'error');\n }\n });\n }, 20_000);\n\n return () => clearInterval(interval);\n }, [activeTab, connectionStatus, setConnectionStatusForTab, isSessionEnded]);\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 // F2 bridge: the page bumps `renameActiveTabNonce` to start renaming the\n // active tab. Guard on the last-seen nonce so merely switching the active\n // tab (which is also a dep) can never spuriously trigger a rename.\n const lastRenameNonce = useRef(renameActiveTabNonce ?? 0);\n useEffect(() => {\n if (renameActiveTabNonce === undefined) return;\n if (renameActiveTabNonce === lastRenameNonce.current) return;\n lastRenameNonce.current = renameActiveTabNonce;\n if (activeTab) handleStartRename(activeTab.sessionId);\n }, [renameActiveTabNonce, activeTab, handleStartRename]);\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 // Full session row for the context-menu target — used to gate the\n // View agent / View target entries on what the session actually has.\n const contextSession = contextMenu.sessionId\n ? (sessions?.find((s) => s.id === contextMenu.sessionId) ?? null)\n : null;\n\n // Session-level context-menu entries (info / view agent / view target /\n // share) — rendered identically in both the docked and immersive menus so\n // terminal view reaches feature parity with grid/list.\n const renderSessionContextActions = (sessionId: string) => (\n <>\n {onShowSessionInfo && (\n <button\n type=\"button\"\n onClick={() => {\n onShowSessionInfo(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 <Info className=\"size-3.5\" />\n {tr('vibecontrols.sessions.terminal.sessionInfo', 'Session info')}\n </button>\n )}\n {onViewAgent && contextSession?.agent && (\n <button\n type=\"button\"\n onClick={() => {\n onViewAgent(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 <Server className=\"size-3.5\" />\n {tr('vibecontrols.sessions.terminal.viewAgent', 'View agent')}\n </button>\n )}\n {onViewTarget && contextSession?.agent?.target && (\n <button\n type=\"button\"\n onClick={() => {\n onViewTarget(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 <MonitorSmartphone className=\"size-3.5\" />\n {tr('vibecontrols.sessions.terminal.viewTarget', 'View target')}\n </button>\n )}\n {onShareSession && (\n <button\n type=\"button\"\n onClick={() => {\n onShareSession(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 <Share2 className=\"size-3.5\" />\n {tr('vibecontrols.sessions.terminal.shareSession', 'Share / offer session')}\n </button>\n )}\n {(onShowSessionInfo || onViewAgent || onViewTarget || onShareSession) && (\n <div className=\"border-t border-border-subtle my-1\" />\n )}\n </>\n );\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 — always available for a live terminal so an\n idled-out ttyd session can be reattached without a refresh.\n Highlighted when the connection is known-broken. */}\n {activeTab?.terminalUrl && (\n <button\n type=\"button\"\n onClick={() => handleReconnect(activeTab.sessionId)}\n className={`p-1.5 transition-colors ${\n connectionStatus[activeTab.sessionId] === 'error'\n ? 'text-status-warning-text hover:text-status-warning-text/80'\n : 'text-text-secondary hover:text-text-primary'\n }`}\n title={tr('vibecontrols.sessions.terminal.reconnect', 'Reconnect')}\n aria-label={tr('vibecontrols.sessions.terminal.reconnect', 'Reconnect')}\n data-testid=\"terminal-reconnect\"\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={tr(\n 'vibecontrols.sessions.terminal.openInNewTabShortcut',\n 'Open in new tab (⌘/Ctrl+O)'\n )}\n >\n <ExternalLink className=\"size-4\" />\n </button>\n )}\n {/* Stop Session — also exposed in the immersive header so\n operators can shut down the underlying session without\n having to leave fullscreen first. */}\n {activeTab && onStopSession && (\n <button\n type=\"button\"\n onClick={() => handleStopSessionClick(activeTab.sessionId)}\n disabled={stoppingSessionIds.has(activeTab.sessionId)}\n className=\"inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md border border-status-error-bg/40 bg-status-error-bg/10 text-status-error-text hover:bg-status-error-bg/20 hover:border-status-error-bg/60 active:scale-95 transition-all disabled:opacity-60 disabled:cursor-not-allowed\"\n title={tr('vibecontrols.sessions.terminal.stopSession', 'Stop Session')}\n aria-label={tr('vibecontrols.sessions.terminal.stopSession', 'Stop Session')}\n aria-busy={stoppingSessionIds.has(activeTab.sessionId)}\n >\n {stoppingSessionIds.has(activeTab.sessionId) ? (\n <Loader2 className=\"size-3.5 animate-spin\" />\n ) : (\n <Square className=\"size-3.5 fill-current\" />\n )}\n <span className=\"text-xs font-medium\">\n {stoppingSessionIds.has(activeTab.sessionId)\n ? tr('vibecontrols.sessions.terminal.stoppingSession', 'Stopping…')\n : tr('vibecontrols.sessions.terminal.stopSession', 'Stop')}\n </span>\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 {displayTabs.map((tab, index) => (\n <TerminalTabItem\n key={tab.sessionId}\n tab={tab}\n index={index}\n isActive={tab.sessionId === activeTab?.sessionId}\n isDragging={dragIndex === index}\n statusIndicator={renderStatusIndicator(tab)}\n isRenaming={renamingTabId === tab.sessionId}\n renameValue={renameValue}\n renameInputRef={renameInputRef}\n onRenameChange={setRenameValue}\n onRenameBlur={handleFinishRename}\n onRenameKeyDown={handleRenameKeyDown}\n onSelect={() => onTabSelect(tab.sessionId)}\n onClose={() => onTabClose(tab.sessionId)}\n onContextMenu={(e) => handleContextMenu(e, tab.sessionId)}\n onDoubleClick={() => handleStartRename(tab.sessionId)}\n onDragStart={(e) => handleTabDragStart(e, index)}\n onDragOver={handleTabDragOver}\n onDrop={(e) => handleTabDrop(e, index)}\n onDragEnd={handleTabDragEnd}\n closeLabel={tr('vibecontrols.sessions.terminal.closeTab', 'Close terminal')}\n />\n ))}\n </div>\n\n {/* Add tab: split \"+\" (one-click quick-create) + caret (menu) */}\n <div className=\"flex items-center shrink-0 border-r border-border-subtle\">\n <button\n type=\"button\"\n onClick={() => {\n if (onQuickCreate) onQuickCreate();\n else if (onCreateSession || availableSessions.length > 0) {\n setShowSessionPicker((prev) => !prev);\n setSessionSearch('');\n }\n }}\n className=\"flex items-center pl-2.5 pr-1.5 py-1.5 text-text-secondary hover:text-text-primary transition-colors\"\n title={\n onQuickCreate\n ? tr('vibecontrols.sessions.terminal.quickNewSession', 'New terminal (⌘/Ctrl+T)')\n : tr('vibecontrols.sessions.terminal.newOrOpen', 'New session or open existing')\n }\n data-testid=\"terminal-quick-create\"\n >\n <Plus className=\"size-3.5\" />\n </button>\n <button\n type=\"button\"\n ref={plusBtnRef}\n onClick={() => {\n setShowSessionPicker((prev) => !prev);\n setSessionSearch('');\n }}\n className=\"flex items-center pr-2 pl-0.5 py-1.5 text-text-secondary hover:text-text-primary transition-colors\"\n title={tr(\n 'vibecontrols.sessions.terminal.moreSessionOptions',\n 'New session with options / open existing'\n )}\n aria-haspopup=\"menu\"\n aria-expanded={showSessionPicker}\n data-testid=\"terminal-session-menu\"\n >\n <ChevronDown className=\"size-3\" />\n </button>\n </div>\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 // A stopped/terminated session takes precedence over every\n // connection overlay: the terminal is gone on purpose, so we\n // show an explicit \"Session stopped\" state rather than the\n // misleading \"Connection Lost — reconnecting…\" spinner.\n const sessionEnded = isSessionEnded(tab.sessionId);\n const endedStatus = sessionStatusById.get(tab.sessionId);\n const showConnectionIssue =\n !sessionEnded && (!!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 key={`tif-${tab.sessionId}-${reloadNonce[tab.sessionId] ?? 0}`}\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 && !sessionEnded && (\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.settingUpWebTerminal',\n 'Setting up session + web terminal'\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 {/* Session stopped overlay — definitive feedback that a\n Stop Session / Stop Terminal action took effect. Covers\n the (now dead) iframe so the operator isn't left staring\n at a frozen terminal wondering whether the stop worked. */}\n {sessionEnded && (\n <div\n className=\"absolute inset-0 bg-bg-overlay backdrop-blur-[2px] flex items-center justify-center z-20\"\n data-testid=\"terminal-session-stopped\"\n >\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 <Square className=\"size-6 text-text-secondary\" />\n <div className=\"text-center\">\n <p className=\"text-text-primary text-sm font-medium\">\n {endedStatus === 'ERROR'\n ? tr('vibecontrols.sessions.terminal.sessionErrored', 'Session ended')\n : tr(\n 'vibecontrols.sessions.terminal.sessionStopped',\n 'Session stopped'\n )}\n </p>\n <p className=\"text-text-secondary text-xs mt-1\">\n {tr(\n 'vibecontrols.sessions.terminal.sessionStoppedHint',\n 'This terminal is no longer running.'\n )}\n </p>\n </div>\n <div className=\"flex items-center gap-2\">\n {onRestartTerminal && (\n <button\n type=\"button\"\n onClick={() => onRestartTerminal(tab.sessionId)}\n className=\"px-3 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.restartSession', 'Restart')}\n </button>\n )}\n <button\n type=\"button\"\n onClick={() => onTabClose(tab.sessionId)}\n className=\"px-3 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 <X className=\"size-3.5\" />\n {tr('vibecontrols.sessions.terminal.closeTab', 'Close')}\n </button>\n </div>\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 session + web 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 {(onQuickCreate || onCreateSession) && (\n <button\n type=\"button\"\n onClick={() => (onQuickCreate ?? 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>\n {tr(\n 'vibecontrols.sessions.terminal.newSessionWithOptions',\n 'New session with options…'\n )}\n </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 &ldquo;{sessionSearch}&rdquo;\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 <span className=\"ml-auto text-[10px] text-text-tertiary font-mono\">F2</span>\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 {renderSessionContextActions(contextTab.sessionId)}\n {onTogglePin && (\n <button\n type=\"button\"\n onClick={() => {\n onTogglePin(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 {contextTab.pinned ? <PinOff className=\"size-3.5\" /> : <Pin className=\"size-3.5\" />}\n {contextTab.pinned\n ? tr('vibecontrols.sessions.terminal.unpinTab', 'Unpin Tab')\n : tr('vibecontrols.sessions.terminal.pinTab', 'Pin Tab')}\n </button>\n )}\n {contextTab.terminalUrl && (\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 hover:bg-bg-sunken transition-colors ${\n connectionStatus[contextTab.sessionId] === 'error'\n ? 'text-status-warning-text'\n : 'text-text-primary'\n }`}\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 flex-1 min-h-0' : ''} 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 {displayTabs.map((tab, index) => (\n <TerminalTabItem\n key={tab.sessionId}\n tab={tab}\n index={index}\n isActive={tab.sessionId === activeTab?.sessionId}\n isDragging={dragIndex === index}\n statusIndicator={renderStatusIndicator(tab)}\n isRenaming={renamingTabId === tab.sessionId}\n renameValue={renameValue}\n renameInputRef={renameInputRef}\n onRenameChange={setRenameValue}\n onRenameBlur={handleFinishRename}\n onRenameKeyDown={handleRenameKeyDown}\n onSelect={() => onTabSelect(tab.sessionId)}\n onClose={() => onTabClose(tab.sessionId)}\n onContextMenu={(e) => handleContextMenu(e, tab.sessionId)}\n onDoubleClick={() => handleStartRename(tab.sessionId)}\n onDragStart={(e) => handleTabDragStart(e, index)}\n onDragOver={handleTabDragOver}\n onDrop={(e) => handleTabDrop(e, index)}\n onDragEnd={handleTabDragEnd}\n closeLabel={tr('vibecontrols.sessions.terminal.closeTab', 'Close terminal')}\n />\n ))}\n </div>\n )}\n\n {/* Add tab: split \"+\" (quick-create) + caret (menu) — outside the\n overflow container so the dropdown isn't clipped */}\n {(!collapsed || fullScreen) && (\n <div className=\"flex items-center shrink-0 border-r border-border-subtle\">\n <button\n type=\"button\"\n onClick={() => {\n if (onQuickCreate) onQuickCreate();\n else if (onCreateSession || availableSessions.length > 0) {\n setShowSessionPicker((prev) => !prev);\n setSessionSearch('');\n }\n }}\n className=\"flex items-center pl-2.5 pr-1.5 py-1.5 text-text-secondary hover:text-text-primary transition-colors\"\n title={\n onQuickCreate\n ? tr('vibecontrols.sessions.terminal.quickNewSession', 'New terminal (⌘/Ctrl+T)')\n : tr('vibecontrols.sessions.terminal.newOrOpen', 'New session or open existing')\n }\n data-testid=\"terminal-quick-create\"\n >\n <Plus className=\"size-3.5\" />\n </button>\n <button\n type=\"button\"\n ref={plusBtnRef}\n onClick={() => {\n setShowSessionPicker((prev) => !prev);\n setSessionSearch('');\n }}\n className=\"flex items-center pr-2 pl-0.5 py-1.5 text-text-secondary hover:text-text-primary transition-colors\"\n title={tr(\n 'vibecontrols.sessions.terminal.moreSessionOptions',\n 'New session with options / open existing'\n )}\n aria-haspopup=\"menu\"\n aria-expanded={showSessionPicker}\n data-testid=\"terminal-session-menu\"\n >\n <ChevronDown className=\"size-3\" />\n </button>\n </div>\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 — always available for a live terminal so an\n idled-out ttyd session can be reattached without a refresh.\n Highlighted when the connection is known-broken. */}\n {activeTab?.terminalUrl && (\n <button\n type=\"button\"\n onClick={() => handleReconnect(activeTab.sessionId)}\n className={`p-1 transition-colors ${\n connectionStatus[activeTab.sessionId] === 'error'\n ? 'text-status-warning-text hover:text-status-warning-text/80'\n : 'text-text-secondary hover:text-text-primary'\n }`}\n title={tr('vibecontrols.sessions.terminal.reconnect', 'Reconnect')}\n aria-label={tr('vibecontrols.sessions.terminal.reconnect', 'Reconnect')}\n data-testid=\"terminal-reconnect\"\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={tr(\n 'vibecontrols.sessions.terminal.openInNewTabShortcut',\n 'Open in new tab (⌘/Ctrl+O)'\n )}\n >\n <ExternalLink className=\"size-3.5\" />\n </button>\n )}\n {/* Stop Session — visible alongside the existing terminal\n actions so operators don't have to right-click the tab\n to discover the context menu. Mirrors the Stop Session\n entry there: kills the underlying session (not just\n ttyd) and surfaces quota errors via the shared modal. */}\n {activeTab && onStopSession && (\n <button\n type=\"button\"\n onClick={() => handleStopSessionClick(activeTab.sessionId)}\n disabled={stoppingSessionIds.has(activeTab.sessionId)}\n className=\"inline-flex items-center gap-1 px-2 py-0.5 rounded-md border border-status-error-bg/40 bg-status-error-bg/10 text-status-error-text hover:bg-status-error-bg/20 hover:border-status-error-bg/60 active:scale-95 transition-all disabled:opacity-60 disabled:cursor-not-allowed\"\n title={tr('vibecontrols.sessions.terminal.stopSession', 'Stop Session')}\n aria-label={tr('vibecontrols.sessions.terminal.stopSession', 'Stop Session')}\n aria-busy={stoppingSessionIds.has(activeTab.sessionId)}\n >\n {stoppingSessionIds.has(activeTab.sessionId) ? (\n <Loader2 className=\"size-3 animate-spin\" />\n ) : (\n <Square className=\"size-3 fill-current\" />\n )}\n <span className=\"text-[11px] font-medium\">\n {stoppingSessionIds.has(activeTab.sessionId)\n ? tr('vibecontrols.sessions.terminal.stoppingSession', 'Stopping…')\n : tr('vibecontrols.sessions.terminal.stopSession', 'Stop')}\n </span>\n </button>\n )}\n {/* Connection-log toggle — surfaces the same step-by-step\n timeline operators can see on the AgentDetails page,\n scoped to the active session. Required for the\n \"Connect fails — why?\" debugging path that previously\n forced operators to dig through the agent's local\n log file. */}\n {activeTab && (\n <button\n type=\"button\"\n onClick={() => setShowConnectionLog((v) => !v)}\n className={`p-1 transition-colors ${\n showConnectionLog\n ? 'text-action-primary-text bg-action-primary-bg rounded'\n : 'text-text-secondary hover:text-text-primary'\n }`}\n title={tr('vibecontrols.sessions.terminal.connectionLog', 'Connection Log')}\n >\n <ScrollText 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 {/* Connection-log slide-over for the active session. Renders\n ABOVE the terminal iframe so operators can read the\n step-by-step trace without losing their place. */}\n {showConnectionLog && activeTab && (\n <div className=\"absolute inset-0 z-30 bg-bg-surface flex flex-col\">\n <div className=\"flex items-center justify-between px-4 py-2 border-b border-border-default\">\n <div className=\"flex items-center gap-2 text-sm font-semibold text-text-primary\">\n <ScrollText className=\"size-4\" />\n {tr('vibecontrols.sessions.terminal.connectionLog', 'Connection Log')}\n <span className=\"text-text-tertiary font-normal\">· {activeTab.name}</span>\n </div>\n <button\n type=\"button\"\n onClick={() => setShowConnectionLog(false)}\n className=\"p-1 text-text-secondary hover:text-text-primary\"\n title={tr('common.close', 'Close')}\n >\n <X className=\"size-4\" />\n </button>\n </div>\n <div className=\"flex-1 overflow-hidden\">\n <ConnectionLogPanel sessionId={activeTab.sessionId} sessionName={activeTab.name} />\n </div>\n </div>\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 // A stopped/terminated session takes precedence over every\n // connection overlay (see the immersive view for rationale).\n const sessionEnded = isSessionEnded(tab.sessionId);\n const endedStatus = sessionStatusById.get(tab.sessionId);\n const showConnectionIssue =\n !sessionEnded && (!!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 key={`tif-${tab.sessionId}-${reloadNonce[tab.sessionId] ?? 0}`}\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 - shows on top of terminal without destroying it */}\n {showIframe && isConnecting && !sessionEnded && (\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.settingUpWebTerminal',\n 'Setting up session + web terminal'\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 <div className=\"flex items-center justify-center gap-2 flex-wrap\">\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 {/* Connection-log shortcut — same affordance the\n agent status badge gets so users in either\n surface can land on the timeline of what the\n svc actually tried. Anchor so Ctrl-click opens\n in a new tab. */}\n <a\n href={`/vibecontrols/sessions/${tab.sessionId}#connection-log`}\n className=\"px-4 py-2 text-action-primary-text bg-action-primary-bg text-sm rounded-md hover:bg-action-primary-bg-hover transition-colors inline-flex items-center gap-2\"\n >\n <ScrollText className=\"size-3.5\" />\n {tr(\n 'vibecontrols.sessions.terminal.seeConnectionLog',\n 'See connection log'\n )}\n </a>\n </div>\n </div>\n </div>\n )}\n\n {/* Session stopped overlay — definitive feedback that a\n Stop Session / Stop Terminal action took effect, instead of\n leaving the operator on a frozen terminal iframe. */}\n {sessionEnded && (\n <div\n className=\"absolute inset-0 bg-bg-overlay backdrop-blur-[2px] flex items-center justify-center z-20\"\n data-testid=\"terminal-session-stopped\"\n >\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 <Square className=\"size-6 text-text-secondary\" />\n <div className=\"text-center\">\n <p className=\"text-text-primary text-sm font-medium\">\n {endedStatus === 'ERROR'\n ? tr('vibecontrols.sessions.terminal.sessionErrored', 'Session ended')\n : tr(\n 'vibecontrols.sessions.terminal.sessionStopped',\n 'Session stopped'\n )}\n </p>\n <p className=\"text-text-secondary text-xs mt-1\">\n {tr(\n 'vibecontrols.sessions.terminal.sessionStoppedHint',\n 'This terminal is no longer running.'\n )}\n </p>\n </div>\n <div className=\"flex items-center gap-2\">\n {onRestartTerminal && (\n <button\n type=\"button\"\n onClick={() => onRestartTerminal(tab.sessionId)}\n className=\"px-3 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.restartSession', 'Restart')}\n </button>\n )}\n <button\n type=\"button\"\n onClick={() => onTabClose(tab.sessionId)}\n className=\"px-3 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 <X className=\"size-3.5\" />\n {tr('vibecontrols.sessions.terminal.closeTab', 'Close')}\n </button>\n </div>\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 session + web 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 <p className=\"text-text-secondary text-xs mt-3\">\n {tr(\n 'vibecontrols.sessions.terminal.seeConnectionLogPrompt',\n 'See the connection log for the step-by-step timeline of probe / start / mint / exchange events.'\n )}\n </p>\n <div className=\"flex items-center justify-center gap-3 mt-4 flex-wrap\">\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 <a\n href={`/vibecontrols/sessions/${tab.sessionId}#connection-log`}\n className=\"px-4 py-2 text-action-primary-text bg-action-primary-bg text-sm rounded-md hover:bg-action-primary-bg-hover transition-colors inline-flex items-center gap-2\"\n >\n <ScrollText className=\"size-3.5\" />\n {tr(\n 'vibecontrols.sessions.terminal.seeConnectionLog',\n 'See connection log'\n )}\n </a>\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 {(onQuickCreate || onCreateSession) && (\n <button\n type=\"button\"\n onClick={() => (onQuickCreate ?? 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-col items-stretch gap-2 max-w-md mx-auto\">\n {availableSessions.slice(0, 6).map((s) => (\n // Compound row: clicking the body opens the session;\n // the trailing Stop button kills the session without\n // having to switch out of terminal-first view first.\n // Previously the empty-state offered only \"open\" — an\n // operator who closed every tab while sessions were\n // still running had no way to shut them down without\n // going to Grid/List, which broke the terminal-first\n // promise.\n <div\n key={s.id}\n className=\"flex items-center gap-2 px-3 py-2 bg-bg-canvas text-text-secondary text-xs rounded-md border border-border-default\"\n >\n <button\n type=\"button\"\n onClick={() => onOpenTerminal?.(s)}\n className=\"flex items-center gap-1.5 flex-1 min-w-0 text-left hover:text-text-primary transition-colors\"\n title={`Open ${s.name}`}\n >\n <Terminal className=\"size-3 shrink-0\" />\n <span className=\"truncate\">{s.name}</span>\n <span\n className={`size-1.5 rounded-full shrink-0 ${\n s.status === 'RUNNING' ? 'bg-status-success-text' : 'bg-text-secondary'\n }`}\n title={s.status}\n />\n </button>\n {onStopSession && s.status === 'RUNNING' && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n handleStopSessionClick(s.id);\n }}\n disabled={stoppingSessionIds.has(s.id)}\n className=\"inline-flex items-center justify-center size-6 rounded-md border border-status-error-bg/40 bg-status-error-bg/10 text-status-error-text hover:bg-status-error-bg/20 hover:border-status-error-bg/60 active:scale-95 transition-all shrink-0 disabled:opacity-60 disabled:cursor-not-allowed\"\n title={tr('vibecontrols.sessions.terminal.stopSession', 'Stop Session')}\n aria-label={tr(\n 'vibecontrols.sessions.terminal.stopSession',\n 'Stop Session'\n )}\n aria-busy={stoppingSessionIds.has(s.id)}\n >\n {stoppingSessionIds.has(s.id) ? (\n <Loader2 className=\"size-3 animate-spin\" />\n ) : (\n <Square className=\"size-3 fill-current\" />\n )}\n </button>\n )}\n </div>\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>\n {tr(\n 'vibecontrols.sessions.terminal.newSessionWithOptions',\n 'New session with options…'\n )}\n </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 &ldquo;{sessionSearch}&rdquo;\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 {renderSessionContextActions(contextTab.sessionId)}\n {onTogglePin && (\n <button\n type=\"button\"\n onClick={() => {\n onTogglePin(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 {contextTab.pinned ? <PinOff className=\"size-3.5\" /> : <Pin className=\"size-3.5\" />}\n {contextTab.pinned\n ? tr('vibecontrols.sessions.terminal.unpinTab', 'Unpin Tab')\n : tr('vibecontrols.sessions.terminal.pinTab', 'Pin Tab')}\n </button>\n )}\n {/* Reconnect — always available for a live terminal (reattach to\n the running session), highlighted when the connection broke. */}\n {contextTab.terminalUrl && (\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 hover:bg-bg-sunken transition-colors ${\n connectionStatus[contextTab.sessionId] === 'error'\n ? 'text-status-warning-text'\n : 'text-text-primary'\n }`}\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 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 handleStopSessionClick(contextTab.sessionId);\n setContextMenu((prev) => ({ ...prev, visible: false }));\n }}\n disabled={stoppingSessionIds.has(contextTab.sessionId)}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-status-error-text hover:bg-status-error-bg/15 transition-colors disabled:opacity-60 disabled:cursor-not-allowed\"\n aria-busy={stoppingSessionIds.has(contextTab.sessionId)}\n >\n {stoppingSessionIds.has(contextTab.sessionId) ? (\n <Loader2 className=\"size-3.5 animate-spin\" />\n ) : (\n <Square className=\"size-3.5 fill-current\" />\n )}\n {stoppingSessionIds.has(contextTab.sessionId)\n ? tr('vibecontrols.sessions.terminal.stoppingSession', 'Stopping…')\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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"}