@burdenoff/microfe-vibecontrols 2026.531.3 → 2026.531.4

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.
@@ -679,6 +679,15 @@ function ct() {
679
679
  R(t), z === e.id && B(t.length > 0 ? t[t.length - 1].sessionId : null), l(), O(!0);
680
680
  return;
681
681
  }
682
+ if (t.error) {
683
+ let n = t.error.message || y("sessions.failedStartTerminal", "Failed to start terminal");
684
+ R((t) => t.map((t) => t.sessionId === e.id ? {
685
+ ...t,
686
+ loading: !1,
687
+ error: n
688
+ } : t));
689
+ return;
690
+ }
682
691
  let n = ot(t.data?.startSessionTerminal?.ttydUrl || null, X.find((t) => t.id === e.id)?.agent?.tunnelUrl ?? null);
683
692
  R((t) => t.map((t) => t.sessionId === e.id ? {
684
693
  ...t,
@@ -1 +1 @@
1
- {"version":3,"file":"SessionsPage.js","names":[],"sources":["../../src/pages/SessionsPage.tsx"],"sourcesContent":["import { useState, useMemo, useRef, useCallback, useEffect } from 'react';\nimport {\n Plus,\n Terminal,\n Grid,\n List,\n Play,\n Square,\n RefreshCw,\n Monitor,\n Trash2,\n RotateCcw,\n CheckSquare,\n XSquare,\n Activity,\n Download,\n} from 'lucide-react';\nimport { MotionPage, MotionList, MotionItem } from '@burdenoff/fe-libs/shared/motion';\nimport { AnimatePresence } from 'framer-motion';\nimport { SessionCard, SessionList, TerminalPanel, SessionShareDialog } from '@/components/sessions';\nimport type { Session, TerminalTab } from '@/components/sessions';\nimport { getAgentStatus } from '@/components/agents/agentStatus';\nimport { EntityTagPicker } from '@/components/shared/EntityTagPicker';\nimport { useDraftEntityTags } from '@/hooks/useDraftEntityTags';\nimport {\n PageHeader,\n SearchInput,\n FilterDropdown,\n EmptyState,\n LoadingState,\n ErrorState,\n AgentSetupBanner,\n SearchableSelect,\n SessionNotifications,\n GetHelpButton,\n ConfirmDialog,\n} from '@/components/shared';\nimport { useVibeControls, useVibeNavigate } from '@/providers/VibeControlsProvider';\nimport { useVibeControlsStore } from '@/store/vibeControlsStore';\nimport { useCachedInitialData } from '@/hooks/useCachedInitialData';\nimport {\n useListSessionsQuery,\n useVibecontrolsAgentHealthProbesLazyQuery,\n useVibecontrolsAgentHealthProbeStreamSubscription,\n useStartSessionMutation,\n useStopSessionMutation,\n useRestartSessionMutation,\n useCreateSessionMutation,\n useStartSessionTerminalMutation,\n useStopSessionTerminalMutation,\n useRestartSessionTerminalMutation,\n useUpdateSessionMutation,\n useTerminateSessionMutation,\n useCheckSessionHealthMutation,\n useSessionStatusChangedSubscription,\n useBulkStopSessionsMutation,\n useBulkRestartSessionsMutation,\n useBulkTerminateSessionsMutation,\n useAgentSessionOrphansLazyQuery,\n useAdoptAgentSessionMutation,\n useExportSessionsMutation,\n useListAgentProfilesQuery,\n} from '@/generated/wspace-operations';\nimport { PermissionButton, AccessDenied } from '@burdenoff/fe-libs/shared/components';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { SaveToFilesButton } from '@burdenoff/fe-libs/storage';\nimport { useSearchParams } from 'react-router-dom';\nimport { VIBE_PERMISSIONS } from '@/constants/permissions';\nimport {\n getQuotaDialogContent,\n markQuotaHandledLocally,\n isQuotaExhaustedError,\n} from '@/utils/quotaUtils';\n\ntype ViewMode = 'terminal' | 'grid' | 'list';\n\n// Long-tail health-check fallback. The primary path is the\n// SessionStatusChanged subscription (sub-second updates). This poll only\n// fires if the subscription is dropped; 5m is fine because the server\n// also runs a throttled background health check on every ListSessions\n// query.\nconst HEALTH_CHECK_INTERVAL_MS = 300_000;\n\nfunction normalizeSessionTerminalUrl(\n terminalUrl: string | null | undefined,\n tunnelUrl: string | null | undefined\n): string | null {\n if (!terminalUrl) return null;\n\n try {\n const parsedTerminalUrl = new URL(terminalUrl);\n const blockedHosts = new Set(['localhost', '127.0.0.1', 'host.docker.internal']);\n\n if (tunnelUrl && blockedHosts.has(parsedTerminalUrl.hostname)) {\n const parsedTunnelUrl = new URL(tunnelUrl);\n parsedTunnelUrl.pathname = parsedTerminalUrl.pathname;\n parsedTunnelUrl.search = parsedTerminalUrl.search;\n parsedTunnelUrl.hash = parsedTerminalUrl.hash;\n return parsedTunnelUrl.toString();\n }\n\n return parsedTerminalUrl.toString();\n } catch {\n return terminalUrl;\n }\n}\n\nfunction deriveBrowserSessionStatus(\n status: Session['status'],\n rawTerminalUrl: string | null | undefined,\n normalizedTerminalUrl: string | null,\n agentIsActive?: boolean\n): Session['status'] {\n if (status !== 'RUNNING') return status;\n if (agentIsActive === false) return 'ERROR';\n if (!rawTerminalUrl) return status;\n if (!normalizedTerminalUrl) return 'ERROR';\n\n try {\n const pageProtocol = typeof window !== 'undefined' ? window.location.protocol : 'https:';\n const terminalProtocol = new URL(normalizedTerminalUrl).protocol;\n if (pageProtocol === 'https:' && terminalProtocol !== 'https:') {\n return 'ERROR';\n }\n } catch {\n return 'ERROR';\n }\n\n return status;\n}\n\n/**\n * Sessions management page with terminal-first UI.\n * Persists tab state, supports bulk operations, keyboard shortcuts, and health polling.\n */\nexport function SessionsPage() {\n const { t } = useI18n();\n const { onSessionStart, workspaceId } = useVibeControls();\n const navigate = useVibeNavigate();\n const { hasPermission, isLoading: permissionsLoading, permissions } = usePermissions();\n const [searchParams, setSearchParams] = useSearchParams();\n\n const tr = useCallback(\n (key: string, fallback: string, params?: Record<string, string | number>): string => {\n const translated = t(key, params);\n const template = translated === key ? fallback : translated;\n\n if (!params) return template;\n\n return Object.entries(params).reduce(\n (acc, [paramKey, paramValue]) => acc.replace(`{{${paramKey}}}`, String(paramValue)),\n template\n );\n },\n [t]\n );\n\n const statusOptions = useMemo(\n () => [\n { value: 'all', label: tr('sessions.statusAll', 'All Statuses') },\n { value: 'RUNNING', label: tr('sessions.running', 'Running') },\n { value: 'STOPPED', label: tr('sessions.stopped', 'Stopped') },\n { value: 'STARTING', label: tr('sessions.starting', 'Starting') },\n { value: 'ERROR', label: tr('common.error', 'Error') },\n ],\n [tr]\n );\n\n const typeOptions = useMemo(\n () => [\n { value: 'all', label: tr('sessions.typeAll', 'All Types') },\n { value: 'TMUX', label: tr('sessions.typeTmux', 'Tmux') },\n { value: 'TERMINAL', label: tr('sessions.typeTerminal', 'Terminal') },\n { value: 'SCRIPT', label: tr('sessions.typeScript', 'Script') },\n { value: 'CUSTOM', label: tr('sessions.typeCustom', 'Custom') },\n ],\n [tr]\n );\n\n // Check permissions\n const canListSessions = hasPermission(VIBE_PERMISSIONS.SESSION_LIST);\n const canCreateSession = hasPermission(VIBE_PERMISSIONS.SESSION_CREATE);\n\n const [searchQuery, setSearchQuery] = useState('');\n const [statusFilter, setStatusFilter] = useState('all');\n const [typeFilter, setTypeFilter] = useState('all');\n const [vibeFilter, setVibeFilter] = useState('all');\n const [agentFilter, setAgentFilter] = useState('all');\n const [viewMode, setViewMode] = useState<ViewMode>('terminal');\n const [showCreateForm, setShowCreateForm] = useState(false);\n const [showQuotaExhausted, setShowQuotaExhausted] = useState(false);\n const [createError, setCreateError] = useState<string | null>(null);\n const [createLoading, setCreateLoading] = useState(false);\n const [successMessage, setSuccessMessage] = useState<string | null>(null);\n\n // Bulk selection state\n const [selectedSessionIds, setSelectedSessionIds] = useState<Set<string>>(new Set());\n const [bulkLoading, setBulkLoading] = useState(false);\n const [isExporting, setIsExporting] = useState(false);\n\n // Form refs\n const sessionNameRef = useRef<HTMLInputElement>(null);\n const sessionCommandRef = useRef<HTMLInputElement>(null);\n const sessionAutoStartRef = useRef<HTMLInputElement>(null);\n // Advanced: provider-native session name to attach to (tmux/wezterm/zellij).\n // When set on submit, the agent reattaches to an existing daemon-side\n // session instead of spawning a fresh one. Empty = normal create.\n const sessionExternalNameRef = useRef<HTMLInputElement>(null);\n\n // Controlled form state for searchable selects\n const [formVibeId, setFormVibeId] = useState('');\n const [formAgentId, setFormAgentId] = useState('');\n const [formProfile, setFormProfile] = useState('default');\n const [formType, setFormType] = useState('TMUX');\n const [formShowAdvanced, setFormShowAdvanced] = useState(false);\n\n // Terminal panel state - use store for persistence across navigation\n const {\n terminalTabs: persistedTabs,\n activeTerminalTabId: persistedActiveTab,\n sessionsViewMode: persistedViewMode,\n setTerminalTabs: setPersistedTabs,\n setActiveTerminalTabId: setPersistedActiveTab,\n setSessionsViewMode: setPersistedViewMode,\n activeSessionId,\n } = useVibeControlsStore();\n\n // Local terminal tabs state (enriched with loading/error/url)\n const [terminalTabs, setTerminalTabs] = useState<TerminalTab[]>([]);\n const [activeTerminalTab, setActiveTerminalTab] = useState<string | null>(null);\n // F4 round-3: keyboard cheatsheet + command palette modals\n const [showCheatsheet, setShowCheatsheet] = useState(false);\n const [showCommandPalette, setShowCommandPalette] = useState(false);\n const [paletteQuery, setPaletteQuery] = useState('');\n const [terminalCollapsed, setTerminalCollapsed] = useState(false);\n\n // Share dialog state\n const [shareSession, setShareSession] = useState<Session | null>(null);\n\n // Immersive fullscreen state (terminal takes over the entire screen)\n const [isImmersiveFullscreen, setIsImmersiveFullscreen] = useState(false);\n\n // UI state persistence flag — true once we've loaded from store\n const [uiStateLoaded, setUiStateLoaded] = useState(false);\n\n const queryViewMode = searchParams.get('view');\n const querySessionId = searchParams.get('session');\n\n // Mutations\n const [createSession] = useCreateSessionMutation({ refetchQueries: ['ListSessions'] });\n const { setDraftTagIds, flushTags } = useDraftEntityTags('VIBECONTROLS_SESSION');\n\n // Reconnect (orphan adoption) modal state. Independent from the\n // New Session form so the user can reconnect without prefilling the\n // create form. Opened via the \"Reconnect\" header button.\n const [showReconnectModal, setShowReconnectModal] = useState(false);\n const [reconnectAgentId, setReconnectAgentId] = useState<string>('');\n const [reconnectError, setReconnectError] = useState<string | null>(null);\n const [reconnectAdoptingName, setReconnectAdoptingName] = useState<string | null>(null);\n const [discoverOrphans, { data: orphansData, loading: orphansLoading }] =\n useAgentSessionOrphansLazyQuery({ fetchPolicy: 'network-only' });\n const [adoptAgentSession] = useAdoptAgentSessionMutation({\n refetchQueries: ['ListSessions'],\n });\n const [startSession] = useStartSessionMutation({ refetchQueries: ['ListSessions'] });\n const [stopSession] = useStopSessionMutation({ refetchQueries: ['ListSessions'] });\n const [restartSession] = useRestartSessionMutation({ refetchQueries: ['ListSessions'] });\n const [startSessionTerminal] = useStartSessionTerminalMutation({\n refetchQueries: ['ListSessions'],\n });\n const [stopSessionTerminal] = useStopSessionTerminalMutation({\n refetchQueries: ['ListSessions'],\n });\n const [restartSessionTerminal] = useRestartSessionTerminalMutation({\n refetchQueries: ['ListSessions'],\n });\n const [updateSession] = useUpdateSessionMutation({ refetchQueries: ['ListSessions'] });\n const [terminateSession] = useTerminateSessionMutation({ refetchQueries: ['ListSessions'] });\n const [checkSessionHealth] = useCheckSessionHealthMutation({ refetchQueries: ['ListSessions'] });\n const [bulkStopSessions] = useBulkStopSessionsMutation({ refetchQueries: ['ListSessions'] });\n const [bulkRestartSessions] = useBulkRestartSessionsMutation({\n refetchQueries: ['ListSessions'],\n });\n const [bulkTerminateSessions] = useBulkTerminateSessionsMutation({\n refetchQueries: ['ListSessions'],\n });\n const [exportSessions] = useExportSessionsMutation();\n\n // ── UI State Persistence (localStorage via Zustand store) ─────────────\n // Load persisted UI state from store on mount\n useEffect(() => {\n if (uiStateLoaded) return;\n\n // Query params take precedence over persisted UI state so session tabs are bookmarkable.\n if (queryViewMode === 'terminal' || queryViewMode === 'grid' || queryViewMode === 'list') {\n setViewMode(queryViewMode);\n } else if (persistedViewMode) {\n setViewMode(persistedViewMode);\n }\n\n if (querySessionId) {\n setActiveTerminalTab(querySessionId);\n } else if (persistedActiveTab) {\n setActiveTerminalTab(persistedActiveTab);\n }\n\n // Restore tabs from store — they'll need to re-fetch terminal URLs\n if (persistedTabs.length > 0) {\n const restoredTabs: TerminalTab[] = persistedTabs.map((t) => ({\n sessionId: t.sessionId,\n name: t.name,\n terminalUrl: null, // Will be populated when sessions load\n loading: false,\n error: null,\n }));\n setTerminalTabs(restoredTabs);\n }\n\n setUiStateLoaded(true);\n }, [\n persistedTabs,\n persistedActiveTab,\n persistedViewMode,\n querySessionId,\n queryViewMode,\n uiStateLoaded,\n ]);\n\n // Once sessions are loaded and we have restored tabs, populate terminal URLs\n const sessionsLoadedRef = useRef(false);\n\n // Sync local state changes to store for persistence\n useEffect(() => {\n if (!uiStateLoaded) return;\n // Sync terminal tabs to store (only sessionId and name for persistence)\n const tabsToStore = terminalTabs.map((t) => ({ sessionId: t.sessionId, name: t.name }));\n setPersistedTabs(tabsToStore);\n }, [terminalTabs, uiStateLoaded, setPersistedTabs]);\n\n useEffect(() => {\n if (!uiStateLoaded) return;\n setPersistedActiveTab(activeTerminalTab);\n }, [activeTerminalTab, uiStateLoaded, setPersistedActiveTab]);\n\n useEffect(() => {\n if (!uiStateLoaded) return;\n setPersistedViewMode(viewMode);\n }, [viewMode, uiStateLoaded, setPersistedViewMode]);\n\n useEffect(() => {\n if (!uiStateLoaded) return;\n\n const next = new URLSearchParams(searchParams);\n let changed = false;\n\n const nextView = viewMode === 'terminal' ? null : viewMode;\n if (nextView) {\n if (next.get('view') !== nextView) {\n next.set('view', nextView);\n changed = true;\n }\n } else if (next.has('view')) {\n next.delete('view');\n changed = true;\n }\n\n if (activeTerminalTab) {\n if (next.get('session') !== activeTerminalTab) {\n next.set('session', activeTerminalTab);\n changed = true;\n }\n } else if (next.has('session')) {\n next.delete('session');\n changed = true;\n }\n\n if (changed) {\n setSearchParams(next, { replace: true });\n }\n }, [activeTerminalTab, searchParams, setSearchParams, uiStateLoaded, viewMode]);\n\n // Track previous activeSessionId to detect changes from the context switcher\n const prevActiveSessionIdRef = useRef<string | null>(null);\n\n // ── Live session-status subscription ─────────────────────────────────\n // Replaces the prior `setInterval(checkSessionHealth, …)` poll. The\n // backend already runs a throttled background health check on every\n // ListSessions query (see SessionService.triggerBackgroundHealthCheck);\n // the subscription propagates resulting status flips into Apollo's\n // normalized cache by id, so any rendered session row updates without\n // a manual refetch.\n //\n // workspaceId comes from VibeControlsProvider; skipping until it\n // resolves avoids a \"Workspace context required\" error during initial\n // mount before the auth token is issued.\n useSessionStatusChangedSubscription({\n skip: !workspaceId,\n variables: { workspaceId: workspaceId || '' },\n });\n\n // Long-tail safety fallback: in case the subscription drops (network\n // blip, agent restart) and a status flip is missed, re-trigger the\n // server-side health check every 5 minutes. Server-side throttling\n // (30s) protects against thundering-herd if multiple tabs are open.\n useEffect(() => {\n const interval = setInterval(() => {\n checkSessionHealth().catch(() => {\n /* silent — fallback path */\n });\n }, HEALTH_CHECK_INTERVAL_MS);\n return () => clearInterval(interval);\n }, [checkSessionHealth]);\n\n // ── Keyboard Shortcuts for Terminal Tabs ────────────────────────────\n useEffect(() => {\n const handleKeyDown = (e: KeyboardEvent) => {\n // Don't intercept when typing in inputs/textareas\n const target = e.target as HTMLElement;\n if (\n target.tagName === 'INPUT' ||\n target.tagName === 'TEXTAREA' ||\n target.tagName === 'SELECT' ||\n target.isContentEditable\n ) {\n return;\n }\n\n const isMod = e.metaKey || e.ctrlKey;\n\n // Ctrl/Cmd+T — New terminal tab (open session picker)\n if (isMod && e.key === 't') {\n e.preventDefault();\n setShowCreateForm(true);\n return;\n }\n\n // Ctrl/Cmd+W — Close active terminal tab\n if (isMod && e.key === 'w' && activeTerminalTab) {\n e.preventDefault();\n const sessionIdToClose = activeTerminalTab;\n setTerminalTabs((prev) => prev.filter((t) => t.sessionId !== sessionIdToClose));\n setActiveTerminalTab(() => {\n const remaining = terminalTabs.filter((t) => t.sessionId !== sessionIdToClose);\n return remaining.length > 0 ? remaining[remaining.length - 1].sessionId : null;\n });\n return;\n }\n\n // Ctrl+Tab — Next terminal tab\n if (e.ctrlKey && e.key === 'Tab' && !e.shiftKey && terminalTabs.length > 1) {\n e.preventDefault();\n const currentIdx = terminalTabs.findIndex((t) => t.sessionId === activeTerminalTab);\n const nextIdx = (currentIdx + 1) % terminalTabs.length;\n setActiveTerminalTab(terminalTabs[nextIdx].sessionId);\n return;\n }\n\n // Ctrl+Shift+Tab — Previous terminal tab\n if (e.ctrlKey && e.key === 'Tab' && e.shiftKey && terminalTabs.length > 1) {\n e.preventDefault();\n const currentIdx = terminalTabs.findIndex((t) => t.sessionId === activeTerminalTab);\n const prevIdx = (currentIdx - 1 + terminalTabs.length) % terminalTabs.length;\n setActiveTerminalTab(terminalTabs[prevIdx].sessionId);\n return;\n }\n\n // F4 round-3: Cmd/Ctrl + 1..9 — Jump to tab N (1-indexed)\n if (isMod && /^[1-9]$/.test(e.key) && terminalTabs.length > 0) {\n const idx = Number(e.key) - 1;\n if (idx < terminalTabs.length) {\n e.preventDefault();\n setActiveTerminalTab(terminalTabs[idx].sessionId);\n }\n return;\n }\n\n // F4 round-3: Cmd/Ctrl + P — Open command palette\n if (isMod && (e.key === 'p' || e.key === 'P')) {\n e.preventDefault();\n setPaletteQuery('');\n setShowCommandPalette(true);\n return;\n }\n\n // F4 round-3: ? (no modifier) — Open keyboard cheatsheet\n if (!isMod && !e.altKey && !e.shiftKey && e.key === '?') {\n e.preventDefault();\n setShowCheatsheet(true);\n return;\n }\n };\n\n document.addEventListener('keydown', handleKeyDown);\n return () => document.removeEventListener('keydown', handleKeyDown);\n }, [activeTerminalTab, terminalTabs]);\n\n // Fetch sessions from GraphQL API (with filters)\n // Use previousData to prevent flash of loading state during refetches\n // When no filters narrow the result set, the query variables match\n // the InitialData prefetch — use cache-first to skip the redundant\n // network hit. With filters applied, cache-and-network keeps stale\n // narrowed results from being shown silently.\n const noSessionFilters =\n statusFilter === 'all' && typeFilter === 'all' && vibeFilter === 'all' && agentFilter === 'all';\n const sessionsFetchPolicy = noSessionFilters ? 'cache-first' : 'cache-and-network';\n const { data, previousData, loading, error, refetch } = useListSessionsQuery({\n variables: {\n filter: {\n status:\n statusFilter !== 'all'\n ? (statusFilter as 'RUNNING' | 'STOPPED' | 'STARTING' | 'ERROR')\n : undefined,\n type:\n typeFilter !== 'all'\n ? (typeFilter as 'TMUX' | 'TERMINAL' | 'SSH' | 'SCRIPT' | 'CUSTOM')\n : undefined,\n vibeId: vibeFilter !== 'all' && vibeFilter !== 'none' ? vibeFilter : undefined,\n agentId: agentFilter !== 'all' ? agentFilter : undefined,\n },\n pagination: { limit: 50 },\n },\n fetchPolicy: sessionsFetchPolicy,\n });\n\n // Use current data or fall back to previous data during refetches\n // This prevents the \"Loading sessions...\" flash when refetching\n const effectiveData = data ?? previousData;\n\n // Use cached initial data for dropdowns (agents and vibes)\n // This reads from Apollo cache - no network request\n const { agents: cachedAgents, vibes: cachedVibes } = useCachedInitialData();\n\n // Live deep-health probe per agent (every 30s). Used to gate the\n // \"Create session\" button — without this, the button stays enabled\n // against an agent whose cloudflared tunnel has died, and clicking\n // it produces the user-visible \"Failed to start terminal\" error\n // long after the session row is already created in PENDING state.\n // Initial query primes the cache; the subscription below replaces\n // the prior 30s pollInterval so health flips reach this page in <1s.\n // Probe query is lazy — the subscription below is the source of truth\n // for initial paint, so we only fire the query on user-triggered\n // refresh (see refetch handler below).\n const [refetchProbes, agentHealthQuery] = useVibecontrolsAgentHealthProbesLazyQuery({\n fetchPolicy: 'network-only',\n notifyOnNetworkStatusChange: true,\n });\n const { data: agentHealthStreamData } = useVibecontrolsAgentHealthProbeStreamSubscription();\n const agentHealthByIdRef = useMemo(() => {\n const m = new Map<string, { healthy: boolean; error: string | null }>();\n const probes =\n agentHealthStreamData?.vibecontrolsAgentHealthProbeStream ??\n agentHealthQuery.data?.vibecontrolsAgentHealthProbes ??\n [];\n for (const p of probes) {\n m.set(p.agentId, { healthy: p.healthy, error: p.errorMessage ?? null });\n }\n return m;\n }, [agentHealthQuery.data, agentHealthStreamData]);\n\n // Build vibe options for filter using cached data\n const vibeOptions = useMemo(() => {\n return [\n { value: 'all', label: tr('sessions.filterVibeAll', 'All Vibes') },\n { value: 'none', label: tr('sessions.filterVibeNone', 'No Vibe') },\n ...cachedVibes.map((vibe) => ({\n value: vibe.id,\n label: vibe.name,\n })),\n ];\n }, [cachedVibes, tr]);\n\n // Build agent options for filter using cached data\n const agentOptions = useMemo(() => {\n return [\n { value: 'all', label: tr('sessions.filterAgentAll', 'All Agents') },\n ...cachedAgents.map((agent) => ({\n value: agent.id,\n label: `${agent.name} ${getAgentStatus(agent) === 'ACTIVE' ? '' : `(${tr('sessions.offline', 'Offline')})`}`,\n })),\n ];\n }, [cachedAgents, tr]);\n\n // Build options for create form using cached data (no \"all\"/\"none\" entries)\n const createVibeOptions = useMemo(() => {\n return [\n { value: '', label: tr('sessions.noVibe', 'No vibe') },\n ...cachedVibes.map((vibe) => ({\n value: vibe.id,\n label: vibe.name,\n })),\n ];\n }, [cachedVibes, tr]);\n\n const createAgentOptions = useMemo(() => {\n return [\n { value: '', label: tr('sessions.selectAgent', 'Select an agent') },\n ...cachedAgents.map((agent) => {\n const status = getAgentStatus(agent);\n const label =\n status === 'ACTIVE' ? tr('sessions.active', 'Active') : tr('sessions.offline', 'Offline');\n return {\n value: agent.id,\n label: `${agent.name} (${label})`,\n };\n }),\n ];\n }, [cachedAgents, tr]);\n\n // Pull the agent's active profiles so the create-form dropdown shows\n // the real options instead of a hardcoded \"default\". Skips when no\n // agent is picked yet. Cache-and-network so the list updates after\n // the operator creates / deletes a profile elsewhere in the session.\n const { data: agentProfilesData } = useListAgentProfilesQuery({\n variables: { agentId: formAgentId },\n skip: !formAgentId,\n fetchPolicy: 'cache-and-network',\n });\n const profileOptions = useMemo(() => {\n const list = agentProfilesData?.vibecontrolsAgentProfiles ?? [];\n const fromAgent = list.map((p: { name: string; status?: string | null }) => ({\n value: p.name,\n label: p.status && p.status !== 'ACTIVE' ? `${p.name} (${p.status})` : p.name,\n }));\n // Ensure \"default\" is always a selectable option even if the\n // profile-discovery query is still loading or the agent has been\n // freshly registered. The agent-side bare-path fallback routes\n // bare /api/* → /api/profiles/default/* so this is always safe.\n const hasDefault = fromAgent.some((o: { value: string }) => o.value === 'default');\n return hasDefault ? fromAgent : [{ value: 'default', label: 'default' }, ...fromAgent];\n }, [agentProfilesData]);\n\n // Reset profile selection when the operator picks a different agent\n // so we don't carry over a profile name that doesn't exist on the\n // newly-selected agent.\n useEffect(() => {\n setFormProfile('default');\n }, [formAgentId]);\n\n const sessionTypeOptions = useMemo(\n () => [\n { value: 'TMUX', label: tr('sessions.typeTmuxLower', 'tmux') },\n { value: 'WEZTERM', label: tr('sessions.typeWezTerm', 'WezTerm') },\n { value: 'ZELLIJ', label: tr('sessions.typeZellij', 'Zellij') },\n { value: 'TERMINAL', label: tr('sessions.typeTerminal', 'Terminal') },\n { value: 'SSH', label: tr('sessions.typeSsh', 'SSH') },\n { value: 'SCRIPT', label: tr('sessions.typeScript', 'Script') },\n { value: 'CUSTOM', label: tr('sessions.typeCustom', 'Custom') },\n ],\n [tr]\n );\n\n // Map GraphQL response to Session type\n // Use effectiveData to prevent flash during refetches\n const sessions: Session[] = useMemo(() => {\n const rawSessions = effectiveData?.sessions?.items ?? [];\n return rawSessions.map((session: NonNullable<typeof rawSessions>[number]) => {\n const normalizedTtydUrl = normalizeSessionTerminalUrl(\n session.ttydUrl || null,\n session.agent?.tunnelUrl || null\n );\n\n return {\n id: session.id,\n workspaceId: session.workspaceId,\n vibeId: session.vibeId || null,\n agentId: session.agentId || null,\n profile: session.profile ?? 'default',\n name: session.name,\n type: session.type as Session['type'],\n status: deriveBrowserSessionStatus(\n session.status as Session['status'],\n session.ttydUrl || null,\n normalizedTtydUrl,\n // Use the shared derivation, not the sticky isActive flag, so a\n // stale-swept agent that's already heartbeating again doesn't\n // poison the row's derived status.\n session.agent ? getAgentStatus(session.agent) === 'ACTIVE' : undefined\n ),\n command: session.command || null,\n workingDirectory: session.workingDirectory || null,\n environmentVariables: (session.environmentVariables as Record<string, string>) || {},\n autoStart: session.autoStart,\n providerSessionName: session.providerSessionName || undefined,\n providerWindowName: session.providerWindowId || undefined,\n pid: session.pid || null,\n exitCode: session.exitCode || null,\n lastOutput: session.lastOutput || null,\n ttydUrl: normalizedTtydUrl,\n metadata: (session.metadata as Record<string, unknown>) || {},\n createdAt: session.createdAt,\n updatedAt: session.updatedAt,\n vibe: session.vibe ? { id: session.vibe.id, name: session.vibe.name } : undefined,\n agent: session.agent\n ? {\n id: session.agent.id,\n name: session.agent.name,\n tunnelUrl: session.agent.tunnelUrl || null,\n isActive: session.agent.isActive,\n }\n : undefined,\n };\n });\n }, [effectiveData?.sessions?.items]);\n\n // Populate terminal URLs for restored tabs once sessions load\n useEffect(() => {\n if (sessionsLoadedRef.current || sessions.length === 0 || terminalTabs.length === 0) return;\n sessionsLoadedRef.current = true;\n\n // Re-spawn ttyd for every restored tab. The persisted ttydUrl from\n // the session row is stale on remount: the agent's ttyd process\n // dies on agent restart / idle GC, and the cached URL surfaces as\n // \"Terminal not running for this session\" when the iframe loads.\n // startSessionTerminal is idempotent server-side (re-uses an\n // existing ttyd if it's still running, spawns a fresh one if not),\n // so this is safe to call unconditionally on tab restore. The\n // iframe url returned in the mutation response is the source of\n // truth — we no longer trust the persisted ttydUrl.\n void Promise.allSettled(\n terminalTabs.map(async (tab) => {\n const session = sessions.find((s) => s.id === tab.sessionId);\n if (!session) return;\n try {\n const result = await startSessionTerminal({ variables: { id: tab.sessionId } });\n const tunnelHint = session.agent?.tunnelUrl ?? null;\n const newUrl = normalizeSessionTerminalUrl(\n result.data?.startSessionTerminal?.ttydUrl ?? null,\n tunnelHint\n );\n setTerminalTabs((prev) =>\n prev.map((t) =>\n t.sessionId === tab.sessionId\n ? {\n ...t,\n terminalUrl: newUrl,\n agentId: session.agent?.id ?? t.agentId ?? null,\n loading: false,\n error: null,\n }\n : t\n )\n );\n } catch (err) {\n // Don't blow up the tab — leave it loading=false with the\n // cached ttydUrl as a best-effort, the user can hit\n // \"Restart terminal\" if the iframe still 404s.\n // eslint-disable-next-line no-console\n console.warn('[sessions] tab restore startSessionTerminal failed', err);\n setTerminalTabs((prev) =>\n prev.map((t) =>\n t.sessionId === tab.sessionId && session.ttydUrl\n ? {\n ...t,\n terminalUrl: session.ttydUrl,\n agentId: session.agent?.id ?? t.agentId ?? null,\n loading: false,\n }\n : t\n )\n );\n }\n })\n );\n }, [sessions, terminalTabs, startSessionTerminal]);\n\n // ── Session Switcher Integration ─────────────────────────────────────\n // When activeSessionId changes, open/switch to that terminal\n useEffect(() => {\n // Skip if not initialized or no session selected\n if (!uiStateLoaded || !activeSessionId) {\n prevActiveSessionIdRef.current = activeSessionId;\n return;\n }\n\n // Skip if this is the initial load (not a user-initiated change)\n if (prevActiveSessionIdRef.current === null && activeSessionId) {\n prevActiveSessionIdRef.current = activeSessionId;\n return;\n }\n\n // Skip if the session hasn't actually changed\n if (prevActiveSessionIdRef.current === activeSessionId) {\n return;\n }\n\n prevActiveSessionIdRef.current = activeSessionId;\n\n // Find the session in the sessions list\n const session = sessions.find((s) => s.id === activeSessionId);\n if (!session) return;\n\n // Check if this session already has an open tab\n const existingTab = terminalTabs.find((t) => t.sessionId === activeSessionId);\n\n if (existingTab) {\n // Tab already exists - just switch to it\n setActiveTerminalTab(activeSessionId);\n setTerminalCollapsed(false);\n } else {\n // Need to open a new tab for this session\n const url = session.ttydUrl || null;\n const newTab: TerminalTab = {\n sessionId: session.id,\n name: session.name,\n terminalUrl: url,\n agentId: session.agent?.id ?? null,\n profile: session.profile ?? 'default',\n loading: !url,\n error: null,\n };\n setTerminalTabs((prev) => [...prev, newTab]);\n setActiveTerminalTab(activeSessionId);\n setTerminalCollapsed(false);\n\n // If no URL available, start the terminal\n if (!url) {\n startSessionTerminal({ variables: { id: session.id } })\n .then((result) => {\n const newUrl = result.data?.startSessionTerminal?.ttydUrl || null;\n setTerminalTabs((prev) =>\n prev.map((t) =>\n t.sessionId === session.id\n ? {\n ...t,\n terminalUrl: newUrl,\n loading: false,\n error: newUrl\n ? null\n : tr(\n 'sessions.terminalStartedNoUrl',\n 'Terminal started but no URL was returned. Try restarting.'\n ),\n }\n : t\n )\n );\n })\n .catch((err) => {\n const message =\n err instanceof Error\n ? err.message\n : tr('sessions.failedStartTerminal', 'Failed to start terminal');\n setTerminalTabs((prev) =>\n prev.map((t) =>\n t.sessionId === session.id ? { ...t, loading: false, error: message } : t\n )\n );\n });\n }\n }\n }, [activeSessionId, sessions, terminalTabs, uiStateLoaded, startSessionTerminal]);\n\n // Filter sessions\n const filteredSessions = useMemo(() => {\n return sessions.filter((session) => {\n if (searchQuery) {\n const query = searchQuery.toLowerCase();\n const matchesSearch =\n session.name.toLowerCase().includes(query) ||\n session.command?.toLowerCase().includes(query) ||\n session.workingDirectory?.toLowerCase().includes(query);\n if (!matchesSearch) return false;\n }\n if (vibeFilter === 'none' && session.vibeId) return false;\n return true;\n });\n }, [sessions, searchQuery, vibeFilter]);\n\n // ── Bulk Selection ───────────────────────────────────────────────────\n const toggleSessionSelection = useCallback((sessionId: string) => {\n setSelectedSessionIds((prev) => {\n const next = new Set(prev);\n if (next.has(sessionId)) {\n next.delete(sessionId);\n } else {\n next.add(sessionId);\n }\n return next;\n });\n }, []);\n\n const selectAllSessions = useCallback(() => {\n setSelectedSessionIds(new Set(filteredSessions.map((s) => s.id)));\n }, [filteredSessions]);\n\n const deselectAllSessions = useCallback(() => {\n setSelectedSessionIds(new Set());\n }, []);\n\n const isAllSelected =\n filteredSessions.length > 0 && selectedSessionIds.size === filteredSessions.length;\n const hasSelection = selectedSessionIds.size > 0;\n\n // ── Bulk Actions ─────────────────────────────────────────────────────\n const handleBulkStop = useCallback(async () => {\n if (selectedSessionIds.size === 0) return;\n setBulkLoading(true);\n try {\n const result = await bulkStopSessions({ variables: { ids: Array.from(selectedSessionIds) } });\n const data = result.data?.bulkStopSessions;\n setSuccessMessage(\n `${tr('sessions.stopped', 'Stopped')} ${data?.succeeded ?? 0} ${tr('nav.sessions', 'sessions')}${\n data?.failed ? `, ${data.failed} ${tr('sessions.failed', 'failed')}` : ''\n }`\n );\n setSelectedSessionIds(new Set());\n setTimeout(() => setSuccessMessage(null), 3000);\n } catch (err) {\n setSuccessMessage(\n `${tr('sessions.errorPrefix', 'Error')}: ${\n err instanceof Error ? err.message : tr('sessions.failedStop', 'Failed to stop sessions')\n }`\n );\n setTimeout(() => setSuccessMessage(null), 5000);\n } finally {\n setBulkLoading(false);\n }\n }, [selectedSessionIds, bulkStopSessions, tr]);\n\n const handleBulkRestart = useCallback(async () => {\n if (selectedSessionIds.size === 0) return;\n setBulkLoading(true);\n try {\n const result = await bulkRestartSessions({\n variables: { ids: Array.from(selectedSessionIds) },\n });\n const data = result.data?.bulkRestartSessions;\n setSuccessMessage(\n `${tr('sessions.restarted', 'Restarted')} ${data?.succeeded ?? 0} ${tr('nav.sessions', 'sessions')}${\n data?.failed ? `, ${data.failed} ${tr('sessions.failed', 'failed')}` : ''\n }`\n );\n setSelectedSessionIds(new Set());\n setTimeout(() => setSuccessMessage(null), 3000);\n } catch (err) {\n setSuccessMessage(\n `${tr('sessions.errorPrefix', 'Error')}: ${\n err instanceof Error\n ? err.message\n : tr('sessions.failedRestart', 'Failed to restart sessions')\n }`\n );\n setTimeout(() => setSuccessMessage(null), 5000);\n } finally {\n setBulkLoading(false);\n }\n }, [selectedSessionIds, bulkRestartSessions, tr]);\n\n const handleBulkTerminate = useCallback(async () => {\n if (selectedSessionIds.size === 0) return;\n if (\n !confirm(\n `${tr('sessions.confirmRemovePrefix', 'Are you sure you want to remove')} ${selectedSessionIds.size} ${tr('nav.sessions', 'session(s)')}? ${tr('sessions.confirmRemoveSuffix', 'This cannot be undone.')}`\n )\n )\n return;\n setBulkLoading(true);\n try {\n const result = await bulkTerminateSessions({\n variables: { ids: Array.from(selectedSessionIds) },\n });\n const data = result.data?.bulkTerminateSessions;\n setSuccessMessage(\n `${tr('sessions.removed', 'Removed')} ${data?.succeeded ?? 0} ${tr('nav.sessions', 'sessions')}${\n data?.failed ? `, ${data.failed} ${tr('sessions.failed', 'failed')}` : ''\n }`\n );\n setSelectedSessionIds(new Set());\n setTimeout(() => setSuccessMessage(null), 3000);\n } catch (err) {\n setSuccessMessage(\n `${tr('sessions.errorPrefix', 'Error')}: ${\n err instanceof Error\n ? err.message\n : tr('sessions.failedRemove', 'Failed to remove sessions')\n }`\n );\n setTimeout(() => setSuccessMessage(null), 5000);\n } finally {\n setBulkLoading(false);\n }\n }, [selectedSessionIds, bulkTerminateSessions, tr]);\n\n const handleExportSessions = useCallback(\n async (format: 'CSV' | 'JSON' = 'CSV') => {\n setIsExporting(true);\n try {\n const result = await exportSessions({\n variables: {\n input: {\n filter: {\n status:\n statusFilter !== 'all'\n ? (statusFilter as 'RUNNING' | 'STOPPED' | 'STARTING' | 'ERROR')\n : undefined,\n type:\n typeFilter !== 'all'\n ? (typeFilter as 'TMUX' | 'TERMINAL' | 'SSH' | 'SCRIPT' | 'CUSTOM')\n : undefined,\n vibeId: vibeFilter !== 'all' && vibeFilter !== 'none' ? vibeFilter : undefined,\n agentId: agentFilter !== 'all' ? agentFilter : undefined,\n },\n format,\n },\n },\n });\n const exportData = result.data?.exportSessions;\n if (!exportData) return;\n\n const mimeType = format === 'CSV' ? 'text/csv' : 'application/json';\n const blob = new Blob([exportData.data], { type: mimeType });\n const url = URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.href = url;\n a.download = `sessions-export-${new Date().toISOString().slice(0, 10)}.${format.toLowerCase()}`;\n a.click();\n URL.revokeObjectURL(url);\n setSuccessMessage(\n tr('sessions.exportSuccess', 'Exported {{count}} sessions as {{format}}', {\n count: exportData.count,\n format,\n })\n );\n setTimeout(() => setSuccessMessage(null), 3000);\n } catch (err) {\n setSuccessMessage(\n `${tr('sessions.errorPrefix', 'Error')}: ${\n err instanceof Error\n ? err.message\n : tr('sessions.failedExport', 'Failed to export sessions')\n }`\n );\n setTimeout(() => setSuccessMessage(null), 5000);\n } finally {\n setIsExporting(false);\n }\n },\n [exportSessions, statusFilter, typeFilter, vibeFilter, agentFilter, tr]\n );\n\n // ── Single Session Actions ───────────────────────────────────────────\n const handleSessionSelect = (session: Session) => {\n navigate(`/sessions/${session.id}`);\n };\n\n const handleCreateSession = () => {\n setCreateError(null);\n setShowCreateForm(true);\n };\n\n const handleSubmitCreateSession = async () => {\n const name = sessionNameRef.current?.value?.trim();\n const vibeId = formVibeId;\n const selectedAgentId = formAgentId;\n const type = formType;\n const command = sessionCommandRef.current?.value?.trim();\n const autoStart = sessionAutoStartRef.current?.checked ?? false;\n\n if (!name) {\n setCreateError(tr('sessions.nameRequired', 'Name is required'));\n return;\n }\n\n // Multi-agent: a vibe has 0..N attached agents. Pick the user's selected\n // agent when set; otherwise auto-pick when the vibe has exactly one\n // attached agent; otherwise force an explicit choice.\n const selectedVibe = cachedVibes.find((v) => v.id === vibeId);\n const candidateAgents = selectedVibe?.agents ?? [];\n let agentId: string | null = null;\n if (selectedAgentId && (!vibeId || candidateAgents.some((a) => a.id === selectedAgentId))) {\n agentId = selectedAgentId;\n } else if (candidateAgents.length === 1) {\n agentId = candidateAgents[0]?.id ?? null;\n }\n if (!agentId) {\n const message =\n vibeId && candidateAgents.length === 0\n ? tr(\n 'sessions.noAgentAttached',\n 'This vibe has no agents attached. Attach one from the Vibe → Agents tab before creating sessions.'\n )\n : vibeId && candidateAgents.length > 1\n ? tr(\n 'sessions.pickAgentFromVibe',\n 'This vibe has multiple agents attached. Pick one before creating the session.'\n )\n : tr('sessions.agentRequired', 'Please select an agent for this session.');\n setCreateError(message);\n return;\n }\n\n // Block session create when the agent's live deep-health probe is\n // not green — without this gate, the create succeeds (PENDING row\n // gets written) but the subsequent terminal-start step fails with\n // \"Terminal started but no tunnel URL\". Surface the actual probe\n // error inline instead of letting the user pile up dead PENDING\n // sessions against an unreachable agent.\n const probeRow = agentHealthByIdRef.get(agentId);\n if (probeRow && !probeRow.healthy) {\n setCreateError(\n tr(\n 'sessions.agentNotHealthy',\n 'The selected agent isn\\u2019t fully reachable right now. Please wait for the agent badge to turn green and retry.'\n ) + (probeRow.error ? ` (${probeRow.error})` : '')\n );\n return;\n }\n\n setCreateLoading(true);\n setCreateError(null);\n try {\n const externalSessionName = sessionExternalNameRef.current?.value?.trim() || undefined;\n const result = await createSession({\n variables: {\n input: {\n name,\n type: (type || 'TMUX') as\n | 'TMUX'\n | 'WEZTERM'\n | 'ZELLIJ'\n | 'TERMINAL'\n | 'SSH'\n | 'SCRIPT'\n | 'CUSTOM',\n command: command || undefined,\n vibeId: vibeId || undefined,\n agentId,\n profile: formProfile?.trim() || 'default',\n autoStart,\n externalSessionName,\n },\n },\n });\n // The Apollo client used by this MFE is configured with\n // `errorPolicy: 'all'` for mutations (see VibeControlsProvider). That\n // makes the promise RESOLVE on GraphQL errors instead of throwing —\n // so the catch block below never fires for `code: QUOTA_EXHAUSTED`\n // from wspace-vibecontrols-svc. Without this guard the operator sees\n // \"Session created successfully\" + an empty terminal tab while the\n // server actually returned `data: null, errors: [...]`. Mirror the\n // pattern already used by ActionsPage / TargetsPage: detect the\n // quota signal on `result.error` before reading `result.data`.\n if (result.error && isQuotaExhaustedError(result.error)) {\n setShowCreateForm(false);\n markQuotaHandledLocally();\n setShowQuotaExhausted(true);\n return;\n }\n // Any other GraphQL error path: surface inline on the form so the\n // operator sees the actual failure instead of a misleading success.\n if (result.error) {\n setCreateError(\n result.error.message || tr('sessions.failedCreate', 'Failed to create session')\n );\n return;\n }\n const newSessionId = result.data?.createSession?.id;\n // No data despite no error is itself a failure — surface a generic\n // message rather than closing the form silently.\n if (!newSessionId) {\n setCreateError(tr('sessions.failedCreate', 'Failed to create session'));\n return;\n }\n setShowCreateForm(false);\n setSuccessMessage(tr('sessions.createdSuccess', 'Session created successfully'));\n await flushTags(newSessionId);\n setTimeout(() => setSuccessMessage(null), 3000);\n\n // Auto-open the new session as a terminal tab regardless of viewMode.\n // The terminal-first layout obviously needs it, but list/grid views\n // expect the same behaviour — the bottom terminal panel is shared\n // across all viewModes and the operator just asked us to spawn a\n // session. Previously this was gated on viewMode==='terminal', so\n // creating from list/grid view succeeded server-side but left the\n // operator looking at \"Session created successfully\" with no tab.\n if (result.data?.createSession) {\n const created = result.data.createSession;\n // Hydrate the agent reference from cachedAgents so TerminalIframe\n // has an agentId to mint TERMINAL_SESSION capability against. The\n // createSession mutation response is the flat SessionFields\n // fragment (no agent sub-object), so without this the tab opens\n // with agentId=null and the capability mint never fires.\n const ownedAgent = created.agentId\n ? cachedAgents.find((a) => a.id === created.agentId)\n : null;\n const newSession: Session = {\n id: created.id,\n workspaceId: created.workspaceId,\n vibeId: created.vibeId || null,\n agentId: created.agentId || null,\n profile: created.profile ?? 'default',\n name: created.name,\n type: created.type as Session['type'],\n status: created.status as Session['status'],\n command: created.command || null,\n workingDirectory: created.workingDirectory || null,\n environmentVariables: (created.environmentVariables as Record<string, string>) || {},\n autoStart: created.autoStart,\n providerSessionName: created.providerSessionName || undefined,\n providerWindowName: created.providerWindowId || undefined,\n pid: created.pid || null,\n exitCode: created.exitCode || null,\n lastOutput: created.lastOutput || null,\n ttydUrl: created.ttydUrl || null,\n metadata: (created.metadata as Record<string, unknown>) || {},\n createdAt: created.createdAt,\n updatedAt: created.updatedAt,\n agent: ownedAgent\n ? {\n id: ownedAgent.id,\n name: ownedAgent.name,\n tunnelUrl: ownedAgent.tunnelUrl || null,\n isActive: ownedAgent.isActive,\n }\n : undefined,\n };\n void handleOpenTerminal(newSession);\n }\n } catch (err) {\n if (isQuotaExhaustedError(err)) {\n setShowCreateForm(false);\n markQuotaHandledLocally();\n setShowQuotaExhausted(true);\n return;\n }\n\n setCreateError(\n err instanceof Error ? err.message : tr('sessions.failedCreate', 'Failed to create session')\n );\n } finally {\n setCreateLoading(false);\n }\n };\n\n const handleStartSession = async (session: Session) => {\n try {\n await startSession({ variables: { id: session.id } });\n onSessionStart?.({\n id: session.id,\n name: session.name,\n type: session.type,\n status: 'RUNNING',\n });\n } catch (err) {\n if (isQuotaExhaustedError(err)) {\n markQuotaHandledLocally();\n setShowQuotaExhausted(true);\n return;\n }\n\n console.error('Failed to start session:', err);\n }\n };\n\n const handleStopSession = async (session: Session) => {\n try {\n await stopSession({ variables: { id: session.id } });\n } catch (err) {\n console.error('Failed to stop session:', err);\n }\n };\n\n const handleRestartSession = async (session: Session) => {\n try {\n await restartSession({ variables: { id: session.id } });\n } catch (err) {\n if (isQuotaExhaustedError(err)) {\n markQuotaHandledLocally();\n setShowQuotaExhausted(true);\n return;\n }\n\n console.error('Failed to restart session:', err);\n }\n };\n\n const handleTerminateSession = async (session: Session) => {\n try {\n await terminateSession({ variables: { id: session.id } });\n } catch (err) {\n console.error('Failed to terminate session:', err);\n }\n };\n\n const getTerminalUrl = useCallback((session: Session): string | null => {\n return session.ttydUrl || null;\n }, []);\n\n // Share session handler\n const handleShareSession = useCallback((session: Session) => {\n setShareSession(session);\n }, []);\n\n const handleOpenTerminal = useCallback(\n async (session: Session) => {\n const existingTab = terminalTabs.find((t) => t.sessionId === session.id);\n if (existingTab?.terminalUrl) {\n setActiveTerminalTab(session.id);\n setTerminalCollapsed(false);\n return;\n }\n\n const url = getTerminalUrl(session);\n\n if (url) {\n const newTab: TerminalTab = {\n sessionId: session.id,\n name: session.name,\n terminalUrl: url,\n agentId: session.agent?.id ?? session.agentId ?? null,\n profile: session.profile ?? 'default',\n loading: false,\n error: null,\n };\n setTerminalTabs((prev) => {\n const filtered = prev.filter((t) => t.sessionId !== session.id);\n return [...filtered, newTab];\n });\n setActiveTerminalTab(session.id);\n setTerminalCollapsed(false);\n return;\n }\n\n const loadingTab: TerminalTab = {\n sessionId: session.id,\n name: session.name,\n terminalUrl: null,\n agentId: session.agent?.id ?? null,\n loading: true,\n error: null,\n };\n setTerminalTabs((prev) => {\n const filtered = prev.filter((t) => t.sessionId !== session.id);\n return [...filtered, loadingTab];\n });\n setActiveTerminalTab(session.id);\n setTerminalCollapsed(false);\n\n try {\n const result = await startSessionTerminal({ variables: { id: session.id } });\n if (result.error && isQuotaExhaustedError(result.error)) {\n const remainingTabs = terminalTabs.filter((t) => t.sessionId !== session.id);\n setTerminalTabs(remainingTabs);\n if (activeTerminalTab === session.id) {\n setActiveTerminalTab(\n remainingTabs.length > 0 ? remainingTabs[remainingTabs.length - 1].sessionId : null\n );\n }\n markQuotaHandledLocally();\n setShowQuotaExhausted(true);\n return;\n }\n\n // Apply the same browser-host normalization we use for list rows\n // so a `host.docker.internal`/`localhost` URL falls back to the\n // agent's known tunnel host where possible. The mutation response\n // doesn't fetch the agent inline (SessionFields fragment is flat),\n // so we look up the tunnelUrl from the already-loaded session row.\n const rawUrl = result.data?.startSessionTerminal?.ttydUrl || null;\n const sessionRow = sessions.find((s) => s.id === session.id);\n const tunnelHint = sessionRow?.agent?.tunnelUrl ?? null;\n const newUrl = normalizeSessionTerminalUrl(rawUrl, tunnelHint);\n setTerminalTabs((prev) =>\n prev.map((t) =>\n t.sessionId === session.id\n ? {\n ...t,\n terminalUrl: newUrl,\n loading: false,\n error: newUrl\n ? null\n : tr(\n 'sessions.terminalStartedNoTunnelUrl',\n 'Terminal started but no tunnel URL was returned. Try restarting.'\n ),\n }\n : t\n )\n );\n } catch (err) {\n if (isQuotaExhaustedError(err)) {\n const remainingTabs = terminalTabs.filter((t) => t.sessionId !== session.id);\n setTerminalTabs(remainingTabs);\n if (activeTerminalTab === session.id) {\n setActiveTerminalTab(\n remainingTabs.length > 0 ? remainingTabs[remainingTabs.length - 1].sessionId : null\n );\n }\n markQuotaHandledLocally();\n setShowQuotaExhausted(true);\n return;\n }\n\n const message =\n err instanceof Error\n ? err.message\n : tr('sessions.failedStartTerminal', 'Failed to start terminal');\n setTerminalTabs((prev) =>\n prev.map((t) =>\n t.sessionId === session.id ? { ...t, loading: false, error: message } : t\n )\n );\n }\n },\n [activeTerminalTab, terminalTabs, getTerminalUrl, startSessionTerminal]\n );\n\n useEffect(() => {\n if (!uiStateLoaded || !activeTerminalTab) return;\n if (terminalTabs.some((tab) => tab.sessionId === activeTerminalTab)) return;\n\n const session = sessions.find((item) => item.id === activeTerminalTab);\n if (session) {\n void handleOpenTerminal(session);\n }\n }, [activeTerminalTab, handleOpenTerminal, sessions, terminalTabs, uiStateLoaded]);\n\n const handleCloseTerminalTab = useCallback(\n (sessionId: string) => {\n setTerminalTabs((prev) => prev.filter((t) => t.sessionId !== sessionId));\n if (activeTerminalTab === sessionId) {\n setActiveTerminalTab(() => {\n const remaining = terminalTabs.filter((t) => t.sessionId !== sessionId);\n return remaining.length > 0 ? remaining[remaining.length - 1].sessionId : null;\n });\n }\n },\n [activeTerminalTab, terminalTabs]\n );\n\n const handleOpenTerminalExternal = useCallback(\n (sessionId: string) => {\n const tab = terminalTabs.find((t) => t.sessionId === sessionId);\n if (tab?.terminalUrl) window.open(tab.terminalUrl, '_blank');\n },\n [terminalTabs]\n );\n\n const handleRenameSession = useCallback(\n async (sessionId: string, newName: string) => {\n try {\n await updateSession({ variables: { id: sessionId, input: { name: newName } } });\n setTerminalTabs((prev) =>\n prev.map((t) => (t.sessionId === sessionId ? { ...t, name: newName } : t))\n );\n } catch (err) {\n console.error('Failed to rename session:', err);\n }\n },\n [updateSession]\n );\n\n const handleStopTerminal = useCallback(\n async (sessionId: string) => {\n try {\n await stopSessionTerminal({ variables: { id: sessionId } });\n setTerminalTabs((prev) =>\n prev.map((t) =>\n t.sessionId === sessionId\n ? {\n ...t,\n terminalUrl: null,\n error: tr('sessions.terminalStopped', 'Terminal stopped'),\n }\n : t\n )\n );\n } catch (err) {\n console.error('Failed to stop terminal:', err);\n }\n },\n [stopSessionTerminal]\n );\n\n const handleRestartTerminal = useCallback(\n async (sessionId: string) => {\n const previousTab = terminalTabs.find((t) => t.sessionId === sessionId) || null;\n\n try {\n setTerminalTabs((prev) =>\n prev.map((t) =>\n t.sessionId === sessionId ? { ...t, loading: true, error: null, terminalUrl: null } : t\n )\n );\n const result = await restartSessionTerminal({ variables: { id: sessionId } });\n if (result.error && isQuotaExhaustedError(result.error)) {\n setTerminalTabs((prev) =>\n prev.map((t) =>\n t.sessionId === sessionId\n ? {\n ...t,\n loading: false,\n error: null,\n terminalUrl: previousTab?.terminalUrl ?? t.terminalUrl,\n }\n : t\n )\n );\n markQuotaHandledLocally();\n setShowQuotaExhausted(true);\n return;\n }\n\n const newUrl = result.data?.restartSessionTerminal?.ttydUrl || null;\n setTerminalTabs((prev) =>\n prev.map((t) =>\n t.sessionId === sessionId\n ? {\n ...t,\n terminalUrl: newUrl,\n loading: false,\n error: newUrl\n ? null\n : tr(\n 'sessions.terminalRestartedNoUrl',\n 'Terminal restarted but no URL returned.'\n ),\n }\n : t\n )\n );\n } catch (err) {\n if (isQuotaExhaustedError(err)) {\n setTerminalTabs((prev) =>\n prev.map((t) =>\n t.sessionId === sessionId\n ? {\n ...t,\n loading: false,\n error: null,\n terminalUrl: previousTab?.terminalUrl ?? t.terminalUrl,\n }\n : t\n )\n );\n markQuotaHandledLocally();\n setShowQuotaExhausted(true);\n return;\n }\n\n const message =\n err instanceof Error\n ? err.message\n : tr('sessions.failedRestartTerminal', 'Failed to restart terminal');\n setTerminalTabs((prev) =>\n prev.map((t) =>\n t.sessionId === sessionId ? { ...t, loading: false, error: message } : t\n )\n );\n }\n },\n [restartSessionTerminal, terminalTabs]\n );\n\n // Toggle immersive fullscreen mode\n const handleToggleImmersiveFullscreen = useCallback(() => {\n setIsImmersiveFullscreen((prev) => !prev);\n }, []);\n\n // Stats\n const stats = {\n total: sessions.length,\n running: sessions.filter((s) => s.status === 'RUNNING').length,\n stopped: sessions.filter((s) => s.status === 'STOPPED').length,\n };\n\n // Guard states rendered inside MotionPage to prevent unmount/remount animation replay\n if (permissionsLoading) {\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n <LoadingState message={tr('sessions.loadingPermissions', 'Loading permissions...')} />\n </MotionPage>\n );\n }\n\n if (permissions && permissions.permissionStrings?.length > 0 && !canListSessions) {\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n <AccessDenied\n title={tr('common.accessDeniedTitle', 'Access Denied')}\n description={tr(\n 'sessions.accessDeniedDescription',\n \"You don't have permission to view sessions.\"\n )}\n details={tr('sessions.requiredPermission', 'Required permission: session:list')}\n />\n </MotionPage>\n );\n }\n\n const isInitialLoad = loading && !previousData && sessions.length === 0;\n if (isInitialLoad) {\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n <LoadingState message={tr('sessions.loading', 'Loading sessions...')} />\n </MotionPage>\n );\n }\n\n if (error && sessions.length === 0) {\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n <ErrorState\n title={tr('sessions.failedLoad', 'Failed to load sessions')}\n message={error.message}\n onRetry={() => refetch()}\n />\n </MotionPage>\n );\n }\n\n // ── Bulk Action Bar ──────────────────────────────────────────────────\n const renderBulkActionBar = () => {\n if (!hasSelection || viewMode === 'terminal') return null;\n return (\n <div className=\"flex flex-col sm:flex-row items-start sm:items-center gap-3 px-4 py-2 bg-action-primary-bg/10 border border-action-primary-bg/20 rounded-lg\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr(\n selectedSessionIds.size === 1\n ? 'sessions.selectionCountSingle'\n : 'sessions.selectionCountPlural',\n selectedSessionIds.size === 1\n ? '{{count}} session selected'\n : '{{count}} sessions selected',\n { count: selectedSessionIds.size }\n )}\n </span>\n <div className=\"flex items-center gap-1.5 sm:ml-auto flex-wrap\">\n <button\n type=\"button\"\n onClick={handleBulkStop}\n disabled={bulkLoading}\n className=\"flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-text-primary bg-bg-surface border border-border-default rounded-md hover:bg-bg-sunken transition-colors disabled:opacity-50\"\n >\n <Square className=\"size-3\" /> {tr('sessions.stop', 'Stop')}\n </button>\n <button\n type=\"button\"\n onClick={handleBulkRestart}\n disabled={bulkLoading}\n className=\"flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-text-primary bg-bg-surface border border-border-default rounded-md hover:bg-bg-sunken transition-colors disabled:opacity-50\"\n >\n <RotateCcw className=\"size-3\" /> {tr('sessions.restart', 'Restart')}\n </button>\n <button\n type=\"button\"\n onClick={handleBulkTerminate}\n disabled={bulkLoading}\n className=\"flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-status-error-text bg-status-error-bg border border-status-error-text/20 rounded-md hover:bg-status-error-bg/80 transition-colors disabled:opacity-50\"\n >\n <Trash2 className=\"size-3\" /> {tr('sessions.remove', 'Remove')}\n </button>\n <button\n type=\"button\"\n onClick={deselectAllSessions}\n className=\"flex items-center gap-1.5 px-3 py-1.5 text-xs text-text-secondary hover:text-text-primary transition-colors\"\n >\n <XSquare className=\"size-3\" /> {tr('sessions.deselect', 'Deselect')}\n </button>\n </div>\n </div>\n );\n };\n\n // Shared view-mode toggle (terminal / grid / list). Rendered in the\n // same place in both view branches' action-strips so the operator's\n // muscle memory doesn't have to relocate it when switching views.\n // Previously the grid/list view placed it inline with the search\n // input (row 2) while terminal view placed it top-right of the\n // header, which made the icon \"move\" between views.\n const viewToggle = (\n <div className=\"flex items-center gap-0.5 rounded-md bg-bg-sunken p-0.5 flex-shrink-0\">\n <button\n type=\"button\"\n onClick={() => setViewMode('terminal')}\n className={`p-1.5 rounded ${viewMode === 'terminal' ? 'bg-bg-surface shadow-sm' : 'hover:bg-bg-surface/50'}`}\n title={tr('sessions.terminalViewTitle', 'Terminal view')}\n aria-label={tr('sessions.terminalViewTitle', 'Terminal view')}\n >\n <Monitor className=\"size-4 text-text-secondary\" />\n </button>\n <button\n type=\"button\"\n onClick={() => setViewMode('grid')}\n className={`p-1.5 rounded ${viewMode === 'grid' ? 'bg-bg-surface shadow-sm' : 'hover:bg-bg-surface/50'}`}\n title={tr('sessions.gridViewTitle', 'Grid view')}\n aria-label={tr('sessions.gridViewTitle', 'Grid view')}\n >\n <Grid className=\"size-4 text-text-secondary\" />\n </button>\n <button\n type=\"button\"\n onClick={() => setViewMode('list')}\n className={`p-1.5 rounded ${viewMode === 'list' ? 'bg-bg-surface shadow-sm' : 'hover:bg-bg-surface/50'}`}\n title={tr('sessions.listViewTitle', 'List view')}\n aria-label={tr('sessions.listViewTitle', 'List view')}\n >\n <List className=\"size-4 text-text-secondary\" />\n </button>\n </div>\n );\n\n // Shared overlay dialogs — rendered identically in both terminal-first\n // and grid/list returns so that the quota modal, the create-session\n // modal, the reconnect modal, and the share dialog all surface\n // regardless of the active view. Previously these only existed in the\n // grid/list branch, so a quota-exhausted createSession in terminal\n // view silently closed the form with no operator feedback.\n const overlayDialogs = (\n <>\n {showCreateForm && renderCreateModal()}\n {renderReconnectModal()}\n {shareSession && (\n <SessionShareDialog\n session={shareSession}\n isOpen={!!shareSession}\n onClose={() => setShareSession(null)}\n />\n )}\n <ConfirmDialog\n isOpen={showQuotaExhausted}\n onClose={() => setShowQuotaExhausted(false)}\n onConfirm={() => {\n setShowQuotaExhausted(false);\n navigate('/billing');\n }}\n title={getQuotaDialogContent('session').title}\n message={getQuotaDialogContent('session').message}\n confirmLabel=\"View Billing\"\n cancelLabel=\"Dismiss\"\n variant=\"warning\"\n />\n </>\n );\n\n // Terminal-first view\n if (viewMode === 'terminal') {\n return (\n <MotionPage className=\"flex flex-col bg-bg-surface h-screen\">\n <div className=\"flex flex-col sm:flex-row items-start sm:items-center justify-between gap-2 px-4 py-2 border-b border-border-default bg-bg-sunken\">\n <div className=\"flex flex-wrap items-center gap-3\">\n <h1 className=\"text-sm font-semibold text-text-primary\">\n {tr('nav.sessions', 'Sessions')}\n </h1>\n <span className=\"text-xs text-text-muted\">\n {tr('sessions.runningSummary', '{{running}} running / {{total}} total', {\n running: stats.running,\n total: stats.total,\n })}\n </span>\n </div>\n <div className=\"flex w-full flex-wrap items-center gap-2 sm:w-auto sm:justify-end\">\n <button\n type=\"button\"\n onClick={() => checkSessionHealth().then(() => refetch())}\n className=\"p-1.5 text-text-secondary hover:bg-bg-surface rounded transition-colors\"\n title={tr('sessions.checkHealth', 'Check session health')}\n >\n <Activity className=\"size-3.5\" />\n </button>\n <button\n type=\"button\"\n onClick={() => {\n refetch();\n void refetchProbes();\n }}\n disabled={loading}\n className=\"p-1.5 text-text-secondary hover:bg-bg-surface rounded transition-colors disabled:opacity-50\"\n title={tr('common.refresh', 'Refresh')}\n >\n <RefreshCw className={`size-3.5 ${loading ? 'animate-spin' : ''}`} />\n </button>\n {viewToggle}\n </div>\n </div>\n\n {/* Agent Notifications Banner */}\n <div className=\"px-4 pt-3\">\n <SessionNotifications showConnectionStatus={true} maxVisible={3} />\n </div>\n\n <TerminalPanel\n tabs={terminalTabs}\n activeTabId={activeTerminalTab}\n onTabSelect={setActiveTerminalTab}\n onTabClose={handleCloseTerminalTab}\n onOpenExternal={handleOpenTerminalExternal}\n collapsed={false}\n sessions={filteredSessions}\n onOpenTerminal={handleOpenTerminal}\n onRetryTerminal={handleOpenTerminal}\n onRenameSession={handleRenameSession}\n onStopTerminal={handleStopTerminal}\n onStopSession={(sessionId) => {\n const session = sessions.find((s) => s.id === sessionId);\n if (session) void handleStopSession(session);\n }}\n onRestartTerminal={handleRestartTerminal}\n onCreateSession={handleCreateSession}\n fullScreen={true}\n isImmersiveFullscreen={isImmersiveFullscreen}\n onToggleImmersiveFullscreen={handleToggleImmersiveFullscreen}\n contextSwitcher={undefined}\n />\n\n {/* F4 round-3: status bar (online state + shortcut hint). Kept\n compact so it does not steal real estate from the terminal. */}\n <div className=\"flex items-center justify-between gap-3 px-4 py-1.5 border-t border-border-default bg-bg-sunken text-[11px] text-text-tertiary\">\n <div className=\"flex items-center gap-3\">\n <span className=\"inline-flex items-center gap-1.5\">\n <span className=\"size-1.5 rounded-full bg-status-success-bg\" aria-hidden />\n {tr('sessions.statusOnline', 'Online')}\n </span>\n <span>\n {terminalTabs.length} {terminalTabs.length === 1 ? 'tab' : 'tabs'}\n </span>\n {activeTerminalTab && (\n <span className=\"truncate max-w-[40ch]\">\n {terminalTabs.find((t) => t.sessionId === activeTerminalTab)?.name}\n </span>\n )}\n </div>\n <div className=\"hidden sm:flex items-center gap-3\">\n <button\n type=\"button\"\n onClick={() => setShowCheatsheet(true)}\n className=\"hover:text-text-secondary transition-colors\"\n >\n {tr('sessions.shortcutsHint', 'Press ? for shortcuts')}\n </button>\n <button\n type=\"button\"\n onClick={() => {\n setPaletteQuery('');\n setShowCommandPalette(true);\n }}\n className=\"hover:text-text-secondary transition-colors\"\n >\n {tr('sessions.paletteHint', 'Cmd+P · Command palette')}\n </button>\n </div>\n </div>\n\n {successMessage && (\n <div className=\"fixed bottom-4 right-4 z-50 px-4 py-3 bg-status-success-bg text-status-success-text rounded-md shadow-lg text-sm\">\n {successMessage}\n </div>\n )}\n {showCreateForm && renderCreateModal()}\n {renderReconnectModal()}\n\n {/* F4 round-3: keyboard cheatsheet overlay */}\n {showCheatsheet && (\n <div\n className=\"fixed inset-0 z-50 flex items-center justify-center bg-bg-overlay/80 backdrop-blur-sm p-4\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"sessions-cheatsheet-title\"\n onClick={() => setShowCheatsheet(false)}\n >\n <div\n className=\"bg-bg-surface border border-border-default rounded-xl shadow-2xl p-6 max-w-md w-full\"\n onClick={(e) => e.stopPropagation()}\n >\n <h2\n id=\"sessions-cheatsheet-title\"\n className=\"text-lg font-semibold text-text-primary mb-4\"\n >\n {tr('sessions.cheatsheet.title', 'Keyboard shortcuts')}\n </h2>\n <dl className=\"grid grid-cols-[auto_1fr] gap-x-4 gap-y-2 text-sm\">\n <dt className=\"font-mono text-text-secondary\">Cmd / Ctrl + T</dt>\n <dd className=\"text-text-primary\">New session</dd>\n <dt className=\"font-mono text-text-secondary\">Cmd / Ctrl + W</dt>\n <dd className=\"text-text-primary\">Close active tab</dd>\n <dt className=\"font-mono text-text-secondary\">Cmd / Ctrl + 1–9</dt>\n <dd className=\"text-text-primary\">Switch to tab N</dd>\n <dt className=\"font-mono text-text-secondary\">Ctrl + Tab</dt>\n <dd className=\"text-text-primary\">Next tab</dd>\n <dt className=\"font-mono text-text-secondary\">Ctrl + Shift + Tab</dt>\n <dd className=\"text-text-primary\">Previous tab</dd>\n <dt className=\"font-mono text-text-secondary\">Cmd / Ctrl + P</dt>\n <dd className=\"text-text-primary\">Command palette</dd>\n <dt className=\"font-mono text-text-secondary\">?</dt>\n <dd className=\"text-text-primary\">This cheatsheet</dd>\n </dl>\n <div className=\"mt-5 flex justify-end\">\n <button\n type=\"button\"\n onClick={() => setShowCheatsheet(false)}\n className=\"px-3 py-1.5 text-sm rounded-md bg-bg-sunken hover:bg-bg-secondary text-text-primary transition-colors\"\n >\n {tr('common.close', 'Close')}\n </button>\n </div>\n </div>\n </div>\n )}\n\n {/* F4 round-3: command palette — filter by session name + ad-hoc\n commands. Hand-rolled (fe-libs `CommandDialog` exists but we\n want richer keyboard handling than the canonical primitive\n offers). */}\n {showCommandPalette && (\n <div\n className=\"fixed inset-0 z-50 flex items-start justify-center bg-bg-overlay/80 backdrop-blur-sm p-4 pt-20\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"sessions-palette-title\"\n onClick={() => setShowCommandPalette(false)}\n >\n <div\n className=\"bg-bg-surface border border-border-default rounded-xl shadow-2xl w-full max-w-lg overflow-hidden\"\n onClick={(e) => e.stopPropagation()}\n >\n <input\n autoFocus\n type=\"search\"\n value={paletteQuery}\n onChange={(e) => setPaletteQuery(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === 'Escape') setShowCommandPalette(false);\n }}\n placeholder={tr(\n 'sessions.palette.placeholder',\n 'Search sessions or type a command…'\n )}\n aria-label=\"Command palette search\"\n className=\"w-full px-4 py-3 bg-bg-surface border-b border-border-default text-text-primary placeholder:text-text-muted focus:outline-none\"\n />\n <ul\n id=\"sessions-palette-title\"\n className=\"max-h-80 overflow-y-auto divide-y divide-border-subtle\"\n >\n {/* Hard-coded actions */}\n {(['new'] as const).map((id) => {\n const label = tr('sessions.palette.new', 'New session');\n if (paletteQuery && !label.toLowerCase().includes(paletteQuery.toLowerCase()))\n return null;\n return (\n <li key={id}>\n <button\n type=\"button\"\n onClick={() => {\n setShowCommandPalette(false);\n setShowCreateForm(true);\n }}\n className=\"w-full text-left px-4 py-2.5 text-sm hover:bg-bg-sunken transition-colors\"\n >\n <span className=\"font-medium text-text-primary\">{label}</span>\n <span className=\"ml-2 text-xs text-text-muted\">Cmd+T</span>\n </button>\n </li>\n );\n })}\n\n {/* Open sessions */}\n {filteredSessions\n .filter(\n (s) =>\n !paletteQuery || s.name.toLowerCase().includes(paletteQuery.toLowerCase())\n )\n .slice(0, 20)\n .map((s) => (\n <li key={s.id}>\n <button\n type=\"button\"\n onClick={() => {\n setShowCommandPalette(false);\n void handleOpenTerminal(s);\n }}\n className=\"w-full text-left px-4 py-2.5 text-sm hover:bg-bg-sunken transition-colors flex items-center justify-between\"\n >\n <span className=\"text-text-primary truncate\">{s.name}</span>\n <span className=\"ml-2 text-xs text-text-muted\">{s.status}</span>\n </button>\n </li>\n ))}\n </ul>\n </div>\n </div>\n )}\n {overlayDialogs}\n </MotionPage>\n );\n }\n\n // Grid/List view\n return (\n <MotionPage className=\"flex flex-col bg-bg-surface min-h-screen\">\n <div className=\"flex-1 overflow-y-auto space-y-6 p-4 sm:p-6\">\n <PageHeader\n title={tr('nav.sessions', 'Sessions')}\n description={tr('sessions.pageDescription', '{{running}} of {{total}} sessions running', {\n running: stats.running,\n total: stats.total,\n })}\n actions={\n <div className=\"flex flex-col sm:flex-row items-stretch sm:items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => checkSessionHealth().then(() => refetch())}\n className=\"flex items-center gap-2 px-3 py-2 text-text-secondary hover:bg-bg-sunken rounded-md transition-colors\"\n title={tr('sessions.checkHealth', 'Check session health')}\n >\n <Activity className=\"size-4\" />\n </button>\n <button\n type=\"button\"\n onClick={() => refetch()}\n disabled={loading}\n className=\"flex items-center gap-2 px-3 py-2 text-text-secondary hover:bg-bg-sunken rounded-md transition-colors disabled:opacity-50\"\n title={tr('sessions.refreshTitle', 'Refresh sessions')}\n >\n <RefreshCw className={`size-4 ${loading ? 'animate-spin' : ''}`} />\n </button>\n <SaveToFilesButton\n label={tr('sessions.export', 'Export')}\n disabled={isExporting}\n testId=\"sessions-export\"\n defaultFolderPath=\"/Vibecontrols/Sessions\"\n source={{\n kind: 'blob',\n filename: `sessions-export-${new Date().toISOString().slice(0, 10)}.csv`,\n mimeType: 'text/csv',\n getBlob: async () => {\n const result = await exportSessions({\n variables: {\n input: {\n filter: {\n status:\n statusFilter !== 'all'\n ? (statusFilter as 'RUNNING' | 'STOPPED' | 'STARTING' | 'ERROR')\n : undefined,\n type:\n typeFilter !== 'all'\n ? (typeFilter as 'TMUX' | 'TERMINAL' | 'SSH' | 'SCRIPT' | 'CUSTOM')\n : undefined,\n vibeId:\n vibeFilter !== 'all' && vibeFilter !== 'none'\n ? vibeFilter\n : undefined,\n agentId: agentFilter !== 'all' ? agentFilter : undefined,\n },\n format: 'CSV',\n },\n },\n });\n const exportData = result.data?.exportSessions;\n if (!exportData) return null;\n return { blob: new Blob([exportData.data], { type: 'text/csv' }) };\n },\n }}\n onDownload={() => handleExportSessions('CSV')}\n />\n <PermissionButton permission={VIBE_PERMISSIONS.SESSION_CREATE}>\n <button\n type=\"button\"\n onClick={() => setShowReconnectModal(true)}\n className=\"flex items-center gap-2 px-3 py-2 text-text-primary border border-border-default rounded-md hover:bg-bg-sunken transition-colors\"\n data-testid=\"sessions-reconnect-button\"\n title={tr(\n 'sessions.reconnectTooltip',\n 'Reconnect orphan tmux/wezterm/zellij sessions left over from a previous agent run'\n )}\n >\n <RefreshCw className=\"size-4\" />\n {tr('sessions.reconnect', 'Reconnect')}\n </button>\n </PermissionButton>\n <PermissionButton permission={VIBE_PERMISSIONS.SESSION_CREATE}>\n <button\n type=\"button\"\n onClick={handleCreateSession}\n className=\"flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors\"\n >\n <Plus className=\"size-4\" />\n {tr('sessions.newSession', 'New Session')}\n </button>\n </PermissionButton>\n {viewToggle}\n </div>\n }\n />\n\n {/* Agent Notifications Banner */}\n <SessionNotifications showConnectionStatus={true} maxVisible={3} />\n\n {/* Filters Bar */}\n <div data-tour=\"sessions-filters\" className=\"space-y-3\">\n {/* Row 1: Search.\n The view toggle is rendered in the page-header actions\n block so its position matches the terminal-view header —\n no relocation when the operator switches viewMode. */}\n <div className=\"flex flex-col sm:flex-row items-stretch sm:items-center gap-2\">\n <SearchInput\n value={searchQuery}\n onChange={setSearchQuery}\n placeholder={tr('sessions.searchPlaceholder', 'Search sessions...')}\n className=\"flex-1 sm:w-64 sm:flex-none\"\n />\n </div>\n\n {/* Row 2: Filters - wrap on all screens */}\n <div className=\"flex items-center gap-2 sm:gap-3 flex-wrap\">\n <FilterDropdown\n label={tr('sessions.filterStatus', 'Status')}\n value={statusFilter}\n options={statusOptions}\n onChange={setStatusFilter}\n />\n <FilterDropdown\n label={tr('sessions.filterType', 'Type')}\n value={typeFilter}\n options={typeOptions}\n onChange={setTypeFilter}\n />\n <FilterDropdown\n label={tr('sessions.filterVibe', 'Vibe')}\n value={vibeFilter}\n options={vibeOptions}\n onChange={setVibeFilter}\n />\n <FilterDropdown\n label={tr('sessions.filterAgent', 'Agent')}\n value={agentFilter}\n options={agentOptions}\n onChange={setAgentFilter}\n />\n </div>\n </div>\n\n {/* Quick Actions & Select All */}\n <div className=\"flex flex-col sm:flex-row items-start sm:items-center gap-2 flex-wrap\">\n <button\n type=\"button\"\n onClick={() => setStatusFilter('RUNNING')}\n className={`flex items-center gap-2 px-3 py-1.5 text-sm rounded-full transition-colors ${\n statusFilter === 'RUNNING'\n ? 'bg-status-success-bg text-status-success-text'\n : 'bg-bg-sunken text-text-secondary hover:bg-bg-sunken/80'\n }`}\n >\n <Play className=\"size-3\" /> {tr('sessions.running', 'Running')} ({stats.running})\n </button>\n <button\n type=\"button\"\n onClick={() => setStatusFilter('STOPPED')}\n className={`flex items-center gap-2 px-3 py-1.5 text-sm rounded-full transition-colors ${\n statusFilter === 'STOPPED'\n ? 'bg-bg-sunken text-text-secondary'\n : 'bg-bg-sunken text-text-secondary hover:bg-bg-sunken/80'\n }`}\n >\n <Square className=\"size-3\" /> {tr('sessions.stoppedState', 'Stopped')} ({stats.stopped})\n </button>\n <div className=\"sm:ml-auto flex items-center gap-2\">\n {filteredSessions.length > 0 && (\n <button\n type=\"button\"\n onClick={isAllSelected ? deselectAllSessions : selectAllSessions}\n className=\"flex items-center gap-1.5 px-3 py-1.5 text-xs text-text-secondary hover:text-text-primary transition-colors\"\n >\n <CheckSquare className=\"size-3.5\" />\n {isAllSelected\n ? tr('sessions.deselectAll', 'Deselect All')\n : tr('sessions.selectAll', 'Select All')}\n </button>\n )}\n </div>\n </div>\n\n {/* Bulk Action Bar */}\n {renderBulkActionBar()}\n\n {/* Results Count */}\n {searchQuery ||\n statusFilter !== 'all' ||\n typeFilter !== 'all' ||\n vibeFilter !== 'all' ||\n agentFilter !== 'all' ? (\n <p className=\"text-sm text-text-secondary\">\n {tr('sessions.showing', 'Showing')} {filteredSessions.length} {tr('sessions.of', 'of')}{' '}\n {sessions.length} {tr('nav.sessions', 'sessions')}\n {loading && (\n <span className=\"ml-2 text-text-muted\">\n ({tr('sessions.refreshing', 'refreshing...')})\n </span>\n )}\n </p>\n ) : null}\n\n {/* Sessions Display */}\n <AnimatePresence mode=\"wait\">\n {filteredSessions.length > 0 ? (\n viewMode === 'grid' ? (\n <MotionList\n key=\"grid-view\"\n stagger={0.04}\n className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\"\n >\n {filteredSessions.map((session) => (\n <MotionItem key={session.id}>\n <SessionCard\n session={session}\n onOpen={handleSessionSelect}\n onStart={handleStartSession}\n onStop={handleStopSession}\n onRestart={handleRestartSession}\n onOpenTerminal={handleOpenTerminal}\n onShare={handleShareSession}\n selected={selectedSessionIds.has(session.id)}\n onToggleSelect={() => toggleSessionSelection(session.id)}\n />\n </MotionItem>\n ))}\n </MotionList>\n ) : (\n <MotionItem key=\"list-view\">\n <SessionList\n sessions={filteredSessions}\n onOpen={handleSessionSelect}\n onStart={handleStartSession}\n onStop={handleStopSession}\n onRestart={handleRestartSession}\n onTerminate={handleTerminateSession}\n onOpenTerminal={handleOpenTerminal}\n onShare={handleShareSession}\n selectedIds={selectedSessionIds}\n onToggleSelect={toggleSessionSelection}\n onSelectAll={selectAllSessions}\n onDeselectAll={deselectAllSessions}\n />\n </MotionItem>\n )\n ) : (\n <div className=\"space-y-4\">\n <AgentSetupBanner variant=\"compact\" dismissKey=\"vibecontrols-sessions-setup-banner\" />\n <EmptyState\n icon={<Terminal className=\"size-8 text-text-secondary\" />}\n title={\n searchQuery ||\n statusFilter !== 'all' ||\n typeFilter !== 'all' ||\n vibeFilter !== 'all' ||\n agentFilter !== 'all'\n ? tr('sessions.emptyFound', 'No sessions found')\n : tr('sessions.emptyConfigured', 'No sessions configured')\n }\n description={\n searchQuery ||\n statusFilter !== 'all' ||\n typeFilter !== 'all' ||\n vibeFilter !== 'all' ||\n agentFilter !== 'all'\n ? tr('sessions.emptyAdjustSearch', 'Try adjusting your search or filters')\n : tr(\n 'sessions.emptyDescription',\n 'Sessions require an active agent. Install the CLI agent first, then start a session.'\n )\n }\n action={\n !searchQuery &&\n statusFilter === 'all' &&\n typeFilter === 'all' &&\n vibeFilter === 'all' &&\n agentFilter === 'all' ? (\n <div className=\"flex items-center gap-2\">\n {canCreateSession && (\n <button\n type=\"button\"\n onClick={handleCreateSession}\n className=\"flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors\"\n >\n <Plus className=\"size-4\" /> {tr('sessions.newSession', 'New Session')}\n </button>\n )}\n <GetHelpButton prompt=\"What is a Session in VibeControls and how do I start one?\" />\n </div>\n ) : undefined\n }\n />\n </div>\n )}\n </AnimatePresence>\n\n {successMessage && (\n <div className=\"fixed bottom-4 right-4 z-50 px-4 py-3 bg-status-success-bg text-status-success-text rounded-md shadow-lg text-sm\">\n {successMessage}\n </div>\n )}\n </div>\n\n {/* Tabbed Terminal Panel - docked at bottom */}\n {terminalTabs.length > 0 && (\n <TerminalPanel\n tabs={terminalTabs}\n activeTabId={activeTerminalTab}\n onTabSelect={setActiveTerminalTab}\n onTabClose={handleCloseTerminalTab}\n onOpenExternal={handleOpenTerminalExternal}\n onToggleCollapse={() => setTerminalCollapsed((prev) => !prev)}\n collapsed={terminalCollapsed}\n sessions={filteredSessions}\n onOpenTerminal={handleOpenTerminal}\n onRetryTerminal={handleOpenTerminal}\n onRenameSession={handleRenameSession}\n onStopTerminal={handleStopTerminal}\n onStopSession={(sessionId) => {\n const session = sessions.find((s) => s.id === sessionId);\n if (session) void handleStopSession(session);\n }}\n onRestartTerminal={handleRestartTerminal}\n heightClass=\"h-[280px] sm:h-[380px]\"\n isImmersiveFullscreen={isImmersiveFullscreen}\n onToggleImmersiveFullscreen={handleToggleImmersiveFullscreen}\n contextSwitcher={undefined}\n />\n )}\n\n {overlayDialogs}\n </MotionPage>\n );\n\n function renderReconnectModal() {\n if (!showReconnectModal) return null;\n const orphans = orphansData?.agentSessionOrphans ?? [];\n return (\n <div\n className=\"fixed inset-0 bg-bg-overlay flex items-center justify-center z-50 p-4\"\n data-testid=\"sessions-reconnect-modal\"\n >\n <div className=\"bg-bg-surface rounded-lg p-6 w-full max-w-2xl max-h-[80vh] overflow-y-auto\">\n <h2 className=\"text-lg font-semibold text-text-primary mb-1\">\n {tr('sessions.reconnectTitle', 'Reconnect orphan sessions')}\n </h2>\n <p className=\"text-sm text-text-secondary mb-4\">\n {tr(\n 'sessions.reconnectDesc',\n 'Pick an agent to scan its host for tmux / wezterm / zellij sessions named vibe-* that are not currently tracked. Adopt one to start a web terminal and bring it back into the UI.'\n )}\n </p>\n <div className=\"space-y-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('sessions.agent', 'Agent')}\n </label>\n <SearchableSelect\n options={createAgentOptions}\n value={reconnectAgentId}\n onChange={(id) => {\n setReconnectAgentId(id);\n setReconnectError(null);\n if (id) void discoverOrphans({ variables: { agentId: id } });\n }}\n placeholder={tr('sessions.selectAgent', 'Select an agent')}\n searchPlaceholder={tr('sessions.searchAgents', 'Search agents...')}\n clearable\n />\n </div>\n {reconnectError && (\n <div\n className=\"p-3 bg-status-error-bg text-status-error-text text-sm rounded-md\"\n data-testid=\"sessions-reconnect-error\"\n >\n {reconnectError}\n </div>\n )}\n {reconnectAgentId && (\n <div data-testid=\"sessions-reconnect-list\" className=\"space-y-2\">\n {orphansLoading ? (\n <div className=\"text-sm text-text-secondary\">\n {tr('sessions.scanningHost', 'Scanning host...')}\n </div>\n ) : orphans.length === 0 ? (\n <div className=\"text-sm text-text-secondary\">\n {tr('sessions.noOrphans', 'No orphan vibe-* sessions found on this agent.')}\n </div>\n ) : (\n orphans.map(\n (o: {\n externalName: string;\n provider: string;\n windows: number;\n attached: boolean;\n createdAt?: string | null;\n }) => (\n <div\n key={o.externalName}\n className=\"flex items-center justify-between gap-3 p-3 border border-border-default rounded-md bg-bg-sunken/40\"\n data-testid=\"sessions-reconnect-row\"\n >\n <div className=\"min-w-0 flex-1\">\n <div className=\"font-mono text-sm text-text-primary break-all\">\n {o.externalName}\n </div>\n <div className=\"text-xs text-text-tertiary mt-0.5\">\n {o.provider} · {o.windows} window{o.windows !== 1 ? 's' : ''}\n {o.attached ? ' · attached' : ''}\n {o.createdAt ? ` · created ${o.createdAt}` : ''}\n </div>\n </div>\n <button\n type=\"button\"\n disabled={reconnectAdoptingName === o.externalName}\n onClick={async () => {\n setReconnectAdoptingName(o.externalName);\n setReconnectError(null);\n try {\n await adoptAgentSession({\n variables: {\n agentId: reconnectAgentId,\n externalName: o.externalName,\n name: o.externalName,\n },\n });\n // Refresh orphan list so the adopted one disappears.\n await discoverOrphans({\n variables: { agentId: reconnectAgentId },\n });\n } catch (e) {\n setReconnectError(e instanceof Error ? e.message : String(e));\n } finally {\n setReconnectAdoptingName(null);\n }\n }}\n className=\"shrink-0 px-3 py-1.5 text-xs bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50\"\n data-testid={`sessions-reconnect-adopt-${o.externalName}`}\n >\n {reconnectAdoptingName === o.externalName\n ? tr('sessions.adopting', 'Adopting…')\n : tr('sessions.adopt', 'Adopt')}\n </button>\n </div>\n )\n )\n )}\n </div>\n )}\n </div>\n <div className=\"flex justify-end gap-3 mt-6\">\n <button\n type=\"button\"\n onClick={() => {\n setShowReconnectModal(false);\n setReconnectError(null);\n }}\n className=\"px-4 py-2 text-text-primary hover:bg-bg-sunken rounded-md transition-colors\"\n >\n {tr('common.close', 'Close')}\n </button>\n </div>\n </div>\n </div>\n );\n }\n\n function renderCreateModal() {\n return (\n <div className=\"fixed inset-0 bg-bg-overlay flex items-center justify-center z-50 p-4\">\n <div className=\"bg-bg-surface rounded-lg p-6 w-full max-w-lg\">\n <h2 className=\"text-lg font-semibold text-text-primary mb-4\">\n {tr('sessions.newSession', 'New Session')}\n </h2>\n <div className=\"space-y-4\">\n {createError && (\n <div className=\"p-3 bg-status-error-bg text-status-error-text text-sm rounded-md\">\n {createError}\n </div>\n )}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('sessions.name', 'Name')}\n </label>\n <input\n aria-label=\"control\"\n ref={sessionNameRef}\n type=\"text\"\n placeholder={tr('sessions.namePlaceholder', 'dev-server')}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder\"\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('sessions.filterVibe', 'Vibe')}\n </label>\n <SearchableSelect\n options={createVibeOptions}\n value={formVibeId}\n onChange={setFormVibeId}\n placeholder={tr('sessions.noVibe', 'No vibe')}\n searchPlaceholder={tr('sessions.searchVibes', 'Search vibes...')}\n clearable\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('sessions.filterAgent', 'Agent')}\n </label>\n <SearchableSelect\n options={createAgentOptions}\n value={formAgentId}\n onChange={setFormAgentId}\n placeholder={tr('sessions.selectAgent', 'Select an agent')}\n searchPlaceholder={tr('sessions.searchAgents', 'Search agents...')}\n clearable\n />\n <p className=\"text-xs text-text-muted mt-1\">\n {tr(\n 'sessions.agentHint',\n \"Required when no vibe is selected. Overridden by the vibe's agent if a vibe is chosen.\"\n )}\n </p>\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('sessions.profile', 'Profile')}\n </label>\n <SearchableSelect\n options={profileOptions}\n value={formProfile}\n onChange={setFormProfile}\n placeholder={tr('sessions.selectProfile', 'default')}\n searchPlaceholder={tr('sessions.searchProfiles', 'Search profiles...')}\n />\n <p className=\"text-xs text-text-muted mt-1\">\n {tr(\n 'sessions.profileHint',\n 'Which agent profile this session targets. Defaults to \"default\" — change only if the agent runs multiple isolated profiles.'\n )}\n </p>\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('sessions.filterType', 'Type')}\n </label>\n <SearchableSelect\n options={sessionTypeOptions}\n value={formType}\n onChange={setFormType}\n placeholder={tr('sessions.selectType', 'Select type')}\n searchPlaceholder={tr('sessions.searchTypes', 'Search types...')}\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('sessions.command', 'Command')}\n </label>\n <input\n aria-label=\"control\"\n ref={sessionCommandRef}\n type=\"text\"\n placeholder={tr('sessions.commandPlaceholder', 'bun run dev')}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder\"\n />\n </div>\n <div className=\"flex items-center gap-2\">\n <input\n aria-label=\"autoStart\"\n ref={sessionAutoStartRef}\n type=\"checkbox\"\n id=\"autoStart\"\n className=\"size-4 rounded border-border-default\"\n />\n <label htmlFor=\"autoStart\" className=\"text-sm text-text-primary\">\n {tr('sessions.autoStartWithVibe', 'Auto-start with vibe')}\n </label>\n </div>\n {(formType === 'TMUX' || formType === 'WEZTERM' || formType === 'ZELLIJ') && (\n <div\n className=\"rounded-md border border-border-default bg-bg-sunken/40\"\n data-testid=\"sessions-create-advanced\"\n >\n <button\n type=\"button\"\n onClick={() => setFormShowAdvanced((v) => !v)}\n className=\"w-full flex items-center justify-between px-3 py-2 text-sm font-medium text-text-primary\"\n aria-expanded={formShowAdvanced}\n >\n <span>{tr('sessions.advanced', 'Advanced options')}</span>\n <span className=\"text-text-tertiary text-xs\">{formShowAdvanced ? '▾' : '▸'}</span>\n </button>\n {formShowAdvanced && (\n <div className=\"px-3 pb-3 space-y-2\">\n <label className=\"block text-xs font-medium text-text-secondary\">\n {tr('sessions.attachExistingLabel', 'Attach to existing session (optional)')}\n </label>\n <input\n aria-label=\"control\"\n ref={sessionExternalNameRef}\n type=\"text\"\n data-testid=\"sessions-create-external-name\"\n placeholder={\n formType === 'WEZTERM'\n ? 'e.g. vibe-051c9c62 (existing wezterm workspace)'\n : formType === 'ZELLIJ'\n ? 'e.g. vibe-051c9c62 (existing zellij session)'\n : 'e.g. vibe-051c9c62 (existing tmux session)'\n }\n className=\"w-full px-3 py-2 text-sm bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder font-mono\"\n />\n <p className=\"text-xs text-text-tertiary\">\n {tr(\n 'sessions.attachExistingHint',\n 'When set, the agent attaches to that existing session instead of creating a new one. Leave blank to spawn fresh.'\n )}\n </p>\n </div>\n )}\n </div>\n )}\n </div>\n <div className=\"border-t border-border-default pt-4\">\n <h3 className=\"text-sm font-medium text-text-secondary mb-2\">\n {tr('sessions.tags', 'Tags')}\n </h3>\n <EntityTagPicker entityType=\"VIBECONTROLS_SESSION\" onDraftChange={setDraftTagIds} />\n </div>\n <div className=\"flex justify-end gap-3 mt-6\">\n <button\n type=\"button\"\n onClick={() => setShowCreateForm(false)}\n disabled={createLoading}\n className=\"px-4 py-2 text-text-primary hover:bg-bg-sunken rounded-md transition-colors disabled:opacity-50\"\n >\n {tr('common.cancel', 'Cancel')}\n </button>\n <button\n type=\"button\"\n onClick={handleSubmitCreateSession}\n disabled={createLoading}\n className=\"px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50\"\n >\n {createLoading\n ? tr('sessions.creating', 'Creating...')\n : tr('sessions.createSession', 'Create Session')}\n </button>\n </div>\n </div>\n </div>\n );\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"SessionsPage.js","names":[],"sources":["../../src/pages/SessionsPage.tsx"],"sourcesContent":["import { useState, useMemo, useRef, useCallback, useEffect } from 'react';\nimport {\n Plus,\n Terminal,\n Grid,\n List,\n Play,\n Square,\n RefreshCw,\n Monitor,\n Trash2,\n RotateCcw,\n CheckSquare,\n XSquare,\n Activity,\n Download,\n} from 'lucide-react';\nimport { MotionPage, MotionList, MotionItem } from '@burdenoff/fe-libs/shared/motion';\nimport { AnimatePresence } from 'framer-motion';\nimport { SessionCard, SessionList, TerminalPanel, SessionShareDialog } from '@/components/sessions';\nimport type { Session, TerminalTab } from '@/components/sessions';\nimport { getAgentStatus } from '@/components/agents/agentStatus';\nimport { EntityTagPicker } from '@/components/shared/EntityTagPicker';\nimport { useDraftEntityTags } from '@/hooks/useDraftEntityTags';\nimport {\n PageHeader,\n SearchInput,\n FilterDropdown,\n EmptyState,\n LoadingState,\n ErrorState,\n AgentSetupBanner,\n SearchableSelect,\n SessionNotifications,\n GetHelpButton,\n ConfirmDialog,\n} from '@/components/shared';\nimport { useVibeControls, useVibeNavigate } from '@/providers/VibeControlsProvider';\nimport { useVibeControlsStore } from '@/store/vibeControlsStore';\nimport { useCachedInitialData } from '@/hooks/useCachedInitialData';\nimport {\n useListSessionsQuery,\n useVibecontrolsAgentHealthProbesLazyQuery,\n useVibecontrolsAgentHealthProbeStreamSubscription,\n useStartSessionMutation,\n useStopSessionMutation,\n useRestartSessionMutation,\n useCreateSessionMutation,\n useStartSessionTerminalMutation,\n useStopSessionTerminalMutation,\n useRestartSessionTerminalMutation,\n useUpdateSessionMutation,\n useTerminateSessionMutation,\n useCheckSessionHealthMutation,\n useSessionStatusChangedSubscription,\n useBulkStopSessionsMutation,\n useBulkRestartSessionsMutation,\n useBulkTerminateSessionsMutation,\n useAgentSessionOrphansLazyQuery,\n useAdoptAgentSessionMutation,\n useExportSessionsMutation,\n useListAgentProfilesQuery,\n} from '@/generated/wspace-operations';\nimport { PermissionButton, AccessDenied } from '@burdenoff/fe-libs/shared/components';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { SaveToFilesButton } from '@burdenoff/fe-libs/storage';\nimport { useSearchParams } from 'react-router-dom';\nimport { VIBE_PERMISSIONS } from '@/constants/permissions';\nimport {\n getQuotaDialogContent,\n markQuotaHandledLocally,\n isQuotaExhaustedError,\n} from '@/utils/quotaUtils';\n\ntype ViewMode = 'terminal' | 'grid' | 'list';\n\n// Long-tail health-check fallback. The primary path is the\n// SessionStatusChanged subscription (sub-second updates). This poll only\n// fires if the subscription is dropped; 5m is fine because the server\n// also runs a throttled background health check on every ListSessions\n// query.\nconst HEALTH_CHECK_INTERVAL_MS = 300_000;\n\nfunction normalizeSessionTerminalUrl(\n terminalUrl: string | null | undefined,\n tunnelUrl: string | null | undefined\n): string | null {\n if (!terminalUrl) return null;\n\n try {\n const parsedTerminalUrl = new URL(terminalUrl);\n const blockedHosts = new Set(['localhost', '127.0.0.1', 'host.docker.internal']);\n\n if (tunnelUrl && blockedHosts.has(parsedTerminalUrl.hostname)) {\n const parsedTunnelUrl = new URL(tunnelUrl);\n parsedTunnelUrl.pathname = parsedTerminalUrl.pathname;\n parsedTunnelUrl.search = parsedTerminalUrl.search;\n parsedTunnelUrl.hash = parsedTerminalUrl.hash;\n return parsedTunnelUrl.toString();\n }\n\n return parsedTerminalUrl.toString();\n } catch {\n return terminalUrl;\n }\n}\n\nfunction deriveBrowserSessionStatus(\n status: Session['status'],\n rawTerminalUrl: string | null | undefined,\n normalizedTerminalUrl: string | null,\n agentIsActive?: boolean\n): Session['status'] {\n if (status !== 'RUNNING') return status;\n if (agentIsActive === false) return 'ERROR';\n if (!rawTerminalUrl) return status;\n if (!normalizedTerminalUrl) return 'ERROR';\n\n try {\n const pageProtocol = typeof window !== 'undefined' ? window.location.protocol : 'https:';\n const terminalProtocol = new URL(normalizedTerminalUrl).protocol;\n if (pageProtocol === 'https:' && terminalProtocol !== 'https:') {\n return 'ERROR';\n }\n } catch {\n return 'ERROR';\n }\n\n return status;\n}\n\n/**\n * Sessions management page with terminal-first UI.\n * Persists tab state, supports bulk operations, keyboard shortcuts, and health polling.\n */\nexport function SessionsPage() {\n const { t } = useI18n();\n const { onSessionStart, workspaceId } = useVibeControls();\n const navigate = useVibeNavigate();\n const { hasPermission, isLoading: permissionsLoading, permissions } = usePermissions();\n const [searchParams, setSearchParams] = useSearchParams();\n\n const tr = useCallback(\n (key: string, fallback: string, params?: Record<string, string | number>): string => {\n const translated = t(key, params);\n const template = translated === key ? fallback : translated;\n\n if (!params) return template;\n\n return Object.entries(params).reduce(\n (acc, [paramKey, paramValue]) => acc.replace(`{{${paramKey}}}`, String(paramValue)),\n template\n );\n },\n [t]\n );\n\n const statusOptions = useMemo(\n () => [\n { value: 'all', label: tr('sessions.statusAll', 'All Statuses') },\n { value: 'RUNNING', label: tr('sessions.running', 'Running') },\n { value: 'STOPPED', label: tr('sessions.stopped', 'Stopped') },\n { value: 'STARTING', label: tr('sessions.starting', 'Starting') },\n { value: 'ERROR', label: tr('common.error', 'Error') },\n ],\n [tr]\n );\n\n const typeOptions = useMemo(\n () => [\n { value: 'all', label: tr('sessions.typeAll', 'All Types') },\n { value: 'TMUX', label: tr('sessions.typeTmux', 'Tmux') },\n { value: 'TERMINAL', label: tr('sessions.typeTerminal', 'Terminal') },\n { value: 'SCRIPT', label: tr('sessions.typeScript', 'Script') },\n { value: 'CUSTOM', label: tr('sessions.typeCustom', 'Custom') },\n ],\n [tr]\n );\n\n // Check permissions\n const canListSessions = hasPermission(VIBE_PERMISSIONS.SESSION_LIST);\n const canCreateSession = hasPermission(VIBE_PERMISSIONS.SESSION_CREATE);\n\n const [searchQuery, setSearchQuery] = useState('');\n const [statusFilter, setStatusFilter] = useState('all');\n const [typeFilter, setTypeFilter] = useState('all');\n const [vibeFilter, setVibeFilter] = useState('all');\n const [agentFilter, setAgentFilter] = useState('all');\n const [viewMode, setViewMode] = useState<ViewMode>('terminal');\n const [showCreateForm, setShowCreateForm] = useState(false);\n const [showQuotaExhausted, setShowQuotaExhausted] = useState(false);\n const [createError, setCreateError] = useState<string | null>(null);\n const [createLoading, setCreateLoading] = useState(false);\n const [successMessage, setSuccessMessage] = useState<string | null>(null);\n\n // Bulk selection state\n const [selectedSessionIds, setSelectedSessionIds] = useState<Set<string>>(new Set());\n const [bulkLoading, setBulkLoading] = useState(false);\n const [isExporting, setIsExporting] = useState(false);\n\n // Form refs\n const sessionNameRef = useRef<HTMLInputElement>(null);\n const sessionCommandRef = useRef<HTMLInputElement>(null);\n const sessionAutoStartRef = useRef<HTMLInputElement>(null);\n // Advanced: provider-native session name to attach to (tmux/wezterm/zellij).\n // When set on submit, the agent reattaches to an existing daemon-side\n // session instead of spawning a fresh one. Empty = normal create.\n const sessionExternalNameRef = useRef<HTMLInputElement>(null);\n\n // Controlled form state for searchable selects\n const [formVibeId, setFormVibeId] = useState('');\n const [formAgentId, setFormAgentId] = useState('');\n const [formProfile, setFormProfile] = useState('default');\n const [formType, setFormType] = useState('TMUX');\n const [formShowAdvanced, setFormShowAdvanced] = useState(false);\n\n // Terminal panel state - use store for persistence across navigation\n const {\n terminalTabs: persistedTabs,\n activeTerminalTabId: persistedActiveTab,\n sessionsViewMode: persistedViewMode,\n setTerminalTabs: setPersistedTabs,\n setActiveTerminalTabId: setPersistedActiveTab,\n setSessionsViewMode: setPersistedViewMode,\n activeSessionId,\n } = useVibeControlsStore();\n\n // Local terminal tabs state (enriched with loading/error/url)\n const [terminalTabs, setTerminalTabs] = useState<TerminalTab[]>([]);\n const [activeTerminalTab, setActiveTerminalTab] = useState<string | null>(null);\n // F4 round-3: keyboard cheatsheet + command palette modals\n const [showCheatsheet, setShowCheatsheet] = useState(false);\n const [showCommandPalette, setShowCommandPalette] = useState(false);\n const [paletteQuery, setPaletteQuery] = useState('');\n const [terminalCollapsed, setTerminalCollapsed] = useState(false);\n\n // Share dialog state\n const [shareSession, setShareSession] = useState<Session | null>(null);\n\n // Immersive fullscreen state (terminal takes over the entire screen)\n const [isImmersiveFullscreen, setIsImmersiveFullscreen] = useState(false);\n\n // UI state persistence flag — true once we've loaded from store\n const [uiStateLoaded, setUiStateLoaded] = useState(false);\n\n const queryViewMode = searchParams.get('view');\n const querySessionId = searchParams.get('session');\n\n // Mutations\n const [createSession] = useCreateSessionMutation({ refetchQueries: ['ListSessions'] });\n const { setDraftTagIds, flushTags } = useDraftEntityTags('VIBECONTROLS_SESSION');\n\n // Reconnect (orphan adoption) modal state. Independent from the\n // New Session form so the user can reconnect without prefilling the\n // create form. Opened via the \"Reconnect\" header button.\n const [showReconnectModal, setShowReconnectModal] = useState(false);\n const [reconnectAgentId, setReconnectAgentId] = useState<string>('');\n const [reconnectError, setReconnectError] = useState<string | null>(null);\n const [reconnectAdoptingName, setReconnectAdoptingName] = useState<string | null>(null);\n const [discoverOrphans, { data: orphansData, loading: orphansLoading }] =\n useAgentSessionOrphansLazyQuery({ fetchPolicy: 'network-only' });\n const [adoptAgentSession] = useAdoptAgentSessionMutation({\n refetchQueries: ['ListSessions'],\n });\n const [startSession] = useStartSessionMutation({ refetchQueries: ['ListSessions'] });\n const [stopSession] = useStopSessionMutation({ refetchQueries: ['ListSessions'] });\n const [restartSession] = useRestartSessionMutation({ refetchQueries: ['ListSessions'] });\n const [startSessionTerminal] = useStartSessionTerminalMutation({\n refetchQueries: ['ListSessions'],\n });\n const [stopSessionTerminal] = useStopSessionTerminalMutation({\n refetchQueries: ['ListSessions'],\n });\n const [restartSessionTerminal] = useRestartSessionTerminalMutation({\n refetchQueries: ['ListSessions'],\n });\n const [updateSession] = useUpdateSessionMutation({ refetchQueries: ['ListSessions'] });\n const [terminateSession] = useTerminateSessionMutation({ refetchQueries: ['ListSessions'] });\n const [checkSessionHealth] = useCheckSessionHealthMutation({ refetchQueries: ['ListSessions'] });\n const [bulkStopSessions] = useBulkStopSessionsMutation({ refetchQueries: ['ListSessions'] });\n const [bulkRestartSessions] = useBulkRestartSessionsMutation({\n refetchQueries: ['ListSessions'],\n });\n const [bulkTerminateSessions] = useBulkTerminateSessionsMutation({\n refetchQueries: ['ListSessions'],\n });\n const [exportSessions] = useExportSessionsMutation();\n\n // ── UI State Persistence (localStorage via Zustand store) ─────────────\n // Load persisted UI state from store on mount\n useEffect(() => {\n if (uiStateLoaded) return;\n\n // Query params take precedence over persisted UI state so session tabs are bookmarkable.\n if (queryViewMode === 'terminal' || queryViewMode === 'grid' || queryViewMode === 'list') {\n setViewMode(queryViewMode);\n } else if (persistedViewMode) {\n setViewMode(persistedViewMode);\n }\n\n if (querySessionId) {\n setActiveTerminalTab(querySessionId);\n } else if (persistedActiveTab) {\n setActiveTerminalTab(persistedActiveTab);\n }\n\n // Restore tabs from store — they'll need to re-fetch terminal URLs\n if (persistedTabs.length > 0) {\n const restoredTabs: TerminalTab[] = persistedTabs.map((t) => ({\n sessionId: t.sessionId,\n name: t.name,\n terminalUrl: null, // Will be populated when sessions load\n loading: false,\n error: null,\n }));\n setTerminalTabs(restoredTabs);\n }\n\n setUiStateLoaded(true);\n }, [\n persistedTabs,\n persistedActiveTab,\n persistedViewMode,\n querySessionId,\n queryViewMode,\n uiStateLoaded,\n ]);\n\n // Once sessions are loaded and we have restored tabs, populate terminal URLs\n const sessionsLoadedRef = useRef(false);\n\n // Sync local state changes to store for persistence\n useEffect(() => {\n if (!uiStateLoaded) return;\n // Sync terminal tabs to store (only sessionId and name for persistence)\n const tabsToStore = terminalTabs.map((t) => ({ sessionId: t.sessionId, name: t.name }));\n setPersistedTabs(tabsToStore);\n }, [terminalTabs, uiStateLoaded, setPersistedTabs]);\n\n useEffect(() => {\n if (!uiStateLoaded) return;\n setPersistedActiveTab(activeTerminalTab);\n }, [activeTerminalTab, uiStateLoaded, setPersistedActiveTab]);\n\n useEffect(() => {\n if (!uiStateLoaded) return;\n setPersistedViewMode(viewMode);\n }, [viewMode, uiStateLoaded, setPersistedViewMode]);\n\n useEffect(() => {\n if (!uiStateLoaded) return;\n\n const next = new URLSearchParams(searchParams);\n let changed = false;\n\n const nextView = viewMode === 'terminal' ? null : viewMode;\n if (nextView) {\n if (next.get('view') !== nextView) {\n next.set('view', nextView);\n changed = true;\n }\n } else if (next.has('view')) {\n next.delete('view');\n changed = true;\n }\n\n if (activeTerminalTab) {\n if (next.get('session') !== activeTerminalTab) {\n next.set('session', activeTerminalTab);\n changed = true;\n }\n } else if (next.has('session')) {\n next.delete('session');\n changed = true;\n }\n\n if (changed) {\n setSearchParams(next, { replace: true });\n }\n }, [activeTerminalTab, searchParams, setSearchParams, uiStateLoaded, viewMode]);\n\n // Track previous activeSessionId to detect changes from the context switcher\n const prevActiveSessionIdRef = useRef<string | null>(null);\n\n // ── Live session-status subscription ─────────────────────────────────\n // Replaces the prior `setInterval(checkSessionHealth, …)` poll. The\n // backend already runs a throttled background health check on every\n // ListSessions query (see SessionService.triggerBackgroundHealthCheck);\n // the subscription propagates resulting status flips into Apollo's\n // normalized cache by id, so any rendered session row updates without\n // a manual refetch.\n //\n // workspaceId comes from VibeControlsProvider; skipping until it\n // resolves avoids a \"Workspace context required\" error during initial\n // mount before the auth token is issued.\n useSessionStatusChangedSubscription({\n skip: !workspaceId,\n variables: { workspaceId: workspaceId || '' },\n });\n\n // Long-tail safety fallback: in case the subscription drops (network\n // blip, agent restart) and a status flip is missed, re-trigger the\n // server-side health check every 5 minutes. Server-side throttling\n // (30s) protects against thundering-herd if multiple tabs are open.\n useEffect(() => {\n const interval = setInterval(() => {\n checkSessionHealth().catch(() => {\n /* silent — fallback path */\n });\n }, HEALTH_CHECK_INTERVAL_MS);\n return () => clearInterval(interval);\n }, [checkSessionHealth]);\n\n // ── Keyboard Shortcuts for Terminal Tabs ────────────────────────────\n useEffect(() => {\n const handleKeyDown = (e: KeyboardEvent) => {\n // Don't intercept when typing in inputs/textareas\n const target = e.target as HTMLElement;\n if (\n target.tagName === 'INPUT' ||\n target.tagName === 'TEXTAREA' ||\n target.tagName === 'SELECT' ||\n target.isContentEditable\n ) {\n return;\n }\n\n const isMod = e.metaKey || e.ctrlKey;\n\n // Ctrl/Cmd+T — New terminal tab (open session picker)\n if (isMod && e.key === 't') {\n e.preventDefault();\n setShowCreateForm(true);\n return;\n }\n\n // Ctrl/Cmd+W — Close active terminal tab\n if (isMod && e.key === 'w' && activeTerminalTab) {\n e.preventDefault();\n const sessionIdToClose = activeTerminalTab;\n setTerminalTabs((prev) => prev.filter((t) => t.sessionId !== sessionIdToClose));\n setActiveTerminalTab(() => {\n const remaining = terminalTabs.filter((t) => t.sessionId !== sessionIdToClose);\n return remaining.length > 0 ? remaining[remaining.length - 1].sessionId : null;\n });\n return;\n }\n\n // Ctrl+Tab — Next terminal tab\n if (e.ctrlKey && e.key === 'Tab' && !e.shiftKey && terminalTabs.length > 1) {\n e.preventDefault();\n const currentIdx = terminalTabs.findIndex((t) => t.sessionId === activeTerminalTab);\n const nextIdx = (currentIdx + 1) % terminalTabs.length;\n setActiveTerminalTab(terminalTabs[nextIdx].sessionId);\n return;\n }\n\n // Ctrl+Shift+Tab — Previous terminal tab\n if (e.ctrlKey && e.key === 'Tab' && e.shiftKey && terminalTabs.length > 1) {\n e.preventDefault();\n const currentIdx = terminalTabs.findIndex((t) => t.sessionId === activeTerminalTab);\n const prevIdx = (currentIdx - 1 + terminalTabs.length) % terminalTabs.length;\n setActiveTerminalTab(terminalTabs[prevIdx].sessionId);\n return;\n }\n\n // F4 round-3: Cmd/Ctrl + 1..9 — Jump to tab N (1-indexed)\n if (isMod && /^[1-9]$/.test(e.key) && terminalTabs.length > 0) {\n const idx = Number(e.key) - 1;\n if (idx < terminalTabs.length) {\n e.preventDefault();\n setActiveTerminalTab(terminalTabs[idx].sessionId);\n }\n return;\n }\n\n // F4 round-3: Cmd/Ctrl + P — Open command palette\n if (isMod && (e.key === 'p' || e.key === 'P')) {\n e.preventDefault();\n setPaletteQuery('');\n setShowCommandPalette(true);\n return;\n }\n\n // F4 round-3: ? (no modifier) — Open keyboard cheatsheet\n if (!isMod && !e.altKey && !e.shiftKey && e.key === '?') {\n e.preventDefault();\n setShowCheatsheet(true);\n return;\n }\n };\n\n document.addEventListener('keydown', handleKeyDown);\n return () => document.removeEventListener('keydown', handleKeyDown);\n }, [activeTerminalTab, terminalTabs]);\n\n // Fetch sessions from GraphQL API (with filters)\n // Use previousData to prevent flash of loading state during refetches\n // When no filters narrow the result set, the query variables match\n // the InitialData prefetch — use cache-first to skip the redundant\n // network hit. With filters applied, cache-and-network keeps stale\n // narrowed results from being shown silently.\n const noSessionFilters =\n statusFilter === 'all' && typeFilter === 'all' && vibeFilter === 'all' && agentFilter === 'all';\n const sessionsFetchPolicy = noSessionFilters ? 'cache-first' : 'cache-and-network';\n const { data, previousData, loading, error, refetch } = useListSessionsQuery({\n variables: {\n filter: {\n status:\n statusFilter !== 'all'\n ? (statusFilter as 'RUNNING' | 'STOPPED' | 'STARTING' | 'ERROR')\n : undefined,\n type:\n typeFilter !== 'all'\n ? (typeFilter as 'TMUX' | 'TERMINAL' | 'SSH' | 'SCRIPT' | 'CUSTOM')\n : undefined,\n vibeId: vibeFilter !== 'all' && vibeFilter !== 'none' ? vibeFilter : undefined,\n agentId: agentFilter !== 'all' ? agentFilter : undefined,\n },\n pagination: { limit: 50 },\n },\n fetchPolicy: sessionsFetchPolicy,\n });\n\n // Use current data or fall back to previous data during refetches\n // This prevents the \"Loading sessions...\" flash when refetching\n const effectiveData = data ?? previousData;\n\n // Use cached initial data for dropdowns (agents and vibes)\n // This reads from Apollo cache - no network request\n const { agents: cachedAgents, vibes: cachedVibes } = useCachedInitialData();\n\n // Live deep-health probe per agent (every 30s). Used to gate the\n // \"Create session\" button — without this, the button stays enabled\n // against an agent whose cloudflared tunnel has died, and clicking\n // it produces the user-visible \"Failed to start terminal\" error\n // long after the session row is already created in PENDING state.\n // Initial query primes the cache; the subscription below replaces\n // the prior 30s pollInterval so health flips reach this page in <1s.\n // Probe query is lazy — the subscription below is the source of truth\n // for initial paint, so we only fire the query on user-triggered\n // refresh (see refetch handler below).\n const [refetchProbes, agentHealthQuery] = useVibecontrolsAgentHealthProbesLazyQuery({\n fetchPolicy: 'network-only',\n notifyOnNetworkStatusChange: true,\n });\n const { data: agentHealthStreamData } = useVibecontrolsAgentHealthProbeStreamSubscription();\n const agentHealthByIdRef = useMemo(() => {\n const m = new Map<string, { healthy: boolean; error: string | null }>();\n const probes =\n agentHealthStreamData?.vibecontrolsAgentHealthProbeStream ??\n agentHealthQuery.data?.vibecontrolsAgentHealthProbes ??\n [];\n for (const p of probes) {\n m.set(p.agentId, { healthy: p.healthy, error: p.errorMessage ?? null });\n }\n return m;\n }, [agentHealthQuery.data, agentHealthStreamData]);\n\n // Build vibe options for filter using cached data\n const vibeOptions = useMemo(() => {\n return [\n { value: 'all', label: tr('sessions.filterVibeAll', 'All Vibes') },\n { value: 'none', label: tr('sessions.filterVibeNone', 'No Vibe') },\n ...cachedVibes.map((vibe) => ({\n value: vibe.id,\n label: vibe.name,\n })),\n ];\n }, [cachedVibes, tr]);\n\n // Build agent options for filter using cached data\n const agentOptions = useMemo(() => {\n return [\n { value: 'all', label: tr('sessions.filterAgentAll', 'All Agents') },\n ...cachedAgents.map((agent) => ({\n value: agent.id,\n label: `${agent.name} ${getAgentStatus(agent) === 'ACTIVE' ? '' : `(${tr('sessions.offline', 'Offline')})`}`,\n })),\n ];\n }, [cachedAgents, tr]);\n\n // Build options for create form using cached data (no \"all\"/\"none\" entries)\n const createVibeOptions = useMemo(() => {\n return [\n { value: '', label: tr('sessions.noVibe', 'No vibe') },\n ...cachedVibes.map((vibe) => ({\n value: vibe.id,\n label: vibe.name,\n })),\n ];\n }, [cachedVibes, tr]);\n\n const createAgentOptions = useMemo(() => {\n return [\n { value: '', label: tr('sessions.selectAgent', 'Select an agent') },\n ...cachedAgents.map((agent) => {\n const status = getAgentStatus(agent);\n const label =\n status === 'ACTIVE' ? tr('sessions.active', 'Active') : tr('sessions.offline', 'Offline');\n return {\n value: agent.id,\n label: `${agent.name} (${label})`,\n };\n }),\n ];\n }, [cachedAgents, tr]);\n\n // Pull the agent's active profiles so the create-form dropdown shows\n // the real options instead of a hardcoded \"default\". Skips when no\n // agent is picked yet. Cache-and-network so the list updates after\n // the operator creates / deletes a profile elsewhere in the session.\n const { data: agentProfilesData } = useListAgentProfilesQuery({\n variables: { agentId: formAgentId },\n skip: !formAgentId,\n fetchPolicy: 'cache-and-network',\n });\n const profileOptions = useMemo(() => {\n const list = agentProfilesData?.vibecontrolsAgentProfiles ?? [];\n const fromAgent = list.map((p: { name: string; status?: string | null }) => ({\n value: p.name,\n label: p.status && p.status !== 'ACTIVE' ? `${p.name} (${p.status})` : p.name,\n }));\n // Ensure \"default\" is always a selectable option even if the\n // profile-discovery query is still loading or the agent has been\n // freshly registered. The agent-side bare-path fallback routes\n // bare /api/* → /api/profiles/default/* so this is always safe.\n const hasDefault = fromAgent.some((o: { value: string }) => o.value === 'default');\n return hasDefault ? fromAgent : [{ value: 'default', label: 'default' }, ...fromAgent];\n }, [agentProfilesData]);\n\n // Reset profile selection when the operator picks a different agent\n // so we don't carry over a profile name that doesn't exist on the\n // newly-selected agent.\n useEffect(() => {\n setFormProfile('default');\n }, [formAgentId]);\n\n const sessionTypeOptions = useMemo(\n () => [\n { value: 'TMUX', label: tr('sessions.typeTmuxLower', 'tmux') },\n { value: 'WEZTERM', label: tr('sessions.typeWezTerm', 'WezTerm') },\n { value: 'ZELLIJ', label: tr('sessions.typeZellij', 'Zellij') },\n { value: 'TERMINAL', label: tr('sessions.typeTerminal', 'Terminal') },\n { value: 'SSH', label: tr('sessions.typeSsh', 'SSH') },\n { value: 'SCRIPT', label: tr('sessions.typeScript', 'Script') },\n { value: 'CUSTOM', label: tr('sessions.typeCustom', 'Custom') },\n ],\n [tr]\n );\n\n // Map GraphQL response to Session type\n // Use effectiveData to prevent flash during refetches\n const sessions: Session[] = useMemo(() => {\n const rawSessions = effectiveData?.sessions?.items ?? [];\n return rawSessions.map((session: NonNullable<typeof rawSessions>[number]) => {\n const normalizedTtydUrl = normalizeSessionTerminalUrl(\n session.ttydUrl || null,\n session.agent?.tunnelUrl || null\n );\n\n return {\n id: session.id,\n workspaceId: session.workspaceId,\n vibeId: session.vibeId || null,\n agentId: session.agentId || null,\n profile: session.profile ?? 'default',\n name: session.name,\n type: session.type as Session['type'],\n status: deriveBrowserSessionStatus(\n session.status as Session['status'],\n session.ttydUrl || null,\n normalizedTtydUrl,\n // Use the shared derivation, not the sticky isActive flag, so a\n // stale-swept agent that's already heartbeating again doesn't\n // poison the row's derived status.\n session.agent ? getAgentStatus(session.agent) === 'ACTIVE' : undefined\n ),\n command: session.command || null,\n workingDirectory: session.workingDirectory || null,\n environmentVariables: (session.environmentVariables as Record<string, string>) || {},\n autoStart: session.autoStart,\n providerSessionName: session.providerSessionName || undefined,\n providerWindowName: session.providerWindowId || undefined,\n pid: session.pid || null,\n exitCode: session.exitCode || null,\n lastOutput: session.lastOutput || null,\n ttydUrl: normalizedTtydUrl,\n metadata: (session.metadata as Record<string, unknown>) || {},\n createdAt: session.createdAt,\n updatedAt: session.updatedAt,\n vibe: session.vibe ? { id: session.vibe.id, name: session.vibe.name } : undefined,\n agent: session.agent\n ? {\n id: session.agent.id,\n name: session.agent.name,\n tunnelUrl: session.agent.tunnelUrl || null,\n isActive: session.agent.isActive,\n }\n : undefined,\n };\n });\n }, [effectiveData?.sessions?.items]);\n\n // Populate terminal URLs for restored tabs once sessions load\n useEffect(() => {\n if (sessionsLoadedRef.current || sessions.length === 0 || terminalTabs.length === 0) return;\n sessionsLoadedRef.current = true;\n\n // Re-spawn ttyd for every restored tab. The persisted ttydUrl from\n // the session row is stale on remount: the agent's ttyd process\n // dies on agent restart / idle GC, and the cached URL surfaces as\n // \"Terminal not running for this session\" when the iframe loads.\n // startSessionTerminal is idempotent server-side (re-uses an\n // existing ttyd if it's still running, spawns a fresh one if not),\n // so this is safe to call unconditionally on tab restore. The\n // iframe url returned in the mutation response is the source of\n // truth — we no longer trust the persisted ttydUrl.\n void Promise.allSettled(\n terminalTabs.map(async (tab) => {\n const session = sessions.find((s) => s.id === tab.sessionId);\n if (!session) return;\n try {\n const result = await startSessionTerminal({ variables: { id: tab.sessionId } });\n const tunnelHint = session.agent?.tunnelUrl ?? null;\n const newUrl = normalizeSessionTerminalUrl(\n result.data?.startSessionTerminal?.ttydUrl ?? null,\n tunnelHint\n );\n setTerminalTabs((prev) =>\n prev.map((t) =>\n t.sessionId === tab.sessionId\n ? {\n ...t,\n terminalUrl: newUrl,\n agentId: session.agent?.id ?? t.agentId ?? null,\n loading: false,\n error: null,\n }\n : t\n )\n );\n } catch (err) {\n // Don't blow up the tab — leave it loading=false with the\n // cached ttydUrl as a best-effort, the user can hit\n // \"Restart terminal\" if the iframe still 404s.\n // eslint-disable-next-line no-console\n console.warn('[sessions] tab restore startSessionTerminal failed', err);\n setTerminalTabs((prev) =>\n prev.map((t) =>\n t.sessionId === tab.sessionId && session.ttydUrl\n ? {\n ...t,\n terminalUrl: session.ttydUrl,\n agentId: session.agent?.id ?? t.agentId ?? null,\n loading: false,\n }\n : t\n )\n );\n }\n })\n );\n }, [sessions, terminalTabs, startSessionTerminal]);\n\n // ── Session Switcher Integration ─────────────────────────────────────\n // When activeSessionId changes, open/switch to that terminal\n useEffect(() => {\n // Skip if not initialized or no session selected\n if (!uiStateLoaded || !activeSessionId) {\n prevActiveSessionIdRef.current = activeSessionId;\n return;\n }\n\n // Skip if this is the initial load (not a user-initiated change)\n if (prevActiveSessionIdRef.current === null && activeSessionId) {\n prevActiveSessionIdRef.current = activeSessionId;\n return;\n }\n\n // Skip if the session hasn't actually changed\n if (prevActiveSessionIdRef.current === activeSessionId) {\n return;\n }\n\n prevActiveSessionIdRef.current = activeSessionId;\n\n // Find the session in the sessions list\n const session = sessions.find((s) => s.id === activeSessionId);\n if (!session) return;\n\n // Check if this session already has an open tab\n const existingTab = terminalTabs.find((t) => t.sessionId === activeSessionId);\n\n if (existingTab) {\n // Tab already exists - just switch to it\n setActiveTerminalTab(activeSessionId);\n setTerminalCollapsed(false);\n } else {\n // Need to open a new tab for this session\n const url = session.ttydUrl || null;\n const newTab: TerminalTab = {\n sessionId: session.id,\n name: session.name,\n terminalUrl: url,\n agentId: session.agent?.id ?? null,\n profile: session.profile ?? 'default',\n loading: !url,\n error: null,\n };\n setTerminalTabs((prev) => [...prev, newTab]);\n setActiveTerminalTab(activeSessionId);\n setTerminalCollapsed(false);\n\n // If no URL available, start the terminal\n if (!url) {\n startSessionTerminal({ variables: { id: session.id } })\n .then((result) => {\n const newUrl = result.data?.startSessionTerminal?.ttydUrl || null;\n setTerminalTabs((prev) =>\n prev.map((t) =>\n t.sessionId === session.id\n ? {\n ...t,\n terminalUrl: newUrl,\n loading: false,\n error: newUrl\n ? null\n : tr(\n 'sessions.terminalStartedNoUrl',\n 'Terminal started but no URL was returned. Try restarting.'\n ),\n }\n : t\n )\n );\n })\n .catch((err) => {\n const message =\n err instanceof Error\n ? err.message\n : tr('sessions.failedStartTerminal', 'Failed to start terminal');\n setTerminalTabs((prev) =>\n prev.map((t) =>\n t.sessionId === session.id ? { ...t, loading: false, error: message } : t\n )\n );\n });\n }\n }\n }, [activeSessionId, sessions, terminalTabs, uiStateLoaded, startSessionTerminal]);\n\n // Filter sessions\n const filteredSessions = useMemo(() => {\n return sessions.filter((session) => {\n if (searchQuery) {\n const query = searchQuery.toLowerCase();\n const matchesSearch =\n session.name.toLowerCase().includes(query) ||\n session.command?.toLowerCase().includes(query) ||\n session.workingDirectory?.toLowerCase().includes(query);\n if (!matchesSearch) return false;\n }\n if (vibeFilter === 'none' && session.vibeId) return false;\n return true;\n });\n }, [sessions, searchQuery, vibeFilter]);\n\n // ── Bulk Selection ───────────────────────────────────────────────────\n const toggleSessionSelection = useCallback((sessionId: string) => {\n setSelectedSessionIds((prev) => {\n const next = new Set(prev);\n if (next.has(sessionId)) {\n next.delete(sessionId);\n } else {\n next.add(sessionId);\n }\n return next;\n });\n }, []);\n\n const selectAllSessions = useCallback(() => {\n setSelectedSessionIds(new Set(filteredSessions.map((s) => s.id)));\n }, [filteredSessions]);\n\n const deselectAllSessions = useCallback(() => {\n setSelectedSessionIds(new Set());\n }, []);\n\n const isAllSelected =\n filteredSessions.length > 0 && selectedSessionIds.size === filteredSessions.length;\n const hasSelection = selectedSessionIds.size > 0;\n\n // ── Bulk Actions ─────────────────────────────────────────────────────\n const handleBulkStop = useCallback(async () => {\n if (selectedSessionIds.size === 0) return;\n setBulkLoading(true);\n try {\n const result = await bulkStopSessions({ variables: { ids: Array.from(selectedSessionIds) } });\n const data = result.data?.bulkStopSessions;\n setSuccessMessage(\n `${tr('sessions.stopped', 'Stopped')} ${data?.succeeded ?? 0} ${tr('nav.sessions', 'sessions')}${\n data?.failed ? `, ${data.failed} ${tr('sessions.failed', 'failed')}` : ''\n }`\n );\n setSelectedSessionIds(new Set());\n setTimeout(() => setSuccessMessage(null), 3000);\n } catch (err) {\n setSuccessMessage(\n `${tr('sessions.errorPrefix', 'Error')}: ${\n err instanceof Error ? err.message : tr('sessions.failedStop', 'Failed to stop sessions')\n }`\n );\n setTimeout(() => setSuccessMessage(null), 5000);\n } finally {\n setBulkLoading(false);\n }\n }, [selectedSessionIds, bulkStopSessions, tr]);\n\n const handleBulkRestart = useCallback(async () => {\n if (selectedSessionIds.size === 0) return;\n setBulkLoading(true);\n try {\n const result = await bulkRestartSessions({\n variables: { ids: Array.from(selectedSessionIds) },\n });\n const data = result.data?.bulkRestartSessions;\n setSuccessMessage(\n `${tr('sessions.restarted', 'Restarted')} ${data?.succeeded ?? 0} ${tr('nav.sessions', 'sessions')}${\n data?.failed ? `, ${data.failed} ${tr('sessions.failed', 'failed')}` : ''\n }`\n );\n setSelectedSessionIds(new Set());\n setTimeout(() => setSuccessMessage(null), 3000);\n } catch (err) {\n setSuccessMessage(\n `${tr('sessions.errorPrefix', 'Error')}: ${\n err instanceof Error\n ? err.message\n : tr('sessions.failedRestart', 'Failed to restart sessions')\n }`\n );\n setTimeout(() => setSuccessMessage(null), 5000);\n } finally {\n setBulkLoading(false);\n }\n }, [selectedSessionIds, bulkRestartSessions, tr]);\n\n const handleBulkTerminate = useCallback(async () => {\n if (selectedSessionIds.size === 0) return;\n if (\n !confirm(\n `${tr('sessions.confirmRemovePrefix', 'Are you sure you want to remove')} ${selectedSessionIds.size} ${tr('nav.sessions', 'session(s)')}? ${tr('sessions.confirmRemoveSuffix', 'This cannot be undone.')}`\n )\n )\n return;\n setBulkLoading(true);\n try {\n const result = await bulkTerminateSessions({\n variables: { ids: Array.from(selectedSessionIds) },\n });\n const data = result.data?.bulkTerminateSessions;\n setSuccessMessage(\n `${tr('sessions.removed', 'Removed')} ${data?.succeeded ?? 0} ${tr('nav.sessions', 'sessions')}${\n data?.failed ? `, ${data.failed} ${tr('sessions.failed', 'failed')}` : ''\n }`\n );\n setSelectedSessionIds(new Set());\n setTimeout(() => setSuccessMessage(null), 3000);\n } catch (err) {\n setSuccessMessage(\n `${tr('sessions.errorPrefix', 'Error')}: ${\n err instanceof Error\n ? err.message\n : tr('sessions.failedRemove', 'Failed to remove sessions')\n }`\n );\n setTimeout(() => setSuccessMessage(null), 5000);\n } finally {\n setBulkLoading(false);\n }\n }, [selectedSessionIds, bulkTerminateSessions, tr]);\n\n const handleExportSessions = useCallback(\n async (format: 'CSV' | 'JSON' = 'CSV') => {\n setIsExporting(true);\n try {\n const result = await exportSessions({\n variables: {\n input: {\n filter: {\n status:\n statusFilter !== 'all'\n ? (statusFilter as 'RUNNING' | 'STOPPED' | 'STARTING' | 'ERROR')\n : undefined,\n type:\n typeFilter !== 'all'\n ? (typeFilter as 'TMUX' | 'TERMINAL' | 'SSH' | 'SCRIPT' | 'CUSTOM')\n : undefined,\n vibeId: vibeFilter !== 'all' && vibeFilter !== 'none' ? vibeFilter : undefined,\n agentId: agentFilter !== 'all' ? agentFilter : undefined,\n },\n format,\n },\n },\n });\n const exportData = result.data?.exportSessions;\n if (!exportData) return;\n\n const mimeType = format === 'CSV' ? 'text/csv' : 'application/json';\n const blob = new Blob([exportData.data], { type: mimeType });\n const url = URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.href = url;\n a.download = `sessions-export-${new Date().toISOString().slice(0, 10)}.${format.toLowerCase()}`;\n a.click();\n URL.revokeObjectURL(url);\n setSuccessMessage(\n tr('sessions.exportSuccess', 'Exported {{count}} sessions as {{format}}', {\n count: exportData.count,\n format,\n })\n );\n setTimeout(() => setSuccessMessage(null), 3000);\n } catch (err) {\n setSuccessMessage(\n `${tr('sessions.errorPrefix', 'Error')}: ${\n err instanceof Error\n ? err.message\n : tr('sessions.failedExport', 'Failed to export sessions')\n }`\n );\n setTimeout(() => setSuccessMessage(null), 5000);\n } finally {\n setIsExporting(false);\n }\n },\n [exportSessions, statusFilter, typeFilter, vibeFilter, agentFilter, tr]\n );\n\n // ── Single Session Actions ───────────────────────────────────────────\n const handleSessionSelect = (session: Session) => {\n navigate(`/sessions/${session.id}`);\n };\n\n const handleCreateSession = () => {\n setCreateError(null);\n setShowCreateForm(true);\n };\n\n const handleSubmitCreateSession = async () => {\n const name = sessionNameRef.current?.value?.trim();\n const vibeId = formVibeId;\n const selectedAgentId = formAgentId;\n const type = formType;\n const command = sessionCommandRef.current?.value?.trim();\n const autoStart = sessionAutoStartRef.current?.checked ?? false;\n\n if (!name) {\n setCreateError(tr('sessions.nameRequired', 'Name is required'));\n return;\n }\n\n // Multi-agent: a vibe has 0..N attached agents. Pick the user's selected\n // agent when set; otherwise auto-pick when the vibe has exactly one\n // attached agent; otherwise force an explicit choice.\n const selectedVibe = cachedVibes.find((v) => v.id === vibeId);\n const candidateAgents = selectedVibe?.agents ?? [];\n let agentId: string | null = null;\n if (selectedAgentId && (!vibeId || candidateAgents.some((a) => a.id === selectedAgentId))) {\n agentId = selectedAgentId;\n } else if (candidateAgents.length === 1) {\n agentId = candidateAgents[0]?.id ?? null;\n }\n if (!agentId) {\n const message =\n vibeId && candidateAgents.length === 0\n ? tr(\n 'sessions.noAgentAttached',\n 'This vibe has no agents attached. Attach one from the Vibe → Agents tab before creating sessions.'\n )\n : vibeId && candidateAgents.length > 1\n ? tr(\n 'sessions.pickAgentFromVibe',\n 'This vibe has multiple agents attached. Pick one before creating the session.'\n )\n : tr('sessions.agentRequired', 'Please select an agent for this session.');\n setCreateError(message);\n return;\n }\n\n // Block session create when the agent's live deep-health probe is\n // not green — without this gate, the create succeeds (PENDING row\n // gets written) but the subsequent terminal-start step fails with\n // \"Terminal started but no tunnel URL\". Surface the actual probe\n // error inline instead of letting the user pile up dead PENDING\n // sessions against an unreachable agent.\n const probeRow = agentHealthByIdRef.get(agentId);\n if (probeRow && !probeRow.healthy) {\n setCreateError(\n tr(\n 'sessions.agentNotHealthy',\n 'The selected agent isn\\u2019t fully reachable right now. Please wait for the agent badge to turn green and retry.'\n ) + (probeRow.error ? ` (${probeRow.error})` : '')\n );\n return;\n }\n\n setCreateLoading(true);\n setCreateError(null);\n try {\n const externalSessionName = sessionExternalNameRef.current?.value?.trim() || undefined;\n const result = await createSession({\n variables: {\n input: {\n name,\n type: (type || 'TMUX') as\n | 'TMUX'\n | 'WEZTERM'\n | 'ZELLIJ'\n | 'TERMINAL'\n | 'SSH'\n | 'SCRIPT'\n | 'CUSTOM',\n command: command || undefined,\n vibeId: vibeId || undefined,\n agentId,\n profile: formProfile?.trim() || 'default',\n autoStart,\n externalSessionName,\n },\n },\n });\n // The Apollo client used by this MFE is configured with\n // `errorPolicy: 'all'` for mutations (see VibeControlsProvider). That\n // makes the promise RESOLVE on GraphQL errors instead of throwing —\n // so the catch block below never fires for `code: QUOTA_EXHAUSTED`\n // from wspace-vibecontrols-svc. Without this guard the operator sees\n // \"Session created successfully\" + an empty terminal tab while the\n // server actually returned `data: null, errors: [...]`. Mirror the\n // pattern already used by ActionsPage / TargetsPage: detect the\n // quota signal on `result.error` before reading `result.data`.\n if (result.error && isQuotaExhaustedError(result.error)) {\n setShowCreateForm(false);\n markQuotaHandledLocally();\n setShowQuotaExhausted(true);\n return;\n }\n // Any other GraphQL error path: surface inline on the form so the\n // operator sees the actual failure instead of a misleading success.\n if (result.error) {\n setCreateError(\n result.error.message || tr('sessions.failedCreate', 'Failed to create session')\n );\n return;\n }\n const newSessionId = result.data?.createSession?.id;\n // No data despite no error is itself a failure — surface a generic\n // message rather than closing the form silently.\n if (!newSessionId) {\n setCreateError(tr('sessions.failedCreate', 'Failed to create session'));\n return;\n }\n setShowCreateForm(false);\n setSuccessMessage(tr('sessions.createdSuccess', 'Session created successfully'));\n await flushTags(newSessionId);\n setTimeout(() => setSuccessMessage(null), 3000);\n\n // Auto-open the new session as a terminal tab regardless of viewMode.\n // The terminal-first layout obviously needs it, but list/grid views\n // expect the same behaviour — the bottom terminal panel is shared\n // across all viewModes and the operator just asked us to spawn a\n // session. Previously this was gated on viewMode==='terminal', so\n // creating from list/grid view succeeded server-side but left the\n // operator looking at \"Session created successfully\" with no tab.\n if (result.data?.createSession) {\n const created = result.data.createSession;\n // Hydrate the agent reference from cachedAgents so TerminalIframe\n // has an agentId to mint TERMINAL_SESSION capability against. The\n // createSession mutation response is the flat SessionFields\n // fragment (no agent sub-object), so without this the tab opens\n // with agentId=null and the capability mint never fires.\n const ownedAgent = created.agentId\n ? cachedAgents.find((a) => a.id === created.agentId)\n : null;\n const newSession: Session = {\n id: created.id,\n workspaceId: created.workspaceId,\n vibeId: created.vibeId || null,\n agentId: created.agentId || null,\n profile: created.profile ?? 'default',\n name: created.name,\n type: created.type as Session['type'],\n status: created.status as Session['status'],\n command: created.command || null,\n workingDirectory: created.workingDirectory || null,\n environmentVariables: (created.environmentVariables as Record<string, string>) || {},\n autoStart: created.autoStart,\n providerSessionName: created.providerSessionName || undefined,\n providerWindowName: created.providerWindowId || undefined,\n pid: created.pid || null,\n exitCode: created.exitCode || null,\n lastOutput: created.lastOutput || null,\n ttydUrl: created.ttydUrl || null,\n metadata: (created.metadata as Record<string, unknown>) || {},\n createdAt: created.createdAt,\n updatedAt: created.updatedAt,\n agent: ownedAgent\n ? {\n id: ownedAgent.id,\n name: ownedAgent.name,\n tunnelUrl: ownedAgent.tunnelUrl || null,\n isActive: ownedAgent.isActive,\n }\n : undefined,\n };\n void handleOpenTerminal(newSession);\n }\n } catch (err) {\n if (isQuotaExhaustedError(err)) {\n setShowCreateForm(false);\n markQuotaHandledLocally();\n setShowQuotaExhausted(true);\n return;\n }\n\n setCreateError(\n err instanceof Error ? err.message : tr('sessions.failedCreate', 'Failed to create session')\n );\n } finally {\n setCreateLoading(false);\n }\n };\n\n const handleStartSession = async (session: Session) => {\n try {\n await startSession({ variables: { id: session.id } });\n onSessionStart?.({\n id: session.id,\n name: session.name,\n type: session.type,\n status: 'RUNNING',\n });\n } catch (err) {\n if (isQuotaExhaustedError(err)) {\n markQuotaHandledLocally();\n setShowQuotaExhausted(true);\n return;\n }\n\n console.error('Failed to start session:', err);\n }\n };\n\n const handleStopSession = async (session: Session) => {\n try {\n await stopSession({ variables: { id: session.id } });\n } catch (err) {\n console.error('Failed to stop session:', err);\n }\n };\n\n const handleRestartSession = async (session: Session) => {\n try {\n await restartSession({ variables: { id: session.id } });\n } catch (err) {\n if (isQuotaExhaustedError(err)) {\n markQuotaHandledLocally();\n setShowQuotaExhausted(true);\n return;\n }\n\n console.error('Failed to restart session:', err);\n }\n };\n\n const handleTerminateSession = async (session: Session) => {\n try {\n await terminateSession({ variables: { id: session.id } });\n } catch (err) {\n console.error('Failed to terminate session:', err);\n }\n };\n\n const getTerminalUrl = useCallback((session: Session): string | null => {\n return session.ttydUrl || null;\n }, []);\n\n // Share session handler\n const handleShareSession = useCallback((session: Session) => {\n setShareSession(session);\n }, []);\n\n const handleOpenTerminal = useCallback(\n async (session: Session) => {\n const existingTab = terminalTabs.find((t) => t.sessionId === session.id);\n if (existingTab?.terminalUrl) {\n setActiveTerminalTab(session.id);\n setTerminalCollapsed(false);\n return;\n }\n\n const url = getTerminalUrl(session);\n\n if (url) {\n const newTab: TerminalTab = {\n sessionId: session.id,\n name: session.name,\n terminalUrl: url,\n agentId: session.agent?.id ?? session.agentId ?? null,\n profile: session.profile ?? 'default',\n loading: false,\n error: null,\n };\n setTerminalTabs((prev) => {\n const filtered = prev.filter((t) => t.sessionId !== session.id);\n return [...filtered, newTab];\n });\n setActiveTerminalTab(session.id);\n setTerminalCollapsed(false);\n return;\n }\n\n const loadingTab: TerminalTab = {\n sessionId: session.id,\n name: session.name,\n terminalUrl: null,\n agentId: session.agent?.id ?? null,\n loading: true,\n error: null,\n };\n setTerminalTabs((prev) => {\n const filtered = prev.filter((t) => t.sessionId !== session.id);\n return [...filtered, loadingTab];\n });\n setActiveTerminalTab(session.id);\n setTerminalCollapsed(false);\n\n try {\n const result = await startSessionTerminal({ variables: { id: session.id } });\n if (result.error && isQuotaExhaustedError(result.error)) {\n const remainingTabs = terminalTabs.filter((t) => t.sessionId !== session.id);\n setTerminalTabs(remainingTabs);\n if (activeTerminalTab === session.id) {\n setActiveTerminalTab(\n remainingTabs.length > 0 ? remainingTabs[remainingTabs.length - 1].sessionId : null\n );\n }\n markQuotaHandledLocally();\n setShowQuotaExhausted(true);\n return;\n }\n\n // Surface the REAL backend reason (e.g. AGENT_NOT_READY — \"database not\n // opened\" / a finalize TLS failure) instead of falling through to the\n // misleading \"no tunnel URL\" message below, which only applies when the\n // mutation SUCCEEDED but returned an empty ttydUrl.\n if (result.error) {\n const reason =\n result.error.message || tr('sessions.failedStartTerminal', 'Failed to start terminal');\n setTerminalTabs((prev) =>\n prev.map((t) =>\n t.sessionId === session.id ? { ...t, loading: false, error: reason } : t\n )\n );\n return;\n }\n\n // Apply the same browser-host normalization we use for list rows\n // so a `host.docker.internal`/`localhost` URL falls back to the\n // agent's known tunnel host where possible. The mutation response\n // doesn't fetch the agent inline (SessionFields fragment is flat),\n // so we look up the tunnelUrl from the already-loaded session row.\n const rawUrl = result.data?.startSessionTerminal?.ttydUrl || null;\n const sessionRow = sessions.find((s) => s.id === session.id);\n const tunnelHint = sessionRow?.agent?.tunnelUrl ?? null;\n const newUrl = normalizeSessionTerminalUrl(rawUrl, tunnelHint);\n setTerminalTabs((prev) =>\n prev.map((t) =>\n t.sessionId === session.id\n ? {\n ...t,\n terminalUrl: newUrl,\n loading: false,\n error: newUrl\n ? null\n : tr(\n 'sessions.terminalStartedNoTunnelUrl',\n 'Terminal started but no tunnel URL was returned. Try restarting.'\n ),\n }\n : t\n )\n );\n } catch (err) {\n if (isQuotaExhaustedError(err)) {\n const remainingTabs = terminalTabs.filter((t) => t.sessionId !== session.id);\n setTerminalTabs(remainingTabs);\n if (activeTerminalTab === session.id) {\n setActiveTerminalTab(\n remainingTabs.length > 0 ? remainingTabs[remainingTabs.length - 1].sessionId : null\n );\n }\n markQuotaHandledLocally();\n setShowQuotaExhausted(true);\n return;\n }\n\n const message =\n err instanceof Error\n ? err.message\n : tr('sessions.failedStartTerminal', 'Failed to start terminal');\n setTerminalTabs((prev) =>\n prev.map((t) =>\n t.sessionId === session.id ? { ...t, loading: false, error: message } : t\n )\n );\n }\n },\n [activeTerminalTab, terminalTabs, getTerminalUrl, startSessionTerminal]\n );\n\n useEffect(() => {\n if (!uiStateLoaded || !activeTerminalTab) return;\n if (terminalTabs.some((tab) => tab.sessionId === activeTerminalTab)) return;\n\n const session = sessions.find((item) => item.id === activeTerminalTab);\n if (session) {\n void handleOpenTerminal(session);\n }\n }, [activeTerminalTab, handleOpenTerminal, sessions, terminalTabs, uiStateLoaded]);\n\n const handleCloseTerminalTab = useCallback(\n (sessionId: string) => {\n setTerminalTabs((prev) => prev.filter((t) => t.sessionId !== sessionId));\n if (activeTerminalTab === sessionId) {\n setActiveTerminalTab(() => {\n const remaining = terminalTabs.filter((t) => t.sessionId !== sessionId);\n return remaining.length > 0 ? remaining[remaining.length - 1].sessionId : null;\n });\n }\n },\n [activeTerminalTab, terminalTabs]\n );\n\n const handleOpenTerminalExternal = useCallback(\n (sessionId: string) => {\n const tab = terminalTabs.find((t) => t.sessionId === sessionId);\n if (tab?.terminalUrl) window.open(tab.terminalUrl, '_blank');\n },\n [terminalTabs]\n );\n\n const handleRenameSession = useCallback(\n async (sessionId: string, newName: string) => {\n try {\n await updateSession({ variables: { id: sessionId, input: { name: newName } } });\n setTerminalTabs((prev) =>\n prev.map((t) => (t.sessionId === sessionId ? { ...t, name: newName } : t))\n );\n } catch (err) {\n console.error('Failed to rename session:', err);\n }\n },\n [updateSession]\n );\n\n const handleStopTerminal = useCallback(\n async (sessionId: string) => {\n try {\n await stopSessionTerminal({ variables: { id: sessionId } });\n setTerminalTabs((prev) =>\n prev.map((t) =>\n t.sessionId === sessionId\n ? {\n ...t,\n terminalUrl: null,\n error: tr('sessions.terminalStopped', 'Terminal stopped'),\n }\n : t\n )\n );\n } catch (err) {\n console.error('Failed to stop terminal:', err);\n }\n },\n [stopSessionTerminal]\n );\n\n const handleRestartTerminal = useCallback(\n async (sessionId: string) => {\n const previousTab = terminalTabs.find((t) => t.sessionId === sessionId) || null;\n\n try {\n setTerminalTabs((prev) =>\n prev.map((t) =>\n t.sessionId === sessionId ? { ...t, loading: true, error: null, terminalUrl: null } : t\n )\n );\n const result = await restartSessionTerminal({ variables: { id: sessionId } });\n if (result.error && isQuotaExhaustedError(result.error)) {\n setTerminalTabs((prev) =>\n prev.map((t) =>\n t.sessionId === sessionId\n ? {\n ...t,\n loading: false,\n error: null,\n terminalUrl: previousTab?.terminalUrl ?? t.terminalUrl,\n }\n : t\n )\n );\n markQuotaHandledLocally();\n setShowQuotaExhausted(true);\n return;\n }\n\n const newUrl = result.data?.restartSessionTerminal?.ttydUrl || null;\n setTerminalTabs((prev) =>\n prev.map((t) =>\n t.sessionId === sessionId\n ? {\n ...t,\n terminalUrl: newUrl,\n loading: false,\n error: newUrl\n ? null\n : tr(\n 'sessions.terminalRestartedNoUrl',\n 'Terminal restarted but no URL returned.'\n ),\n }\n : t\n )\n );\n } catch (err) {\n if (isQuotaExhaustedError(err)) {\n setTerminalTabs((prev) =>\n prev.map((t) =>\n t.sessionId === sessionId\n ? {\n ...t,\n loading: false,\n error: null,\n terminalUrl: previousTab?.terminalUrl ?? t.terminalUrl,\n }\n : t\n )\n );\n markQuotaHandledLocally();\n setShowQuotaExhausted(true);\n return;\n }\n\n const message =\n err instanceof Error\n ? err.message\n : tr('sessions.failedRestartTerminal', 'Failed to restart terminal');\n setTerminalTabs((prev) =>\n prev.map((t) =>\n t.sessionId === sessionId ? { ...t, loading: false, error: message } : t\n )\n );\n }\n },\n [restartSessionTerminal, terminalTabs]\n );\n\n // Toggle immersive fullscreen mode\n const handleToggleImmersiveFullscreen = useCallback(() => {\n setIsImmersiveFullscreen((prev) => !prev);\n }, []);\n\n // Stats\n const stats = {\n total: sessions.length,\n running: sessions.filter((s) => s.status === 'RUNNING').length,\n stopped: sessions.filter((s) => s.status === 'STOPPED').length,\n };\n\n // Guard states rendered inside MotionPage to prevent unmount/remount animation replay\n if (permissionsLoading) {\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n <LoadingState message={tr('sessions.loadingPermissions', 'Loading permissions...')} />\n </MotionPage>\n );\n }\n\n if (permissions && permissions.permissionStrings?.length > 0 && !canListSessions) {\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n <AccessDenied\n title={tr('common.accessDeniedTitle', 'Access Denied')}\n description={tr(\n 'sessions.accessDeniedDescription',\n \"You don't have permission to view sessions.\"\n )}\n details={tr('sessions.requiredPermission', 'Required permission: session:list')}\n />\n </MotionPage>\n );\n }\n\n const isInitialLoad = loading && !previousData && sessions.length === 0;\n if (isInitialLoad) {\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n <LoadingState message={tr('sessions.loading', 'Loading sessions...')} />\n </MotionPage>\n );\n }\n\n if (error && sessions.length === 0) {\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n <ErrorState\n title={tr('sessions.failedLoad', 'Failed to load sessions')}\n message={error.message}\n onRetry={() => refetch()}\n />\n </MotionPage>\n );\n }\n\n // ── Bulk Action Bar ──────────────────────────────────────────────────\n const renderBulkActionBar = () => {\n if (!hasSelection || viewMode === 'terminal') return null;\n return (\n <div className=\"flex flex-col sm:flex-row items-start sm:items-center gap-3 px-4 py-2 bg-action-primary-bg/10 border border-action-primary-bg/20 rounded-lg\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr(\n selectedSessionIds.size === 1\n ? 'sessions.selectionCountSingle'\n : 'sessions.selectionCountPlural',\n selectedSessionIds.size === 1\n ? '{{count}} session selected'\n : '{{count}} sessions selected',\n { count: selectedSessionIds.size }\n )}\n </span>\n <div className=\"flex items-center gap-1.5 sm:ml-auto flex-wrap\">\n <button\n type=\"button\"\n onClick={handleBulkStop}\n disabled={bulkLoading}\n className=\"flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-text-primary bg-bg-surface border border-border-default rounded-md hover:bg-bg-sunken transition-colors disabled:opacity-50\"\n >\n <Square className=\"size-3\" /> {tr('sessions.stop', 'Stop')}\n </button>\n <button\n type=\"button\"\n onClick={handleBulkRestart}\n disabled={bulkLoading}\n className=\"flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-text-primary bg-bg-surface border border-border-default rounded-md hover:bg-bg-sunken transition-colors disabled:opacity-50\"\n >\n <RotateCcw className=\"size-3\" /> {tr('sessions.restart', 'Restart')}\n </button>\n <button\n type=\"button\"\n onClick={handleBulkTerminate}\n disabled={bulkLoading}\n className=\"flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-status-error-text bg-status-error-bg border border-status-error-text/20 rounded-md hover:bg-status-error-bg/80 transition-colors disabled:opacity-50\"\n >\n <Trash2 className=\"size-3\" /> {tr('sessions.remove', 'Remove')}\n </button>\n <button\n type=\"button\"\n onClick={deselectAllSessions}\n className=\"flex items-center gap-1.5 px-3 py-1.5 text-xs text-text-secondary hover:text-text-primary transition-colors\"\n >\n <XSquare className=\"size-3\" /> {tr('sessions.deselect', 'Deselect')}\n </button>\n </div>\n </div>\n );\n };\n\n // Shared view-mode toggle (terminal / grid / list). Rendered in the\n // same place in both view branches' action-strips so the operator's\n // muscle memory doesn't have to relocate it when switching views.\n // Previously the grid/list view placed it inline with the search\n // input (row 2) while terminal view placed it top-right of the\n // header, which made the icon \"move\" between views.\n const viewToggle = (\n <div className=\"flex items-center gap-0.5 rounded-md bg-bg-sunken p-0.5 flex-shrink-0\">\n <button\n type=\"button\"\n onClick={() => setViewMode('terminal')}\n className={`p-1.5 rounded ${viewMode === 'terminal' ? 'bg-bg-surface shadow-sm' : 'hover:bg-bg-surface/50'}`}\n title={tr('sessions.terminalViewTitle', 'Terminal view')}\n aria-label={tr('sessions.terminalViewTitle', 'Terminal view')}\n >\n <Monitor className=\"size-4 text-text-secondary\" />\n </button>\n <button\n type=\"button\"\n onClick={() => setViewMode('grid')}\n className={`p-1.5 rounded ${viewMode === 'grid' ? 'bg-bg-surface shadow-sm' : 'hover:bg-bg-surface/50'}`}\n title={tr('sessions.gridViewTitle', 'Grid view')}\n aria-label={tr('sessions.gridViewTitle', 'Grid view')}\n >\n <Grid className=\"size-4 text-text-secondary\" />\n </button>\n <button\n type=\"button\"\n onClick={() => setViewMode('list')}\n className={`p-1.5 rounded ${viewMode === 'list' ? 'bg-bg-surface shadow-sm' : 'hover:bg-bg-surface/50'}`}\n title={tr('sessions.listViewTitle', 'List view')}\n aria-label={tr('sessions.listViewTitle', 'List view')}\n >\n <List className=\"size-4 text-text-secondary\" />\n </button>\n </div>\n );\n\n // Shared overlay dialogs — rendered identically in both terminal-first\n // and grid/list returns so that the quota modal, the create-session\n // modal, the reconnect modal, and the share dialog all surface\n // regardless of the active view. Previously these only existed in the\n // grid/list branch, so a quota-exhausted createSession in terminal\n // view silently closed the form with no operator feedback.\n const overlayDialogs = (\n <>\n {showCreateForm && renderCreateModal()}\n {renderReconnectModal()}\n {shareSession && (\n <SessionShareDialog\n session={shareSession}\n isOpen={!!shareSession}\n onClose={() => setShareSession(null)}\n />\n )}\n <ConfirmDialog\n isOpen={showQuotaExhausted}\n onClose={() => setShowQuotaExhausted(false)}\n onConfirm={() => {\n setShowQuotaExhausted(false);\n navigate('/billing');\n }}\n title={getQuotaDialogContent('session').title}\n message={getQuotaDialogContent('session').message}\n confirmLabel=\"View Billing\"\n cancelLabel=\"Dismiss\"\n variant=\"warning\"\n />\n </>\n );\n\n // Terminal-first view\n if (viewMode === 'terminal') {\n return (\n <MotionPage className=\"flex flex-col bg-bg-surface h-screen\">\n <div className=\"flex flex-col sm:flex-row items-start sm:items-center justify-between gap-2 px-4 py-2 border-b border-border-default bg-bg-sunken\">\n <div className=\"flex flex-wrap items-center gap-3\">\n <h1 className=\"text-sm font-semibold text-text-primary\">\n {tr('nav.sessions', 'Sessions')}\n </h1>\n <span className=\"text-xs text-text-muted\">\n {tr('sessions.runningSummary', '{{running}} running / {{total}} total', {\n running: stats.running,\n total: stats.total,\n })}\n </span>\n </div>\n <div className=\"flex w-full flex-wrap items-center gap-2 sm:w-auto sm:justify-end\">\n <button\n type=\"button\"\n onClick={() => checkSessionHealth().then(() => refetch())}\n className=\"p-1.5 text-text-secondary hover:bg-bg-surface rounded transition-colors\"\n title={tr('sessions.checkHealth', 'Check session health')}\n >\n <Activity className=\"size-3.5\" />\n </button>\n <button\n type=\"button\"\n onClick={() => {\n refetch();\n void refetchProbes();\n }}\n disabled={loading}\n className=\"p-1.5 text-text-secondary hover:bg-bg-surface rounded transition-colors disabled:opacity-50\"\n title={tr('common.refresh', 'Refresh')}\n >\n <RefreshCw className={`size-3.5 ${loading ? 'animate-spin' : ''}`} />\n </button>\n {viewToggle}\n </div>\n </div>\n\n {/* Agent Notifications Banner */}\n <div className=\"px-4 pt-3\">\n <SessionNotifications showConnectionStatus={true} maxVisible={3} />\n </div>\n\n <TerminalPanel\n tabs={terminalTabs}\n activeTabId={activeTerminalTab}\n onTabSelect={setActiveTerminalTab}\n onTabClose={handleCloseTerminalTab}\n onOpenExternal={handleOpenTerminalExternal}\n collapsed={false}\n sessions={filteredSessions}\n onOpenTerminal={handleOpenTerminal}\n onRetryTerminal={handleOpenTerminal}\n onRenameSession={handleRenameSession}\n onStopTerminal={handleStopTerminal}\n onStopSession={(sessionId) => {\n const session = sessions.find((s) => s.id === sessionId);\n if (session) void handleStopSession(session);\n }}\n onRestartTerminal={handleRestartTerminal}\n onCreateSession={handleCreateSession}\n fullScreen={true}\n isImmersiveFullscreen={isImmersiveFullscreen}\n onToggleImmersiveFullscreen={handleToggleImmersiveFullscreen}\n contextSwitcher={undefined}\n />\n\n {/* F4 round-3: status bar (online state + shortcut hint). Kept\n compact so it does not steal real estate from the terminal. */}\n <div className=\"flex items-center justify-between gap-3 px-4 py-1.5 border-t border-border-default bg-bg-sunken text-[11px] text-text-tertiary\">\n <div className=\"flex items-center gap-3\">\n <span className=\"inline-flex items-center gap-1.5\">\n <span className=\"size-1.5 rounded-full bg-status-success-bg\" aria-hidden />\n {tr('sessions.statusOnline', 'Online')}\n </span>\n <span>\n {terminalTabs.length} {terminalTabs.length === 1 ? 'tab' : 'tabs'}\n </span>\n {activeTerminalTab && (\n <span className=\"truncate max-w-[40ch]\">\n {terminalTabs.find((t) => t.sessionId === activeTerminalTab)?.name}\n </span>\n )}\n </div>\n <div className=\"hidden sm:flex items-center gap-3\">\n <button\n type=\"button\"\n onClick={() => setShowCheatsheet(true)}\n className=\"hover:text-text-secondary transition-colors\"\n >\n {tr('sessions.shortcutsHint', 'Press ? for shortcuts')}\n </button>\n <button\n type=\"button\"\n onClick={() => {\n setPaletteQuery('');\n setShowCommandPalette(true);\n }}\n className=\"hover:text-text-secondary transition-colors\"\n >\n {tr('sessions.paletteHint', 'Cmd+P · Command palette')}\n </button>\n </div>\n </div>\n\n {successMessage && (\n <div className=\"fixed bottom-4 right-4 z-50 px-4 py-3 bg-status-success-bg text-status-success-text rounded-md shadow-lg text-sm\">\n {successMessage}\n </div>\n )}\n {showCreateForm && renderCreateModal()}\n {renderReconnectModal()}\n\n {/* F4 round-3: keyboard cheatsheet overlay */}\n {showCheatsheet && (\n <div\n className=\"fixed inset-0 z-50 flex items-center justify-center bg-bg-overlay/80 backdrop-blur-sm p-4\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"sessions-cheatsheet-title\"\n onClick={() => setShowCheatsheet(false)}\n >\n <div\n className=\"bg-bg-surface border border-border-default rounded-xl shadow-2xl p-6 max-w-md w-full\"\n onClick={(e) => e.stopPropagation()}\n >\n <h2\n id=\"sessions-cheatsheet-title\"\n className=\"text-lg font-semibold text-text-primary mb-4\"\n >\n {tr('sessions.cheatsheet.title', 'Keyboard shortcuts')}\n </h2>\n <dl className=\"grid grid-cols-[auto_1fr] gap-x-4 gap-y-2 text-sm\">\n <dt className=\"font-mono text-text-secondary\">Cmd / Ctrl + T</dt>\n <dd className=\"text-text-primary\">New session</dd>\n <dt className=\"font-mono text-text-secondary\">Cmd / Ctrl + W</dt>\n <dd className=\"text-text-primary\">Close active tab</dd>\n <dt className=\"font-mono text-text-secondary\">Cmd / Ctrl + 1–9</dt>\n <dd className=\"text-text-primary\">Switch to tab N</dd>\n <dt className=\"font-mono text-text-secondary\">Ctrl + Tab</dt>\n <dd className=\"text-text-primary\">Next tab</dd>\n <dt className=\"font-mono text-text-secondary\">Ctrl + Shift + Tab</dt>\n <dd className=\"text-text-primary\">Previous tab</dd>\n <dt className=\"font-mono text-text-secondary\">Cmd / Ctrl + P</dt>\n <dd className=\"text-text-primary\">Command palette</dd>\n <dt className=\"font-mono text-text-secondary\">?</dt>\n <dd className=\"text-text-primary\">This cheatsheet</dd>\n </dl>\n <div className=\"mt-5 flex justify-end\">\n <button\n type=\"button\"\n onClick={() => setShowCheatsheet(false)}\n className=\"px-3 py-1.5 text-sm rounded-md bg-bg-sunken hover:bg-bg-secondary text-text-primary transition-colors\"\n >\n {tr('common.close', 'Close')}\n </button>\n </div>\n </div>\n </div>\n )}\n\n {/* F4 round-3: command palette — filter by session name + ad-hoc\n commands. Hand-rolled (fe-libs `CommandDialog` exists but we\n want richer keyboard handling than the canonical primitive\n offers). */}\n {showCommandPalette && (\n <div\n className=\"fixed inset-0 z-50 flex items-start justify-center bg-bg-overlay/80 backdrop-blur-sm p-4 pt-20\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"sessions-palette-title\"\n onClick={() => setShowCommandPalette(false)}\n >\n <div\n className=\"bg-bg-surface border border-border-default rounded-xl shadow-2xl w-full max-w-lg overflow-hidden\"\n onClick={(e) => e.stopPropagation()}\n >\n <input\n autoFocus\n type=\"search\"\n value={paletteQuery}\n onChange={(e) => setPaletteQuery(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === 'Escape') setShowCommandPalette(false);\n }}\n placeholder={tr(\n 'sessions.palette.placeholder',\n 'Search sessions or type a command…'\n )}\n aria-label=\"Command palette search\"\n className=\"w-full px-4 py-3 bg-bg-surface border-b border-border-default text-text-primary placeholder:text-text-muted focus:outline-none\"\n />\n <ul\n id=\"sessions-palette-title\"\n className=\"max-h-80 overflow-y-auto divide-y divide-border-subtle\"\n >\n {/* Hard-coded actions */}\n {(['new'] as const).map((id) => {\n const label = tr('sessions.palette.new', 'New session');\n if (paletteQuery && !label.toLowerCase().includes(paletteQuery.toLowerCase()))\n return null;\n return (\n <li key={id}>\n <button\n type=\"button\"\n onClick={() => {\n setShowCommandPalette(false);\n setShowCreateForm(true);\n }}\n className=\"w-full text-left px-4 py-2.5 text-sm hover:bg-bg-sunken transition-colors\"\n >\n <span className=\"font-medium text-text-primary\">{label}</span>\n <span className=\"ml-2 text-xs text-text-muted\">Cmd+T</span>\n </button>\n </li>\n );\n })}\n\n {/* Open sessions */}\n {filteredSessions\n .filter(\n (s) =>\n !paletteQuery || s.name.toLowerCase().includes(paletteQuery.toLowerCase())\n )\n .slice(0, 20)\n .map((s) => (\n <li key={s.id}>\n <button\n type=\"button\"\n onClick={() => {\n setShowCommandPalette(false);\n void handleOpenTerminal(s);\n }}\n className=\"w-full text-left px-4 py-2.5 text-sm hover:bg-bg-sunken transition-colors flex items-center justify-between\"\n >\n <span className=\"text-text-primary truncate\">{s.name}</span>\n <span className=\"ml-2 text-xs text-text-muted\">{s.status}</span>\n </button>\n </li>\n ))}\n </ul>\n </div>\n </div>\n )}\n {overlayDialogs}\n </MotionPage>\n );\n }\n\n // Grid/List view\n return (\n <MotionPage className=\"flex flex-col bg-bg-surface min-h-screen\">\n <div className=\"flex-1 overflow-y-auto space-y-6 p-4 sm:p-6\">\n <PageHeader\n title={tr('nav.sessions', 'Sessions')}\n description={tr('sessions.pageDescription', '{{running}} of {{total}} sessions running', {\n running: stats.running,\n total: stats.total,\n })}\n actions={\n <div className=\"flex flex-col sm:flex-row items-stretch sm:items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => checkSessionHealth().then(() => refetch())}\n className=\"flex items-center gap-2 px-3 py-2 text-text-secondary hover:bg-bg-sunken rounded-md transition-colors\"\n title={tr('sessions.checkHealth', 'Check session health')}\n >\n <Activity className=\"size-4\" />\n </button>\n <button\n type=\"button\"\n onClick={() => refetch()}\n disabled={loading}\n className=\"flex items-center gap-2 px-3 py-2 text-text-secondary hover:bg-bg-sunken rounded-md transition-colors disabled:opacity-50\"\n title={tr('sessions.refreshTitle', 'Refresh sessions')}\n >\n <RefreshCw className={`size-4 ${loading ? 'animate-spin' : ''}`} />\n </button>\n <SaveToFilesButton\n label={tr('sessions.export', 'Export')}\n disabled={isExporting}\n testId=\"sessions-export\"\n defaultFolderPath=\"/Vibecontrols/Sessions\"\n source={{\n kind: 'blob',\n filename: `sessions-export-${new Date().toISOString().slice(0, 10)}.csv`,\n mimeType: 'text/csv',\n getBlob: async () => {\n const result = await exportSessions({\n variables: {\n input: {\n filter: {\n status:\n statusFilter !== 'all'\n ? (statusFilter as 'RUNNING' | 'STOPPED' | 'STARTING' | 'ERROR')\n : undefined,\n type:\n typeFilter !== 'all'\n ? (typeFilter as 'TMUX' | 'TERMINAL' | 'SSH' | 'SCRIPT' | 'CUSTOM')\n : undefined,\n vibeId:\n vibeFilter !== 'all' && vibeFilter !== 'none'\n ? vibeFilter\n : undefined,\n agentId: agentFilter !== 'all' ? agentFilter : undefined,\n },\n format: 'CSV',\n },\n },\n });\n const exportData = result.data?.exportSessions;\n if (!exportData) return null;\n return { blob: new Blob([exportData.data], { type: 'text/csv' }) };\n },\n }}\n onDownload={() => handleExportSessions('CSV')}\n />\n <PermissionButton permission={VIBE_PERMISSIONS.SESSION_CREATE}>\n <button\n type=\"button\"\n onClick={() => setShowReconnectModal(true)}\n className=\"flex items-center gap-2 px-3 py-2 text-text-primary border border-border-default rounded-md hover:bg-bg-sunken transition-colors\"\n data-testid=\"sessions-reconnect-button\"\n title={tr(\n 'sessions.reconnectTooltip',\n 'Reconnect orphan tmux/wezterm/zellij sessions left over from a previous agent run'\n )}\n >\n <RefreshCw className=\"size-4\" />\n {tr('sessions.reconnect', 'Reconnect')}\n </button>\n </PermissionButton>\n <PermissionButton permission={VIBE_PERMISSIONS.SESSION_CREATE}>\n <button\n type=\"button\"\n onClick={handleCreateSession}\n className=\"flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors\"\n >\n <Plus className=\"size-4\" />\n {tr('sessions.newSession', 'New Session')}\n </button>\n </PermissionButton>\n {viewToggle}\n </div>\n }\n />\n\n {/* Agent Notifications Banner */}\n <SessionNotifications showConnectionStatus={true} maxVisible={3} />\n\n {/* Filters Bar */}\n <div data-tour=\"sessions-filters\" className=\"space-y-3\">\n {/* Row 1: Search.\n The view toggle is rendered in the page-header actions\n block so its position matches the terminal-view header —\n no relocation when the operator switches viewMode. */}\n <div className=\"flex flex-col sm:flex-row items-stretch sm:items-center gap-2\">\n <SearchInput\n value={searchQuery}\n onChange={setSearchQuery}\n placeholder={tr('sessions.searchPlaceholder', 'Search sessions...')}\n className=\"flex-1 sm:w-64 sm:flex-none\"\n />\n </div>\n\n {/* Row 2: Filters - wrap on all screens */}\n <div className=\"flex items-center gap-2 sm:gap-3 flex-wrap\">\n <FilterDropdown\n label={tr('sessions.filterStatus', 'Status')}\n value={statusFilter}\n options={statusOptions}\n onChange={setStatusFilter}\n />\n <FilterDropdown\n label={tr('sessions.filterType', 'Type')}\n value={typeFilter}\n options={typeOptions}\n onChange={setTypeFilter}\n />\n <FilterDropdown\n label={tr('sessions.filterVibe', 'Vibe')}\n value={vibeFilter}\n options={vibeOptions}\n onChange={setVibeFilter}\n />\n <FilterDropdown\n label={tr('sessions.filterAgent', 'Agent')}\n value={agentFilter}\n options={agentOptions}\n onChange={setAgentFilter}\n />\n </div>\n </div>\n\n {/* Quick Actions & Select All */}\n <div className=\"flex flex-col sm:flex-row items-start sm:items-center gap-2 flex-wrap\">\n <button\n type=\"button\"\n onClick={() => setStatusFilter('RUNNING')}\n className={`flex items-center gap-2 px-3 py-1.5 text-sm rounded-full transition-colors ${\n statusFilter === 'RUNNING'\n ? 'bg-status-success-bg text-status-success-text'\n : 'bg-bg-sunken text-text-secondary hover:bg-bg-sunken/80'\n }`}\n >\n <Play className=\"size-3\" /> {tr('sessions.running', 'Running')} ({stats.running})\n </button>\n <button\n type=\"button\"\n onClick={() => setStatusFilter('STOPPED')}\n className={`flex items-center gap-2 px-3 py-1.5 text-sm rounded-full transition-colors ${\n statusFilter === 'STOPPED'\n ? 'bg-bg-sunken text-text-secondary'\n : 'bg-bg-sunken text-text-secondary hover:bg-bg-sunken/80'\n }`}\n >\n <Square className=\"size-3\" /> {tr('sessions.stoppedState', 'Stopped')} ({stats.stopped})\n </button>\n <div className=\"sm:ml-auto flex items-center gap-2\">\n {filteredSessions.length > 0 && (\n <button\n type=\"button\"\n onClick={isAllSelected ? deselectAllSessions : selectAllSessions}\n className=\"flex items-center gap-1.5 px-3 py-1.5 text-xs text-text-secondary hover:text-text-primary transition-colors\"\n >\n <CheckSquare className=\"size-3.5\" />\n {isAllSelected\n ? tr('sessions.deselectAll', 'Deselect All')\n : tr('sessions.selectAll', 'Select All')}\n </button>\n )}\n </div>\n </div>\n\n {/* Bulk Action Bar */}\n {renderBulkActionBar()}\n\n {/* Results Count */}\n {searchQuery ||\n statusFilter !== 'all' ||\n typeFilter !== 'all' ||\n vibeFilter !== 'all' ||\n agentFilter !== 'all' ? (\n <p className=\"text-sm text-text-secondary\">\n {tr('sessions.showing', 'Showing')} {filteredSessions.length} {tr('sessions.of', 'of')}{' '}\n {sessions.length} {tr('nav.sessions', 'sessions')}\n {loading && (\n <span className=\"ml-2 text-text-muted\">\n ({tr('sessions.refreshing', 'refreshing...')})\n </span>\n )}\n </p>\n ) : null}\n\n {/* Sessions Display */}\n <AnimatePresence mode=\"wait\">\n {filteredSessions.length > 0 ? (\n viewMode === 'grid' ? (\n <MotionList\n key=\"grid-view\"\n stagger={0.04}\n className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\"\n >\n {filteredSessions.map((session) => (\n <MotionItem key={session.id}>\n <SessionCard\n session={session}\n onOpen={handleSessionSelect}\n onStart={handleStartSession}\n onStop={handleStopSession}\n onRestart={handleRestartSession}\n onOpenTerminal={handleOpenTerminal}\n onShare={handleShareSession}\n selected={selectedSessionIds.has(session.id)}\n onToggleSelect={() => toggleSessionSelection(session.id)}\n />\n </MotionItem>\n ))}\n </MotionList>\n ) : (\n <MotionItem key=\"list-view\">\n <SessionList\n sessions={filteredSessions}\n onOpen={handleSessionSelect}\n onStart={handleStartSession}\n onStop={handleStopSession}\n onRestart={handleRestartSession}\n onTerminate={handleTerminateSession}\n onOpenTerminal={handleOpenTerminal}\n onShare={handleShareSession}\n selectedIds={selectedSessionIds}\n onToggleSelect={toggleSessionSelection}\n onSelectAll={selectAllSessions}\n onDeselectAll={deselectAllSessions}\n />\n </MotionItem>\n )\n ) : (\n <div className=\"space-y-4\">\n <AgentSetupBanner variant=\"compact\" dismissKey=\"vibecontrols-sessions-setup-banner\" />\n <EmptyState\n icon={<Terminal className=\"size-8 text-text-secondary\" />}\n title={\n searchQuery ||\n statusFilter !== 'all' ||\n typeFilter !== 'all' ||\n vibeFilter !== 'all' ||\n agentFilter !== 'all'\n ? tr('sessions.emptyFound', 'No sessions found')\n : tr('sessions.emptyConfigured', 'No sessions configured')\n }\n description={\n searchQuery ||\n statusFilter !== 'all' ||\n typeFilter !== 'all' ||\n vibeFilter !== 'all' ||\n agentFilter !== 'all'\n ? tr('sessions.emptyAdjustSearch', 'Try adjusting your search or filters')\n : tr(\n 'sessions.emptyDescription',\n 'Sessions require an active agent. Install the CLI agent first, then start a session.'\n )\n }\n action={\n !searchQuery &&\n statusFilter === 'all' &&\n typeFilter === 'all' &&\n vibeFilter === 'all' &&\n agentFilter === 'all' ? (\n <div className=\"flex items-center gap-2\">\n {canCreateSession && (\n <button\n type=\"button\"\n onClick={handleCreateSession}\n className=\"flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors\"\n >\n <Plus className=\"size-4\" /> {tr('sessions.newSession', 'New Session')}\n </button>\n )}\n <GetHelpButton prompt=\"What is a Session in VibeControls and how do I start one?\" />\n </div>\n ) : undefined\n }\n />\n </div>\n )}\n </AnimatePresence>\n\n {successMessage && (\n <div className=\"fixed bottom-4 right-4 z-50 px-4 py-3 bg-status-success-bg text-status-success-text rounded-md shadow-lg text-sm\">\n {successMessage}\n </div>\n )}\n </div>\n\n {/* Tabbed Terminal Panel - docked at bottom */}\n {terminalTabs.length > 0 && (\n <TerminalPanel\n tabs={terminalTabs}\n activeTabId={activeTerminalTab}\n onTabSelect={setActiveTerminalTab}\n onTabClose={handleCloseTerminalTab}\n onOpenExternal={handleOpenTerminalExternal}\n onToggleCollapse={() => setTerminalCollapsed((prev) => !prev)}\n collapsed={terminalCollapsed}\n sessions={filteredSessions}\n onOpenTerminal={handleOpenTerminal}\n onRetryTerminal={handleOpenTerminal}\n onRenameSession={handleRenameSession}\n onStopTerminal={handleStopTerminal}\n onStopSession={(sessionId) => {\n const session = sessions.find((s) => s.id === sessionId);\n if (session) void handleStopSession(session);\n }}\n onRestartTerminal={handleRestartTerminal}\n heightClass=\"h-[280px] sm:h-[380px]\"\n isImmersiveFullscreen={isImmersiveFullscreen}\n onToggleImmersiveFullscreen={handleToggleImmersiveFullscreen}\n contextSwitcher={undefined}\n />\n )}\n\n {overlayDialogs}\n </MotionPage>\n );\n\n function renderReconnectModal() {\n if (!showReconnectModal) return null;\n const orphans = orphansData?.agentSessionOrphans ?? [];\n return (\n <div\n className=\"fixed inset-0 bg-bg-overlay flex items-center justify-center z-50 p-4\"\n data-testid=\"sessions-reconnect-modal\"\n >\n <div className=\"bg-bg-surface rounded-lg p-6 w-full max-w-2xl max-h-[80vh] overflow-y-auto\">\n <h2 className=\"text-lg font-semibold text-text-primary mb-1\">\n {tr('sessions.reconnectTitle', 'Reconnect orphan sessions')}\n </h2>\n <p className=\"text-sm text-text-secondary mb-4\">\n {tr(\n 'sessions.reconnectDesc',\n 'Pick an agent to scan its host for tmux / wezterm / zellij sessions named vibe-* that are not currently tracked. Adopt one to start a web terminal and bring it back into the UI.'\n )}\n </p>\n <div className=\"space-y-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('sessions.agent', 'Agent')}\n </label>\n <SearchableSelect\n options={createAgentOptions}\n value={reconnectAgentId}\n onChange={(id) => {\n setReconnectAgentId(id);\n setReconnectError(null);\n if (id) void discoverOrphans({ variables: { agentId: id } });\n }}\n placeholder={tr('sessions.selectAgent', 'Select an agent')}\n searchPlaceholder={tr('sessions.searchAgents', 'Search agents...')}\n clearable\n />\n </div>\n {reconnectError && (\n <div\n className=\"p-3 bg-status-error-bg text-status-error-text text-sm rounded-md\"\n data-testid=\"sessions-reconnect-error\"\n >\n {reconnectError}\n </div>\n )}\n {reconnectAgentId && (\n <div data-testid=\"sessions-reconnect-list\" className=\"space-y-2\">\n {orphansLoading ? (\n <div className=\"text-sm text-text-secondary\">\n {tr('sessions.scanningHost', 'Scanning host...')}\n </div>\n ) : orphans.length === 0 ? (\n <div className=\"text-sm text-text-secondary\">\n {tr('sessions.noOrphans', 'No orphan vibe-* sessions found on this agent.')}\n </div>\n ) : (\n orphans.map(\n (o: {\n externalName: string;\n provider: string;\n windows: number;\n attached: boolean;\n createdAt?: string | null;\n }) => (\n <div\n key={o.externalName}\n className=\"flex items-center justify-between gap-3 p-3 border border-border-default rounded-md bg-bg-sunken/40\"\n data-testid=\"sessions-reconnect-row\"\n >\n <div className=\"min-w-0 flex-1\">\n <div className=\"font-mono text-sm text-text-primary break-all\">\n {o.externalName}\n </div>\n <div className=\"text-xs text-text-tertiary mt-0.5\">\n {o.provider} · {o.windows} window{o.windows !== 1 ? 's' : ''}\n {o.attached ? ' · attached' : ''}\n {o.createdAt ? ` · created ${o.createdAt}` : ''}\n </div>\n </div>\n <button\n type=\"button\"\n disabled={reconnectAdoptingName === o.externalName}\n onClick={async () => {\n setReconnectAdoptingName(o.externalName);\n setReconnectError(null);\n try {\n await adoptAgentSession({\n variables: {\n agentId: reconnectAgentId,\n externalName: o.externalName,\n name: o.externalName,\n },\n });\n // Refresh orphan list so the adopted one disappears.\n await discoverOrphans({\n variables: { agentId: reconnectAgentId },\n });\n } catch (e) {\n setReconnectError(e instanceof Error ? e.message : String(e));\n } finally {\n setReconnectAdoptingName(null);\n }\n }}\n className=\"shrink-0 px-3 py-1.5 text-xs bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50\"\n data-testid={`sessions-reconnect-adopt-${o.externalName}`}\n >\n {reconnectAdoptingName === o.externalName\n ? tr('sessions.adopting', 'Adopting…')\n : tr('sessions.adopt', 'Adopt')}\n </button>\n </div>\n )\n )\n )}\n </div>\n )}\n </div>\n <div className=\"flex justify-end gap-3 mt-6\">\n <button\n type=\"button\"\n onClick={() => {\n setShowReconnectModal(false);\n setReconnectError(null);\n }}\n className=\"px-4 py-2 text-text-primary hover:bg-bg-sunken rounded-md transition-colors\"\n >\n {tr('common.close', 'Close')}\n </button>\n </div>\n </div>\n </div>\n );\n }\n\n function renderCreateModal() {\n return (\n <div className=\"fixed inset-0 bg-bg-overlay flex items-center justify-center z-50 p-4\">\n <div className=\"bg-bg-surface rounded-lg p-6 w-full max-w-lg\">\n <h2 className=\"text-lg font-semibold text-text-primary mb-4\">\n {tr('sessions.newSession', 'New Session')}\n </h2>\n <div className=\"space-y-4\">\n {createError && (\n <div className=\"p-3 bg-status-error-bg text-status-error-text text-sm rounded-md\">\n {createError}\n </div>\n )}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('sessions.name', 'Name')}\n </label>\n <input\n aria-label=\"control\"\n ref={sessionNameRef}\n type=\"text\"\n placeholder={tr('sessions.namePlaceholder', 'dev-server')}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder\"\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('sessions.filterVibe', 'Vibe')}\n </label>\n <SearchableSelect\n options={createVibeOptions}\n value={formVibeId}\n onChange={setFormVibeId}\n placeholder={tr('sessions.noVibe', 'No vibe')}\n searchPlaceholder={tr('sessions.searchVibes', 'Search vibes...')}\n clearable\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('sessions.filterAgent', 'Agent')}\n </label>\n <SearchableSelect\n options={createAgentOptions}\n value={formAgentId}\n onChange={setFormAgentId}\n placeholder={tr('sessions.selectAgent', 'Select an agent')}\n searchPlaceholder={tr('sessions.searchAgents', 'Search agents...')}\n clearable\n />\n <p className=\"text-xs text-text-muted mt-1\">\n {tr(\n 'sessions.agentHint',\n \"Required when no vibe is selected. Overridden by the vibe's agent if a vibe is chosen.\"\n )}\n </p>\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('sessions.profile', 'Profile')}\n </label>\n <SearchableSelect\n options={profileOptions}\n value={formProfile}\n onChange={setFormProfile}\n placeholder={tr('sessions.selectProfile', 'default')}\n searchPlaceholder={tr('sessions.searchProfiles', 'Search profiles...')}\n />\n <p className=\"text-xs text-text-muted mt-1\">\n {tr(\n 'sessions.profileHint',\n 'Which agent profile this session targets. Defaults to \"default\" — change only if the agent runs multiple isolated profiles.'\n )}\n </p>\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('sessions.filterType', 'Type')}\n </label>\n <SearchableSelect\n options={sessionTypeOptions}\n value={formType}\n onChange={setFormType}\n placeholder={tr('sessions.selectType', 'Select type')}\n searchPlaceholder={tr('sessions.searchTypes', 'Search types...')}\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('sessions.command', 'Command')}\n </label>\n <input\n aria-label=\"control\"\n ref={sessionCommandRef}\n type=\"text\"\n placeholder={tr('sessions.commandPlaceholder', 'bun run dev')}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder\"\n />\n </div>\n <div className=\"flex items-center gap-2\">\n <input\n aria-label=\"autoStart\"\n ref={sessionAutoStartRef}\n type=\"checkbox\"\n id=\"autoStart\"\n className=\"size-4 rounded border-border-default\"\n />\n <label htmlFor=\"autoStart\" className=\"text-sm text-text-primary\">\n {tr('sessions.autoStartWithVibe', 'Auto-start with vibe')}\n </label>\n </div>\n {(formType === 'TMUX' || formType === 'WEZTERM' || formType === 'ZELLIJ') && (\n <div\n className=\"rounded-md border border-border-default bg-bg-sunken/40\"\n data-testid=\"sessions-create-advanced\"\n >\n <button\n type=\"button\"\n onClick={() => setFormShowAdvanced((v) => !v)}\n className=\"w-full flex items-center justify-between px-3 py-2 text-sm font-medium text-text-primary\"\n aria-expanded={formShowAdvanced}\n >\n <span>{tr('sessions.advanced', 'Advanced options')}</span>\n <span className=\"text-text-tertiary text-xs\">{formShowAdvanced ? '▾' : '▸'}</span>\n </button>\n {formShowAdvanced && (\n <div className=\"px-3 pb-3 space-y-2\">\n <label className=\"block text-xs font-medium text-text-secondary\">\n {tr('sessions.attachExistingLabel', 'Attach to existing session (optional)')}\n </label>\n <input\n aria-label=\"control\"\n ref={sessionExternalNameRef}\n type=\"text\"\n data-testid=\"sessions-create-external-name\"\n placeholder={\n formType === 'WEZTERM'\n ? 'e.g. vibe-051c9c62 (existing wezterm workspace)'\n : formType === 'ZELLIJ'\n ? 'e.g. vibe-051c9c62 (existing zellij session)'\n : 'e.g. vibe-051c9c62 (existing tmux session)'\n }\n className=\"w-full px-3 py-2 text-sm bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder font-mono\"\n />\n <p className=\"text-xs text-text-tertiary\">\n {tr(\n 'sessions.attachExistingHint',\n 'When set, the agent attaches to that existing session instead of creating a new one. Leave blank to spawn fresh.'\n )}\n </p>\n </div>\n )}\n </div>\n )}\n </div>\n <div className=\"border-t border-border-default pt-4\">\n <h3 className=\"text-sm font-medium text-text-secondary mb-2\">\n {tr('sessions.tags', 'Tags')}\n </h3>\n <EntityTagPicker entityType=\"VIBECONTROLS_SESSION\" onDraftChange={setDraftTagIds} />\n </div>\n <div className=\"flex justify-end gap-3 mt-6\">\n <button\n type=\"button\"\n onClick={() => setShowCreateForm(false)}\n disabled={createLoading}\n className=\"px-4 py-2 text-text-primary hover:bg-bg-sunken rounded-md transition-colors disabled:opacity-50\"\n >\n {tr('common.cancel', 'Cancel')}\n </button>\n <button\n type=\"button\"\n onClick={handleSubmitCreateSession}\n disabled={createLoading}\n className=\"px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50\"\n >\n {createLoading\n ? tr('sessions.creating', 'Creating...')\n : tr('sessions.createSession', 'Create Session')}\n </button>\n </div>\n </div>\n </div>\n );\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-vibecontrols",
3
- "version": "2026.531.3",
3
+ "version": "2026.531.4",
4
4
  "description": "VibeControls microfrontend for Burdenoff products",
5
5
  "type": "module",
6
6
  "files": [