@burdenoff/microfe-vibecontrols 2026.608.4 → 2026.608.5
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.
|
@@ -704,7 +704,12 @@ function jt() {
|
|
|
704
704
|
let t = Wr.get(e);
|
|
705
705
|
return !t || t.healthy;
|
|
706
706
|
}, t = Rn && q.some((e) => e.id === Rn) ? Rn : null, n = null;
|
|
707
|
-
if (t && e(t)
|
|
707
|
+
if (t && e(t)) n = t;
|
|
708
|
+
else {
|
|
709
|
+
let t = q.filter((t) => e(t.id));
|
|
710
|
+
n = (t.find((e) => e.isActive) ?? t[0])?.id ?? null;
|
|
711
|
+
}
|
|
712
|
+
if (!n) {
|
|
708
713
|
t && bn(t), zn && J.some((e) => e.id === zn) && vn(zn), Cn(Bn || "TMUX"), Sn(Vn || "default"), j(!0), v.emitCustom("vibecontrols.session.create_clicked", { workspaceId: _ ?? void 0 });
|
|
709
714
|
return;
|
|
710
715
|
}
|
|
@@ -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 Layers,\n MonitorSmartphone,\n Server,\n Share2,\n Keyboard,\n Info,\n ChevronDown,\n SlidersHorizontal,\n} from 'lucide-react';\nimport { useEventBus } from '@burdenoff/fe-libs/shared/events';\nimport { MotionPage, MotionList, MotionItem } from '@burdenoff/fe-libs/shared/motion';\nimport { useSafeShellUi } from '@burdenoff/fe-libs/shared/providers/shell/ShellUiProvider';\nimport { AnimatePresence } from 'framer-motion';\nimport {\n SessionCard,\n SessionList,\n TerminalPanel,\n SessionShareDialog,\n SessionGroup,\n groupSessions,\n ShortcutCheatsheet,\n SessionsCommandPalette,\n SessionInfoPanel,\n AgentInfoPopover,\n TargetInfoPopover,\n buildSessionShortcuts,\n} from '@/components/sessions';\nimport type {\n Session,\n TerminalTab,\n TargetType,\n SessionsGroupBy,\n PaletteCommand,\n} from '@/components/sessions';\nimport { getAgentStatus } from '@/components/agents/agentStatus';\nimport { EntityShareButton } from '@/components/sharing';\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 SessionConnectionStatus,\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';\nimport { randomSessionName } from '@/utils/randomSessionName';\n\ntype ViewMode = 'terminal' | 'grid' | 'list';\n\n/** The GraphQL SessionType union accepted by the createSession mutation. */\ntype CreateSessionType = 'TMUX' | 'WEZTERM' | 'ZELLIJ' | 'TERMINAL' | 'SSH' | 'SCRIPT' | 'CUSTOM';\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 bus = useEventBus();\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 // Session sharing (create / revoke share link, share-link list) is gated\n // on `session:update` per the @rbac directives in session-sharing.gql.\n // Used below to suppress the Share button so viewers don't trip a Forbidden.\n const canUpdateSession = hasPermission(VIBE_PERMISSIONS.SESSION_UPDATE);\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 [targetFilter, setTargetFilter] = useState('all');\n const [groupBy, setGroupBy] = useState<SessionsGroupBy>('none');\n // Collapsed grouping headers — render-only, not persisted (matches the\n // AgentManager sidebar precedent).\n const [collapsedGroups, setCollapsedGroups] = useState<Set<string>>(() => new Set());\n const [viewMode, setViewMode] = useState<ViewMode>('terminal');\n const [showCreateForm, setShowCreateForm] = useState(false);\n // Split \"New Session\" button: the caret opens this menu (options form /\n // reconnect existing). The primary button quick-creates.\n const [showHeaderCreateMenu, setShowHeaderCreateMenu] = useState(false);\n const headerCreateMenuRef = useRef<HTMLDivElement>(null);\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 // Wraps the search input so the `/` shortcut can focus it.\n const searchContainerRef = useRef<HTMLDivElement>(null);\n // The grid/list scroll container — used to preserve scroll position when\n // clicking a docked terminal tab (the tab/iframe focus would otherwise\n // scroll the page).\n const gridScrollRef = useRef<HTMLDivElement>(null);\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 // Close the split-button \"New Session\" caret menu on outside click.\n useEffect(() => {\n if (!showHeaderCreateMenu) return;\n const onClick = (e: MouseEvent) => {\n if (headerCreateMenuRef.current && !headerCreateMenuRef.current.contains(e.target as Node)) {\n setShowHeaderCreateMenu(false);\n }\n };\n document.addEventListener('mousedown', onClick);\n return () => document.removeEventListener('mousedown', onClick);\n }, [showHeaderCreateMenu]);\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 sessionsGroupBy: persistedGroupBy,\n sessionsTargetFilter: persistedTargetFilter,\n setTerminalTabs: setPersistedTabs,\n setActiveTerminalTabId: setPersistedActiveTab,\n setSessionsViewMode: setPersistedViewMode,\n setSessionsGroupBy: setPersistedGroupBy,\n setSessionsTargetFilter: setPersistedTargetFilter,\n sessionInfoPanelOpen,\n setSessionInfoPanelOpen,\n activeSessionId,\n lastUsedAgentId,\n lastUsedVibeId,\n lastUsedSessionType,\n lastUsedProfile,\n setLastUsedSessionDefaults,\n } = useVibeControlsStore();\n\n // In terminal view, reclaim the whole content area for the terminal: collapse\n // the app-shell breadcrumb AND hide the tall marketing footer (rendered in\n // content via footerInContent) which otherwise squeezes the flex-1 terminal\n // down to a sliver. Both restore to their prior values on leave/unmount.\n // Read via a ref so the effect depends only on `viewMode` (the shell context\n // value identity changes whenever a flag flips, which would loop the effect).\n const shellUi = useSafeShellUi();\n const shellUiRef = useRef(shellUi);\n shellUiRef.current = shellUi;\n useEffect(() => {\n const ui = shellUiRef.current;\n if (!ui || viewMode !== 'terminal') return;\n const prevBreadcrumb = ui.isBreadcrumbCollapsed;\n const prevFooter = ui.footerVisible;\n ui.setBreadcrumbCollapsed(true);\n ui.setFooterVisible(false);\n return () => {\n shellUiRef.current?.setBreadcrumbCollapsed(prevBreadcrumb);\n shellUiRef.current?.setFooterVisible(prevFooter);\n };\n }, [viewMode]);\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 [terminalCollapsed, setTerminalCollapsed] = useState(false);\n // Bumped by the F2 shortcut to tell the TerminalPanel to start renaming\n // its active tab (rename state lives inside the panel).\n const [renameTabNonce, setRenameTabNonce] = useState(0);\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 const queryGroupBy = searchParams.get('group');\n const queryTargetId = searchParams.get('target');\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 // Grouping + target filter: query params win (bookmarkable), else\n // fall back to the persisted UI preference.\n const validGroupBy: SessionsGroupBy[] = ['none', 'agent', 'target', 'vibe', 'status', 'type'];\n if (queryGroupBy && (validGroupBy as string[]).includes(queryGroupBy)) {\n setGroupBy(queryGroupBy as SessionsGroupBy);\n } else if (persistedGroupBy) {\n setGroupBy(persistedGroupBy);\n }\n if (queryTargetId) {\n setTargetFilter(queryTargetId);\n } else if (persistedTargetFilter) {\n setTargetFilter(persistedTargetFilter);\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 pinned: t.pinned,\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 persistedGroupBy,\n persistedTargetFilter,\n querySessionId,\n queryViewMode,\n queryGroupBy,\n queryTargetId,\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 (sessionId, name + pinned for persistence;\n // tab order is the array order, so reorder persists for free).\n const tabsToStore = terminalTabs.map((t) => ({\n sessionId: t.sessionId,\n name: t.name,\n pinned: t.pinned,\n }));\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 setPersistedGroupBy(groupBy);\n }, [groupBy, uiStateLoaded, setPersistedGroupBy]);\n\n useEffect(() => {\n if (!uiStateLoaded) return;\n setPersistedTargetFilter(targetFilter);\n }, [targetFilter, uiStateLoaded, setPersistedTargetFilter]);\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 (groupBy !== 'none') {\n if (next.get('group') !== groupBy) {\n next.set('group', groupBy);\n changed = true;\n }\n } else if (next.has('group')) {\n next.delete('group');\n changed = true;\n }\n\n if (targetFilter !== 'all') {\n if (next.get('target') !== targetFilter) {\n next.set('target', targetFilter);\n changed = true;\n }\n } else if (next.has('target')) {\n next.delete('target');\n changed = true;\n }\n\n if (changed) {\n setSearchParams(next, { replace: true });\n }\n }, [\n activeTerminalTab,\n searchParams,\n setSearchParams,\n uiStateLoaded,\n viewMode,\n groupBy,\n targetFilter,\n ]);\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 are installed further down (after the tab/terminal\n // action handlers are defined) so the handler can reference them.\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 // Group-by options for the Sessions grid/list views.\n const groupByOptions = useMemo(\n () => [\n { value: 'agent', label: tr('sessions.groupBy.agent', 'Agent') },\n { value: 'target', label: tr('sessions.groupBy.target', 'Target') },\n { value: 'vibe', label: tr('sessions.groupBy.vibe', 'Vibe') },\n { value: 'status', label: tr('sessions.groupBy.status', 'Status') },\n { value: 'type', label: tr('sessions.groupBy.type', 'Type') },\n ],\n [tr]\n );\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 hostname: session.agent.hostname ?? null,\n platform: session.agent.platform ?? null,\n tunnelUrl: session.agent.tunnelUrl || null,\n tunnelStatus: session.agent.tunnelStatus ?? null,\n isActive: session.agent.isActive,\n lastHeartbeat: session.agent.lastHeartbeat ?? null,\n target: session.agent.target\n ? {\n id: session.agent.target.id,\n name: session.agent.target.name,\n // Generated `TargetType` is a string enum; normalize to the\n // domain string-literal union for grouping / badge logic.\n type: String(session.agent.target.type) as TargetType,\n status: session.agent.target.status,\n host: session.agent.target.host ?? null,\n port: session.agent.target.port ?? null,\n }\n : null,\n }\n : undefined,\n };\n });\n }, [effectiveData?.sessions?.items]);\n\n // Build target options from the targets present on loaded sessions'\n // agents. SessionFilterInput has no targetId, so this filter is applied\n // client-side over the loaded page (consistent with the vibe \"none\"\n // filter). Distinct by target id.\n const targetOptions = useMemo(() => {\n const seen = new Map<string, string>();\n for (const session of sessions) {\n const target = session.agent?.target;\n if (target && !seen.has(target.id)) seen.set(target.id, target.name);\n }\n return [\n { value: 'all', label: tr('sessions.filterTargetAll', 'All Targets') },\n ...Array.from(seen.entries()).map(([id, name]) => ({ value: id, label: name })),\n ];\n }, [sessions, tr]);\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 // Target filter is client-side (no targetId on SessionFilterInput).\n if (targetFilter !== 'all' && session.agent?.target?.id !== targetFilter) return false;\n return true;\n });\n }, [sessions, searchQuery, vibeFilter, targetFilter]);\n\n // Grouped partition for grid/list views. Render-only — selection stays\n // on the page-level Set.\n const groupedSessions = useMemo(\n () => groupSessions(filteredSessions, groupBy),\n [filteredSessions, groupBy]\n );\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 // Toggle every session in a group: if all are already selected, clear\n // them; otherwise add them. Mutates the same page-level Set so the bulk\n // action bar and global select-all keep working unchanged.\n const toggleGroupSelection = useCallback((ids: string[]) => {\n setSelectedSessionIds((prev) => {\n const next = new Set(prev);\n const allSelected = ids.length > 0 && ids.every((id) => next.has(id));\n if (allSelected) {\n ids.forEach((id) => next.delete(id));\n } else {\n ids.forEach((id) => next.add(id));\n }\n return next;\n });\n }, []);\n\n const toggleGroupCollapse = useCallback((key: string) => {\n setCollapsedGroups((prev) => {\n const next = new Set(prev);\n if (next.has(key)) next.delete(key);\n else next.add(key);\n return next;\n });\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 bus.emitCustom('vibecontrols.session.create_clicked', {\n workspaceId: workspaceId ?? undefined,\n });\n };\n\n /**\n * Core create-session + auto-open-terminal flow, shared by the modal\n * submit (`fromModal: true`, flushes draft tags) and the one-click\n * quick-create (`fromModal: false`). On success it remembers the\n * agent/vibe/type/profile so the next \"+\" click can skip the form.\n */\n const createAndOpenSession = async (params: {\n name: string;\n type: CreateSessionType;\n command?: string;\n vibeId?: string;\n agentId: string;\n profile: string;\n autoStart: boolean;\n externalSessionName?: string;\n fromModal: boolean;\n }): Promise<void> => {\n setCreateLoading(true);\n setCreateError(null);\n try {\n const result = await createSession({\n variables: {\n input: {\n name: params.name,\n type: params.type,\n command: params.command || undefined,\n vibeId: params.vibeId || undefined,\n agentId: params.agentId,\n profile: params.profile?.trim() || 'default',\n autoStart: params.autoStart,\n externalSessionName: params.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 bus.emitCustom('vibecontrols.quota.session_limit_hit', {\n workspaceId: workspaceId ?? undefined,\n });\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 // Remember these settings so the next \"+\" click can quick-create\n // without re-prompting the form.\n setLastUsedSessionDefaults({\n agentId: params.agentId,\n vibeId: params.vibeId || null,\n sessionType: params.type,\n profile: params.profile?.trim() || 'default',\n });\n setShowCreateForm(false);\n setSuccessMessage(tr('sessions.createdSuccess', 'Session created successfully'));\n bus.emitCustom('vibecontrols.session.created', {\n workspaceId: workspaceId ?? undefined,\n sessionId: newSessionId,\n });\n // Draft tags only exist for the modal flow (the EntityTagPicker lives\n // in the form); quick-create has none.\n if (params.fromModal) {\n await flushTags(newSessionId);\n }\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 bus.emitCustom('vibecontrols.quota.session_limit_hit', {\n workspaceId: workspaceId ?? undefined,\n });\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 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 const externalSessionName = sessionExternalNameRef.current?.value?.trim() || undefined;\n await createAndOpenSession({\n name,\n type: (type || 'TMUX') as CreateSessionType,\n command: command || undefined,\n vibeId: vibeId || undefined,\n agentId,\n profile: formProfile?.trim() || 'default',\n autoStart,\n externalSessionName,\n fromModal: true,\n });\n };\n\n /**\n * Quick-create the new session: clicking the \"+\" remembers the user's\n * last-used agent/vibe/type and spins up a terminal with an auto-generated\n * name — no form. If no agent can be safely auto-resolved (first use, or\n * the remembered agent is gone/unhealthy), it falls back to the prefilled\n * \"New session with options…\" modal so the user can pick an agent.\n */\n const handleQuickCreateSession = () => {\n setCreateError(null);\n const isHealthyOrUnknown = (id: string) => {\n const row = agentHealthByIdRef.get(id);\n return !row || row.healthy;\n };\n const remembered =\n lastUsedAgentId && cachedAgents.some((a) => a.id === lastUsedAgentId)\n ? lastUsedAgentId\n : null;\n\n let agentId: string | null = null;\n if (remembered && isHealthyOrUnknown(remembered)) {\n agentId = remembered;\n } else if (cachedAgents.length === 1 && isHealthyOrUnknown(cachedAgents[0].id)) {\n agentId = cachedAgents[0].id;\n }\n\n if (!agentId) {\n // Can't safely auto-pick — open the form prefilled with what we know.\n if (remembered) setFormAgentId(remembered);\n if (lastUsedVibeId && cachedVibes.some((v) => v.id === lastUsedVibeId)) {\n setFormVibeId(lastUsedVibeId);\n }\n setFormType(lastUsedSessionType || 'TMUX');\n setFormProfile(lastUsedProfile || 'default');\n setShowCreateForm(true);\n bus.emitCustom('vibecontrols.session.create_clicked', {\n workspaceId: workspaceId ?? undefined,\n });\n return;\n }\n\n // Keep the remembered vibe only if it still exists AND the chosen agent\n // is attached to it (the server resolves the agent from the vibe's\n // attached agents — a mismatch would be rejected).\n let vibeId: string | undefined;\n if (lastUsedVibeId) {\n const vibe = cachedVibes.find((v) => v.id === lastUsedVibeId);\n if (vibe && (vibe.agents ?? []).some((a) => a.id === agentId)) {\n vibeId = lastUsedVibeId;\n }\n }\n\n void createAndOpenSession({\n name: randomSessionName(),\n type: (lastUsedSessionType || 'TMUX') as CreateSessionType,\n vibeId,\n agentId,\n profile: lastUsedProfile || 'default',\n autoStart: false,\n fromModal: 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 bus.emitCustom('vibecontrols.session.started', {\n workspaceId: workspaceId ?? undefined,\n sessionId: session.id,\n });\n } catch (err) {\n if (isQuotaExhaustedError(err)) {\n markQuotaHandledLocally();\n setShowQuotaExhausted(true);\n bus.emitCustom('vibecontrols.quota.session_limit_hit', {\n workspaceId: workspaceId ?? undefined,\n });\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) {\n bus.emitCustom('vibecontrols.session.terminal_opened_in_browser', {\n workspaceId: workspaceId ?? undefined,\n sessionId,\n });\n window.open(tab.terminalUrl, '_blank');\n }\n },\n [terminalTabs, workspaceId, bus]\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 // Reattach to a still-running session (idempotent). Backs the terminal\n // panel's Reconnect affordance: refreshes ttydUrl without stop/start so an\n // idled-out terminal reconnects to the SAME live session on the agent.\n const handleReattachTerminal = useCallback(\n async (sessionId: string) => {\n const session = sessions.find((s) => s.id === sessionId);\n try {\n const result = await startSessionTerminal({ variables: { id: sessionId } });\n if (result.error) return; // panel's fresh-token iframe remount may still recover\n const rawUrl = result.data?.startSessionTerminal?.ttydUrl || null;\n const tunnelHint = session?.agent?.tunnelUrl ?? null;\n const newUrl = normalizeSessionTerminalUrl(rawUrl, tunnelHint);\n if (newUrl) {\n setTerminalTabs((prev) =>\n prev.map((t) =>\n t.sessionId === sessionId\n ? { ...t, terminalUrl: newUrl, loading: false, error: null }\n : t\n )\n );\n }\n } catch {\n // best-effort — the panel's iframe remount alone often re-establishes the WS\n }\n },\n [sessions, startSessionTerminal]\n );\n\n const handleReorderTerminalTabs = useCallback((orderedSessionIds: string[]) => {\n setTerminalTabs((prev) => {\n const byId = new Map(prev.map((t) => [t.sessionId, t]));\n const reordered = orderedSessionIds\n .map((id) => byId.get(id))\n .filter((t): t is TerminalTab => Boolean(t));\n // Defensive: keep any tab not present in the ordered list.\n for (const t of prev) {\n if (!orderedSessionIds.includes(t.sessionId)) reordered.push(t);\n }\n return reordered;\n });\n }, []);\n\n const handleToggleTabPin = useCallback((sessionId: string) => {\n setTerminalTabs((prev) =>\n prev.map((t) => (t.sessionId === sessionId ? { ...t, pinned: !t.pinned } : t))\n );\n }, []);\n\n // Terminal-view feature parity: open info / navigate / share for a tab's\n // session straight from the tab context menu.\n const handleShowSessionInfo = useCallback(\n (sessionId: string) => {\n setActiveTerminalTab(sessionId);\n setSessionInfoPanelOpen(true);\n },\n [setSessionInfoPanelOpen]\n );\n\n const handleViewSessionAgent = useCallback(\n (sessionId: string) => {\n const session = sessions.find((s) => s.id === sessionId);\n if (session?.agent?.id) navigate(`/agents/${session.agent.id}`);\n },\n [sessions, navigate]\n );\n\n const handleViewSessionTarget = useCallback(\n (sessionId: string) => {\n const session = sessions.find((s) => s.id === sessionId);\n if (session?.agent?.target?.id) navigate(`/targets/${session.agent.target.id}`);\n },\n [sessions, navigate]\n );\n\n const handleShareSessionById = useCallback(\n (sessionId: string) => {\n const session = sessions.find((s) => s.id === sessionId);\n if (session) setShareSession(session);\n },\n [sessions]\n );\n\n // Toggle immersive fullscreen mode\n const handleToggleImmersiveFullscreen = useCallback(() => {\n setIsImmersiveFullscreen((prev) => !prev);\n }, []);\n\n // ── Shortcut actions ─────────────────────────────────────────────────\n // Defined once and shared by the keydown handler, the cheatsheet, and the\n // command palette so a binding and its help/label can never drift.\n const activeSession = useMemo(\n () => sessions.find((s) => s.id === activeTerminalTab) ?? null,\n [sessions, activeTerminalTab]\n );\n\n const focusSearch = useCallback(() => {\n searchContainerRef.current?.querySelector('input')?.focus();\n }, []);\n\n const cycleView = useCallback(() => {\n setViewMode((v) => (v === 'terminal' ? 'grid' : v === 'grid' ? 'list' : 'terminal'));\n }, []);\n\n const openActiveAgent = useCallback(() => {\n if (activeSession?.agent?.id) navigate(`/agents/${activeSession.agent.id}`);\n }, [activeSession, navigate]);\n\n const openActiveTarget = useCallback(() => {\n if (activeSession?.agent?.target?.id) navigate(`/targets/${activeSession.agent.target.id}`);\n }, [activeSession, navigate]);\n\n const reconnectActive = useCallback(() => {\n if (activeTerminalTab) void handleReattachTerminal(activeTerminalTab);\n }, [activeTerminalTab, handleReattachTerminal]);\n\n const closeActiveTab = useCallback(() => {\n if (activeTerminalTab) handleCloseTerminalTab(activeTerminalTab);\n }, [activeTerminalTab, handleCloseTerminalTab]);\n\n const renameActiveTab = useCallback(() => {\n if (activeTerminalTab) setRenameTabNonce((n) => n + 1);\n }, [activeTerminalTab]);\n\n const openActiveInNewTab = useCallback(() => {\n if (activeTerminalTab) handleOpenTerminalExternal(activeTerminalTab);\n }, [activeTerminalTab, handleOpenTerminalExternal]);\n\n // Run `fn` (a terminal-tab switch) while pinning the page scroll position.\n // Switching the active tab toggles which iframe is visible and the\n // tab/iframe focus would otherwise scroll the page back to the panel.\n // We snapshot every plausible scroller (the grid scroll container, the\n // app-shell main, and the document) and restore them on the next frame.\n const preserveScroll = useCallback((fn: () => void) => {\n const scrollers: Array<{ el: Element; top: number; left: number }> = [];\n const record = (el: Element | null) => {\n if (el) scrollers.push({ el, top: el.scrollTop, left: el.scrollLeft });\n };\n record(gridScrollRef.current);\n record(document.querySelector('[data-shell-scroll-container]'));\n record(document.scrollingElement);\n fn();\n requestAnimationFrame(() => {\n scrollers.forEach(({ el, top, left }) => {\n el.scrollTop = top;\n el.scrollLeft = left;\n });\n });\n }, []);\n\n const handleSelectTerminalTabPreservingScroll = useCallback(\n (sessionId: string) => {\n preserveScroll(() => setActiveTerminalTab(sessionId));\n },\n [preserveScroll]\n );\n\n // Always-fresh pointer to the quick-create handler so the cheatsheet /\n // command palette never fire a stale closure (the handler is a plain\n // per-render function that reads current store + cached data).\n const quickCreateRef = useRef(handleQuickCreateSession);\n quickCreateRef.current = handleQuickCreateSession;\n\n const openPalette = useCallback(() => {\n setShowCommandPalette(true);\n }, []);\n\n const toggleInfoPanel = useCallback(() => {\n setSessionInfoPanelOpen(!sessionInfoPanelOpen);\n }, [sessionInfoPanelOpen, setSessionInfoPanelOpen]);\n\n // Canonical shortcut registry (drives the cheatsheet + palette labels).\n const sessionShortcuts = useMemo(\n () =>\n buildSessionShortcuts(\n {\n newSession: () => quickCreateRef.current(),\n newSessionWithOptions: () => setShowCreateForm(true),\n renameActiveTab,\n openActiveInNewTab,\n closeActiveTab,\n openPalette,\n openCheatsheet: () => setShowCheatsheet(true),\n focusSearch,\n cycleView,\n groupByNone: () => setGroupBy('none'),\n groupByAgent: () => setGroupBy('agent'),\n groupByTarget: () => setGroupBy('target'),\n groupByVibe: () => setGroupBy('vibe'),\n groupByStatus: () => setGroupBy('status'),\n reconnectActive,\n toggleFullscreen: handleToggleImmersiveFullscreen,\n toggleInfoPanel,\n openActiveAgent,\n openActiveTarget,\n },\n tr\n ),\n [\n renameActiveTab,\n openActiveInNewTab,\n closeActiveTab,\n openPalette,\n focusSearch,\n cycleView,\n reconnectActive,\n handleToggleImmersiveFullscreen,\n toggleInfoPanel,\n openActiveAgent,\n openActiveTarget,\n tr,\n ]\n );\n\n // ── Keyboard handler ─────────────────────────────────────────────────\n // Single raw listener (full control over Tab/digit combos + the `g`\n // sequence). Keystrokes inside the ttyd terminal fire in the iframe's own\n // document and never reach this listener, so single-key shortcuts are\n // safe while a terminal is focused.\n const seqRef = useRef<{ key: string; at: number } | null>(null);\n useEffect(() => {\n const handleKeyDown = (e: KeyboardEvent) => {\n const target = e.target as HTMLElement;\n const inInput =\n target.tagName === 'INPUT' ||\n target.tagName === 'TEXTAREA' ||\n target.tagName === 'SELECT' ||\n target.isContentEditable;\n\n const isMod = e.metaKey || e.ctrlKey;\n\n // Command palette — Cmd/Ctrl+K or Cmd/Ctrl+P (works even in inputs).\n if (isMod && ['k', 'p'].includes(e.key.toLowerCase())) {\n e.preventDefault();\n openPalette();\n return;\n }\n\n if (inInput) return;\n\n // `g`-prefixed sequences (group-by + go-to).\n const now = Date.now();\n const seq = seqRef.current;\n if (seq && seq.key === 'g' && now - seq.at < 1000) {\n seqRef.current = null;\n switch (e.key) {\n case 'a':\n e.preventDefault();\n setGroupBy('agent');\n return;\n case 't':\n e.preventDefault();\n setGroupBy('target');\n return;\n case 'v':\n e.preventDefault();\n setGroupBy('vibe');\n return;\n case 's':\n e.preventDefault();\n setGroupBy('status');\n return;\n case '0':\n e.preventDefault();\n setGroupBy('none');\n return;\n case 'A':\n e.preventDefault();\n openActiveAgent();\n return;\n case 'T':\n e.preventDefault();\n openActiveTarget();\n return;\n default:\n break;\n }\n }\n if (!isMod && !e.altKey && e.key === 'g') {\n seqRef.current = { key: 'g', at: now };\n return;\n }\n\n // New session with options (full form) — ⌘/Ctrl+Shift+T\n if (isMod && e.shiftKey && e.key.toLowerCase() === 't') {\n e.preventDefault();\n setShowCreateForm(true);\n return;\n }\n // New session (quick, one-click) — ⌘/Ctrl+T\n if (isMod && !e.shiftKey && e.key.toLowerCase() === 't') {\n e.preventDefault();\n quickCreateRef.current();\n return;\n }\n // Open active terminal in a new browser tab — ⌘/Ctrl+O\n if (isMod && !e.shiftKey && e.key.toLowerCase() === 'o' && activeTerminalTab) {\n e.preventDefault();\n openActiveInNewTab();\n return;\n }\n // Close active tab\n if (isMod && e.key.toLowerCase() === 'w' && activeTerminalTab) {\n e.preventDefault();\n closeActiveTab();\n return;\n }\n // Next / previous tab — Ctrl+Tab / Ctrl+Shift+Tab, and the iTerm2/Chrome\n // alternate ⌘/Ctrl+Shift+] / [ (e.code is layout-independent).\n const moveTab = (delta: number) => {\n if (terminalTabs.length < 2) return;\n const i = terminalTabs.findIndex((t) => t.sessionId === activeTerminalTab);\n const base = i < 0 ? 0 : i;\n const nextIdx = (base + delta + terminalTabs.length) % terminalTabs.length;\n setActiveTerminalTab(terminalTabs[nextIdx].sessionId);\n };\n if (e.ctrlKey && e.key === 'Tab' && !e.shiftKey && terminalTabs.length > 1) {\n e.preventDefault();\n moveTab(1);\n return;\n }\n if (e.ctrlKey && e.key === 'Tab' && e.shiftKey && terminalTabs.length > 1) {\n e.preventDefault();\n moveTab(-1);\n return;\n }\n if (isMod && e.shiftKey && e.code === 'BracketRight' && terminalTabs.length > 1) {\n e.preventDefault();\n moveTab(1);\n return;\n }\n if (isMod && e.shiftKey && e.code === 'BracketLeft' && terminalTabs.length > 1) {\n e.preventDefault();\n moveTab(-1);\n return;\n }\n // Jump to tab N\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 // Reconnect active terminal (guard browser refresh)\n if (isMod && e.key.toLowerCase() === 'r' && activeTerminalTab) {\n e.preventDefault();\n reconnectActive();\n return;\n }\n // Cycle view\n if (isMod && e.key === '\\\\') {\n e.preventDefault();\n cycleView();\n return;\n }\n if (isMod) return; // leave other mod-combos to the browser\n\n // Single-key shortcuts (outside inputs / terminal iframe).\n if (e.key === '/') {\n e.preventDefault();\n focusSearch();\n return;\n }\n if (e.key === 'f') {\n e.preventDefault();\n handleToggleImmersiveFullscreen();\n return;\n }\n if (e.key === 'i') {\n e.preventDefault();\n toggleInfoPanel();\n return;\n }\n // Rename the active tab — F2 (universal rename convention).\n if (e.key === 'F2' && activeTerminalTab) {\n e.preventDefault();\n renameActiveTab();\n return;\n }\n if (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 }, [\n activeTerminalTab,\n terminalTabs,\n closeActiveTab,\n renameActiveTab,\n openActiveInNewTab,\n reconnectActive,\n cycleView,\n focusSearch,\n openPalette,\n openActiveAgent,\n openActiveTarget,\n toggleInfoPanel,\n handleToggleImmersiveFullscreen,\n ]);\n\n // Command palette command list — actions + jump-to-session, sharing the\n // same handlers as the shortcuts.\n const paletteCommands = useMemo<PaletteCommand[]>(() => {\n const actionsGroup = tr('sessions.palette.groupActions', 'Actions');\n const viewGroup = tr('sessions.palette.groupView', 'View');\n const navGroup = tr('sessions.palette.groupNav', 'Navigation');\n const sessionsGroup = tr('sessions.palette.groupSessions', 'Sessions');\n\n const cmds: PaletteCommand[] = [\n {\n id: 'new-session',\n label: tr('sessions.palette.newSessionQuick', 'New terminal (quick)'),\n group: actionsGroup,\n icon: <Plus className=\"size-4\" />,\n shortcut: '⌘/Ctrl T',\n run: () => quickCreateRef.current(),\n },\n {\n id: 'new-session-options',\n label: tr('sessions.newSessionWithOptions', 'New session with options…'),\n group: actionsGroup,\n icon: <SlidersHorizontal className=\"size-4\" />,\n shortcut: '⌘/Ctrl ⇧ T',\n run: () => setShowCreateForm(true),\n },\n ...(activeTerminalTab\n ? [\n {\n id: 'rename-tab',\n label: tr('sessions.sc.renameTab', 'Rename active tab'),\n group: actionsGroup,\n icon: <Keyboard className=\"size-4\" />,\n shortcut: 'F2',\n run: renameActiveTab,\n },\n {\n id: 'open-tab-new',\n label: tr('sessions.sc.openTabNew', 'Open active tab in new browser tab'),\n group: actionsGroup,\n icon: <Share2 className=\"size-4\" />,\n shortcut: '⌘/Ctrl O',\n run: openActiveInNewTab,\n },\n ]\n : []),\n {\n id: 'reconnect',\n label: tr('sessions.palette.reconnect', 'Reconnect active terminal'),\n group: actionsGroup,\n icon: <RotateCcw className=\"size-4\" />,\n run: reconnectActive,\n },\n {\n id: 'refresh',\n label: tr('common.refresh', 'Refresh'),\n group: actionsGroup,\n icon: <RefreshCw className=\"size-4\" />,\n run: () => void refetch(),\n },\n ...(activeSession\n ? [\n {\n id: 'share-active',\n label: tr('sessions.palette.shareActive', 'Share / offer active session'),\n group: actionsGroup,\n icon: <Share2 className=\"size-4\" />,\n run: () => setShareSession(activeSession),\n },\n ]\n : []),\n // View\n {\n id: 'view-terminal',\n label: tr('sessions.palette.viewTerminal', 'Switch to terminal view'),\n group: viewGroup,\n icon: <Monitor className=\"size-4\" />,\n run: () => setViewMode('terminal'),\n },\n {\n id: 'view-grid',\n label: tr('sessions.palette.viewGrid', 'Switch to grid view'),\n group: viewGroup,\n icon: <Grid className=\"size-4\" />,\n run: () => setViewMode('grid'),\n },\n {\n id: 'view-list',\n label: tr('sessions.palette.viewList', 'Switch to list view'),\n group: viewGroup,\n icon: <List className=\"size-4\" />,\n run: () => setViewMode('list'),\n },\n {\n id: 'group-agent',\n label: tr('sessions.palette.groupByAgent', 'Group by agent'),\n group: viewGroup,\n icon: <Server className=\"size-4\" />,\n run: () => setGroupBy('agent'),\n },\n {\n id: 'group-target',\n label: tr('sessions.palette.groupByTarget', 'Group by target'),\n group: viewGroup,\n icon: <MonitorSmartphone className=\"size-4\" />,\n run: () => setGroupBy('target'),\n },\n {\n id: 'group-vibe',\n label: tr('sessions.palette.groupByVibe', 'Group by vibe'),\n group: viewGroup,\n icon: <Layers className=\"size-4\" />,\n run: () => setGroupBy('vibe'),\n },\n {\n id: 'group-none',\n label: tr('sessions.palette.groupClear', 'Clear grouping'),\n group: viewGroup,\n icon: <Layers className=\"size-4\" />,\n run: () => setGroupBy('none'),\n },\n {\n id: 'toggle-info',\n label: tr('sessions.palette.toggleInfo', 'Toggle session info panel'),\n group: viewGroup,\n icon: <Info className=\"size-4\" />,\n run: toggleInfoPanel,\n },\n {\n id: 'shortcuts',\n label: tr('sessions.palette.shortcuts', 'Keyboard shortcuts'),\n group: viewGroup,\n icon: <Keyboard className=\"size-4\" />,\n run: () => setShowCheatsheet(true),\n },\n // Navigation\n ...(activeSession?.agent\n ? [\n {\n id: 'goto-agent',\n label: tr('sessions.palette.gotoAgent', \"Open active session's agent\"),\n group: navGroup,\n icon: <Server className=\"size-4\" />,\n run: openActiveAgent,\n },\n ]\n : []),\n ...(activeSession?.agent?.target\n ? [\n {\n id: 'goto-target',\n label: tr('sessions.palette.gotoTarget', \"Open active session's target\"),\n group: navGroup,\n icon: <MonitorSmartphone className=\"size-4\" />,\n run: openActiveTarget,\n },\n ]\n : []),\n ];\n\n // Jump-to-session (open terminal) for the loaded sessions.\n for (const s of filteredSessions.slice(0, 30)) {\n cmds.push({\n id: `open-${s.id}`,\n label: s.name,\n group: sessionsGroup,\n icon: <Terminal className=\"size-4\" />,\n keywords: `${s.status} ${s.agent?.name ?? ''} ${s.agent?.target?.name ?? ''}`,\n run: () => void handleOpenTerminal(s),\n });\n }\n return cmds;\n }, [\n tr,\n activeSession,\n activeTerminalTab,\n renameActiveTab,\n openActiveInNewTab,\n filteredSessions,\n reconnectActive,\n refetch,\n toggleInfoPanel,\n openActiveAgent,\n openActiveTarget,\n handleOpenTerminal,\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/plans');\n }}\n title={getQuotaDialogContent('session').title}\n message={getQuotaDialogContent('session').message}\n confirmLabel=\"View Billing\"\n cancelLabel=\"Dismiss\"\n variant=\"warning\"\n />\n <ShortcutCheatsheet\n isOpen={showCheatsheet}\n onClose={() => setShowCheatsheet(false)}\n shortcuts={sessionShortcuts}\n />\n <SessionsCommandPalette\n open={showCommandPalette}\n onOpenChange={setShowCommandPalette}\n commands={paletteCommands}\n />\n <SessionInfoPanel\n open={sessionInfoPanelOpen}\n onOpenChange={setSessionInfoPanelOpen}\n session={activeSession}\n />\n </>\n );\n\n // Terminal-first view\n if (viewMode === 'terminal') {\n return (\n <MotionPage className=\"flex flex-col bg-bg-surface min-h-0 flex-1\">\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 {/* Connection status merged into the header (was a separate\n \"Agent Notifications\" row that wasted vertical space). */}\n <SessionConnectionStatus />\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 <button\n type=\"button\"\n onClick={() => setShowCheatsheet(true)}\n className=\"p-1.5 text-text-secondary hover:bg-bg-surface rounded transition-colors\"\n title={tr('sessions.keyboardShortcuts', 'Keyboard shortcuts (?)')}\n aria-label={tr('sessions.keyboardShortcuts', 'Keyboard shortcuts (?)')}\n data-testid=\"sessions-shortcuts-button\"\n >\n <Keyboard className=\"size-3.5\" />\n </button>\n {viewToggle}\n </div>\n </div>\n\n {/* Agent notifications — compact: only takes space when there are\n notifications; the status chip lives in the header above. The\n `empty:hidden` collapses the padded wrapper when the banner\n renders nothing. */}\n <div className=\"px-4 pt-3 empty:hidden empty:p-0\">\n <SessionNotifications compact 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 onReattachTerminal={handleReattachTerminal}\n onReorderTabs={handleReorderTerminalTabs}\n onTogglePin={handleToggleTabPin}\n onShowSessionInfo={handleShowSessionInfo}\n onViewAgent={handleViewSessionAgent}\n onViewTarget={handleViewSessionTarget}\n onShareSession={canUpdateSession ? handleShareSessionById : undefined}\n onCreateSession={handleCreateSession}\n onQuickCreate={canCreateSession ? handleQuickCreateSession : undefined}\n renameActiveTabNonce={renameTabNonce}\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-[28ch]\">\n {terminalTabs.find((t) => t.sessionId === activeTerminalTab)?.name}\n </span>\n )}\n {/* Active session's agent + target — visible context in\n terminal view, with click-through to detail popovers. */}\n {activeSession?.agent && (\n <span className=\"hidden md:inline-flex items-center gap-1\">\n <span className=\"text-text-muted\">·</span>\n <AgentInfoPopover agent={activeSession.agent} />\n {activeSession.agent.target && (\n <TargetInfoPopover target={activeSession.agent.target} />\n )}\n </span>\n )}\n </div>\n <div className=\"hidden sm:flex items-center gap-3\">\n <button\n type=\"button\"\n onClick={() => setSessionInfoPanelOpen(!sessionInfoPanelOpen)}\n className=\"inline-flex items-center gap-1 hover:text-text-secondary transition-colors\"\n title={tr('sessions.infoPanelHint', 'Session info (i)')}\n >\n <Info className=\"size-3\" />\n {tr('sessions.infoPanelLabel', 'Info')}\n </button>\n {activeSession && (\n // Generic Share entry point for the active session. Self-gates on\n // the `session:share` RBAC permission + the\n // `vibecontrols.sharing.session` feature flag, then opens the\n // generic `<ShareDialog>` (email invite + accept-gate + four\n // visibility modes + expiry + public link) backed by the generic\n // `shareSessionGrant` mutations. This ONLY manages share grants —\n // live terminal control handoff + collaborator presence remain in\n // `SharedTerminalPanel` / `SharedSessionPage`, untouched.\n <EntityShareButton\n entity=\"session\"\n resource={{\n id: activeSession.id,\n name: activeSession.name,\n vibeId: activeSession.vibeId ?? null,\n }}\n label={tr('sessions.shareOfferLabel', 'Share')}\n variant=\"button\"\n />\n )}\n <button\n type=\"button\"\n onClick={() => setShowCheatsheet(true)}\n className=\"inline-flex items-center gap-1 hover:text-text-secondary transition-colors\"\n >\n <Keyboard className=\"size-3\" />\n {tr('sessions.shortcutsHint', 'Press ? for shortcuts')}\n </button>\n <button\n type=\"button\"\n onClick={openPalette}\n className=\"hover:text-text-secondary transition-colors\"\n >\n {tr('sessions.paletteHint', '⌘/Ctrl K · 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 {overlayDialogs}\n </MotionPage>\n );\n }\n\n // Render helpers shared by flat + grouped layouts.\n const renderSessionGrid = (items: Session[]) => (\n <MotionList stagger={0.04} className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\">\n {items.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 // Suppress the inline Share button entirely when the viewer\n // lacks session:update — the child component already treats an\n // undefined `onShare` as \"don't render the button\".\n onShare={canUpdateSession ? handleShareSession : undefined}\n selected={selectedSessionIds.has(session.id)}\n onToggleSelect={() => toggleSessionSelection(session.id)}\n />\n </MotionItem>\n ))}\n </MotionList>\n );\n\n const renderSessionListView = (items: Session[], grouped: boolean) => (\n <SessionList\n sessions={items}\n onOpen={handleSessionSelect}\n onStart={handleStartSession}\n onStop={handleStopSession}\n onRestart={handleRestartSession}\n onTerminate={handleTerminateSession}\n onOpenTerminal={handleOpenTerminal}\n // Same suppression as the grid view above — hide Share when the\n // viewer can't `session:update`.\n onShare={canUpdateSession ? handleShareSession : undefined}\n selectedIds={selectedSessionIds}\n onToggleSelect={toggleSessionSelection}\n onSelectAll={selectAllSessions}\n onDeselectAll={deselectAllSessions}\n hideHeaderSelectAll={grouped}\n />\n );\n\n // Grid/List view\n return (\n <MotionPage className=\"flex flex-col bg-bg-surface min-h-0 flex-1\">\n <div ref={gridScrollRef} 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 {/* Connection status merged into the header (replaces the\n separate \"Agent Notifications\" row). */}\n <div className=\"flex items-center self-center\">\n <SessionConnectionStatus />\n </div>\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 {/* Split button: primary quick-creates a terminal (no form,\n remembered defaults); the caret opens options. */}\n <div className=\"relative flex items-stretch\" ref={headerCreateMenuRef}>\n <button\n type=\"button\"\n onClick={handleQuickCreateSession}\n className=\"flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-l-md hover:bg-action-primary-bg-hover transition-colors\"\n title={tr('sessions.quickNewSessionHint', 'New terminal (⌘/Ctrl+T)')}\n data-testid=\"sessions-new-session\"\n >\n <Plus className=\"size-4\" />\n {tr('sessions.newSession', 'New Session')}\n </button>\n <button\n type=\"button\"\n onClick={() => setShowHeaderCreateMenu((p) => !p)}\n className=\"flex items-center px-2 bg-action-primary-bg text-action-primary-text rounded-r-md border-l border-action-primary-text/20 hover:bg-action-primary-bg-hover transition-colors\"\n aria-haspopup=\"menu\"\n aria-expanded={showHeaderCreateMenu}\n title={tr('sessions.newSessionOptions', 'New session options')}\n data-testid=\"sessions-new-session-menu\"\n >\n <ChevronDown className=\"size-4\" />\n </button>\n {showHeaderCreateMenu && (\n <div\n role=\"menu\"\n className=\"absolute right-0 top-full mt-1 z-50 w-64 bg-bg-elevated border border-border-default rounded-md shadow-xl py-1\"\n >\n <button\n type=\"button\"\n role=\"menuitem\"\n onClick={() => {\n setShowHeaderCreateMenu(false);\n handleCreateSession();\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors text-left\"\n >\n <SlidersHorizontal className=\"size-3.5 text-text-secondary\" />\n {tr('sessions.newSessionWithOptions', 'New session with options…')}\n </button>\n <button\n type=\"button\"\n role=\"menuitem\"\n onClick={() => {\n setShowHeaderCreateMenu(false);\n setShowReconnectModal(true);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors text-left\"\n >\n <RefreshCw className=\"size-3.5 text-text-secondary\" />\n {tr('sessions.reconnectExisting', 'Reconnect existing session…')}\n </button>\n </div>\n )}\n </div>\n </PermissionButton>\n <button\n type=\"button\"\n onClick={() => setShowCheatsheet(true)}\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.keyboardShortcuts', 'Keyboard shortcuts (?)')}\n aria-label={tr('sessions.keyboardShortcuts', 'Keyboard shortcuts (?)')}\n data-testid=\"sessions-shortcuts-button\"\n >\n <Keyboard className=\"size-4\" />\n </button>\n {viewToggle}\n </div>\n }\n />\n\n {/* Agent notifications — compact: the connection status is in the\n page header, so this only takes space when notifications exist. */}\n <SessionNotifications compact 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\n ref={searchContainerRef}\n className=\"flex flex-col sm:flex-row items-stretch sm:items-center gap-2\"\n >\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 <FilterDropdown\n label={tr('sessions.filterTarget', 'Target')}\n value={targetFilter}\n options={targetOptions}\n onChange={setTargetFilter}\n icon={<MonitorSmartphone className=\"size-3.5 text-text-secondary\" />}\n />\n <div className=\"sm:ml-auto\">\n <FilterDropdown\n label={tr('sessions.groupByLabel', 'Group')}\n value={groupBy}\n options={groupByOptions}\n onChange={(v) => setGroupBy(v as SessionsGroupBy)}\n allValue=\"none\"\n allLabel={tr('sessions.groupBy.none', 'No grouping')}\n icon={<Layers className=\"size-3.5 text-text-secondary\" />}\n />\n </div>\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 targetFilter !== '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 groupBy !== 'none' ? (\n <div key=\"grouped-view\" className=\"space-y-3\" data-testid=\"sessions-grouped\">\n {groupedSessions.map((group) => {\n const ids = group.sessions.map((s) => s.id);\n const allSelected =\n ids.length > 0 && ids.every((id) => selectedSessionIds.has(id));\n const someSelected = ids.some((id) => selectedSessionIds.has(id));\n return (\n <SessionGroup\n key={group.key}\n label={group.label}\n count={group.sessions.length}\n collapsed={collapsedGroups.has(group.key)}\n onToggleCollapse={() => toggleGroupCollapse(group.key)}\n allSelected={allSelected}\n someSelected={someSelected}\n onToggleSelectGroup={() => toggleGroupSelection(ids)}\n >\n {viewMode === 'grid'\n ? renderSessionGrid(group.sessions)\n : renderSessionListView(group.sessions, true)}\n </SessionGroup>\n );\n })}\n </div>\n ) : viewMode === 'grid' ? (\n <div key=\"grid-view\">{renderSessionGrid(filteredSessions)}</div>\n ) : (\n <MotionItem key=\"list-view\">\n {renderSessionListView(filteredSessions, false)}\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 targetFilter !== '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 targetFilter !== '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 targetFilter === 'all' ? (\n <div className=\"flex items-center gap-2\">\n {canCreateSession && (\n <button\n type=\"button\"\n onClick={handleQuickCreateSession}\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={handleSelectTerminalTabPreservingScroll}\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 onReattachTerminal={handleReattachTerminal}\n onReorderTabs={handleReorderTerminalTabs}\n onTogglePin={handleToggleTabPin}\n onShowSessionInfo={handleShowSessionInfo}\n onViewAgent={handleViewSessionAgent}\n onViewTarget={handleViewSessionTarget}\n onShareSession={canUpdateSession ? handleShareSessionById : undefined}\n onCreateSession={canCreateSession ? handleCreateSession : undefined}\n onQuickCreate={canCreateSession ? handleQuickCreateSession : undefined}\n renameActiveTabNonce={renameTabNonce}\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 {formType === 'TERMINAL' && (\n <p className=\"mt-1 text-xs text-text-muted\">\n {tr(\n 'sessions.terminalTypeHint',\n 'Plain terminal — a shell over ttyd with no tmux/wezterm/zellij multiplexer and no persisted session. Native drag-to-select copy/paste.'\n )}\n </p>\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 Layers,\n MonitorSmartphone,\n Server,\n Share2,\n Keyboard,\n Info,\n ChevronDown,\n SlidersHorizontal,\n} from 'lucide-react';\nimport { useEventBus } from '@burdenoff/fe-libs/shared/events';\nimport { MotionPage, MotionList, MotionItem } from '@burdenoff/fe-libs/shared/motion';\nimport { useSafeShellUi } from '@burdenoff/fe-libs/shared/providers/shell/ShellUiProvider';\nimport { AnimatePresence } from 'framer-motion';\nimport {\n SessionCard,\n SessionList,\n TerminalPanel,\n SessionShareDialog,\n SessionGroup,\n groupSessions,\n ShortcutCheatsheet,\n SessionsCommandPalette,\n SessionInfoPanel,\n AgentInfoPopover,\n TargetInfoPopover,\n buildSessionShortcuts,\n} from '@/components/sessions';\nimport type {\n Session,\n TerminalTab,\n TargetType,\n SessionsGroupBy,\n PaletteCommand,\n} from '@/components/sessions';\nimport { getAgentStatus } from '@/components/agents/agentStatus';\nimport { EntityShareButton } from '@/components/sharing';\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 SessionConnectionStatus,\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';\nimport { randomSessionName } from '@/utils/randomSessionName';\n\ntype ViewMode = 'terminal' | 'grid' | 'list';\n\n/** The GraphQL SessionType union accepted by the createSession mutation. */\ntype CreateSessionType = 'TMUX' | 'WEZTERM' | 'ZELLIJ' | 'TERMINAL' | 'SSH' | 'SCRIPT' | 'CUSTOM';\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 bus = useEventBus();\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 // Session sharing (create / revoke share link, share-link list) is gated\n // on `session:update` per the @rbac directives in session-sharing.gql.\n // Used below to suppress the Share button so viewers don't trip a Forbidden.\n const canUpdateSession = hasPermission(VIBE_PERMISSIONS.SESSION_UPDATE);\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 [targetFilter, setTargetFilter] = useState('all');\n const [groupBy, setGroupBy] = useState<SessionsGroupBy>('none');\n // Collapsed grouping headers — render-only, not persisted (matches the\n // AgentManager sidebar precedent).\n const [collapsedGroups, setCollapsedGroups] = useState<Set<string>>(() => new Set());\n const [viewMode, setViewMode] = useState<ViewMode>('terminal');\n const [showCreateForm, setShowCreateForm] = useState(false);\n // Split \"New Session\" button: the caret opens this menu (options form /\n // reconnect existing). The primary button quick-creates.\n const [showHeaderCreateMenu, setShowHeaderCreateMenu] = useState(false);\n const headerCreateMenuRef = useRef<HTMLDivElement>(null);\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 // Wraps the search input so the `/` shortcut can focus it.\n const searchContainerRef = useRef<HTMLDivElement>(null);\n // The grid/list scroll container — used to preserve scroll position when\n // clicking a docked terminal tab (the tab/iframe focus would otherwise\n // scroll the page).\n const gridScrollRef = useRef<HTMLDivElement>(null);\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 // Close the split-button \"New Session\" caret menu on outside click.\n useEffect(() => {\n if (!showHeaderCreateMenu) return;\n const onClick = (e: MouseEvent) => {\n if (headerCreateMenuRef.current && !headerCreateMenuRef.current.contains(e.target as Node)) {\n setShowHeaderCreateMenu(false);\n }\n };\n document.addEventListener('mousedown', onClick);\n return () => document.removeEventListener('mousedown', onClick);\n }, [showHeaderCreateMenu]);\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 sessionsGroupBy: persistedGroupBy,\n sessionsTargetFilter: persistedTargetFilter,\n setTerminalTabs: setPersistedTabs,\n setActiveTerminalTabId: setPersistedActiveTab,\n setSessionsViewMode: setPersistedViewMode,\n setSessionsGroupBy: setPersistedGroupBy,\n setSessionsTargetFilter: setPersistedTargetFilter,\n sessionInfoPanelOpen,\n setSessionInfoPanelOpen,\n activeSessionId,\n lastUsedAgentId,\n lastUsedVibeId,\n lastUsedSessionType,\n lastUsedProfile,\n setLastUsedSessionDefaults,\n } = useVibeControlsStore();\n\n // In terminal view, reclaim the whole content area for the terminal: collapse\n // the app-shell breadcrumb AND hide the tall marketing footer (rendered in\n // content via footerInContent) which otherwise squeezes the flex-1 terminal\n // down to a sliver. Both restore to their prior values on leave/unmount.\n // Read via a ref so the effect depends only on `viewMode` (the shell context\n // value identity changes whenever a flag flips, which would loop the effect).\n const shellUi = useSafeShellUi();\n const shellUiRef = useRef(shellUi);\n shellUiRef.current = shellUi;\n useEffect(() => {\n const ui = shellUiRef.current;\n if (!ui || viewMode !== 'terminal') return;\n const prevBreadcrumb = ui.isBreadcrumbCollapsed;\n const prevFooter = ui.footerVisible;\n ui.setBreadcrumbCollapsed(true);\n ui.setFooterVisible(false);\n return () => {\n shellUiRef.current?.setBreadcrumbCollapsed(prevBreadcrumb);\n shellUiRef.current?.setFooterVisible(prevFooter);\n };\n }, [viewMode]);\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 [terminalCollapsed, setTerminalCollapsed] = useState(false);\n // Bumped by the F2 shortcut to tell the TerminalPanel to start renaming\n // its active tab (rename state lives inside the panel).\n const [renameTabNonce, setRenameTabNonce] = useState(0);\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 const queryGroupBy = searchParams.get('group');\n const queryTargetId = searchParams.get('target');\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 // Grouping + target filter: query params win (bookmarkable), else\n // fall back to the persisted UI preference.\n const validGroupBy: SessionsGroupBy[] = ['none', 'agent', 'target', 'vibe', 'status', 'type'];\n if (queryGroupBy && (validGroupBy as string[]).includes(queryGroupBy)) {\n setGroupBy(queryGroupBy as SessionsGroupBy);\n } else if (persistedGroupBy) {\n setGroupBy(persistedGroupBy);\n }\n if (queryTargetId) {\n setTargetFilter(queryTargetId);\n } else if (persistedTargetFilter) {\n setTargetFilter(persistedTargetFilter);\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 pinned: t.pinned,\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 persistedGroupBy,\n persistedTargetFilter,\n querySessionId,\n queryViewMode,\n queryGroupBy,\n queryTargetId,\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 (sessionId, name + pinned for persistence;\n // tab order is the array order, so reorder persists for free).\n const tabsToStore = terminalTabs.map((t) => ({\n sessionId: t.sessionId,\n name: t.name,\n pinned: t.pinned,\n }));\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 setPersistedGroupBy(groupBy);\n }, [groupBy, uiStateLoaded, setPersistedGroupBy]);\n\n useEffect(() => {\n if (!uiStateLoaded) return;\n setPersistedTargetFilter(targetFilter);\n }, [targetFilter, uiStateLoaded, setPersistedTargetFilter]);\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 (groupBy !== 'none') {\n if (next.get('group') !== groupBy) {\n next.set('group', groupBy);\n changed = true;\n }\n } else if (next.has('group')) {\n next.delete('group');\n changed = true;\n }\n\n if (targetFilter !== 'all') {\n if (next.get('target') !== targetFilter) {\n next.set('target', targetFilter);\n changed = true;\n }\n } else if (next.has('target')) {\n next.delete('target');\n changed = true;\n }\n\n if (changed) {\n setSearchParams(next, { replace: true });\n }\n }, [\n activeTerminalTab,\n searchParams,\n setSearchParams,\n uiStateLoaded,\n viewMode,\n groupBy,\n targetFilter,\n ]);\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 are installed further down (after the tab/terminal\n // action handlers are defined) so the handler can reference them.\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 // Group-by options for the Sessions grid/list views.\n const groupByOptions = useMemo(\n () => [\n { value: 'agent', label: tr('sessions.groupBy.agent', 'Agent') },\n { value: 'target', label: tr('sessions.groupBy.target', 'Target') },\n { value: 'vibe', label: tr('sessions.groupBy.vibe', 'Vibe') },\n { value: 'status', label: tr('sessions.groupBy.status', 'Status') },\n { value: 'type', label: tr('sessions.groupBy.type', 'Type') },\n ],\n [tr]\n );\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 hostname: session.agent.hostname ?? null,\n platform: session.agent.platform ?? null,\n tunnelUrl: session.agent.tunnelUrl || null,\n tunnelStatus: session.agent.tunnelStatus ?? null,\n isActive: session.agent.isActive,\n lastHeartbeat: session.agent.lastHeartbeat ?? null,\n target: session.agent.target\n ? {\n id: session.agent.target.id,\n name: session.agent.target.name,\n // Generated `TargetType` is a string enum; normalize to the\n // domain string-literal union for grouping / badge logic.\n type: String(session.agent.target.type) as TargetType,\n status: session.agent.target.status,\n host: session.agent.target.host ?? null,\n port: session.agent.target.port ?? null,\n }\n : null,\n }\n : undefined,\n };\n });\n }, [effectiveData?.sessions?.items]);\n\n // Build target options from the targets present on loaded sessions'\n // agents. SessionFilterInput has no targetId, so this filter is applied\n // client-side over the loaded page (consistent with the vibe \"none\"\n // filter). Distinct by target id.\n const targetOptions = useMemo(() => {\n const seen = new Map<string, string>();\n for (const session of sessions) {\n const target = session.agent?.target;\n if (target && !seen.has(target.id)) seen.set(target.id, target.name);\n }\n return [\n { value: 'all', label: tr('sessions.filterTargetAll', 'All Targets') },\n ...Array.from(seen.entries()).map(([id, name]) => ({ value: id, label: name })),\n ];\n }, [sessions, tr]);\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 // Target filter is client-side (no targetId on SessionFilterInput).\n if (targetFilter !== 'all' && session.agent?.target?.id !== targetFilter) return false;\n return true;\n });\n }, [sessions, searchQuery, vibeFilter, targetFilter]);\n\n // Grouped partition for grid/list views. Render-only — selection stays\n // on the page-level Set.\n const groupedSessions = useMemo(\n () => groupSessions(filteredSessions, groupBy),\n [filteredSessions, groupBy]\n );\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 // Toggle every session in a group: if all are already selected, clear\n // them; otherwise add them. Mutates the same page-level Set so the bulk\n // action bar and global select-all keep working unchanged.\n const toggleGroupSelection = useCallback((ids: string[]) => {\n setSelectedSessionIds((prev) => {\n const next = new Set(prev);\n const allSelected = ids.length > 0 && ids.every((id) => next.has(id));\n if (allSelected) {\n ids.forEach((id) => next.delete(id));\n } else {\n ids.forEach((id) => next.add(id));\n }\n return next;\n });\n }, []);\n\n const toggleGroupCollapse = useCallback((key: string) => {\n setCollapsedGroups((prev) => {\n const next = new Set(prev);\n if (next.has(key)) next.delete(key);\n else next.add(key);\n return next;\n });\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 bus.emitCustom('vibecontrols.session.create_clicked', {\n workspaceId: workspaceId ?? undefined,\n });\n };\n\n /**\n * Core create-session + auto-open-terminal flow, shared by the modal\n * submit (`fromModal: true`, flushes draft tags) and the one-click\n * quick-create (`fromModal: false`). On success it remembers the\n * agent/vibe/type/profile so the next \"+\" click can skip the form.\n */\n const createAndOpenSession = async (params: {\n name: string;\n type: CreateSessionType;\n command?: string;\n vibeId?: string;\n agentId: string;\n profile: string;\n autoStart: boolean;\n externalSessionName?: string;\n fromModal: boolean;\n }): Promise<void> => {\n setCreateLoading(true);\n setCreateError(null);\n try {\n const result = await createSession({\n variables: {\n input: {\n name: params.name,\n type: params.type,\n command: params.command || undefined,\n vibeId: params.vibeId || undefined,\n agentId: params.agentId,\n profile: params.profile?.trim() || 'default',\n autoStart: params.autoStart,\n externalSessionName: params.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 bus.emitCustom('vibecontrols.quota.session_limit_hit', {\n workspaceId: workspaceId ?? undefined,\n });\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 // Remember these settings so the next \"+\" click can quick-create\n // without re-prompting the form.\n setLastUsedSessionDefaults({\n agentId: params.agentId,\n vibeId: params.vibeId || null,\n sessionType: params.type,\n profile: params.profile?.trim() || 'default',\n });\n setShowCreateForm(false);\n setSuccessMessage(tr('sessions.createdSuccess', 'Session created successfully'));\n bus.emitCustom('vibecontrols.session.created', {\n workspaceId: workspaceId ?? undefined,\n sessionId: newSessionId,\n });\n // Draft tags only exist for the modal flow (the EntityTagPicker lives\n // in the form); quick-create has none.\n if (params.fromModal) {\n await flushTags(newSessionId);\n }\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 bus.emitCustom('vibecontrols.quota.session_limit_hit', {\n workspaceId: workspaceId ?? undefined,\n });\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 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 const externalSessionName = sessionExternalNameRef.current?.value?.trim() || undefined;\n await createAndOpenSession({\n name,\n type: (type || 'TMUX') as CreateSessionType,\n command: command || undefined,\n vibeId: vibeId || undefined,\n agentId,\n profile: formProfile?.trim() || 'default',\n autoStart,\n externalSessionName,\n fromModal: true,\n });\n };\n\n /**\n * Quick-create the new session: clicking the \"+\" remembers the user's\n * last-used agent/vibe/type and spins up a terminal with an auto-generated\n * name — no form. If no agent can be safely auto-resolved (first use, or\n * the remembered agent is gone/unhealthy), it falls back to the prefilled\n * \"New session with options…\" modal so the user can pick an agent.\n */\n const handleQuickCreateSession = () => {\n setCreateError(null);\n const isHealthyOrUnknown = (id: string) => {\n const row = agentHealthByIdRef.get(id);\n return !row || row.healthy;\n };\n const remembered =\n lastUsedAgentId && cachedAgents.some((a) => a.id === lastUsedAgentId)\n ? lastUsedAgentId\n : null;\n\n let agentId: string | null = null;\n if (remembered && isHealthyOrUnknown(remembered)) {\n agentId = remembered;\n } else {\n // No usable remembered agent — pick a sensible default so the \"+\" never\n // forces the form when a usable agent exists (the whole point of\n // one-click create). Prefer an active + healthy agent, then any healthy\n // agent. Only when there is no usable agent at all do we fall back to\n // the form below.\n const healthy = cachedAgents.filter((a) => isHealthyOrUnknown(a.id));\n const pick = healthy.find((a) => a.isActive) ?? healthy[0];\n agentId = pick?.id ?? null;\n }\n\n if (!agentId) {\n // No usable agent at all — open the form so the user can pick/register one.\n if (remembered) setFormAgentId(remembered);\n if (lastUsedVibeId && cachedVibes.some((v) => v.id === lastUsedVibeId)) {\n setFormVibeId(lastUsedVibeId);\n }\n setFormType(lastUsedSessionType || 'TMUX');\n setFormProfile(lastUsedProfile || 'default');\n setShowCreateForm(true);\n bus.emitCustom('vibecontrols.session.create_clicked', {\n workspaceId: workspaceId ?? undefined,\n });\n return;\n }\n\n // Keep the remembered vibe only if it still exists AND the chosen agent\n // is attached to it (the server resolves the agent from the vibe's\n // attached agents — a mismatch would be rejected).\n let vibeId: string | undefined;\n if (lastUsedVibeId) {\n const vibe = cachedVibes.find((v) => v.id === lastUsedVibeId);\n if (vibe && (vibe.agents ?? []).some((a) => a.id === agentId)) {\n vibeId = lastUsedVibeId;\n }\n }\n\n void createAndOpenSession({\n name: randomSessionName(),\n type: (lastUsedSessionType || 'TMUX') as CreateSessionType,\n vibeId,\n agentId,\n profile: lastUsedProfile || 'default',\n autoStart: false,\n fromModal: 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 bus.emitCustom('vibecontrols.session.started', {\n workspaceId: workspaceId ?? undefined,\n sessionId: session.id,\n });\n } catch (err) {\n if (isQuotaExhaustedError(err)) {\n markQuotaHandledLocally();\n setShowQuotaExhausted(true);\n bus.emitCustom('vibecontrols.quota.session_limit_hit', {\n workspaceId: workspaceId ?? undefined,\n });\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) {\n bus.emitCustom('vibecontrols.session.terminal_opened_in_browser', {\n workspaceId: workspaceId ?? undefined,\n sessionId,\n });\n window.open(tab.terminalUrl, '_blank');\n }\n },\n [terminalTabs, workspaceId, bus]\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 // Reattach to a still-running session (idempotent). Backs the terminal\n // panel's Reconnect affordance: refreshes ttydUrl without stop/start so an\n // idled-out terminal reconnects to the SAME live session on the agent.\n const handleReattachTerminal = useCallback(\n async (sessionId: string) => {\n const session = sessions.find((s) => s.id === sessionId);\n try {\n const result = await startSessionTerminal({ variables: { id: sessionId } });\n if (result.error) return; // panel's fresh-token iframe remount may still recover\n const rawUrl = result.data?.startSessionTerminal?.ttydUrl || null;\n const tunnelHint = session?.agent?.tunnelUrl ?? null;\n const newUrl = normalizeSessionTerminalUrl(rawUrl, tunnelHint);\n if (newUrl) {\n setTerminalTabs((prev) =>\n prev.map((t) =>\n t.sessionId === sessionId\n ? { ...t, terminalUrl: newUrl, loading: false, error: null }\n : t\n )\n );\n }\n } catch {\n // best-effort — the panel's iframe remount alone often re-establishes the WS\n }\n },\n [sessions, startSessionTerminal]\n );\n\n const handleReorderTerminalTabs = useCallback((orderedSessionIds: string[]) => {\n setTerminalTabs((prev) => {\n const byId = new Map(prev.map((t) => [t.sessionId, t]));\n const reordered = orderedSessionIds\n .map((id) => byId.get(id))\n .filter((t): t is TerminalTab => Boolean(t));\n // Defensive: keep any tab not present in the ordered list.\n for (const t of prev) {\n if (!orderedSessionIds.includes(t.sessionId)) reordered.push(t);\n }\n return reordered;\n });\n }, []);\n\n const handleToggleTabPin = useCallback((sessionId: string) => {\n setTerminalTabs((prev) =>\n prev.map((t) => (t.sessionId === sessionId ? { ...t, pinned: !t.pinned } : t))\n );\n }, []);\n\n // Terminal-view feature parity: open info / navigate / share for a tab's\n // session straight from the tab context menu.\n const handleShowSessionInfo = useCallback(\n (sessionId: string) => {\n setActiveTerminalTab(sessionId);\n setSessionInfoPanelOpen(true);\n },\n [setSessionInfoPanelOpen]\n );\n\n const handleViewSessionAgent = useCallback(\n (sessionId: string) => {\n const session = sessions.find((s) => s.id === sessionId);\n if (session?.agent?.id) navigate(`/agents/${session.agent.id}`);\n },\n [sessions, navigate]\n );\n\n const handleViewSessionTarget = useCallback(\n (sessionId: string) => {\n const session = sessions.find((s) => s.id === sessionId);\n if (session?.agent?.target?.id) navigate(`/targets/${session.agent.target.id}`);\n },\n [sessions, navigate]\n );\n\n const handleShareSessionById = useCallback(\n (sessionId: string) => {\n const session = sessions.find((s) => s.id === sessionId);\n if (session) setShareSession(session);\n },\n [sessions]\n );\n\n // Toggle immersive fullscreen mode\n const handleToggleImmersiveFullscreen = useCallback(() => {\n setIsImmersiveFullscreen((prev) => !prev);\n }, []);\n\n // ── Shortcut actions ─────────────────────────────────────────────────\n // Defined once and shared by the keydown handler, the cheatsheet, and the\n // command palette so a binding and its help/label can never drift.\n const activeSession = useMemo(\n () => sessions.find((s) => s.id === activeTerminalTab) ?? null,\n [sessions, activeTerminalTab]\n );\n\n const focusSearch = useCallback(() => {\n searchContainerRef.current?.querySelector('input')?.focus();\n }, []);\n\n const cycleView = useCallback(() => {\n setViewMode((v) => (v === 'terminal' ? 'grid' : v === 'grid' ? 'list' : 'terminal'));\n }, []);\n\n const openActiveAgent = useCallback(() => {\n if (activeSession?.agent?.id) navigate(`/agents/${activeSession.agent.id}`);\n }, [activeSession, navigate]);\n\n const openActiveTarget = useCallback(() => {\n if (activeSession?.agent?.target?.id) navigate(`/targets/${activeSession.agent.target.id}`);\n }, [activeSession, navigate]);\n\n const reconnectActive = useCallback(() => {\n if (activeTerminalTab) void handleReattachTerminal(activeTerminalTab);\n }, [activeTerminalTab, handleReattachTerminal]);\n\n const closeActiveTab = useCallback(() => {\n if (activeTerminalTab) handleCloseTerminalTab(activeTerminalTab);\n }, [activeTerminalTab, handleCloseTerminalTab]);\n\n const renameActiveTab = useCallback(() => {\n if (activeTerminalTab) setRenameTabNonce((n) => n + 1);\n }, [activeTerminalTab]);\n\n const openActiveInNewTab = useCallback(() => {\n if (activeTerminalTab) handleOpenTerminalExternal(activeTerminalTab);\n }, [activeTerminalTab, handleOpenTerminalExternal]);\n\n // Run `fn` (a terminal-tab switch) while pinning the page scroll position.\n // Switching the active tab toggles which iframe is visible and the\n // tab/iframe focus would otherwise scroll the page back to the panel.\n // We snapshot every plausible scroller (the grid scroll container, the\n // app-shell main, and the document) and restore them on the next frame.\n const preserveScroll = useCallback((fn: () => void) => {\n const scrollers: Array<{ el: Element; top: number; left: number }> = [];\n const record = (el: Element | null) => {\n if (el) scrollers.push({ el, top: el.scrollTop, left: el.scrollLeft });\n };\n record(gridScrollRef.current);\n record(document.querySelector('[data-shell-scroll-container]'));\n record(document.scrollingElement);\n fn();\n requestAnimationFrame(() => {\n scrollers.forEach(({ el, top, left }) => {\n el.scrollTop = top;\n el.scrollLeft = left;\n });\n });\n }, []);\n\n const handleSelectTerminalTabPreservingScroll = useCallback(\n (sessionId: string) => {\n preserveScroll(() => setActiveTerminalTab(sessionId));\n },\n [preserveScroll]\n );\n\n // Always-fresh pointer to the quick-create handler so the cheatsheet /\n // command palette never fire a stale closure (the handler is a plain\n // per-render function that reads current store + cached data).\n const quickCreateRef = useRef(handleQuickCreateSession);\n quickCreateRef.current = handleQuickCreateSession;\n\n const openPalette = useCallback(() => {\n setShowCommandPalette(true);\n }, []);\n\n const toggleInfoPanel = useCallback(() => {\n setSessionInfoPanelOpen(!sessionInfoPanelOpen);\n }, [sessionInfoPanelOpen, setSessionInfoPanelOpen]);\n\n // Canonical shortcut registry (drives the cheatsheet + palette labels).\n const sessionShortcuts = useMemo(\n () =>\n buildSessionShortcuts(\n {\n newSession: () => quickCreateRef.current(),\n newSessionWithOptions: () => setShowCreateForm(true),\n renameActiveTab,\n openActiveInNewTab,\n closeActiveTab,\n openPalette,\n openCheatsheet: () => setShowCheatsheet(true),\n focusSearch,\n cycleView,\n groupByNone: () => setGroupBy('none'),\n groupByAgent: () => setGroupBy('agent'),\n groupByTarget: () => setGroupBy('target'),\n groupByVibe: () => setGroupBy('vibe'),\n groupByStatus: () => setGroupBy('status'),\n reconnectActive,\n toggleFullscreen: handleToggleImmersiveFullscreen,\n toggleInfoPanel,\n openActiveAgent,\n openActiveTarget,\n },\n tr\n ),\n [\n renameActiveTab,\n openActiveInNewTab,\n closeActiveTab,\n openPalette,\n focusSearch,\n cycleView,\n reconnectActive,\n handleToggleImmersiveFullscreen,\n toggleInfoPanel,\n openActiveAgent,\n openActiveTarget,\n tr,\n ]\n );\n\n // ── Keyboard handler ─────────────────────────────────────────────────\n // Single raw listener (full control over Tab/digit combos + the `g`\n // sequence). Keystrokes inside the ttyd terminal fire in the iframe's own\n // document and never reach this listener, so single-key shortcuts are\n // safe while a terminal is focused.\n const seqRef = useRef<{ key: string; at: number } | null>(null);\n useEffect(() => {\n const handleKeyDown = (e: KeyboardEvent) => {\n const target = e.target as HTMLElement;\n const inInput =\n target.tagName === 'INPUT' ||\n target.tagName === 'TEXTAREA' ||\n target.tagName === 'SELECT' ||\n target.isContentEditable;\n\n const isMod = e.metaKey || e.ctrlKey;\n\n // Command palette — Cmd/Ctrl+K or Cmd/Ctrl+P (works even in inputs).\n if (isMod && ['k', 'p'].includes(e.key.toLowerCase())) {\n e.preventDefault();\n openPalette();\n return;\n }\n\n if (inInput) return;\n\n // `g`-prefixed sequences (group-by + go-to).\n const now = Date.now();\n const seq = seqRef.current;\n if (seq && seq.key === 'g' && now - seq.at < 1000) {\n seqRef.current = null;\n switch (e.key) {\n case 'a':\n e.preventDefault();\n setGroupBy('agent');\n return;\n case 't':\n e.preventDefault();\n setGroupBy('target');\n return;\n case 'v':\n e.preventDefault();\n setGroupBy('vibe');\n return;\n case 's':\n e.preventDefault();\n setGroupBy('status');\n return;\n case '0':\n e.preventDefault();\n setGroupBy('none');\n return;\n case 'A':\n e.preventDefault();\n openActiveAgent();\n return;\n case 'T':\n e.preventDefault();\n openActiveTarget();\n return;\n default:\n break;\n }\n }\n if (!isMod && !e.altKey && e.key === 'g') {\n seqRef.current = { key: 'g', at: now };\n return;\n }\n\n // New session with options (full form) — ⌘/Ctrl+Shift+T\n if (isMod && e.shiftKey && e.key.toLowerCase() === 't') {\n e.preventDefault();\n setShowCreateForm(true);\n return;\n }\n // New session (quick, one-click) — ⌘/Ctrl+T\n if (isMod && !e.shiftKey && e.key.toLowerCase() === 't') {\n e.preventDefault();\n quickCreateRef.current();\n return;\n }\n // Open active terminal in a new browser tab — ⌘/Ctrl+O\n if (isMod && !e.shiftKey && e.key.toLowerCase() === 'o' && activeTerminalTab) {\n e.preventDefault();\n openActiveInNewTab();\n return;\n }\n // Close active tab\n if (isMod && e.key.toLowerCase() === 'w' && activeTerminalTab) {\n e.preventDefault();\n closeActiveTab();\n return;\n }\n // Next / previous tab — Ctrl+Tab / Ctrl+Shift+Tab, and the iTerm2/Chrome\n // alternate ⌘/Ctrl+Shift+] / [ (e.code is layout-independent).\n const moveTab = (delta: number) => {\n if (terminalTabs.length < 2) return;\n const i = terminalTabs.findIndex((t) => t.sessionId === activeTerminalTab);\n const base = i < 0 ? 0 : i;\n const nextIdx = (base + delta + terminalTabs.length) % terminalTabs.length;\n setActiveTerminalTab(terminalTabs[nextIdx].sessionId);\n };\n if (e.ctrlKey && e.key === 'Tab' && !e.shiftKey && terminalTabs.length > 1) {\n e.preventDefault();\n moveTab(1);\n return;\n }\n if (e.ctrlKey && e.key === 'Tab' && e.shiftKey && terminalTabs.length > 1) {\n e.preventDefault();\n moveTab(-1);\n return;\n }\n if (isMod && e.shiftKey && e.code === 'BracketRight' && terminalTabs.length > 1) {\n e.preventDefault();\n moveTab(1);\n return;\n }\n if (isMod && e.shiftKey && e.code === 'BracketLeft' && terminalTabs.length > 1) {\n e.preventDefault();\n moveTab(-1);\n return;\n }\n // Jump to tab N\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 // Reconnect active terminal (guard browser refresh)\n if (isMod && e.key.toLowerCase() === 'r' && activeTerminalTab) {\n e.preventDefault();\n reconnectActive();\n return;\n }\n // Cycle view\n if (isMod && e.key === '\\\\') {\n e.preventDefault();\n cycleView();\n return;\n }\n if (isMod) return; // leave other mod-combos to the browser\n\n // Single-key shortcuts (outside inputs / terminal iframe).\n if (e.key === '/') {\n e.preventDefault();\n focusSearch();\n return;\n }\n if (e.key === 'f') {\n e.preventDefault();\n handleToggleImmersiveFullscreen();\n return;\n }\n if (e.key === 'i') {\n e.preventDefault();\n toggleInfoPanel();\n return;\n }\n // Rename the active tab — F2 (universal rename convention).\n if (e.key === 'F2' && activeTerminalTab) {\n e.preventDefault();\n renameActiveTab();\n return;\n }\n if (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 }, [\n activeTerminalTab,\n terminalTabs,\n closeActiveTab,\n renameActiveTab,\n openActiveInNewTab,\n reconnectActive,\n cycleView,\n focusSearch,\n openPalette,\n openActiveAgent,\n openActiveTarget,\n toggleInfoPanel,\n handleToggleImmersiveFullscreen,\n ]);\n\n // Command palette command list — actions + jump-to-session, sharing the\n // same handlers as the shortcuts.\n const paletteCommands = useMemo<PaletteCommand[]>(() => {\n const actionsGroup = tr('sessions.palette.groupActions', 'Actions');\n const viewGroup = tr('sessions.palette.groupView', 'View');\n const navGroup = tr('sessions.palette.groupNav', 'Navigation');\n const sessionsGroup = tr('sessions.palette.groupSessions', 'Sessions');\n\n const cmds: PaletteCommand[] = [\n {\n id: 'new-session',\n label: tr('sessions.palette.newSessionQuick', 'New terminal (quick)'),\n group: actionsGroup,\n icon: <Plus className=\"size-4\" />,\n shortcut: '⌘/Ctrl T',\n run: () => quickCreateRef.current(),\n },\n {\n id: 'new-session-options',\n label: tr('sessions.newSessionWithOptions', 'New session with options…'),\n group: actionsGroup,\n icon: <SlidersHorizontal className=\"size-4\" />,\n shortcut: '⌘/Ctrl ⇧ T',\n run: () => setShowCreateForm(true),\n },\n ...(activeTerminalTab\n ? [\n {\n id: 'rename-tab',\n label: tr('sessions.sc.renameTab', 'Rename active tab'),\n group: actionsGroup,\n icon: <Keyboard className=\"size-4\" />,\n shortcut: 'F2',\n run: renameActiveTab,\n },\n {\n id: 'open-tab-new',\n label: tr('sessions.sc.openTabNew', 'Open active tab in new browser tab'),\n group: actionsGroup,\n icon: <Share2 className=\"size-4\" />,\n shortcut: '⌘/Ctrl O',\n run: openActiveInNewTab,\n },\n ]\n : []),\n {\n id: 'reconnect',\n label: tr('sessions.palette.reconnect', 'Reconnect active terminal'),\n group: actionsGroup,\n icon: <RotateCcw className=\"size-4\" />,\n run: reconnectActive,\n },\n {\n id: 'refresh',\n label: tr('common.refresh', 'Refresh'),\n group: actionsGroup,\n icon: <RefreshCw className=\"size-4\" />,\n run: () => void refetch(),\n },\n ...(activeSession\n ? [\n {\n id: 'share-active',\n label: tr('sessions.palette.shareActive', 'Share / offer active session'),\n group: actionsGroup,\n icon: <Share2 className=\"size-4\" />,\n run: () => setShareSession(activeSession),\n },\n ]\n : []),\n // View\n {\n id: 'view-terminal',\n label: tr('sessions.palette.viewTerminal', 'Switch to terminal view'),\n group: viewGroup,\n icon: <Monitor className=\"size-4\" />,\n run: () => setViewMode('terminal'),\n },\n {\n id: 'view-grid',\n label: tr('sessions.palette.viewGrid', 'Switch to grid view'),\n group: viewGroup,\n icon: <Grid className=\"size-4\" />,\n run: () => setViewMode('grid'),\n },\n {\n id: 'view-list',\n label: tr('sessions.palette.viewList', 'Switch to list view'),\n group: viewGroup,\n icon: <List className=\"size-4\" />,\n run: () => setViewMode('list'),\n },\n {\n id: 'group-agent',\n label: tr('sessions.palette.groupByAgent', 'Group by agent'),\n group: viewGroup,\n icon: <Server className=\"size-4\" />,\n run: () => setGroupBy('agent'),\n },\n {\n id: 'group-target',\n label: tr('sessions.palette.groupByTarget', 'Group by target'),\n group: viewGroup,\n icon: <MonitorSmartphone className=\"size-4\" />,\n run: () => setGroupBy('target'),\n },\n {\n id: 'group-vibe',\n label: tr('sessions.palette.groupByVibe', 'Group by vibe'),\n group: viewGroup,\n icon: <Layers className=\"size-4\" />,\n run: () => setGroupBy('vibe'),\n },\n {\n id: 'group-none',\n label: tr('sessions.palette.groupClear', 'Clear grouping'),\n group: viewGroup,\n icon: <Layers className=\"size-4\" />,\n run: () => setGroupBy('none'),\n },\n {\n id: 'toggle-info',\n label: tr('sessions.palette.toggleInfo', 'Toggle session info panel'),\n group: viewGroup,\n icon: <Info className=\"size-4\" />,\n run: toggleInfoPanel,\n },\n {\n id: 'shortcuts',\n label: tr('sessions.palette.shortcuts', 'Keyboard shortcuts'),\n group: viewGroup,\n icon: <Keyboard className=\"size-4\" />,\n run: () => setShowCheatsheet(true),\n },\n // Navigation\n ...(activeSession?.agent\n ? [\n {\n id: 'goto-agent',\n label: tr('sessions.palette.gotoAgent', \"Open active session's agent\"),\n group: navGroup,\n icon: <Server className=\"size-4\" />,\n run: openActiveAgent,\n },\n ]\n : []),\n ...(activeSession?.agent?.target\n ? [\n {\n id: 'goto-target',\n label: tr('sessions.palette.gotoTarget', \"Open active session's target\"),\n group: navGroup,\n icon: <MonitorSmartphone className=\"size-4\" />,\n run: openActiveTarget,\n },\n ]\n : []),\n ];\n\n // Jump-to-session (open terminal) for the loaded sessions.\n for (const s of filteredSessions.slice(0, 30)) {\n cmds.push({\n id: `open-${s.id}`,\n label: s.name,\n group: sessionsGroup,\n icon: <Terminal className=\"size-4\" />,\n keywords: `${s.status} ${s.agent?.name ?? ''} ${s.agent?.target?.name ?? ''}`,\n run: () => void handleOpenTerminal(s),\n });\n }\n return cmds;\n }, [\n tr,\n activeSession,\n activeTerminalTab,\n renameActiveTab,\n openActiveInNewTab,\n filteredSessions,\n reconnectActive,\n refetch,\n toggleInfoPanel,\n openActiveAgent,\n openActiveTarget,\n handleOpenTerminal,\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/plans');\n }}\n title={getQuotaDialogContent('session').title}\n message={getQuotaDialogContent('session').message}\n confirmLabel=\"View Billing\"\n cancelLabel=\"Dismiss\"\n variant=\"warning\"\n />\n <ShortcutCheatsheet\n isOpen={showCheatsheet}\n onClose={() => setShowCheatsheet(false)}\n shortcuts={sessionShortcuts}\n />\n <SessionsCommandPalette\n open={showCommandPalette}\n onOpenChange={setShowCommandPalette}\n commands={paletteCommands}\n />\n <SessionInfoPanel\n open={sessionInfoPanelOpen}\n onOpenChange={setSessionInfoPanelOpen}\n session={activeSession}\n />\n </>\n );\n\n // Terminal-first view\n if (viewMode === 'terminal') {\n return (\n <MotionPage className=\"flex flex-col bg-bg-surface min-h-0 flex-1\">\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 {/* Connection status merged into the header (was a separate\n \"Agent Notifications\" row that wasted vertical space). */}\n <SessionConnectionStatus />\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 <button\n type=\"button\"\n onClick={() => setShowCheatsheet(true)}\n className=\"p-1.5 text-text-secondary hover:bg-bg-surface rounded transition-colors\"\n title={tr('sessions.keyboardShortcuts', 'Keyboard shortcuts (?)')}\n aria-label={tr('sessions.keyboardShortcuts', 'Keyboard shortcuts (?)')}\n data-testid=\"sessions-shortcuts-button\"\n >\n <Keyboard className=\"size-3.5\" />\n </button>\n {viewToggle}\n </div>\n </div>\n\n {/* Agent notifications — compact: only takes space when there are\n notifications; the status chip lives in the header above. The\n `empty:hidden` collapses the padded wrapper when the banner\n renders nothing. */}\n <div className=\"px-4 pt-3 empty:hidden empty:p-0\">\n <SessionNotifications compact 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 onReattachTerminal={handleReattachTerminal}\n onReorderTabs={handleReorderTerminalTabs}\n onTogglePin={handleToggleTabPin}\n onShowSessionInfo={handleShowSessionInfo}\n onViewAgent={handleViewSessionAgent}\n onViewTarget={handleViewSessionTarget}\n onShareSession={canUpdateSession ? handleShareSessionById : undefined}\n onCreateSession={handleCreateSession}\n onQuickCreate={canCreateSession ? handleQuickCreateSession : undefined}\n renameActiveTabNonce={renameTabNonce}\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-[28ch]\">\n {terminalTabs.find((t) => t.sessionId === activeTerminalTab)?.name}\n </span>\n )}\n {/* Active session's agent + target — visible context in\n terminal view, with click-through to detail popovers. */}\n {activeSession?.agent && (\n <span className=\"hidden md:inline-flex items-center gap-1\">\n <span className=\"text-text-muted\">·</span>\n <AgentInfoPopover agent={activeSession.agent} />\n {activeSession.agent.target && (\n <TargetInfoPopover target={activeSession.agent.target} />\n )}\n </span>\n )}\n </div>\n <div className=\"hidden sm:flex items-center gap-3\">\n <button\n type=\"button\"\n onClick={() => setSessionInfoPanelOpen(!sessionInfoPanelOpen)}\n className=\"inline-flex items-center gap-1 hover:text-text-secondary transition-colors\"\n title={tr('sessions.infoPanelHint', 'Session info (i)')}\n >\n <Info className=\"size-3\" />\n {tr('sessions.infoPanelLabel', 'Info')}\n </button>\n {activeSession && (\n // Generic Share entry point for the active session. Self-gates on\n // the `session:share` RBAC permission + the\n // `vibecontrols.sharing.session` feature flag, then opens the\n // generic `<ShareDialog>` (email invite + accept-gate + four\n // visibility modes + expiry + public link) backed by the generic\n // `shareSessionGrant` mutations. This ONLY manages share grants —\n // live terminal control handoff + collaborator presence remain in\n // `SharedTerminalPanel` / `SharedSessionPage`, untouched.\n <EntityShareButton\n entity=\"session\"\n resource={{\n id: activeSession.id,\n name: activeSession.name,\n vibeId: activeSession.vibeId ?? null,\n }}\n label={tr('sessions.shareOfferLabel', 'Share')}\n variant=\"button\"\n />\n )}\n <button\n type=\"button\"\n onClick={() => setShowCheatsheet(true)}\n className=\"inline-flex items-center gap-1 hover:text-text-secondary transition-colors\"\n >\n <Keyboard className=\"size-3\" />\n {tr('sessions.shortcutsHint', 'Press ? for shortcuts')}\n </button>\n <button\n type=\"button\"\n onClick={openPalette}\n className=\"hover:text-text-secondary transition-colors\"\n >\n {tr('sessions.paletteHint', '⌘/Ctrl K · 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 {overlayDialogs}\n </MotionPage>\n );\n }\n\n // Render helpers shared by flat + grouped layouts.\n const renderSessionGrid = (items: Session[]) => (\n <MotionList stagger={0.04} className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\">\n {items.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 // Suppress the inline Share button entirely when the viewer\n // lacks session:update — the child component already treats an\n // undefined `onShare` as \"don't render the button\".\n onShare={canUpdateSession ? handleShareSession : undefined}\n selected={selectedSessionIds.has(session.id)}\n onToggleSelect={() => toggleSessionSelection(session.id)}\n />\n </MotionItem>\n ))}\n </MotionList>\n );\n\n const renderSessionListView = (items: Session[], grouped: boolean) => (\n <SessionList\n sessions={items}\n onOpen={handleSessionSelect}\n onStart={handleStartSession}\n onStop={handleStopSession}\n onRestart={handleRestartSession}\n onTerminate={handleTerminateSession}\n onOpenTerminal={handleOpenTerminal}\n // Same suppression as the grid view above — hide Share when the\n // viewer can't `session:update`.\n onShare={canUpdateSession ? handleShareSession : undefined}\n selectedIds={selectedSessionIds}\n onToggleSelect={toggleSessionSelection}\n onSelectAll={selectAllSessions}\n onDeselectAll={deselectAllSessions}\n hideHeaderSelectAll={grouped}\n />\n );\n\n // Grid/List view\n return (\n <MotionPage className=\"flex flex-col bg-bg-surface min-h-0 flex-1\">\n <div ref={gridScrollRef} 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 {/* Connection status merged into the header (replaces the\n separate \"Agent Notifications\" row). */}\n <div className=\"flex items-center self-center\">\n <SessionConnectionStatus />\n </div>\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 {/* Split button: primary quick-creates a terminal (no form,\n remembered defaults); the caret opens options. */}\n <div className=\"relative flex items-stretch\" ref={headerCreateMenuRef}>\n <button\n type=\"button\"\n onClick={handleQuickCreateSession}\n className=\"flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-l-md hover:bg-action-primary-bg-hover transition-colors\"\n title={tr('sessions.quickNewSessionHint', 'New terminal (⌘/Ctrl+T)')}\n data-testid=\"sessions-new-session\"\n >\n <Plus className=\"size-4\" />\n {tr('sessions.newSession', 'New Session')}\n </button>\n <button\n type=\"button\"\n onClick={() => setShowHeaderCreateMenu((p) => !p)}\n className=\"flex items-center px-2 bg-action-primary-bg text-action-primary-text rounded-r-md border-l border-action-primary-text/20 hover:bg-action-primary-bg-hover transition-colors\"\n aria-haspopup=\"menu\"\n aria-expanded={showHeaderCreateMenu}\n title={tr('sessions.newSessionOptions', 'New session options')}\n data-testid=\"sessions-new-session-menu\"\n >\n <ChevronDown className=\"size-4\" />\n </button>\n {showHeaderCreateMenu && (\n <div\n role=\"menu\"\n className=\"absolute right-0 top-full mt-1 z-50 w-64 bg-bg-elevated border border-border-default rounded-md shadow-xl py-1\"\n >\n <button\n type=\"button\"\n role=\"menuitem\"\n onClick={() => {\n setShowHeaderCreateMenu(false);\n handleCreateSession();\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors text-left\"\n >\n <SlidersHorizontal className=\"size-3.5 text-text-secondary\" />\n {tr('sessions.newSessionWithOptions', 'New session with options…')}\n </button>\n <button\n type=\"button\"\n role=\"menuitem\"\n onClick={() => {\n setShowHeaderCreateMenu(false);\n setShowReconnectModal(true);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors text-left\"\n >\n <RefreshCw className=\"size-3.5 text-text-secondary\" />\n {tr('sessions.reconnectExisting', 'Reconnect existing session…')}\n </button>\n </div>\n )}\n </div>\n </PermissionButton>\n <button\n type=\"button\"\n onClick={() => setShowCheatsheet(true)}\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.keyboardShortcuts', 'Keyboard shortcuts (?)')}\n aria-label={tr('sessions.keyboardShortcuts', 'Keyboard shortcuts (?)')}\n data-testid=\"sessions-shortcuts-button\"\n >\n <Keyboard className=\"size-4\" />\n </button>\n {viewToggle}\n </div>\n }\n />\n\n {/* Agent notifications — compact: the connection status is in the\n page header, so this only takes space when notifications exist. */}\n <SessionNotifications compact 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\n ref={searchContainerRef}\n className=\"flex flex-col sm:flex-row items-stretch sm:items-center gap-2\"\n >\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 <FilterDropdown\n label={tr('sessions.filterTarget', 'Target')}\n value={targetFilter}\n options={targetOptions}\n onChange={setTargetFilter}\n icon={<MonitorSmartphone className=\"size-3.5 text-text-secondary\" />}\n />\n <div className=\"sm:ml-auto\">\n <FilterDropdown\n label={tr('sessions.groupByLabel', 'Group')}\n value={groupBy}\n options={groupByOptions}\n onChange={(v) => setGroupBy(v as SessionsGroupBy)}\n allValue=\"none\"\n allLabel={tr('sessions.groupBy.none', 'No grouping')}\n icon={<Layers className=\"size-3.5 text-text-secondary\" />}\n />\n </div>\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 targetFilter !== '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 groupBy !== 'none' ? (\n <div key=\"grouped-view\" className=\"space-y-3\" data-testid=\"sessions-grouped\">\n {groupedSessions.map((group) => {\n const ids = group.sessions.map((s) => s.id);\n const allSelected =\n ids.length > 0 && ids.every((id) => selectedSessionIds.has(id));\n const someSelected = ids.some((id) => selectedSessionIds.has(id));\n return (\n <SessionGroup\n key={group.key}\n label={group.label}\n count={group.sessions.length}\n collapsed={collapsedGroups.has(group.key)}\n onToggleCollapse={() => toggleGroupCollapse(group.key)}\n allSelected={allSelected}\n someSelected={someSelected}\n onToggleSelectGroup={() => toggleGroupSelection(ids)}\n >\n {viewMode === 'grid'\n ? renderSessionGrid(group.sessions)\n : renderSessionListView(group.sessions, true)}\n </SessionGroup>\n );\n })}\n </div>\n ) : viewMode === 'grid' ? (\n <div key=\"grid-view\">{renderSessionGrid(filteredSessions)}</div>\n ) : (\n <MotionItem key=\"list-view\">\n {renderSessionListView(filteredSessions, false)}\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 targetFilter !== '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 targetFilter !== '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 targetFilter === 'all' ? (\n <div className=\"flex items-center gap-2\">\n {canCreateSession && (\n <button\n type=\"button\"\n onClick={handleQuickCreateSession}\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={handleSelectTerminalTabPreservingScroll}\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 onReattachTerminal={handleReattachTerminal}\n onReorderTabs={handleReorderTerminalTabs}\n onTogglePin={handleToggleTabPin}\n onShowSessionInfo={handleShowSessionInfo}\n onViewAgent={handleViewSessionAgent}\n onViewTarget={handleViewSessionTarget}\n onShareSession={canUpdateSession ? handleShareSessionById : undefined}\n onCreateSession={canCreateSession ? handleCreateSession : undefined}\n onQuickCreate={canCreateSession ? handleQuickCreateSession : undefined}\n renameActiveTabNonce={renameTabNonce}\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 {formType === 'TERMINAL' && (\n <p className=\"mt-1 text-xs text-text-muted\">\n {tr(\n 'sessions.terminalTypeHint',\n 'Plain terminal — a shell over ttyd with no tmux/wezterm/zellij multiplexer and no persisted session. Native drag-to-select copy/paste.'\n )}\n </p>\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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"}
|