@burdenoff/microfe-vibecontrols 2026.528.2 → 2026.528.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"OverviewPage.js","names":[],"sources":["../../src/pages/OverviewPage.tsx"],"sourcesContent":["import { useMemo } from 'react';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { VIBE_PERMISSIONS } from '../constants/permissions';\nimport {\n Bot,\n Zap,\n Terminal,\n FileText,\n Activity,\n Server,\n Play,\n GitBranch,\n RefreshCw,\n} from 'lucide-react';\nimport { MotionPage, MotionList, MotionItem } from '@burdenoff/fe-libs/shared/motion';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { AgentCard } from '@/components/agents';\nimport { SessionCard } from '@/components/sessions';\nimport { VibeCard } from '@/components/vibes';\nimport { PageHeader, StatCard, Card, AgentSetupBanner, ErrorState, LoadingState } from '@/components/shared';\nimport { useVibeNavigate } from '@/providers/VibeControlsProvider';\nimport { useCachedInitialData } from '@/hooks/useCachedInitialData';\nimport type { Vibe } from '@/components/vibes/types';\nimport type { Session } from '@/components/sessions/types';\n\n/**\n * Overview page showing summary dashboard with stats, active resources, and recent activity\n *\n * This page now uses cached initial data from Apollo instead of independent queries,\n * reducing GraphQL calls from 5 to 0 for the initial render.\n * The refresh button triggers a refetch of the initial data.\n */\nexport function OverviewPage() {\n const handleNavigate = useVibeNavigate();\n const { t } = useI18n();\n const { hasPermission, isLoading: permissionsLoading } = usePermissions();\n\n const tr = (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\n // Use cached initial data - reads from Apollo cache, no network request\n // Loading state reflects whether initial data has been loaded\n const {\n agents: cachedAgents,\n vibes: cachedVibes,\n sessions: cachedSessions,\n notes,\n targets,\n loading,\n error: dataError,\n totals,\n refetch,\n } = useCachedInitialData();\n\n if (dataError) {\n // eslint-disable-next-line no-console -- intentional debug breadcrumb for failed overview load\n console.error('[overview] failed to load initial workspace data', dataError);\n }\n\n // Map cached data to component types\n // Using type assertion since we know the structure from the GraphQL query\n const agents = useMemo(() => {\n return cachedAgents.map((agent) => ({\n id: agent.id,\n workspaceId: '',\n name: agent.name,\n hostname: agent.hostname,\n platform: agent.platform,\n architecture: agent.architecture || '',\n version: agent.version || '',\n isActive: agent.isActive,\n status: agent.isActive ? ('ACTIVE' as const) : ('OFFLINE' as const),\n lastHeartbeat: agent.lastHeartbeat || null,\n tunnelUrl: agent.tunnelUrl ?? null,\n tunnelStatus: agent.tunnelStatus ?? null,\n agentApiKey: null,\n aiContext: null,\n configuration: {},\n capabilities: [],\n metadata: {},\n createdAt: agent.createdAt || '',\n updatedAt: '',\n }));\n }, [cachedAgents]);\n\n const vibes = useMemo(() => {\n return cachedVibes.map((vibe) => ({\n id: vibe.id,\n workspaceId: '',\n name: vibe.name,\n description: '',\n type: (vibe.type || 'PROJECT') as Vibe['type'],\n status: vibe.status as Vibe['status'],\n path: '',\n parentId: null,\n gitBranch: null,\n gitRemote: null,\n gitStatus: null,\n environmentVariables: {},\n tags: [],\n metadata: {},\n createdAt: '',\n updatedAt: '',\n archivedAt: null,\n // Multi-agent: surface attached agents so Overview cards can render a\n // count badge (\"+N agents\") or single name. Empty when none.\n agents: vibe.agents ?? [],\n }));\n }, [cachedVibes]);\n\n const sessions: Session[] = useMemo(() => {\n return cachedSessions.map((session) => ({\n id: session.id,\n workspaceId: '',\n agentId: session.agentId ?? null,\n vibeId: session.vibeId ?? null,\n name: session.name,\n type: (session.type || 'TERMINAL') as Session['type'],\n status: session.status as Session['status'],\n command: null,\n workingDirectory: null,\n environmentVariables: {},\n autoStart: false,\n pid: null,\n exitCode: null,\n lastOutput: null,\n metadata: {},\n createdAt: session.createdAt || '',\n updatedAt: '',\n agent: undefined,\n vibe: undefined,\n }));\n }, [cachedSessions]);\n\n // Calculate stats from cached totals\n const activeAgents = agents.filter((a) => a.isActive);\n const runningSessions = sessions.filter((s) => s.status === 'RUNNING');\n const activeVibes = vibes.filter((v) => v.status === 'ACTIVE');\n\n const stats = {\n agents: {\n total: totals.agents,\n active: activeAgents.length,\n offline: agents.filter((a) => !a.isActive).length,\n },\n vibes: {\n total: totals.vibes,\n active: activeVibes.length,\n archived: vibes.filter((v) => v.status === 'ARCHIVED').length,\n },\n sessions: {\n total: totals.sessions,\n running: runningSessions.length,\n stopped: sessions.filter((s) => s.status === 'STOPPED').length,\n },\n notes: {\n total: totals.notes,\n pinned: notes.filter((n) => n.isPinned).length,\n },\n };\n\n // Recent activity - derive from session/agent data (simplified for now)\n const recentActivity = useMemo(() => {\n const activities: Array<{\n id: string;\n type: string;\n title: string;\n description: string;\n timestamp: string;\n color: string;\n }> = [];\n\n // Add recent sessions as activity\n sessions.slice(0, 3).forEach((session) => {\n activities.push({\n id: `session-${session.id}`,\n type: 'session_started',\n title:\n session.status === 'RUNNING'\n ? tr('overviewPage.activity.sessionRunning', 'Session Running')\n : tr('overviewPage.activity.sessionStatus', 'Session {{status}}', {\n status: session.status.toLowerCase(),\n }),\n description: `${session.name}`,\n timestamp: session.createdAt,\n color: session.status === 'RUNNING' ? 'green' : 'blue',\n });\n });\n\n // Add active agents as activity\n activeAgents.slice(0, 2).forEach((agent) => {\n activities.push({\n id: `agent-${agent.id}`,\n type: 'agent_connected',\n title: tr('overviewPage.activity.agentOnline', 'Agent Online'),\n description: tr('overviewPage.activity.agentOnlineDescription', '{{name}} is active', {\n name: agent.name,\n }),\n timestamp: agent.lastHeartbeat || agent.updatedAt,\n color: 'blue',\n });\n });\n\n // Sort by timestamp descending\n return activities\n .sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime())\n .slice(0, 6);\n }, [sessions, activeAgents]);\n\n if (permissionsLoading || (loading && cachedAgents.length === 0 && cachedVibes.length === 0 && cachedSessions.length === 0))\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <LoadingState\n message={tr('overviewPage.loadingPermissions', 'Loading permissions...')}\n />\n </div>\n );\n if (!hasPermission(VIBE_PERMISSIONS.VIBE_LIST))\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <div className=\"text-center space-y-2\">\n <h2 className=\"text-lg font-semibold text-foreground\">\n {tr('common.accessDeniedTitle', 'Access Denied')}\n </h2>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'overviewPage.accessDeniedDescription',\n \"You don't have permission to access this page.\"\n )}\n </p>\n </div>\n </div>\n );\n\n const formatTimestamp = (timestamp: string) => {\n if (!timestamp) return tr('overviewPage.time.unknown', 'Unknown');\n const diff = Date.now() - new Date(timestamp).getTime();\n if (diff < 60000) return tr('overviewPage.time.justNow', 'Just now');\n if (diff < 3600000) {\n return tr('overviewPage.time.minutesAgo', '{{count}}m ago', {\n count: Math.floor(diff / 60000),\n });\n }\n if (diff < 86400000) {\n return tr('overviewPage.time.hoursAgo', '{{count}}h ago', {\n count: Math.floor(diff / 3600000),\n });\n }\n return tr('overviewPage.time.daysAgo', '{{count}}d ago', {\n count: Math.floor(diff / 86400000),\n });\n };\n\n const getActivityIcon = (type: string) => {\n const icons: Record<string, typeof Activity> = {\n session_started: Play,\n agent_connected: Server,\n vibe_updated: GitBranch,\n note_created: FileText,\n module_installed: Activity,\n };\n const Icon = icons[type] || Activity;\n return <Icon className=\"size-4\" />;\n };\n\n const getActivityColor = (color: string) => {\n const colors: Record<string, string> = {\n green: 'bg-status-success-bg text-status-success-text',\n blue: 'bg-status-info-bg text-status-info-text',\n purple: 'bg-accent-purple-subtle text-accent-purple',\n orange: 'bg-status-warning-bg text-status-warning-text',\n cyan: 'bg-accent-teal-subtle text-accent-teal',\n yellow: 'bg-status-warning-bg text-status-warning-text',\n pink: 'bg-accent-purple-subtle text-accent-purple',\n };\n return colors[color] || 'bg-bg-sunken text-text-secondary';\n };\n\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n <PageHeader\n title={tr('overviewPage.title', 'VibeControls Overview')}\n description={tr(\n 'overviewPage.description',\n 'Monitor and manage your development environments'\n )}\n actions={\n <button\n type=\"button\"\n onClick={refetch}\n disabled={loading}\n className=\"flex items-center gap-2 px-3 py-2 text-sm bg-bg-sunken border border-border-default rounded-md text-text-primary hover:bg-bg-sunken/80 transition-colors disabled:opacity-50\"\n >\n <RefreshCw className={`size-4 ${loading ? 'animate-spin' : ''}`} />\n {tr('overviewPage.refresh', 'Refresh')}\n </button>\n }\n />\n\n {/* Friendly fallback when the initial workspace load fails — keeps the\n page header / refresh button rendered above so the user can recover\n without a hard reload. */}\n {dataError && !loading && (\n <ErrorState\n title={tr('overviewPage.loadFailed.title', 'Could not load workspace data')}\n message={tr(\n 'overviewPage.loadFailed.message',\n \"We couldn't load your VibeControls overview. Please try again in a moment.\"\n )}\n onRetry={refetch}\n />\n )}\n\n {/* Agent CLI Setup Banner — shown when no agents exist */}\n {!dataError && agents.length === 0 && !loading && (\n <AgentSetupBanner variant=\"full\" dismissKey=\"vibecontrols-overview-setup-banner\" />\n )}\n\n {/* Stats Grid */}\n <MotionList\n data-tour=\"overview-stats\"\n className=\"grid grid-cols-2 sm:grid-cols-5 gap-4\"\n stagger={0.06}\n >\n <MotionItem variant=\"fadeScale\">\n <StatCard\n title={tr('overviewPage.stats.targets', 'Targets')}\n value={loading ? '-' : targets.length}\n subtitle={\n loading\n ? ''\n : tr('overviewPage.stats.connected', '{{count}} connected', {\n count: targets.filter((t) => t.status === 'connected' || t.status === 'active')\n .length,\n })\n }\n icon={<Server className=\"size-6\" />}\n variant={!loading && targets.length > 0 ? 'success' : 'default'}\n onClick={() => handleNavigate('/targets')}\n />\n </MotionItem>\n <MotionItem variant=\"fadeScale\">\n <StatCard\n title={tr('overviewPage.stats.agents', 'Agents')}\n value={loading ? '-' : stats.agents.total}\n subtitle={\n loading\n ? ''\n : tr('overviewPage.stats.active', '{{count}} active', {\n count: stats.agents.active,\n })\n }\n icon={<Bot className=\"size-6\" />}\n variant={!loading && stats.agents.active > 0 ? 'success' : 'default'}\n onClick={() => handleNavigate('/agents')}\n />\n </MotionItem>\n <MotionItem variant=\"fadeScale\">\n <StatCard\n title={tr('overviewPage.stats.vibes', 'Vibes')}\n value={loading ? '-' : stats.vibes.total}\n subtitle={\n loading\n ? ''\n : tr('overviewPage.stats.active', '{{count}} active', {\n count: stats.vibes.active,\n })\n }\n icon={<Zap className=\"size-6\" />}\n onClick={() => handleNavigate('/vibes')}\n />\n </MotionItem>\n <MotionItem variant=\"fadeScale\">\n <StatCard\n title={tr('overviewPage.stats.sessions', 'Sessions')}\n value={loading ? '-' : stats.sessions.total}\n subtitle={\n loading\n ? ''\n : tr('overviewPage.stats.running', '{{count}} running', {\n count: stats.sessions.running,\n })\n }\n icon={<Terminal className=\"size-6\" />}\n variant={!loading && stats.sessions.running > 0 ? 'success' : 'default'}\n onClick={() => handleNavigate('/sessions')}\n />\n </MotionItem>\n <MotionItem variant=\"fadeScale\" className=\"col-span-2 sm:col-span-1\">\n <StatCard\n title={tr('overviewPage.stats.notes', 'Notes')}\n value={loading ? '-' : stats.notes.total}\n subtitle={\n loading\n ? ''\n : tr('overviewPage.stats.pinned', '{{count}} pinned', {\n count: stats.notes.pinned,\n })\n }\n icon={<FileText className=\"size-6\" />}\n />\n </MotionItem>\n </MotionList>\n\n {/* Main Content Grid */}\n <MotionList className=\"grid grid-cols-1 gap-6 xl:grid-cols-3\" stagger={0.08}>\n {/* Active Agents */}\n <MotionItem className=\"xl:col-span-2\">\n <Card>\n <div className=\"flex items-center justify-between mb-4\">\n <h2 className=\"text-lg font-semibold text-text-primary flex items-center gap-2\">\n <Bot className=\"size-5\" />\n {tr('overviewPage.sections.activeAgents', 'Active Agents')}\n </h2>\n <button\n type=\"button\"\n onClick={() => handleNavigate('/agents')}\n className=\"text-sm text-text-link hover:underline\"\n aria-label={tr('overviewPage.sections.viewAllAgents', 'View all agents')}\n >\n {tr('overviewPage.sections.viewAll', 'View all')}\n </button>\n </div>\n {activeAgents.length > 0 ? (\n <MotionList className=\"grid grid-cols-1 sm:grid-cols-2 gap-3\" stagger={0.04}>\n {activeAgents.slice(0, 4).map((agent) => (\n <MotionItem key={agent.id}>\n <AgentCard agent={agent} onSelect={(a) => handleNavigate(`/agents/${a.id}`)} />\n </MotionItem>\n ))}\n </MotionList>\n ) : (\n <div className=\"text-center py-6 text-text-secondary\">\n <Bot className=\"size-12 mx-auto mb-2 opacity-50\" />\n <p>{tr('overviewPage.empty.noActiveAgents', 'No active agents')}</p>\n <p className=\"text-xs text-text-muted mt-1 mb-3\">\n {tr('overviewPage.empty.installCliHint', 'Install the agent CLI to get started')}\n </p>\n <div className=\"flex flex-wrap items-center justify-center gap-2\">\n <button\n type=\"button\"\n onClick={() => handleNavigate('/getting-started')}\n className=\"text-sm text-text-link hover:underline\"\n >\n {tr('overviewPage.empty.setupGuide', 'Setup guide')}\n </button>\n <span className=\"text-text-muted\">&middot;</span>\n <button\n type=\"button\"\n onClick={() => handleNavigate('/getting-started')}\n className=\"text-sm font-medium text-text-link hover:underline\"\n >\n {tr('overviewPage.empty.fullSetupGuide', 'View full setup guide')}\n </button>\n <span className=\"text-text-muted\">&middot;</span>\n <button\n type=\"button\"\n onClick={() => handleNavigate('/agents')}\n className=\"text-sm text-text-link hover:underline\"\n >\n {tr('overviewPage.empty.addAgent', 'Add agent')}\n </button>\n </div>\n </div>\n )}\n </Card>\n </MotionItem>\n\n {/* Recent Activity */}\n <MotionItem>\n <Card>\n <div className=\"flex items-center justify-between mb-4\">\n <h2 className=\"text-lg font-semibold text-text-primary flex items-center gap-2\">\n <Activity className=\"size-5\" />\n {tr('overviewPage.sections.recentActivity', 'Recent Activity')}\n </h2>\n <button\n type=\"button\"\n onClick={() => handleNavigate('/audit')}\n className=\"text-sm text-text-link hover:underline\"\n aria-label={tr('overviewPage.sections.viewAllActivity', 'View all activity')}\n >\n View all\n </button>\n </div>\n <MotionList className=\"space-y-3\" stagger={0.04}>\n {recentActivity.length > 0 ? (\n recentActivity.map((activity) => (\n <MotionItem key={activity.id} variant=\"fadeRight\">\n <div className=\"flex items-start gap-3\">\n <div\n className={`flex-shrink-0 size-8 rounded-full flex items-center justify-center ${getActivityColor(activity.color)}`}\n >\n {getActivityIcon(activity.type)}\n </div>\n <div className=\"flex-1 min-w-0\">\n <p className=\"text-sm font-medium text-text-primary truncate\">\n {activity.title}\n </p>\n <p className=\"text-xs text-text-secondary truncate\">\n {activity.description}\n </p>\n </div>\n <span className=\"text-xs text-text-secondary flex-shrink-0\">\n {formatTimestamp(activity.timestamp)}\n </span>\n </div>\n </MotionItem>\n ))\n ) : (\n <div className=\"text-center py-4 text-text-secondary text-sm\">\n <p>{tr('overviewPage.empty.noRecentActivity', 'No recent activity')}</p>\n <button\n type=\"button\"\n onClick={() => handleNavigate('/audit')}\n className=\"mt-2 text-sm text-text-link hover:underline\"\n >\n {tr('overviewPage.empty.viewAuditLog', 'View audit log')}\n </button>\n </div>\n )}\n </MotionList>\n </Card>\n </MotionItem>\n </MotionList>\n\n {/* Running Sessions */}\n <MotionItem>\n <Card>\n <div className=\"flex items-center justify-between mb-4\">\n <h2 className=\"text-lg font-semibold text-text-primary flex items-center gap-2\">\n <Terminal className=\"size-5\" />\n {tr('overviewPage.sections.runningSessions', 'Running Sessions')}\n </h2>\n <button\n type=\"button\"\n onClick={() => handleNavigate('/sessions')}\n className=\"text-sm text-text-link hover:underline\"\n >\n {tr('overviewPage.sections.viewAll', 'View all')}\n </button>\n </div>\n {runningSessions.length > 0 ? (\n <MotionList\n className=\"grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-3 gap-3\"\n stagger={0.04}\n >\n {runningSessions.slice(0, 6).map((session) => (\n <MotionItem key={session.id}>\n <SessionCard\n session={session}\n onOpen={(s) => handleNavigate(`/sessions/${s.id}`)}\n />\n </MotionItem>\n ))}\n </MotionList>\n ) : (\n <div className=\"text-center py-8 text-text-secondary\">\n <Terminal className=\"size-12 mx-auto mb-2 opacity-50\" />\n <p>{tr('overviewPage.empty.noRunningSessions', 'No running sessions')}</p>\n <button\n type=\"button\"\n onClick={() => handleNavigate('/sessions')}\n className=\"mt-2 text-sm text-text-link hover:underline\"\n >\n {tr('overviewPage.empty.startSession', 'Start a session')}\n </button>\n </div>\n )}\n </Card>\n </MotionItem>\n\n {/* Active Vibes */}\n <MotionItem>\n <Card>\n <div className=\"flex items-center justify-between mb-4\">\n <h2 className=\"text-lg font-semibold text-text-primary flex items-center gap-2\">\n <Zap className=\"size-5\" />\n {tr('overviewPage.sections.activeVibes', 'Active Vibes')}\n </h2>\n <button\n type=\"button\"\n onClick={() => handleNavigate('/vibes')}\n className=\"text-sm text-text-link hover:underline\"\n >\n {tr('overviewPage.sections.viewAll', 'View all')}\n </button>\n </div>\n {activeVibes.length > 0 ? (\n <MotionList\n className=\"grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-3 gap-3\"\n stagger={0.04}\n >\n {activeVibes.slice(0, 6).map((vibe) => (\n <MotionItem key={vibe.id}>\n <VibeCard vibe={vibe} onOpen={(v) => handleNavigate(`/vibes/${v.id}`)} />\n </MotionItem>\n ))}\n </MotionList>\n ) : (\n <div className=\"text-center py-8 text-text-secondary\">\n <Zap className=\"size-12 mx-auto mb-2 opacity-50\" />\n <p>{tr('overviewPage.empty.noActiveVibes', 'No active vibes')}</p>\n <button\n type=\"button\"\n onClick={() => handleNavigate('/vibes')}\n className=\"mt-2 text-sm text-text-link hover:underline\"\n >\n {tr('overviewPage.empty.addVibe', 'Add a vibe')}\n </button>\n </div>\n )}\n </Card>\n </MotionItem>\n </MotionPage>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"OverviewPage.js","names":[],"sources":["../../src/pages/OverviewPage.tsx"],"sourcesContent":["import { useMemo } from 'react';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { VIBE_PERMISSIONS } from '../constants/permissions';\nimport {\n Bot,\n Zap,\n Terminal,\n FileText,\n Activity,\n Server,\n Play,\n GitBranch,\n RefreshCw,\n} from 'lucide-react';\nimport { MotionPage, MotionList, MotionItem } from '@burdenoff/fe-libs/shared/motion';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { AgentCard } from '@/components/agents';\nimport { SessionCard } from '@/components/sessions';\nimport { VibeCard } from '@/components/vibes';\nimport {\n PageHeader,\n StatCard,\n Card,\n AgentSetupBanner,\n ErrorState,\n LoadingState,\n} from '@/components/shared';\nimport { useVibeNavigate } from '@/providers/VibeControlsProvider';\nimport { useCachedInitialData } from '@/hooks/useCachedInitialData';\nimport type { Vibe } from '@/components/vibes/types';\nimport type { Session } from '@/components/sessions/types';\n\n/**\n * Overview page showing summary dashboard with stats, active resources, and recent activity\n *\n * This page now uses cached initial data from Apollo instead of independent queries,\n * reducing GraphQL calls from 5 to 0 for the initial render.\n * The refresh button triggers a refetch of the initial data.\n */\nexport function OverviewPage() {\n const handleNavigate = useVibeNavigate();\n const { t } = useI18n();\n const { hasPermission, isLoading: permissionsLoading } = usePermissions();\n\n const tr = (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\n // Use cached initial data - reads from Apollo cache, no network request\n // Loading state reflects whether initial data has been loaded\n const {\n agents: cachedAgents,\n vibes: cachedVibes,\n sessions: cachedSessions,\n notes,\n targets,\n loading,\n error: dataError,\n totals,\n refetch,\n } = useCachedInitialData();\n\n if (dataError) {\n // eslint-disable-next-line no-console -- intentional debug breadcrumb for failed overview load\n console.error('[overview] failed to load initial workspace data', dataError);\n }\n\n // Map cached data to component types\n // Using type assertion since we know the structure from the GraphQL query\n const agents = useMemo(() => {\n return cachedAgents.map((agent) => ({\n id: agent.id,\n workspaceId: '',\n name: agent.name,\n hostname: agent.hostname,\n platform: agent.platform,\n architecture: agent.architecture || '',\n version: agent.version || '',\n isActive: agent.isActive,\n status: agent.isActive ? ('ACTIVE' as const) : ('OFFLINE' as const),\n lastHeartbeat: agent.lastHeartbeat || null,\n tunnelUrl: agent.tunnelUrl ?? null,\n tunnelStatus: agent.tunnelStatus ?? null,\n agentApiKey: null,\n aiContext: null,\n configuration: {},\n capabilities: [],\n metadata: {},\n createdAt: agent.createdAt || '',\n updatedAt: '',\n }));\n }, [cachedAgents]);\n\n const vibes = useMemo(() => {\n return cachedVibes.map((vibe) => ({\n id: vibe.id,\n workspaceId: '',\n name: vibe.name,\n description: '',\n type: (vibe.type || 'PROJECT') as Vibe['type'],\n status: vibe.status as Vibe['status'],\n path: '',\n parentId: null,\n gitBranch: null,\n gitRemote: null,\n gitStatus: null,\n environmentVariables: {},\n tags: [],\n metadata: {},\n createdAt: '',\n updatedAt: '',\n archivedAt: null,\n // Multi-agent: surface attached agents so Overview cards can render a\n // count badge (\"+N agents\") or single name. Empty when none.\n agents: vibe.agents ?? [],\n }));\n }, [cachedVibes]);\n\n const sessions: Session[] = useMemo(() => {\n return cachedSessions.map((session) => ({\n id: session.id,\n workspaceId: '',\n agentId: session.agentId ?? null,\n vibeId: session.vibeId ?? null,\n name: session.name,\n type: (session.type || 'TERMINAL') as Session['type'],\n status: session.status as Session['status'],\n command: null,\n workingDirectory: null,\n environmentVariables: {},\n autoStart: false,\n pid: null,\n exitCode: null,\n lastOutput: null,\n metadata: {},\n createdAt: session.createdAt || '',\n updatedAt: '',\n agent: undefined,\n vibe: undefined,\n }));\n }, [cachedSessions]);\n\n // Calculate stats from cached totals\n const activeAgents = agents.filter((a) => a.isActive);\n const runningSessions = sessions.filter((s) => s.status === 'RUNNING');\n const activeVibes = vibes.filter((v) => v.status === 'ACTIVE');\n\n const stats = {\n agents: {\n total: totals.agents,\n active: activeAgents.length,\n offline: agents.filter((a) => !a.isActive).length,\n },\n vibes: {\n total: totals.vibes,\n active: activeVibes.length,\n archived: vibes.filter((v) => v.status === 'ARCHIVED').length,\n },\n sessions: {\n total: totals.sessions,\n running: runningSessions.length,\n stopped: sessions.filter((s) => s.status === 'STOPPED').length,\n },\n notes: {\n total: totals.notes,\n pinned: notes.filter((n) => n.isPinned).length,\n },\n };\n\n // Recent activity - derive from session/agent data (simplified for now)\n const recentActivity = useMemo(() => {\n const activities: Array<{\n id: string;\n type: string;\n title: string;\n description: string;\n timestamp: string;\n color: string;\n }> = [];\n\n // Add recent sessions as activity\n sessions.slice(0, 3).forEach((session) => {\n activities.push({\n id: `session-${session.id}`,\n type: 'session_started',\n title:\n session.status === 'RUNNING'\n ? tr('overviewPage.activity.sessionRunning', 'Session Running')\n : tr('overviewPage.activity.sessionStatus', 'Session {{status}}', {\n status: session.status.toLowerCase(),\n }),\n description: `${session.name}`,\n timestamp: session.createdAt,\n color: session.status === 'RUNNING' ? 'green' : 'blue',\n });\n });\n\n // Add active agents as activity\n activeAgents.slice(0, 2).forEach((agent) => {\n activities.push({\n id: `agent-${agent.id}`,\n type: 'agent_connected',\n title: tr('overviewPage.activity.agentOnline', 'Agent Online'),\n description: tr('overviewPage.activity.agentOnlineDescription', '{{name}} is active', {\n name: agent.name,\n }),\n timestamp: agent.lastHeartbeat || agent.updatedAt,\n color: 'blue',\n });\n });\n\n // Sort by timestamp descending\n return activities\n .sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime())\n .slice(0, 6);\n }, [sessions, activeAgents]);\n\n if (\n permissionsLoading ||\n (loading &&\n cachedAgents.length === 0 &&\n cachedVibes.length === 0 &&\n cachedSessions.length === 0)\n )\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <LoadingState message={tr('overviewPage.loadingPermissions', 'Loading permissions...')} />\n </div>\n );\n if (!hasPermission(VIBE_PERMISSIONS.VIBE_LIST))\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <div className=\"text-center space-y-2\">\n <h2 className=\"text-lg font-semibold text-foreground\">\n {tr('common.accessDeniedTitle', 'Access Denied')}\n </h2>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'overviewPage.accessDeniedDescription',\n \"You don't have permission to access this page.\"\n )}\n </p>\n </div>\n </div>\n );\n\n const formatTimestamp = (timestamp: string) => {\n if (!timestamp) return tr('overviewPage.time.unknown', 'Unknown');\n const diff = Date.now() - new Date(timestamp).getTime();\n if (diff < 60000) return tr('overviewPage.time.justNow', 'Just now');\n if (diff < 3600000) {\n return tr('overviewPage.time.minutesAgo', '{{count}}m ago', {\n count: Math.floor(diff / 60000),\n });\n }\n if (diff < 86400000) {\n return tr('overviewPage.time.hoursAgo', '{{count}}h ago', {\n count: Math.floor(diff / 3600000),\n });\n }\n return tr('overviewPage.time.daysAgo', '{{count}}d ago', {\n count: Math.floor(diff / 86400000),\n });\n };\n\n const getActivityIcon = (type: string) => {\n const icons: Record<string, typeof Activity> = {\n session_started: Play,\n agent_connected: Server,\n vibe_updated: GitBranch,\n note_created: FileText,\n module_installed: Activity,\n };\n const Icon = icons[type] || Activity;\n return <Icon className=\"size-4\" />;\n };\n\n const getActivityColor = (color: string) => {\n const colors: Record<string, string> = {\n green: 'bg-status-success-bg text-status-success-text',\n blue: 'bg-status-info-bg text-status-info-text',\n purple: 'bg-accent-purple-subtle text-accent-purple',\n orange: 'bg-status-warning-bg text-status-warning-text',\n cyan: 'bg-accent-teal-subtle text-accent-teal',\n yellow: 'bg-status-warning-bg text-status-warning-text',\n pink: 'bg-accent-purple-subtle text-accent-purple',\n };\n return colors[color] || 'bg-bg-sunken text-text-secondary';\n };\n\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n <PageHeader\n title={tr('overviewPage.title', 'VibeControls Overview')}\n description={tr(\n 'overviewPage.description',\n 'Monitor and manage your development environments'\n )}\n actions={\n <button\n type=\"button\"\n onClick={refetch}\n disabled={loading}\n className=\"flex items-center gap-2 px-3 py-2 text-sm bg-bg-sunken border border-border-default rounded-md text-text-primary hover:bg-bg-sunken/80 transition-colors disabled:opacity-50\"\n >\n <RefreshCw className={`size-4 ${loading ? 'animate-spin' : ''}`} />\n {tr('overviewPage.refresh', 'Refresh')}\n </button>\n }\n />\n\n {/* Friendly fallback when the initial workspace load fails — keeps the\n page header / refresh button rendered above so the user can recover\n without a hard reload. */}\n {dataError && !loading && (\n <ErrorState\n title={tr('overviewPage.loadFailed.title', 'Could not load workspace data')}\n message={tr(\n 'overviewPage.loadFailed.message',\n \"We couldn't load your VibeControls overview. Please try again in a moment.\"\n )}\n onRetry={refetch}\n />\n )}\n\n {/* Agent CLI Setup Banner — shown when no agents exist */}\n {!dataError && agents.length === 0 && !loading && (\n <AgentSetupBanner variant=\"full\" dismissKey=\"vibecontrols-overview-setup-banner\" />\n )}\n\n {/* Stats Grid */}\n <MotionList\n data-tour=\"overview-stats\"\n className=\"grid grid-cols-2 sm:grid-cols-5 gap-4\"\n stagger={0.06}\n >\n <MotionItem variant=\"fadeScale\">\n <StatCard\n title={tr('overviewPage.stats.targets', 'Targets')}\n value={loading ? '-' : targets.length}\n subtitle={\n loading\n ? ''\n : tr('overviewPage.stats.connected', '{{count}} connected', {\n count: targets.filter((t) => t.status === 'connected' || t.status === 'active')\n .length,\n })\n }\n icon={<Server className=\"size-6\" />}\n variant={!loading && targets.length > 0 ? 'success' : 'default'}\n onClick={() => handleNavigate('/targets')}\n />\n </MotionItem>\n <MotionItem variant=\"fadeScale\">\n <StatCard\n title={tr('overviewPage.stats.agents', 'Agents')}\n value={loading ? '-' : stats.agents.total}\n subtitle={\n loading\n ? ''\n : tr('overviewPage.stats.active', '{{count}} active', {\n count: stats.agents.active,\n })\n }\n icon={<Bot className=\"size-6\" />}\n variant={!loading && stats.agents.active > 0 ? 'success' : 'default'}\n onClick={() => handleNavigate('/agents')}\n />\n </MotionItem>\n <MotionItem variant=\"fadeScale\">\n <StatCard\n title={tr('overviewPage.stats.vibes', 'Vibes')}\n value={loading ? '-' : stats.vibes.total}\n subtitle={\n loading\n ? ''\n : tr('overviewPage.stats.active', '{{count}} active', {\n count: stats.vibes.active,\n })\n }\n icon={<Zap className=\"size-6\" />}\n onClick={() => handleNavigate('/vibes')}\n />\n </MotionItem>\n <MotionItem variant=\"fadeScale\">\n <StatCard\n title={tr('overviewPage.stats.sessions', 'Sessions')}\n value={loading ? '-' : stats.sessions.total}\n subtitle={\n loading\n ? ''\n : tr('overviewPage.stats.running', '{{count}} running', {\n count: stats.sessions.running,\n })\n }\n icon={<Terminal className=\"size-6\" />}\n variant={!loading && stats.sessions.running > 0 ? 'success' : 'default'}\n onClick={() => handleNavigate('/sessions')}\n />\n </MotionItem>\n <MotionItem variant=\"fadeScale\" className=\"col-span-2 sm:col-span-1\">\n <StatCard\n title={tr('overviewPage.stats.notes', 'Notes')}\n value={loading ? '-' : stats.notes.total}\n subtitle={\n loading\n ? ''\n : tr('overviewPage.stats.pinned', '{{count}} pinned', {\n count: stats.notes.pinned,\n })\n }\n icon={<FileText className=\"size-6\" />}\n />\n </MotionItem>\n </MotionList>\n\n {/* Main Content Grid */}\n <MotionList className=\"grid grid-cols-1 gap-6 xl:grid-cols-3\" stagger={0.08}>\n {/* Active Agents */}\n <MotionItem className=\"xl:col-span-2\">\n <Card>\n <div className=\"flex items-center justify-between mb-4\">\n <h2 className=\"text-lg font-semibold text-text-primary flex items-center gap-2\">\n <Bot className=\"size-5\" />\n {tr('overviewPage.sections.activeAgents', 'Active Agents')}\n </h2>\n <button\n type=\"button\"\n onClick={() => handleNavigate('/agents')}\n className=\"text-sm text-text-link hover:underline\"\n aria-label={tr('overviewPage.sections.viewAllAgents', 'View all agents')}\n >\n {tr('overviewPage.sections.viewAll', 'View all')}\n </button>\n </div>\n {activeAgents.length > 0 ? (\n <MotionList className=\"grid grid-cols-1 sm:grid-cols-2 gap-3\" stagger={0.04}>\n {activeAgents.slice(0, 4).map((agent) => (\n <MotionItem key={agent.id}>\n <AgentCard agent={agent} onSelect={(a) => handleNavigate(`/agents/${a.id}`)} />\n </MotionItem>\n ))}\n </MotionList>\n ) : (\n <div className=\"text-center py-6 text-text-secondary\">\n <Bot className=\"size-12 mx-auto mb-2 opacity-50\" />\n <p>{tr('overviewPage.empty.noActiveAgents', 'No active agents')}</p>\n <p className=\"text-xs text-text-muted mt-1 mb-3\">\n {tr('overviewPage.empty.installCliHint', 'Install the agent CLI to get started')}\n </p>\n <div className=\"flex flex-wrap items-center justify-center gap-2\">\n <button\n type=\"button\"\n onClick={() => handleNavigate('/getting-started')}\n className=\"text-sm text-text-link hover:underline\"\n >\n {tr('overviewPage.empty.setupGuide', 'Setup guide')}\n </button>\n <span className=\"text-text-muted\">&middot;</span>\n <button\n type=\"button\"\n onClick={() => handleNavigate('/getting-started')}\n className=\"text-sm font-medium text-text-link hover:underline\"\n >\n {tr('overviewPage.empty.fullSetupGuide', 'View full setup guide')}\n </button>\n <span className=\"text-text-muted\">&middot;</span>\n <button\n type=\"button\"\n onClick={() => handleNavigate('/agents')}\n className=\"text-sm text-text-link hover:underline\"\n >\n {tr('overviewPage.empty.addAgent', 'Add agent')}\n </button>\n </div>\n </div>\n )}\n </Card>\n </MotionItem>\n\n {/* Recent Activity */}\n <MotionItem>\n <Card>\n <div className=\"flex items-center justify-between mb-4\">\n <h2 className=\"text-lg font-semibold text-text-primary flex items-center gap-2\">\n <Activity className=\"size-5\" />\n {tr('overviewPage.sections.recentActivity', 'Recent Activity')}\n </h2>\n <button\n type=\"button\"\n onClick={() => handleNavigate('/audit')}\n className=\"text-sm text-text-link hover:underline\"\n aria-label={tr('overviewPage.sections.viewAllActivity', 'View all activity')}\n >\n View all\n </button>\n </div>\n <MotionList className=\"space-y-3\" stagger={0.04}>\n {recentActivity.length > 0 ? (\n recentActivity.map((activity) => (\n <MotionItem key={activity.id} variant=\"fadeRight\">\n <div className=\"flex items-start gap-3\">\n <div\n className={`flex-shrink-0 size-8 rounded-full flex items-center justify-center ${getActivityColor(activity.color)}`}\n >\n {getActivityIcon(activity.type)}\n </div>\n <div className=\"flex-1 min-w-0\">\n <p className=\"text-sm font-medium text-text-primary truncate\">\n {activity.title}\n </p>\n <p className=\"text-xs text-text-secondary truncate\">\n {activity.description}\n </p>\n </div>\n <span className=\"text-xs text-text-secondary flex-shrink-0\">\n {formatTimestamp(activity.timestamp)}\n </span>\n </div>\n </MotionItem>\n ))\n ) : (\n <div className=\"text-center py-4 text-text-secondary text-sm\">\n <p>{tr('overviewPage.empty.noRecentActivity', 'No recent activity')}</p>\n <button\n type=\"button\"\n onClick={() => handleNavigate('/audit')}\n className=\"mt-2 text-sm text-text-link hover:underline\"\n >\n {tr('overviewPage.empty.viewAuditLog', 'View audit log')}\n </button>\n </div>\n )}\n </MotionList>\n </Card>\n </MotionItem>\n </MotionList>\n\n {/* Running Sessions */}\n <MotionItem>\n <Card>\n <div className=\"flex items-center justify-between mb-4\">\n <h2 className=\"text-lg font-semibold text-text-primary flex items-center gap-2\">\n <Terminal className=\"size-5\" />\n {tr('overviewPage.sections.runningSessions', 'Running Sessions')}\n </h2>\n <button\n type=\"button\"\n onClick={() => handleNavigate('/sessions')}\n className=\"text-sm text-text-link hover:underline\"\n >\n {tr('overviewPage.sections.viewAll', 'View all')}\n </button>\n </div>\n {runningSessions.length > 0 ? (\n <MotionList\n className=\"grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-3 gap-3\"\n stagger={0.04}\n >\n {runningSessions.slice(0, 6).map((session) => (\n <MotionItem key={session.id}>\n <SessionCard\n session={session}\n onOpen={(s) => handleNavigate(`/sessions/${s.id}`)}\n />\n </MotionItem>\n ))}\n </MotionList>\n ) : (\n <div className=\"text-center py-8 text-text-secondary\">\n <Terminal className=\"size-12 mx-auto mb-2 opacity-50\" />\n <p>{tr('overviewPage.empty.noRunningSessions', 'No running sessions')}</p>\n <button\n type=\"button\"\n onClick={() => handleNavigate('/sessions')}\n className=\"mt-2 text-sm text-text-link hover:underline\"\n >\n {tr('overviewPage.empty.startSession', 'Start a session')}\n </button>\n </div>\n )}\n </Card>\n </MotionItem>\n\n {/* Active Vibes */}\n <MotionItem>\n <Card>\n <div className=\"flex items-center justify-between mb-4\">\n <h2 className=\"text-lg font-semibold text-text-primary flex items-center gap-2\">\n <Zap className=\"size-5\" />\n {tr('overviewPage.sections.activeVibes', 'Active Vibes')}\n </h2>\n <button\n type=\"button\"\n onClick={() => handleNavigate('/vibes')}\n className=\"text-sm text-text-link hover:underline\"\n >\n {tr('overviewPage.sections.viewAll', 'View all')}\n </button>\n </div>\n {activeVibes.length > 0 ? (\n <MotionList\n className=\"grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-3 gap-3\"\n stagger={0.04}\n >\n {activeVibes.slice(0, 6).map((vibe) => (\n <MotionItem key={vibe.id}>\n <VibeCard vibe={vibe} onOpen={(v) => handleNavigate(`/vibes/${v.id}`)} />\n </MotionItem>\n ))}\n </MotionList>\n ) : (\n <div className=\"text-center py-8 text-text-secondary\">\n <Zap className=\"size-12 mx-auto mb-2 opacity-50\" />\n <p>{tr('overviewPage.empty.noActiveVibes', 'No active vibes')}</p>\n <button\n type=\"button\"\n onClick={() => handleNavigate('/vibes')}\n className=\"mt-2 text-sm text-text-link hover:underline\"\n >\n {tr('overviewPage.empty.addVibe', 'Add a vibe')}\n </button>\n </div>\n )}\n </Card>\n </MotionItem>\n </MotionPage>\n );\n}\n"],"mappings":"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"}
@@ -21,7 +21,16 @@ import { Fragment as y, jsx as b, jsxs as x } from "react/jsx-runtime";
21
21
  import { MotionItem as S, MotionList as Pe, MotionPage as C } from "@burdenoff/fe-libs/shared/motion";
22
22
  //#region src/pages/SessionDetailsPage.tsx
23
23
  function Fe(e) {
24
- return e.ttydUrl || null;
24
+ let t = e.ttydUrl;
25
+ if (!t) return null;
26
+ let n = e.agent?.agentApiKey;
27
+ if (!n) return t;
28
+ try {
29
+ let e = new URL(t);
30
+ return e.searchParams.has("apiKey") || e.searchParams.set("apiKey", n), e.toString();
31
+ } catch {
32
+ return t;
33
+ }
25
34
  }
26
35
  var Ie = (e) => e.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
27
36
  function w() {
@@ -1 +1 @@
1
- {"version":3,"file":"SessionDetailsPage.js","names":[],"sources":["../../src/pages/SessionDetailsPage.tsx"],"sourcesContent":["import { useState, useCallback, useEffect, useRef } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { VIBE_PERMISSIONS } from '../constants/permissions';\nimport { EntityTagPicker } from '@/components/shared/EntityTagPicker';\nimport { PageSkeleton } from '@/components/shared/PageSkeleton';\nimport { useParams } from 'react-router-dom';\nimport { useVibeControlsStore } from '@/store/vibeControlsStore';\nimport {\n ArrowLeft,\n Terminal,\n Play,\n Square,\n RotateCcw,\n ExternalLink,\n Server,\n Zap,\n Clock,\n Folder,\n Code,\n Variable,\n Hash,\n RefreshCw,\n Loader2,\n AlertCircle,\n Maximize2,\n Minimize2,\n ChevronDown,\n ChevronUp,\n Pencil,\n Check,\n X,\n Tag,\n} from 'lucide-react';\nimport {\n PageHeader,\n Card,\n StatusBadge,\n EmptyState,\n ErrorState,\n ConfirmDialog,\n} from '@/components/shared';\nimport { MotionPage, MotionList, MotionItem } from '@burdenoff/fe-libs/shared/motion';\nimport { useVibeControls, useVibeNavigate } from '@/providers/VibeControlsProvider';\nimport {\n useGetSessionQuery,\n useStartSessionMutation,\n useStopSessionMutation,\n useRestartSessionMutation,\n useStartSessionTerminalMutation,\n useRestartSessionTerminalMutation,\n useUpdateSessionMutation,\n} from '@/generated/wspace-operations';\nimport {\n getQuotaDialogContent,\n markQuotaHandledLocally,\n isQuotaExhaustedError,\n} from '@/utils/quotaUtils';\n\n/**\n * Resolves the terminal URL for a session.\n * Each session gets its own ttyd instance + cloudflared tunnel.\n * The URL is set by the backend's startSessionTerminal mutation.\n */\nfunction getTerminalUrl(session: { ttydUrl?: string | null }): string | null {\n return session.ttydUrl || null;\n}\n\n// Escape regex meta-chars for use inside `new RegExp(...)`. Pure,\n// no closure state, so hoisted to module scope to avoid re-creating\n// the function on every `tr()` invocation.\nconst escapeRe = (s: string) => s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\n/**\n * Session details page - shows comprehensive information about a single session\n * with an integrated terminal panel.\n */\nexport function SessionDetailsPage() {\n const { t } = useI18n();\n // tr(key, fallback, params?) — forwards params to useI18n().t so\n // `{{name}}` placeholders are interpolated. Also interpolates into\n // the fallback when the i18n key isn't in the catalog yet, so a\n // missing key doesn't render literal `{{name}}` text to users.\n const tr = useCallback(\n (key: string, fallback: string, params?: Record<string, string | number>): string => {\n const value = t(key, params);\n if (value !== key) return value;\n if (!params) return fallback;\n // Mirror the i18n layer's `{{key}}` placeholder syntax. Replacer\n // function (not a string) so values containing `$$`/`$&` aren't\n // interpreted as `String.prototype.replace` specials.\n return Object.entries(params).reduce(\n (acc, [k, v]) =>\n acc.replace(new RegExp(`\\\\{\\\\{${escapeRe(k)}\\\\}\\\\}`, 'g'), () => String(v)),\n fallback\n );\n },\n [t]\n );\n const { id } = useParams<{ id: string }>();\n const { basePath: basePathProp } = useVibeControls();\n const basePath = basePathProp || '';\n const navigate = useVibeNavigate();\n const { hasPermission, isLoading: permissionsLoading, permissions } = usePermissions();\n\n const [startSession] = useStartSessionMutation({ refetchQueries: ['GetSession'] });\n const [stopSession] = useStopSessionMutation({ refetchQueries: ['GetSession'] });\n const [restartSession] = useRestartSessionMutation({ refetchQueries: ['GetSession'] });\n const [startSessionTerminal] = useStartSessionTerminalMutation({\n refetchQueries: ['GetSession'],\n });\n const [restartSessionTerminal] = useRestartSessionTerminalMutation({\n refetchQueries: ['GetSession'],\n });\n const [updateSession] = useUpdateSessionMutation({ refetchQueries: ['GetSession'] });\n\n // Command/working-directory inline edit state\n const [editingCommand, setEditingCommand] = useState(false);\n const [editCommand, setEditCommand] = useState('');\n const [editWorkingDirectory, setEditWorkingDirectory] = useState('');\n const [editSaving, setEditSaving] = useState(false);\n\n const handleEditCommandOpen = () => {\n setEditCommand(session?.command || '');\n setEditWorkingDirectory(session?.workingDirectory || '');\n setEditingCommand(true);\n };\n\n const handleEditCommandSave = async () => {\n if (!session?.id) return;\n setEditSaving(true);\n try {\n await updateSession({\n variables: {\n id: session.id,\n input: {\n command: editCommand || null,\n workingDirectory: editWorkingDirectory || null,\n },\n },\n });\n setEditingCommand(false);\n } finally {\n setEditSaving(false);\n }\n };\n\n // Fetch session by ID from GraphQL API\n const { data, loading, error, refetch } = useGetSessionQuery({\n variables: { id: id || '' },\n skip: !id,\n fetchPolicy: 'cache-and-network',\n });\n\n // Get session from response\n const session = data?.session;\n\n // Get related agent and vibe from the session response\n const agent = session?.agent || null;\n const vibe = session?.vibe || null;\n\n // ── Sync active context ────────────────────────────────────────────────\n useEffect(() => {\n if (session) {\n useVibeControlsStore.setState({\n activeAgentId: (agent?.id as string) ?? null,\n selectedAgentId: (agent?.id as string) ?? null,\n activeVibeId: (vibe?.id as string) ?? null,\n selectedVibeId: (vibe?.id as string) ?? null,\n activeSessionId: session.id,\n selectedSessionId: session.id,\n });\n }\n }, [session?.id, agent?.id, vibe?.id]); // eslint-disable-line react-hooks/exhaustive-deps\n\n // Terminal state\n const [terminalOpen, setTerminalOpen] = useState(true);\n const [terminalMaximized, setTerminalMaximized] = useState(false);\n const [terminalLoading, setTerminalLoading] = useState(false);\n const [terminalError, setTerminalError] = useState<string | null>(null);\n const [showQuotaExhausted, setShowQuotaExhausted] = useState(false);\n const [showRestartTerminalConfirm, setShowRestartTerminalConfirm] = useState(false);\n const [exitedNotice, setExitedNotice] = useState<{\n exitCode: number | null;\n } | null>(null);\n const prevStatusRef = useRef<string | null>(null);\n\n // Ref target for scrollIntoView when the terminal opens.\n const terminalPanelRef = useRef<HTMLDivElement | null>(null);\n\n // Track first-render + the previous open state so the toggle-driven\n // scroll only fires on a deliberate closed→open transition, not on\n // initial mount (terminalOpen defaults to true).\n const didInitialMountRef = useRef(false);\n const prevTerminalOpenRef = useRef(false);\n\n const terminalUrl = session ? getTerminalUrl(session) : null;\n\n const handleBack = () => {\n navigate('/sessions');\n };\n\n const handleStartSession = async () => {\n if (!session?.id) return;\n try {\n await startSession({ 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 start session:', err);\n }\n };\n\n const handleStopSession = async () => {\n if (!session?.id) return;\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 () => {\n if (!session?.id) return;\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 // Scroll the terminal panel into view as a fire-and-forget op,\n // gated on the panel ref being mounted. Owned by the handler that\n // triggered the action, not by an effect — so a successful\n // start/restart always scrolls exactly once, even when the URL\n // didn't change (no useEffect re-run otherwise).\n const scrollTerminalIntoView = useCallback(() => {\n requestAnimationFrame(() => {\n terminalPanelRef.current?.scrollIntoView({ behavior: 'smooth', block: 'end' });\n });\n }, []);\n\n // Shared bookkeeping for start/restart-terminal: identical try/catch\n // /finally shape, identical quota-exhausted + generic-error handling;\n // only the mutation and the fallback error string differ. Takes the\n // session id as an explicit arg so callers don't need a non-null\n // assertion against `session` — the helper owns the null guard.\n const withTerminalAction = useCallback(\n async <T,>(\n action: (id: string) => Promise<{\n error?: { message: string } | null | undefined;\n data?: T | null | undefined;\n }>,\n dataKey: keyof T,\n fallbackErrorMsg: string\n ) => {\n const id = session?.id;\n if (!id) return;\n setTerminalLoading(true);\n setTerminalError(null);\n let succeeded = false;\n try {\n const result = await action(id);\n if (result.error && isQuotaExhaustedError(result.error)) {\n markQuotaHandledLocally();\n setShowQuotaExhausted(true);\n } else if (result.error) {\n setTerminalError(result.error.message);\n } else if (!result.data || !result.data[dataKey]) {\n setTerminalError(fallbackErrorMsg);\n } else {\n setTerminalOpen(true);\n succeeded = true;\n }\n } catch (err) {\n if (isQuotaExhaustedError(err)) {\n markQuotaHandledLocally();\n setShowQuotaExhausted(true);\n return;\n }\n const message = err instanceof Error ? err.message : fallbackErrorMsg;\n setTerminalError(message);\n } finally {\n setTerminalLoading(false);\n if (succeeded) scrollTerminalIntoView();\n }\n },\n [session?.id, scrollTerminalIntoView]\n );\n\n const handleStartTerminal = useCallback(\n () =>\n withTerminalAction(\n (id) => startSessionTerminal({ variables: { id } }),\n 'startSessionTerminal',\n 'Failed to start terminal'\n ),\n [startSessionTerminal, withTerminalAction]\n );\n\n const handleRestartTerminal = useCallback(\n () =>\n withTerminalAction(\n (id) => restartSessionTerminal({ variables: { id } }),\n 'restartSessionTerminal',\n 'Failed to restart terminal'\n ),\n [restartSessionTerminal, withTerminalAction]\n );\n\n // Scroll on the toggle button — closed→open. User-initiated start/\n // restart paths scroll inline from their handlers (see\n // `scrollTerminalIntoView`), which avoids the stale-flag problem\n // where a no-op success leaves a ref stuck true and a later\n // background refetch fires an unexpected scroll.\n useEffect(() => {\n if (!didInitialMountRef.current) {\n didInitialMountRef.current = true;\n prevTerminalOpenRef.current = terminalOpen;\n return;\n }\n const justOpened = terminalOpen && !prevTerminalOpenRef.current;\n prevTerminalOpenRef.current = terminalOpen;\n if (justOpened) scrollTerminalIntoView();\n }, [terminalOpen, scrollTerminalIntoView]);\n\n // Track the last session whose status we processed, so a route\n // change to a sibling /sessions/:id reseeds `prevStatusRef` with\n // the new session's *current* status — not `null`. Reseeding to\n // null was the bug: if the next status update happened to equal\n // the seed, the transition guard (prev === 'RUNNING'/'STARTING')\n // never matched and the command-exited notice never fired.\n const sessionId = session?.id ?? null;\n const lastProcessedSessionRef = useRef<string | null>(null);\n\n // Surface \"command exited\" inline when the session's status flips\n // to STOPPED while the terminal panel is still open — otherwise\n // the empty iframe looks like a crash.\n useEffect(() => {\n const status = session?.status ?? null;\n const exitCode = session?.exitCode ?? null;\n\n // Session changed: reseed prev to the new session's current\n // status and clear any stale notice. Don't fire a transition on\n // this first observation — we have no \"before\" state yet.\n if (sessionId !== lastProcessedSessionRef.current) {\n lastProcessedSessionRef.current = sessionId;\n prevStatusRef.current = status;\n setExitedNotice(null);\n return;\n }\n\n if (status === 'RUNNING' || status === 'STARTING') {\n // Clear stale notice once the session is running again, then\n // record the status. Mutually exclusive with the STOPPED paths\n // below, so handle it with an early return.\n setExitedNotice(null);\n prevStatusRef.current = status;\n return;\n }\n\n const prev = prevStatusRef.current;\n if (status === 'STOPPED' && (prev === 'RUNNING' || prev === 'STARTING') && terminalOpen) {\n setExitedNotice({ exitCode });\n } else if (status === 'STOPPED' && terminalOpen && exitCode !== null) {\n // exitCode can land in a later GraphQL response than the\n // status transition (poll N returns STOPPED + null code, poll\n // N+1 returns the real code). Patch the existing notice so\n // the banner shows the real code rather than \"(exit null)\".\n setExitedNotice((cur) => (cur && cur.exitCode === null ? { exitCode } : cur));\n }\n prevStatusRef.current = status;\n }, [sessionId, session?.status, session?.exitCode, terminalOpen]);\n\n const handleOpenInNewTab = useCallback(() => {\n if (terminalUrl) {\n window.open(terminalUrl, '_blank');\n }\n }, [terminalUrl]);\n\n if (permissionsLoading)\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <p className=\"text-text-secondary\">\n {tr('sessionDetails.loadingPermissions', 'Loading permissions...')}\n </p>\n </div>\n );\n if (\n permissions &&\n (permissions.permissionStrings?.length ?? 0) > 0 &&\n !hasPermission(VIBE_PERMISSIONS.SESSION_READ)\n )\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <div className=\"text-center space-y-2\">\n <h2 className=\"text-lg font-semibold text-foreground\">\n {tr('sessionDetails.accessDenied', 'Access Denied')}\n </h2>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'sessionDetails.accessDeniedDesc',\n \"You don't have permission to access this page.\"\n )}\n </p>\n </div>\n </div>\n );\n\n const handleAgentClick = () => {\n if (!agent) return;\n navigate(`/agents/${agent.id}`);\n };\n\n const handleVibeClick = () => {\n if (!vibe) return;\n navigate(`/vibes/${vibe.id}`);\n };\n\n const formatTimestamp = (timestamp: string | null | undefined) => {\n if (!timestamp) return 'Never';\n const diff = Date.now() - new Date(timestamp).getTime();\n if (diff < 60000) return 'Just now';\n if (diff < 3600000) return `${Math.floor(diff / 60000)}m ago`;\n if (diff < 86400000) return `${Math.floor(diff / 3600000)}h ago`;\n return new Date(timestamp).toLocaleDateString();\n };\n\n // Guard states wrapped in MotionPage to prevent unmount/remount animation replay\n if (loading && !session) {\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"flex items-center gap-2 text-text-secondary hover:text-text-primary transition-colors\"\n >\n <ArrowLeft className=\"size-4\" />\n {tr('sessionDetails.backToSessions', 'Back to Sessions')}\n </button>\n <PageSkeleton variant=\"detail\" />\n </MotionPage>\n );\n }\n\n if (error && !session) {\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"flex items-center gap-2 text-text-secondary hover:text-text-primary transition-colors\"\n >\n <ArrowLeft className=\"size-4\" />\n Back to Sessions\n </button>\n <ErrorState\n title={tr('sessionDetails.failedLoad', 'Failed to load session')}\n message={error.message}\n onRetry={() => refetch()}\n />\n </MotionPage>\n );\n }\n\n if (!session) {\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"flex items-center gap-2 text-text-secondary hover:text-text-primary transition-colors\"\n >\n <ArrowLeft className=\"size-4\" />\n Back to Sessions\n </button>\n <EmptyState\n icon={<Terminal className=\"size-8 text-text-secondary\" />}\n title={tr('sessionDetails.notFound', 'Session not found')}\n description={`${tr('sessionDetails.notFoundDescPrefix', 'No session with ID')} \"${id}\" ${tr('sessionDetails.notFoundDescSuffix', 'exists')}`}\n action={\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors\"\n >\n {tr('sessionDetails.backToSessions', 'Back to Sessions')}\n </button>\n }\n />\n </MotionPage>\n );\n }\n\n const isRunning = session.status === 'RUNNING';\n const terminalHeight = terminalMaximized ? 'h-[calc(100vh-200px)]' : 'h-[280px] sm:h-[420px]';\n\n return (\n <MotionPage className=\"flex flex-col bg-bg-surface min-h-screen\">\n {/* Main content - scrollable */}\n <div\n className={`flex-1 overflow-y-auto space-y-6 p-4 sm:p-6 ${terminalOpen && terminalUrl ? 'pb-2' : ''}`}\n >\n {/* Back Button */}\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"flex items-center gap-2 text-text-secondary hover:text-text-primary transition-colors\"\n >\n <ArrowLeft className=\"size-4\" />\n Back to Sessions\n </button>\n\n {/* Header */}\n <PageHeader\n title={session.name}\n description={session.command || 'No command specified'}\n breadcrumb={[\n { label: 'Sessions', path: basePath ? `${basePath}/sessions` : '/sessions' },\n { label: session.name },\n ]}\n actions={\n <div className=\"flex items-center gap-2 flex-wrap\">\n <button\n type=\"button\"\n onClick={() => refetch()}\n disabled={loading}\n className=\"flex items-center gap-2 px-3 py-2 bg-bg-sunken text-text-primary rounded-md hover:bg-bg-sunken/80 transition-colors disabled:opacity-50\"\n title=\"Refresh\"\n >\n <RefreshCw className={`size-4 ${loading ? 'animate-spin' : ''}`} />\n </button>\n {isRunning ? (\n <>\n <button\n type=\"button\"\n onClick={handleStopSession}\n className=\"flex items-center gap-2 px-3 py-2 bg-bg-sunken text-text-primary rounded-md hover:bg-bg-sunken/80 transition-colors\"\n >\n <Square className=\"size-4\" />\n {tr('sessionDetails.stopCommand', 'Stop Command')}\n </button>\n <button\n type=\"button\"\n onClick={handleRestartSession}\n className=\"flex items-center gap-2 px-3 py-2 bg-bg-sunken text-text-primary rounded-md hover:bg-bg-sunken/80 transition-colors\"\n >\n <RotateCcw className=\"size-4\" />\n {tr('sessionDetails.restartCommand', 'Restart Command')}\n </button>\n </>\n ) : (\n <button\n type=\"button\"\n onClick={handleStartSession}\n className=\"flex items-center gap-2 px-3 py-2 bg-status-success-bg text-status-success-text rounded-md hover:opacity-80 transition-colors\"\n >\n <Play className=\"size-4\" />\n {tr('sessionDetails.runCommand', 'Run Command')}\n </button>\n )}\n {/* Terminal toggle + open in new tab */}\n {terminalUrl ? (\n <>\n <button\n type=\"button\"\n onClick={() => setTerminalOpen((prev) => !prev)}\n className={`flex items-center gap-2 px-3 py-2 rounded-md transition-colors ${\n terminalOpen\n ? 'bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg-hover'\n : 'bg-bg-sunken text-text-primary hover:bg-bg-sunken/80'\n }`}\n title={\n terminalOpen\n ? tr('sessionDetails.hideTerminal', 'Hide terminal')\n : tr('sessionDetails.showTerminal', 'Show terminal')\n }\n >\n <Terminal className=\"size-4\" />\n {tr('sessionDetails.terminal', 'Terminal')}\n </button>\n <button\n type=\"button\"\n onClick={() => setShowRestartTerminalConfirm(true)}\n disabled={terminalLoading}\n className=\"flex items-center gap-1.5 p-2 bg-bg-sunken text-text-secondary rounded-md hover:text-text-primary hover:bg-bg-sunken/80 transition-colors disabled:opacity-50\"\n title={tr(\n 'sessionDetails.restartTerminalTitle',\n 'Restart terminal — kills the tmux session and re-runs the command'\n )}\n >\n {terminalLoading ? (\n <Loader2 className=\"size-3.5 animate-spin\" />\n ) : (\n <RotateCcw className=\"size-3.5\" />\n )}\n <span className=\"text-xs\">\n {tr('sessionDetails.restartTerminalShort', 'Restart')}\n </span>\n </button>\n <button\n type=\"button\"\n onClick={handleOpenInNewTab}\n className=\"flex items-center p-2 bg-bg-sunken text-text-secondary rounded-md hover:text-text-primary hover:bg-bg-sunken/80 transition-colors\"\n title={tr('sessionDetails.openInNewTab', 'Open terminal in new tab')}\n >\n <ExternalLink className=\"size-4\" />\n </button>\n </>\n ) : (\n <button\n type=\"button\"\n onClick={handleStartTerminal}\n disabled={terminalLoading || !agent}\n className=\"flex items-center gap-2 px-3 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50\"\n title={\n !agent\n ? tr('sessionDetails.noAgentAssigned', 'No agent assigned')\n : !agent.tunnelUrl\n ? tr(\n 'sessionDetails.agentNoTunnelUrl',\n 'Agent has no tunnel URL configured'\n )\n : tr('sessionDetails.openTerminal', 'Open terminal')\n }\n >\n {terminalLoading ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <Terminal className=\"size-4\" />\n )}\n {tr('sessionDetails.openTerminal', 'Open Terminal')}\n </button>\n )}\n </div>\n }\n />\n\n {/* Status Cards */}\n <MotionList stagger={0.04} className=\"grid grid-cols-2 sm:grid-cols-4 gap-4\">\n <MotionItem>\n <Card className=\"p-4\">\n <div className=\"flex items-center gap-3\">\n <div className=\"size-10 rounded-lg bg-bg-sunken flex items-center justify-center\">\n <Terminal className=\"size-5 text-text-secondary\" />\n </div>\n <div>\n <p className=\"text-sm text-text-secondary\">\n {tr('sessionDetails.statusLabel', 'Status')}\n </p>\n <StatusBadge status={session.status} size=\"md\" />\n </div>\n </div>\n </Card>\n </MotionItem>\n <MotionItem>\n <Card className=\"p-4\">\n <div className=\"flex items-center gap-3\">\n <div className=\"size-10 rounded-lg bg-bg-sunken flex items-center justify-center\">\n <Code className=\"size-5 text-text-secondary\" />\n </div>\n <div>\n <p className=\"text-sm text-text-secondary\">\n {tr('sessionDetails.typeLabel', 'Type')}\n </p>\n <p className=\"font-semibold text-text-primary\">{session.type}</p>\n </div>\n </div>\n </Card>\n </MotionItem>\n <MotionItem>\n <Card className=\"p-4\">\n <div className=\"flex items-center gap-3\">\n <div className=\"size-10 rounded-lg bg-bg-sunken flex items-center justify-center\">\n <Hash className=\"size-5 text-text-secondary\" />\n </div>\n <div>\n <p className=\"text-sm text-text-secondary\">\n {tr('sessionDetails.pidLabel', 'PID')}\n </p>\n <p className=\"font-semibold text-text-primary\">{session.pid || 'N/A'}</p>\n </div>\n </div>\n </Card>\n </MotionItem>\n <MotionItem>\n <Card className=\"p-4\">\n <div className=\"flex items-center gap-3\">\n <div className=\"size-10 rounded-lg bg-bg-sunken flex items-center justify-center\">\n <Server className=\"size-5 text-text-secondary\" />\n </div>\n <div>\n <p className=\"text-sm text-text-secondary\">\n {tr('sessionDetails.agentLabel', 'Agent')}\n </p>\n <p className=\"font-semibold text-text-primary truncate\">\n {agent?.name || 'Unknown'}\n </p>\n </div>\n </div>\n </Card>\n </MotionItem>\n </MotionList>\n\n {/* Command */}\n <Card>\n <div className=\"p-4 border-b border-border-default flex items-center justify-between\">\n <h3 className=\"font-semibold text-text-primary flex items-center gap-2\">\n <Code className=\"size-5\" />\n {tr('sessionDetails.commandTitle', 'Command')}\n </h3>\n {!editingCommand && (\n <button\n type=\"button\"\n onClick={handleEditCommandOpen}\n className=\"p-1.5 rounded text-text-secondary hover:text-text-primary hover:bg-bg-sunken transition-colors\"\n title={tr('sessionDetails.editCommandTitle', 'Edit command and working directory')}\n >\n <Pencil className=\"size-4\" />\n </button>\n )}\n </div>\n <div className=\"p-4\">\n {editingCommand ? (\n <div className=\"space-y-3\">\n <div>\n <label className=\"block text-xs font-medium text-text-secondary mb-1\">\n {tr('sessionDetails.commandLabel', 'Command')}\n </label>\n <input\n aria-label=\"control\"\n type=\"text\"\n value={editCommand}\n onChange={(e) => setEditCommand(e.target.value)}\n placeholder={tr('sessionDetails.commandPlaceholder', 'bun run dev')}\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 focus:outline-none focus:ring-2 focus:ring-action-primary-bg font-mono\"\n />\n </div>\n <div>\n <label className=\"block text-xs font-medium text-text-secondary mb-1\">\n {tr('sessionDetails.workingDirLabel', 'Working Directory')}\n </label>\n <input\n aria-label=\"control\"\n type=\"text\"\n value={editWorkingDirectory}\n onChange={(e) => setEditWorkingDirectory(e.target.value)}\n placeholder={tr('sessionDetails.workingDirPlaceholder', '/home/user/myproject')}\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 focus:outline-none focus:ring-2 focus:ring-action-primary-bg font-mono\"\n />\n </div>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={handleEditCommandSave}\n disabled={editSaving}\n className=\"flex items-center gap-1.5 px-3 py-1.5 text-sm bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50\"\n >\n {editSaving ? (\n <Loader2 className=\"size-3.5 animate-spin\" />\n ) : (\n <Check className=\"size-3.5\" />\n )}\n {tr('sessionDetails.save', 'Save')}\n </button>\n <button\n type=\"button\"\n onClick={() => setEditingCommand(false)}\n disabled={editSaving}\n className=\"flex items-center gap-1.5 px-3 py-1.5 text-sm bg-bg-sunken text-text-primary rounded-md hover:bg-bg-elevated transition-colors disabled:opacity-50\"\n >\n <X className=\"size-3.5\" />\n {tr('sessionDetails.cancel', 'Cancel')}\n </button>\n </div>\n </div>\n ) : (\n <>\n <div className=\"bg-bg-sunken rounded-md p-3 font-mono text-sm text-text-primary overflow-x-auto\">\n ${' '}\n {session.command ||\n tr('sessionDetails.noCommandSpecified', 'No command specified')}\n </div>\n <div className=\"mt-3 flex items-center gap-2 text-sm text-text-secondary\">\n <Folder className=\"size-4\" />\n {tr('sessionDetails.workingDirectory', 'Working Directory')}:{' '}\n <span className=\"text-text-primary font-mono\">\n {session.workingDirectory || (\n <em className=\"not-italic text-text-placeholder\">\n {tr('sessionDetails.workingDirDefault', 'not set — defaults to OS home')}\n </em>\n )}\n </span>\n </div>\n </>\n )}\n </div>\n </Card>\n\n {/* Environment Variables */}\n <Card>\n <div className=\"p-4 border-b border-border-default\">\n <h3 className=\"font-semibold text-text-primary flex items-center gap-2\">\n <Variable className=\"size-5\" />\n {tr('sessionDetails.envVars', 'Environment Variables')}\n </h3>\n </div>\n <div className=\"p-4\">\n {session.environmentVariables &&\n Object.keys(session.environmentVariables).length > 0 ? (\n <div className=\"flex flex-wrap gap-2\">\n {Object.entries(session.environmentVariables).map(([key, value]) => (\n <span\n key={key}\n className=\"px-3 py-1 bg-bg-sunken text-text-primary text-sm rounded-full font-mono\"\n >\n {key}={String(value)}\n </span>\n ))}\n </div>\n ) : (\n <p className=\"text-text-secondary\">\n {tr('sessionDetails.noEnvVars', 'No environment variables configured')}\n </p>\n )}\n </div>\n </Card>\n\n {/* Last Output */}\n <Card>\n <div className=\"p-4 border-b border-border-default\">\n <h3 className=\"font-semibold text-text-primary flex items-center gap-2\">\n <Terminal className=\"size-5\" />\n {tr('sessionDetails.lastOutput', 'Last Output')}\n </h3>\n </div>\n <div className=\"p-4\">\n {session.lastOutput ? (\n <div className=\"bg-bg-sunken rounded-md p-3 font-mono text-sm text-text-primary overflow-x-auto whitespace-pre-wrap\">\n {session.lastOutput}\n </div>\n ) : (\n <p className=\"text-text-secondary\">\n {tr('sessionDetails.noOutput', 'No output captured')}\n </p>\n )}\n </div>\n </Card>\n\n {/* Related Vibe */}\n {vibe && (\n <Card onClick={handleVibeClick} hoverable>\n <div className=\"p-4 flex items-center justify-between\">\n <div className=\"flex items-center gap-3\">\n <div className=\"size-10 rounded-lg bg-bg-sunken flex items-center justify-center\">\n <Zap className=\"size-5 text-text-secondary\" />\n </div>\n <div>\n <p className=\"text-sm text-text-secondary\">\n {tr('sessionDetails.relatedVibe', 'Related Vibe')}\n </p>\n <p className=\"font-medium text-text-primary\">{vibe.name}</p>\n {vibe.path && (\n <p className=\"text-sm text-text-secondary truncate max-w-md\">{vibe.path}</p>\n )}\n </div>\n </div>\n <StatusBadge status={vibe.status} />\n </div>\n </Card>\n )}\n\n {/* Related Agent */}\n {agent && (\n <Card onClick={handleAgentClick} hoverable>\n <div className=\"p-4 flex items-center justify-between\">\n <div className=\"flex items-center gap-3\">\n <div className=\"size-10 rounded-lg bg-bg-sunken flex items-center justify-center\">\n <Server className=\"size-5 text-text-secondary\" />\n </div>\n <div>\n <p className=\"text-sm text-text-secondary\">\n {tr('sessionDetails.runningOnAgent', 'Running on Agent')}\n </p>\n <p className=\"font-medium text-text-primary\">{agent.name}</p>\n <p className=\"text-sm text-text-secondary\">{agent.hostname}</p>\n </div>\n </div>\n <StatusBadge status={agent.isActive ? 'ACTIVE' : 'OFFLINE'} />\n </div>\n </Card>\n )}\n\n {/* Metadata */}\n <Card>\n <div className=\"p-4 border-b border-border-default\">\n <h3 className=\"font-semibold text-text-primary flex items-center gap-2\">\n <Clock className=\"size-5\" />\n {tr('sessionDetails.timeline', 'Timeline')}\n </h3>\n </div>\n <div className=\"p-4\">\n <div className=\"grid grid-cols-1 sm:grid-cols-3 gap-4 text-sm\">\n <div>\n <p className=\"text-text-secondary\">\n {tr('sessionDetails.autoStart', 'Auto Start')}\n </p>\n <p className=\"font-medium text-text-primary\">\n {session.autoStart\n ? tr('sessionDetails.yes', 'Yes')\n : tr('sessionDetails.no', 'No')}\n </p>\n </div>\n <div>\n <p className=\"text-text-secondary\">{tr('sessionDetails.created', 'Created')}</p>\n <p className=\"font-medium text-text-primary\">\n {formatTimestamp(session.createdAt)}\n </p>\n </div>\n <div>\n <p className=\"text-text-secondary\">{tr('sessionDetails.updated', 'Updated')}</p>\n <p className=\"font-medium text-text-primary\">\n {formatTimestamp(session.updatedAt)}\n </p>\n </div>\n </div>\n {session.exitCode !== null && session.exitCode !== undefined && (\n <div className=\"mt-4 pt-4 border-t border-border-default\">\n <p className=\"text-sm text-text-secondary\">\n {tr('sessionDetails.exitCode', 'Exit Code')}\n </p>\n <p\n className={`font-medium ${session.exitCode === 0 ? 'text-status-success-text' : 'text-status-error-text'}`}\n >\n {session.exitCode}\n </p>\n </div>\n )}\n </div>\n </Card>\n\n {/* Tags */}\n <Card>\n <div className=\"p-4 border-b border-border-default\">\n <h3 className=\"font-semibold text-text-primary flex items-center gap-2\">\n <Tag className=\"size-5\" />\n {tr('sessionDetails.tags', 'Tags')}\n </h3>\n </div>\n <div className=\"p-4\">\n <EntityTagPicker entityType=\"VIBECONTROLS_SESSION\" entityId={session.id} />\n </div>\n </Card>\n </div>\n\n {/* Integrated Terminal Panel - docked at bottom */}\n {terminalOpen && (\n <div\n ref={terminalPanelRef}\n className=\"border-t border-border-default bg-bg-surface shrink-0\"\n >\n {/* Terminal header bar */}\n <div className=\"flex items-center bg-bg-sunken border-b border-border-default\">\n <button\n type=\"button\"\n onClick={() => setTerminalOpen(false)}\n className=\"flex items-center gap-1.5 px-3 py-2 text-text-secondary hover:text-text-primary transition-colors text-xs font-medium uppercase tracking-wide\"\n title={tr('sessionDetails.collapseTerminal', 'Collapse terminal')}\n >\n <Terminal className=\"size-3.5\" />\n {tr('sessionDetails.terminal', 'Terminal')}\n <ChevronDown className=\"size-3\" />\n </button>\n <div className=\"flex items-center px-3 py-1.5 text-xs bg-bg-surface text-text-primary border-r border-border-subtle border-b-2 border-b-action-primary-bg\">\n <Terminal className=\"size-3 mr-1.5\" />\n {session.name}\n </div>\n <div className=\"flex-1\" />\n <div className=\"flex items-center gap-1 px-2\">\n {terminalUrl && (\n <button\n type=\"button\"\n onClick={handleOpenInNewTab}\n className=\"p-1 text-text-secondary hover:text-text-primary transition-colors\"\n title={tr('sessionDetails.openInNewTabShort', 'Open in new tab')}\n >\n <ExternalLink className=\"size-3.5\" />\n </button>\n )}\n <button\n type=\"button\"\n onClick={() => setTerminalMaximized((prev) => !prev)}\n className=\"p-1 text-text-secondary hover:text-text-primary transition-colors\"\n title={\n terminalMaximized\n ? tr('sessionDetails.restoreSize', 'Restore size')\n : tr('sessionDetails.maximize', 'Maximize')\n }\n >\n {terminalMaximized ? (\n <Minimize2 className=\"size-3.5\" />\n ) : (\n <Maximize2 className=\"size-3.5\" />\n )}\n </button>\n </div>\n </div>\n\n {exitedNotice && (\n <div\n role=\"status\"\n className=\"flex items-start justify-between gap-3 px-3 py-2 bg-status-warning-bg/30 text-status-warning-text border-b border-status-warning-bg/50 text-xs\"\n >\n <div className=\"flex items-center gap-2 flex-1\">\n <AlertCircle className=\"size-3.5 shrink-0\" />\n <span>\n {tr(\n 'sessionDetails.commandExited',\n 'Command exited{{code}} — click Run Command to run it again.',\n {\n code:\n exitedNotice.exitCode !== null ? ` (exit ${exitedNotice.exitCode})` : '',\n }\n )}\n </span>\n </div>\n <button\n type=\"button\"\n onClick={() => setExitedNotice(null)}\n className=\"text-text-secondary hover:text-text-primary\"\n title={tr('common.dismiss', 'Dismiss')}\n >\n <X className=\"size-3.5\" />\n </button>\n </div>\n )}\n\n {/* Terminal content */}\n <div className={`${terminalHeight} bg-bg-inverse text-text-inverse`}>\n {terminalLoading ? (\n <div className=\"flex items-center justify-center h-full\">\n <div className=\"text-center\">\n <Loader2 className=\"size-8 text-text-secondary animate-spin mx-auto mb-3\" />\n <p className=\"text-text-secondary text-sm\">\n {tr('sessionDetails.startingTerminal', 'Starting terminal for')} {session.name}\n ...\n </p>\n <p className=\"text-text-tertiary text-xs mt-1\">\n {tr(\n 'sessionDetails.settingUpTunnel',\n 'Setting up tmux + ttyd + cloudflared tunnel'\n )}\n </p>\n </div>\n </div>\n ) : terminalError ? (\n <div className=\"flex items-center justify-center h-full\">\n <div className=\"text-center max-w-md\">\n <AlertCircle className=\"size-8 text-status-error-text mx-auto mb-3\" />\n <p className=\"text-status-error-text text-sm font-medium\">\n {tr('sessionDetails.failedStartTerminal', 'Failed to start terminal')}\n </p>\n <p className=\"text-text-secondary text-xs mt-2\">{terminalError}</p>\n <button\n type=\"button\"\n onClick={handleStartTerminal}\n className=\"mt-4 px-4 py-2 bg-bg-muted text-text-primary text-sm rounded-md hover:bg-bg-sunken transition-colors\"\n >\n {tr('sessionDetails.retry', 'Retry')}\n </button>\n </div>\n </div>\n ) : terminalUrl ? (\n <iframe\n src={terminalUrl}\n title={`Terminal - ${session.name}`}\n className=\"w-full h-full border-0\"\n allow=\"clipboard-read; clipboard-write\"\n />\n ) : (\n <div className=\"flex items-center justify-center h-full\">\n <div className=\"text-center\">\n <Terminal className=\"size-8 text-text-tertiary mx-auto mb-3\" />\n <p className=\"text-text-secondary text-sm\">\n {tr('sessionDetails.noTerminalRunning', 'No terminal running')}\n </p>\n <p className=\"text-text-tertiary text-xs mt-1\">\n {agent\n ? tr(\n 'sessionDetails.clickOpenTerminalHint',\n 'Click \"Open Terminal\" to create a tmux session with a browser terminal.'\n )\n : tr(\n 'sessionDetails.noAgentForSession',\n 'No agent is assigned to this session.'\n )}\n </p>\n {agent && (\n <button\n type=\"button\"\n onClick={handleStartTerminal}\n disabled={terminalLoading}\n className=\"mt-4 px-4 py-2 bg-bg-muted text-text-primary text-sm rounded-md hover:bg-bg-sunken transition-colors disabled:opacity-50\"\n >\n {terminalLoading\n ? tr('sessionDetails.opening', 'Opening...')\n : tr('sessionDetails.openTerminal', 'Open Terminal')}\n </button>\n )}\n </div>\n </div>\n )}\n </div>\n </div>\n )}\n\n {/* Terminal collapsed indicator - show a clickable bar */}\n {!terminalOpen && (terminalUrl || terminalError) && (\n <div className=\"border-t border-border-default bg-bg-sunken shrink-0\">\n <button\n type=\"button\"\n onClick={() => setTerminalOpen(true)}\n className=\"flex items-center gap-1.5 px-3 py-1.5 text-text-secondary hover:text-text-primary transition-colors text-xs font-medium uppercase tracking-wide w-full\"\n title={tr('sessionDetails.expandTerminal', 'Expand terminal')}\n >\n <Terminal className=\"size-3.5\" />\n {tr('sessionDetails.terminal', 'Terminal')} - {session.name}\n <ChevronUp className=\"size-3 ml-1\" />\n </button>\n </div>\n )}\n\n <ConfirmDialog\n isOpen={showQuotaExhausted}\n onClose={() => setShowQuotaExhausted(false)}\n onConfirm={() => {\n setShowQuotaExhausted(false);\n navigate('/billing');\n }}\n title={getQuotaDialogContent('session').title}\n message={getQuotaDialogContent('session').message}\n confirmLabel=\"View Billing\"\n cancelLabel=\"Dismiss\"\n variant=\"warning\"\n />\n\n {/* Restart-terminal kills tmux and re-runs the command, so\n confirm before firing. */}\n <ConfirmDialog\n isOpen={showRestartTerminalConfirm}\n onClose={() => setShowRestartTerminalConfirm(false)}\n onConfirm={() => {\n setShowRestartTerminalConfirm(false);\n void handleRestartTerminal();\n }}\n title={tr('sessionDetails.restartTerminalConfirmTitle', 'Restart terminal?')}\n message={tr(\n 'sessionDetails.restartTerminalConfirmBody',\n 'This kills the current tmux session and re-runs the command from scratch. Any in-progress output will be lost.'\n )}\n confirmLabel={tr('sessionDetails.restartTerminalConfirmLabel', 'Restart terminal')}\n cancelLabel={tr('common.cancel', 'Cancel')}\n variant=\"warning\"\n />\n </MotionPage>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"SessionDetailsPage.js","names":[],"sources":["../../src/pages/SessionDetailsPage.tsx"],"sourcesContent":["import { useState, useCallback, useEffect, useRef } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { VIBE_PERMISSIONS } from '../constants/permissions';\nimport { EntityTagPicker } from '@/components/shared/EntityTagPicker';\nimport { PageSkeleton } from '@/components/shared/PageSkeleton';\nimport { useParams } from 'react-router-dom';\nimport { useVibeControlsStore } from '@/store/vibeControlsStore';\nimport {\n ArrowLeft,\n Terminal,\n Play,\n Square,\n RotateCcw,\n ExternalLink,\n Server,\n Zap,\n Clock,\n Folder,\n Code,\n Variable,\n Hash,\n RefreshCw,\n Loader2,\n AlertCircle,\n Maximize2,\n Minimize2,\n ChevronDown,\n ChevronUp,\n Pencil,\n Check,\n X,\n Tag,\n} from 'lucide-react';\nimport {\n PageHeader,\n Card,\n StatusBadge,\n EmptyState,\n ErrorState,\n ConfirmDialog,\n} from '@/components/shared';\nimport { MotionPage, MotionList, MotionItem } from '@burdenoff/fe-libs/shared/motion';\nimport { useVibeControls, useVibeNavigate } from '@/providers/VibeControlsProvider';\nimport {\n useGetSessionQuery,\n useStartSessionMutation,\n useStopSessionMutation,\n useRestartSessionMutation,\n useStartSessionTerminalMutation,\n useRestartSessionTerminalMutation,\n useUpdateSessionMutation,\n} from '@/generated/wspace-operations';\nimport {\n getQuotaDialogContent,\n markQuotaHandledLocally,\n isQuotaExhaustedError,\n} from '@/utils/quotaUtils';\n\n/**\n * Resolves the terminal URL for a session.\n *\n * The agent's auth middleware accepts `?apiKey=` on `/terminal/*` (browser\n * embeds can't set custom headers on an iframe), so we append it from the\n * session's source agent. Without this, ttyd returns 401 inside the iframe.\n *\n * KNOWN SECURITY FINDING (BOFF-2636 follow-up): embedding the long-lived\n * `agentApiKey` in the URL leaks it to browser history, the Referer header,\n * and proxy logs. The svc already exposes a `terminalAccessUrl` field that\n * mints a short-lived HMAC `?vt=<token>`, but ttyd's WebSocket upgrade in\n * a cross-site iframe doesn't currently propagate that auth (Chrome CHIPS\n * / third-party cookie partitioning). Pinned to the apiKey path until the\n * WS layer is fixed — see Linear ticket for the design.\n */\nfunction getTerminalUrl(session: {\n ttydUrl?: string | null;\n agent?: { agentApiKey?: string | null } | null;\n}): string | null {\n const url = session.ttydUrl;\n if (!url) return null;\n const apiKey = session.agent?.agentApiKey;\n if (!apiKey) return url;\n try {\n const u = new URL(url);\n if (!u.searchParams.has('apiKey')) {\n u.searchParams.set('apiKey', apiKey);\n }\n return u.toString();\n } catch {\n return url;\n }\n}\n\n// Escape regex meta-chars for use inside `new RegExp(...)`. Pure,\n// no closure state, so hoisted to module scope to avoid re-creating\n// the function on every `tr()` invocation.\nconst escapeRe = (s: string) => s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\n/**\n * Session details page - shows comprehensive information about a single session\n * with an integrated terminal panel.\n */\nexport function SessionDetailsPage() {\n const { t } = useI18n();\n // tr(key, fallback, params?) — forwards params to useI18n().t so\n // `{{name}}` placeholders are interpolated. Also interpolates into\n // the fallback when the i18n key isn't in the catalog yet, so a\n // missing key doesn't render literal `{{name}}` text to users.\n const tr = useCallback(\n (key: string, fallback: string, params?: Record<string, string | number>): string => {\n const value = t(key, params);\n if (value !== key) return value;\n if (!params) return fallback;\n // Mirror the i18n layer's `{{key}}` placeholder syntax. Replacer\n // function (not a string) so values containing `$$`/`$&` aren't\n // interpreted as `String.prototype.replace` specials.\n return Object.entries(params).reduce(\n (acc, [k, v]) =>\n acc.replace(new RegExp(`\\\\{\\\\{${escapeRe(k)}\\\\}\\\\}`, 'g'), () => String(v)),\n fallback\n );\n },\n [t]\n );\n const { id } = useParams<{ id: string }>();\n const { basePath: basePathProp } = useVibeControls();\n const basePath = basePathProp || '';\n const navigate = useVibeNavigate();\n const { hasPermission, isLoading: permissionsLoading, permissions } = usePermissions();\n\n const [startSession] = useStartSessionMutation({ refetchQueries: ['GetSession'] });\n const [stopSession] = useStopSessionMutation({ refetchQueries: ['GetSession'] });\n const [restartSession] = useRestartSessionMutation({ refetchQueries: ['GetSession'] });\n const [startSessionTerminal] = useStartSessionTerminalMutation({\n refetchQueries: ['GetSession'],\n });\n const [restartSessionTerminal] = useRestartSessionTerminalMutation({\n refetchQueries: ['GetSession'],\n });\n const [updateSession] = useUpdateSessionMutation({ refetchQueries: ['GetSession'] });\n\n // Command/working-directory inline edit state\n const [editingCommand, setEditingCommand] = useState(false);\n const [editCommand, setEditCommand] = useState('');\n const [editWorkingDirectory, setEditWorkingDirectory] = useState('');\n const [editSaving, setEditSaving] = useState(false);\n\n const handleEditCommandOpen = () => {\n setEditCommand(session?.command || '');\n setEditWorkingDirectory(session?.workingDirectory || '');\n setEditingCommand(true);\n };\n\n const handleEditCommandSave = async () => {\n if (!session?.id) return;\n setEditSaving(true);\n try {\n await updateSession({\n variables: {\n id: session.id,\n input: {\n command: editCommand || null,\n workingDirectory: editWorkingDirectory || null,\n },\n },\n });\n setEditingCommand(false);\n } finally {\n setEditSaving(false);\n }\n };\n\n // Fetch session by ID from GraphQL API\n const { data, loading, error, refetch } = useGetSessionQuery({\n variables: { id: id || '' },\n skip: !id,\n fetchPolicy: 'cache-and-network',\n });\n\n // Get session from response\n const session = data?.session;\n\n // Get related agent and vibe from the session response\n const agent = session?.agent || null;\n const vibe = session?.vibe || null;\n\n // ── Sync active context ────────────────────────────────────────────────\n useEffect(() => {\n if (session) {\n useVibeControlsStore.setState({\n activeAgentId: (agent?.id as string) ?? null,\n selectedAgentId: (agent?.id as string) ?? null,\n activeVibeId: (vibe?.id as string) ?? null,\n selectedVibeId: (vibe?.id as string) ?? null,\n activeSessionId: session.id,\n selectedSessionId: session.id,\n });\n }\n }, [session?.id, agent?.id, vibe?.id]); // eslint-disable-line react-hooks/exhaustive-deps\n\n // Terminal state\n const [terminalOpen, setTerminalOpen] = useState(true);\n const [terminalMaximized, setTerminalMaximized] = useState(false);\n const [terminalLoading, setTerminalLoading] = useState(false);\n const [terminalError, setTerminalError] = useState<string | null>(null);\n const [showQuotaExhausted, setShowQuotaExhausted] = useState(false);\n const [showRestartTerminalConfirm, setShowRestartTerminalConfirm] = useState(false);\n const [exitedNotice, setExitedNotice] = useState<{\n exitCode: number | null;\n } | null>(null);\n const prevStatusRef = useRef<string | null>(null);\n\n // Ref target for scrollIntoView when the terminal opens.\n const terminalPanelRef = useRef<HTMLDivElement | null>(null);\n\n // Track first-render + the previous open state so the toggle-driven\n // scroll only fires on a deliberate closed→open transition, not on\n // initial mount (terminalOpen defaults to true).\n const didInitialMountRef = useRef(false);\n const prevTerminalOpenRef = useRef(false);\n\n const terminalUrl = session ? getTerminalUrl(session) : null;\n\n const handleBack = () => {\n navigate('/sessions');\n };\n\n const handleStartSession = async () => {\n if (!session?.id) return;\n try {\n await startSession({ 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 start session:', err);\n }\n };\n\n const handleStopSession = async () => {\n if (!session?.id) return;\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 () => {\n if (!session?.id) return;\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 // Scroll the terminal panel into view as a fire-and-forget op,\n // gated on the panel ref being mounted. Owned by the handler that\n // triggered the action, not by an effect — so a successful\n // start/restart always scrolls exactly once, even when the URL\n // didn't change (no useEffect re-run otherwise).\n const scrollTerminalIntoView = useCallback(() => {\n requestAnimationFrame(() => {\n terminalPanelRef.current?.scrollIntoView({ behavior: 'smooth', block: 'end' });\n });\n }, []);\n\n // Shared bookkeeping for start/restart-terminal: identical try/catch\n // /finally shape, identical quota-exhausted + generic-error handling;\n // only the mutation and the fallback error string differ. Takes the\n // session id as an explicit arg so callers don't need a non-null\n // assertion against `session` — the helper owns the null guard.\n const withTerminalAction = useCallback(\n async <T,>(\n action: (id: string) => Promise<{\n error?: { message: string } | null | undefined;\n data?: T | null | undefined;\n }>,\n dataKey: keyof T,\n fallbackErrorMsg: string\n ) => {\n const id = session?.id;\n if (!id) return;\n setTerminalLoading(true);\n setTerminalError(null);\n let succeeded = false;\n try {\n const result = await action(id);\n if (result.error && isQuotaExhaustedError(result.error)) {\n markQuotaHandledLocally();\n setShowQuotaExhausted(true);\n } else if (result.error) {\n setTerminalError(result.error.message);\n } else if (!result.data || !result.data[dataKey]) {\n setTerminalError(fallbackErrorMsg);\n } else {\n setTerminalOpen(true);\n succeeded = true;\n }\n } catch (err) {\n if (isQuotaExhaustedError(err)) {\n markQuotaHandledLocally();\n setShowQuotaExhausted(true);\n return;\n }\n const message = err instanceof Error ? err.message : fallbackErrorMsg;\n setTerminalError(message);\n } finally {\n setTerminalLoading(false);\n if (succeeded) scrollTerminalIntoView();\n }\n },\n [session?.id, scrollTerminalIntoView]\n );\n\n const handleStartTerminal = useCallback(\n () =>\n withTerminalAction(\n (id) => startSessionTerminal({ variables: { id } }),\n 'startSessionTerminal',\n 'Failed to start terminal'\n ),\n [startSessionTerminal, withTerminalAction]\n );\n\n const handleRestartTerminal = useCallback(\n () =>\n withTerminalAction(\n (id) => restartSessionTerminal({ variables: { id } }),\n 'restartSessionTerminal',\n 'Failed to restart terminal'\n ),\n [restartSessionTerminal, withTerminalAction]\n );\n\n // Scroll on the toggle button — closed→open. User-initiated start/\n // restart paths scroll inline from their handlers (see\n // `scrollTerminalIntoView`), which avoids the stale-flag problem\n // where a no-op success leaves a ref stuck true and a later\n // background refetch fires an unexpected scroll.\n useEffect(() => {\n if (!didInitialMountRef.current) {\n didInitialMountRef.current = true;\n prevTerminalOpenRef.current = terminalOpen;\n return;\n }\n const justOpened = terminalOpen && !prevTerminalOpenRef.current;\n prevTerminalOpenRef.current = terminalOpen;\n if (justOpened) scrollTerminalIntoView();\n }, [terminalOpen, scrollTerminalIntoView]);\n\n // Track the last session whose status we processed, so a route\n // change to a sibling /sessions/:id reseeds `prevStatusRef` with\n // the new session's *current* status — not `null`. Reseeding to\n // null was the bug: if the next status update happened to equal\n // the seed, the transition guard (prev === 'RUNNING'/'STARTING')\n // never matched and the command-exited notice never fired.\n const sessionId = session?.id ?? null;\n const lastProcessedSessionRef = useRef<string | null>(null);\n\n // Surface \"command exited\" inline when the session's status flips\n // to STOPPED while the terminal panel is still open — otherwise\n // the empty iframe looks like a crash.\n useEffect(() => {\n const status = session?.status ?? null;\n const exitCode = session?.exitCode ?? null;\n\n // Session changed: reseed prev to the new session's current\n // status and clear any stale notice. Don't fire a transition on\n // this first observation — we have no \"before\" state yet.\n if (sessionId !== lastProcessedSessionRef.current) {\n lastProcessedSessionRef.current = sessionId;\n prevStatusRef.current = status;\n setExitedNotice(null);\n return;\n }\n\n if (status === 'RUNNING' || status === 'STARTING') {\n // Clear stale notice once the session is running again, then\n // record the status. Mutually exclusive with the STOPPED paths\n // below, so handle it with an early return.\n setExitedNotice(null);\n prevStatusRef.current = status;\n return;\n }\n\n const prev = prevStatusRef.current;\n if (status === 'STOPPED' && (prev === 'RUNNING' || prev === 'STARTING') && terminalOpen) {\n setExitedNotice({ exitCode });\n } else if (status === 'STOPPED' && terminalOpen && exitCode !== null) {\n // exitCode can land in a later GraphQL response than the\n // status transition (poll N returns STOPPED + null code, poll\n // N+1 returns the real code). Patch the existing notice so\n // the banner shows the real code rather than \"(exit null)\".\n setExitedNotice((cur) => (cur && cur.exitCode === null ? { exitCode } : cur));\n }\n prevStatusRef.current = status;\n }, [sessionId, session?.status, session?.exitCode, terminalOpen]);\n\n const handleOpenInNewTab = useCallback(() => {\n if (terminalUrl) {\n window.open(terminalUrl, '_blank');\n }\n }, [terminalUrl]);\n\n if (permissionsLoading)\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <p className=\"text-text-secondary\">\n {tr('sessionDetails.loadingPermissions', 'Loading permissions...')}\n </p>\n </div>\n );\n if (\n permissions &&\n (permissions.permissionStrings?.length ?? 0) > 0 &&\n !hasPermission(VIBE_PERMISSIONS.SESSION_READ)\n )\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <div className=\"text-center space-y-2\">\n <h2 className=\"text-lg font-semibold text-foreground\">\n {tr('sessionDetails.accessDenied', 'Access Denied')}\n </h2>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'sessionDetails.accessDeniedDesc',\n \"You don't have permission to access this page.\"\n )}\n </p>\n </div>\n </div>\n );\n\n const handleAgentClick = () => {\n if (!agent) return;\n navigate(`/agents/${agent.id}`);\n };\n\n const handleVibeClick = () => {\n if (!vibe) return;\n navigate(`/vibes/${vibe.id}`);\n };\n\n const formatTimestamp = (timestamp: string | null | undefined) => {\n if (!timestamp) return 'Never';\n const diff = Date.now() - new Date(timestamp).getTime();\n if (diff < 60000) return 'Just now';\n if (diff < 3600000) return `${Math.floor(diff / 60000)}m ago`;\n if (diff < 86400000) return `${Math.floor(diff / 3600000)}h ago`;\n return new Date(timestamp).toLocaleDateString();\n };\n\n // Guard states wrapped in MotionPage to prevent unmount/remount animation replay\n if (loading && !session) {\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"flex items-center gap-2 text-text-secondary hover:text-text-primary transition-colors\"\n >\n <ArrowLeft className=\"size-4\" />\n {tr('sessionDetails.backToSessions', 'Back to Sessions')}\n </button>\n <PageSkeleton variant=\"detail\" />\n </MotionPage>\n );\n }\n\n if (error && !session) {\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"flex items-center gap-2 text-text-secondary hover:text-text-primary transition-colors\"\n >\n <ArrowLeft className=\"size-4\" />\n Back to Sessions\n </button>\n <ErrorState\n title={tr('sessionDetails.failedLoad', 'Failed to load session')}\n message={error.message}\n onRetry={() => refetch()}\n />\n </MotionPage>\n );\n }\n\n if (!session) {\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"flex items-center gap-2 text-text-secondary hover:text-text-primary transition-colors\"\n >\n <ArrowLeft className=\"size-4\" />\n Back to Sessions\n </button>\n <EmptyState\n icon={<Terminal className=\"size-8 text-text-secondary\" />}\n title={tr('sessionDetails.notFound', 'Session not found')}\n description={`${tr('sessionDetails.notFoundDescPrefix', 'No session with ID')} \"${id}\" ${tr('sessionDetails.notFoundDescSuffix', 'exists')}`}\n action={\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors\"\n >\n {tr('sessionDetails.backToSessions', 'Back to Sessions')}\n </button>\n }\n />\n </MotionPage>\n );\n }\n\n const isRunning = session.status === 'RUNNING';\n const terminalHeight = terminalMaximized ? 'h-[calc(100vh-200px)]' : 'h-[280px] sm:h-[420px]';\n\n return (\n <MotionPage className=\"flex flex-col bg-bg-surface min-h-screen\">\n {/* Main content - scrollable */}\n <div\n className={`flex-1 overflow-y-auto space-y-6 p-4 sm:p-6 ${terminalOpen && terminalUrl ? 'pb-2' : ''}`}\n >\n {/* Back Button */}\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"flex items-center gap-2 text-text-secondary hover:text-text-primary transition-colors\"\n >\n <ArrowLeft className=\"size-4\" />\n Back to Sessions\n </button>\n\n {/* Header */}\n <PageHeader\n title={session.name}\n description={session.command || 'No command specified'}\n breadcrumb={[\n { label: 'Sessions', path: basePath ? `${basePath}/sessions` : '/sessions' },\n { label: session.name },\n ]}\n actions={\n <div className=\"flex items-center gap-2 flex-wrap\">\n <button\n type=\"button\"\n onClick={() => refetch()}\n disabled={loading}\n className=\"flex items-center gap-2 px-3 py-2 bg-bg-sunken text-text-primary rounded-md hover:bg-bg-sunken/80 transition-colors disabled:opacity-50\"\n title=\"Refresh\"\n >\n <RefreshCw className={`size-4 ${loading ? 'animate-spin' : ''}`} />\n </button>\n {isRunning ? (\n <>\n <button\n type=\"button\"\n onClick={handleStopSession}\n className=\"flex items-center gap-2 px-3 py-2 bg-bg-sunken text-text-primary rounded-md hover:bg-bg-sunken/80 transition-colors\"\n >\n <Square className=\"size-4\" />\n {tr('sessionDetails.stopCommand', 'Stop Command')}\n </button>\n <button\n type=\"button\"\n onClick={handleRestartSession}\n className=\"flex items-center gap-2 px-3 py-2 bg-bg-sunken text-text-primary rounded-md hover:bg-bg-sunken/80 transition-colors\"\n >\n <RotateCcw className=\"size-4\" />\n {tr('sessionDetails.restartCommand', 'Restart Command')}\n </button>\n </>\n ) : (\n <button\n type=\"button\"\n onClick={handleStartSession}\n className=\"flex items-center gap-2 px-3 py-2 bg-status-success-bg text-status-success-text rounded-md hover:opacity-80 transition-colors\"\n >\n <Play className=\"size-4\" />\n {tr('sessionDetails.runCommand', 'Run Command')}\n </button>\n )}\n {/* Terminal toggle + open in new tab */}\n {terminalUrl ? (\n <>\n <button\n type=\"button\"\n onClick={() => setTerminalOpen((prev) => !prev)}\n className={`flex items-center gap-2 px-3 py-2 rounded-md transition-colors ${\n terminalOpen\n ? 'bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg-hover'\n : 'bg-bg-sunken text-text-primary hover:bg-bg-sunken/80'\n }`}\n title={\n terminalOpen\n ? tr('sessionDetails.hideTerminal', 'Hide terminal')\n : tr('sessionDetails.showTerminal', 'Show terminal')\n }\n >\n <Terminal className=\"size-4\" />\n {tr('sessionDetails.terminal', 'Terminal')}\n </button>\n <button\n type=\"button\"\n onClick={() => setShowRestartTerminalConfirm(true)}\n disabled={terminalLoading}\n className=\"flex items-center gap-1.5 p-2 bg-bg-sunken text-text-secondary rounded-md hover:text-text-primary hover:bg-bg-sunken/80 transition-colors disabled:opacity-50\"\n title={tr(\n 'sessionDetails.restartTerminalTitle',\n 'Restart terminal — kills the tmux session and re-runs the command'\n )}\n >\n {terminalLoading ? (\n <Loader2 className=\"size-3.5 animate-spin\" />\n ) : (\n <RotateCcw className=\"size-3.5\" />\n )}\n <span className=\"text-xs\">\n {tr('sessionDetails.restartTerminalShort', 'Restart')}\n </span>\n </button>\n <button\n type=\"button\"\n onClick={handleOpenInNewTab}\n className=\"flex items-center p-2 bg-bg-sunken text-text-secondary rounded-md hover:text-text-primary hover:bg-bg-sunken/80 transition-colors\"\n title={tr('sessionDetails.openInNewTab', 'Open terminal in new tab')}\n >\n <ExternalLink className=\"size-4\" />\n </button>\n </>\n ) : (\n <button\n type=\"button\"\n onClick={handleStartTerminal}\n disabled={terminalLoading || !agent}\n className=\"flex items-center gap-2 px-3 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50\"\n title={\n !agent\n ? tr('sessionDetails.noAgentAssigned', 'No agent assigned')\n : !agent.tunnelUrl\n ? tr(\n 'sessionDetails.agentNoTunnelUrl',\n 'Agent has no tunnel URL configured'\n )\n : tr('sessionDetails.openTerminal', 'Open terminal')\n }\n >\n {terminalLoading ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <Terminal className=\"size-4\" />\n )}\n {tr('sessionDetails.openTerminal', 'Open Terminal')}\n </button>\n )}\n </div>\n }\n />\n\n {/* Status Cards */}\n <MotionList stagger={0.04} className=\"grid grid-cols-2 sm:grid-cols-4 gap-4\">\n <MotionItem>\n <Card className=\"p-4\">\n <div className=\"flex items-center gap-3\">\n <div className=\"size-10 rounded-lg bg-bg-sunken flex items-center justify-center\">\n <Terminal className=\"size-5 text-text-secondary\" />\n </div>\n <div>\n <p className=\"text-sm text-text-secondary\">\n {tr('sessionDetails.statusLabel', 'Status')}\n </p>\n <StatusBadge status={session.status} size=\"md\" />\n </div>\n </div>\n </Card>\n </MotionItem>\n <MotionItem>\n <Card className=\"p-4\">\n <div className=\"flex items-center gap-3\">\n <div className=\"size-10 rounded-lg bg-bg-sunken flex items-center justify-center\">\n <Code className=\"size-5 text-text-secondary\" />\n </div>\n <div>\n <p className=\"text-sm text-text-secondary\">\n {tr('sessionDetails.typeLabel', 'Type')}\n </p>\n <p className=\"font-semibold text-text-primary\">{session.type}</p>\n </div>\n </div>\n </Card>\n </MotionItem>\n <MotionItem>\n <Card className=\"p-4\">\n <div className=\"flex items-center gap-3\">\n <div className=\"size-10 rounded-lg bg-bg-sunken flex items-center justify-center\">\n <Hash className=\"size-5 text-text-secondary\" />\n </div>\n <div>\n <p className=\"text-sm text-text-secondary\">\n {tr('sessionDetails.pidLabel', 'PID')}\n </p>\n <p className=\"font-semibold text-text-primary\">{session.pid || 'N/A'}</p>\n </div>\n </div>\n </Card>\n </MotionItem>\n <MotionItem>\n <Card className=\"p-4\">\n <div className=\"flex items-center gap-3\">\n <div className=\"size-10 rounded-lg bg-bg-sunken flex items-center justify-center\">\n <Server className=\"size-5 text-text-secondary\" />\n </div>\n <div>\n <p className=\"text-sm text-text-secondary\">\n {tr('sessionDetails.agentLabel', 'Agent')}\n </p>\n <p className=\"font-semibold text-text-primary truncate\">\n {agent?.name || 'Unknown'}\n </p>\n </div>\n </div>\n </Card>\n </MotionItem>\n </MotionList>\n\n {/* Command */}\n <Card>\n <div className=\"p-4 border-b border-border-default flex items-center justify-between\">\n <h3 className=\"font-semibold text-text-primary flex items-center gap-2\">\n <Code className=\"size-5\" />\n {tr('sessionDetails.commandTitle', 'Command')}\n </h3>\n {!editingCommand && (\n <button\n type=\"button\"\n onClick={handleEditCommandOpen}\n className=\"p-1.5 rounded text-text-secondary hover:text-text-primary hover:bg-bg-sunken transition-colors\"\n title={tr('sessionDetails.editCommandTitle', 'Edit command and working directory')}\n >\n <Pencil className=\"size-4\" />\n </button>\n )}\n </div>\n <div className=\"p-4\">\n {editingCommand ? (\n <div className=\"space-y-3\">\n <div>\n <label className=\"block text-xs font-medium text-text-secondary mb-1\">\n {tr('sessionDetails.commandLabel', 'Command')}\n </label>\n <input\n aria-label=\"control\"\n type=\"text\"\n value={editCommand}\n onChange={(e) => setEditCommand(e.target.value)}\n placeholder={tr('sessionDetails.commandPlaceholder', 'bun run dev')}\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 focus:outline-none focus:ring-2 focus:ring-action-primary-bg font-mono\"\n />\n </div>\n <div>\n <label className=\"block text-xs font-medium text-text-secondary mb-1\">\n {tr('sessionDetails.workingDirLabel', 'Working Directory')}\n </label>\n <input\n aria-label=\"control\"\n type=\"text\"\n value={editWorkingDirectory}\n onChange={(e) => setEditWorkingDirectory(e.target.value)}\n placeholder={tr('sessionDetails.workingDirPlaceholder', '/home/user/myproject')}\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 focus:outline-none focus:ring-2 focus:ring-action-primary-bg font-mono\"\n />\n </div>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={handleEditCommandSave}\n disabled={editSaving}\n className=\"flex items-center gap-1.5 px-3 py-1.5 text-sm bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50\"\n >\n {editSaving ? (\n <Loader2 className=\"size-3.5 animate-spin\" />\n ) : (\n <Check className=\"size-3.5\" />\n )}\n {tr('sessionDetails.save', 'Save')}\n </button>\n <button\n type=\"button\"\n onClick={() => setEditingCommand(false)}\n disabled={editSaving}\n className=\"flex items-center gap-1.5 px-3 py-1.5 text-sm bg-bg-sunken text-text-primary rounded-md hover:bg-bg-elevated transition-colors disabled:opacity-50\"\n >\n <X className=\"size-3.5\" />\n {tr('sessionDetails.cancel', 'Cancel')}\n </button>\n </div>\n </div>\n ) : (\n <>\n <div className=\"bg-bg-sunken rounded-md p-3 font-mono text-sm text-text-primary overflow-x-auto\">\n ${' '}\n {session.command ||\n tr('sessionDetails.noCommandSpecified', 'No command specified')}\n </div>\n <div className=\"mt-3 flex items-center gap-2 text-sm text-text-secondary\">\n <Folder className=\"size-4\" />\n {tr('sessionDetails.workingDirectory', 'Working Directory')}:{' '}\n <span className=\"text-text-primary font-mono\">\n {session.workingDirectory || (\n <em className=\"not-italic text-text-placeholder\">\n {tr('sessionDetails.workingDirDefault', 'not set — defaults to OS home')}\n </em>\n )}\n </span>\n </div>\n </>\n )}\n </div>\n </Card>\n\n {/* Environment Variables */}\n <Card>\n <div className=\"p-4 border-b border-border-default\">\n <h3 className=\"font-semibold text-text-primary flex items-center gap-2\">\n <Variable className=\"size-5\" />\n {tr('sessionDetails.envVars', 'Environment Variables')}\n </h3>\n </div>\n <div className=\"p-4\">\n {session.environmentVariables &&\n Object.keys(session.environmentVariables).length > 0 ? (\n <div className=\"flex flex-wrap gap-2\">\n {Object.entries(session.environmentVariables).map(([key, value]) => (\n <span\n key={key}\n className=\"px-3 py-1 bg-bg-sunken text-text-primary text-sm rounded-full font-mono\"\n >\n {key}={String(value)}\n </span>\n ))}\n </div>\n ) : (\n <p className=\"text-text-secondary\">\n {tr('sessionDetails.noEnvVars', 'No environment variables configured')}\n </p>\n )}\n </div>\n </Card>\n\n {/* Last Output */}\n <Card>\n <div className=\"p-4 border-b border-border-default\">\n <h3 className=\"font-semibold text-text-primary flex items-center gap-2\">\n <Terminal className=\"size-5\" />\n {tr('sessionDetails.lastOutput', 'Last Output')}\n </h3>\n </div>\n <div className=\"p-4\">\n {session.lastOutput ? (\n <div className=\"bg-bg-sunken rounded-md p-3 font-mono text-sm text-text-primary overflow-x-auto whitespace-pre-wrap\">\n {session.lastOutput}\n </div>\n ) : (\n <p className=\"text-text-secondary\">\n {tr('sessionDetails.noOutput', 'No output captured')}\n </p>\n )}\n </div>\n </Card>\n\n {/* Related Vibe */}\n {vibe && (\n <Card onClick={handleVibeClick} hoverable>\n <div className=\"p-4 flex items-center justify-between\">\n <div className=\"flex items-center gap-3\">\n <div className=\"size-10 rounded-lg bg-bg-sunken flex items-center justify-center\">\n <Zap className=\"size-5 text-text-secondary\" />\n </div>\n <div>\n <p className=\"text-sm text-text-secondary\">\n {tr('sessionDetails.relatedVibe', 'Related Vibe')}\n </p>\n <p className=\"font-medium text-text-primary\">{vibe.name}</p>\n {vibe.path && (\n <p className=\"text-sm text-text-secondary truncate max-w-md\">{vibe.path}</p>\n )}\n </div>\n </div>\n <StatusBadge status={vibe.status} />\n </div>\n </Card>\n )}\n\n {/* Related Agent */}\n {agent && (\n <Card onClick={handleAgentClick} hoverable>\n <div className=\"p-4 flex items-center justify-between\">\n <div className=\"flex items-center gap-3\">\n <div className=\"size-10 rounded-lg bg-bg-sunken flex items-center justify-center\">\n <Server className=\"size-5 text-text-secondary\" />\n </div>\n <div>\n <p className=\"text-sm text-text-secondary\">\n {tr('sessionDetails.runningOnAgent', 'Running on Agent')}\n </p>\n <p className=\"font-medium text-text-primary\">{agent.name}</p>\n <p className=\"text-sm text-text-secondary\">{agent.hostname}</p>\n </div>\n </div>\n <StatusBadge status={agent.isActive ? 'ACTIVE' : 'OFFLINE'} />\n </div>\n </Card>\n )}\n\n {/* Metadata */}\n <Card>\n <div className=\"p-4 border-b border-border-default\">\n <h3 className=\"font-semibold text-text-primary flex items-center gap-2\">\n <Clock className=\"size-5\" />\n {tr('sessionDetails.timeline', 'Timeline')}\n </h3>\n </div>\n <div className=\"p-4\">\n <div className=\"grid grid-cols-1 sm:grid-cols-3 gap-4 text-sm\">\n <div>\n <p className=\"text-text-secondary\">\n {tr('sessionDetails.autoStart', 'Auto Start')}\n </p>\n <p className=\"font-medium text-text-primary\">\n {session.autoStart\n ? tr('sessionDetails.yes', 'Yes')\n : tr('sessionDetails.no', 'No')}\n </p>\n </div>\n <div>\n <p className=\"text-text-secondary\">{tr('sessionDetails.created', 'Created')}</p>\n <p className=\"font-medium text-text-primary\">\n {formatTimestamp(session.createdAt)}\n </p>\n </div>\n <div>\n <p className=\"text-text-secondary\">{tr('sessionDetails.updated', 'Updated')}</p>\n <p className=\"font-medium text-text-primary\">\n {formatTimestamp(session.updatedAt)}\n </p>\n </div>\n </div>\n {session.exitCode !== null && session.exitCode !== undefined && (\n <div className=\"mt-4 pt-4 border-t border-border-default\">\n <p className=\"text-sm text-text-secondary\">\n {tr('sessionDetails.exitCode', 'Exit Code')}\n </p>\n <p\n className={`font-medium ${session.exitCode === 0 ? 'text-status-success-text' : 'text-status-error-text'}`}\n >\n {session.exitCode}\n </p>\n </div>\n )}\n </div>\n </Card>\n\n {/* Tags */}\n <Card>\n <div className=\"p-4 border-b border-border-default\">\n <h3 className=\"font-semibold text-text-primary flex items-center gap-2\">\n <Tag className=\"size-5\" />\n {tr('sessionDetails.tags', 'Tags')}\n </h3>\n </div>\n <div className=\"p-4\">\n <EntityTagPicker entityType=\"VIBECONTROLS_SESSION\" entityId={session.id} />\n </div>\n </Card>\n </div>\n\n {/* Integrated Terminal Panel - docked at bottom */}\n {terminalOpen && (\n <div\n ref={terminalPanelRef}\n className=\"border-t border-border-default bg-bg-surface shrink-0\"\n >\n {/* Terminal header bar */}\n <div className=\"flex items-center bg-bg-sunken border-b border-border-default\">\n <button\n type=\"button\"\n onClick={() => setTerminalOpen(false)}\n className=\"flex items-center gap-1.5 px-3 py-2 text-text-secondary hover:text-text-primary transition-colors text-xs font-medium uppercase tracking-wide\"\n title={tr('sessionDetails.collapseTerminal', 'Collapse terminal')}\n >\n <Terminal className=\"size-3.5\" />\n {tr('sessionDetails.terminal', 'Terminal')}\n <ChevronDown className=\"size-3\" />\n </button>\n <div className=\"flex items-center px-3 py-1.5 text-xs bg-bg-surface text-text-primary border-r border-border-subtle border-b-2 border-b-action-primary-bg\">\n <Terminal className=\"size-3 mr-1.5\" />\n {session.name}\n </div>\n <div className=\"flex-1\" />\n <div className=\"flex items-center gap-1 px-2\">\n {terminalUrl && (\n <button\n type=\"button\"\n onClick={handleOpenInNewTab}\n className=\"p-1 text-text-secondary hover:text-text-primary transition-colors\"\n title={tr('sessionDetails.openInNewTabShort', 'Open in new tab')}\n >\n <ExternalLink className=\"size-3.5\" />\n </button>\n )}\n <button\n type=\"button\"\n onClick={() => setTerminalMaximized((prev) => !prev)}\n className=\"p-1 text-text-secondary hover:text-text-primary transition-colors\"\n title={\n terminalMaximized\n ? tr('sessionDetails.restoreSize', 'Restore size')\n : tr('sessionDetails.maximize', 'Maximize')\n }\n >\n {terminalMaximized ? (\n <Minimize2 className=\"size-3.5\" />\n ) : (\n <Maximize2 className=\"size-3.5\" />\n )}\n </button>\n </div>\n </div>\n\n {exitedNotice && (\n <div\n role=\"status\"\n className=\"flex items-start justify-between gap-3 px-3 py-2 bg-status-warning-bg/30 text-status-warning-text border-b border-status-warning-bg/50 text-xs\"\n >\n <div className=\"flex items-center gap-2 flex-1\">\n <AlertCircle className=\"size-3.5 shrink-0\" />\n <span>\n {tr(\n 'sessionDetails.commandExited',\n 'Command exited{{code}} — click Run Command to run it again.',\n {\n code:\n exitedNotice.exitCode !== null ? ` (exit ${exitedNotice.exitCode})` : '',\n }\n )}\n </span>\n </div>\n <button\n type=\"button\"\n onClick={() => setExitedNotice(null)}\n className=\"text-text-secondary hover:text-text-primary\"\n title={tr('common.dismiss', 'Dismiss')}\n >\n <X className=\"size-3.5\" />\n </button>\n </div>\n )}\n\n {/* Terminal content */}\n <div className={`${terminalHeight} bg-bg-inverse text-text-inverse`}>\n {terminalLoading ? (\n <div className=\"flex items-center justify-center h-full\">\n <div className=\"text-center\">\n <Loader2 className=\"size-8 text-text-secondary animate-spin mx-auto mb-3\" />\n <p className=\"text-text-secondary text-sm\">\n {tr('sessionDetails.startingTerminal', 'Starting terminal for')} {session.name}\n ...\n </p>\n <p className=\"text-text-tertiary text-xs mt-1\">\n {tr(\n 'sessionDetails.settingUpTunnel',\n 'Setting up tmux + ttyd + cloudflared tunnel'\n )}\n </p>\n </div>\n </div>\n ) : terminalError ? (\n <div className=\"flex items-center justify-center h-full\">\n <div className=\"text-center max-w-md\">\n <AlertCircle className=\"size-8 text-status-error-text mx-auto mb-3\" />\n <p className=\"text-status-error-text text-sm font-medium\">\n {tr('sessionDetails.failedStartTerminal', 'Failed to start terminal')}\n </p>\n <p className=\"text-text-secondary text-xs mt-2\">{terminalError}</p>\n <button\n type=\"button\"\n onClick={handleStartTerminal}\n className=\"mt-4 px-4 py-2 bg-bg-muted text-text-primary text-sm rounded-md hover:bg-bg-sunken transition-colors\"\n >\n {tr('sessionDetails.retry', 'Retry')}\n </button>\n </div>\n </div>\n ) : terminalUrl ? (\n <iframe\n src={terminalUrl}\n title={`Terminal - ${session.name}`}\n className=\"w-full h-full border-0\"\n allow=\"clipboard-read; clipboard-write\"\n />\n ) : (\n <div className=\"flex items-center justify-center h-full\">\n <div className=\"text-center\">\n <Terminal className=\"size-8 text-text-tertiary mx-auto mb-3\" />\n <p className=\"text-text-secondary text-sm\">\n {tr('sessionDetails.noTerminalRunning', 'No terminal running')}\n </p>\n <p className=\"text-text-tertiary text-xs mt-1\">\n {agent\n ? tr(\n 'sessionDetails.clickOpenTerminalHint',\n 'Click \"Open Terminal\" to create a tmux session with a browser terminal.'\n )\n : tr(\n 'sessionDetails.noAgentForSession',\n 'No agent is assigned to this session.'\n )}\n </p>\n {agent && (\n <button\n type=\"button\"\n onClick={handleStartTerminal}\n disabled={terminalLoading}\n className=\"mt-4 px-4 py-2 bg-bg-muted text-text-primary text-sm rounded-md hover:bg-bg-sunken transition-colors disabled:opacity-50\"\n >\n {terminalLoading\n ? tr('sessionDetails.opening', 'Opening...')\n : tr('sessionDetails.openTerminal', 'Open Terminal')}\n </button>\n )}\n </div>\n </div>\n )}\n </div>\n </div>\n )}\n\n {/* Terminal collapsed indicator - show a clickable bar */}\n {!terminalOpen && (terminalUrl || terminalError) && (\n <div className=\"border-t border-border-default bg-bg-sunken shrink-0\">\n <button\n type=\"button\"\n onClick={() => setTerminalOpen(true)}\n className=\"flex items-center gap-1.5 px-3 py-1.5 text-text-secondary hover:text-text-primary transition-colors text-xs font-medium uppercase tracking-wide w-full\"\n title={tr('sessionDetails.expandTerminal', 'Expand terminal')}\n >\n <Terminal className=\"size-3.5\" />\n {tr('sessionDetails.terminal', 'Terminal')} - {session.name}\n <ChevronUp className=\"size-3 ml-1\" />\n </button>\n </div>\n )}\n\n <ConfirmDialog\n isOpen={showQuotaExhausted}\n onClose={() => setShowQuotaExhausted(false)}\n onConfirm={() => {\n setShowQuotaExhausted(false);\n navigate('/billing');\n }}\n title={getQuotaDialogContent('session').title}\n message={getQuotaDialogContent('session').message}\n confirmLabel=\"View Billing\"\n cancelLabel=\"Dismiss\"\n variant=\"warning\"\n />\n\n {/* Restart-terminal kills tmux and re-runs the command, so\n confirm before firing. */}\n <ConfirmDialog\n isOpen={showRestartTerminalConfirm}\n onClose={() => setShowRestartTerminalConfirm(false)}\n onConfirm={() => {\n setShowRestartTerminalConfirm(false);\n void handleRestartTerminal();\n }}\n title={tr('sessionDetails.restartTerminalConfirmTitle', 'Restart terminal?')}\n message={tr(\n 'sessionDetails.restartTerminalConfirmBody',\n 'This kills the current tmux session and re-runs the command from scratch. Any in-progress output will be lost.'\n )}\n confirmLabel={tr('sessionDetails.restartTerminalConfirmLabel', 'Restart terminal')}\n cancelLabel={tr('common.cancel', 'Cancel')}\n variant=\"warning\"\n />\n </MotionPage>\n );\n}\n"],"mappings":"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"}