@burdenoff/microfe-vibecontrols 2026.608.2 → 2026.608.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/audit/AuditLogDetail.js +10 -6
- package/dist/components/audit/AuditLogDetail.js.map +1 -1
- package/dist/components/notes/NotesTabPanel.js +117 -111
- package/dist/components/notes/NotesTabPanel.js.map +1 -1
- package/dist/components/sessions/SessionWorkbenchDrawer.js +231 -0
- package/dist/components/sessions/SessionWorkbenchDrawer.js.map +1 -0
- package/dist/components/sessions/TerminalPanel.js +1 -1
- package/dist/components/sessions/TerminalPanel.js.map +1 -1
- package/dist/generated/wspace-operations.js +395 -362
- package/dist/generated/wspace-operations.js.map +1 -1
- package/dist/generated/wspace-types.js.map +1 -1
- package/dist/index.js +10 -10
- package/dist/pages/SessionDetailsPage.js +454 -434
- package/dist/pages/SessionDetailsPage.js.map +1 -1
- package/dist/pages/SessionsPage.js +3 -3
- package/dist/pages/SessionsPage.js.map +1 -1
- package/dist/pages/VibeAuditPage.js +148 -119
- package/dist/pages/VibeAuditPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -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 { ConnectionLogPanel } from '@/components/agents/ConnectionLogPanel';\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 useMintAgentCapabilityMutation,\n useJoinSharedSessionMutation,\n useRequestSessionControlMutation,\n useReleaseSessionControlMutation,\n useCollaboratorJoinedSubscription,\n useCollaboratorLeftSubscription,\n useSessionControlChangedSubscription,\n type SessionCollaboratorFieldsFragment,\n} from '@/generated/wspace-operations';\nimport {\n AgentCapabilityScope,\n CollaboratorRole,\n SessionSharePermission,\n} from '@/generated/wspace-types';\nimport { SharedTerminalPanel } from '@/components/sessions/SharedTerminalPanel';\nimport {\n getQuotaDialogContent,\n markQuotaHandledLocally,\n isQuotaExhaustedError,\n} from '@/utils/quotaUtils';\n\n/**\n * Returns the bare ttyd URL for a session — no embedded credentials.\n *\n * F3 cutover: the long-lived `agentApiKey` is NO LONGER appended to the URL.\n * The browser instead calls `mintAgentCapability(TERMINAL_SESSION)` to mint a\n * short-lived HMAC token, then loads the iframe with `#vt=<token>` as a URL\n * fragment. The agent's `/terminal/<sid>` bootstrap exchanges that fragment\n * token for a session-scoped HttpOnly cookie before proxying to ttyd.\n *\n * Returns null when the session has no ttydUrl yet (terminal not started).\n */\nfunction getTerminalBaseUrl(session: { ttydUrl?: string | null }): string | null {\n const url = session.ttydUrl;\n if (!url) return null;\n // Defensively strip any legacy `?apiKey=` that a stale server build may\n // have persisted to ttydUrl. The agent now rejects `?apiKey` on /terminal\n // paths so leaving it would just look messy; remove it to keep history /\n // Referer clean.\n try {\n const u = new URL(url);\n u.searchParams.delete('apiKey');\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\nfunction 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/**\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, currentUser } = useVibeControls();\n const basePath = basePathProp || '';\n const currentUserId = currentUser?.id ?? '';\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 // ── Generic-share → live-collaboration bridge ──────────────────────────\n // A session shared via the generic <ShareDialog> gives the recipient a\n // ShareGrant + canAccess (the session shows in their list / is GetSession-\n // readable), but NOT a live `SessionCollaborator` — so the collaboration\n // UI (presence, turn-based control) stays inert and the recipient can't\n // safely co-drive the terminal. When the caller opens a session they have\n // access to but do NOT own and aren't yet an active collaborator, join via\n // `joinSharedSession({ sessionId })` (the new sessionId field, authorized\n // server-side by ShareService.canAccess) to establish the collaborator row,\n // then render the shared/live terminal path below. Owners keep the existing\n // solo terminal — we never force a join for them.\n const ownerUserId = session?.context?.userId ?? null;\n // Owner only when we can positively confirm it. If the owner id is unknown\n // we treat the viewer as a potential collaborator rather than forcing a\n // join — but the auto-join below additionally requires a known owner id\n // that differs from the caller, so an unknown owner never triggers a join.\n const isOwner = !!ownerUserId && !!currentUserId && ownerUserId === currentUserId;\n const collaborators: SessionCollaboratorFieldsFragment[] = session?.collaborators ?? [];\n const myCollaborator = collaborators.find((c) => c.userId === currentUserId) ?? null;\n // Live collaboration applies when the caller is a non-owner who has (or is\n // about to have) a collaborator row — i.e. a generic-share recipient. Detect\n // the recipient via `myGrantRole` (a share grant on this session), an existing\n // collaborator row, or a known-and-different owner id. `myGrantRole` is the\n // reliable signal because a recipient cannot read the owner id (`ownerUserId`\n // is null for them) — the previous `!!ownerUserId` requirement never fired for\n // real recipients, so the live-join bridge stayed dormant.\n const isSharedCollaborator =\n !!session && !isOwner && (!!ownerUserId || !!session?.myGrantRole || !!myCollaborator);\n\n const [joinSharedSession] = useJoinSharedSessionMutation();\n const [requestControl, { loading: requestingControl }] = useRequestSessionControlMutation();\n const [releaseControl, { loading: releasingControl }] = useReleaseSessionControlMutation();\n // Guard so the idempotent join fires at most once per session mount.\n const joinAttemptedSessionRef = useRef<string | null>(null);\n\n useEffect(() => {\n const sid = session?.id;\n if (!sid) return;\n // Only bridge non-owners with a confirmed different owner; never join as\n // the owner, and skip if already an active collaborator for this session.\n if (!isSharedCollaborator || myCollaborator) return;\n if (joinAttemptedSessionRef.current === sid) return;\n joinAttemptedSessionRef.current = sid;\n joinSharedSession({ variables: { input: { sessionId: sid } } })\n .then(() => {\n // Refetch so `session.collaborators` includes the freshly-created row\n // (and our role/hasControl), which drives the SharedTerminalPanel.\n void refetch();\n })\n .catch((err) => {\n // Non-fatal: leave the read-only terminal in place. Allow a retry on\n // a later mount if the join transiently failed.\n joinAttemptedSessionRef.current = null;\n console.warn('[sessionDetails] joinSharedSession(sessionId) failed', err);\n });\n }, [session?.id, isSharedCollaborator, myCollaborator, joinSharedSession, refetch]);\n\n // Refetch collaborators when a presence / control event arrives so the\n // SharedTerminalPanel reflects who's connected and who holds control.\n const { data: collabJoinedData } = useCollaboratorJoinedSubscription({\n variables: { sessionId: session?.id ?? '' },\n skip: !session?.id || !isSharedCollaborator,\n });\n const { data: collabLeftData } = useCollaboratorLeftSubscription({\n variables: { sessionId: session?.id ?? '' },\n skip: !session?.id || !isSharedCollaborator,\n });\n const { data: controlChangedData } = useSessionControlChangedSubscription({\n variables: { sessionId: session?.id ?? '' },\n skip: !session?.id || !isSharedCollaborator,\n });\n useEffect(() => {\n if (collabJoinedData || collabLeftData || controlChangedData) {\n void refetch();\n }\n }, [collabJoinedData, collabLeftData, controlChangedData, refetch]);\n\n // Effective permission for the collaboration overlay: EDITOR/OWNER roles map\n // to interactive (turn-based control), VIEWER to view-only.\n const collaboratorPermission =\n myCollaborator?.role === CollaboratorRole.Viewer\n ? SessionSharePermission.View\n : SessionSharePermission.Interact;\n\n const handleRequestControl = useCallback(async () => {\n if (!session?.id) return;\n try {\n await requestControl({ variables: { sessionId: session.id } });\n await refetch();\n } catch (err) {\n console.error('Failed to request control:', err);\n }\n }, [session?.id, requestControl, refetch]);\n\n const handleReleaseControl = useCallback(async () => {\n if (!session?.id) return;\n try {\n await releaseControl({ variables: { sessionId: session.id } });\n await refetch();\n } catch (err) {\n console.error('Failed to release control:', err);\n }\n }, [session?.id, releaseControl, refetch]);\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 // Bare ttyd URL — does NOT include the capability token. The fragment\n // (`#vt=`) is appended below once `mintAgentCapability` resolves.\n const terminalBaseUrl = session ? getTerminalBaseUrl(session) : null;\n\n // F3 — short-lived capability mint. The token is appended to the iframe\n // src as a URL fragment (`#vt=<token>`). Fragments stay client-side, so\n // the token doesn't appear in proxy logs, Referer, or browser history.\n const [mintAgentCapability] = useMintAgentCapabilityMutation();\n const [capabilityToken, setCapabilityToken] = useState<string | null>(null);\n const [capabilityError, setCapabilityError] = useState<string | null>(null);\n const [capabilityLoading, setCapabilityLoading] = useState(false);\n // Re-mint counter — bump to force a new mint (used on iframe error / on\n // session/agent change).\n const [mintNonce, setMintNonce] = useState(0);\n const requestRemint = useCallback(() => setMintNonce((n) => n + 1), []);\n\n const agentIdForMint = agent?.id ?? null;\n const sessionIdForMint = session?.id ?? null;\n\n useEffect(() => {\n let cancelled = false;\n if (!terminalBaseUrl || !agentIdForMint || !sessionIdForMint) {\n setCapabilityToken(null);\n setCapabilityError(null);\n return undefined;\n }\n setCapabilityLoading(true);\n setCapabilityError(null);\n mintAgentCapability({\n variables: {\n agentId: agentIdForMint,\n // Multi-profile is a follow-up — see useActiveAgentProfile. The agent\n // mounts /api/profiles/default/* by default, which is what the F3\n // exchange endpoint resolves to as well.\n profile: 'default',\n scope: AgentCapabilityScope.TerminalSession,\n resourceId: sessionIdForMint,\n ttlSeconds: 60,\n },\n })\n .then((res) => {\n if (cancelled) return;\n const token = res.data?.mintAgentCapability?.token ?? null;\n if (!token) {\n setCapabilityError('Failed to mint terminal capability');\n setCapabilityToken(null);\n } else {\n setCapabilityToken(token);\n }\n })\n .catch((err) => {\n if (cancelled) return;\n setCapabilityError(\n err instanceof Error ? err.message : 'Failed to mint terminal capability'\n );\n setCapabilityToken(null);\n })\n .finally(() => {\n if (!cancelled) setCapabilityLoading(false);\n });\n return () => {\n cancelled = true;\n };\n }, [terminalBaseUrl, agentIdForMint, sessionIdForMint, mintNonce, mintAgentCapability]);\n\n const terminalUrl =\n terminalBaseUrl && capabilityToken\n ? `${terminalBaseUrl}#vt=${encodeURIComponent(capabilityToken)}`\n : 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 // 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\n {/* Connection Log — every probe / start / mint / exchange the\n svc did for this session. Operators export this when\n \"Connect\" fails and paste into a bug report. The\n `#connection-log` anchor is referenced from the TerminalPanel\n disconnected overlay so a failed terminal lands the user at\n the log card directly. Anchor wraps the Card because the\n Card primitive doesn't pass `id` through. */}\n <div id=\"connection-log\" className=\"scroll-mt-20\">\n <Card className=\"min-h-[40vh]\">\n <ConnectionLogPanel sessionId={session.id} sessionName={session.name} />\n </Card>\n </div>\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 session + web terminal 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 // For a generic-share recipient (non-owner collaborator) wrap the\n // terminal in the same SharedTerminalPanel the SharedSessionPage\n // link flow uses — presence + turn-based control + read-only\n // overlay. `isShared={false}` (owner / solo) renders the bare\n // iframe unchanged.\n <SharedTerminalPanel\n isShared={isSharedCollaborator}\n collaborators={collaborators}\n currentUserId={currentUserId}\n permission={collaboratorPermission}\n onRequestControl={handleRequestControl}\n onReleaseControl={handleReleaseControl}\n isRequestingControl={requestingControl}\n isReleasingControl={releasingControl}\n className=\"h-full\"\n >\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 // F3 — the cookie-shim cookie has a 10-minute lifetime. If the\n // iframe errors after that (e.g. user leaves the page idle), the\n // bootstrap exchange will 401 and we need a fresh capability.\n onError={requestRemint}\n />\n </SharedTerminalPanel>\n ) : terminalBaseUrl && (capabilityLoading || capabilityError) ? (\n <div className=\"flex items-center justify-center h-full\">\n <div className=\"text-center\">\n {capabilityError ? (\n <>\n <AlertCircle className=\"size-8 text-status-error mx-auto mb-3\" />\n <p className=\"text-text-secondary text-sm\">\n {tr(\n 'sessionDetails.terminalCapabilityFailed',\n 'Could not prepare terminal access'\n )}\n </p>\n <p className=\"text-text-tertiary text-xs mt-1\">{capabilityError}</p>\n <button\n type=\"button\"\n onClick={requestRemint}\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 </>\n ) : (\n <>\n <Loader2 className=\"size-6 text-text-tertiary mx-auto mb-3 animate-spin\" />\n <p className=\"text-text-secondary text-sm\">\n {tr('sessionDetails.preparingTerminal', 'Preparing terminal…')}\n </p>\n </>\n )}\n </div>\n </div>\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/plans');\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 { ConnectionLogPanel } from '@/components/agents/ConnectionLogPanel';\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 PanelRightOpen,\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 useMintAgentCapabilityMutation,\n useJoinSharedSessionMutation,\n useRequestSessionControlMutation,\n useReleaseSessionControlMutation,\n useCollaboratorJoinedSubscription,\n useCollaboratorLeftSubscription,\n useSessionControlChangedSubscription,\n type SessionCollaboratorFieldsFragment,\n} from '@/generated/wspace-operations';\nimport {\n AgentCapabilityScope,\n CollaboratorRole,\n SessionSharePermission,\n} from '@/generated/wspace-types';\nimport { SharedTerminalPanel } from '@/components/sessions/SharedTerminalPanel';\nimport { SessionWorkbenchDrawer } from '@/components/sessions/SessionWorkbenchDrawer';\nimport {\n getQuotaDialogContent,\n markQuotaHandledLocally,\n isQuotaExhaustedError,\n} from '@/utils/quotaUtils';\n\n/**\n * Returns the bare ttyd URL for a session — no embedded credentials.\n *\n * F3 cutover: the long-lived `agentApiKey` is NO LONGER appended to the URL.\n * The browser instead calls `mintAgentCapability(TERMINAL_SESSION)` to mint a\n * short-lived HMAC token, then loads the iframe with `#vt=<token>` as a URL\n * fragment. The agent's `/terminal/<sid>` bootstrap exchanges that fragment\n * token for a session-scoped HttpOnly cookie before proxying to ttyd.\n *\n * Returns null when the session has no ttydUrl yet (terminal not started).\n */\nfunction getTerminalBaseUrl(session: { ttydUrl?: string | null }): string | null {\n const url = session.ttydUrl;\n if (!url) return null;\n // Defensively strip any legacy `?apiKey=` that a stale server build may\n // have persisted to ttydUrl. The agent now rejects `?apiKey` on /terminal\n // paths so leaving it would just look messy; remove it to keep history /\n // Referer clean.\n try {\n const u = new URL(url);\n u.searchParams.delete('apiKey');\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\nfunction 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/**\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, currentUser } = useVibeControls();\n const basePath = basePathProp || '';\n const currentUserId = currentUser?.id ?? '';\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 // ── Generic-share → live-collaboration bridge ──────────────────────────\n // A session shared via the generic <ShareDialog> gives the recipient a\n // ShareGrant + canAccess (the session shows in their list / is GetSession-\n // readable), but NOT a live `SessionCollaborator` — so the collaboration\n // UI (presence, turn-based control) stays inert and the recipient can't\n // safely co-drive the terminal. When the caller opens a session they have\n // access to but do NOT own and aren't yet an active collaborator, join via\n // `joinSharedSession({ sessionId })` (the new sessionId field, authorized\n // server-side by ShareService.canAccess) to establish the collaborator row,\n // then render the shared/live terminal path below. Owners keep the existing\n // solo terminal — we never force a join for them.\n const ownerUserId = session?.context?.userId ?? null;\n // Owner only when we can positively confirm it. If the owner id is unknown\n // we treat the viewer as a potential collaborator rather than forcing a\n // join — but the auto-join below additionally requires a known owner id\n // that differs from the caller, so an unknown owner never triggers a join.\n const isOwner = !!ownerUserId && !!currentUserId && ownerUserId === currentUserId;\n const collaborators: SessionCollaboratorFieldsFragment[] = session?.collaborators ?? [];\n const myCollaborator = collaborators.find((c) => c.userId === currentUserId) ?? null;\n // Live collaboration applies when the caller is a non-owner who has (or is\n // about to have) a collaborator row — i.e. a generic-share recipient. Detect\n // the recipient via `myGrantRole` (a share grant on this session), an existing\n // collaborator row, or a known-and-different owner id. `myGrantRole` is the\n // reliable signal because a recipient cannot read the owner id (`ownerUserId`\n // is null for them) — the previous `!!ownerUserId` requirement never fired for\n // real recipients, so the live-join bridge stayed dormant.\n const isSharedCollaborator =\n !!session && !isOwner && (!!ownerUserId || !!session?.myGrantRole || !!myCollaborator);\n\n const [joinSharedSession] = useJoinSharedSessionMutation();\n const [requestControl, { loading: requestingControl }] = useRequestSessionControlMutation();\n const [releaseControl, { loading: releasingControl }] = useReleaseSessionControlMutation();\n // Guard so the idempotent join fires at most once per session mount.\n const joinAttemptedSessionRef = useRef<string | null>(null);\n\n useEffect(() => {\n const sid = session?.id;\n if (!sid) return;\n // Only bridge non-owners with a confirmed different owner; never join as\n // the owner, and skip if already an active collaborator for this session.\n if (!isSharedCollaborator || myCollaborator) return;\n if (joinAttemptedSessionRef.current === sid) return;\n joinAttemptedSessionRef.current = sid;\n joinSharedSession({ variables: { input: { sessionId: sid } } })\n .then(() => {\n // Refetch so `session.collaborators` includes the freshly-created row\n // (and our role/hasControl), which drives the SharedTerminalPanel.\n void refetch();\n })\n .catch((err) => {\n // Non-fatal: leave the read-only terminal in place. Allow a retry on\n // a later mount if the join transiently failed.\n joinAttemptedSessionRef.current = null;\n console.warn('[sessionDetails] joinSharedSession(sessionId) failed', err);\n });\n }, [session?.id, isSharedCollaborator, myCollaborator, joinSharedSession, refetch]);\n\n // Refetch collaborators when a presence / control event arrives so the\n // SharedTerminalPanel reflects who's connected and who holds control.\n const { data: collabJoinedData } = useCollaboratorJoinedSubscription({\n variables: { sessionId: session?.id ?? '' },\n skip: !session?.id || !isSharedCollaborator,\n });\n const { data: collabLeftData } = useCollaboratorLeftSubscription({\n variables: { sessionId: session?.id ?? '' },\n skip: !session?.id || !isSharedCollaborator,\n });\n const { data: controlChangedData } = useSessionControlChangedSubscription({\n variables: { sessionId: session?.id ?? '' },\n skip: !session?.id || !isSharedCollaborator,\n });\n useEffect(() => {\n if (collabJoinedData || collabLeftData || controlChangedData) {\n void refetch();\n }\n }, [collabJoinedData, collabLeftData, controlChangedData, refetch]);\n\n // Effective permission for the collaboration overlay: EDITOR/OWNER roles map\n // to interactive (turn-based control), VIEWER to view-only.\n const collaboratorPermission =\n myCollaborator?.role === CollaboratorRole.Viewer\n ? SessionSharePermission.View\n : SessionSharePermission.Interact;\n\n const handleRequestControl = useCallback(async () => {\n if (!session?.id) return;\n try {\n await requestControl({ variables: { sessionId: session.id } });\n await refetch();\n } catch (err) {\n console.error('Failed to request control:', err);\n }\n }, [session?.id, requestControl, refetch]);\n\n const handleReleaseControl = useCallback(async () => {\n if (!session?.id) return;\n try {\n await releaseControl({ variables: { sessionId: session.id } });\n await refetch();\n } catch (err) {\n console.error('Failed to release control:', err);\n }\n }, [session?.id, releaseControl, refetch]);\n\n // Session workbench drawer (notes / prompts / history) — F1 + F2.\n // Toggle with Cmd/Ctrl+J; close with Esc. The button in the header is the\n // primary affordance so it's discoverable without navigating away.\n const [workbenchOpen, setWorkbenchOpen] = useState(false);\n useEffect(() => {\n const onKey = (e: KeyboardEvent) => {\n if ((e.metaKey || e.ctrlKey) && (e.key === 'j' || e.key === 'J')) {\n e.preventDefault();\n setWorkbenchOpen((v) => !v);\n } else if (e.key === 'Escape') {\n setWorkbenchOpen(false);\n }\n };\n window.addEventListener('keydown', onKey);\n return () => window.removeEventListener('keydown', onKey);\n }, []);\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 // Bare ttyd URL — does NOT include the capability token. The fragment\n // (`#vt=`) is appended below once `mintAgentCapability` resolves.\n const terminalBaseUrl = session ? getTerminalBaseUrl(session) : null;\n\n // F3 — short-lived capability mint. The token is appended to the iframe\n // src as a URL fragment (`#vt=<token>`). Fragments stay client-side, so\n // the token doesn't appear in proxy logs, Referer, or browser history.\n const [mintAgentCapability] = useMintAgentCapabilityMutation();\n const [capabilityToken, setCapabilityToken] = useState<string | null>(null);\n const [capabilityError, setCapabilityError] = useState<string | null>(null);\n const [capabilityLoading, setCapabilityLoading] = useState(false);\n // Re-mint counter — bump to force a new mint (used on iframe error / on\n // session/agent change).\n const [mintNonce, setMintNonce] = useState(0);\n const requestRemint = useCallback(() => setMintNonce((n) => n + 1), []);\n\n const agentIdForMint = agent?.id ?? null;\n const sessionIdForMint = session?.id ?? null;\n\n useEffect(() => {\n let cancelled = false;\n if (!terminalBaseUrl || !agentIdForMint || !sessionIdForMint) {\n setCapabilityToken(null);\n setCapabilityError(null);\n return undefined;\n }\n setCapabilityLoading(true);\n setCapabilityError(null);\n mintAgentCapability({\n variables: {\n agentId: agentIdForMint,\n // Multi-profile is a follow-up — see useActiveAgentProfile. The agent\n // mounts /api/profiles/default/* by default, which is what the F3\n // exchange endpoint resolves to as well.\n profile: 'default',\n scope: AgentCapabilityScope.TerminalSession,\n resourceId: sessionIdForMint,\n ttlSeconds: 60,\n },\n })\n .then((res) => {\n if (cancelled) return;\n const token = res.data?.mintAgentCapability?.token ?? null;\n if (!token) {\n setCapabilityError('Failed to mint terminal capability');\n setCapabilityToken(null);\n } else {\n setCapabilityToken(token);\n }\n })\n .catch((err) => {\n if (cancelled) return;\n setCapabilityError(\n err instanceof Error ? err.message : 'Failed to mint terminal capability'\n );\n setCapabilityToken(null);\n })\n .finally(() => {\n if (!cancelled) setCapabilityLoading(false);\n });\n return () => {\n cancelled = true;\n };\n }, [terminalBaseUrl, agentIdForMint, sessionIdForMint, mintNonce, mintAgentCapability]);\n\n const terminalUrl =\n terminalBaseUrl && capabilityToken\n ? `${terminalBaseUrl}#vt=${encodeURIComponent(capabilityToken)}`\n : 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 // 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 <button\n type=\"button\"\n onClick={() => setWorkbenchOpen((v) => !v)}\n className={`flex items-center gap-2 px-3 py-2 rounded-md transition-colors ${\n workbenchOpen\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={tr(\n 'sessionDetails.workbenchTitle',\n 'Notes, prompts & history for this session (Cmd/Ctrl+J)'\n )}\n >\n <PanelRightOpen className=\"size-4\" />\n {tr('sessionDetails.workbench', 'Workbench')}\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\n {/* Connection Log — every probe / start / mint / exchange the\n svc did for this session. Operators export this when\n \"Connect\" fails and paste into a bug report. The\n `#connection-log` anchor is referenced from the TerminalPanel\n disconnected overlay so a failed terminal lands the user at\n the log card directly. Anchor wraps the Card because the\n Card primitive doesn't pass `id` through. */}\n <div id=\"connection-log\" className=\"scroll-mt-20\">\n <Card className=\"min-h-[40vh]\">\n <ConnectionLogPanel sessionId={session.id} sessionName={session.name} />\n </Card>\n </div>\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 session + web terminal 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 // For a generic-share recipient (non-owner collaborator) wrap the\n // terminal in the same SharedTerminalPanel the SharedSessionPage\n // link flow uses — presence + turn-based control + read-only\n // overlay. `isShared={false}` (owner / solo) renders the bare\n // iframe unchanged.\n <SharedTerminalPanel\n isShared={isSharedCollaborator}\n collaborators={collaborators}\n currentUserId={currentUserId}\n permission={collaboratorPermission}\n onRequestControl={handleRequestControl}\n onReleaseControl={handleReleaseControl}\n isRequestingControl={requestingControl}\n isReleasingControl={releasingControl}\n className=\"h-full\"\n >\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 // F3 — the cookie-shim cookie has a 10-minute lifetime. If the\n // iframe errors after that (e.g. user leaves the page idle), the\n // bootstrap exchange will 401 and we need a fresh capability.\n onError={requestRemint}\n />\n </SharedTerminalPanel>\n ) : terminalBaseUrl && (capabilityLoading || capabilityError) ? (\n <div className=\"flex items-center justify-center h-full\">\n <div className=\"text-center\">\n {capabilityError ? (\n <>\n <AlertCircle className=\"size-8 text-status-error mx-auto mb-3\" />\n <p className=\"text-text-secondary text-sm\">\n {tr(\n 'sessionDetails.terminalCapabilityFailed',\n 'Could not prepare terminal access'\n )}\n </p>\n <p className=\"text-text-tertiary text-xs mt-1\">{capabilityError}</p>\n <button\n type=\"button\"\n onClick={requestRemint}\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 </>\n ) : (\n <>\n <Loader2 className=\"size-6 text-text-tertiary mx-auto mb-3 animate-spin\" />\n <p className=\"text-text-secondary text-sm\">\n {tr('sessionDetails.preparingTerminal', 'Preparing terminal…')}\n </p>\n </>\n )}\n </div>\n </div>\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/plans');\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\n {/* Session workbench drawer — notes / prompts / history (F1 + F2) */}\n <SessionWorkbenchDrawer\n sessionId={session.id}\n open={workbenchOpen}\n onClose={() => setWorkbenchOpen(false)}\n />\n </MotionPage>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAuFA,SAAS,GAAmB,GAAqD;CAC/E,IAAM,IAAM,EAAQ;AACpB,KAAI,CAAC,EAAK,QAAO;AAKjB,KAAI;EACF,IAAM,IAAI,IAAI,IAAI,EAAI;AAEtB,SADA,EAAE,aAAa,OAAO,SAAS,EACxB,EAAE,UAAU;SACb;AACN,SAAO;;;AAOX,IAAM,MAAY,MAAc,EAAE,QAAQ,uBAAuB,OAAO;AAExE,SAAS,GAAgB,GAAsC;AAC7D,KAAI,CAAC,EAAW,QAAO;CACvB,IAAM,IAAO,KAAK,KAAK,GAAG,IAAI,KAAK,EAAU,CAAC,SAAS;AAIvD,QAHI,IAAO,MAAc,aACrB,IAAO,OAAgB,GAAG,KAAK,MAAM,IAAO,IAAM,CAAC,SACnD,IAAO,QAAiB,GAAG,KAAK,MAAM,IAAO,KAAQ,CAAC,SACnD,IAAI,KAAK,EAAU,CAAC,oBAAoB;;AAOjD,SAAgB,IAAqB;CACnC,IAAM,EAAE,SAAM,IAAS,EAKjB,IAAK,GACR,GAAa,GAAkB,MAAqD;EACnF,IAAM,IAAQ,EAAE,GAAK,EAAO;AAM5B,SALI,MAAU,IACT,IAIE,OAAO,QAAQ,EAAO,CAAC,QAC3B,GAAK,CAAC,GAAG,OACR,EAAI,QAAY,OAAO,SAAS,GAAS,EAAE,CAAC,SAAS,IAAI,QAAQ,OAAO,EAAE,CAAC,EAC7E,EACD,GARmB,IADM;IAW5B,CAAC,EAAE,CACJ,EACK,EAAE,UAAO,IAA2B,EACpC,EAAE,UAAU,IAAc,oBAAgB,IAAiB,EAC3D,KAAW,MAAgB,IAC3B,IAAgB,IAAa,MAAM,IACnC,IAAW,IAAiB,EAC5B,EAAE,mBAAe,WAAW,IAAoB,oBAAgB,IAAgB,EAEhF,CAAC,MAAgB,GAAwB,EAAE,gBAAgB,CAAC,aAAa,EAAE,CAAC,EAC5E,CAAC,MAAe,GAAuB,EAAE,gBAAgB,CAAC,aAAa,EAAE,CAAC,EAC1E,CAAC,MAAkB,GAA0B,EAAE,gBAAgB,CAAC,aAAa,EAAE,CAAC,EAChF,CAAC,MAAwB,GAAgC,EAC7D,gBAAgB,CAAC,aAAa,EAC/B,CAAC,EACI,CAAC,MAA0B,GAAkC,EACjE,gBAAgB,CAAC,aAAa,EAC/B,CAAC,EACI,CAAC,MAAiB,GAAyB,EAAE,gBAAgB,CAAC,aAAa,EAAE,CAAC,EAG9E,CAAC,IAAgB,MAAqB,EAAS,GAAM,EACrD,CAAC,IAAa,MAAkB,EAAS,GAAG,EAC5C,CAAC,IAAsB,MAA2B,EAAS,GAAG,EAC9D,CAAC,GAAY,MAAiB,EAAS,GAAM,EAE7C,WAA8B;AAGlC,EAFA,GAAe,GAAS,WAAW,GAAG,EACtC,GAAwB,GAAS,oBAAoB,GAAG,EACxD,GAAkB,GAAK;IAGnB,KAAwB,YAAY;AACnC,SAAS,IACd;MAAc,GAAK;AACnB,OAAI;AAUF,IATA,MAAM,GAAc,EAClB,WAAW;KACT,IAAI,EAAQ;KACZ,OAAO;MACL,SAAS,MAAe;MACxB,kBAAkB,MAAwB;MAC3C;KACF,EACF,CAAC,EACF,GAAkB,GAAM;aAChB;AACR,OAAc,GAAM;;;IAKlB,EAAE,UAAM,YAAS,WAAO,eAAY,EAAmB;EAC3D,WAAW,EAAE,IAAI,KAAM,IAAI;EAC3B,MAAM,CAAC;EACP,aAAa;EACd,CAAC,EAGI,IAAU,IAAM,SAGhB,IAAQ,GAAS,SAAS,MAC1B,IAAO,GAAS,QAAQ;AAG9B,SAAgB;AACd,EAAI,KACF,GAAqB,SAAS;GAC5B,eAAgB,GAAO,MAAiB;GACxC,iBAAkB,GAAO,MAAiB;GAC1C,cAAe,GAAM,MAAiB;GACtC,gBAAiB,GAAM,MAAiB;GACxC,iBAAiB,EAAQ;GACzB,mBAAmB,EAAQ;GAC5B,CAAC;IAEH;EAAC,GAAS;EAAI,GAAO;EAAI,GAAM;EAAG,CAAC;CAatC,IAAM,IAAc,GAAS,SAAS,UAAU,MAK1C,KAAU,CAAC,CAAC,KAAe,CAAC,CAAC,KAAiB,MAAgB,GAC9D,KAAqD,GAAS,iBAAiB,EAAE,EACjF,IAAiB,GAAc,MAAM,MAAM,EAAE,WAAW,EAAc,IAAI,MAQ1E,IACJ,CAAC,CAAC,KAAW,CAAC,OAAY,CAAC,CAAC,KAAe,CAAC,CAAC,GAAS,eAAe,CAAC,CAAC,IAEnE,CAAC,MAAqB,GAA8B,EACpD,CAAC,IAAgB,EAAE,SAAS,QAAuB,IAAkC,EACrF,CAAC,IAAgB,EAAE,SAAS,QAAsB,GAAkC,EAEpF,IAA0B,EAAsB,KAAK;AAE3D,SAAgB;EACd,IAAM,IAAM,GAAS;AAChB,QAGD,CAAC,KAAwB,KACzB,EAAwB,YAAY,MACxC,EAAwB,UAAU,GAClC,GAAkB,EAAE,WAAW,EAAE,OAAO,EAAE,WAAW,GAAK,EAAE,EAAE,CAAC,CAC5D,WAAW;AAGL,MAAS;IACd,CACD,OAAO,MAAQ;AAId,GADA,EAAwB,UAAU,MAClC,QAAQ,KAAK,wDAAwD,EAAI;IACzE;IACH;EAAC,GAAS;EAAI;EAAsB;EAAgB;EAAmB;EAAQ,CAAC;CAInF,IAAM,EAAE,MAAM,OAAqB,EAAkC;EACnE,WAAW,EAAE,WAAW,GAAS,MAAM,IAAI;EAC3C,MAAM,CAAC,GAAS,MAAM,CAAC;EACxB,CAAC,EACI,EAAE,MAAM,OAAmB,EAAgC;EAC/D,WAAW,EAAE,WAAW,GAAS,MAAM,IAAI;EAC3C,MAAM,CAAC,GAAS,MAAM,CAAC;EACxB,CAAC,EACI,EAAE,MAAM,OAAuB,GAAqC;EACxE,WAAW,EAAE,WAAW,GAAS,MAAM,IAAI;EAC3C,MAAM,CAAC,GAAS,MAAM,CAAC;EACxB,CAAC;AACF,SAAgB;AACd,GAAI,MAAoB,MAAkB,OACnC,GAAS;IAEf;EAAC;EAAkB;EAAgB;EAAoB;EAAQ,CAAC;CAInE,IAAM,KACJ,GAAgB,SAAS,GAAiB,SACtC,GAAuB,OACvB,GAAuB,UAEvB,KAAuB,EAAY,YAAY;AAC9C,SAAS,GACd,KAAI;AAEF,GADA,MAAM,GAAe,EAAE,WAAW,EAAE,WAAW,EAAQ,IAAI,EAAE,CAAC,EAC9D,MAAM,GAAS;WACR,GAAK;AACZ,WAAQ,MAAM,8BAA8B,EAAI;;IAEjD;EAAC,GAAS;EAAI;EAAgB;EAAQ,CAAC,EAEpC,KAAuB,EAAY,YAAY;AAC9C,SAAS,GACd,KAAI;AAEF,GADA,MAAM,GAAe,EAAE,WAAW,EAAE,WAAW,EAAQ,IAAI,EAAE,CAAC,EAC9D,MAAM,GAAS;WACR,GAAK;AACZ,WAAQ,MAAM,8BAA8B,EAAI;;IAEjD;EAAC,GAAS;EAAI;EAAgB;EAAQ,CAAC,EAKpC,CAAC,IAAe,KAAoB,EAAS,GAAM;AACzD,SAAgB;EACd,IAAM,KAAS,MAAqB;AAClC,IAAK,EAAE,WAAW,EAAE,aAAa,EAAE,QAAQ,OAAO,EAAE,QAAQ,QAC1D,EAAE,gBAAgB,EAClB,GAAkB,MAAM,CAAC,EAAE,IAClB,EAAE,QAAQ,YACnB,EAAiB,GAAM;;AAI3B,SADA,OAAO,iBAAiB,WAAW,EAAM,QAC5B,OAAO,oBAAoB,WAAW,EAAM;IACxD,EAAE,CAAC;CAGN,IAAM,CAAC,GAAc,KAAmB,EAAS,GAAK,EAChD,CAAC,GAAmB,MAAwB,EAAS,GAAM,EAC3D,CAAC,GAAiB,MAAsB,EAAS,GAAM,EACvD,CAAC,GAAe,KAAoB,EAAwB,KAAK,EACjE,CAAC,IAAoB,KAAyB,EAAS,GAAM,EAC7D,CAAC,IAA4B,MAAiC,EAAS,GAAM,EAC7E,CAAC,IAAc,KAAmB,EAE9B,KAAK,EACT,IAAgB,EAAsB,KAAK,EAG3C,KAAmB,EAA8B,KAAK,EAKtD,KAAqB,EAAO,GAAM,EAClC,KAAsB,EAAO,GAAM,EAInC,IAAkB,IAAU,GAAmB,EAAQ,GAAG,MAK1D,CAAC,MAAuB,IAAgC,EACxD,CAAC,IAAiB,KAAsB,EAAwB,KAAK,EACrE,CAAC,IAAiB,KAAsB,EAAwB,KAAK,EACrE,CAAC,IAAmB,MAAwB,EAAS,GAAM,EAG3D,CAAC,IAAW,MAAgB,EAAS,EAAE,EACvC,KAAgB,QAAkB,IAAc,MAAM,IAAI,EAAE,EAAE,EAAE,CAAC,EAEjE,KAAiB,GAAO,MAAM,MAC9B,KAAmB,GAAS,MAAM;AAExC,SAAgB;EACd,IAAI,IAAY;AAChB,MAAI,CAAC,KAAmB,CAAC,MAAkB,CAAC,IAAkB;AAE5D,GADA,EAAmB,KAAK,EACxB,EAAmB,KAAK;AACxB;;AAoCF,SAlCA,GAAqB,GAAK,EAC1B,EAAmB,KAAK,EACxB,GAAoB,EAClB,WAAW;GACT,SAAS;GAIT,SAAS;GACT,OAAO,GAAqB;GAC5B,YAAY;GACZ,YAAY;GACb,EACF,CAAC,CACC,MAAM,MAAQ;AACb,OAAI,EAAW;GACf,IAAM,IAAQ,EAAI,MAAM,qBAAqB,SAAS;AACtD,GAAK,IAIH,EAAmB,EAAM,IAHzB,EAAmB,qCAAqC,EACxD,EAAmB,KAAK;IAI1B,CACD,OAAO,MAAQ;AACV,SACJ,EACE,aAAe,QAAQ,EAAI,UAAU,qCACtC,EACD,EAAmB,KAAK;IACxB,CACD,cAAc;AACb,GAAK,KAAW,GAAqB,GAAM;IAC3C,QACS;AACX,OAAY;;IAEb;EAAC;EAAiB;EAAgB;EAAkB;EAAW;EAAoB,CAAC;CAEvF,IAAM,IACJ,KAAmB,KACf,GAAG,EAAgB,MAAM,mBAAmB,GAAgB,KAC5D,MAEA,UAAmB;AACvB,IAAS,YAAY;IAGjB,KAAqB,YAAY;AAChC,SAAS,GACd,KAAI;AACF,SAAM,GAAa,EAAE,WAAW,EAAE,IAAI,EAAQ,IAAI,EAAE,CAAC;WAC9C,GAAK;AACZ,OAAI,EAAsB,EAAI,EAAE;AAE9B,IADA,GAAyB,EACzB,EAAsB,GAAK;AAC3B;;AAGF,WAAQ,MAAM,4BAA4B,EAAI;;IAI5C,KAAoB,YAAY;AAC/B,SAAS,GACd,KAAI;AACF,SAAM,GAAY,EAAE,WAAW,EAAE,IAAI,EAAQ,IAAI,EAAE,CAAC;WAC7C,GAAK;AACZ,WAAQ,MAAM,2BAA2B,EAAI;;IAI3C,KAAuB,YAAY;AAClC,SAAS,GACd,KAAI;AACF,SAAM,GAAe,EAAE,WAAW,EAAE,IAAI,EAAQ,IAAI,EAAE,CAAC;WAChD,GAAK;AACZ,OAAI,EAAsB,EAAI,EAAE;AAE9B,IADA,GAAyB,EACzB,EAAsB,GAAK;AAC3B;;AAGF,WAAQ,MAAM,8BAA8B,EAAI;;IAS9C,IAAyB,QAAkB;AAC/C,8BAA4B;AAC1B,MAAiB,SAAS,eAAe;IAAE,UAAU;IAAU,OAAO;IAAO,CAAC;IAC9E;IACD,EAAE,CAAC,EAOA,IAAqB,EACzB,OACE,GAIA,GACA,MACG;EACH,IAAM,IAAK,GAAS;AACpB,MAAI,CAAC,EAAI;AAET,EADA,GAAmB,GAAK,EACxB,EAAiB,KAAK;EACtB,IAAI,KAAY;AAChB,MAAI;GACF,IAAM,IAAS,MAAM,EAAO,EAAG;AAC/B,GAAI,EAAO,SAAS,EAAsB,EAAO,MAAM,IACrD,GAAyB,EACzB,EAAsB,GAAK,IAClB,EAAO,QAChB,EAAiB,EAAO,MAAM,QAAQ,GAC7B,CAAC,EAAO,QAAQ,CAAC,EAAO,KAAK,KACtC,EAAiB,EAAiB,IAElC,EAAgB,GAAK,EACrB,KAAY;WAEP,GAAK;AACZ,OAAI,EAAsB,EAAI,EAAE;AAE9B,IADA,GAAyB,EACzB,EAAsB,GAAK;AAC3B;;AAGF,KADgB,aAAe,QAAQ,EAAI,UAAU,EAC5B;YACjB;AAER,GADA,GAAmB,GAAM,EACrB,MAAW,GAAwB;;IAG3C,CAAC,GAAS,IAAI,EAAuB,CACtC,EAEK,KAAsB,QAExB,GACG,MAAO,GAAqB,EAAE,WAAW,EAAE,OAAI,EAAE,CAAC,EACnD,wBACA,2BACD,EACH,CAAC,IAAsB,EAAmB,CAC3C,EAEK,KAAwB,QAE1B,GACG,MAAO,GAAuB,EAAE,WAAW,EAAE,OAAI,EAAE,CAAC,EACrD,0BACA,6BACD,EACH,CAAC,IAAwB,EAAmB,CAC7C;AAOD,SAAgB;AACd,MAAI,CAAC,GAAmB,SAAS;AAE/B,GADA,GAAmB,UAAU,IAC7B,GAAoB,UAAU;AAC9B;;EAEF,IAAM,IAAa,KAAgB,CAAC,GAAoB;AAExD,EADA,GAAoB,UAAU,GAC1B,KAAY,GAAwB;IACvC,CAAC,GAAc,EAAuB,CAAC;CAQ1C,IAAM,KAAY,GAAS,MAAM,MAC3B,KAA0B,EAAsB,KAAK;AAK3D,SAAgB;EACd,IAAM,IAAS,GAAS,UAAU,MAC5B,IAAW,GAAS,YAAY;AAKtC,MAAI,OAAc,GAAwB,SAAS;AAGjD,GAFA,GAAwB,UAAU,IAClC,EAAc,UAAU,GACxB,EAAgB,KAAK;AACrB;;AAGF,MAAI,MAAW,aAAa,MAAW,YAAY;AAKjD,GADA,EAAgB,KAAK,EACrB,EAAc,UAAU;AACxB;;EAGF,IAAM,IAAO,EAAc;AAU3B,EATI,MAAW,cAAc,MAAS,aAAa,MAAS,eAAe,IACzE,EAAgB,EAAE,aAAU,CAAC,GACpB,MAAW,aAAa,KAAgB,MAAa,QAK9D,GAAiB,MAAS,KAAO,EAAI,aAAa,OAAO,EAAE,aAAU,GAAG,EAAK,EAE/E,EAAc,UAAU;IACvB;EAAC;EAAW,GAAS;EAAQ,GAAS;EAAU;EAAa,CAAC;CAEjE,IAAM,KAAqB,QAAkB;AAC3C,EAAI,KACF,OAAO,KAAK,GAAa,SAAS;IAEnC,CAAC,EAAY,CAAC;AAEjB,KAAI,GACF,QACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,KAAD;GAAG,WAAU;aACV,EAAG,qCAAqC,yBAAyB;GAChE,CAAA;EACA,CAAA;AAEV,KACE,OACC,GAAY,mBAAmB,UAAU,KAAK,KAC/C,CAAC,GAAc,GAAiB,aAAa,CAE7C,QACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,MAAD;IAAI,WAAU;cACX,EAAG,+BAA+B,gBAAgB;IAChD,CAAA,EACL,kBAAC,KAAD;IAAG,WAAU;cACV,EACC,mCACA,iDACD;IACC,CAAA,CACA;;EACF,CAAA;CAGV,IAAM,WAAyB;AACxB,OACL,EAAS,WAAW,EAAM,KAAK;IAG3B,WAAwB;AACvB,OACL,EAAS,UAAU,EAAK,KAAK;;AAI/B,KAAI,KAAW,CAAC,EACd,QACE,kBAAC,GAAD;EAAY,WAAU;YAAtB,CACE,kBAAC,UAAD;GACE,MAAK;GACL,SAAS;GACT,WAAU;aAHZ,CAKE,kBAAC,GAAD,EAAW,WAAU,UAAW,CAAA,EAC/B,EAAG,iCAAiC,mBAAmB,CACjD;MACT,kBAAC,IAAD,EAAc,SAAQ,UAAW,CAAA,CACtB;;AAIjB,KAAI,MAAS,CAAC,EACZ,QACE,kBAAC,GAAD;EAAY,WAAU;YAAtB,CACE,kBAAC,UAAD;GACE,MAAK;GACL,SAAS;GACT,WAAU;aAHZ,CAKE,kBAAC,GAAD,EAAW,WAAU,UAAW,CAAA,EAAA,mBAEzB;MACT,kBAAC,IAAD;GACE,OAAO,EAAG,6BAA6B,yBAAyB;GAChE,SAAS,GAAM;GACf,eAAe,GAAS;GACxB,CAAA,CACS;;AAIjB,KAAI,CAAC,EACH,QACE,kBAAC,GAAD;EAAY,WAAU;YAAtB,CACE,kBAAC,UAAD;GACE,MAAK;GACL,SAAS;GACT,WAAU;aAHZ,CAKE,kBAAC,GAAD,EAAW,WAAU,UAAW,CAAA,EAAA,mBAEzB;MACT,kBAAC,IAAD;GACE,MAAM,kBAAC,GAAD,EAAU,WAAU,8BAA+B,CAAA;GACzD,OAAO,EAAG,2BAA2B,oBAAoB;GACzD,aAAa,GAAG,EAAG,qCAAqC,qBAAqB,CAAC,IAAI,EAAG,IAAI,EAAG,qCAAqC,SAAS;GAC1I,QACE,kBAAC,UAAD;IACE,MAAK;IACL,SAAS;IACT,WAAU;cAET,EAAG,iCAAiC,mBAAmB;IACjD,CAAA;GAEX,CAAA,CACS;;CAIjB,IAAM,KAAY,EAAQ,WAAW,WAC/B,KAAiB,IAAoB,0BAA0B;AAErE,QACE,kBAAC,GAAD;EAAY,WAAU;YAAtB;GAEE,kBAAC,OAAD;IACE,WAAW,+CAA+C,KAAgB,IAAc,SAAS;cADnG;KAIE,kBAAC,UAAD;MACE,MAAK;MACL,SAAS;MACT,WAAU;gBAHZ,CAKE,kBAAC,GAAD,EAAW,WAAU,UAAW,CAAA,EAAA,mBAEzB;;KAGT,kBAAC,IAAD;MACE,OAAO,EAAQ;MACf,aAAa,EAAQ,WAAW;MAChC,YAAY,CACV;OAAE,OAAO;OAAY,MAAM,KAAW,GAAG,GAAS,aAAa;OAAa,EAC5E,EAAE,OAAO,EAAQ,MAAM,CACxB;MACD,SACE,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,UAAD;SACE,MAAK;SACL,eAAe,GAAS;SACxB,UAAU;SACV,WAAU;SACV,OAAM;mBAEN,kBAAC,IAAD,EAAW,WAAW,UAAU,IAAU,iBAAiB,MAAQ,CAAA;SAC5D,CAAA;QACT,kBAAC,UAAD;SACE,MAAK;SACL,eAAe,GAAkB,MAAM,CAAC,EAAE;SAC1C,WAAW,kEACT,KACI,mFACA;SAEN,OAAO,EACL,iCACA,yDACD;mBAXH,CAaE,kBAAC,IAAD,EAAgB,WAAU,UAAW,CAAA,EACpC,EAAG,4BAA4B,YAAY,CACrC;;QACR,KACC,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,UAAD;SACE,MAAK;SACL,SAAS;SACT,WAAU;mBAHZ,CAKE,kBAAC,IAAD,EAAQ,WAAU,UAAW,CAAA,EAC5B,EAAG,8BAA8B,eAAe,CAC1C;YACT,kBAAC,UAAD;SACE,MAAK;SACL,SAAS;SACT,WAAU;mBAHZ,CAKE,kBAAC,IAAD,EAAW,WAAU,UAAW,CAAA,EAC/B,EAAG,iCAAiC,kBAAkB,CAChD;WACR,EAAA,CAAA,GAEH,kBAAC,UAAD;SACE,MAAK;SACL,SAAS;SACT,WAAU;mBAHZ,CAKE,kBAAC,IAAD,EAAM,WAAU,UAAW,CAAA,EAC1B,EAAG,6BAA6B,cAAc,CACxC;;QAGV,IACC,kBAAA,GAAA,EAAA,UAAA;SACE,kBAAC,UAAD;UACE,MAAK;UACL,eAAe,GAAiB,MAAS,CAAC,EAAK;UAC/C,WAAW,kEACT,IACI,mFACA;UAEN,OACE,IACI,EAAG,+BAA+B,gBAAgB,GAClD,EAAG,+BAA+B,gBAAgB;oBAX1D,CAcE,kBAAC,GAAD,EAAU,WAAU,UAAW,CAAA,EAC9B,EAAG,2BAA2B,WAAW,CACnC;;SACT,kBAAC,UAAD;UACE,MAAK;UACL,eAAe,GAA8B,GAAK;UAClD,UAAU;UACV,WAAU;UACV,OAAO,EACL,uCACA,oEACD;oBARH,CAUG,IACC,kBAAC,GAAD,EAAS,WAAU,yBAA0B,CAAA,GAE7C,kBAAC,IAAD,EAAW,WAAU,YAAa,CAAA,EAEpC,kBAAC,QAAD;WAAM,WAAU;qBACb,EAAG,uCAAuC,UAAU;WAChD,CAAA,CACA;;SACT,kBAAC,UAAD;UACE,MAAK;UACL,SAAS;UACT,WAAU;UACV,OAAO,EAAG,+BAA+B,2BAA2B;oBAEpE,kBAAC,IAAD,EAAc,WAAU,UAAW,CAAA;UAC5B,CAAA;SACR,EAAA,CAAA,GAEH,kBAAC,UAAD;SACE,MAAK;SACL,SAAS;SACT,UAAU,KAAmB,CAAC;SAC9B,WAAU;SACV,OACG,IAEI,EAAM,YAKL,EAAG,+BAA+B,gBAAgB,GAJlD,EACE,mCACA,qCACD,GALH,EAAG,kCAAkC,oBAAoB;mBAPjE,CAgBG,IACC,kBAAC,GAAD,EAAS,WAAU,uBAAwB,CAAA,GAE3C,kBAAC,GAAD,EAAU,WAAU,UAAW,CAAA,EAEhC,EAAG,+BAA+B,gBAAgB,CAC5C;;QAEP;;MAER,CAAA;KAGF,kBAAC,IAAD;MAAY,SAAS;MAAM,WAAU;gBAArC;OACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;QAAM,WAAU;kBACd,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,OAAD;UAAK,WAAU;oBACb,kBAAC,GAAD,EAAU,WAAU,8BAA+B,CAAA;UAC/C,CAAA,EACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;UAAG,WAAU;oBACV,EAAG,8BAA8B,SAAS;UACzC,CAAA,EACJ,kBAAC,GAAD;UAAa,QAAQ,EAAQ;UAAQ,MAAK;UAAO,CAAA,CAC7C,EAAA,CAAA,CACF;;QACD,CAAA,EACI,CAAA;OACb,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;QAAM,WAAU;kBACd,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,OAAD;UAAK,WAAU;oBACb,kBAAC,IAAD,EAAM,WAAU,8BAA+B,CAAA;UAC3C,CAAA,EACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;UAAG,WAAU;oBACV,EAAG,4BAA4B,OAAO;UACrC,CAAA,EACJ,kBAAC,KAAD;UAAG,WAAU;oBAAmC,EAAQ;UAAS,CAAA,CAC7D,EAAA,CAAA,CACF;;QACD,CAAA,EACI,CAAA;OACb,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;QAAM,WAAU;kBACd,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,OAAD;UAAK,WAAU;oBACb,kBAAC,IAAD,EAAM,WAAU,8BAA+B,CAAA;UAC3C,CAAA,EACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;UAAG,WAAU;oBACV,EAAG,2BAA2B,MAAM;UACnC,CAAA,EACJ,kBAAC,KAAD;UAAG,WAAU;oBAAmC,EAAQ,OAAO;UAAU,CAAA,CACrE,EAAA,CAAA,CACF;;QACD,CAAA,EACI,CAAA;OACb,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;QAAM,WAAU;kBACd,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,OAAD;UAAK,WAAU;oBACb,kBAAC,IAAD,EAAQ,WAAU,8BAA+B,CAAA;UAC7C,CAAA,EACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;UAAG,WAAU;oBACV,EAAG,6BAA6B,QAAQ;UACvC,CAAA,EACJ,kBAAC,KAAD;UAAG,WAAU;oBACV,GAAO,QAAQ;UACd,CAAA,CACA,EAAA,CAAA,CACF;;QACD,CAAA,EACI,CAAA;OACF;;KAGb,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,MAAD;OAAI,WAAU;iBAAd,CACE,kBAAC,IAAD,EAAM,WAAU,UAAW,CAAA,EAC1B,EAAG,+BAA+B,UAAU,CAC1C;UACJ,CAAC,MACA,kBAAC,UAAD;OACE,MAAK;OACL,SAAS;OACT,WAAU;OACV,OAAO,EAAG,mCAAmC,qCAAqC;iBAElF,kBAAC,IAAD,EAAQ,WAAU,UAAW,CAAA;OACtB,CAAA,CAEP;SACN,kBAAC,OAAD;MAAK,WAAU;gBACZ,KACC,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;SAAO,WAAU;mBACd,EAAG,+BAA+B,UAAU;SACvC,CAAA,EACR,kBAAC,SAAD;SACE,cAAW;SACX,MAAK;SACL,OAAO;SACP,WAAW,MAAM,GAAe,EAAE,OAAO,MAAM;SAC/C,aAAa,EAAG,qCAAqC,cAAc;SACnE,WAAU;SACV,CAAA,CACE,EAAA,CAAA;QACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;SAAO,WAAU;mBACd,EAAG,kCAAkC,oBAAoB;SACpD,CAAA,EACR,kBAAC,SAAD;SACE,cAAW;SACX,MAAK;SACL,OAAO;SACP,WAAW,MAAM,GAAwB,EAAE,OAAO,MAAM;SACxD,aAAa,EAAG,wCAAwC,uBAAuB;SAC/E,WAAU;SACV,CAAA,CACE,EAAA,CAAA;QACN,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,UAAD;UACE,MAAK;UACL,SAAS;UACT,UAAU;UACV,WAAU;oBAJZ,CAMG,IACC,kBAAC,GAAD,EAAS,WAAU,yBAA0B,CAAA,GAE7C,kBAAC,IAAD,EAAO,WAAU,YAAa,CAAA,EAE/B,EAAG,uBAAuB,OAAO,CAC3B;aACT,kBAAC,UAAD;UACE,MAAK;UACL,eAAe,GAAkB,GAAM;UACvC,UAAU;UACV,WAAU;oBAJZ,CAME,kBAAC,IAAD,EAAG,WAAU,YAAa,CAAA,EACzB,EAAG,yBAAyB,SAAS,CAC/B;YACL;;QACF;WAEN,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf;QAAiG;QAC7F;QACD,EAAQ,WACP,EAAG,qCAAqC,uBAAuB;QAC7D;UACN,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,IAAD,EAAQ,WAAU,UAAW,CAAA;QAC5B,EAAG,mCAAmC,oBAAoB;QAAC;QAAE;QAC9D,kBAAC,QAAD;SAAM,WAAU;mBACb,EAAQ,oBACP,kBAAC,MAAD;UAAI,WAAU;oBACX,EAAG,oCAAoC,gCAAgC;UACrE,CAAA;SAEF,CAAA;QACH;SACL,EAAA,CAAA;MAED,CAAA,CACD,EAAA,CAAA;KAGP,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,MAAD;OAAI,WAAU;iBAAd,CACE,kBAAC,IAAD,EAAU,WAAU,UAAW,CAAA,EAC9B,EAAG,0BAA0B,wBAAwB,CACnD;;MACD,CAAA,EACN,kBAAC,OAAD;MAAK,WAAU;gBACZ,EAAQ,wBACT,OAAO,KAAK,EAAQ,qBAAqB,CAAC,SAAS,IACjD,kBAAC,OAAD;OAAK,WAAU;iBACZ,OAAO,QAAQ,EAAQ,qBAAqB,CAAC,KAAK,CAAC,GAAK,OACvD,kBAAC,QAAD;QAEE,WAAU;kBAFZ;SAIG;SAAI;SAAE,OAAO,EAAM;SACf;UAJA,EAIA,CACP;OACE,CAAA,GAEN,kBAAC,KAAD;OAAG,WAAU;iBACV,EAAG,4BAA4B,sCAAsC;OACpE,CAAA;MAEF,CAAA,CACD,EAAA,CAAA;KAGP,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,MAAD;OAAI,WAAU;iBAAd,CACE,kBAAC,GAAD,EAAU,WAAU,UAAW,CAAA,EAC9B,EAAG,6BAA6B,cAAc,CAC5C;;MACD,CAAA,EACN,kBAAC,OAAD;MAAK,WAAU;gBACZ,EAAQ,aACP,kBAAC,OAAD;OAAK,WAAU;iBACZ,EAAQ;OACL,CAAA,GAEN,kBAAC,KAAD;OAAG,WAAU;iBACV,EAAG,2BAA2B,qBAAqB;OAClD,CAAA;MAEF,CAAA,CACD,EAAA,CAAA;KAGN,KACC,kBAAC,GAAD;MAAM,SAAS;MAAiB,WAAA;gBAC9B,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,OAAD;SAAK,WAAU;mBACb,kBAAC,IAAD,EAAK,WAAU,8BAA+B,CAAA;SAC1C,CAAA,EACN,kBAAC,OAAD,EAAA,UAAA;SACE,kBAAC,KAAD;UAAG,WAAU;oBACV,EAAG,8BAA8B,eAAe;UAC/C,CAAA;SACJ,kBAAC,KAAD;UAAG,WAAU;oBAAiC,EAAK;UAAS,CAAA;SAC3D,EAAK,QACJ,kBAAC,KAAD;UAAG,WAAU;oBAAiD,EAAK;UAAS,CAAA;SAE1E,EAAA,CAAA,CACF;WACN,kBAAC,GAAD,EAAa,QAAQ,EAAK,QAAU,CAAA,CAChC;;MACD,CAAA;KAIR,KACC,kBAAC,GAAD;MAAM,SAAS;MAAkB,WAAA;gBAC/B,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,OAAD;SAAK,WAAU;mBACb,kBAAC,IAAD,EAAQ,WAAU,8BAA+B,CAAA;SAC7C,CAAA,EACN,kBAAC,OAAD,EAAA,UAAA;SACE,kBAAC,KAAD;UAAG,WAAU;oBACV,EAAG,iCAAiC,mBAAmB;UACtD,CAAA;SACJ,kBAAC,KAAD;UAAG,WAAU;oBAAiC,EAAM;UAAS,CAAA;SAC7D,kBAAC,KAAD;UAAG,WAAU;oBAA+B,EAAM;UAAa,CAAA;SAC3D,EAAA,CAAA,CACF;WACN,kBAAC,GAAD,EAAa,QAAQ,EAAM,WAAW,WAAW,WAAa,CAAA,CAC1D;;MACD,CAAA;KAIT,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,MAAD;OAAI,WAAU;iBAAd,CACE,kBAAC,IAAD,EAAO,WAAU,UAAW,CAAA,EAC3B,EAAG,2BAA2B,WAAW,CACvC;;MACD,CAAA,EACN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;SAAG,WAAU;mBACV,EAAG,4BAA4B,aAAa;SAC3C,CAAA,EACJ,kBAAC,KAAD;SAAG,WAAU;mBACV,EAAQ,YACL,EAAG,sBAAsB,MAAM,GAC/B,EAAG,qBAAqB,KAAK;SAC/B,CAAA,CACA,EAAA,CAAA;QACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;SAAG,WAAU;mBAAuB,EAAG,0BAA0B,UAAU;SAAK,CAAA,EAChF,kBAAC,KAAD;SAAG,WAAU;mBACV,GAAgB,EAAQ,UAAU;SACjC,CAAA,CACA,EAAA,CAAA;QACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;SAAG,WAAU;mBAAuB,EAAG,0BAA0B,UAAU;SAAK,CAAA,EAChF,kBAAC,KAAD;SAAG,WAAU;mBACV,GAAgB,EAAQ,UAAU;SACjC,CAAA,CACA,EAAA,CAAA;QACF;UACL,EAAQ,aAAa,QAAQ,EAAQ,aAAa,KAAA,KACjD,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,KAAD;QAAG,WAAU;kBACV,EAAG,2BAA2B,YAAY;QACzC,CAAA,EACJ,kBAAC,KAAD;QACE,WAAW,eAAe,EAAQ,aAAa,IAAI,6BAA6B;kBAE/E,EAAQ;QACP,CAAA,CACA;SAEJ;QACD,EAAA,CAAA;KAGP,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,MAAD;OAAI,WAAU;iBAAd,CACE,kBAAC,IAAD,EAAK,WAAU,UAAW,CAAA,EACzB,EAAG,uBAAuB,OAAO,CAC/B;;MACD,CAAA,EACN,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,IAAD;OAAiB,YAAW;OAAuB,UAAU,EAAQ;OAAM,CAAA;MACvE,CAAA,CACD,EAAA,CAAA;KASP,kBAAC,OAAD;MAAK,IAAG;MAAiB,WAAU;gBACjC,kBAAC,GAAD;OAAM,WAAU;iBACd,kBAAC,IAAD;QAAoB,WAAW,EAAQ;QAAI,aAAa,EAAQ;QAAQ,CAAA;OACnE,CAAA;MACH,CAAA;KACF;;GAGL,KACC,kBAAC,OAAD;IACE,KAAK;IACL,WAAU;cAFZ;KAKE,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,UAAD;QACE,MAAK;QACL,eAAe,EAAgB,GAAM;QACrC,WAAU;QACV,OAAO,EAAG,mCAAmC,oBAAoB;kBAJnE;SAME,kBAAC,GAAD,EAAU,WAAU,YAAa,CAAA;SAChC,EAAG,2BAA2B,WAAW;SAC1C,kBAAC,IAAD,EAAa,WAAU,UAAW,CAAA;SAC3B;;OACT,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD,EAAU,WAAU,iBAAkB,CAAA,EACrC,EAAQ,KACL;;OACN,kBAAC,OAAD,EAAK,WAAU,UAAW,CAAA;OAC1B,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACG,KACC,kBAAC,UAAD;SACE,MAAK;SACL,SAAS;SACT,WAAU;SACV,OAAO,EAAG,oCAAoC,kBAAkB;mBAEhE,kBAAC,IAAD,EAAc,WAAU,YAAa,CAAA;SAC9B,CAAA,EAEX,kBAAC,UAAD;SACE,MAAK;SACL,eAAe,IAAsB,MAAS,CAAC,EAAK;SACpD,WAAU;SACV,OACE,IACI,EAAG,8BAA8B,eAAe,GAChD,EAAG,2BAA2B,WAAW;mBAI7C,EADD,IACE,KAEA,IAFD,EAAW,WAAU,YAAa,CAEA;SAE7B,CAAA,CACL;;OACF;;KAEL,MACC,kBAAC,OAAD;MACE,MAAK;MACL,WAAU;gBAFZ,CAIE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD,EAAa,WAAU,qBAAsB,CAAA,EAC7C,kBAAC,QAAD,EAAA,UACG,EACC,gCACA,+DACA,EACE,MACE,GAAa,aAAa,OAA4C,KAArC,UAAU,GAAa,SAAS,IACpE,CACF,EACI,CAAA,CACH;UACN,kBAAC,UAAD;OACE,MAAK;OACL,eAAe,EAAgB,KAAK;OACpC,WAAU;OACV,OAAO,EAAG,kBAAkB,UAAU;iBAEtC,kBAAC,IAAD,EAAG,WAAU,YAAa,CAAA;OACnB,CAAA,CACL;;KAIR,kBAAC,OAAD;MAAK,WAAW,GAAG,GAAe;gBAC/B,IACC,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,OAAD;QAAK,WAAU;kBAAf;SACE,kBAAC,GAAD,EAAS,WAAU,wDAAyD,CAAA;SAC5E,kBAAC,KAAD;UAAG,WAAU;oBAAb;WACG,EAAG,mCAAmC,wBAAwB;WAAC;WAAE,EAAQ;WAAK;WAE7E;;SACJ,kBAAC,KAAD;UAAG,WAAU;oBACV,EACC,kCACA,2CACD;UACC,CAAA;SACA;;OACF,CAAA,GACJ,IACF,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,OAAD;QAAK,WAAU;kBAAf;SACE,kBAAC,GAAD,EAAa,WAAU,8CAA+C,CAAA;SACtE,kBAAC,KAAD;UAAG,WAAU;oBACV,EAAG,sCAAsC,2BAA2B;UACnE,CAAA;SACJ,kBAAC,KAAD;UAAG,WAAU;oBAAoC;UAAkB,CAAA;SACnE,kBAAC,UAAD;UACE,MAAK;UACL,SAAS;UACT,WAAU;oBAET,EAAG,wBAAwB,QAAQ;UAC7B,CAAA;SACL;;OACF,CAAA,GACJ,IAMF,kBAAC,IAAD;OACE,UAAU;OACK;OACA;OACf,YAAY;OACZ,kBAAkB;OAClB,kBAAkB;OAClB,qBAAqB;OACrB,oBAAoB;OACpB,WAAU;iBAEV,kBAAC,UAAD;QACE,KAAK;QACL,OAAO,cAAc,EAAQ;QAC7B,WAAU;QACV,OAAM;QAIN,SAAS;QACT,CAAA;OACkB,CAAA,GACpB,MAAoB,MAAqB,MAC3C,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,OAAD;QAAK,WAAU;kBACZ,KACC,kBAAA,GAAA,EAAA,UAAA;SACE,kBAAC,GAAD,EAAa,WAAU,yCAA0C,CAAA;SACjE,kBAAC,KAAD;UAAG,WAAU;oBACV,EACC,2CACA,oCACD;UACC,CAAA;SACJ,kBAAC,KAAD;UAAG,WAAU;oBAAmC;UAAoB,CAAA;SACpE,kBAAC,UAAD;UACE,MAAK;UACL,SAAS;UACT,WAAU;oBAET,EAAG,wBAAwB,QAAQ;UAC7B,CAAA;SACR,EAAA,CAAA,GAEH,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD,EAAS,WAAU,uDAAwD,CAAA,EAC3E,kBAAC,KAAD;SAAG,WAAU;mBACV,EAAG,oCAAoC,sBAAsB;SAC5D,CAAA,CACH,EAAA,CAAA;QAED,CAAA;OACF,CAAA,GAEN,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,OAAD;QAAK,WAAU;kBAAf;SACE,kBAAC,GAAD,EAAU,WAAU,0CAA2C,CAAA;SAC/D,kBAAC,KAAD;UAAG,WAAU;oBACV,EAAG,oCAAoC,sBAAsB;UAC5D,CAAA;SACJ,kBAAC,KAAD;UAAG,WAAU;oBACV,IACG,EACE,wCACA,4EACD,GACD,EACE,oCACA,wCACD;UACH,CAAA;SACH,KACC,kBAAC,UAAD;UACE,MAAK;UACL,SAAS;UACT,UAAU;UACV,WAAU;oBAET,IACG,EAAG,0BAA0B,aAAa,GAC1C,EAAG,+BAA+B,gBAAgB;UAC/C,CAAA;SAEP;;OACF,CAAA;MAEJ,CAAA;KACF;;GAIP,CAAC,MAAiB,KAAe,MAChC,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAgB,GAAK;KACpC,WAAU;KACV,OAAO,EAAG,iCAAiC,kBAAkB;eAJ/D;MAME,kBAAC,GAAD,EAAU,WAAU,YAAa,CAAA;MAChC,EAAG,2BAA2B,WAAW;MAAC;MAAI,EAAQ;MACvD,kBAAC,IAAD,EAAW,WAAU,eAAgB,CAAA;MAC9B;;IACL,CAAA;GAGR,kBAAC,IAAD;IACE,QAAQ;IACR,eAAe,EAAsB,GAAM;IAC3C,iBAAiB;AAEf,KADA,EAAsB,GAAM,EAC5B,EAAS,iBAAiB;;IAE5B,OAAO,GAAsB,UAAU,CAAC;IACxC,SAAS,GAAsB,UAAU,CAAC;IAC1C,cAAa;IACb,aAAY;IACZ,SAAQ;IACR,CAAA;GAIF,kBAAC,IAAD;IACE,QAAQ;IACR,eAAe,GAA8B,GAAM;IACnD,iBAAiB;AAEV,KADL,GAA8B,GAAM,EAC/B,IAAuB;;IAE9B,OAAO,EAAG,8CAA8C,oBAAoB;IAC5E,SAAS,EACP,6CACA,iHACD;IACD,cAAc,EAAG,8CAA8C,mBAAmB;IAClF,aAAa,EAAG,iBAAiB,SAAS;IAC1C,SAAQ;IACR,CAAA;GAGF,kBAAC,IAAD;IACE,WAAW,EAAQ;IACnB,MAAM;IACN,eAAe,EAAiB,GAAM;IACtC,CAAA;GACS"}
|
|
@@ -136,9 +136,9 @@ function jt() {
|
|
|
136
136
|
Wn.current = Un, d(() => {
|
|
137
137
|
let e = Wn.current;
|
|
138
138
|
if (!e || k !== "terminal") return;
|
|
139
|
-
let t = e.isBreadcrumbCollapsed;
|
|
140
|
-
return e.setBreadcrumbCollapsed(!0), () => {
|
|
141
|
-
Wn.current?.setBreadcrumbCollapsed(t);
|
|
139
|
+
let t = e.isBreadcrumbCollapsed, n = e.footerVisible;
|
|
140
|
+
return e.setBreadcrumbCollapsed(!0), e.setFooterVisible(!1), () => {
|
|
141
|
+
Wn.current?.setBreadcrumbCollapsed(t), Wn.current?.setFooterVisible(n);
|
|
142
142
|
};
|
|
143
143
|
}, [k]);
|
|
144
144
|
let [z, B] = m([]), [V, H] = m(null), [Gn, U] = m(!1), [Kn, qn] = m(!1), [Jn, Yn] = m(!1), [Xn, Zn] = m(0), [Qn, $n] = m(null), [er, tr] = m(!1), [W, nr] = m(!1), rr = It.get("view"), ir = It.get("session"), ar = It.get("group"), or = It.get("target"), [sr] = o({ refetchQueries: ["ListSessions"] }), { setDraftTagIds: cr, flushTags: lr } = we("VIBECONTROLS_SESSION"), [ur, dr] = m(!1), [fr, pr] = m(""), [mr, hr] = m(null), [gr, _r] = m(null), [vr, { data: yr, loading: br }] = t({ fetchPolicy: "network-only" }), [xr] = e({ refetchQueries: ["ListSessions"] }), [Sr] = oe({ refetchQueries: ["ListSessions"] }), [Cr] = ce({ refetchQueries: ["ListSessions"] }), [wr] = re({ refetchQueries: ["ListSessions"] }), [G] = se({ refetchQueries: ["ListSessions"] }), [Tr] = le({ refetchQueries: ["ListSessions"] }), [Er] = ie({ refetchQueries: ["ListSessions"] }), [Dr] = de({ refetchQueries: ["ListSessions"] }), [Or] = ue({ refetchQueries: ["ListSessions"] }), [kr] = a({ refetchQueries: ["ListSessions"] }), [Ar] = r({ refetchQueries: ["ListSessions"] }), [jr] = n({ refetchQueries: ["ListSessions"] }), [Mr] = i({ refetchQueries: ["ListSessions"] }), [Nr] = ee();
|