@polderlabs/bizar 5.0.2 → 5.1.0
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/bizar-dash/dist/assets/MobileChat-BVojK0n5.js +1 -0
- package/bizar-dash/dist/assets/MobileChat-BVojK0n5.js.map +1 -0
- package/bizar-dash/dist/assets/MobileSettings-C85ApWKZ.js +1 -0
- package/bizar-dash/dist/assets/MobileSettings-C85ApWKZ.js.map +1 -0
- package/bizar-dash/dist/assets/{icons-Bo0iH9EC.js → icons-Clz0NR6Y.js} +20 -25
- package/bizar-dash/dist/assets/icons-Clz0NR6Y.js.map +1 -0
- package/bizar-dash/dist/assets/{main-C1cpttnv.js → main-8feQWXiF.js} +10 -10
- package/bizar-dash/dist/assets/main-8feQWXiF.js.map +1 -0
- package/bizar-dash/dist/assets/markdown-C6mXtQxD.js +1 -0
- package/bizar-dash/dist/assets/markdown-C6mXtQxD.js.map +1 -0
- package/bizar-dash/dist/assets/mobile-D2pc-iNh.js +1 -0
- package/bizar-dash/dist/assets/{mobile-DD-FZrTC.js.map → mobile-D2pc-iNh.js.map} +1 -1
- package/bizar-dash/dist/assets/mobile-IaZ47uKC.js +2 -0
- package/bizar-dash/dist/assets/mobile-IaZ47uKC.js.map +1 -0
- package/bizar-dash/dist/assets/{react-vendor-DZRUXSPQ.js → react-vendor-Dn4wqh4Z.js} +8 -8
- package/bizar-dash/dist/assets/react-vendor-Dn4wqh4Z.js.map +1 -0
- package/bizar-dash/dist/assets/useSlashCommands-DjEwHl4n.js +2 -0
- package/bizar-dash/dist/assets/useSlashCommands-DjEwHl4n.js.map +1 -0
- package/bizar-dash/dist/assets/vendor-CeHGtduv.js +29 -0
- package/bizar-dash/dist/assets/vendor-CeHGtduv.js.map +1 -0
- package/bizar-dash/dist/index.html +7 -5
- package/bizar-dash/dist/mobile.html +4 -5
- package/bizar-dash/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/bizar-dash/src/server/eval.mjs +93 -3
- package/bizar-dash/src/server/metrics.mjs +75 -0
- package/bizar-dash/src/server/otel.mjs +141 -14
- package/bizar-dash/src/server/plugins/registry.mjs +145 -43
- package/bizar-dash/src/server/routes/chat.mjs +46 -9
- package/bizar-dash/src/server/routes/opencode-session-detail.mjs +120 -17
- package/bizar-dash/src/server/routes/plugins.mjs +123 -16
- package/bizar-dash/src/server/routes/workspaces.mjs +156 -61
- package/bizar-dash/src/server/server.mjs +44 -15
- package/bizar-dash/src/web/MobileApp.tsx +18 -10
- package/bizar-dash/src/web/mobile/MobileChat.tsx +138 -0
- package/bizar-dash/src/web/mobile/MobileSettings.tsx +196 -0
- package/bizar-dash/tests/bundle-analysis.test.mjs +5 -4
- package/bizar-dash/tests/eval/fixtures-extra.test.mjs +285 -0
- package/bizar-dash/tests/otel-spans.test.mjs +253 -0
- package/bizar-dash/tests/plugins-registry-fallback.test.mjs +259 -0
- package/package.json +1 -1
- package/templates/eval-fixtures/citation.json +16 -0
- package/templates/eval-fixtures/code-review.json +16 -0
- package/templates/eval-fixtures/concise-output.json +16 -0
- package/templates/eval-fixtures/context-window.json +16 -0
- package/templates/eval-fixtures/error-recovery.json +16 -0
- package/templates/eval-fixtures/json-output.json +20 -0
- package/templates/eval-fixtures/multi-language.json +16 -0
- package/templates/eval-fixtures/safe-paths.json +16 -0
- package/templates/eval-fixtures/tool-call-multi-step.json +18 -0
- package/templates/eval-fixtures/unicode-handling.json +16 -0
- package/bizar-dash/dist/assets/icons-Bo0iH9EC.js.map +0 -1
- package/bizar-dash/dist/assets/index-DmpSFPJY.js +0 -9
- package/bizar-dash/dist/assets/index-DmpSFPJY.js.map +0 -1
- package/bizar-dash/dist/assets/main-C1cpttnv.js.map +0 -1
- package/bizar-dash/dist/assets/markdown-DIquRulQ.js +0 -29
- package/bizar-dash/dist/assets/markdown-DIquRulQ.js.map +0 -1
- package/bizar-dash/dist/assets/mobile-BtxQJftK.js +0 -2
- package/bizar-dash/dist/assets/mobile-BtxQJftK.js.map +0 -1
- package/bizar-dash/dist/assets/mobile-DD-FZrTC.js +0 -1
- package/bizar-dash/dist/assets/react-vendor-DZRUXSPQ.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"mappings":"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","names":["cn","parts","formatRelative","ts","t","diff","formatTime","d","truncate","s","max","priorityColors","TOKEN_KEY","LOOPBACK_KEY","ApiError","message","status","data","__publicField","ApiClient","token","params","tok","newSearch","newUrl","path","url","sep","r","signal","body","method","headers","opts","msg","text","api","enhancePrompt","ModalContext","createContext","FOCUSABLE_SELECTOR","useModal","ctx","useContext","ModalProvider","children","stack","setStack","useState","close","useCallback","id","cur","idx","open","props","useEffect","handler","e","jsxs","createPortal","jsx","m","ModalShell","_a","modal","onClose","depth","modalRef","useRef","previousFocusRef","titleId","useId","getFocusableElements","root","el","onKeyDown","focusableElements","first","last","active","_b","_c","previous","X","Button","forwardRef","variant","size","iconOnly","loading","disabled","className","rest","ref","ToastContext","useToast","ICONS","Info","CheckCircle2","AlertCircle","AlertTriangle","ToastProvider","toasts","setToasts","idRef","timersRef","dismiss","timer","show","kind","durationMs","useMemo","ToastContainer","onDismiss","ToastItem","toast","Icon","buildWsUrl","base","Ws","socket","err","h","applyTheme","themeName","mode","resolved","applyThemeTokens","theme","hexToRgba","hex","alpha","n","g","b","Spinner","label","VirtualList","items","renderItem","itemHeight","height","overscan","scrollTop","setScrollTop","containerRef","visibleRange","start","end","totalHeight","offsetY","visibleItems","item","i","ChatTopBar","activeProject","sessionCount","sessionsOpen","infoOpen","onToggleSessions","onToggleInfo","onOpenOverview","Folder","ChevronDown","EmptyState","icon","title","action","LoadingSkeleton","count","_","SuggestionCards","projectName","onPickSuggestion","suggestions","Code","BookOpen","Lightbulb","ImageIcon","WelcomeScreen","displayName","MessageBubble","pinned","onCopy","onDelete","onTogglePin","onRegenerate","role","content","isUser","Copy","RefreshCw","Pin","Trash2","ReactMarkdown","remarkGfm","ChatThread","messages","sessionId","activeSource","innerRef","p","wrapped","stream","AgentChip","agent","setAgent","agents","setOpen","current","display","v","Bot","a","useAutoGrowTextarea","value","maxHeight","useLayoutEffect","next","Composer","model","setModel","setText","sending","onSend","attachments","setAttachments","onAttach","inputRef","fileInputRef","onFiles","files","names","Paperclip","j","enhanced","Sparkles","Send","useChat","snapshot","settings","initialTaskId","bizarMessages","setBizarMessages","sessions","setSessions","setSessionId","setLoading","busy","setBusy","opencodeSessions","setOpencodeSessions","opencodeMessages","setOpencodeMessages","setActiveSource","activeOpencodeSessionId","setActiveOpencodeSessionId","opencodeError","setOpencodeError","opencodeSuggestion","setOpencodeSuggestion","opencodeErrorInfo","setOpencodeErrorInfo","sessionStates","setSessionStates","stickToBottom","setStickToBottom","newMessageCount","setNewMessageCount","seenOpencodeMessages","setSeenOpencodeMessages","setPinned","listRef","toastRef","opencodeEsRef","sseReconnectAttemptRef","sseReconnectTimerRef","sseCurrentSessionIdRef","sseAutoReconnectRef","appendOpencodeMessage","markSeen","setSessionDisplayState","patch","prev","markSessionStreaming","streaming","setToast","loadChat","sid","loadTaskChat","taskId","refreshSessions","refreshOpencodeSessions","enriched","openSseForSession","connect","es","onEnvelope","evt","sidForState","payload","_d","parseData","raw","parsed","sessionID","messageID","type","inner","innerData","namedTypes","name","nextAttempt","delayMs","closeOpencodeSession","loadOpencodeSession","info","code","canRetry","selectBizarSession","onCreateSession","created","opencodeErr","_e","trimmed","optimisticId","optimistic","response","messageId","deleteMessage","togglePin","copyMessage","handleScroll","atBottom","jumpToLatest","renameSession","deleteSession","closed","__vitePreload","ws","BUILTIN_COMMANDS","useSlashCommands","query","setQuery","allCommands","q","c","MobileBottomNav","tabs","activeTab","onChange","MobileBottomSheet","actions","sheetRef","closeButtonRef","lastActiveRef","dragStartYRef","dragDeltaRef","scrollLockYRef","getFocusable","onKey","focusable","onPointerDown","onPointerMove","delta","endDrag","notifIcon","severity","formatNotifTime","MobileNotifications","onSelect","notifications","setNotifications","unread","setUnread","load","markAllRead","markRead","u","Fragment","Bell","CheckCheck","TAB_LABELS","MobileTopbar","onSearch","onNavigate","projectSheetOpen","setProjectSheetOpen","handleProjectSelect","projectId","notification","EVENT_KINDS","KIND_LABELS","MobileActivity","onRefresh","events","setEvents","filter","setFilter","paused","setPaused","detail","setDetail","submitText","setSubmitText","submitting","setSubmitting","loadEvents","filtered","_f","tasks","activeTasks","doneTasks","handleSubmit","handleRefresh","chip","Play","Pause","k","renderEventMessage","ActivityIcon","MobileModal","focusableSelector","CATEGORIES","CATEGORY_COLORS","MobileAgents","onBack","onOpenAgent","selectedAgent","setAgents","categoryFilter","setCategoryFilter","search","setSearch","detailAgent","setDetailAgent","newOpen","setNewOpen","invokePrompt","setInvokePrompt","invokeOpen","setInvokeOpen","ag","reload","invokeAgent","prompt","restartAgent","deleteAgent","createAgent","Plus","RotateCw","NewAgentModal","onCreate","setName","description","setDescription","setMode","color","setColor","category","setCategory","tags","setTags","setPrompt","SessionList","activeSessionId","creating","onSelectSession","onSelectOpencodeSession","view","setView","showAll","setShowAll","VISIBLE_LIMIT","allSorted","displayedSessions","totalOpencode","handleSelectSession","isOpencode","isActive","ExternalLink","FloatingComposer","InfoPanel","mcps","onRename","showAllAgents","setShowAllAgents","showCommands","setShowCommands","visibleAgents","visibleCommands","provider","MessageSquare","Pencil","Server","Terminal","MobileChat","setActiveTab","onClearTaskId","chat","setSessionsOpen","setInfoOpen","newNames","handleSend","handleCreateSession","handleDelete","handleDeleteSession","cmd","MobileConfig","entries","setEntries","editing","setEditing","saving","setSaving","key","detectType","handleChange","saveAll","resetRow","defaultValue","renderInput","entry","Save","Sliders","RotateCcw","MobileHistory","latestEventByProject","event","pid","project","latestEvent","formatDuration","ms","History","MobileMods","mods","setMods","statusFilter","setStatusFilter","detailMod","setDetailMod","toggleMod","enabled","Power","MobileMore","FileText","Clock","Settings","ChevronRight","ELEMENT_TYPES","elementColor","ElementIcon","CheckSquare","StickyNote","HelpCircle","MobileArtifactCanvas","slug","canvas","setCanvas","meta","setMeta","editMode","setEditMode","selectedEl","setSelectedEl","showComments","setShowComments","addElementOpen","setAddElementOpen","addCommentOpen","setAddCommentOpen","commentText","setCommentText","plan","addElement","maxX","acc","maxY","updateElement","deleteElement","addComment","elementId","ElementEditForm","AddElementModal","element","onSave","setType","setTitle","setContent","setStatus","onAdd","MobileArtifacts","onOpenArtifact","artifacts","setArtifacts","newSlug","setNewSlug","newTitle","setNewTitle","createPlan","result","TYPES","ACTIONS","TIMEZONES","HOURS","MINUTES","DOWS","INTERVAL_UNITS","MobileSchedules","schedules","setSchedules","detailSchedule","setDetailSchedule","editor","setEditor","toggleSchedule","deleteSchedule","triggerSchedule","humanizeSchedule","formatNextShort","formatRel","ScheduleEditor","initialEditorState","initial","parsedCron","parseSimpleCron","parsedInterval","parseSimpleInterval","toDatetimeLocal","composeScheduleValue","onSaved","state","setState","update","schedule","tz","iso","expr","dow","minNum","hourNum","pad","hourLabel","minute","SCOPES","SCOPE_ICONS","MobileSearchModal","scope","setScope","results","setResults","handleResultClick","grouped","Search","formatCountdown","QrCodePanel","pair","onStart","QRCodeSVG","setQRCodeSVG","remaining","setRemaining","expired","mod","tick","THEMES","Moon","Sun","Monitor","PRESET_ACCENTS","MobileSettings","setSettings","dirty","setDirty","setPair","pairing","setPairing","patchTheme","patchTop","startPair","res","Smartphone","MobileSkills","skills","setSkills","detailSkill","setDetailSkill","toggleSkill","STATUS_ORDER","STATUS_LABELS","NEXT_STATUS","hasTaskChat","task","metadata","hasTaskArtifacts","MobileTasks","selectedTaskId","onCloseDetail","onOpenChat","setTasks","activeStatus","setActiveStatus","detailTask","setDetailTask","newTaskOpen","setNewTaskOpen","artifactTaskId","setArtifactTaskId","artifactIds","setArtifactIds","artifactContent","setArtifactContent","artifactMeta","setArtifactMeta","artifactLoading","setArtifactLoading","assigneeFilter","setAssigneeFilter","sortBy","setSortBy","moveTask","newStatus","deleteTask","archiveTask","sorted","targetAssignee","settingsDefaultAgent","order","createTask","assignee","priority","openTaskArtifact","ids","Archive","NewTaskModal","setAssignee","setPriority","MAIN_TABS","Activity","SettingsIcon","Grid","parseNotificationTarget","agentName","link","MobileApp","searchOpen","setSearchOpen","pendingChatTaskId","setPendingChatTaskId","setSnapshot","bootError","setBootError","wsEpoch","setWsEpoch","currentView","pushView","popView","goToTab","refreshSnapshot","cancelled","snap","settingsResponse","nextSettings","mql","reconnect","onVisibilityChange","offMessage","nextTask","exists","handleTabChange","handleNavigate","target","renderView","stackedView"],"ignoreList":[],"sources":["../../src/web/lib/utils.ts","../../src/web/lib/api.ts","../../src/web/components/Modal.tsx","../../src/web/components/Button.tsx","../../src/web/components/Toast.tsx","../../src/web/lib/ws.ts","../../src/web/lib/types.ts","../../src/web/components/Spinner.tsx","../../src/web/components/VirtualList.tsx","../../src/web/components/chat/ChatTopBar.tsx","../../src/web/components/chat/EmptyState.tsx","../../src/web/components/chat/LoadingSkeleton.tsx","../../src/web/components/chat/SuggestionCards.tsx","../../src/web/components/chat/WelcomeScreen.tsx","../../src/web/components/chat/MessageBubble.tsx","../../src/web/components/chat/ChatThread.tsx","../../src/web/components/chat/AgentChip.tsx","../../src/web/components/chat/useAutoGrowTextarea.ts","../../src/web/components/chat/Composer.tsx","../../src/web/components/chat/useChat.ts","../../src/web/components/chat/useSlashCommands.ts","../../src/web/mobile/MobileBottomNav.tsx","../../src/web/mobile/components/MobileBottomSheet.tsx","../../src/web/mobile/views/MobileNotifications.tsx","../../src/web/mobile/MobileTopbar.tsx","../../src/web/mobile/views/MobileActivity.tsx","../../src/web/mobile/components/MobileModal.tsx","../../src/web/mobile/views/MobileAgents.tsx","../../src/web/components/chat/SessionList.tsx","../../src/web/components/chat/FloatingComposer.tsx","../../src/web/components/chat/InfoPanel.tsx","../../src/web/mobile/views/MobileChat.tsx","../../src/web/mobile/views/MobileConfig.tsx","../../src/web/mobile/views/MobileHistory.tsx","../../src/web/mobile/views/MobileMods.tsx","../../src/web/mobile/views/MobileMore.tsx","../../src/web/mobile/views/MobileArtifactCanvas.tsx","../../src/web/mobile/views/MobileArtifacts.tsx","../../src/web/mobile/views/MobileSchedules.tsx","../../src/web/mobile/views/MobileSearchModal.tsx","../../src/web/mobile/views/QrCodePanel.tsx","../../src/web/mobile/views/MobileSettings.tsx","../../src/web/mobile/views/MobileSkills.tsx","../../src/web/mobile/views/MobileTasks.tsx","../../src/web/MobileApp.tsx"],"sourcesContent":["// src/lib/utils.ts — tiny shared helpers.\n\nexport function cn(...parts: (string | false | null | undefined)[]): string {\n return parts.filter(Boolean).join(' ');\n}\n\n/** Compact relative time. */\nexport function formatRelative(ts: number | string | Date | undefined | null): string {\n if (!ts && ts !== 0) return '';\n const t = typeof ts === 'number' ? ts : new Date(ts).getTime();\n if (Number.isNaN(t)) return '';\n const diff = Date.now() - t;\n if (diff < 0) return 'just now';\n if (diff < 60_000) return 'just now';\n if (diff < 3_600_000) return `${Math.floor(diff / 60_000)}m ago`;\n if (diff < 86_400_000) return `${Math.floor(diff / 3_600_000)}h ago`;\n if (diff < 7 * 86_400_000) return `${Math.floor(diff / 86_400_000)}d ago`;\n return new Date(t).toLocaleDateString();\n}\n\n/** Full localized timestamp. */\nexport function formatTime(ts: number | string | Date | undefined | null): string {\n if (!ts) return '';\n const d = typeof ts === 'number' ? new Date(ts) : new Date(ts);\n if (Number.isNaN(d.getTime())) return '';\n return d.toLocaleString();\n}\n\n/** Trailing-suffix truncation. */\nexport function truncate(s: string | undefined | null, max = 160): string {\n if (!s) return '';\n return s.length > max ? s.slice(0, max) + '…' : s;\n}\n\n/** Debounce. */\nexport function debounce<T extends (...args: never[]) => void>(\n fn: T,\n ms = 200,\n): (...args: Parameters<T>) => void {\n let timer: ReturnType<typeof setTimeout> | null = null;\n return (...args: Parameters<T>) => {\n if (timer) clearTimeout(timer);\n timer = setTimeout(() => fn(...args), ms);\n };\n}\n\n/** Color helpers (status badges). */\nexport const priorityColors: Record<string, string> = {\n low: 'var(--text-dim)',\n normal: 'var(--info)',\n high: 'var(--error)',\n};\n\nexport const statusBadgeKind = (\n status: string,\n): 'success' | 'warning' | 'error' | 'info' | 'accent' | '' => {\n switch (status) {\n case 'approved':\n case 'done':\n return 'success';\n case 'draft':\n case 'queued':\n return '';\n case 'in-progress':\n case 'doing':\n return 'info';\n case 'rejected':\n return 'error';\n default:\n return 'accent';\n }\n};\n\n/** Cheap hash for change-detection on text (e.g. raw JSON). */\nexport function hashText(s: string): string {\n let h = 5381;\n for (let i = 0; i < s.length; i++) h = ((h << 5) + h) ^ s.charCodeAt(i);\n return (h >>> 0).toString(36);\n}\n\n/**\n * v3.7.1 — Auto-generate a task title from a prompt body when the user\n * didn't provide one. Strips markdown headers, takes the first non-empty\n * line, truncates to ~60 chars at a word boundary. Falls back to a\n * timestamped placeholder if there's nothing to summarize.\n */\nexport function autoTitleFromContent(\n body: string | undefined | null,\n fallback = true,\n): string {\n const raw = (body || '').replace(/\\r\\n?/g, '\\n').trim();\n if (!raw) {\n if (!fallback) return '';\n const now = new Date();\n const pad = (n: number) => String(n).padStart(2, '0');\n return `Untitled task — ${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}`;\n }\n // Take first non-empty line; strip leading markdown heading hashes + quotes.\n const firstLine = raw\n .split('\\n')\n .map((l) => l.replace(/^[\\s>#*\\-]+/, '').trim())\n .find((l) => l.length > 0) || '';\n if (!firstLine) {\n if (!fallback) return '';\n const now = new Date();\n const pad = (n: number) => String(n).padStart(2, '0');\n return `Untitled task — ${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}`;\n }\n if (firstLine.length <= 60) return firstLine;\n // Truncate at last word boundary within 60 chars.\n const cut = firstLine.slice(0, 60);\n const lastSpace = cut.lastIndexOf(' ');\n return (lastSpace > 20 ? cut.slice(0, lastSpace) : cut).trimEnd() + '…';\n}\n","// src/lib/api.ts — REST client. Single instance, type-safe wrappers.\n//\n// v3.6.2 — Token URL pickup for reverse-proxy / Tailscale Serve access.\n// When the dashboard is accessed via Tailscale Serve (or any reverse\n// proxy) the browser runs on a different origin than localhost. The server\n// sees the remote client through the local proxy and requires auth.\n// The `?token=…` URL param lets operators share a direct link that\n// automatically installs the token into localStorage so the dashboard\n// \"just works\" after the first paste — no separate localhost tab needed.\n//\n// v3.6.1 — Authentication is now loopback-aware on the server side.\n// The browser makes API calls over 127.0.0.1 (or via Tailscale serve\n// proxying to 127.0.0.1), and the server auto-trusts loopback\n// connections. So a fresh browser tab \"just works\" — no token paste\n// required for the normal case.\n//\n// The token still exists for non-loopback clients (external API\n// consumers, scripts) and as a defense-in-depth option via the\n// BIZAR_DASHBOARD_REQUIRE_AUTH=1 env var. The localStorage flow\n// remains: probe /api/auth/status on load, send the token header\n// if one is set, fall back to none if not (the server will trust\n// us anyway because we're loopback).\n//\n// For SSE routes, the token is also added as a `?token=...` query\n// parameter (browsers can't set custom headers on EventSource).\n// Again, loopback makes this optional but kept for safety.\n\nconst TOKEN_KEY = 'bizar-auth-token';\nconst LOOPBACK_KEY = 'bizar-loopback-trusted';\n\nexport class ApiError extends Error {\n status: number;\n data: unknown;\n constructor(message: string, status: number, data?: unknown) {\n super(message);\n this.name = 'ApiError';\n this.status = status;\n this.data = data;\n }\n}\n\nclass ApiClient {\n base = '/api';\n\n /** Get the cached bearer token from localStorage, or '' if none. */\n getToken(): string {\n try {\n return localStorage.getItem(TOKEN_KEY) || '';\n } catch {\n return '';\n }\n }\n\n /** Persist the token. Called from the Settings tab after Reveal/Regenerate. */\n setToken(token: string): void {\n try {\n if (token) localStorage.setItem(TOKEN_KEY, token);\n else localStorage.removeItem(TOKEN_KEY);\n } catch {\n /* localStorage unavailable — token won't persist across reloads */\n }\n }\n\n /**\n * v3.6.2 — Check the URL search params for a `token` value and\n * persist it into localStorage if found. Strips the param from the\n * URL via `history.replaceState` so the token doesn't leak into\n * browser history or shared links.\n *\n * Returns `true` if a token was found and saved, `false` otherwise.\n * Safe to call multiple times (subsequent calls with no token in URL\n * return `false` and are no-ops).\n */\n pickupTokenFromUrl(): boolean {\n try {\n const params = new URLSearchParams(window.location.search);\n const tok = params.get('token');\n if (!tok) return false;\n this.setToken(tok);\n // Remove the token param from the URL without a page reload.\n params.delete('token');\n const newSearch = params.toString();\n const newUrl = newSearch\n ? `${window.location.pathname}?${newSearch}`\n : window.location.pathname;\n history.replaceState(null, '', newUrl);\n return true;\n } catch {\n return false;\n }\n }\n\n /** Build the URL with the token appended as ?token=… if present. */\n urlWithToken(path: string): string {\n const url = this.base + path;\n const tok = this.getToken();\n if (!tok) return url;\n const sep = url.includes('?') ? '&' : '?';\n return `${url}${sep}token=${encodeURIComponent(tok)}`;\n }\n\n /**\n * v3.6.1 — Probe the server's /api/auth/status endpoint and cache\n * whether the current browser is on a loopback-trusted connection.\n * Called once on app mount so the rest of the client can decide\n * whether to surface the \"paste your token\" UI in Settings or\n * just let the dashboard work.\n *\n * Returns a snapshot of the status; result is also cached for\n * subsequent calls to isLoopbackTrusted().\n */\n async probeAuthStatus(): Promise<{ required: boolean; loopback: boolean; peer: string }> {\n try {\n const r = await this.get<{ required: boolean; loopback: boolean; peer: string }>('/auth/status');\n try { localStorage.setItem(LOOPBACK_KEY, r.loopback ? '1' : '0'); } catch { /* noop */ }\n return r;\n } catch {\n return { required: false, loopback: true, peer: '' };\n }\n }\n\n /** Cached result of the last /api/auth/status probe. */\n isLoopbackTrusted(): boolean {\n try { return localStorage.getItem(LOOPBACK_KEY) !== '0'; } catch { return true; }\n }\n\n /**\n * Perform an authenticated GET against the dashboard server.\n *\n * IMPORTANT: do NOT include the `/api` prefix in `path` — the\n * wrapper adds it for you. Calling `api.get('/api/projects')` will\n * 404 because the server sees `/api/api/projects`. Use the\n * unprefixed form: `api.get('/projects')`.\n *\n * @example\n * const r = await api.get<ProjectList>('/projects');\n */\n async get<T>(path: string, signal?: AbortSignal): Promise<T> {\n return this.req<T>('GET', path, undefined, signal);\n }\n\n /**\n * Perform an authenticated POST against the dashboard server.\n *\n * IMPORTANT: do NOT include the `/api` prefix in `path` — the\n * wrapper adds it. Use the unprefixed form: `api.post('/fs/mkdir', body)`.\n */\n async post<T>(path: string, body?: unknown, signal?: AbortSignal): Promise<T> {\n return this.req<T>('POST', path, body, signal);\n }\n\n /**\n * Perform an authenticated PUT against the dashboard server.\n *\n * IMPORTANT: do NOT include the `/api` prefix in `path` — the\n * wrapper adds it. Use the unprefixed form: `api.put('/settings', body)`.\n */\n async put<T>(path: string, body?: unknown, signal?: AbortSignal): Promise<T> {\n return this.req<T>('PUT', path, body, signal);\n }\n\n /**\n * Perform an authenticated PATCH against the dashboard server.\n *\n * IMPORTANT: do NOT include the `/api` prefix in `path` — the\n * wrapper adds it. Use the unprefixed form: `api.patch('/settings', body)`.\n */\n async patch<T>(path: string, body?: unknown, signal?: AbortSignal): Promise<T> {\n return this.req<T>('PATCH', path, body, signal);\n }\n\n /**\n * Perform an authenticated DELETE against the dashboard server.\n *\n * IMPORTANT: do NOT include the `/api` prefix in `path` — the\n * wrapper adds it. Use the unprefixed form: `api.del('/projects/123')`.\n */\n async del<T = unknown>(path: string, signal?: AbortSignal): Promise<T> {\n return this.req<T>('DELETE', path, undefined, signal);\n }\n\n private async req<T>(method: string, path: string, body?: unknown, signal?: AbortSignal): Promise<T> {\n const headers: Record<string, string> = { 'Content-Type': 'application/json' };\n const tok = this.getToken();\n if (tok) headers.Authorization = `Bearer ${tok}`;\n const opts: RequestInit = { method, headers };\n if (body !== undefined && body !== null) {\n opts.body = typeof body === 'string' ? body : JSON.stringify(body);\n }\n if (signal) opts.signal = signal;\n const r = await fetch(this.base + path, opts);\n const ct = r.headers.get('content-type') || '';\n if (ct.includes('application/json')) {\n const data = await r.json();\n if (!r.ok) {\n const msg =\n (data && typeof data === 'object' && 'message' in data\n ? (data as { message?: string }).message\n : undefined) || `${method} ${path}: ${r.status}`;\n throw new ApiError(msg, r.status, data);\n }\n return data as T;\n }\n const text = await r.text();\n if (!r.ok) {\n throw new ApiError(\n `${method} ${path}: ${r.status} — ${text.slice(0, 200)}`,\n r.status,\n text,\n );\n }\n try {\n return JSON.parse(text) as T;\n } catch {\n return text as T;\n }\n }\n}\n\nexport const api = new ApiClient();\n\n// v3.6.2 — Auto-pickup token from URL on module load so the dashboard\n// works through reverse proxies (Tailscale Serve, nginx, etc.) without\n// needing a separate localhost tab.\napi.pickupTokenFromUrl();\n\nexport { ApiClient, TOKEN_KEY };\n\n/**\n * Placeholder for the \"Enhance prompt\" feature.\n * TODO: call system LLM API to improve the prompt\n * Will use settings.systemLlm config\n * Endpoint: POST /api/enhance-prompt with { text }\n */\nexport async function enhancePrompt(text: string): Promise<string> {\n return text; // passthrough for now\n}\n","// src/components/Modal.tsx — modal context + provider + portal.\n\nimport {\n createContext,\n useId,\n useCallback,\n useContext,\n useEffect,\n useRef,\n useState,\n type ReactNode,\n} from 'react';\nimport { createPortal } from 'react-dom';\nimport { X } from 'lucide-react';\n\nexport type ModalProps = {\n title?: string;\n children: ReactNode;\n footer?: ReactNode;\n onClose?: () => void;\n width?: number;\n};\n\nexport type ModalApi = {\n open: (props: ModalProps) => string;\n close: (id?: string) => void;\n isModalOpen: boolean;\n};\n\nconst ModalContext = createContext<ModalApi | null>(null);\n\nconst FOCUSABLE_SELECTOR = [\n 'button:not([disabled])',\n '[href]',\n 'input:not([disabled])',\n 'select:not([disabled])',\n 'textarea:not([disabled])',\n '[tabindex]:not([tabindex=\"-1\"])',\n].join(', ');\n\nexport function useModal(): ModalApi {\n const ctx = useContext(ModalContext);\n if (!ctx) {\n return { open: () => '', close: () => undefined, isModalOpen: false };\n }\n return ctx;\n}\n\ntype OpenModal = ModalProps & { id: string };\n\nexport function ModalProvider({ children }: { children: ReactNode }) {\n const [stack, setStack] = useState<OpenModal[]>([]);\n\n const close = useCallback((id?: string) => {\n setStack((cur) => {\n if (!id) return cur.slice(0, -1);\n const idx = cur.findIndex((m) => m.id === id);\n if (idx === -1) return cur;\n // close only the top-most matching\n if (idx !== cur.length - 1) return cur;\n return cur.slice(0, -1);\n });\n }, []);\n\n const open = useCallback((props: ModalProps) => {\n const id = `m${Math.random().toString(36).slice(2, 9)}`;\n setStack((cur) => [...cur, { ...props, id }]);\n return id;\n }, []);\n\n // Escape key — close the topmost\n useEffect(() => {\n if (stack.length === 0) return;\n const handler = (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n e.stopPropagation();\n close();\n }\n };\n document.addEventListener('keydown', handler);\n return () => document.removeEventListener('keydown', handler);\n }, [stack.length, close]);\n\n return (\n <ModalContext.Provider value={{ open, close, isModalOpen: stack.length > 0 }}>\n {children}\n {typeof document !== 'undefined' &&\n createPortal(\n <div className=\"modal-stack\" aria-hidden={stack.length === 0}>\n {stack.map((m, idx) => (\n <ModalShell\n key={m.id}\n modal={m}\n onClose={() => {\n m.onClose?.();\n close(m.id);\n }}\n depth={idx}\n />\n ))}\n </div>,\n document.body,\n )}\n </ModalContext.Provider>\n );\n}\n\nfunction ModalShell({\n modal,\n onClose,\n depth,\n}: {\n modal: OpenModal;\n onClose: () => void;\n depth: number;\n}) {\n const modalRef = useRef<HTMLDivElement>(null);\n const previousFocusRef = useRef<HTMLElement | null>(null);\n const titleId = useId();\n\n const getFocusableElements = (): HTMLElement[] => {\n const root = modalRef.current;\n if (!root) return [];\n return Array.from(root.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR)).filter(\n (el) => !el.hasAttribute('disabled') && el.tabIndex !== -1,\n );\n };\n\n useEffect(() => {\n previousFocusRef.current = document.activeElement instanceof HTMLElement\n ? document.activeElement\n : null;\n const focusable = getFocusableElements();\n (focusable[0] ?? modalRef.current)?.focus();\n\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.key !== 'Tab') return;\n const focusableElements = getFocusableElements();\n if (focusableElements.length === 0) {\n e.preventDefault();\n modalRef.current?.focus();\n return;\n }\n\n const first = focusableElements[0];\n const last = focusableElements[focusableElements.length - 1];\n const active = document.activeElement instanceof HTMLElement ? document.activeElement : null;\n\n if (e.shiftKey) {\n if (!active || active === first || !modalRef.current?.contains(active)) {\n e.preventDefault();\n last.focus();\n }\n return;\n }\n\n if (!active || active === last || !modalRef.current?.contains(active)) {\n e.preventDefault();\n first.focus();\n }\n };\n\n document.addEventListener('keydown', onKeyDown);\n return () => {\n document.removeEventListener('keydown', onKeyDown);\n const previous = previousFocusRef.current;\n if (previous && previous.isConnected) previous.focus();\n };\n }, []);\n\n // v3.3.0 — Backdrop click stops propagation. This stops a click on\n // the dim area from bubbling to the document/App level where it\n // could otherwise be misread by the digit-key shortcut handler\n // (the click that closes the modal would otherwise leave focus on\n // <body>, and any subsequent accidental digit press would fire\n // setActiveTab(\"overview\")). The click itself only fires onClose()\n // when the user actually clicked the backdrop, not a child.\n return (\n <div\n className=\"modal-backdrop\"\n onClick={(e) => {\n e.stopPropagation();\n if (e.target === e.currentTarget) onClose();\n }}\n style={depth > 0 ? { background: 'rgba(0,0,0,0.4)' } : undefined}\n >\n <div\n ref={modalRef}\n className=\"modal\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={modal.title ? titleId : undefined}\n tabIndex={-1}\n onClick={(e) => e.stopPropagation()}\n style={modal.width ? { maxWidth: modal.width } : undefined}\n >\n {modal.title && (\n <header className=\"modal-header\">\n <h2 id={titleId} className=\"modal-title\">{modal.title}</h2>\n <button\n type=\"button\"\n className=\"icon-btn\"\n aria-label=\"Close\"\n onClick={onClose}\n >\n <X size={16} />\n </button>\n </header>\n )}\n <div className=\"modal-body\">{modal.children}</div>\n {modal.footer && <footer className=\"modal-footer\">{modal.footer}</footer>}\n </div>\n </div>\n );\n}\n","// src/components/Button.tsx — typed button with variants + sizes.\n\nimport { type ButtonHTMLAttributes, forwardRef } from 'react';\nimport { cn } from '../lib/utils';\n\nexport type ButtonVariant =\n | 'primary'\n | 'secondary'\n | 'ghost'\n | 'danger'\n | 'accent'\n | 'success';\nexport type ButtonSize = 'sm' | 'md' | 'lg';\n\nexport type ButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & {\n variant?: ButtonVariant;\n size?: ButtonSize;\n iconOnly?: boolean;\n loading?: boolean;\n};\n\nexport const Button = forwardRef<HTMLButtonElement, ButtonProps>(\n function Button(\n {\n variant = 'secondary',\n size = 'md',\n iconOnly = false,\n loading = false,\n disabled,\n className,\n children,\n ...rest\n },\n ref,\n ) {\n return (\n <button\n ref={ref}\n type={rest.type ?? 'button'}\n disabled={disabled || loading}\n className={cn(\n 'btn',\n `btn-${variant}`,\n `btn-size-${size}`,\n iconOnly && 'btn-icon',\n loading && 'btn-loading',\n className,\n )}\n {...rest}\n >\n {loading ? <span className=\"btn-spinner\" aria-hidden /> : null}\n {children}\n </button>\n );\n },\n);\n","// src/components/Toast.tsx — toast context, provider hook, container.\n\nimport {\n createContext,\n useEffect,\n useCallback,\n useContext,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport { CheckCircle2, AlertTriangle, AlertCircle, Info, X } from 'lucide-react';\n\nexport type ToastKind = 'info' | 'success' | 'error' | 'warning';\n\nexport type Toast = {\n id: number;\n kind: ToastKind;\n message: string;\n};\n\nexport type ToastApi = {\n show: (message: string, kind?: ToastKind, durationMs?: number) => void;\n info: (message: string, durationMs?: number) => void;\n success: (message: string, durationMs?: number) => void;\n error: (message: string, durationMs?: number) => void;\n warning: (message: string, durationMs?: number) => void;\n dismiss: (id: number) => void;\n toasts: Toast[];\n};\n\nconst ToastContext = createContext<ToastApi | null>(null);\n\nexport function useToast(): ToastApi {\n const ctx = useContext(ToastContext);\n if (!ctx) {\n // Fallback no-op so tests/Storybook don't crash\n return {\n show: () => undefined,\n info: () => undefined,\n success: () => undefined,\n error: () => undefined,\n warning: () => undefined,\n dismiss: () => undefined,\n toasts: [],\n };\n }\n return ctx;\n}\n\nconst ICONS: Record<ToastKind, typeof Info> = {\n info: Info,\n success: CheckCircle2,\n error: AlertCircle,\n warning: AlertTriangle,\n};\n\nexport function ToastProvider({ children }: { children: React.ReactNode }) {\n const [toasts, setToasts] = useState<Toast[]>([]);\n const idRef = useRef(1);\n const timersRef = useRef<Map<number, ReturnType<typeof setTimeout>>>(new Map());\n\n const dismiss = useCallback((id: number) => {\n setToasts((cur) => cur.filter((t) => t.id !== id));\n const timer = timersRef.current.get(id);\n if (timer) {\n clearTimeout(timer);\n timersRef.current.delete(id);\n }\n }, []);\n\n const show = useCallback<ToastApi['show']>(\n (message, kind = 'info', durationMs = 4000) => {\n const id = idRef.current++;\n setToasts((cur) => [...cur, { id, kind, message }]);\n if (durationMs > 0) {\n const timer = setTimeout(() => dismiss(id), durationMs);\n timersRef.current.set(id, timer);\n }\n },\n [dismiss],\n );\n\n useEffect(() => {\n return () => {\n for (const timer of timersRef.current.values()) clearTimeout(timer);\n timersRef.current.clear();\n };\n }, []);\n\n const api = useMemo<ToastApi>(\n () => ({\n show,\n info: (m, d) => show(m, 'info', d),\n success: (m, d) => show(m, 'success', d),\n error: (m, d) => show(m, 'error', d),\n warning: (m, d) => show(m, 'warning', d),\n dismiss,\n toasts,\n }),\n [show, dismiss, toasts],\n );\n\n return (\n <ToastContext.Provider value={api}>\n {children}\n <ToastContainer toasts={toasts} onDismiss={dismiss} />\n </ToastContext.Provider>\n );\n}\n\nexport function ToastContainer({\n toasts,\n onDismiss,\n}: {\n toasts: Toast[];\n onDismiss: (id: number) => void;\n}) {\n return (\n <div className=\"toast-stack\" aria-live=\"polite\">\n {toasts.map((t) => (\n <ToastItem key={t.id} toast={t} onDismiss={onDismiss} />\n ))}\n </div>\n );\n}\n\nfunction ToastItem({\n toast,\n onDismiss,\n}: {\n toast: Toast;\n onDismiss: (id: number) => void;\n}) {\n const Icon = ICONS[toast.kind];\n return (\n <div className={`toast toast-${toast.kind}`} role=\"alert\" aria-live=\"assertive\" aria-atomic=\"true\">\n <Icon size={16} className=\"toast-icon\" />\n <span className=\"toast-message\">{toast.message}</span>\n <button\n type=\"button\"\n className=\"toast-close\"\n aria-label=\"Dismiss\"\n onClick={() => onDismiss(toast.id)}\n >\n <X size={14} />\n </button>\n </div>\n );\n}\n","// src/lib/ws.ts — WebSocket with auto-reconnect + status + handlers.\n//\n// v3.6.0 — Bearer-token authentication. Browsers cannot set the\n// Authorization header on the native WebSocket constructor, so we\n// append the token as a `?token=<token>` query parameter on the\n// connection URL. The server (auth.mjs) accepts both forms.\nimport type { WsMessage, WsStatus } from './types';\nimport { TOKEN_KEY } from './api';\n\ntype Handler = (msg: WsMessage) => void;\ntype StatusHandler = (status: WsStatus) => void;\n\n/**\n * Build the WS URL with the auth token query param appended.\n * Returns just the bare /ws URL if no token is set (the server\n * will 401 and the connection will fail fast — the UI surfaces\n * \"auth required\" in that case).\n */\nfunction buildWsUrl(): string {\n if (typeof location === 'undefined') return 'ws://localhost/ws';\n const base = `${location.protocol === 'https:' ? 'wss' : 'ws'}://${location.host}/ws`;\n let tok = '';\n try {\n tok = localStorage.getItem(TOKEN_KEY) || '';\n } catch {\n /* localStorage unavailable */\n }\n if (!tok) return base;\n return `${base}?token=${encodeURIComponent(tok)}`;\n}\n\nexport class Ws {\n private readonly urlOverride?: string;\n private handlers = new Set<Handler>();\n private statusHandlers = new Set<StatusHandler>();\n private ws: WebSocket | null = null;\n private reconnectDelay = 1000;\n private readonly maxReconnectDelay = 15000;\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n private _status: WsStatus = 'connecting';\n private pingTimer: ReturnType<typeof setInterval> | null = null;\n private closed = false;\n\n constructor(url?: string) {\n this.urlOverride = url;\n this.connect();\n }\n\n on(handler: Handler): () => void {\n this.handlers.add(handler);\n return () => {\n this.handlers.delete(handler);\n };\n }\n\n onStatus(handler: StatusHandler): () => void {\n this.statusHandlers.add(handler);\n // Fire current status immediately\n handler(this._status);\n return () => {\n this.statusHandlers.delete(handler);\n };\n }\n\n get status(): WsStatus {\n return this._status;\n }\n\n send(msg: unknown): boolean {\n if (this.ws && this.ws.readyState === WebSocket.OPEN) {\n this.ws.send(JSON.stringify(msg));\n return true;\n }\n return false;\n }\n\n close(): void {\n this.closed = true;\n if (this.reconnectTimer) clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n if (this.pingTimer) clearInterval(this.pingTimer);\n this.pingTimer = null;\n try {\n this.ws?.close();\n } catch {\n /* ignore */\n }\n this.ws = null;\n this.setStatus('disconnected');\n }\n\n private connect(): void {\n if (this.closed) return;\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n this.setStatus('connecting');\n const url = this.urlOverride ?? buildWsUrl();\n let socket: WebSocket;\n try {\n // Re-resolve the URL on every (re)connect — the token may have\n // changed since the last attempt (e.g. after Regenerate).\n socket = new WebSocket(url);\n this.ws = socket;\n } catch (err) {\n console.warn('[ws] construct failed:', err);\n this.scheduleReconnect();\n return;\n }\n socket.addEventListener('open', () => {\n if (this.ws !== socket || this.closed) return;\n this.reconnectDelay = 1000;\n this.setStatus('connected');\n // Keep-alive ping every 30s\n if (this.pingTimer) clearInterval(this.pingTimer);\n this.pingTimer = setInterval(() => {\n if (this._status === 'connected') this.send({ type: 'ping' });\n }, 30_000);\n });\n socket.addEventListener('close', () => {\n if (this.ws !== socket) return;\n this.ws = null;\n this.setStatus('disconnected');\n if (this.pingTimer) {\n clearInterval(this.pingTimer);\n this.pingTimer = null;\n }\n if (!this.closed) this.scheduleReconnect();\n });\n socket.addEventListener('error', () => {\n if (this.ws !== socket) return;\n // 'close' will follow — keep this for logs\n console.warn('[ws] error');\n });\n socket.addEventListener('message', (e) => {\n let msg: WsMessage;\n try {\n msg = JSON.parse(e.data);\n } catch {\n console.warn('[ws] bad message');\n return;\n }\n for (const h of this.handlers) {\n try {\n h(msg);\n } catch (err) {\n console.error('[ws] handler error:', err);\n }\n }\n });\n }\n\n private scheduleReconnect(): void {\n if (this.closed || this.reconnectTimer) return;\n this.reconnectTimer = setTimeout(() => {\n this.reconnectTimer = null;\n this.connect();\n }, this.reconnectDelay);\n this.reconnectDelay = Math.min(\n this.reconnectDelay * 1.6,\n this.maxReconnectDelay,\n );\n }\n\n private setStatus(s: WsStatus): void {\n this._status = s;\n for (const h of this.statusHandlers) {\n try {\n h(s);\n } catch (err) {\n console.error('[ws] status handler error:', err);\n }\n }\n }\n}\n","// src/lib/types.ts — TypeScript types for the Bizar dashboard.\n// All shapes here mirror the JSON returned by src/server/api.mjs.\n\n/**\n * Dialog component types that can be rendered in the dashboard.\n * Aligned with `DialogComponent` in plugins/bizar/src/commands.ts.\n * If you add a new component here, mirror it there and in\n * dialog-store.mjs KNOWN_COMPONENTS.\n */\nexport type DialogComponent = 'visual-artifact' | 'visual-plan' | 'plan-create' | 'plan-list' | 'artifact-create' | 'artifact-list' | 'help' | 'audit' | 'generic';\n\n/**\n * A modal dialog request emitted by the slash-command plugin and\n * delivered to the dashboard via the `dialog:show` WS message.\n * Thor's plugin code emits these; the desktop Chat view (sibling scope)\n * mounts a component keyed by `component` and seeds it with `data`.\n */\nexport type DialogDescriptor = {\n id: string;\n title: string;\n /** Original slash command, e.g. \"/visual-plan on\". */\n command: string;\n /** Component key the Chat view mounts. One of: 'visual-plan' | 'plan-create' | 'plan-list' | 'help' | 'audit' | 'generic' */\n component: DialogComponent;\n /** Component-specific payload. Shape depends on `component`. */\n data?: Record<string, unknown>;\n};\n\nexport type ThemeName = 'dark' | 'light' | 'system';\n\nexport type ThemeSettings = {\n mode: ThemeName;\n accent: string;\n success: string;\n warning: string;\n error: string;\n info: string;\n fontFamily: string;\n fontSize: number;\n compactMode: boolean;\n animations: boolean;\n};\n\nexport type UiSettings = {\n layout: 'topnav' | 'sidebar' | 'both';\n showHeader: boolean;\n showStatusBar: boolean;\n defaultTab: string;\n accentColor?: string;\n};\n\nexport type Agent = {\n name: string;\n description: string;\n model: string;\n mode: string;\n file: string;\n path: string;\n mtime: number;\n tools?: string[];\n tags?: string[];\n category?: string;\n color?: string;\n prompt?: string;\n permissions?: unknown;\n // v3.1.0 — runtime status (server-attached)\n status?: 'idle' | 'working' | 'error' | 'stuck' | string;\n currentTaskId?: string | null;\n currentTaskStartedAt?: number;\n lastSeen?: number;\n heartbeat?: number;\n lastError?: { ts: number; message: string } | null;\n lastTask?: { id: string; finishedAt: number; status: string } | null;\n successRate?: number;\n tasksTotal?: number;\n tasksSucceeded?: number;\n tasksFailed?: number;\n isStuck?: boolean;\n // v3.2.0 — agent hierarchy (parent/level/role).\n level?: number;\n parent?: string | null;\n role?: string;\n};\n\nexport type ProjectRecord = {\n id: string;\n name: string;\n path: string;\n lastAccessed?: string;\n status: 'active' | 'inactive' | 'error' | string;\n summary?: string;\n};\n\nexport type ProjectsResponse = {\n projects: ProjectRecord[];\n active: string | null;\n};\n\n/** A single entry returned by GET /api/fs */\nexport type DirectoryEntry = {\n name: string;\n path: string;\n isDir: boolean;\n isSymlink: boolean;\n};\n\n/** Response shape for GET /api/fs */\nexport type DirectoryListing = {\n path: string;\n parent: string | null;\n entries: DirectoryEntry[];\n /** True when the directory had more than 500 entries (capped). */\n truncated?: boolean;\n /** Actual entry count when truncated. */\n totalEntries?: number;\n};\n\n/** Request shape for POST /api/fs/mkdir */\nexport type MkdirRequest = { parent: string; name: string };\n\n/** Response shape for POST /api/fs/mkdir */\nexport type MkdirResponse = { path: string; parent: string; name: string };\n\n/** Response shape for POST /api/projects/scan */\nexport type ScanResult = {\n added: Array<{ id: string; path: string; name: string }>;\n skipped: number;\n scanned: number;\n error?: string;\n};\n\n// v3.19.0 — Artifact (formerly Plan). A user-facing proposal that an agent\n// creates to surface design choices, options, or implementation paths.\n// The user reviews it and either approves, rejects, or modifies.\nexport type Artifact = {\n /** Unique slug — used in the file path (`plans/<slug>/`) and URL. */\n slug: string;\n /** Human-readable title shown in the artifacts list. */\n title: string;\n /** One-line summary shown under the title. */\n description: string;\n /** Lifecycle status. */\n status: 'draft' | 'pending-review' | 'approved' | 'rejected' | 'archived';\n /** What kind of artifact this is — drives the renderer. */\n kind: 'design' | 'plan' | 'spec' | 'mockup' | 'report' | 'freeform' | string;\n /** Agent or user that created this artifact. */\n author: string;\n /** User's decision on this artifact. */\n decision: 'pending' | 'approved' | 'rejected' | 'modified' | null;\n /** Optional user-supplied notes when approving/rejecting. */\n decisionNote: string | null;\n /** Filesystem source. */\n source: 'worktree' | 'global' | string;\n /** Element count (visual elements) — for plan-like artifacts. */\n elementCount: number | null;\n /** Comment count. */\n commentCount: number | null;\n /** Last-modified time. */\n mtime: number;\n /** Optional URL to the artifact's full view. */\n artifactUrl: string | null;\n /** Tags — free-form, used for filtering. */\n tags: string[];\n};\n\nexport type CanvasElement = {\n id: string;\n type: string;\n title?: string;\n content?: string;\n x: number;\n y: number;\n width: number;\n height: number;\n status?: string;\n};\n\nexport type CanvasConnection = {\n id: string;\n fromElementId?: string;\n from?: string;\n toElementId?: string;\n to?: string;\n label?: string;\n};\n\nexport type CanvasComment = {\n id: string;\n elementId?: string;\n text: string;\n author: string;\n created: string;\n thread?: { author: string; text: string }[];\n};\n\nexport type CanvasViewport = { x: number; y: number; zoom: number };\n\nexport type Canvas = {\n schemaVersion?: number;\n title: string;\n elements: CanvasElement[];\n connections: CanvasConnection[];\n comments: CanvasComment[];\n viewport: CanvasViewport;\n};\n\nexport type ConfigResponse = {\n path: string;\n data: unknown;\n raw: string;\n exists: boolean;\n};\n\nexport interface SystemLlmConfig {\n enabled: boolean;\n provider: string; // e.g. \"opencode\"\n model: string; // e.g. \"opencode/deepseek-v4-flash-free\"\n // api key is read from auth.json for the provider, not stored here\n}\n\nexport type Settings = {\n theme: ThemeSettings;\n ui: UiSettings;\n defaultAgent: string;\n defaultModel: string;\n notifications: {\n onAgentComplete: boolean;\n onPlanApproval: boolean;\n };\n dashboard: {\n autoLaunchWeb: boolean;\n projectsDirectory?: string;\n allowedRoots?: string[];\n };\n service: {\n enabled: boolean;\n autostart: boolean;\n };\n about: {\n version: string;\n homepage: string;\n license: string;\n };\n agents: {\n maxParallel: number;\n stuckThresholdMs: number;\n autoRestart: boolean;\n };\n // v3.19.0 — Personalization (free-form text shown to agents).\n personalization: {\n displayName: string;\n role: string;\n team: string;\n aboutMe: string;\n preferences: string;\n };\n // v3.19.0 — Workflow toggles.\n workflow: {\n /** Allow agents to create / mutate artifacts. */\n artifactsEnabled: boolean;\n /** When true, agents make decisions themselves instead of asking. */\n agentsDecideAutonomously: boolean;\n /** Per-tab override for autonomy (chat input). */\n chatAutonomous: boolean;\n };\n // v3.21.0 — System LLM calls (auto-title, enhance-prompt, summarization).\n systemLlm?: SystemLlmConfig;\n // v4.5.0 — MiniMax Token Plan integration. `apiKey` is the user's\n // Subscription Key (NOT a pay-as-you-go API key). `groupId` is usually\n // 'default' for an individual team. `baseUrl` is the Token Plan host\n // (the remains endpoint lives on www.*, not api.*). `chatBaseUrl` is\n // the chat-completions host (api.*/v1).\n minimax?: {\n enabled: boolean;\n apiKey: string;\n groupId: string;\n baseUrl: string;\n chatBaseUrl: string;\n };\n // v5.0.0 — Headroom context compression settings.\n headroom?: HeadroomSettings;\n};\n\nexport type HeadroomSettings = {\n enabled: boolean;\n autoInstall: boolean;\n port: number;\n host: string;\n outputShaper: boolean;\n telemetry: boolean;\n budget: number;\n backend: string;\n autoStart: boolean;\n autoWrap: boolean;\n routeAllProviders: boolean;\n};\n\nexport type SettingsResponse = {\n path: string;\n data: Settings;\n exists: boolean;\n};\n\nexport type OverviewCounts = {\n agents: number;\n plans: number;\n projects: number;\n sessions: number;\n activeProject?: string | null;\n};\n\nexport type OverviewVersions = {\n node: string;\n platform: string;\n projectRoot: string;\n bizarRoot: string;\n};\n\nexport type ActivityItem = {\n ts: string;\n kind: string;\n [k: string]: unknown;\n};\n\nexport type Overview = {\n counts: OverviewCounts;\n recentActivity: ActivityItem[];\n versions: OverviewVersions;\n generatedAt: string;\n};\n\nexport type ChatMessage = {\n id?: string;\n role: 'user' | 'assistant' | 'system' | string;\n content?: string;\n message?: string;\n agent?: string;\n ts?: string | number;\n pinned?: boolean;\n};\n\nexport type ChatSession = {\n id: string;\n file: string;\n mtime: number;\n size: number;\n /** 'bizar' sessions come from the per-project .jsonl store; 'opencode' come from opencode.db */\n source?: 'bizar' | 'opencode';\n /** Filled when source === 'opencode' — the URL to open that session in the opencode web UI */\n opencodeUrl?: string;\n /** Display title; defaults to id when absent */\n title?: string;\n};\n\nexport type ChatResponse = {\n messages: ChatMessage[];\n sessions: ChatSession[];\n};\n\nexport type Task = {\n id: string;\n title: string;\n description: string;\n status: 'queued' | 'doing' | 'done' | 'blocked' | 'archived' | string;\n tags: string[];\n priority: 'low' | 'normal' | 'high' | string;\n assignee?: string | null;\n parent?: string | null;\n dependencies?: string[];\n timeSpent?: number;\n recurring?: { cron?: string; lastGenerated?: string; freq?: 'daily' | 'weekly' | 'monthly' | string } | null;\n attachments?: string[];\n comments?: { id: string; text: string; createdAt: string }[];\n activity?: { id: string; type: string; ts: string; data?: unknown }[];\n archived?: boolean;\n workedBy?: string | null;\n dueDate?: string | null;\n // v3.2.0 — main-task fields from the task delegator.\n subtasks?: string[];\n metadata?: Record<string, unknown> | null;\n // v3.3.0 — progress fields surfaced from metadata.\n progress?: number;\n currentStep?: string | null;\n progressAgent?: string | null;\n progressHistory?: { ts: string; progress: number; step: string | null; agent?: string | null }[];\n createdAt: string;\n updatedAt: string;\n completedAt?: string | null;\n _timerStart?: number;\n};\n\nexport type Notification = {\n id: string;\n ts: string;\n severity: 'info' | 'success' | 'warning' | 'error';\n message: string;\n source: string;\n title?: string;\n link?: string | null;\n read?: boolean;\n meta?: Record<string, unknown> | null;\n};\n\nexport type NotificationStats = {\n total: number;\n unread: number;\n lastTs: string | null;\n counts: Record<string, number>;\n};\n\nexport type NotificationsResponse = {\n notifications: Notification[];\n stats: NotificationStats;\n};\n\nexport type CustomTheme = {\n name: string;\n colors: Partial<ThemeSettings>;\n createdAt?: string | null;\n};\n\nexport type BudgetCheck = {\n /** Skip the run if at least this many bg tasks are already running or pending. */\n maxConcurrent?: number;\n /** When true, the runner consults `maxConcurrent` and skips the run if the budget is exceeded. */\n skipIfBudgetLow?: boolean;\n};\n\nexport type ScheduleAction = {\n type: 'command' | 'agent' | 'webhook';\n target: string;\n /** Free-form prompt for `agent` actions — the text the agent will work from. */\n prompt?: string;\n /** Optional webhook method override (defaults to POST). */\n method?: string;\n /** Webhook body. May also carry `prompt` for an agent dispatch. */\n body?: unknown;\n /** Title used when this action dispatches an agent task. */\n title?: string;\n /** Display name override when dispatching. */\n name?: string;\n};\n\nexport type Schedule = {\n id: string;\n name: string;\n type: 'cron' | 'interval' | 'once';\n /** For `cron`: a 5-field cron expression. For `interval`: a \"30m\" / \"2h\" / \"1d\" string. For `once`: an ISO datetime. */\n schedule: string;\n /** IANA timezone for `cron` schedules. Defaults to \"UTC\" when absent. */\n timezone?: string;\n action: ScheduleAction;\n /** Optional budget pre-flight: skip the run when too many bg tasks are already in flight. */\n budgetCheck?: BudgetCheck;\n enabled: boolean;\n createdAt: string;\n updatedAt: string;\n lastRun: string | null;\n lastResult: 'success' | 'error' | 'skipped' | null;\n lastError: string | null;\n nextRun: string | null;\n history: { ts: string; result: string; error: string | null }[];\n};\n\nexport type Mod = {\n id: string;\n name: string;\n version: string;\n author: string;\n description: string;\n bizar: string;\n type: string;\n enabled: boolean;\n permissions: string[];\n entry: Record<string, string>;\n files: { category: string; name: string; path: string }[];\n path: string;\n installedAt: string | null;\n};\n\nexport type Provider = {\n id: string;\n name: string;\n baseURL: string;\n apiKey: string;\n models: string[];\n enabled: boolean;\n};\n\nexport type McpServer = {\n id: string;\n type?: 'local' | 'remote';\n command: string;\n args: string[];\n env: Record<string, string>;\n url?: string;\n headers?: Record<string, string>;\n oauth?: boolean;\n enabled: boolean;\n};\n\nexport type DiagnosticItem = {\n line: string;\n ts: string | null;\n};\n\nexport type Diagnostics = {\n version: string;\n uptime: number;\n uptimeMs: number;\n nodeVersion: string;\n platform: string;\n memory: { rss: number; heapUsed: number; heapTotal: number };\n counts: {\n agents: number;\n plans: number;\n tasks: number;\n projects: number;\n activeProject: string | null;\n mods: number;\n schedules: number;\n providers: number;\n mcps: number;\n };\n errors: DiagnosticItem[];\n service: { running: boolean; pid?: number; error?: string };\n};\n\nexport type TailscaleStatus = {\n installed: boolean;\n version: string | null;\n authenticated: boolean;\n backend: string;\n hostname: string;\n settings: { enabled: boolean; port: number; https: boolean; hostname: string };\n};\n\n// ────────────────────────────────────────────────────────────────────────\n// v6.0.0 — Doctor page types\n// ────────────────────────────────────────────────────────────────────────\n\nexport type DoctorStatus = 'ok' | 'warn' | 'fail';\n\nexport type DoctorCheck = {\n name: string;\n status: DoctorStatus;\n message: string;\n};\n\nexport type DoctorChecks = {\n system: DoctorCheck[];\n config: DoctorCheck[];\n services: DoctorCheck[];\n};\n\nexport type DoctorHealth = {\n ts: string;\n status: DoctorStatus;\n issues: DoctorCheck[];\n groups: DoctorChecks;\n};\n\nexport type DoctorCounts = {\n tasks: number;\n schedules: number;\n mods: number;\n providers: number;\n mcps: number;\n agents: number;\n projects: number;\n activeProject: string | null;\n workspaces: number;\n voiceNotes: number;\n evalRuns: number;\n backups: number;\n};\n\nexport type DoctorService = {\n running: boolean;\n port?: number;\n host?: string;\n pid?: number;\n uptime?: number;\n error?: string;\n baseUrl?: string | null;\n reachable?: boolean;\n path?: string | null;\n logPath?: string;\n logExists?: boolean;\n};\n\nexport type DoctorServices = {\n dashboard: DoctorService;\n headroom: DoctorService;\n lightrag: DoctorService;\n opencode: DoctorService;\n};\n\nexport type DoctorRecentError = {\n line: string;\n ts: string | null;\n tsMs: number | null;\n};\n\nexport type DoctorOpencode = {\n configExists: boolean;\n configPath: string;\n agentsDir: string;\n agentFiles: string[];\n dashboardConnected: boolean;\n};\n\nexport type DoctorDisk = {\n path: string;\n exists: boolean;\n sizeBytes: number;\n};\n\nexport type DoctorSnapshot = {\n timestamp: string;\n bizarVersion: string;\n nodeVersion: string;\n platform: string;\n arch: string;\n uptime: number;\n memory: NodeJS.MemoryUsage;\n disk: DoctorDisk;\n services: DoctorServices;\n counts: DoctorCounts;\n recentErrors: DoctorRecentError[];\n configHealth: DoctorCheck[];\n opencode: DoctorOpencode;\n checks: DoctorChecks;\n health: { status: DoctorStatus; issues: DoctorCheck[] };\n};\n\n/**\n * The categories the Doctor page renders. Order is the render order.\n * Each maps 1:1 to either a `DoctorChecks` group or a derived slice.\n */\nexport type DoctorCategory =\n | 'system'\n | 'services'\n | 'counts'\n | 'errors'\n | 'actions';\n\nexport type DoctorCheckOrInfo =\n | DoctorCheck\n | { kind: 'count'; label: string; value: number | string };\n\nexport type DoctorPanelProps = {\n category: DoctorCategory;\n /** Title shown in the card header. Optional — derived from category if omitted. */\n title?: string;\n /** Lucide icon for the header. Optional — derived from category if omitted. */\n icon?: React.ComponentType<{ size?: number; 'aria-hidden'?: boolean }>;\n /** Per-line entries. Each carries an implicit `ok | warn | fail` status pill. */\n checks: DoctorCheck[];\n /**\n * Plain key/value rows that aren't checks (e.g. counts). Rendered\n * as a compact two-column list below the check rows when present.\n */\n info?: Array<{ label: string; value: string | number }>;\n /** Meta line shown beneath the title. */\n meta?: string;\n /** Optional custom body — bypasses the default check-list render. */\n children?: React.ReactNode;\n className?: string;\n};\n\nexport type SearchResult = {\n type: string;\n score: number;\n item: Record<string, unknown>;\n};\n\n/**\n * One background-agent instance. Mirrors the JSON shape returned by\n * GET /api/background (src/server/routes/background.mjs). Status values:\n * - 'pending' — accepted by the runner, dispatch not yet started\n * - 'running' — actively executing in a tmux session\n * - 'done' — finished with success\n * - 'failed' — terminated with an error\n * - 'killed' — user-requested kill\n * - 'timed_out' — exceeded plugin stall/dispatch timeout\n */\nexport type BgInstance = {\n instanceId: string;\n status?: 'pending' | 'running' | 'done' | 'failed' | 'killed' | 'timed_out' | string;\n agent?: string;\n prompt?: string;\n startedAt?: number;\n completedAt?: number;\n currentStep?: string | null;\n progress?: number;\n toolCallCount?: number;\n tmuxSession?: string;\n tmuxActive?: boolean;\n sessionId?: string;\n error?: string;\n // Optional fields the activity timeline tracks — not required for this view.\n parentAgent?: string;\n parentInstanceId?: string;\n promptPreview?: string;\n resultPreview?: string;\n lastEventAt?: number;\n taskId?: string;\n};\n\nexport type BackgroundListResponse = {\n instances: BgInstance[];\n status: { dir: string; exists: boolean; count: number };\n};\n\nexport type BackgroundOutputResponse = {\n /** Captured stdout/stderr text (tail). */\n output?: string;\n /** True when the underlying log file is gone (e.g. after cleanup). */\n available?: boolean;\n /** Total captured bytes — useful for the modal header. */\n bytes?: number;\n /** Truncation flag set by the server when output was capped. */\n truncated?: boolean;\n};\n\nexport type BackgroundTmuxResponse = {\n /** Computed tmux session name (e.g. \"bg-r2u9q8\"). */\n session?: string;\n /** Local command the user can paste to attach. */\n attachCommand?: string;\n /** Whether the session exists in tmux right now. */\n exists?: boolean;\n /** Optional reason when exists === false. */\n reason?: string;\n};\n\nexport type Snapshot = {\n overview: Overview;\n agents: Agent[];\n artifacts: Artifact[];\n projects: ProjectRecord[];\n activeProject: ProjectRecord | null;\n config: ConfigResponse;\n settings: SettingsResponse;\n tasks: Task[];\n mods: Mod[];\n schedules: Schedule[];\n providers: Provider[];\n mcps: McpServer[];\n};\n\nexport type WsStatus = 'connecting' | 'connected' | 'disconnected';\n\nexport type WsMessage =\n | { type: 'snapshot'; ts: number; data: Snapshot }\n | { type: 'change'; event: string; path: string; ts: number }\n | { type: 'tasks:change'; task: Task }\n | { type: 'tasks:delete'; id: string }\n | { type: 'task:progress'; taskId: string; progress?: number; step?: string | null; agent?: string | null }\n | { type: 'settings:change'; settings: Settings }\n | { type: 'agents:change' }\n | { type: 'background:change'; action?: string; id?: string; sessionId?: string }\n | { type: 'background:cleanup'; deleted?: number }\n | { type: 'agent:status'; agent: Agent }\n | { type: 'agent:restarted'; agent: Agent }\n | { type: 'agent:stuck'; agents: { name: string }[] }\n | { type: 'artifact:change'; slug: string; deleted?: boolean }\n | { type: 'schedules:change' }\n | { type: 'project:change'; project?: ProjectRecord }\n | { type: 'chat:message'; message: ChatMessage }\n | { type: 'notification:new'; notification: Notification }\n | { type: 'notifications:change' }\n | { type: 'dialog:show'; dialog: DialogDescriptor }\n | { type: 'pong'; ts: number }\n | { type: 'ping' }\n | { type: 'refresh' };\n\n/** Resolve a theme to the actual key applied to <html data-theme=\"...\"> */\nexport function applyTheme(themeName: ThemeName | ThemeSettings): 'dark' | 'light' {\n const mode = typeof themeName === 'string' ? themeName : themeName.mode;\n const resolved: 'dark' | 'light' =\n mode === 'system'\n ? typeof window !== 'undefined' &&\n window.matchMedia('(prefers-color-scheme: light)').matches\n ? 'light'\n : 'dark'\n : mode === 'light'\n ? 'light'\n : 'dark';\n if (typeof document !== 'undefined') {\n if (resolved === 'light') {\n document.documentElement.setAttribute('data-theme', 'light');\n } else {\n document.documentElement.removeAttribute('data-theme');\n }\n }\n return resolved;\n}\n\n/** Apply the full theme object — sets CSS variables. */\nexport function applyThemeTokens(theme: ThemeSettings) {\n if (typeof document === 'undefined') return;\n const root = document.documentElement;\n root.style.setProperty('--accent', theme.accent);\n root.style.setProperty('--success', theme.success);\n root.style.setProperty('--warning', theme.warning);\n root.style.setProperty('--error', theme.error);\n root.style.setProperty('--info', theme.info);\n // Derive accent-bg/border lightly\n root.style.setProperty('--accent-bg', hexToRgba(theme.accent, 0.12));\n root.style.setProperty('--accent-border', hexToRgba(theme.accent, 0.4));\n if (theme.fontFamily) {\n root.style.setProperty('--font-sans', `'${theme.fontFamily}', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif`);\n }\n if (theme.fontSize) {\n root.style.setProperty('--base-font-size', `${theme.fontSize}px`);\n }\n root.dataset.compactMode = theme.compactMode ? 'true' : 'false';\n root.dataset.animations = theme.animations ? 'true' : 'false';\n}\n\nfunction hexToRgba(hex: string, alpha: number) {\n const m = /^#?([0-9a-f]{6})$/i.exec(hex || '');\n if (!m) return `rgba(139, 92, 246, ${alpha})`;\n const n = parseInt(m[1], 16);\n const r = (n >> 16) & 255;\n const g = (n >> 8) & 255;\n const b = n & 255;\n return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n}\n","// src/components/Spinner.tsx — loading indicator.\n\nimport { cn } from '../lib/utils';\n\nexport type SpinnerProps = {\n size?: 'sm' | 'md' | 'lg';\n className?: string;\n label?: string;\n};\n\nexport function Spinner({ size = 'md', className, label }: SpinnerProps) {\n return (\n <span\n className={cn('spinner', `spinner-${size}`, className)}\n role=\"status\"\n aria-label={label || 'Loading'}\n />\n );\n}\n","// src/web/components/VirtualList.tsx — hand-rolled virtual scrolling (no external deps).\nimport React, { useState, useRef, useEffect, useMemo } from 'react';\n\nexport function VirtualList<T>({\n items,\n renderItem,\n itemHeight = 80,\n height = 400,\n overscan = 5,\n className,\n}: {\n items: T[];\n renderItem: (item: T, index: number) => React.ReactNode;\n itemHeight?: number;\n height?: number;\n overscan?: number;\n className?: string;\n}) {\n const [scrollTop, setScrollTop] = useState(0);\n const containerRef = useRef<HTMLDivElement>(null);\n\n const visibleRange = useMemo(() => {\n const start = Math.max(0, Math.floor(scrollTop / itemHeight) - overscan);\n const end = Math.min(\n items.length,\n Math.ceil((scrollTop + height) / itemHeight) + overscan,\n );\n return { start, end };\n }, [scrollTop, itemHeight, height, overscan, items.length]);\n\n const totalHeight = items.length * itemHeight;\n const offsetY = visibleRange.start * itemHeight;\n const visibleItems = items.slice(visibleRange.start, visibleRange.end);\n\n return (\n <div\n ref={containerRef}\n onScroll={(e) => setScrollTop(e.currentTarget.scrollTop)}\n style={{ height, overflowY: 'auto' }}\n className={className}\n >\n <div style={{ height: totalHeight, position: 'relative' }}>\n <div style={{ transform: `translateY(${offsetY}px)` }}>\n {visibleItems.map((item, i) => (\n <div key={visibleRange.start + i} style={{ height: itemHeight }}>\n {renderItem(item, visibleRange.start + i)}\n </div>\n ))}\n </div>\n </div>\n </div>\n );\n}\n","// src/components/chat/ChatTopBar.tsx — 48px topbar with project chip + panel toggles.\n\nimport { Folder, ChevronDown } from 'lucide-react';\n\ninterface Props {\n activeProject: { name: string; path?: string } | null;\n sessionCount: number;\n sessionsOpen: boolean;\n infoOpen: boolean;\n onToggleSessions: () => void;\n onToggleInfo: () => void;\n onOpenOverview: () => void;\n}\n\nexport function ChatTopBar({\n activeProject,\n sessionCount,\n sessionsOpen,\n infoOpen,\n onToggleSessions,\n onToggleInfo,\n onOpenOverview,\n}: Props) {\n return (\n <header className=\"chat-topbar\">\n <button type=\"button\" className=\"chat-topbar-project\" onClick={onOpenOverview}>\n <Folder size={14} />\n <span className=\"chat-topbar-project-name\">{activeProject?.name ?? 'No project'}</span>\n <ChevronDown size={12} />\n </button>\n <div className=\"chat-topbar-spacer\" />\n <button\n type=\"button\"\n className={`chat-topbar-btn ${sessionsOpen ? 'active' : ''}`}\n onClick={onToggleSessions}\n aria-pressed={sessionsOpen}\n >\n Sessions ({sessionCount})\n </button>\n <button\n type=\"button\"\n className={`chat-topbar-btn ${infoOpen ? 'active' : ''}`}\n onClick={onToggleInfo}\n aria-pressed={infoOpen}\n >\n Info\n </button>\n </header>\n );\n}","// src/components/chat/EmptyState.tsx — chat-local empty state (variant of shared EmptyState).\n\nimport type { ReactNode } from 'react';\n\ninterface Props {\n icon?: ReactNode;\n title: string;\n message: ReactNode;\n action?: ReactNode;\n}\n\nexport function EmptyState({ icon, title, message, action }: Props) {\n return (\n <div className=\"chat-empty-state\">\n {icon && <div className=\"chat-empty-state-icon\">{icon}</div>}\n <h2 className=\"chat-empty-state-title\">{title}</h2>\n <p className=\"chat-empty-state-message\">{message}</p>\n {action && <div className=\"chat-empty-state-action\">{action}</div>}\n </div>\n );\n}","// src/components/chat/LoadingSkeleton.tsx — pulsing skeleton bubbles while loading.\n\ninterface Props {\n count?: number;\n}\n\nexport function LoadingSkeleton({ count = 3 }: Props) {\n return (\n <div className=\"chat-loading-skeleton\" aria-label=\"Loading messages\">\n {Array.from({ length: count }).map((_, i) => (\n <div key={i} className=\"chat-loading-skeleton-bubble\">\n <div className=\"chat-loading-skeleton-meta\" />\n <div className=\"chat-loading-skeleton-line\" />\n <div className=\"chat-loading-skeleton-line short\" />\n </div>\n ))}\n </div>\n );\n}","// src/components/chat/SuggestionCards.tsx — 4 hardcoded suggestion cards for first-run.\n\nimport { Code, BookOpen, Lightbulb, Image as ImageIcon } from 'lucide-react';\n\ninterface Props {\n projectName: string;\n onPickSuggestion: (text: string) => void;\n}\n\nexport function SuggestionCards({ projectName, onPickSuggestion }: Props) {\n const suggestions = [\n {\n icon: <Code size={16} />,\n title: projectName ? `Explain ${projectName}` : 'Explain this project',\n preview: 'Give me a tour of the codebase, highlight the architecture, and call out any anti-patterns or hotspots.',\n prompt: projectName\n ? `Explain the ${projectName} project. Give me a tour of the codebase, highlight the architecture, and call out any anti-patterns or hotspots.`\n : 'Explain the current project. Give me a tour of the codebase, highlight the architecture, and call out any anti-patterns or hotspots.',\n },\n {\n icon: <BookOpen size={16} />,\n title: 'Summarize recent sessions',\n preview: 'Read my last 5 chat sessions and give me a 3-bullet summary of what I worked on and what got stuck.',\n prompt: 'Read my last 5 chat sessions and give me a 3-bullet summary of what I worked on and what got stuck.',\n },\n {\n icon: <Lightbulb size={16} />,\n title: 'Suggest a refactor',\n preview: 'Look for the largest, most painful files in this project and propose a concrete refactor for the worst one.',\n prompt: 'Look for the largest, most painful files in this project and propose a concrete refactor for the worst one.',\n },\n {\n icon: <ImageIcon size={16} />,\n title: 'Find visual regressions',\n preview: 'Open the dashboard, screenshot the Chat tab, and flag any layout problems or anti-patterns you see.',\n prompt: 'Open the dashboard, screenshot the Chat tab, and flag any layout problems or anti-patterns you see.',\n },\n ];\n\n return (\n <div className=\"chat-suggestions\">\n {suggestions.map((s, i) => (\n <button\n key={i}\n type=\"button\"\n className=\"chat-suggestion\"\n onClick={() => onPickSuggestion(s.prompt)}\n >\n <div className=\"chat-suggestion-title\">{s.icon}<span>{s.title}</span></div>\n <div className=\"chat-suggestion-preview\">{s.preview}</div>\n </button>\n ))}\n </div>\n );\n}","// src/components/chat/WelcomeScreen.tsx — gradient greeting + no-project state.\n\nimport { Folder } from 'lucide-react';\nimport { SuggestionCards } from './SuggestionCards';\n\ninterface Props {\n variant: 'empty' | 'no-project';\n projectName: string;\n onPickSuggestion: (text: string) => void;\n}\n\nexport function WelcomeScreen({ variant, projectName, onPickSuggestion }: Props) {\n if (variant === 'no-project') {\n return (\n <div className=\"chat-welcome\">\n <h1 className=\"chat-welcome-h1\">\n <span className=\"chat-greeting-hello\">No project selected.</span>\n </h1>\n <p className=\"chat-welcome-subtitle\">Pick a project in Overview to scope chat sessions.</p>\n </div>\n );\n }\n\n const displayName = projectName || 'there';\n\n return (\n <div className=\"chat-welcome\">\n <h1 className=\"chat-welcome-h1\">\n <span className=\"chat-greeting-hello\">Hello, </span>\n <span className=\"chat-greeting-name\">{displayName}.</span>\n </h1>\n <p className=\"chat-welcome-subtitle\">How can I help you today?</p>\n <SuggestionCards projectName={projectName} onPickSuggestion={onPickSuggestion} />\n </div>\n );\n}","// src/components/chat/MessageBubble.tsx — user/assistant message with hover actions.\n\nimport ReactMarkdown from 'react-markdown';\nimport remarkGfm from 'remark-gfm';\nimport { Copy, RefreshCw, Pin, Trash2 } from 'lucide-react';\nimport { formatTime } from '../../lib/utils';\n\ninterface Message {\n role?: string;\n content?: string;\n message?: string;\n agent?: string;\n ts?: string | number;\n}\n\ninterface Props {\n message: Message;\n pinned: boolean;\n onCopy: () => void;\n onDelete: () => void;\n onTogglePin: () => void;\n onRegenerate: () => void;\n}\n\nexport function MessageBubble({ message, pinned, onCopy, onDelete, onTogglePin, onRegenerate }: Props) {\n const role = (message.role || 'assistant').toLowerCase();\n const ts = message.ts ? formatTime(message.ts) : '';\n const content = message.content || message.message || '';\n const isUser = role === 'user';\n\n return (\n <article className={`chat-message ${isUser ? 'chat-message-user' : 'chat-message-assistant'} ${pinned ? 'chat-message-pinned' : ''}`}>\n <header className=\"chat-message-meta\">\n <span className=\"chat-message-role\">{role}</span>\n {message.agent && <span className=\"chat-message-agent\">@{message.agent}</span>}\n {ts && <time className=\"chat-message-time\">{ts}</time>}\n {pinned && <span className=\"chat-message-pin\">pinned</span>}\n <div className=\"chat-message-actions\">\n <button type=\"button\" className=\"chat-msg-action-btn\" onClick={onCopy} title=\"Copy\" aria-label=\"Copy\">\n <Copy size={12} />\n </button>\n {!isUser && (\n <button type=\"button\" className=\"chat-msg-action-btn\" onClick={onRegenerate} title=\"Regenerate\" aria-label=\"Regenerate\">\n <RefreshCw size={12} />\n </button>\n )}\n <button type=\"button\" className=\"chat-msg-action-btn\" onClick={onTogglePin} title=\"Pin\" aria-label=\"Pin\">\n <Pin size={12} />\n </button>\n <button type=\"button\" className=\"chat-msg-action-btn chat-msg-action-btn-danger\" onClick={onDelete} title=\"Delete\" aria-label=\"Delete\">\n <Trash2 size={12} />\n </button>\n </div>\n </header>\n <div className=\"chat-message-content markdown-body\">\n <ReactMarkdown remarkPlugins={[remarkGfm]}>{content}</ReactMarkdown>\n </div>\n </article>\n );\n}","// src/components/chat/ChatThread.tsx — message list, welcome screen, loading skeleton.\n//\n// v4.2.5 — accepts `activeSource` so the loading skeleton can hint\n// which stream is loading and the welcome variant for \"opencode\"\n// can differ from the local chat welcome.\n\nimport { useEffect, useRef } from 'react';\nimport { LoadingSkeleton } from './LoadingSkeleton';\nimport { WelcomeScreen } from './WelcomeScreen';\nimport { MessageBubble } from './MessageBubble';\nimport { VirtualList } from '../VirtualList';\n\ninterface Message {\n role?: string;\n content?: string;\n message?: string;\n agent?: string;\n ts?: string | number;\n}\n\ninterface Props {\n messages: Message[];\n loading: boolean;\n activeProject: { name: string } | null;\n sessionId: string;\n pinned: Set<number>;\n /** Which stream currently owns the thread — purely cosmetic, lets\n * the empty/loading states be slightly more specific. The type\n * uses `unknown` so we don't pull in a tight coupling; the value\n * is reflected back into the loading skeleton and welcome screen\n * via data attributes so CSS can style accordingly. */\n activeSource?: 'bizar' | 'opencode' | null;\n onPickSuggestion: (text: string) => void;\n onCopy: (m: Message) => void;\n onDelete: (idx: number) => void;\n onTogglePin: (idx: number) => void;\n onRegenerate: (messageId: string) => void;\n}\n\nexport function ChatThread({\n messages,\n loading,\n activeProject,\n sessionId,\n pinned,\n activeSource,\n onPickSuggestion,\n onCopy,\n onDelete,\n onTogglePin,\n onRegenerate,\n}: Props) {\n const innerRef = useRef<HTMLDivElement>(null);\n\n // Only auto-scroll when we own the scroll container (i.e. when\n // rendered directly as the message scroller). When embedded\n // inside Chat.tsx's chat-thread-scroll, the parent handles scroll.\n useEffect(() => {\n const el = innerRef.current;\n if (!el) return;\n let p: HTMLElement | null = el.parentElement;\n let wrapped = false;\n while (p) {\n if (p.classList.contains('chat-thread-scroll')) {\n wrapped = true;\n break;\n }\n p = p.parentElement;\n }\n if (!wrapped) el.scrollTop = el.scrollHeight;\n }, [messages]);\n\n const stream: 'bizar' | 'opencode' = activeSource === 'opencode' ? 'opencode' : 'bizar';\n\n if (loading) {\n return (\n <div className=\"chat-thread legacy\" ref={innerRef} data-stream={stream}>\n <LoadingSkeleton count={3} />\n </div>\n );\n }\n\n if (!activeProject) {\n return (\n <div className=\"chat-thread legacy\" ref={innerRef} data-stream={stream}>\n <WelcomeScreen\n variant=\"no-project\"\n projectName=\"\"\n onPickSuggestion={onPickSuggestion}\n />\n </div>\n );\n }\n\n if (messages.length === 0) {\n return (\n <div className=\"chat-thread legacy\" ref={innerRef} data-stream={stream}>\n <WelcomeScreen\n variant=\"empty\"\n projectName={activeProject.name}\n onPickSuggestion={onPickSuggestion}\n />\n </div>\n );\n }\n\n return (\n <div className=\"chat-thread legacy\" ref={innerRef}>\n <VirtualList\n items={messages}\n itemHeight={120}\n height={800}\n className=\"chat-thread-virtual\"\n renderItem={(m, i) => (\n <MessageBubble\n message={m}\n pinned={pinned.has(i)}\n onCopy={() => onCopy(m)}\n onDelete={() => onDelete(i)}\n onTogglePin={() => onTogglePin(i)}\n onRegenerate={() => onRegenerate(String(m.ts ?? i))}\n />\n )}\n />\n </div>\n );\n}\n","// src/components/chat/AgentChip.tsx — custom popover agent picker, replaces <select>.\n\nimport { useEffect, useRef, useState } from 'react';\nimport { Bot, ChevronDown } from 'lucide-react';\n\ninterface Props {\n agent: string;\n setAgent: (a: string) => void;\n agents: Array<{ name: string }>;\n}\n\nexport function AgentChip({ agent, setAgent, agents }: Props) {\n const [open, setOpen] = useState(false);\n const ref = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n if (!open) return;\n const handler = (e: MouseEvent) => {\n if (ref.current && !ref.current.contains(e.target as Node)) {\n setOpen(false);\n }\n };\n document.addEventListener('mousedown', handler);\n return () => document.removeEventListener('mousedown', handler);\n }, [open]);\n\n const current = agent || 'no agent';\n const display = current.startsWith('@') ? current.slice(1) : current;\n\n return (\n <div className=\"chat-agent-chip\" ref={ref}>\n <button\n type=\"button\"\n className=\"chat-agent-chip-btn\"\n onClick={() => setOpen((v) => !v)}\n aria-haspopup=\"listbox\"\n aria-expanded={open}\n >\n <Bot size={14} />\n <span>{display}</span>\n <ChevronDown size={12} className={open ? 'rotated' : ''} />\n </button>\n {open && (\n <div className=\"chat-agent-chip-menu\" role=\"listbox\">\n <button\n type=\"button\"\n role=\"option\"\n aria-selected={!agent}\n onClick={() => { setAgent(''); setOpen(false); }}\n >\n <span>no agent</span>\n </button>\n {agents.map((a) => (\n <button\n key={a.name}\n type=\"button\"\n role=\"option\"\n aria-selected={agent === a.name}\n onClick={() => { setAgent(a.name); setOpen(false); }}\n >\n <span>@{a.name}</span>\n </button>\n ))}\n </div>\n )}\n </div>\n );\n}","// src/components/chat/useAutoGrowTextarea.ts — auto-resize textarea up to maxHeight.\n\nimport { useLayoutEffect } from 'react';\n\nexport function useAutoGrowTextarea(\n ref: React.RefObject<HTMLTextAreaElement | null>,\n value: string,\n maxHeight = 240,\n): void {\n useLayoutEffect(() => {\n const el = ref.current;\n if (!el) return;\n el.style.height = 'auto';\n const next = Math.min(el.scrollHeight, maxHeight);\n el.style.height = `${next}px`;\n el.style.overflowY = el.scrollHeight > maxHeight ? 'auto' : 'hidden';\n }, [ref, value, maxHeight]);\n}","// src/components/chat/Composer.tsx — textarea + toolbar, used inside FloatingComposer.\n\nimport { useRef } from 'react';\nimport { Send, Paperclip, X, Sparkles } from 'lucide-react';\nimport { enhancePrompt } from '../../lib/api';\nimport { AgentChip } from './AgentChip';\nimport { useAutoGrowTextarea } from './useAutoGrowTextarea';\n\ninterface Props {\n agent: string;\n setAgent: (a: string) => void;\n model: string;\n setModel: (m: string) => void;\n text: string;\n setText: (t: string) => void;\n sending: boolean;\n onSend: () => void;\n attachments: string[];\n setAttachments: (a: string[] | ((prev: string[]) => string[])) => void;\n suggestions: Array<{ cmd: string; desc: string }>;\n onPickSuggestion: (cmd: string) => void;\n agents: Array<{ name: string }>;\n onAttach: () => void;\n}\n\nexport function Composer({\n agent,\n setAgent,\n model,\n setModel,\n text,\n setText,\n sending,\n onSend,\n attachments,\n setAttachments,\n suggestions,\n onPickSuggestion,\n agents,\n onAttach,\n}: Props) {\n const inputRef = useRef<HTMLTextAreaElement>(null);\n const fileInputRef = useRef<HTMLInputElement>(null);\n useAutoGrowTextarea(inputRef, text, 160);\n\n const onKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {\n if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {\n e.preventDefault();\n onSend();\n } else if (e.key === 'Enter' && !e.shiftKey) {\n e.preventDefault();\n onSend();\n } else if (e.key === 'Tab' && suggestions.length) {\n e.preventDefault();\n const base = suggestions[0].cmd.split(' ')[0];\n setText(`${base} `);\n }\n };\n\n const onFiles = (e: React.ChangeEvent<HTMLInputElement>) => {\n const files = e.target.files;\n if (!files) return;\n const names: string[] = [];\n for (let i = 0; i < files.length; i++) names.push(files[i].name);\n setAttachments((cur) => [...cur, ...names]);\n e.target.value = '';\n };\n\n return (\n <div className=\"chat-composer\">\n {suggestions.length > 0 && (\n <div className=\"chat-composer-suggestions\">\n {suggestions.map((s) => (\n <button\n key={s.cmd}\n type=\"button\"\n className=\"chat-composer-suggestion\"\n onClick={() => {\n const base = s.cmd.split(' ')[0];\n setText(`${base} `);\n inputRef.current?.focus();\n }}\n >\n <span className=\"mono\">{s.cmd}</span>\n <span>{s.desc}</span>\n </button>\n ))}\n </div>\n )}\n\n {attachments.length > 0 && (\n <div className=\"chat-composer-attachments\">\n {attachments.map((a, i) => (\n <span key={i} className=\"chat-composer-attachment-tag\">\n <Paperclip size={10} /> {a}\n <button\n type=\"button\"\n className=\"chat-composer-attachment-remove\"\n onClick={() => setAttachments((cur) => cur.filter((_, j) => j !== i))}\n aria-label={`Remove ${a}`}\n >\n <X size={10} />\n </button>\n </span>\n ))}\n </div>\n )}\n\n <div className=\"chat-composer-input\">\n <AgentChip agent={agent} setAgent={setAgent} agents={agents} />\n <textarea\n ref={inputRef}\n className=\"chat-composer-textarea\"\n placeholder={sending ? 'Sending…' : 'Send a message…'}\n rows={1}\n value={text}\n onChange={(e) => setText(e.target.value)}\n onKeyDown={onKeyDown}\n disabled={sending}\n aria-label=\"Message\"\n />\n <input\n ref={fileInputRef}\n type=\"file\"\n multiple\n style={{ display: 'none' }}\n onChange={onFiles}\n aria-label=\"Attach files\"\n tabIndex={-1}\n />\n <button\n type=\"button\"\n className=\"chat-attach-btn\"\n onClick={onAttach}\n title=\"Attach files\"\n aria-label=\"Attach files\"\n >\n <Paperclip size={14} />\n </button>\n <button\n type=\"button\"\n className=\"chat-attach-btn\"\n onClick={async () => {\n if (!text.trim()) return;\n const enhanced = await enhancePrompt(text);\n if (enhanced !== text) setText(enhanced);\n }}\n title=\"Enhance prompt with AI\"\n aria-label=\"Enhance prompt\"\n >\n <Sparkles size={14} />\n </button>\n <button\n type=\"button\"\n className=\"chat-send-btn\"\n onClick={onSend}\n disabled={sending || !text.trim()}\n title=\"Send (Enter)\"\n aria-label=\"Send message\"\n >\n <Send size={16} />\n </button>\n </div>\n </div>\n );\n}","// src/components/chat/useChat.ts — shared chat state: messages, sessions,\n// send, polling, SSE, session-state tracking.\n//\n// v4.2.5 — Chat overhaul. Supports two parallel message streams:\n// - bizar (GET /api/chat)\n// - opencode (GET /api/opencode-sessions/:id/messages + SSE)\n//\n// v5.0.0 — bug #4 fix: `loadOpencodeSession` failure path now extracts\n// structured `code`, `cause`, `status`, and `suggestion` from the\n// server's ApiError.data envelope and exposes them via a new\n// `opencodeErrorInfo` field. `ChatInfoPanel` consumes that field to\n// render a structured \"Couldn't load session\" panel with the server's\n// `suggestion` verbatim, plus a Retry button.\n//\n// Changes from v3.22:\n// * SSE has automatic reconnect-with-backoff (1s → 30s, doubled on\n// each error up to a cap). Closes cleanly on unmount and on\n// session-switch (no leaked EventSource).\n// * Optimistic send uses a stable `msg_*` ID and is deduped when\n// opencode echoes the message back via SSE `message.updated`/`message.user.*`.\n// * `opencodeMessages` keeps server-provided IDs distinct from\n// optimistic IDs so the merge is unambiguous (server echo\n// replaces optimistic by matching messageID; otherwise both stick\n// around briefly until the user/session state catches up).\n// * `onSend` routes to `/opencode-sessions/:id/send` when an\n// opencode session is active, `/chat` otherwise. Errors surface\n// via toast + we always remove the optimistic placeholder.\n// * Sessions are managed by source:\n// - `bizarSessions` → local .jsonl store, POST /chat/sessions\n// - `opencodeSessions` → opencode serve, GET /opencode-sessions\n// The rail shows both side-by-side with a source indicator and\n// uses separate rename/delete endpoints per source.\n// * All async actions surface a single `busy: { create, send, rename, delete }`\n// so the rail can disable buttons properly.\n// * No `any` leaks — all SSE event shapes are typed via\n// `OpencodeSseEnvelope`.\n\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport type {\n ChatMessage,\n ChatResponse,\n ChatSession,\n Settings,\n Snapshot,\n} from '../../lib/types';\nimport { api, ApiError } from '../../lib/api';\nimport type { SessionState } from './ChatRail';\nimport type { AgentTreeNode } from './AgentNode';\n\nexport interface SessionDisplayState {\n /** idle / streaming / awaiting — derived from the latest SSE event. */\n state: SessionState;\n /** Number of unread (i.e. arrived while the user wasn't viewing) messages. */\n unread: number;\n /** Pinned? (cosmetic for now; pinned state lives in the parent.) */\n pinned: boolean;\n /** Sub-agent tree, if any. */\n tree?: { root: AgentTreeNode };\n}\n\n/** Per-source action busy flags. Used by rail/info-panel to disable buttons. */\nexport interface ChatBusyState {\n create: boolean;\n send: boolean;\n rename: boolean;\n delete: boolean;\n}\n\n/**\n * Structured error envelope surfaced by `loadOpencodeSession` when\n * the upstream `/api/opencode-sessions/:id/messages` call fails.\n *\n * - `code` mirrors the server's `error` field (`plugin_offline`,\n * `directory_unknown`, `opencode_error`, `bad_request`).\n * - `cause` is the server's structured cause (`network`, `timeout`,\n * `unknown`, or a raw ECONNREFUSED-style string).\n * - `suggestion` is the operator-friendly hint from the server\n * (e.g. \"Run `bizar doctor` for diagnostics\").\n * - `canRetry` is `true` whenever the failure is transient (network\n * error, upstream 502) — i.e. the user can fix it by trying again\n * after starting the plugin / waiting for opencode to come back.\n * False for hard failures (bad_request).\n */\nexport interface OpencodeErrorInfo {\n type: 'session_load_failed';\n code: string;\n message: string;\n cause?: string;\n status?: number;\n suggestion?: string | null;\n canRetry: boolean;\n}\n\n/** Shape of one opencode SSE envelope after unwrapping. */\ninterface OpencodeSseEnvelope {\n type: string;\n sessionID?: string;\n messageID?: string;\n /** Raw upstream payload (kept for forwarders that want full fidelity). */\n data?: unknown;\n}\n\n/**\n * The chat hook returns a stable bag of state + actions used by\n * both Chat.tsx (desktop) and MobileChat.tsx (mobile).\n *\n * The hook owns the lifecycle of the opencode SSE stream. Callers\n * should never instantiate their own EventSource for the same\n * session — they should always go through this hook.\n */\nexport function useChat(snapshot: Snapshot, settings: Settings, initialTaskId?: string | null) {\n // ── Bizar stream ────────────────────────────────────────────────────────────\n const [bizarMessages, setBizarMessages] = useState<ChatMessage[]>([]);\n const [sessions, setSessions] = useState<ChatSession[]>([]);\n const [sessionId, setSessionId] = useState('');\n const [loading, setLoading] = useState(true);\n const [busy, setBusy] = useState<ChatBusyState>({\n create: false,\n send: false,\n rename: false,\n delete: false,\n });\n\n // ── Opencode stream ─────────────────────────────────────────────────────────\n const [opencodeSessions, setOpencodeSessions] = useState<ChatSession[]>([]);\n const [opencodeMessages, setOpencodeMessages] = useState<ChatMessage[]>([]);\n const [activeSource, setActiveSource] = useState<'bizar' | 'opencode' | null>(null);\n const [activeOpencodeSessionId, setActiveOpencodeSessionId] = useState<string | null>(null);\n const [opencodeError, setOpencodeError] = useState<string | null>(null);\n const [opencodeSuggestion, setOpencodeSuggestion] = useState<string | null>(null);\n /**\n * Structured error info for the opencode session load path. New in\n * v5.0.0 — `ChatInfoPanel` consumes this to render its own error\n * section (in addition to the chat-thread-header error already\n * rendered by Chat.tsx). `null` when no error.\n */\n const [opencodeErrorInfo, setOpencodeErrorInfo] = useState<OpencodeErrorInfo | null>(null);\n\n // ── v3.22 — session-state, unread, tree per session ────────────────────────\n const [sessionStates, setSessionStates] = useState<Record<string, SessionDisplayState>>({});\n const [stickToBottom, setStickToBottom] = useState(true);\n const [newMessageCount, setNewMessageCount] = useState(0);\n\n // Set of messageIDs the user has already accepted (or that arrived\n // via SSE) — used by the opencode path to dedupe optimistic + echo.\n const [seenOpencodeMessages, setSeenOpencodeMessages] = useState<Set<string>>(new Set());\n\n // Backwards-compat alias (chat.sendSession delete etc. used `pinned` historically).\n const [pinned, setPinned] = useState<Set<number>>(new Set());\n\n const listRef = useRef<HTMLDivElement>(null);\n const toastRef = useRef<\n | {\n error: (msg: string) => void;\n success: (msg: string) => void;\n info: (msg: string) => void;\n warning: (msg: string) => void;\n }\n | null\n >(null);\n\n // ── SSE lifecycle refs ──────────────────────────────────────────────────────\n const opencodeEsRef = useRef<EventSource | null>(null);\n const sseReconnectAttemptRef = useRef(0);\n const sseReconnectTimerRef = useRef<number | null>(null);\n // The id of the session we're CURRENTLY streaming. When the user\n // switches sessions, this id changes and the SSE handler short-\n // circuits any in-flight appends.\n const sseCurrentSessionIdRef = useRef<string | null>(null);\n // Whether the SSE connection should be auto-reconnected on error.\n // Set to false on explicit disconnect (session switch / unmount).\n const sseAutoReconnectRef = useRef(false);\n\n /** Append `msg` to `opencodeMessages`, replacing any prior\n * optimistic entry whose content matches or whose ID matches. */\n const appendOpencodeMessage = useCallback((msg: ChatMessage) => {\n if (!msg) return;\n setOpencodeMessages((cur) => {\n // Existing exact-ID match: skip.\n if (msg.id && cur.some((m) => m.id === msg.id)) return cur;\n // Optimistic placeholder (starts with msg_) with the same role\n // and content: replace it. This is how opencode's echo of our\n // own message gets deduplicated against the optimistic copy.\n const idx = cur.findIndex(\n (m) =>\n typeof m.id === 'string' &&\n m.id.startsWith('msg_') &&\n m.role === msg.role &&\n (m.content || '') === (msg.content || ''),\n );\n if (idx >= 0) {\n const next = cur.slice();\n next[idx] = { ...msg, id: msg.id ?? cur[idx].id };\n return next;\n }\n return [...cur, msg];\n });\n }, []);\n\n /** Mark a messageID as seen — used to dedupe echo rounds. */\n const markSeen = useCallback((id: string | undefined) => {\n if (!id) return;\n setSeenOpencodeMessages((cur) => {\n if (cur.has(id)) return cur;\n const next = new Set(cur);\n next.add(id);\n return next;\n });\n }, []);\n\n // ── Helpers to mutate per-session state ────────────────────────────────────\n const setSessionDisplayState = useCallback(\n (id: string, patch: Partial<SessionDisplayState>) => {\n setSessionStates((cur) => {\n const prev: SessionDisplayState =\n cur[id] ?? { state: 'idle', unread: 0, pinned: false };\n return { ...cur, [id]: { ...prev, ...patch } };\n });\n },\n [],\n );\n\n const markSessionStreaming = useCallback(\n (id: string, streaming: boolean) => {\n setSessionDisplayState(id, { state: streaming ? 'streaming' : 'idle' });\n },\n [setSessionDisplayState],\n );\n\n /** ─── Toast binding (set by the consuming view via `setToast`) ──── */\n const setToast = useCallback(\n (toast: typeof toastRef.current) => {\n toastRef.current = toast;\n },\n [],\n );\n\n // ── Load bizar chat ─────────────────────────────────────────────────────────\n const loadChat = useCallback(\n async (sid?: string) => {\n try {\n const url = sid\n ? `/chat?session=${encodeURIComponent(sid)}`\n : '/chat?limit=200';\n const data = await api.get<ChatResponse>(url);\n setBizarMessages(data.messages || []);\n setSessions(data.sessions || []);\n } catch (err) {\n toastRef.current?.error(\n `Chat load failed: ${(err as Error).message}`,\n );\n } finally {\n setLoading(false);\n }\n },\n [],\n );\n\n const loadTaskChat = useCallback(async (taskId: string) => {\n setLoading(true);\n try {\n const data = await api.get<{\n sessionId?: string;\n messages: ChatMessage[];\n }>(`/tasks/${encodeURIComponent(taskId)}/chat`);\n setBizarMessages(data.messages || []);\n setSessionId(data.sessionId || taskId);\n } catch (err) {\n toastRef.current?.error(\n `Task chat load failed: ${(err as Error).message}`,\n );\n } finally {\n setLoading(false);\n }\n }, []);\n\n const refreshSessions = useCallback(async () => {\n try {\n const data = await api.get<{ sessions: ChatSession[] }>('/chat/sessions');\n setSessions(data.sessions || []);\n } catch {\n /* best-effort */\n }\n }, []);\n\n const refreshOpencodeSessions = useCallback(async () => {\n try {\n const data = await api.get<{ sessions: ChatSession[] }>(\n '/opencode-sessions',\n );\n // Enrich with source marker.\n const enriched: ChatSession[] = (data.sessions || []).map((s) => ({\n ...s,\n source: 'opencode' as const,\n title: s.title || s.id,\n }));\n setOpencodeSessions(enriched);\n } catch {\n /* best-effort */\n }\n }, []);\n\n // ── Opencode SSE connection ─────────────────────────────────────────────────\n //\n // Single source of truth for the EventSource lifecycle. Callers:\n // - `loadOpencodeSession(id)` — close any current, open a new one.\n // - `closeOpencodeSession()` — close and stop auto-reconnect.\n // - unmount — close and stop.\n //\n // On error / network drop, the EventSource triggers `onerror`. We\n // close the connection and schedule a reconnect (with exponential\n // backoff: 1s, 2s, 4s, …, capped at 30s) UNLESS `sseAutoReconnectRef`\n // is false (explicit disconnect).\n //\n const openSseForSession = useCallback(\n (id: string) => {\n // Close any existing connection first.\n if (opencodeEsRef.current) {\n try {\n opencodeEsRef.current.close();\n } catch {\n /* noop */\n }\n opencodeEsRef.current = null;\n }\n if (sseReconnectTimerRef.current !== null) {\n clearTimeout(sseReconnectTimerRef.current);\n sseReconnectTimerRef.current = null;\n }\n\n // Build the SSE URL. EventSource can't set headers, so we\n // pass the auth token as ?token= when set (loopback makes this\n // optional, but harmless).\n const tok = api.getToken();\n const url = tok\n ? `/api/opencode-sessions/${encodeURIComponent(id)}/stream?token=${encodeURIComponent(tok)}`\n : `/api/opencode-sessions/${encodeURIComponent(id)}/stream`;\n\n sseCurrentSessionIdRef.current = id;\n sseAutoReconnectRef.current = true;\n sseReconnectAttemptRef.current = 0;\n setOpencodeError(null);\n setOpencodeSuggestion(null);\n setOpencodeErrorInfo(null);\n\n const connect = () => {\n if (!sseAutoReconnectRef.current) return;\n if (sseCurrentSessionIdRef.current !== id) return;\n const es = new EventSource(url);\n opencodeEsRef.current = es;\n\n // Helper: dispatch one parsed envelope to local state.\n const onEnvelope = (evt: OpencodeSseEnvelope | null) => {\n if (!evt || !evt.type) return;\n // Drop events for OTHER sessions even though the proxy\n // already filters — defense in depth.\n if (evt.sessionID && evt.sessionID !== id) return;\n\n const ts = new Date().toISOString();\n const sidForState = id;\n\n if (\n evt.type === 'message.user.created' ||\n evt.type === 'message.user.updated'\n ) {\n const payload = evt.data as\n | { messageID?: string; data?: { content?: string } }\n | undefined;\n appendOpencodeMessage({\n id: payload?.messageID ?? evt.messageID,\n role: 'user',\n content: payload?.data?.content ?? '',\n ts,\n });\n markSeen(payload?.messageID ?? evt.messageID);\n return;\n }\n if (\n evt.type === 'message.assistant.created' ||\n evt.type === 'message.assistant.updated'\n ) {\n const payload = evt.data as\n | { messageID?: string; data?: { content?: string } }\n | undefined;\n appendOpencodeMessage({\n id: payload?.messageID ?? evt.messageID,\n role: 'assistant',\n content: payload?.data?.content ?? '',\n ts,\n });\n markSeen(payload?.messageID ?? evt.messageID);\n markSessionStreaming(sidForState, true);\n return;\n }\n if (evt.type === 'message.part.updated') {\n const payload = evt.data as\n | {\n part?: { type?: string; text?: string };\n messageID?: string;\n id?: string;\n }\n | undefined;\n const text = payload?.part?.text ?? '';\n if (!text) return;\n appendOpencodeMessage({\n id: payload?.id ?? payload?.messageID ?? evt.messageID,\n role: 'assistant',\n content: text,\n ts,\n });\n markSeen(payload?.id ?? payload?.messageID ?? evt.messageID);\n markSessionStreaming(sidForState, true);\n return;\n }\n if (\n evt.type === 'session.idle' ||\n evt.type === 'message.assistant.completed' ||\n evt.type === 'session.completed'\n ) {\n markSessionStreaming(sidForState, false);\n return;\n }\n if (\n evt.type === 'session.awaiting' ||\n evt.type === 'session.awaiting_input' ||\n evt.type === 'message.assistant.awaiting'\n ) {\n setSessionDisplayState(sidForState, { state: 'awaiting' });\n return;\n }\n if (evt.type === 'session.error') {\n setOpencodeError(\n (evt.data as { message?: string } | undefined)?.message ||\n 'opencode session error',\n );\n return;\n }\n // Other event types are intentionally ignored — the proxy\n // is canonical, we don't need to project every upstream\n // event into local state.\n };\n\n // Stable parser — the server may send both `{type: \"x\"}` and\n // `sync` envelopes with a `.<n>` version suffix. We accept both.\n const parseData = (raw: string | null | undefined):\n | OpencodeSseEnvelope\n | null => {\n if (!raw) return null;\n try {\n const parsed = JSON.parse(raw) as Record<string, unknown>;\n // Direct shape: {type, properties: {...}}\n if (typeof parsed.type === 'string') {\n const props = parsed.properties as\n | Record<string, unknown>\n | undefined;\n const data = (parsed.data as Record<string, unknown> | undefined) ?? props;\n const sessionID = (\n typeof parsed.sessionID === 'string'\n ? parsed.sessionID\n : typeof props?.sessionID === 'string'\n ? (props.sessionID as string)\n : typeof data?.sessionID === 'string'\n ? (data.sessionID as string)\n : undefined\n );\n const messageID = (\n typeof parsed.messageID === 'string'\n ? parsed.messageID\n : typeof props?.messageID === 'string'\n ? (props.messageID as string)\n : typeof data?.messageID === 'string'\n ? (data.messageID as string)\n : undefined\n );\n let type = parsed.type as string;\n const m = /\\.\\d+$/.exec(type);\n if (m) type = type.slice(0, m.index);\n return { type, sessionID, messageID, data: parsed };\n }\n // Sync envelope: {type: \"sync\", syncEvent: {...}}\n if (\n parsed.type === 'sync' &&\n parsed.syncEvent &&\n typeof parsed.syncEvent === 'object'\n ) {\n const inner = parsed.syncEvent as Record<string, unknown>;\n let type = (inner.type as string) || '';\n const m = /\\.\\d+$/.exec(type);\n if (m) type = type.slice(0, m.index);\n const innerData = (inner.data as Record<string, unknown>) || {};\n return {\n type,\n sessionID:\n typeof innerData.sessionID === 'string'\n ? innerData.sessionID\n : undefined,\n messageID:\n typeof innerData.messageID === 'string'\n ? innerData.messageID\n : undefined,\n data: inner,\n };\n }\n return null;\n } catch {\n return null;\n }\n };\n\n // register handlers per named event type\n const namedTypes = [\n 'message.user.created',\n 'message.user.updated',\n 'message.assistant.created',\n 'message.assistant.updated',\n 'message.part.updated',\n 'session.idle',\n 'message.assistant.completed',\n 'session.completed',\n 'session.awaiting',\n 'session.awaiting_input',\n 'message.assistant.awaiting',\n 'session.error',\n ];\n for (const name of namedTypes) {\n es.addEventListener(name, (e: MessageEvent) => {\n onEnvelope(parseData((e as MessageEvent).data));\n });\n }\n\n es.onerror = () => {\n if (sseCurrentSessionIdRef.current !== id) return;\n if (!sseAutoReconnectRef.current) {\n try {\n es.close();\n } catch {\n /* noop */\n }\n opencodeEsRef.current = null;\n return;\n }\n try {\n es.close();\n } catch {\n /* noop */\n }\n opencodeEsRef.current = null;\n\n const nextAttempt = sseReconnectAttemptRef.current + 1;\n sseReconnectAttemptRef.current = nextAttempt;\n // Backoff: 1s, 2s, 4s, 8s, … capped at 30s.\n const delayMs = Math.min(30_000, 1000 * 2 ** (nextAttempt - 1));\n\n if (sseReconnectTimerRef.current !== null) {\n clearTimeout(sseReconnectTimerRef.current);\n }\n sseReconnectTimerRef.current = window.setTimeout(() => {\n sseReconnectTimerRef.current = null;\n if (\n sseAutoReconnectRef.current &&\n sseCurrentSessionIdRef.current === id\n ) {\n connect();\n }\n }, delayMs);\n };\n\n es.onopen = () => {\n // Successful (re)connect — reset backoff.\n sseReconnectAttemptRef.current = 0;\n setOpencodeError(null);\n setOpencodeSuggestion(null);\n setOpencodeErrorInfo(null);\n };\n };\n\n connect();\n },\n [appendOpencodeMessage, markSeen, markSessionStreaming, setSessionDisplayState],\n );\n\n const closeOpencodeSession = useCallback(() => {\n // Disable auto-reconnect first so the onerror handler short-circuits.\n sseAutoReconnectRef.current = false;\n sseCurrentSessionIdRef.current = null;\n if (sseReconnectTimerRef.current !== null) {\n clearTimeout(sseReconnectTimerRef.current);\n sseReconnectTimerRef.current = null;\n }\n if (opencodeEsRef.current) {\n try {\n opencodeEsRef.current.close();\n } catch {\n /* noop */\n }\n opencodeEsRef.current = null;\n }\n setOpencodeMessages([]);\n setSeenOpencodeMessages(new Set());\n setOpencodeError(null);\n setOpencodeSuggestion(null);\n setOpencodeErrorInfo(null);\n setActiveOpencodeSessionId(null);\n setActiveSource('bizar');\n }, []);\n\n const loadOpencodeSession = useCallback(\n async (id: string) => {\n // Close any previous opencode session cleanly.\n sseAutoReconnectRef.current = false;\n if (opencodeEsRef.current) {\n try {\n opencodeEsRef.current.close();\n } catch {\n /* noop */\n }\n opencodeEsRef.current = null;\n }\n if (sseReconnectTimerRef.current !== null) {\n clearTimeout(sseReconnectTimerRef.current);\n sseReconnectTimerRef.current = null;\n }\n\n setOpencodeError(null);\n setOpencodeSuggestion(null);\n setOpencodeErrorInfo(null);\n setLoading(true);\n\n try {\n const data = await api.get<{ messages: ChatMessage[] }>(\n `/opencode-sessions/${encodeURIComponent(id)}/messages`,\n );\n setOpencodeMessages(data.messages || []);\n } catch (err) {\n const msg = (err as Error).message;\n // v5.0.0 — bug #4: surface the server's structured error\n // envelope so the UI can render a useful suggestion instead\n // of the raw upstream error string. The server returns\n // `{error, message, cause?, status?, suggestion?}` for every\n // non-2xx response (see serve-info.mjs + routes/opencode-\n // session-detail.mjs).\n let info: OpencodeErrorInfo | null = null;\n if (err instanceof ApiError && err.data && typeof err.data === 'object') {\n const d = err.data as {\n error?: string;\n message?: string;\n cause?: string;\n status?: number;\n suggestion?: string;\n };\n // canRetry: 503/502 are transient (plugin offline /\n // upstream hiccup); the operator can fix the plugin and\n // try again. 400 is a hard bad_request — retrying with the\n // same payload won't help.\n const code = d.error || 'unknown';\n const canRetry =\n code !== 'bad_request' &&\n code !== 'too_many_subscribers' &&\n code !== 'unauthorized';\n info = {\n type: 'session_load_failed',\n code,\n message: d.message || msg,\n cause: d.cause,\n status: d.status,\n suggestion: d.suggestion ?? null,\n canRetry,\n };\n } else if (err instanceof ApiError) {\n info = {\n type: 'session_load_failed',\n code: 'unknown',\n message: msg,\n suggestion: null,\n canRetry: true,\n };\n }\n // Legacy fields — kept so the existing Chat.tsx thread-header\n // error display continues to work unchanged.\n setOpencodeError(\n info\n ? `Opencode session load failed (${info.code}): ${info.message}`\n : `Opencode session load failed: ${msg}`,\n );\n setOpencodeSuggestion(info?.suggestion ?? null);\n setOpencodeErrorInfo(info);\n toastRef.current?.error(\n info\n ? `Opencode session load failed: ${info.message}`\n : `Opencode session load failed: ${msg}`,\n );\n setOpencodeMessages([]);\n } finally {\n setLoading(false);\n }\n\n setActiveOpencodeSessionId(id);\n setActiveSource('opencode');\n markSessionStreaming(id, false);\n openSseForSession(id);\n },\n [markSessionStreaming, openSseForSession],\n );\n\n // ── Select a bizar session ──────────────────────────────────────────────────\n const selectBizarSession = useCallback(\n (sid: string) => {\n closeOpencodeSession();\n setSessionId(sid);\n setActiveSource('bizar');\n loadChat(sid);\n // Reset unread for the session being opened.\n setSessionDisplayState(sid, { unread: 0 });\n },\n [closeOpencodeSession, loadChat, setSessionDisplayState],\n );\n\n // ── Create session (opencode primary; bizar fallback) ──────────────────────\n const onCreateSession = useCallback(async (): Promise<{\n ok: boolean;\n source?: 'bizar' | 'opencode';\n id?: string;\n }> => {\n if (busy.create) return { ok: false };\n const agent = settings.defaultAgent || 'odin';\n setBusy((b) => ({ ...b, create: true }));\n try {\n // Try to create a real opencode session first. This is the\n // primary create path: it gives the user a full conversation\n // roundtrip with SSE streaming, abort, etc.\n try {\n const created = await api.post<{\n id: string;\n title: string;\n agent: string;\n directory: string;\n createdAt: number;\n }>('/opencode-sessions/new', { agent });\n await refreshOpencodeSessions();\n // Open the newly created session right away so the user is\n // dropped into a fresh empty thread, not stuck on the\n // previous one.\n await loadOpencodeSession(created.id);\n toastRef.current?.success(`Session ${created.id} created.`);\n return { ok: true, source: 'opencode', id: created.id };\n } catch (opencodeErr) {\n // Opencode plugin offline (503) — fall back to the local\n // per-project .jsonl session. Otherwise, surface the error.\n if (!(opencodeErr instanceof ApiError) || opencodeErr.status !== 503) {\n toastRef.current?.error(\n `Create failed: ${(opencodeErr as Error).message}`,\n );\n return { ok: false };\n }\n }\n\n // Fallback: local jsonl session.\n if (!snapshot.activeProject) {\n toastRef.current?.error(\n 'Pick a project in Overview to scope chat sessions.',\n );\n return { ok: false };\n }\n const created = await api.post<ChatSession>('/chat/sessions', {});\n setSessionId(created.id);\n setBizarMessages([]);\n setPinned(new Set());\n setSessionDisplayState(created.id, { state: 'idle', unread: 0 });\n await loadChat(created.id);\n refreshSessions().catch(() => {});\n toastRef.current?.success(`Session ${created.id} created.`);\n return { ok: true, source: 'bizar', id: created.id };\n } catch (err) {\n toastRef.current?.error(`Create failed: ${(err as Error).message}`);\n return { ok: false };\n } finally {\n setBusy((b) => ({ ...b, create: false }));\n }\n }, [\n busy.create,\n settings.defaultAgent,\n snapshot.activeProject,\n refreshOpencodeSessions,\n loadOpencodeSession,\n loadChat,\n refreshSessions,\n setSessionDisplayState,\n ]);\n\n // ── Send message ────────────────────────────────────────────────────────────\n const onSend = useCallback(\n async (\n message: string,\n agent: string,\n model: string,\n attachments?: string[],\n ): Promise<{ ok: boolean }> => {\n if (busy.send) return { ok: false };\n const trimmed = message.trim();\n if (!trimmed) return { ok: false };\n\n if (activeSource === 'opencode' && activeOpencodeSessionId) {\n const sid = activeOpencodeSessionId;\n const optimisticId = `msg_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`;\n const optimistic: ChatMessage = {\n id: optimisticId,\n role: 'user',\n content: trimmed,\n agent,\n ts: new Date().toISOString(),\n };\n appendOpencodeMessage(optimistic);\n markSeen(optimisticId);\n setBusy((b) => ({ ...b, send: true }));\n try {\n await api.post<{ ok: boolean; messageId: string }>(\n `/opencode-sessions/${encodeURIComponent(sid)}/send`,\n {\n message: trimmed,\n agent,\n },\n );\n markSessionStreaming(sid, true);\n return { ok: true };\n } catch (err) {\n // Remove optimistic on failure.\n setOpencodeMessages((cur) => cur.filter((m) => m.id !== optimisticId));\n toastRef.current?.error(\n `Send failed: ${(err as Error).message}`,\n );\n return { ok: false };\n } finally {\n setBusy((b) => ({ ...b, send: false }));\n }\n }\n\n // Bizar fallback.\n const optimistic: ChatMessage = {\n role: 'user',\n content: trimmed,\n agent,\n ts: new Date().toISOString(),\n };\n setBizarMessages((cur) => [...cur, optimistic]);\n setBusy((b) => ({ ...b, send: true }));\n markSessionStreaming(sessionId, true);\n try {\n const response = await api.post<{ messages?: ChatMessage[] }>(\n '/chat',\n {\n message: trimmed,\n agent,\n model,\n attachments: attachments || [],\n },\n );\n if (response?.messages) {\n for (const msg of response.messages) {\n if (msg.role === 'assistant') {\n setBizarMessages((cur) => {\n const exists = cur.some((m) => m.ts === msg.ts);\n return exists ? cur : [...cur, msg];\n });\n }\n }\n } else {\n toastRef.current?.info(\n 'Still processing… the response will appear shortly.',\n );\n }\n return { ok: true };\n } catch (err) {\n setBizarMessages((cur) => cur.filter((m) => m !== optimistic));\n toastRef.current?.error(\n `Send failed: ${(err as Error).message}`,\n );\n return { ok: false };\n } finally {\n setBusy((b) => ({ ...b, send: false }));\n markSessionStreaming(sessionId, false);\n }\n },\n [\n activeSource,\n activeOpencodeSessionId,\n appendOpencodeMessage,\n markSeen,\n markSessionStreaming,\n sessionId,\n busy.send,\n ],\n );\n\n const onRegenerate = useCallback(\n async (messageId: string) => {\n if (!sessionId) {\n toastRef.current?.error('No active session to regenerate.');\n return;\n }\n try {\n await api.post('/chat/regenerate', { sessionId, messageId });\n await loadChat(sessionId);\n } catch (err) {\n toastRef.current?.error(\n `Regenerate failed: ${(err as Error).message}`,\n );\n }\n },\n [sessionId, loadChat],\n );\n\n const deleteMessage = useCallback(\n (idx: number) => {\n if (activeSource === 'opencode') {\n setOpencodeMessages((cur) => cur.filter((_, i) => i !== idx));\n } else {\n setBizarMessages((cur) => cur.filter((_, i) => i !== idx));\n }\n },\n [activeSource],\n );\n\n const togglePin = useCallback((idx: number) => {\n setPinned((cur) => {\n const next = new Set(cur);\n if (next.has(idx)) next.delete(idx);\n else next.add(idx);\n return next;\n });\n }, []);\n\n const copyMessage = useCallback((m: ChatMessage) => {\n const text = m.content || m.message || '';\n try {\n navigator.clipboard?.writeText(text).then(\n () => toastRef.current?.success('Copied.'),\n () => toastRef.current?.error('Copy failed.'),\n );\n } catch {\n toastRef.current?.error('Copy failed.');\n }\n }, []);\n\n // ── Jump-to-latest ──────────────────────────────────────────────────────────\n const handleScroll = useCallback(() => {\n const el = listRef.current;\n if (!el) return;\n const distFromBottom = el.scrollHeight - el.scrollTop - el.clientHeight;\n const atBottom = distFromBottom < 32;\n setStickToBottom(atBottom);\n if (atBottom) setNewMessageCount(0);\n }, []);\n\n const jumpToLatest = useCallback(() => {\n const el = listRef.current;\n if (!el) return;\n el.scrollTop = el.scrollHeight;\n setStickToBottom(true);\n setNewMessageCount(0);\n }, []);\n\n // ── Session rename / delete (rail row menu + info panel actions) ────────────\n const renameSession = useCallback(\n async (id: string, title: string): Promise<boolean> => {\n if (busy.rename) return false;\n setBusy((b) => ({ ...b, rename: true }));\n try {\n const trimmed = title.trim();\n if (!trimmed) {\n toastRef.current?.error('Title cannot be empty.');\n return false;\n }\n // Try the opencode path first if this id is in the opencode\n // session list, otherwise the local path. We dispatch by\n // inspecting the in-memory set (avoids a round trip).\n const isOpencode = opencodeSessions.some((s) => s.id === id);\n if (isOpencode) {\n await api.patch<{ id: string; title: string }>(\n `/opencode-sessions/${encodeURIComponent(id)}`,\n { title: trimmed },\n );\n setOpencodeSessions((cur) =>\n cur.map((s) => (s.id === id ? { ...s, title: trimmed } : s)),\n );\n } else {\n await api.post(\n `/chat/sessions/${encodeURIComponent(id)}/rename`,\n { title: trimmed },\n );\n setSessions((cur) =>\n cur.map((s) => (s.id === id ? { ...s, title: trimmed } : s)),\n );\n }\n toastRef.current?.success('Renamed.');\n return true;\n } catch (err) {\n toastRef.current?.error(\n `Rename failed: ${(err as Error).message}`,\n );\n return false;\n } finally {\n setBusy((b) => ({ ...b, rename: false }));\n }\n },\n [busy.rename, opencodeSessions],\n );\n\n const deleteSession = useCallback(\n async (id: string): Promise<boolean> => {\n if (busy.delete) return false;\n setBusy((b) => ({ ...b, delete: true }));\n try {\n const isOpencode = opencodeSessions.some((s) => s.id === id);\n if (isOpencode) {\n await api.del(`/opencode-sessions/${encodeURIComponent(id)}`);\n setOpencodeSessions((cur) => cur.filter((s) => s.id !== id));\n if (activeOpencodeSessionId === id) {\n closeOpencodeSession();\n }\n } else {\n await api.del(`/chat/sessions/${encodeURIComponent(id)}`);\n setSessions((cur) => cur.filter((s) => s.id !== id));\n if (sessionId === id) {\n setSessionId('');\n setBizarMessages([]);\n }\n }\n setSessionStates((cur) => {\n const next = { ...cur };\n delete next[id];\n return next;\n });\n toastRef.current?.success('Session deleted.');\n return true;\n } catch (err) {\n toastRef.current?.error(\n `Delete failed: ${(err as Error).message}`,\n );\n return false;\n } finally {\n setBusy((b) => ({ ...b, delete: false }));\n }\n },\n [\n busy.delete,\n opencodeSessions,\n activeOpencodeSessionId,\n closeOpencodeSession,\n sessionId,\n ],\n );\n\n // ── Auto-scroll on new messages ─────────────────────────────────────────────\n useEffect(() => {\n if (listRef.current) {\n if (stickToBottom) {\n listRef.current.scrollTop = listRef.current.scrollHeight;\n } else {\n setNewMessageCount((n) => n + 1);\n }\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [bizarMessages, opencodeMessages]);\n\n // ── Cleanup SSE on unmount ──────────────────────────────────────────────────\n useEffect(() => {\n return () => {\n sseAutoReconnectRef.current = false;\n sseCurrentSessionIdRef.current = null;\n if (sseReconnectTimerRef.current !== null) {\n clearTimeout(sseReconnectTimerRef.current);\n sseReconnectTimerRef.current = null;\n }\n if (opencodeEsRef.current) {\n try {\n opencodeEsRef.current.close();\n } catch {\n /* noop */\n }\n opencodeEsRef.current = null;\n }\n };\n }, []);\n\n // ── Initial load ────────────────────────────────────────────────────────────\n useEffect(() => {\n if (initialTaskId) {\n loadTaskChat(initialTaskId);\n } else {\n loadChat();\n }\n refreshOpencodeSessions();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n // ── Listen for bizar:setChatTask events ────────────────────────────────────\n useEffect(() => {\n const handler = (e: Event) => {\n const taskId = (e as CustomEvent<{ taskId: string }>).detail?.taskId;\n if (taskId) loadTaskChat(taskId);\n };\n window.addEventListener('bizar:setChatTask', handler);\n return () => window.removeEventListener('bizar:setChatTask', handler);\n }, [loadTaskChat]);\n\n // ── WebSocket for live bizar updates ────────────────────────────────────────\n useEffect(() => {\n let closed = false;\n import('../../lib/ws').then(({ Ws }) => {\n if (closed) return;\n const ws = new Ws();\n ws.on((msg: { type: string; message?: ChatMessage }) => {\n if (msg.type !== 'chat:message') return;\n const next = msg.message;\n if (!next) return;\n setBizarMessages((cur) => {\n const exists = cur.some((m) => m.ts === next.ts);\n return exists ? cur : [...cur, next];\n });\n });\n });\n return () => {\n closed = true;\n };\n }, []);\n\n return {\n // ── State ────────────────────────────────────────────────────────────────\n messages:\n activeSource === 'opencode' ? opencodeMessages : bizarMessages,\n bizarMessages,\n opencodeMessages,\n sessions,\n opencodeSessions,\n sessionId,\n setSessionId,\n loading,\n sending: busy.send,\n pinned,\n listRef,\n // ── Opencode state ──────────────────────────────────────────────────────\n activeSource,\n activeOpencodeSessionId,\n opencodeError,\n opencodeSuggestion,\n /**\n * v5.0.0 — bug #4: structured error info consumed by\n * `ChatInfoPanel`. `null` when no error is active.\n */\n opencodeErrorInfo,\n /**\n * v5.0.0 — bug #4: retry helper exposed for `ChatInfoPanel`'s\n * Retry button. Calls `loadOpencodeSession` on the currently\n * active opencode session (or no-op if none is active).\n */\n retryOpencodeSession: () => {\n if (activeOpencodeSessionId) void loadOpencodeSession(activeOpencodeSessionId);\n },\n // ── v3.22 — per-session display state ──────────────────────────────────\n sessionStates,\n busy,\n getSessionDisplay: (s: ChatSession) => ({\n ...s,\n state: sessionStates[s.id]?.state ?? 'idle',\n unread: sessionStates[s.id]?.unread ?? 0,\n pinned: sessionStates[s.id]?.pinned ?? false,\n tree: sessionStates[s.id]?.tree,\n }),\n // ── Jump-to-latest ──────────────────────────────────────────────────────\n stickToBottom,\n newMessageCount,\n handleScroll,\n jumpToLatest,\n // ── Toast integration ──────────────────────────────────────────────────\n setToast,\n // ── Actions ─────────────────────────────────────────────────────────────\n loadChat,\n loadTaskChat,\n refreshSessions,\n refreshOpencodeSessions,\n loadOpencodeSession,\n closeOpencodeSession,\n selectBizarSession,\n onCreateSession,\n onSend,\n onRegenerate,\n deleteMessage,\n togglePin,\n copyMessage,\n renameSession,\n deleteSession,\n seenOpencodeMessages,\n };\n}\n","// src/components/chat/useSlashCommands.ts — filter slash-command suggestions from mods.\n\nimport { useMemo, useState } from 'react';\nimport type { Mod, Snapshot } from '../../lib/types';\n\nexport type SlashCommand = { cmd: string; desc: string; mod?: string };\n\nconst BUILTIN_COMMANDS: SlashCommand[] = [\n { cmd: '/visual-plan [on|off|status]', desc: 'Toggle or view visual plan mode' },\n { cmd: '/plan new <slug> [template]', desc: 'Create a new plan' },\n { cmd: '/plan list', desc: 'List all plans' },\n { cmd: '/plan open <slug>', desc: 'Open a plan in the viewer' },\n { cmd: '/plan status <slug> <status>', desc: 'Set plan status' },\n { cmd: '/plan get <slug>', desc: 'Fetch plan canvas' },\n { cmd: '/plan add <slug> --title T --type kind', desc: 'Add element to a plan' },\n { cmd: '/plan update <slug> <id>', desc: 'Update a plan element' },\n { cmd: '/plan delete <slug> <id>', desc: 'Delete a plan element' },\n { cmd: '/plan comment <slug> [id] \"text\"', desc: 'Add a comment' },\n { cmd: '/plan comments <slug> [id]', desc: 'Read plan comments' },\n { cmd: '/bizar', desc: 'Launch Bizar dashboard' },\n { cmd: '/bizar <args>', desc: 'Route via Bizar menu' },\n { cmd: '/audit', desc: 'Run security audit' },\n { cmd: '/explain <q>', desc: 'Read-only code Q&A' },\n { cmd: '/init', desc: 'Initialize .bizar/ in this project' },\n { cmd: '/learn', desc: 'Extract patterns from session' },\n { cmd: '/pr-review', desc: 'PR review' },\n { cmd: '/help | /commands', desc: 'Show all Bizar commands' },\n];\n\nexport function useSlashCommands(snapshot: Snapshot) {\n const [query, setQuery] = useState('');\n\n const allCommands = useMemo<SlashCommand[]>(() => [\n ...BUILTIN_COMMANDS,\n ...(snapshot.mods || []).flatMap((m: Mod) =>\n m.entry?.command ? [{ cmd: `/${m.id}`, desc: m.description || m.name, mod: m.id }] : [],\n ),\n ], [snapshot.mods]);\n\n const suggestions = useMemo(() => {\n if (!query.startsWith('/') || query.includes(' ')) return [];\n const q = query.toLowerCase();\n return allCommands.filter((c) => c.cmd.toLowerCase().startsWith(q)).slice(0, 6);\n }, [query, allCommands]);\n\n return { allCommands, suggestions, setQuery };\n}","// src/mobile/MobileBottomNav.tsx — bottom tab bar for mobile.\nimport type { LucideIcon } from 'lucide-react';\nimport { cn } from '../lib/utils';\n\nexport type MobileTab = {\n id: string;\n label: string;\n icon: LucideIcon;\n};\n\ntype Props = {\n tabs: MobileTab[];\n activeTab: string;\n onChange: (id: string) => void;\n};\n\nexport function MobileBottomNav({ tabs, activeTab, onChange }: Props) {\n return (\n <nav className=\"mobile-bottom-nav\" role=\"navigation\" aria-label=\"Mobile navigation\">\n {tabs.map((t) => {\n const Icon = t.icon;\n return (\n <button\n key={t.id}\n type=\"button\"\n className={cn('mobile-nav-btn', activeTab === t.id && 'mobile-nav-btn-active')}\n onClick={() => onChange(t.id)}\n aria-label={t.label}\n aria-current={activeTab === t.id ? 'page' : undefined}\n >\n <Icon size={22} />\n <span className=\"mobile-nav-label\">{t.label}</span>\n </button>\n );\n })}\n </nav>\n );\n}\n","// src/mobile/components/MobileBottomSheet.tsx — slide-up bottom sheet for mobile.\nimport { useEffect, useId, useRef, type PointerEvent as ReactPointerEvent } from 'react';\nimport { X } from 'lucide-react';\n\ntype Props = {\n open: boolean;\n onClose: () => void;\n title?: string;\n children: React.ReactNode;\n actions?: React.ReactNode;\n maxHeight?: string;\n};\n\nexport function MobileBottomSheet({ open, onClose, title, children, actions, maxHeight = '85vh' }: Props) {\n const sheetRef = useRef<HTMLDivElement>(null);\n const closeButtonRef = useRef<HTMLButtonElement>(null);\n const titleId = useId();\n const lastActiveRef = useRef<HTMLElement | null>(null);\n const dragStartYRef = useRef<number | null>(null);\n const dragDeltaRef = useRef(0);\n const scrollLockYRef = useRef(0);\n\n const getFocusable = () => {\n return sheetRef.current?.querySelectorAll<HTMLElement>(\n 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])',\n ) ?? [];\n };\n\n // Close on ESC\n useEffect(() => {\n if (!open) return;\n lastActiveRef.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;\n\n const onKey = (e: KeyboardEvent) => {\n if (e.key === 'Escape') onClose();\n if (e.key !== 'Tab') return;\n\n const focusable = Array.from(getFocusable());\n if (focusable.length === 0) {\n e.preventDefault();\n sheetRef.current?.focus();\n return;\n }\n\n const first = focusable[0];\n const last = focusable[focusable.length - 1];\n const active = document.activeElement as HTMLElement | null;\n\n if (e.shiftKey) {\n if (!active || active === first || active === sheetRef.current) {\n e.preventDefault();\n last?.focus();\n }\n return;\n }\n\n if (active === last) {\n e.preventDefault();\n first?.focus();\n }\n };\n\n window.addEventListener('keydown', onKey);\n requestAnimationFrame(() => {\n closeButtonRef.current?.focus() ?? sheetRef.current?.focus();\n });\n\n return () => {\n window.removeEventListener('keydown', onKey);\n lastActiveRef.current?.focus();\n };\n }, [open, onClose]);\n\n // Prevent body scroll when open\n useEffect(() => {\n if (open) {\n scrollLockYRef.current = window.scrollY;\n document.documentElement.style.overflow = 'hidden';\n document.body.style.overflow = 'hidden';\n document.body.style.position = 'fixed';\n document.body.style.top = `-${scrollLockYRef.current}px`;\n document.body.style.left = '0';\n document.body.style.right = '0';\n document.body.style.width = '100%';\n } else {\n document.documentElement.style.overflow = '';\n document.body.style.overflow = '';\n document.body.style.position = '';\n document.body.style.top = '';\n document.body.style.left = '';\n document.body.style.right = '';\n document.body.style.width = '';\n }\n return () => {\n document.documentElement.style.overflow = '';\n document.body.style.overflow = '';\n document.body.style.position = '';\n document.body.style.top = '';\n document.body.style.left = '';\n document.body.style.right = '';\n document.body.style.width = '';\n window.scrollTo(0, scrollLockYRef.current);\n };\n }, [open]);\n\n if (!open) return null;\n\n const onPointerDown = (e: ReactPointerEvent<HTMLDivElement>) => {\n if (!e.isPrimary) return;\n dragStartYRef.current = e.clientY;\n dragDeltaRef.current = 0;\n sheetRef.current?.setPointerCapture(e.pointerId);\n };\n\n const onPointerMove = (e: ReactPointerEvent<HTMLDivElement>) => {\n if (dragStartYRef.current == null || !sheetRef.current) return;\n const delta = Math.max(0, e.clientY - dragStartYRef.current);\n dragDeltaRef.current = delta;\n sheetRef.current.style.transform = `translateY(${delta}px)`;\n };\n\n const endDrag = (e?: ReactPointerEvent<HTMLDivElement>) => {\n if (!sheetRef.current) return;\n if (e && sheetRef.current.hasPointerCapture(e.pointerId)) {\n sheetRef.current.releasePointerCapture(e.pointerId);\n }\n const delta = dragDeltaRef.current;\n sheetRef.current.style.transform = '';\n dragStartYRef.current = null;\n dragDeltaRef.current = 0;\n if (delta > 72) onClose();\n };\n\n return (\n <div className=\"mobile-sheet-overlay\" onClick={onClose}>\n <div\n ref={sheetRef}\n className=\"mobile-sheet\"\n style={{ maxHeight }}\n onClick={(e) => e.stopPropagation()}\n role=\"dialog\"\n aria-modal\n aria-label={title ? undefined : 'Sheet'}\n aria-labelledby={title ? titleId : undefined}\n tabIndex={-1}\n >\n {/* Drag handle */}\n <div\n className=\"mobile-sheet-handle\"\n onPointerDown={onPointerDown}\n onPointerMove={onPointerMove}\n onPointerUp={endDrag}\n onPointerCancel={endDrag}\n aria-hidden=\"true\"\n />\n\n <div className=\"mobile-sheet-header\">\n {title ? <h3 id={titleId} className=\"mobile-sheet-title\">{title}</h3> : <div />}\n <button\n ref={closeButtonRef}\n type=\"button\"\n className=\"mobile-icon-btn\"\n onClick={onClose}\n aria-label=\"Close\"\n >\n <X size={18} />\n </button>\n </div>\n\n <div className=\"mobile-sheet-content\">\n {children}\n </div>\n\n {actions && (\n <div className=\"mobile-sheet-actions\">\n {actions}\n </div>\n )}\n </div>\n </div>\n );\n}\n","// src/mobile/views/MobileNotifications.tsx — notifications bell + sheet.\nimport { useEffect, useState } from 'react';\nimport { Bell, CheckCheck, X } from 'lucide-react';\nimport { api } from '../../lib/api';\nimport type { Notification } from '../../lib/types';\nimport { MobileBottomSheet } from '../components/MobileBottomSheet';\n\ntype Props = {\n onSelect: (notification: Notification) => void;\n};\n\nfunction notifIcon(severity: string) {\n switch (severity) {\n case 'success': return '✓';\n case 'error': return '✗';\n case 'warning': return '⚠';\n default: return 'ℹ';\n }\n}\n\nfunction formatNotifTime(ts: string): string {\n const d = new Date(ts);\n const now = Date.now();\n const diff = now - d.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 d.toLocaleDateString();\n}\n\nexport function MobileNotifications({ onSelect }: Props) {\n const [open, setOpen] = useState(false);\n const [notifications, setNotifications] = useState<Notification[]>([]);\n const [unread, setUnread] = useState(0);\n\n const load = async () => {\n try {\n const data = await api.get<{ notifications: Notification[]; stats: { unread: number } }>('/notifications');\n setNotifications(data.notifications || []);\n setUnread(data.stats?.unread || 0);\n } catch {\n // best-effort\n }\n };\n\n useEffect(() => {\n load();\n }, []);\n\n useEffect(() => {\n if (open) load();\n }, [open]);\n\n const markAllRead = async () => {\n try {\n await api.post('/notifications/read-all');\n setNotifications((cur) => cur.map((n) => ({ ...n, read: true })));\n setUnread(0);\n } catch {\n // best-effort\n }\n };\n\n const markRead = async (id: string) => {\n try {\n await api.post(`/notifications/${encodeURIComponent(id)}/read`);\n setNotifications((cur) =>\n cur.map((n) => (n.id === id ? { ...n, read: true } : n)),\n );\n setUnread((u) => Math.max(0, u - 1));\n } catch {\n // best-effort\n }\n };\n\n return (\n <>\n <button\n type=\"button\"\n className=\"mobile-icon-btn mobile-notif-btn\"\n onClick={() => setOpen(true)}\n aria-label={`Notifications${unread > 0 ? ` (${unread} unread)` : ''}`}\n >\n <Bell size={20} />\n {unread > 0 && (\n <span className=\"mobile-notif-badge\">{unread > 9 ? '9+' : unread}</span>\n )}\n </button>\n\n <MobileBottomSheet\n open={open}\n onClose={() => setOpen(false)}\n title=\"Notifications\"\n actions={\n unread > 0 ? (\n <button type=\"button\" className=\"mobile-btn mobile-btn-secondary\" style={{ width: '100%' }} onClick={markAllRead}>\n <CheckCheck size={14} /> Mark all read\n </button>\n ) : undefined\n }\n >\n <div className=\"mobile-notif-list\">\n {notifications.length === 0 && (\n <div className=\"mobile-empty\">\n <Bell size={32} />\n <p>No notifications</p>\n </div>\n )}\n {notifications.map((n) => (\n <button\n key={n.id}\n type=\"button\"\n className={`mobile-notif-item ${n.read ? 'read' : 'unread'}`}\n onClick={() => {\n if (!n.read) markRead(n.id);\n onSelect(n);\n setOpen(false);\n }}\n >\n <span className={`mobile-notif-icon severity-${n.severity}`}>\n {notifIcon(n.severity)}\n </span>\n <div className=\"mobile-notif-body\">\n <span className=\"mobile-notif-title\">{n.title || n.message}</span>\n {n.message && n.title && (\n <span className=\"mobile-notif-text\">{n.message}</span>\n )}\n <span className=\"mobile-notif-time\">{formatNotifTime(n.ts)}</span>\n </div>\n {!n.read && <span className=\"mobile-notif-dot\" />}\n </button>\n ))}\n </div>\n </MobileBottomSheet>\n </>\n );\n}\n","// src/mobile/MobileTopbar.tsx — compact top bar with search, notifications, project selector.\nimport { useState } from 'react';\nimport { ChevronDown } from 'lucide-react';\nimport type { Snapshot } from '../lib/types';\nimport { api } from '../lib/api';\nimport { MobileNotifications } from './views/MobileNotifications';\nimport { MobileBottomSheet } from './components/MobileBottomSheet';\n\ntype Props = {\n activeTab: string;\n snapshot: Snapshot | null;\n onSearch: () => void;\n onNavigate: (type: string, id: string, meta?: Record<string, unknown> | null) => void;\n};\n\nconst TAB_LABELS: Record<string, string> = {\n activity: 'Activity',\n chat: 'Chat',\n tasks: 'Tasks',\n settings: 'Settings',\n more: 'More',\n plans: 'Plans',\n agents: 'Agents',\n skills: 'Skills',\n mods: 'Mods',\n schedules: 'Schedules',\n history: 'History',\n config: 'Config',\n 'plan-detail': 'Plan',\n 'agent-detail': 'Agent',\n 'task-detail': 'Task',\n};\n\nexport function MobileTopbar({ activeTab, snapshot, onSearch, onNavigate }: Props) {\n const [projectSheetOpen, setProjectSheetOpen] = useState(false);\n\n const handleProjectSelect = async (projectId: string) => {\n try {\n await api.post(`/projects/${encodeURIComponent(projectId)}/activate`);\n setProjectSheetOpen(false);\n // Force reload by navigating to activity\n window.location.reload();\n } catch {\n // best-effort\n }\n };\n\n return (\n <header className=\"mobile-topbar\">\n <div className=\"mobile-topbar-left\">\n <span className=\"mobile-logo\">ᛒ</span>\n {snapshot?.activeProject && (\n <button\n type=\"button\"\n className=\"mobile-project-btn\"\n onClick={() => setProjectSheetOpen(true)}\n aria-label=\"Select project\"\n >\n <span className=\"mobile-project-name\">{snapshot.activeProject.name}</span>\n <ChevronDown size={14} />\n </button>\n )}\n <span className=\"mobile-title\">{TAB_LABELS[activeTab] || 'Bizar'}</span>\n </div>\n\n <div className=\"mobile-topbar-right\">\n <button\n type=\"button\"\n className=\"mobile-icon-btn\"\n onClick={onSearch}\n aria-label=\"Search\"\n >\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\">\n <circle cx=\"11\" cy=\"11\" r=\"8\" />\n <path d=\"m21 21-4.35-4.35\" />\n </svg>\n </button>\n\n <MobileNotifications\n onSelect={(notification) => {\n onNavigate('notification', notification.link || '', notification.meta ?? null);\n }}\n />\n\n {snapshot?.activeProject && (\n <MobileBottomSheet\n open={projectSheetOpen}\n onClose={() => setProjectSheetOpen(false)}\n title=\"Projects\"\n >\n <div className=\"mobile-project-list\">\n {(snapshot.projects || []).map((p) => (\n <button\n key={p.id}\n type=\"button\"\n className={`mobile-project-item ${p.id === snapshot.activeProject?.id ? 'active' : ''}`}\n onClick={() => handleProjectSelect(p.id)}\n >\n <div className=\"mobile-project-status\" data-status={p.status} />\n <div className=\"mobile-project-info\">\n <span className=\"mobile-project-item-name\">{p.name}</span>\n <span className=\"mobile-project-item-path\">{p.path}</span>\n </div>\n {p.id === snapshot.activeProject?.id && (\n <span className=\"mobile-active-badge\">active</span>\n )}\n </button>\n ))}\n </div>\n </MobileBottomSheet>\n )}\n </div>\n </header>\n );\n}\n","// src/mobile/views/MobileActivity.tsx — enhanced activity with filters, event detail, and snapshot submit hero.\nimport { useEffect, useState } from 'react';\nimport { RefreshCw, Bot, Activity as ActivityIcon, Pause, Play, Send } from 'lucide-react';\nimport { api } from '../../lib/api';\nimport { formatRelative } from '../../lib/utils';\nimport type { ActivityItem, Snapshot } from '../../lib/types';\nimport { MobileBottomSheet } from '../components/MobileBottomSheet';\n\ntype Props = {\n snapshot: Snapshot;\n onRefresh: () => Promise<void>;\n};\n\nconst EVENT_KINDS = ['all', 'tasks', 'agents', 'bg', 'artifacts', 'mods'] as const;\ntype EventKind = typeof EVENT_KINDS[number];\n\nconst KIND_LABELS: Record<string, string> = {\n all: 'All',\n tasks: 'Tasks',\n agents: 'Agents',\n bg: 'Background',\n artifacts: 'Plans',\n mods: 'Mods',\n};\n\nexport function MobileActivity({ snapshot, onRefresh }: Props) {\n const [events, setEvents] = useState<ActivityItem[]>(snapshot.overview?.recentActivity || []);\n const [loading, setLoading] = useState(true);\n const [filter, setFilter] = useState<EventKind>('all');\n const [paused, setPaused] = useState(false);\n const [detail, setDetail] = useState<ActivityItem | null>(null);\n const [submitText, setSubmitText] = useState('');\n const [submitting, setSubmitting] = useState(false);\n\n useEffect(() => {\n if (snapshot.overview?.recentActivity) {\n setEvents(snapshot.overview.recentActivity);\n setLoading(false);\n } else {\n loadEvents();\n }\n }, [snapshot.overview]);\n\n const loadEvents = async () => {\n try {\n const data = await api.get<Snapshot>('/snapshot');\n setEvents(data.overview?.recentActivity || []);\n } catch {\n // best-effort\n } finally {\n setLoading(false);\n }\n };\n\n const filtered = filter === 'all' ? events : events.filter((e) => {\n if (filter === 'tasks') return e.kind?.startsWith('task');\n if (filter === 'agents') return e.kind?.startsWith('agent');\n if (filter === 'bg') return e.kind?.includes('bg') || e.kind?.includes('schedule');\n if (filter === 'artifacts') return e.kind?.includes('artifacts');\n if (filter === 'mods') return e.kind?.includes('mod');\n return true;\n });\n\n const agents = snapshot.agents || [];\n const tasks = snapshot.tasks || [];\n const activeTasks = tasks.filter((t) => t.status === 'doing' || t.status === 'queued');\n const doneTasks = tasks.filter((t) => t.status === 'done');\n\n const handleSubmit = async () => {\n if (!submitText.trim() || submitting) return;\n setSubmitting(true);\n try {\n await api.post('/chat', { message: submitText.trim(), agent: 'odin' });\n setSubmitText('');\n await onRefresh().catch(() => undefined);\n await loadEvents();\n } catch {\n // best-effort\n } finally {\n setSubmitting(false);\n }\n };\n\n const handleRefresh = async () => {\n await onRefresh().catch(() => undefined);\n await loadEvents();\n };\n\n return (\n <div className=\"mobile-view\">\n {/* Snapshot Submit Hero */}\n <div className=\"mobile-submit-hero\">\n <textarea\n className=\"mobile-submit-input\"\n placeholder=\"What needs to be done? Describe a task, bug, or refactor for Odin to artifacts…\"\n value={submitText}\n onChange={(e) => setSubmitText(e.target.value)}\n rows={2}\n />\n <div className=\"mobile-submit-actions\">\n <div className=\"mobile-submit-chips\">\n {['Implement feature', 'Fix bug', 'Refactor', 'Investigate', 'Write tests'].map((chip) => (\n <button\n key={chip}\n type=\"button\"\n className=\"mobile-submit-chip\"\n onClick={() => setSubmitText((cur) => cur ? `${cur} ${chip}` : chip)}\n >\n {chip}\n </button>\n ))}\n </div>\n <button\n type=\"button\"\n className=\"mobile-btn\"\n disabled={!submitText.trim() || submitting}\n onClick={handleSubmit}\n >\n <Send size={14} /> Submit to Odin\n </button>\n </div>\n </div>\n\n {/* Quick stats */}\n <div className=\"mobile-stats\">\n <div className=\"mobile-stat\">\n <div className=\"mobile-stat-value\">{agents.length}</div>\n <div className=\"mobile-stat-label\">Agents</div>\n </div>\n <div className=\"mobile-stat\">\n <div className=\"mobile-stat-value\">{activeTasks.length}</div>\n <div className=\"mobile-stat-label\">Active</div>\n </div>\n <div className=\"mobile-stat\">\n <div className=\"mobile-stat-value\">{doneTasks.length}</div>\n <div className=\"mobile-stat-label\">Done</div>\n </div>\n <div className=\"mobile-stat\">\n <div className=\"mobile-stat-value\">{snapshot.artifacts.length || 0}</div>\n <div className=\"mobile-stat-label\">Plans</div>\n </div>\n </div>\n\n {loading && events.length === 0 && (\n <div className=\"mobile-loading mobile-loading-inline\">\n <p>Loading activity…</p>\n </div>\n )}\n\n {/* Filter chips */}\n <div className=\"mobile-activity-header\">\n <h3 className=\"mobile-section-title\" style={{ margin: 0 }}>Recent Activity</h3>\n <div style={{ display: 'flex', gap: 4 }}>\n <button\n type=\"button\"\n className=\"mobile-icon-btn\"\n onClick={() => setPaused((v) => !v)}\n aria-label={paused ? 'Resume' : 'Pause'}\n >\n {paused ? <Play size={14} /> : <Pause size={14} />}\n </button>\n <button type=\"button\" className=\"mobile-icon-btn\" onClick={handleRefresh} aria-label=\"Refresh\">\n <RefreshCw size={14} />\n </button>\n </div>\n </div>\n\n <div className=\"mobile-search-scopes\">\n {EVENT_KINDS.map((k) => (\n <button\n key={k}\n type=\"button\"\n className={`mobile-scope-chip ${filter === k ? 'active' : ''}`}\n onClick={() => setFilter(k)}\n >\n {KIND_LABELS[k]}\n </button>\n ))}\n </div>\n\n {/* Active agents */}\n {agents.length > 0 && (\n <section className=\"mobile-section\">\n <h3 className=\"mobile-section-title\"><Bot size={14} /> Agents</h3>\n <div className=\"mobile-card-list\">\n {agents.slice(0, 6).map((a) => (\n <div key={a.name} className=\"mobile-agent-card\">\n <div className=\"mobile-agent-dot\" data-status={a.status || 'idle'} />\n <div className=\"mobile-agent-info\">\n <span className=\"mobile-agent-name\">{a.name}</span>\n <span className=\"mobile-agent-meta\">{a.status || 'idle'}</span>\n </div>\n </div>\n ))}\n </div>\n </section>\n )}\n\n {/* Event feed */}\n {!paused && filtered.length > 0 && (\n <section className=\"mobile-section\">\n <div className=\"mobile-card-list\">\n {filtered.slice(0, 30).map((e, i) => (\n <button\n key={`${e.ts}-${e.kind}-${i}`}\n type=\"button\"\n className=\"mobile-event-item\"\n onClick={() => setDetail(e)}\n >\n <span className=\"mobile-event-kind\" data-kind={e.kind}>{KIND_LABELS[e.kind as keyof typeof KIND_LABELS] || e.kind}</span>\n <span className=\"mobile-event-msg\">\n {renderEventMessage(e)}\n </span>\n <span className=\"mobile-event-time\">{formatRelative(e.ts)}</span>\n </button>\n ))}\n </div>\n </section>\n )}\n\n {!paused && filtered.length === 0 && events.length > 0 && (\n <div className=\"mobile-empty\">\n <ActivityIcon size={40} />\n <p>No matching activity.</p>\n <p className=\"muted\">Try a different filter.</p>\n </div>\n )}\n\n {/* Event detail sheet */}\n {detail && (\n <MobileBottomSheet\n open={true}\n onClose={() => setDetail(null)}\n title={KIND_LABELS[detail.kind as keyof typeof KIND_LABELS] || detail.kind}\n >\n <div className=\"mobile-agent-detail-meta\">\n <div className=\"mobile-task-detail-row\"><span>Kind</span><span>{detail.kind}</span></div>\n <div className=\"mobile-task-detail-row\"><span>Time</span><span>{new Date(detail.ts).toLocaleString()}</span></div>\n {Object.entries(detail)\n .filter(([k]) => !['ts', 'kind'].includes(k))\n .map(([k, v]) => (\n <div key={k} className=\"mobile-task-detail-row\">\n <span>{k}</span>\n <span style={{ fontSize: 12, maxWidth: 200, wordBreak: 'break-all' }}>\n {typeof v === 'object' ? JSON.stringify(v) : String(v)}\n </span>\n </div>\n ))}\n </div>\n </MobileBottomSheet>\n )}\n\n {/* Empty state */}\n {agents.length === 0 && activeTasks.length === 0 && (\n <div className=\"mobile-empty\">\n <ActivityIcon size={40} />\n <p>No activity yet.</p>\n <p className=\"muted\">Start a task or chat to see things here.</p>\n </div>\n )}\n </div>\n );\n}\n\nfunction renderEventMessage(e: ActivityItem): string {\n const k = e.kind || '';\n if (k.includes('task')) return `Task: ${e.title || e.id || 'updated'}`;\n if (k.includes('agent')) return `Agent: ${e.name || 'status changed'}`;\n if (k.includes('artifacts')) return `Plan: ${e.slug || 'changed'}`;\n if (k.includes('mod')) return `Mod: ${e.name || 'changed'}`;\n if (k.includes('schedule')) return `Schedule: ${e.name || 'triggered'}`;\n return JSON.stringify(e).slice(0, 80);\n}\n","// src/mobile/components/MobileModal.tsx — full-screen mobile modal.\nimport { useEffect, useId, useRef } from 'react';\nimport { X } from 'lucide-react';\n\ntype Props = {\n open: boolean;\n onClose: () => void;\n title?: string;\n children: React.ReactNode;\n actions?: React.ReactNode;\n};\n\nexport function MobileModal({ open, onClose, title, children, actions }: Props) {\n const modalRef = useRef<HTMLDivElement>(null);\n const closeButtonRef = useRef<HTMLButtonElement>(null);\n const lastActiveRef = useRef<HTMLElement | null>(null);\n const titleId = useId();\n const scrollLockYRef = useRef(0);\n\n useEffect(() => {\n if (!open) return;\n\n lastActiveRef.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;\n\n const focusableSelector = 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])';\n const onKey = (e: KeyboardEvent) => {\n if (e.key === 'Escape') onClose();\n if (e.key !== 'Tab') return;\n\n const focusable = Array.from(modalRef.current?.querySelectorAll<HTMLElement>(focusableSelector) ?? []);\n if (focusable.length === 0) {\n e.preventDefault();\n modalRef.current?.focus();\n return;\n }\n\n const first = focusable[0];\n const last = focusable[focusable.length - 1];\n const active = document.activeElement as HTMLElement | null;\n\n if (e.shiftKey) {\n if (!active || active === first || active === modalRef.current) {\n e.preventDefault();\n last?.focus();\n }\n return;\n }\n\n if (active === last) {\n e.preventDefault();\n first?.focus();\n }\n };\n\n window.addEventListener('keydown', onKey);\n scrollLockYRef.current = window.scrollY;\n document.documentElement.style.overflow = 'hidden';\n document.body.style.overflow = 'hidden';\n document.body.style.position = 'fixed';\n document.body.style.top = `-${scrollLockYRef.current}px`;\n document.body.style.left = '0';\n document.body.style.right = '0';\n document.body.style.width = '100%';\n requestAnimationFrame(() => {\n closeButtonRef.current?.focus() ?? modalRef.current?.focus();\n });\n return () => {\n window.removeEventListener('keydown', onKey);\n document.documentElement.style.overflow = '';\n document.body.style.overflow = '';\n document.body.style.position = '';\n document.body.style.top = '';\n document.body.style.left = '';\n document.body.style.right = '';\n document.body.style.width = '';\n window.scrollTo(0, scrollLockYRef.current);\n lastActiveRef.current?.focus();\n };\n }, [open, onClose]);\n\n if (!open) return null;\n\n return (\n <div\n className=\"mobile-modal-overlay\"\n onClick={(e) => {\n if (e.target === e.currentTarget) onClose();\n }}\n >\n <div\n ref={modalRef}\n className=\"mobile-modal\"\n onClick={(e) => e.stopPropagation()}\n role=\"dialog\"\n aria-modal\n aria-label={title ? undefined : 'Modal'}\n aria-labelledby={title ? titleId : undefined}\n tabIndex={-1}\n >\n <div className=\"mobile-modal-header\">\n {title ? <h2 id={titleId} className=\"mobile-modal-title\">{title}</h2> : <div />}\n <button\n ref={closeButtonRef}\n type=\"button\"\n className=\"mobile-icon-btn\"\n onClick={onClose}\n aria-label=\"Close\"\n >\n <X size={20} />\n </button>\n </div>\n <div className=\"mobile-modal-content\">\n {children}\n </div>\n {actions && (\n <div className=\"mobile-modal-actions\">\n {actions}\n </div>\n )}\n </div>\n </div>\n );\n}\n","// src/mobile/views/MobileAgents.tsx — agents list with detail bottom sheet.\nimport { useEffect, useState } from 'react';\nimport { Bot, Plus, RefreshCw, Play, Trash2, RotateCw } from 'lucide-react';\nimport { api } from '../../lib/api';\nimport { formatRelative } from '../../lib/utils';\nimport type { Agent, Snapshot } from '../../lib/types';\nimport { MobileBottomSheet } from '../components/MobileBottomSheet';\nimport { MobileModal } from '../components/MobileModal';\n\ntype Props = {\n snapshot: Snapshot;\n onBack: () => void;\n onOpenAgent: (name: string) => void;\n selectedAgent?: string;\n onRefresh?: () => Promise<void>;\n};\n\nconst CATEGORIES = ['reasoning', 'code', 'design', 'planning', 'gitops', 'analysis'];\n\nconst CATEGORY_COLORS: Record<string, string> = {\n reasoning: 'var(--accent)',\n code: 'var(--info)',\n design: 'var(--success)',\n planning: 'var(--warning)',\n gitops: 'var(--error)',\n analysis: 'var(--text-dim)',\n};\n\nexport function MobileAgents({ snapshot, onBack, onOpenAgent, selectedAgent, onRefresh }: Props) {\n const [agents, setAgents] = useState<Agent[]>(snapshot.agents || []);\n const [loading, setLoading] = useState(!snapshot.agents);\n const [categoryFilter, setCategoryFilter] = useState('');\n const [search, setSearch] = useState('');\n const [detailAgent, setDetailAgent] = useState<Agent | null>(null);\n const [newOpen, setNewOpen] = useState(false);\n const [invokePrompt, setInvokePrompt] = useState('');\n const [invokeOpen, setInvokeOpen] = useState(false);\n\n useEffect(() => {\n if (selectedAgent) {\n const a = agents.find((ag) => ag.name === selectedAgent);\n if (a) setDetailAgent(a);\n }\n }, [selectedAgent, agents]);\n\n const reload = async () => {\n try {\n const data = await api.get<{ agents: Agent[] }>('/agents');\n setAgents(data.agents || []);\n } catch {\n // best-effort\n } finally {\n setLoading(false);\n }\n };\n\n const filtered = agents.filter((a) => {\n if (categoryFilter && a.category !== categoryFilter) return false;\n if (search) {\n const q = search.toLowerCase();\n if (!a.name.toLowerCase().includes(q) && !(a.description || '').toLowerCase().includes(q)) return false;\n }\n return true;\n });\n\n const invokeAgent = async (name: string, prompt: string) => {\n try {\n await api.post(`/agents/${encodeURIComponent(name)}/invoke`, { prompt });\n setInvokeOpen(false);\n setInvokePrompt('');\n } catch {\n // best-effort\n }\n };\n\n const restartAgent = async (name: string) => {\n try {\n await api.post(`/agents/${encodeURIComponent(name)}/restart`);\n await reload();\n } catch {\n // best-effort\n }\n };\n\n const deleteAgent = async (name: string) => {\n if (!confirm(`Delete agent \"${name}\"?`)) return;\n try {\n await api.del(`/agents/${encodeURIComponent(name)}`);\n setAgents((cur) => cur.filter((a) => a.name !== name));\n setDetailAgent(null);\n onRefresh?.();\n } catch {\n // best-effort\n }\n };\n\n const createAgent = async (data: {\n name: string; description: string; model: string; mode: string;\n color: string; tools: string[]; tags: string[]; category: string; prompt: string;\n }) => {\n try {\n const created = await api.post<Agent>('/agents', data);\n setAgents((cur) => [...cur, created]);\n setNewOpen(false);\n onRefresh?.();\n } catch {\n // best-effort\n }\n };\n\n return (\n <div className=\"mobile-view\">\n {/* Toolbar */}\n <div className=\"mobile-tasks-toolbar\">\n <input\n className=\"mobile-search-input\"\n type=\"text\"\n placeholder=\"Search agents…\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n style={{ flex: 1 }}\n />\n <button type=\"button\" className=\"mobile-icon-btn\" onClick={() => reload()} aria-label=\"Refresh\">\n <RefreshCw size={16} />\n </button>\n <button type=\"button\" className=\"mobile-icon-btn\" onClick={() => setNewOpen(true)} aria-label=\"New agent\">\n <Plus size={16} />\n </button>\n </div>\n\n {/* Category filter */}\n <div className=\"mobile-search-scopes\">\n <button\n type=\"button\"\n className={`mobile-scope-chip ${!categoryFilter ? 'active' : ''}`}\n onClick={() => setCategoryFilter('')}\n >\n All\n </button>\n {CATEGORIES.map((c) => (\n <button\n key={c}\n type=\"button\"\n className={`mobile-scope-chip ${categoryFilter === c ? 'active' : ''}`}\n onClick={() => setCategoryFilter(c)}\n >\n {c}\n </button>\n ))}\n </div>\n\n {loading ? (\n <div className=\"mobile-loading\"><p>Loading…</p></div>\n ) : filtered.length === 0 ? (\n <div className=\"mobile-empty\">\n <Bot size={40} />\n <p>No agents found.</p>\n </div>\n ) : (\n <div className=\"mobile-card-list\">\n {filtered.map((a) => (\n <button\n key={a.name}\n type=\"button\"\n className=\"mobile-list-item mobile-list-item-interactive\"\n onClick={() => { setDetailAgent(a); onOpenAgent(a.name); }}\n >\n <div className=\"mobile-agent-dot\" data-status={a.status || 'idle'} />\n <div className=\"mobile-list-content\">\n <span className=\"mobile-list-title\">{a.name}</span>\n <span className=\"mobile-list-meta\">{a.model || a.mode || '—'}</span>\n </div>\n {a.category && (\n <span\n className=\"mobile-list-badge\"\n style={{ color: CATEGORY_COLORS[a.category] || 'var(--text-dim)' }}\n >\n {a.category}\n </span>\n )}\n <span className=\"mobile-list-badge\" data-status={a.status || 'idle'}>\n {a.status || 'idle'}\n </span>\n </button>\n ))}\n </div>\n )}\n\n {/* Agent detail sheet */}\n {detailAgent && (\n <MobileBottomSheet\n open={true}\n onClose={() => setDetailAgent(null)}\n title={detailAgent.name}\n actions={\n <div className=\"mobile-task-detail-actions\">\n <button\n type=\"button\"\n className=\"mobile-btn\"\n onClick={() => { setInvokePrompt(''); setInvokeOpen(true); }}\n >\n <Play size={14} /> Invoke\n </button>\n {(detailAgent.isStuck || detailAgent.status === 'error' || detailAgent.status === 'working') && (\n <button\n type=\"button\"\n className=\"mobile-btn mobile-btn-secondary\"\n onClick={() => restartAgent(detailAgent.name)}\n >\n <RotateCw size={14} /> Restart\n </button>\n )}\n <button\n type=\"button\"\n className=\"mobile-btn mobile-btn-danger\"\n onClick={() => deleteAgent(detailAgent.name)}\n >\n <Trash2 size={14} />\n </button>\n </div>\n }\n >\n <div className=\"mobile-agent-detail\">\n <div className=\"mobile-agent-detail-status\">\n <span className=\"mobile-agent-dot\" data-status={detailAgent.status || 'idle'} />\n <span className=\"mobile-agent-detail-status-text\">{detailAgent.status || 'idle'}</span>\n {detailAgent.category && (\n <span\n className=\"mobile-list-badge\"\n style={{ color: CATEGORY_COLORS[detailAgent.category] }}\n >\n {detailAgent.category}\n </span>\n )}\n </div>\n <p className=\"mobile-agent-detail-desc\">{detailAgent.description || 'No description.'}</p>\n <div className=\"mobile-agent-detail-meta\">\n <div className=\"mobile-task-detail-row\">\n <span>Model</span>\n <span className=\"mono\">{detailAgent.model || '—'}</span>\n </div>\n <div className=\"mobile-task-detail-row\">\n <span>Mode</span>\n <span>{detailAgent.mode || 'subagent'}</span>\n </div>\n <div className=\"mobile-task-detail-row\">\n <span>Path</span>\n <span className=\"mono\" style={{ fontSize: 11 }}>{detailAgent.path}</span>\n </div>\n {detailAgent.lastError && (\n <div className=\"mobile-task-detail-row\" style={{ color: 'var(--error)' }}>\n <span>Last error</span>\n <span>{detailAgent.lastError.message}</span>\n </div>\n )}\n {detailAgent.tasksTotal != null && detailAgent.tasksTotal > 0 && (\n <div className=\"mobile-task-detail-row\">\n <span>Success rate</span>\n <span>{Math.round((detailAgent.successRate || 0) * 100)}%</span>\n </div>\n )}\n <div className=\"mobile-task-detail-row\">\n <span>Last modified</span>\n <span>{formatRelative(detailAgent.mtime)}</span>\n </div>\n </div>\n </div>\n </MobileBottomSheet>\n )}\n\n {/* Invoke modal */}\n <MobileModal open={invokeOpen} onClose={() => setInvokeOpen(false)} title={`Invoke ${detailAgent?.name}`} actions={\n <button\n type=\"button\"\n className=\"mobile-btn\"\n style={{ width: '100%' }}\n disabled={!invokePrompt.trim()}\n onClick={() => { if (detailAgent) invokeAgent(detailAgent.name, invokePrompt.trim()); }}\n >\n <Play size={14} /> Invoke\n </button>\n }>\n <p className=\"muted\" style={{ marginBottom: 12 }}>\n Model: <span className=\"mono\">{detailAgent?.model || '—'}</span>\n </p>\n <textarea\n className=\"mobile-input\"\n rows={5}\n placeholder=\"What should this agent do?\"\n value={invokePrompt}\n onChange={(e) => setInvokePrompt(e.target.value)}\n autoFocus\n />\n </MobileModal>\n\n {/* New agent modal */}\n <NewAgentModal\n open={newOpen}\n onClose={() => setNewOpen(false)}\n onCreate={createAgent}\n />\n </div>\n );\n}\n\nfunction NewAgentModal({ open, onClose, onCreate }: {\n open: boolean;\n onClose: () => void;\n onCreate: (data: {\n name: string; description: string; model: string; mode: string;\n color: string; tools: string[]; tags: string[]; category: string; prompt: string;\n }) => void;\n}) {\n const [name, setName] = useState('');\n const [description, setDescription] = useState('');\n const [model, setModel] = useState('');\n const [mode, setMode] = useState('subagent');\n const [color, setColor] = useState('#8b5cf6');\n const [category, setCategory] = useState('');\n const [tags, setTags] = useState('');\n const [prompt, setPrompt] = useState('');\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n if (!name.trim()) return;\n onCreate({\n name: name.trim(),\n description: description.trim(),\n model,\n mode,\n color,\n tools: [],\n tags: tags.split(',').map((t) => t.trim()).filter(Boolean),\n category,\n prompt,\n });\n setName('');\n setDescription('');\n setModel('');\n setMode('subagent');\n setColor('#8b5cf6');\n setCategory('');\n setTags('');\n setPrompt('');\n };\n\n return (\n <MobileModal open={open} onClose={onClose} title=\"New Agent\" actions={\n <button type=\"submit\" form=\"new-agent-form\" className=\"mobile-btn\" style={{ width: '100%' }}>\n <Plus size={14} /> Create\n </button>\n }>\n <form id=\"new-agent-form\" onSubmit={handleSubmit} className=\"mobile-task-form\">\n <label className=\"mobile-field-label\">Name *</label>\n <input className=\"mobile-input\" type=\"text\" placeholder=\"my-agent\" pattern=\"[a-z0-9][a-z0-9-]{0,63}\"\n value={name} onChange={(e) => setName(e.target.value)} autoFocus required />\n <label className=\"mobile-field-label\">Description</label>\n <input className=\"mobile-input\" type=\"text\" placeholder=\"What does this agent do?\"\n value={description} onChange={(e) => setDescription(e.target.value)} />\n <label className=\"mobile-field-label\">Model</label>\n <input className=\"mobile-input\" type=\"text\" placeholder=\"(provider default)\"\n value={model} onChange={(e) => setModel(e.target.value)} />\n <label className=\"mobile-field-label\">Category</label>\n <select className=\"mobile-input\" value={category} onChange={(e) => setCategory(e.target.value)}>\n <option value=\"\">None</option>\n {CATEGORIES.map((c) => <option key={c} value={c}>{c}</option>)}\n </select>\n <label className=\"mobile-field-label\">Tags (comma-separated)</label>\n <input className=\"mobile-input\" type=\"text\" placeholder=\"tag1, tag2\"\n value={tags} onChange={(e) => setTags(e.target.value)} />\n </form>\n </MobileModal>\n );\n}\n","// src/components/chat/SessionList.tsx — sessions sidebar with create + select.\n\nimport { useState } from 'react';\nimport { Plus, Sparkles, Folder, ExternalLink } from 'lucide-react';\nimport { Button } from '../Button';\nimport { EmptyState } from './EmptyState';\nimport { Spinner } from '../Spinner';\nimport type { ChatSession } from '../../lib/types';\n\ninterface Props {\n sessions: ChatSession[];\n opencodeSessions: ChatSession[];\n activeSessionId: string;\n /** The opencode session currently displayed, or null. */\n activeOpencodeSessionId: string | null;\n activeProject: { name: string } | null;\n creating: boolean;\n onCreateSession: () => void;\n /** Called when a bizar session is selected. */\n onSelectSession: (id: string) => void;\n /** Called when an opencode session is selected — renders it in-dash. */\n onSelectOpencodeSession: (s: ChatSession) => void;\n}\n\nexport function SessionList({\n sessions,\n opencodeSessions,\n activeSessionId,\n activeOpencodeSessionId,\n activeProject,\n creating,\n onCreateSession,\n onSelectSession,\n onSelectOpencodeSession,\n}: Props) {\n const [view, setView] = useState<'bizar' | 'all'>('all');\n const [showAll, setShowAll] = useState(false);\n const VISIBLE_LIMIT = 30;\n\n const allSorted = view === 'all'\n ? [...sessions, ...opencodeSessions].sort((a, b) => Number(b.mtime) - Number(a.mtime))\n : sessions;\n const displayedSessions = showAll ? allSorted : allSorted.slice(0, VISIBLE_LIMIT);\n\n const totalOpencode = opencodeSessions.length;\n\n const handleSelectSession = (s: ChatSession) => {\n if (s.source === 'opencode') {\n // Render opencode session in-dash — no window.open redirect\n onSelectOpencodeSession(s);\n return;\n }\n onSelectSession(s.id);\n };\n\n return (\n <div className=\"chat-sessions\">\n <div className=\"chat-sessions-header\">\n <span className=\"chat-sessions-header-title\">Sessions</span>\n <span className=\"chat-sessions-count\">{displayedSessions.length}</span>\n </div>\n\n {totalOpencode > 0 && (\n <div className=\"chat-sessions-toggle\">\n <button\n type=\"button\"\n className={`chat-sessions-toggle-btn ${view === 'bizar' ? 'active' : ''}`}\n onClick={() => setView('bizar')}\n >\n Bizar\n </button>\n <button\n type=\"button\"\n className={`chat-sessions-toggle-btn ${view === 'all' ? 'active' : ''}`}\n onClick={() => setView('all')}\n >\n All ({totalOpencode} opencode)\n </button>\n </div>\n )}\n\n <button\n type=\"button\"\n className=\"chat-sessions-new\"\n onClick={onCreateSession}\n disabled={creating || !activeProject}\n title={activeProject ? 'Create new session' : 'Pick a project first'}\n aria-label=\"Create new session\"\n >\n {creating ? <Spinner size=\"sm\" /> : <Plus size={14} />}\n <span>{creating ? 'Creating…' : 'New session'}</span>\n </button>\n\n {sessions.length === 0 ? (\n <div className=\"chat-sessions-empty\">\n <EmptyState\n icon={<Sparkles size={20} />}\n title={activeProject ? 'No sessions yet' : 'No project'}\n message={\n activeProject\n ? 'Create your first session to start chatting.'\n : 'Pick a project in Overview to scope chat sessions.'\n }\n action={\n activeProject ? (\n <Button variant=\"primary\" size=\"sm\" onClick={onCreateSession} loading={creating}>\n <Plus size={12} /> New session\n </Button>\n ) : (\n <Button\n variant=\"secondary\"\n size=\"sm\"\n onClick={() => { window.location.hash = '#overview'; }}\n >\n <Folder size={12} /> Open Overview\n </Button>\n )\n }\n />\n </div>\n ) : (\n <ul className=\"chat-sessions-list\">\n {displayedSessions.map((s) => {\n const isOpencode = s.source === 'opencode';\n const isActive = isOpencode\n ? activeOpencodeSessionId === s.id\n : activeSessionId === s.id;\n return (\n <li\n key={isOpencode ? `oc-${s.id}` : s.id}\n className={`chat-sessions-item ${isActive ? 'active' : ''} ${isOpencode ? 'chat-sessions-item-opencode' : ''}`}\n onClick={() => handleSelectSession(s)}\n title={isOpencode ? `Open in dashboard: ${s.title || s.id}` : s.id}\n >\n {isOpencode && (\n <ExternalLink size={10} className=\"chat-sessions-item-icon\" />\n )}\n <span className=\"chat-sessions-item-id\">\n {s.title || s.id}\n </span>\n <span className=\"chat-sessions-item-meta\">\n {new Date(Number(s.mtime)).toLocaleDateString()}\n </span>\n </li>\n );\n })}\n </ul>\n )}\n\n {allSorted.length > VISIBLE_LIMIT && (\n <button\n type=\"button\"\n className=\"chat-sessions-show-more\"\n onClick={() => setShowAll((v) => !v)}\n >\n {showAll\n ? `Show less (${allSorted.length - VISIBLE_LIMIT} hidden)`\n : `Show all ${allSorted.length} sessions`}\n </button>\n )}\n </div>\n );\n}\n","// src/components/chat/FloatingComposer.tsx — sticky bottom wrapper for the composer pill.\n\nimport { Composer } from './Composer';\n\ninterface Props {\n agent: string;\n setAgent: (a: string) => void;\n model: string;\n setModel: (m: string) => void;\n text: string;\n setText: (t: string) => void;\n sending: boolean;\n onSend: () => void;\n attachments: string[];\n setAttachments: (a: string[] | ((prev: string[]) => string[])) => void;\n suggestions: Array<{ cmd: string; desc: string }>;\n onPickSuggestion: (cmd: string) => void;\n agents: Array<{ name: string }>;\n onAttach: () => void;\n sessionsOpen: boolean;\n infoOpen: boolean;\n /** Which stream will receive the next message — propagates the\n * pill border color and a small \"→ opencode\" hint. */\n activeSource?: 'bizar' | 'opencode' | null;\n}\n\nexport function FloatingComposer(props: Props) {\n const { activeSource } = props;\n return (\n <div\n className={`chat-composer-wrap chat-composer-source-${activeSource ?? 'none'} legacy`}\n >\n <div className=\"chat-composer-pill\">\n <Composer {...props} />\n </div>\n </div>\n );\n}","// src/components/chat/InfoPanel.tsx — mobile-compact info sidebar.\n//\n// v4.2.5 — overhauled: shows activeSource badge, message count,\n// session metadata, model/provider, and rename/delete actions for\n// the active session.\n\nimport { useState } from 'react';\nimport {\n MessageSquare,\n Bot,\n Server,\n Terminal,\n ChevronDown,\n Trash2,\n Pencil,\n} from 'lucide-react';\n\ninterface Props {\n sessionId: string;\n messages: Array<unknown>;\n pinned: Set<number>;\n agent: string;\n model: string;\n agents: Array<{ name: string; model?: string; mode?: string }>;\n mcps: Array<{ id: string; command?: string; enabled?: boolean }>;\n allCommands: Array<{ cmd: string; desc: string; mod?: string }>;\n activeSource?: 'bizar' | 'opencode' | null;\n /** Optional rename handler — when omitted the rename button is hidden. */\n onRename?: () => void;\n /** Optional delete handler — when omitted the delete button is hidden. */\n onDelete?: () => void;\n}\n\nexport function InfoPanel({\n sessionId,\n messages,\n pinned,\n agent,\n model,\n agents,\n mcps,\n allCommands,\n activeSource = 'bizar',\n onRename,\n onDelete,\n}: Props) {\n const [showAllAgents, setShowAllAgents] = useState(false);\n const [showCommands, setShowCommands] = useState(false);\n\n const visibleAgents = showAllAgents ? agents : agents.slice(0, 4);\n const visibleCommands = showCommands ? allCommands : allCommands.slice(0, 4);\n\n // Derive a friendly provider from the model id (e.g.\n // \"opencode/deepseek-v4-flash-free\" → provider \"opencode\").\n const provider = model.includes('/') ? model.split('/')[0] : '';\n\n return (\n <div className=\"chat-info\">\n <div className=\"chat-info-card\">\n <div className=\"chat-info-card-title\">\n <MessageSquare size={14} /> Session\n <span\n className={`chat-source-badge chat-source-${activeSource ?? 'none'}`}\n style={{ marginLeft: 'auto' }}\n >\n {activeSource === 'opencode' ? 'opencode' : 'bizar chat'}\n </span>\n </div>\n <dl className=\"env-table\">\n <dt>id</dt>\n <dd className=\"mono\">{sessionId || 'Live'}</dd>\n <dt>Messages</dt>\n <dd className=\"mono\">{messages.length}</dd>\n <dt>Pinned</dt>\n <dd className=\"mono\">{pinned.size}</dd>\n <dt>Agent</dt>\n <dd className=\"mono\">{agent || '—'}</dd>\n <dt>Model</dt>\n <dd className=\"mono ellipsis\" title={model}>\n {model || '—'}\n </dd>\n {provider && (\n <>\n <dt>Provider</dt>\n <dd className=\"mono\">{provider}</dd>\n </>\n )}\n </dl>\n {(onRename || onDelete) && (\n <div className=\"chat-info-actions\">\n {onRename && (\n <button\n type=\"button\"\n className=\"btn btn-ghost btn-sm\"\n onClick={onRename}\n >\n <Pencil size={12} aria-hidden /> Rename\n </button>\n )}\n {onDelete && (\n <button\n type=\"button\"\n className=\"btn btn-ghost btn-danger btn-sm\"\n onClick={onDelete}\n >\n <Trash2 size={12} aria-hidden /> Delete\n </button>\n )}\n </div>\n )}\n </div>\n\n <div className=\"chat-info-card\">\n <div className=\"chat-info-card-title\">\n <Bot size={14} /> Agents\n <span className=\"chat-info-card-count\">{agents.length}</span>\n </div>\n <ul className=\"mod-mini-list\">\n {visibleAgents.map((a) => (\n <li key={a.name} className=\"mod-mini\">\n <span className=\"mod-mini-name\">@{a.name}</span>\n <span className=\"mod-mini-meta\">{a.model || a.mode || ''}</span>\n </li>\n ))}\n </ul>\n {agents.length > 4 && (\n <button\n type=\"button\"\n className=\"chat-info-card-toggle\"\n onClick={() => setShowAllAgents((v) => !v)}\n >\n {showAllAgents ? '− Show fewer' : `+ ${agents.length - 4} more`}\n </button>\n )}\n </div>\n\n <div className=\"chat-info-card\">\n <div className=\"chat-info-card-title\">\n <Server size={14} /> MCPs\n <span className=\"chat-info-card-count\">{mcps.length}</span>\n </div>\n <ul className=\"mod-mini-list\">\n {mcps.length === 0 ? (\n <li className=\"muted\">No MCPs configured.</li>\n ) : null}\n {mcps.map((m) => (\n <li key={m.id} className=\"mod-mini\">\n <span className=\"mod-mini-name\">{m.id}</span>\n <span className={`mod-mini-pill ${m.enabled ? 'on' : 'off'}`}>\n {m.enabled ? 'on' : 'off'}\n </span>\n </li>\n ))}\n </ul>\n </div>\n\n <div className=\"chat-info-card\">\n <button\n type=\"button\"\n className=\"chat-info-card-title chat-info-card-title-button\"\n onClick={() => setShowCommands((v) => !v)}\n aria-expanded={showCommands}\n >\n <Terminal size={14} /> Slash commands\n <span className=\"chat-info-card-count\">{allCommands.length}</span>\n <ChevronDown\n size={12}\n className={`chat-info-card-chevron ${showCommands ? 'open' : ''}`}\n />\n </button>\n {showCommands && (\n <ul className=\"mod-mini-list\">\n {visibleCommands.map((c) => (\n <li key={c.cmd}>\n <code>{c.cmd}</code>\n <span className=\"muted\">{c.desc}</span>\n </li>\n ))}\n </ul>\n )}\n </div>\n </div>\n );\n}\n","// src/web/mobile/views/MobileChat.tsx — mobile chat (v4.2.5).\n//\n// v4.2.5 — matches Chat.tsx overhaul: opencode session-create via the\n// new endpoint, source-routed composer (opencode send vs. local chat),\n// source indicator badge, rename / delete / export on the info panel,\n// and matching busy flags.\n\nimport { useEffect, useRef, useState } from 'react';\nimport { ChatTopBar } from '../../components/chat/ChatTopBar';\nimport { SessionList } from '../../components/chat/SessionList';\nimport { ChatThread } from '../../components/chat/ChatThread';\nimport { FloatingComposer } from '../../components/chat/FloatingComposer';\nimport { InfoPanel } from '../../components/chat/InfoPanel';\nimport { useChat } from '../../components/chat/useChat';\nimport { useSlashCommands } from '../../components/chat/useSlashCommands';\nimport { useToast } from '../../components/Toast';\nimport { useModal } from '../../components/Modal';\nimport { Button } from '../../components/Button';\nimport type { Snapshot, Settings } from '../../lib/types';\n\ninterface Props {\n snapshot: Snapshot;\n settings: Settings;\n setActiveTab?: (id: string) => void;\n initialTaskId?: string | null;\n onClearTaskId?: () => void;\n}\n\nexport function MobileChat({\n snapshot,\n settings,\n setActiveTab,\n initialTaskId,\n onClearTaskId,\n}: Props) {\n const toast = useToast();\n const modal = useModal();\n\n const chat = useChat(snapshot, settings, initialTaskId ?? '');\n useEffect(() => {\n chat.setToast({\n error: (msg: string) => toast.error(msg),\n success: (msg: string) => toast.success(msg),\n info: (msg: string) => toast.info(msg),\n warning: (msg: string) => toast.warning(msg),\n });\n }, [chat, toast]);\n\n const [text, setText] = useState('');\n const [agent, setAgent] = useState(settings.defaultAgent || 'odin');\n const [model, setModel] = useState(settings.defaultModel || '');\n const [attachments, setAttachments] = useState<string[]>([]);\n const [sessionsOpen, setSessionsOpen] = useState(false);\n const [infoOpen, setInfoOpen] = useState(false);\n\n const fileInputRef = useRef<HTMLInputElement>(null);\n\n const { allCommands, suggestions, setQuery } = useSlashCommands(snapshot);\n useEffect(() => {\n setQuery(text);\n }, [text, setQuery]);\n\n const onAttach = () => fileInputRef.current?.click();\n const onFiles = (e: React.ChangeEvent<HTMLInputElement>) => {\n const files = e.target.files;\n if (!files) return;\n const next: string[] = [];\n for (let i = 0; i < files.length; i++) next.push(files[i].name);\n setAttachments((cur) => {\n const newNames = next.filter((n) => !cur.includes(n));\n return [...cur, ...newNames];\n });\n if (fileInputRef.current) fileInputRef.current.value = '';\n };\n\n const handleSend = async () => {\n const msg = text.trim();\n if (!msg) return;\n setText('');\n setQuery('');\n const result = await chat.onSend(msg, agent, model, attachments);\n if (result.ok) chat.jumpToLatest();\n };\n\n const handleCreateSession = async () => {\n if (chat.busy.create) return;\n await chat.onCreateSession();\n setSessionsOpen(false);\n };\n\n const handleDelete = (idx: number) => {\n modal.open({\n title: 'Delete message?',\n children: <p style={{ margin: 0 }}>This action cannot be undone.</p>,\n footer: (\n <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end' }}>\n <Button variant=\"secondary\" size=\"sm\" onClick={() => modal.close()}>\n Cancel\n </Button>\n <Button\n variant=\"danger\"\n size=\"sm\"\n onClick={() => {\n modal.close();\n chat.deleteMessage(idx);\n }}\n >\n Delete\n </Button>\n </div>\n ),\n });\n };\n\n const handleDeleteSession = (id: string, title: string) => {\n modal.open({\n title: 'Delete session?',\n children: (\n <p style={{ margin: 0 }}>\n Delete <strong>{title}</strong>? This cannot be undone.\n </p>\n ),\n footer: (\n <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end' }}>\n <Button variant=\"secondary\" size=\"sm\" onClick={() => modal.close()}>\n Cancel\n </Button>\n <Button\n variant=\"danger\"\n size=\"sm\"\n onClick={async () => {\n modal.close();\n await chat.deleteSession(id);\n }}\n >\n Delete\n </Button>\n </div>\n ),\n });\n };\n\n return (\n <div className=\"chat-shell\">\n <ChatTopBar\n activeProject={snapshot.activeProject}\n sessionCount={chat.sessions.length}\n sessionsOpen={sessionsOpen}\n infoOpen={infoOpen}\n onToggleSessions={() => setSessionsOpen((v) => !v)}\n onToggleInfo={() => setInfoOpen((v) => !v)}\n onOpenOverview={() => setActiveTab?.('overview')}\n />\n <div className=\"chat-body\">\n <aside\n className={`chat-sessions ${!sessionsOpen ? 'chat-sessions-hidden' : ''}`}\n >\n <SessionList\n sessions={chat.sessions}\n opencodeSessions={chat.opencodeSessions}\n activeSessionId={chat.sessionId}\n activeOpencodeSessionId={chat.activeOpencodeSessionId}\n activeProject={snapshot.activeProject}\n onCreateSession={handleCreateSession}\n onSelectSession={chat.selectBizarSession}\n onSelectOpencodeSession={(s) => chat.loadOpencodeSession(s.id)}\n creating={chat.busy.create}\n />\n </aside>\n\n <main className=\"chat-main\">\n <ChatThread\n messages={\n chat.activeSource === 'opencode'\n ? chat.opencodeMessages\n : chat.bizarMessages\n }\n loading={chat.loading}\n activeProject={snapshot.activeProject}\n sessionId={\n chat.activeSource === 'opencode'\n ? chat.activeOpencodeSessionId ?? chat.sessionId\n : chat.sessionId\n }\n pinned={chat.pinned}\n activeSource={chat.activeSource}\n onPickSuggestion={(t) => setText(t)}\n onCopy={(m) =>\n chat.copyMessage(m as Parameters<typeof chat.copyMessage>[0])\n }\n onDelete={handleDelete}\n onTogglePin={chat.togglePin}\n onRegenerate={chat.onRegenerate}\n />\n <FloatingComposer\n agent={agent}\n setAgent={setAgent}\n model={model}\n setModel={setModel}\n text={text}\n setText={setText}\n sending={chat.sending}\n activeSource={chat.activeSource}\n onSend={handleSend}\n attachments={attachments}\n setAttachments={setAttachments}\n suggestions={suggestions}\n onPickSuggestion={(cmd) => setText(`${cmd.split(' ')[0]} `)}\n agents={snapshot.agents || []}\n onAttach={onAttach}\n sessionsOpen={sessionsOpen}\n infoOpen={infoOpen}\n />\n <input\n ref={fileInputRef}\n type=\"file\"\n multiple\n style={{ display: 'none' }}\n onChange={onFiles}\n />\n </main>\n\n <aside\n className={`chat-info ${!infoOpen ? 'chat-info-hidden' : ''}`}\n >\n <InfoPanel\n sessionId={\n chat.activeSource === 'opencode'\n ? chat.activeOpencodeSessionId ?? chat.sessionId\n : chat.sessionId\n }\n messages={\n chat.activeSource === 'opencode'\n ? chat.opencodeMessages\n : chat.bizarMessages\n }\n pinned={chat.pinned}\n agent={agent}\n model={model}\n agents={snapshot.agents || []}\n mcps={snapshot.mcps || []}\n allCommands={allCommands}\n activeSource={chat.activeSource}\n onDelete={() => {\n const id =\n chat.activeSource === 'opencode'\n ? chat.activeOpencodeSessionId\n : chat.sessionId;\n if (!id) return;\n handleDeleteSession(id, id);\n }}\n />\n </aside>\n </div>\n </div>\n );\n}\n","// src/mobile/views/MobileConfig.tsx — config key-value editor with type-aware inputs.\nimport { useEffect, useState } from 'react';\nimport { Sliders, RefreshCw, RotateCcw, Save } from 'lucide-react';\nimport { api } from '../../lib/api';\n\ntype ConfigEntry = {\n key: string;\n value: unknown;\n type: 'boolean' | 'number' | 'string' | 'object' | 'array' | 'unknown';\n defaultValue?: unknown;\n};\n\ntype Props = {\n onBack: () => void;\n};\n\nexport function MobileConfig({ onBack }: Props) {\n const [entries, setEntries] = useState<ConfigEntry[]>([]);\n const [loading, setLoading] = useState(true);\n const [editing, setEditing] = useState<Record<string, unknown>>({});\n const [saving, setSaving] = useState(false);\n\n const load = async () => {\n try {\n const data = await api.get<Record<string, unknown>>('/config');\n const entries: ConfigEntry[] = Object.entries(data).map(([key, value]) => ({\n key,\n value,\n type: detectType(value),\n }));\n setEntries(entries);\n setEditing(Object.fromEntries(Object.entries(data).map(([k, v]) => [k, v])));\n } catch {\n // best-effort\n } finally {\n setLoading(false);\n }\n };\n\n useEffect(() => { load(); }, []);\n\n const detectType = (v: unknown): ConfigEntry['type'] => {\n if (typeof v === 'boolean') return 'boolean';\n if (typeof v === 'number') return 'number';\n if (typeof v === 'string') return 'string';\n if (Array.isArray(v)) return 'array';\n if (v && typeof v === 'object') return 'object';\n return 'unknown';\n };\n\n const handleChange = (key: string, value: unknown) => {\n setEditing((cur) => ({ ...cur, [key]: value }));\n };\n\n const saveAll = async () => {\n setSaving(true);\n try {\n await api.patch('/config', editing);\n await load();\n } catch {\n // best-effort\n } finally {\n setSaving(false);\n }\n };\n\n const resetRow = (key: string, defaultValue?: unknown) => {\n setEditing((cur) => ({ ...cur, [key]: defaultValue }));\n };\n\n const renderInput = (entry: ConfigEntry) => {\n const value = editing[entry.key];\n switch (entry.type) {\n case 'boolean':\n return (\n <label className=\"mobile-toggle\">\n <input\n type=\"checkbox\"\n checked={Boolean(value)}\n onChange={(e) => handleChange(entry.key, e.target.checked)}\n />\n <span className=\"mobile-toggle-slider\" />\n </label>\n );\n case 'number':\n return (\n <input\n className=\"mobile-input mobile-input-sm\"\n type=\"number\"\n inputMode=\"numeric\"\n value={String(value ?? '')}\n onChange={(e) => handleChange(entry.key, Number(e.target.value))}\n />\n );\n case 'string':\n return (\n <input\n className=\"mobile-input mobile-input-sm\"\n type=\"text\"\n value={String(value ?? '')}\n onChange={(e) => handleChange(entry.key, e.target.value)}\n />\n );\n case 'object':\n case 'array':\n return (\n <textarea\n className=\"mobile-input mobile-input-sm\"\n rows={2}\n value={JSON.stringify(value ?? null, null, 2)}\n onChange={(e) => {\n try {\n handleChange(entry.key, JSON.parse(e.target.value));\n } catch {\n // invalid JSON\n }\n }}\n />\n );\n default:\n return <span className=\"mobile-setting-value mono\">{String(value ?? 'null')}</span>;\n }\n };\n\n return (\n <div className=\"mobile-view\">\n <div className=\"mobile-tasks-toolbar\">\n <span style={{ fontSize: 14, color: 'var(--text-muted)', flex: 1 }}>{entries.length} config entries</span>\n <button type=\"button\" className=\"mobile-icon-btn\" onClick={() => load()} aria-label=\"Refresh\">\n <RefreshCw size={16} />\n </button>\n <button\n type=\"button\"\n className=\"mobile-btn\"\n disabled={saving}\n onClick={() => saveAll()}\n >\n <Save size={14} /> {saving ? 'Saving…' : 'Save all'}\n </button>\n </div>\n\n {loading ? (\n <div className=\"mobile-loading\"><p>Loading…</p></div>\n ) : entries.length === 0 ? (\n <div className=\"mobile-empty\">\n <Sliders size={40} />\n <p>No config entries.</p>\n </div>\n ) : (\n <div className=\"mobile-config-list\">\n {entries.map((entry) => (\n <div key={entry.key} className=\"mobile-config-row\">\n <div className=\"mobile-config-key\">\n <span className=\"mono\">{entry.key}</span>\n <span className=\"mobile-config-type\">{entry.type}</span>\n </div>\n <div className=\"mobile-config-value\">\n {renderInput(entry)}\n </div>\n <button\n type=\"button\"\n className=\"mobile-icon-btn\"\n onClick={() => resetRow(entry.key, entry.defaultValue)}\n aria-label=\"Reset to default\"\n title=\"Reset\"\n >\n <RotateCcw size={14} />\n </button>\n </div>\n ))}\n </div>\n )}\n </div>\n );\n}\n","// src/mobile/views/MobileHistory.tsx — session history list.\nimport { useEffect, useState } from 'react';\nimport { History, RefreshCw } from 'lucide-react';\nimport { api } from '../../lib/api';\nimport { MobileBottomSheet } from '../components/MobileBottomSheet';\n\ntype HistoryEntry = {\n id: string;\n ts: string;\n duration?: number;\n agent?: string;\n taskCount?: number;\n summary?: string;\n output?: string;\n};\n\ntype HistoryEvent = {\n ts: string;\n kind: string;\n text?: string;\n projectId?: string;\n};\n\ntype ProjectHistory = {\n id: string;\n name: string;\n path: string;\n status: string;\n lastAccessed?: string | null;\n tasks: { total: number; done: number; doing: number; blocked: number; queued: number };\n plans: number;\n};\n\ntype HistoryResponse = {\n history?: HistoryEntry[];\n events?: HistoryEvent[];\n projects?: ProjectHistory[];\n};\n\ntype Props = {\n onBack: () => void;\n};\n\nexport function MobileHistory({ onBack }: Props) {\n const [entries, setEntries] = useState<HistoryEntry[]>([]);\n const [loading, setLoading] = useState(true);\n const [detail, setDetail] = useState<HistoryEntry | null>(null);\n const [query, setQuery] = useState('');\n\n const load = async () => {\n try {\n const data = await api.get<HistoryResponse>('/history');\n if (Array.isArray(data.history)) {\n setEntries(data.history);\n return;\n }\n\n if (Array.isArray(data.projects)) {\n const latestEventByProject = new Map<string, HistoryEvent>();\n for (const event of data.events || []) {\n const pid = event.projectId;\n if (!pid) continue;\n const current = latestEventByProject.get(pid);\n if (!current || new Date(event.ts).getTime() > new Date(current.ts).getTime()) {\n latestEventByProject.set(pid, event);\n }\n }\n\n setEntries(\n data.projects.map((project) => {\n const latestEvent = latestEventByProject.get(project.id);\n return {\n id: project.id,\n ts: project.lastAccessed || latestEvent?.ts || new Date(0).toISOString(),\n agent: project.name,\n taskCount: project.tasks.total,\n summary: `${project.path} · ${project.tasks.done}/${project.tasks.total} done · ${project.plans} plan${project.plans === 1 ? '' : 's'}`,\n output: latestEvent ? `${latestEvent.kind}${latestEvent.text ? ` — ${latestEvent.text}` : ''}` : 'No recent events.',\n } satisfies HistoryEntry;\n }),\n );\n return;\n }\n\n setEntries([]);\n } catch {\n // best-effort\n } finally {\n setLoading(false);\n }\n };\n\n useEffect(() => { load(); }, []);\n\n const formatDuration = (ms?: number): string => {\n if (!ms) return '—';\n const s = Math.floor(ms / 1000);\n if (s < 60) return `${s}s`;\n const m = Math.floor(s / 60);\n const r = s % 60;\n return `${m}m ${r}s`;\n };\n\n const formatTime = (ts: string): string => {\n const d = new Date(ts);\n return d.toLocaleString();\n };\n\n const filtered = query.trim()\n ? entries.filter((entry) => {\n const q = query.toLowerCase();\n return (entry.agent || 'session').toLowerCase().includes(q)\n || (entry.summary || '').toLowerCase().includes(q)\n || (entry.output || '').toLowerCase().includes(q);\n })\n : entries;\n\n return (\n <div className=\"mobile-view\">\n <div className=\"mobile-tasks-toolbar\">\n <input\n className=\"mobile-search-input\"\n type=\"text\"\n placeholder=\"Search history…\"\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n style={{ flex: 1 }}\n />\n <button type=\"button\" className=\"mobile-icon-btn\" onClick={() => load()} aria-label=\"Refresh\">\n <RefreshCw size={16} />\n </button>\n </div>\n\n {loading ? (\n <div className=\"mobile-loading\"><p>Loading…</p></div>\n ) : filtered.length === 0 ? (\n <div className=\"mobile-empty\">\n <History size={40} />\n <p>{entries.length === 0 ? 'No history yet.' : 'No matching sessions.'}</p>\n </div>\n ) : (\n <div className=\"mobile-card-list\">\n {filtered.map((e) => (\n <button\n key={e.id}\n type=\"button\"\n className=\"mobile-list-item mobile-list-item-interactive\"\n onClick={() => setDetail(e)}\n >\n <div className=\"mobile-list-icon\"><History size={16} /></div>\n <div className=\"mobile-list-content\">\n <span className=\"mobile-list-title\">{e.agent || 'Session'}</span>\n <span className=\"mobile-list-meta\">\n {formatTime(e.ts)}\n {e.duration ? ` · ${formatDuration(e.duration)}` : ''}\n {e.taskCount ? ` · ${e.taskCount} tasks` : ''}\n </span>\n </div>\n </button>\n ))}\n </div>\n )}\n\n {/* Detail sheet */}\n {detail && (\n <MobileBottomSheet open={true} onClose={() => setDetail(null)} title=\"Session Detail\">\n <div className=\"mobile-agent-detail-meta\">\n <div className=\"mobile-task-detail-row\"><span>Agent</span><span>{detail.agent || '—'}</span></div>\n <div className=\"mobile-task-detail-row\"><span>Started</span><span>{formatTime(detail.ts)}</span></div>\n <div className=\"mobile-task-detail-row\"><span>Duration</span><span>{formatDuration(detail.duration)}</span></div>\n <div className=\"mobile-task-detail-row\"><span>Tasks</span><span>{detail.taskCount || 0}</span></div>\n </div>\n {detail.summary && (\n <div style={{ marginTop: 12 }}>\n <h4 style={{ fontSize: 12, color: 'var(--text-muted)', marginBottom: 4 }}>Summary</h4>\n <p style={{ fontSize: 13 }}>{detail.summary}</p>\n </div>\n )}\n {detail.output && (\n <div style={{ marginTop: 12 }}>\n <h4 style={{ fontSize: 12, color: 'var(--text-muted)', marginBottom: 4 }}>Output</h4>\n <pre className=\"mono\" style={{ fontSize: 11, whiteSpace: 'pre-wrap', wordBreak: 'break-all', background: 'var(--bg-elev-2)', padding: 8, borderRadius: 6 }}>\n {detail.output.slice(0, 1000)}{detail.output.length > 1000 ? '…' : ''}\n </pre>\n </div>\n )}\n </MobileBottomSheet>\n )}\n </div>\n );\n}\n","// src/mobile/views/MobileMods.tsx — mods list with detail sheet.\nimport { useEffect, useState } from 'react';\nimport { Sliders, RefreshCw, Power } from 'lucide-react';\nimport { api } from '../../lib/api';\nimport type { Mod, Snapshot } from '../../lib/types';\nimport { MobileBottomSheet } from '../components/MobileBottomSheet';\n\ntype Props = {\n snapshot: Snapshot;\n onBack: () => void;\n};\n\nexport function MobileMods({ snapshot, onBack }: Props) {\n const [mods, setMods] = useState<Mod[]>(snapshot.mods || []);\n const [loading, setLoading] = useState(!snapshot.mods);\n const [filter, setFilter] = useState('');\n const [statusFilter, setStatusFilter] = useState<'all' | 'enabled' | 'disabled'>('all');\n const [detailMod, setDetailMod] = useState<Mod | null>(null);\n\n const reload = async () => {\n try {\n const data = await api.get<{ mods: Mod[] }>('/mods');\n setMods(data.mods || []);\n } catch {\n // best-effort\n } finally {\n setLoading(false);\n }\n };\n\n useEffect(() => {\n if (snapshot.mods) {\n setMods(snapshot.mods);\n setLoading(false);\n } else {\n reload();\n }\n }, [snapshot.mods]);\n\n const toggleMod = async (id: string, enabled: boolean) => {\n try {\n await api.patch(`/mods/${encodeURIComponent(id)}`, { enabled });\n setMods((cur) => cur.map((m) => (m.id === id ? { ...m, enabled } : m)));\n if (detailMod?.id === id) setDetailMod((m) => m ? { ...m, enabled } : m);\n } catch {\n // best-effort\n }\n };\n\n const filtered = mods.filter((m) => {\n if (statusFilter === 'enabled' && !m.enabled) return false;\n if (statusFilter === 'disabled' && m.enabled) return false;\n if (filter) {\n const q = filter.toLowerCase();\n return m.name.toLowerCase().includes(q) || m.description.toLowerCase().includes(q);\n }\n return true;\n });\n\n return (\n <div className=\"mobile-view\">\n <div className=\"mobile-tasks-toolbar\">\n <input\n className=\"mobile-search-input\"\n type=\"text\"\n placeholder=\"Search mods…\"\n value={filter}\n onChange={(e) => setFilter(e.target.value)}\n style={{ flex: 1 }}\n />\n <button type=\"button\" className=\"mobile-icon-btn\" onClick={() => reload()} aria-label=\"Refresh\">\n <RefreshCw size={16} />\n </button>\n </div>\n\n <div className=\"mobile-search-scopes\">\n {(['all', 'enabled', 'disabled'] as const).map((s) => (\n <button key={s} type=\"button\" className={`mobile-scope-chip ${statusFilter === s ? 'active' : ''}`}\n onClick={() => setStatusFilter(s)}>\n {s.charAt(0).toUpperCase() + s.slice(1)}\n </button>\n ))}\n </div>\n\n {loading ? (\n <div className=\"mobile-loading\"><p>Loading…</p></div>\n ) : filtered.length === 0 ? (\n <div className=\"mobile-empty\">\n <Sliders size={40} />\n <p>No mods found.</p>\n </div>\n ) : (\n <div className=\"mobile-card-list\">\n {filtered.map((m) => (\n <button\n key={m.id}\n type=\"button\"\n className=\"mobile-list-item mobile-list-item-interactive\"\n onClick={() => setDetailMod(m)}\n >\n <div className=\"mobile-list-icon\"><Sliders size={16} /></div>\n <div className=\"mobile-list-content\">\n <span className=\"mobile-list-title\">{m.name}</span>\n <span className=\"mobile-list-meta\">v{m.version} · {m.author}</span>\n </div>\n <span className={`mobile-list-badge ${m.enabled ? 'badge-on' : 'badge-off'}`}>\n {m.enabled ? 'on' : 'off'}\n </span>\n </button>\n ))}\n </div>\n )}\n\n {detailMod && (\n <MobileBottomSheet\n open={true}\n onClose={() => setDetailMod(null)}\n title={detailMod.name}\n actions={\n <div className=\"mobile-task-detail-actions\">\n <button\n type=\"button\"\n className=\"mobile-btn\"\n style={{ flex: 1 }}\n onClick={() => { if (detailMod) toggleMod(detailMod.id, !detailMod.enabled); }}\n >\n <Power size={14} /> {detailMod.enabled ? 'Disable' : 'Enable'}\n </button>\n </div>\n }\n >\n <div className=\"mobile-agent-detail\">\n <p className=\"mobile-agent-detail-desc\">{detailMod.description || 'No description.'}</p>\n <div className=\"mobile-agent-detail-meta\">\n <div className=\"mobile-task-detail-row\"><span>Version</span><span className=\"mono\">{detailMod.version}</span></div>\n <div className=\"mobile-task-detail-row\"><span>Author</span><span>{detailMod.author}</span></div>\n <div className=\"mobile-task-detail-row\"><span>Bizar</span><span className=\"mono\">{detailMod.bizar}</span></div>\n <div className=\"mobile-task-detail-row\"><span>Type</span><span>{detailMod.type}</span></div>\n <div className=\"mobile-task-detail-row\"><span>Permissions</span><span>{detailMod.permissions.length}</span></div>\n {detailMod.installedAt && (\n <div className=\"mobile-task-detail-row\"><span>Installed</span><span>{new Date(detailMod.installedAt).toLocaleDateString()}</span></div>\n )}\n </div>\n </div>\n </MobileBottomSheet>\n )}\n </div>\n );\n}\n","// src/mobile/views/MobileMore.tsx — real navigation hub: Plans, Agents, Skills, Mods, Schedules, History, Config.\nimport { useState } from 'react';\nimport { Bot, FileText, Sliders, Clock, History, Settings, ChevronRight } from 'lucide-react';\nimport type { Snapshot } from '../../lib/types';\n\ntype Props = {\n snapshot: Snapshot;\n onNavigate: (id: string) => void;\n};\n\nexport function MobileMore({ snapshot, onNavigate }: Props) {\n const [search, setSearch] = useState('');\n\n const entries = [\n {\n id: 'artifacts',\n icon: FileText,\n label: 'Plans',\n count: snapshot.artifacts.length || 0,\n desc: 'Visual artifacts with elements & comments',\n },\n {\n id: 'agents',\n icon: Bot,\n label: 'Agents',\n count: snapshot.agents?.length || 0,\n desc: 'The Norse pantheon',\n },\n {\n id: 'skills',\n icon: Sliders,\n label: 'Skills',\n count: null,\n desc: 'Agent capabilities & tools',\n },\n {\n id: 'mods',\n icon: Sliders,\n label: 'Mods',\n count: snapshot.mods?.length || 0,\n desc: 'Installed modifications',\n },\n {\n id: 'schedules',\n icon: Clock,\n label: 'Schedules',\n count: snapshot.schedules?.length || 0,\n desc: 'Cron jobs & automated tasks',\n },\n {\n id: 'history',\n icon: History,\n label: 'History',\n count: null,\n desc: 'Past sessions & outputs',\n },\n {\n id: 'config',\n icon: Settings,\n label: 'Config',\n count: null,\n desc: 'Key-value configuration editor',\n },\n ];\n\n const filtered = search.trim()\n ? entries.filter((e) => e.label.toLowerCase().includes(search.toLowerCase()) || e.desc.toLowerCase().includes(search.toLowerCase()))\n : entries;\n\n return (\n <div className=\"mobile-view\">\n {/* Search */}\n <div className=\"mobile-tasks-toolbar\">\n <input\n className=\"mobile-search-input\"\n type=\"text\"\n placeholder=\"Search…\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n style={{ flex: 1 }}\n />\n </div>\n\n {/* Nav entries */}\n <div className=\"mobile-card-list\">\n {filtered.map((entry) => {\n const Icon = entry.icon;\n return (\n <button\n key={entry.id}\n type=\"button\"\n className=\"mobile-more-nav-item\"\n onClick={() => onNavigate(entry.id)}\n >\n <div className=\"mobile-more-nav-icon\">\n <Icon size={20} />\n </div>\n <div className=\"mobile-more-nav-content\">\n <span className=\"mobile-more-nav-label\">\n {entry.label}\n {entry.count != null && (\n <span className=\"mobile-more-nav-count\">{entry.count}</span>\n )}\n </span>\n <span className=\"mobile-more-nav-desc\">{entry.desc}</span>\n </div>\n <ChevronRight size={16} className=\"mobile-more-nav-arrow\" />\n </button>\n );\n })}\n </div>\n\n {/* Footer */}\n <div className=\"mobile-more-footer\">\n <a href=\"/?desktop=1\" className=\"mobile-more-link\">\n Switch to Desktop <ChevronRight size={14} />\n </a>\n </div>\n </div>\n );\n}\n","// src/mobile/views/MobileArtifactCanvas.tsx — mobile plan canvas: read-only + edit mode.\nimport { useEffect, useState } from 'react';\nimport { Plus, MessageSquare, Pencil, Trash2, CheckSquare, StickyNote, HelpCircle, X } from 'lucide-react';\nimport { api } from '../../lib/api';\nimport { formatRelative } from '../../lib/utils';\nimport type { Canvas, CanvasElement } from '../../lib/types';\nimport { MobileBottomSheet } from '../components/MobileBottomSheet';\nimport { MobileModal } from '../components/MobileModal';\n\ntype Props = {\n slug: string;\n onBack: () => void;\n};\n\nconst ELEMENT_TYPES = [\n { id: 'task', label: 'Task', color: 'var(--info)' },\n { id: 'note', label: 'Note', color: 'var(--text-dim)' },\n { id: 'decision', label: 'Decision', color: 'var(--accent)' },\n { id: 'question', label: 'Question', color: 'var(--warning)' },\n];\n\nfunction elementColor(type: string): string {\n return ELEMENT_TYPES.find((t) => t.id === type)?.color || 'var(--text-dim)';\n}\n\nfunction ElementIcon({ type, size = 12 }: { type: string; size?: number }) {\n switch (type) {\n case 'task': return <CheckSquare size={size} style={{ color: 'var(--info)' }} />;\n case 'note': return <StickyNote size={size} style={{ color: 'var(--text-dim)' }} />;\n case 'decision': return <CheckSquare size={size} style={{ color: 'var(--accent)' }} />;\n case 'question': return <HelpCircle size={size} style={{ color: 'var(--warning)' }} />;\n default: return <StickyNote size={size} />;\n }\n}\n\nexport function MobileArtifactCanvas({ slug, onBack }: Props) {\n const [canvas, setCanvas] = useState<Canvas | null>(null);\n const [meta, setMeta] = useState<{ title: string; status: string; tags: string[]; description?: string } | null>(null);\n const [loading, setLoading] = useState(true);\n const [editMode, setEditMode] = useState(false);\n const [selectedEl, setSelectedEl] = useState<CanvasElement | null>(null);\n const [showComments, setShowComments] = useState(false);\n const [addElementOpen, setAddElementOpen] = useState(false);\n const [addCommentOpen, setAddCommentOpen] = useState(false);\n const [commentText, setCommentText] = useState('');\n\n const reload = async () => {\n setLoading(true);\n try {\n const plan = await api.get<{ meta: typeof meta; canvas: Canvas }>(\n `/plans/${encodeURIComponent(slug)}`,\n );\n setCanvas(plan.canvas);\n setMeta(plan.meta);\n } catch {\n // best-effort\n } finally {\n setLoading(false);\n }\n };\n\n useEffect(() => { reload(); }, [slug]);\n\n const addElement = async (type: string, title: string, content: string) => {\n try {\n const maxX = (canvas?.elements || []).reduce((acc, el) => Math.max(acc, el.x || 0), 40);\n const maxY = (canvas?.elements || []).reduce((acc, el) => Math.max(acc, el.y || 0), 40);\n await api.post(`/plans/${encodeURIComponent(slug)}/elements`, {\n type,\n title,\n content,\n x: 20 + maxX,\n y: 20 + maxY,\n width: 240,\n height: 120,\n });\n await reload();\n setAddElementOpen(false);\n } catch {\n // best-effort\n }\n };\n\n const updateElement = async (id: string, body: Partial<CanvasElement>) => {\n try {\n await api.put(`/plans/${encodeURIComponent(slug)}/elements/${encodeURIComponent(id)}`, body);\n await reload();\n setSelectedEl(null);\n } catch {\n // best-effort\n }\n };\n\n const deleteElement = async (id: string) => {\n if (!confirm('Delete this element?')) return;\n try {\n await api.del(`/plans/${encodeURIComponent(slug)}/elements/${encodeURIComponent(id)}`);\n setSelectedEl(null);\n await reload();\n } catch {\n // best-effort\n }\n };\n\n const addComment = async (text: string, elementId: string | null) => {\n try {\n const path = elementId\n ? `/plans/${encodeURIComponent(slug)}/elements/${encodeURIComponent(elementId)}/comments`\n : `/plans/${encodeURIComponent(slug)}/comments`;\n await api.post(path, { text, elementId });\n await reload();\n setCommentText('');\n setAddCommentOpen(false);\n } catch {\n // best-effort\n }\n };\n\n if (loading || !canvas || !meta) {\n return (\n <div className=\"mobile-view\">\n <div className=\"mobile-loading\"><p>Loading canvas…</p></div>\n </div>\n );\n }\n\n return (\n <div className=\"mobile-view mobile-view-canvas\">\n {/* Canvas top bar */}\n <div className=\"mobile-canvas-bar\">\n <div className=\"mobile-canvas-meta\">\n <span className=\"mobile-canvas-title\">{meta.title || slug}</span>\n <span className=\"mobile-canvas-status\" data-status={meta.status}>{meta.status}</span>\n </div>\n <div className=\"mobile-canvas-actions\">\n <button\n type=\"button\"\n className={`mobile-icon-btn ${editMode ? 'active' : ''}`}\n onClick={() => setEditMode((v) => !v)}\n aria-label={editMode ? 'Done editing' : 'Edit'}\n >\n <Pencil size={16} />\n </button>\n <button\n type=\"button\"\n className=\"mobile-icon-btn\"\n onClick={() => setShowComments((v) => !v)}\n aria-label=\"Comments\"\n >\n <MessageSquare size={16} />\n {canvas.comments.length > 0 && (\n <span className=\"mobile-canvas-comment-count\">{canvas.comments.length}</span>\n )}\n </button>\n {editMode && (\n <button\n type=\"button\"\n className=\"mobile-icon-btn\"\n onClick={() => setAddElementOpen(true)}\n aria-label=\"Add element\"\n >\n <Plus size={16} />\n </button>\n )}\n </div>\n </div>\n\n {/* Elements list */}\n <div className=\"mobile-canvas-elements\">\n {canvas.elements.length === 0 && (\n <div className=\"mobile-empty\">\n <p>No elements yet.</p>\n {editMode && (\n <button\n type=\"button\"\n className=\"mobile-btn\"\n onClick={() => setAddElementOpen(true)}\n >\n <Plus size={14} /> Add Element\n </button>\n )}\n </div>\n )}\n {canvas.elements.map((el) => (\n <button\n key={el.id}\n type=\"button\"\n className={`mobile-canvas-element ${selectedEl?.id === el.id ? 'selected' : ''}`}\n style={{ borderColor: elementColor(el.type) }}\n onClick={() => {\n if (editMode) {\n setSelectedEl(el);\n }\n }}\n aria-pressed={selectedEl?.id === el.id}\n >\n <div className=\"mobile-canvas-el-header\" style={{ background: `${elementColor(el.type)}18` }}>\n <ElementIcon type={el.type} size={12} />\n <span className=\"mobile-canvas-el-type\" style={{ color: elementColor(el.type) }}>{el.type}</span>\n {el.status && el.status !== 'open' && (\n <span className=\"mobile-canvas-el-status\">{el.status}</span>\n )}\n </div>\n <div className=\"mobile-canvas-el-title\">{el.title || 'Untitled'}</div>\n {el.content && (\n <div className=\"mobile-canvas-el-content\">\n {el.content.slice(0, 120)}{el.content.length > 120 ? '…' : ''}\n </div>\n )}\n <div className=\"mobile-canvas-el-comments\">\n <MessageSquare size={10} />\n {canvas.comments.filter((c) => c.elementId === el.id).length}\n </div>\n </button>\n ))}\n </div>\n\n {/* Comments panel */}\n {showComments && (\n <div className=\"mobile-canvas-comments\">\n <div className=\"mobile-canvas-comments-header\">\n <h4>Comments ({canvas.comments.length})</h4>\n <button type=\"button\" className=\"mobile-icon-btn\" onClick={() => setShowComments(false)}>\n <X size={16} />\n </button>\n </div>\n <div className=\"mobile-canvas-comment-list\">\n {canvas.comments.length === 0 && (\n <p className=\"mobile-empty-inline\">No comments yet.</p>\n )}\n {canvas.comments.map((c) => (\n <div key={c.id} className=\"mobile-canvas-comment\">\n <div className=\"mobile-canvas-comment-head\">\n <span className=\"mono\">{c.author}</span>\n <span className=\"muted\">{formatRelative(c.created)}</span>\n </div>\n <div className=\"mobile-canvas-comment-text\">{c.text}</div>\n </div>\n ))}\n </div>\n <div className=\"mobile-canvas-comment-input\">\n <textarea\n className=\"mobile-input\"\n rows={2}\n placeholder=\"Add a comment…\"\n value={commentText}\n onChange={(e) => setCommentText(e.target.value)}\n />\n <button\n type=\"button\"\n className=\"mobile-btn\"\n disabled={!commentText.trim()}\n onClick={() => { addComment(commentText.trim(), null); }}\n >\n Post\n </button>\n </div>\n </div>\n )}\n\n {/* Element edit sheet */}\n {selectedEl && (\n <MobileBottomSheet\n open={true}\n onClose={() => setSelectedEl(null)}\n title={`Edit ${selectedEl.title || selectedEl.type}`}\n actions={\n <div className=\"mobile-task-detail-actions\">\n <button\n type=\"button\"\n className=\"mobile-btn mobile-btn-danger\"\n onClick={() => deleteElement(selectedEl.id)}\n >\n <Trash2 size={14} /> Delete\n </button>\n <button\n type=\"button\"\n className=\"mobile-btn\"\n onClick={() => setAddCommentOpen(true)}\n >\n <MessageSquare size={14} /> Comment\n </button>\n </div>\n }\n >\n <ElementEditForm\n element={selectedEl}\n onSave={(body) => updateElement(selectedEl.id, body)}\n onClose={() => setSelectedEl(null)}\n />\n </MobileBottomSheet>\n )}\n\n {/* Add element modal */}\n <AddElementModal\n open={addElementOpen}\n onClose={() => setAddElementOpen(false)}\n onAdd={addElement}\n />\n\n {/* Add comment modal */}\n <MobileModal open={addCommentOpen} onClose={() => setAddCommentOpen(false)} title=\"Add Comment\" actions={\n <button\n type=\"button\"\n className=\"mobile-btn\"\n style={{ width: '100%' }}\n disabled={!commentText.trim()}\n onClick={() => { addComment(commentText.trim(), selectedEl?.id || null); setAddCommentOpen(false); }}\n >\n <MessageSquare size={14} /> Post\n </button>\n }>\n <textarea\n className=\"mobile-input\"\n rows={3}\n placeholder=\"Your comment…\"\n value={commentText}\n onChange={(e) => setCommentText(e.target.value)}\n autoFocus\n />\n </MobileModal>\n </div>\n );\n}\n\nfunction ElementEditForm({\n element,\n onSave,\n onClose,\n}: {\n element: CanvasElement;\n onSave: (body: Partial<CanvasElement>) => void;\n onClose: () => void;\n}) {\n const [type, setType] = useState(element.type);\n const [title, setTitle] = useState(element.title || '');\n const [content, setContent] = useState(element.content || '');\n const [status, setStatus] = useState(element.status || 'open');\n\n return (\n <form\n className=\"mobile-task-form\"\n onSubmit={(e) => {\n e.preventDefault();\n onSave({ type, title, content, status });\n onClose();\n }}\n >\n <label className=\"mobile-field-label\">Type</label>\n <select className=\"mobile-input\" value={type} onChange={(e) => setType(e.target.value)}>\n {ELEMENT_TYPES.map((t) => (\n <option key={t.id} value={t.id}>{t.label}</option>\n ))}\n </select>\n <label className=\"mobile-field-label\">Title</label>\n <input\n className=\"mobile-input\"\n type=\"text\"\n value={title}\n onChange={(e) => setTitle(e.target.value)}\n />\n <label className=\"mobile-field-label\">Status</label>\n <input\n className=\"mobile-input\"\n type=\"text\"\n value={status}\n onChange={(e) => setStatus(e.target.value)}\n placeholder=\"open / done / blocked\"\n />\n <label className=\"mobile-field-label\">Content (markdown)</label>\n <textarea\n className=\"mobile-input\"\n rows={4}\n value={content}\n onChange={(e) => setContent(e.target.value)}\n />\n <button type=\"submit\" className=\"mobile-btn\" style={{ width: '100%', marginTop: 8 }}>\n Save\n </button>\n </form>\n );\n}\n\nfunction AddElementModal({\n open,\n onClose,\n onAdd,\n}: {\n open: boolean;\n onClose: () => void;\n onAdd: (type: string, title: string, content: string) => void;\n}) {\n const [type, setType] = useState('task');\n const [title, setTitle] = useState('');\n const [content, setContent] = useState('');\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n if (!title.trim()) return;\n onAdd(type, title.trim(), content.trim());\n setTitle('');\n setContent('');\n };\n\n return (\n <MobileModal open={open} onClose={onClose} title=\"Add Element\" actions={\n <button type=\"submit\" form=\"add-el-form\" className=\"mobile-btn\" style={{ width: '100%' }}>\n <Plus size={14} /> Add\n </button>\n }>\n <form id=\"add-el-form\" onSubmit={handleSubmit} className=\"mobile-task-form\">\n <label className=\"mobile-field-label\">Type</label>\n <select className=\"mobile-input\" value={type} onChange={(e) => setType(e.target.value)}>\n {ELEMENT_TYPES.map((t) => (\n <option key={t.id} value={t.id}>{t.label}</option>\n ))}\n </select>\n <label className=\"mobile-field-label\">Title *</label>\n <input\n className=\"mobile-input\"\n type=\"text\"\n placeholder=\"Element title\"\n value={title}\n onChange={(e) => setTitle(e.target.value)}\n autoFocus\n required\n />\n <label className=\"mobile-field-label\">Content (markdown)</label>\n <textarea\n className=\"mobile-input\"\n rows={3}\n placeholder=\"Description…\"\n value={content}\n onChange={(e) => setContent(e.target.value)}\n />\n </form>\n </MobileModal>\n );\n}\n","// src/mobile/views/MobileArtifacts.tsx — plan list with tap-to-open.\nimport { useEffect, useState } from 'react';\nimport { FileText, Plus, RefreshCw } from 'lucide-react';\nimport { api } from '../../lib/api';\nimport type { Artifact, Snapshot } from '../../lib/types';\nimport { MobileModal } from '../components/MobileModal';\n\ntype Props = {\n snapshot: Snapshot;\n onBack: () => void;\n onOpenArtifact: (slug: string) => void;\n};\n\nexport function MobileArtifacts({ snapshot, onBack, onOpenArtifact }: Props) {\n const [artifacts, setArtifacts] = useState<Artifact[]>(snapshot.artifacts || []);\n const [loading, setLoading] = useState(!snapshot.artifacts);\n const [filter, setFilter] = useState('');\n const [newOpen, setNewOpen] = useState(false);\n const [newSlug, setNewSlug] = useState('');\n const [newTitle, setNewTitle] = useState('');\n\n const reload = async () => {\n try {\n const data = await api.get<{ artifacts: Artifact[] }>('/artifacts');\n setArtifacts(data.artifacts || []);\n } catch {\n // best-effort\n } finally {\n setLoading(false);\n }\n };\n\n const createPlan = async () => {\n if (!newSlug.trim()) return;\n try {\n const result = await api.post<{ slug: string }>('/artifacts', {\n slug: newSlug.trim(),\n title: newTitle.trim() || undefined,\n });\n setNewOpen(false);\n setNewSlug('');\n setNewTitle('');\n onOpenArtifact(result.slug);\n } catch {\n // best-effort\n }\n };\n\n const filtered = filter.trim()\n ? artifacts.filter((p) =>\n (p.title || p.slug || '').toLowerCase().includes(filter.toLowerCase()),\n )\n : artifacts;\n\n return (\n <div className=\"mobile-view\">\n <div className=\"mobile-tasks-toolbar\">\n <input\n className=\"mobile-search-input\"\n type=\"text\"\n placeholder=\"Search artifacts…\"\n value={filter}\n onChange={(e) => setFilter(e.target.value)}\n style={{ flex: 1 }}\n />\n <button type=\"button\" className=\"mobile-icon-btn\" onClick={() => reload()} aria-label=\"Refresh\">\n <RefreshCw size={16} />\n </button>\n </div>\n\n {/* New plan */}\n <div style={{ marginBottom: 12 }}>\n <button\n type=\"button\"\n className=\"mobile-btn\"\n style={{ width: '100%' }}\n onClick={() => setNewOpen(true)}\n >\n <Plus size={14} /> New Artifact\n </button>\n </div>\n\n {loading ? (\n <div className=\"mobile-loading\"><p>Loading…</p></div>\n ) : filtered.length === 0 ? (\n <div className=\"mobile-empty\">\n <FileText size={40} />\n <p>No artifacts yet.</p>\n </div>\n ) : (\n <div className=\"mobile-card-list\">\n {filtered.map((p) => (\n <button\n key={p.slug}\n type=\"button\"\n className=\"mobile-list-item mobile-list-item-interactive\"\n onClick={() => onOpenArtifact(p.slug)}\n >\n <div className=\"mobile-list-icon\"><FileText size={16} /></div>\n <div className=\"mobile-list-content\">\n <span className=\"mobile-list-title\">{p.title || p.slug}</span>\n <span className=\"mobile-list-meta\">\n {p.status} · {p.source}\n {p.elementCount != null ? ` · ${p.elementCount} elements` : ''}\n {p.commentCount != null ? ` · ${p.commentCount} comments` : ''}\n </span>\n </div>\n <span className={`mobile-list-badge`} data-status={p.status}>{p.status}</span>\n </button>\n ))}\n </div>\n )}\n\n <MobileModal open={newOpen} onClose={() => setNewOpen(false)} title=\"New Artifact\" actions={\n <button type=\"submit\" form=\"new-plan-form\" className=\"mobile-btn\" style={{ width: '100%' }}>\n <Plus size={14} /> Create\n </button>\n }>\n <form id=\"new-plan-form\" onSubmit={(e) => { e.preventDefault(); createPlan(); }} className=\"mobile-task-form\">\n <label className=\"mobile-field-label\">Slug *</label>\n <input\n className=\"mobile-input\"\n type=\"text\"\n placeholder=\"my-plan\"\n pattern=\"[a-z0-9][a-z0-9-]{0,63}\"\n value={newSlug}\n onChange={(e) => setNewSlug(e.target.value)}\n autoFocus\n required\n />\n <label className=\"mobile-field-label\">Title (optional)</label>\n <input\n className=\"mobile-input\"\n type=\"text\"\n placeholder=\"My Artifact\"\n value={newTitle}\n onChange={(e) => setNewTitle(e.target.value)}\n />\n </form>\n </MobileModal>\n </div>\n );\n}\n","// src/mobile/views/MobileSchedules.tsx — schedules list with detail sheet, edit modal, and a reusable New/Edit modal.\n//\n// v3.9.0 — overhaul:\n// - PATCH for partial updates (toggle enabled, edit fields) is now\n// supported by the backend (see routes/schedules.mjs).\n// - NewScheduleModal accepts an optional `initial` schedule and submits\n// PUT when editing, POST when creating. The same modal handles both\n// flows so the operator doesn't context-switch between forms.\n// - Detail sheet exposes an Edit button so the mobile UI can edit any\n// schedule, not just toggle it.\n// - The structured pickers mirror the desktop (DOW, hour, minute,\n// timezone) but in a more compact layout suited to a phone screen.\nimport { useEffect, useState } from 'react';\nimport { Clock, Plus, RefreshCw, Trash2, Play, Pencil } from 'lucide-react';\nimport { api } from '../../lib/api';\nimport type { Schedule, Snapshot } from '../../lib/types';\nimport { MobileBottomSheet } from '../components/MobileBottomSheet';\nimport { MobileModal } from '../components/MobileModal';\n\ntype Props = {\n snapshot: Snapshot;\n onBack: () => void;\n};\n\nconst TYPES: Array<Schedule['type']> = ['cron', 'interval', 'once'];\nconst ACTIONS: Array<Schedule['action']['type']> = ['command', 'agent', 'webhook'];\n\nconst TIMEZONES = ['UTC', 'America/New_York', 'America/Los_Angeles', 'Europe/London', 'Europe/Berlin', 'Asia/Tokyo'];\n\nconst HOURS = Array.from({ length: 24 }, (_, h) => ({\n value: h,\n label: h === 0 ? '12 AM' : h < 12 ? `${h} AM` : h === 12 ? '12 PM' : `${h - 12} PM`,\n}));\nconst MINUTES = Array.from({ length: 12 }, (_, i) => i * 5); // 0,5,…,55\nconst DOWS = [\n { value: '*', label: 'Every day' },\n { value: '0', label: 'Sun' },\n { value: '1', label: 'Mon' },\n { value: '2', label: 'Tue' },\n { value: '3', label: 'Wed' },\n { value: '4', label: 'Thu' },\n { value: '5', label: 'Fri' },\n { value: '6', label: 'Sat' },\n];\nconst INTERVAL_UNITS = [\n { value: 's', label: 'sec' },\n { value: 'm', label: 'min' },\n { value: 'h', label: 'hr' },\n { value: 'd', label: 'day' },\n];\n\nexport function MobileSchedules({ snapshot }: Props) {\n const [schedules, setSchedules] = useState<Schedule[]>(snapshot.schedules || []);\n const [loading, setLoading] = useState(!snapshot.schedules);\n const [filter, setFilter] = useState('');\n const [detailSchedule, setDetailSchedule] = useState<Schedule | null>(null);\n const [editor, setEditor] = useState<{ open: boolean; initial?: Schedule }>({ open: false });\n\n const reload = async () => {\n try {\n const data = await api.get<{ schedules: Schedule[] }>('/schedules');\n setSchedules(data.schedules || []);\n } catch {\n // best-effort\n } finally {\n setLoading(false);\n }\n };\n\n useEffect(() => {\n if (snapshot.schedules) {\n setSchedules(snapshot.schedules);\n setLoading(false);\n } else {\n reload();\n }\n }, [snapshot.schedules]);\n\n // v3.9.0 — PATCH partial update (toggle enabled, edit name, etc.).\n // The backend supports PATCH (see routes/schedules.mjs).\n const toggleSchedule = async (id: string, enabled: boolean) => {\n try {\n await api.patch(`/schedules/${encodeURIComponent(id)}`, { enabled });\n setSchedules((cur) => cur.map((s) => (s.id === id ? { ...s, enabled } : s)));\n if (detailSchedule?.id === id) {\n setDetailSchedule((s) => (s ? { ...s, enabled } : s));\n }\n } catch {\n // best-effort\n }\n };\n\n const deleteSchedule = async (id: string) => {\n if (!confirm('Delete this schedule?')) return;\n try {\n await api.del(`/schedules/${encodeURIComponent(id)}`);\n setSchedules((cur) => cur.filter((s) => s.id !== id));\n if (detailSchedule?.id === id) setDetailSchedule(null);\n } catch {\n // best-effort\n }\n };\n\n // v3.9.0 — backend added /trigger as an alias of /run (mobile uses this).\n const triggerSchedule = async (id: string) => {\n try {\n await api.post(`/schedules/${encodeURIComponent(id)}/trigger`);\n } catch {\n // best-effort\n }\n };\n\n const filtered = filter.trim()\n ? schedules.filter((s) => s.name.toLowerCase().includes(filter.toLowerCase()))\n : schedules;\n\n return (\n <div className=\"mobile-view\">\n <div className=\"mobile-tasks-toolbar\">\n <input\n className=\"mobile-search-input\"\n type=\"text\"\n placeholder=\"Search schedules…\"\n value={filter}\n onChange={(e) => setFilter(e.target.value)}\n style={{ flex: 1 }}\n />\n <button type=\"button\" className=\"mobile-icon-btn\" onClick={() => reload()} aria-label=\"Refresh\">\n <RefreshCw size={16} />\n </button>\n <button\n type=\"button\"\n className=\"mobile-icon-btn\"\n onClick={() => setEditor({ open: true })}\n aria-label=\"New schedule\"\n >\n <Plus size={16} />\n </button>\n </div>\n\n {loading ? (\n <div className=\"mobile-loading\"><p>Loading…</p></div>\n ) : filtered.length === 0 ? (\n <div className=\"mobile-empty\">\n <Clock size={40} />\n <p>No schedules found.</p>\n </div>\n ) : (\n <div className=\"mobile-card-list\">\n {filtered.map((s) => (\n <button\n key={s.id}\n type=\"button\"\n className=\"mobile-list-item mobile-list-item-interactive\"\n onClick={() => setDetailSchedule(s)}\n >\n <div className=\"mobile-list-icon\"><Clock size={16} /></div>\n <div className=\"mobile-list-content\">\n <span className=\"mobile-list-title\">{s.name}</span>\n <span className=\"mobile-list-meta\">\n {humanizeSchedule(s)} ·{' '}\n {s.nextRun ? `next ${formatNextShort(s.nextRun, s.timezone)}` : 'no next run'}\n </span>\n </div>\n <span className={`mobile-list-badge ${s.enabled ? 'badge-on' : 'badge-off'}`}>\n {s.enabled ? 'on' : 'off'}\n </span>\n </button>\n ))}\n </div>\n )}\n\n {detailSchedule && (\n <MobileBottomSheet\n open={true}\n onClose={() => setDetailSchedule(null)}\n title={detailSchedule.name}\n actions={\n <div className=\"mobile-task-detail-actions\">\n <button\n type=\"button\"\n className=\"mobile-btn\"\n onClick={() => { if (detailSchedule) triggerSchedule(detailSchedule.id); }}\n >\n <Play size={14} /> Trigger now\n </button>\n <button\n type=\"button\"\n className={`mobile-btn ${!detailSchedule.enabled ? '' : 'mobile-btn-secondary'}`}\n onClick={() => { if (detailSchedule) toggleSchedule(detailSchedule.id, !detailSchedule.enabled); }}\n >\n {detailSchedule.enabled ? 'Disable' : 'Enable'}\n </button>\n <button\n type=\"button\"\n className=\"mobile-btn\"\n onClick={() => {\n if (!detailSchedule) return;\n setEditor({ open: true, initial: detailSchedule });\n setDetailSchedule(null);\n }}\n >\n <Pencil size={14} /> Edit\n </button>\n <button\n type=\"button\"\n className=\"mobile-btn mobile-btn-danger\"\n onClick={() => { if (detailSchedule) deleteSchedule(detailSchedule.id); }}\n >\n <Trash2 size={14} />\n </button>\n </div>\n }\n >\n <div className=\"mobile-agent-detail\">\n <div className=\"mobile-agent-detail-meta\">\n <div className=\"mobile-task-detail-row\">\n <span>Type</span>\n <span>{detailSchedule.type}</span>\n </div>\n <div className=\"mobile-task-detail-row\">\n <span>Schedule</span>\n <span className=\"mono\">{humanizeSchedule(detailSchedule) || detailSchedule.schedule}</span>\n </div>\n {detailSchedule.timezone && (\n <div className=\"mobile-task-detail-row\">\n <span>Timezone</span>\n <span className=\"mono\">{detailSchedule.timezone}</span>\n </div>\n )}\n <div className=\"mobile-task-detail-row\">\n <span>Action</span>\n <span>{detailSchedule.action.type}: {detailSchedule.action.target}</span>\n </div>\n {detailSchedule.action.type === 'agent' && detailSchedule.action.prompt && (\n <div className=\"mobile-task-detail-row\">\n <span>Prompt</span>\n <span style={{ fontSize: 12 }}>{detailSchedule.action.prompt}</span>\n </div>\n )}\n {detailSchedule.budgetCheck?.skipIfBudgetLow && (\n <div className=\"mobile-task-detail-row\">\n <span>Budget gate</span>\n <span>skip if ≥ {detailSchedule.budgetCheck.maxConcurrent ?? 6} concurrent</span>\n </div>\n )}\n <div className=\"mobile-task-detail-row\">\n <span>Next run</span>\n <span>{detailSchedule.nextRun ? formatNextShort(detailSchedule.nextRun, detailSchedule.timezone) : '—'}</span>\n </div>\n <div className=\"mobile-task-detail-row\">\n <span>Last run</span>\n <span>{detailSchedule.lastRun ? formatRel(detailSchedule.lastRun) : '—'}</span>\n </div>\n {detailSchedule.lastResult && (\n <div className=\"mobile-task-detail-row\">\n <span>Last result</span>\n <span style={{ fontSize: 12 }}>{detailSchedule.lastResult}</span>\n </div>\n )}\n {detailSchedule.lastError && (\n <div className=\"mobile-task-detail-row\">\n <span>Last error</span>\n <span style={{ fontSize: 12 }}>{detailSchedule.lastError}</span>\n </div>\n )}\n </div>\n </div>\n </MobileBottomSheet>\n )}\n\n <ScheduleEditor\n open={editor.open}\n initial={editor.initial}\n onClose={() => setEditor({ open: false })}\n onSaved={() => {\n setEditor({ open: false });\n reload();\n }}\n />\n </div>\n );\n}\n\n/* ------------------------------------------------------------------ */\n/* Reusable mobile editor modal */\n/* ------------------------------------------------------------------ */\n\ntype EditorState = {\n name: string;\n type: Schedule['type'];\n cronMinute: number;\n cronHour: number;\n cronDow: string;\n intervalN: number;\n intervalUnit: string;\n onceAt: string;\n timezone: string;\n actionType: Schedule['action']['type'];\n actionTarget: string;\n actionPrompt: string;\n skipIfBudgetLow: boolean;\n maxConcurrent: number;\n enabled: boolean;\n};\n\nfunction initialEditorState(initial?: Schedule): EditorState {\n if (!initial) {\n return {\n name: '',\n type: 'cron',\n cronMinute: 0,\n cronHour: 13,\n cronDow: '0',\n intervalN: 30,\n intervalUnit: 'm',\n onceAt: '',\n timezone: 'UTC',\n actionType: 'agent',\n actionTarget: '',\n actionPrompt: '',\n skipIfBudgetLow: false,\n maxConcurrent: 6,\n enabled: true,\n };\n }\n const parsedCron = initial.type === 'cron' ? parseSimpleCron(initial.schedule) : null;\n const parsedInterval = initial.type === 'interval' ? parseSimpleInterval(initial.schedule) : null;\n return {\n name: initial.name || '',\n type: initial.type,\n cronMinute: parsedCron?.minute ?? 0,\n cronHour: parsedCron?.hour ?? 9,\n cronDow: parsedCron?.dow ?? '*',\n intervalN: parsedInterval?.n ?? 30,\n intervalUnit: parsedInterval?.unit ?? 'm',\n onceAt: initial.type === 'once' ? toDatetimeLocal(initial.schedule) : '',\n timezone: initial.timezone || 'UTC',\n actionType: initial.action.type,\n actionTarget: initial.action.target || '',\n actionPrompt: initial.action.prompt || '',\n skipIfBudgetLow: !!initial.budgetCheck?.skipIfBudgetLow,\n maxConcurrent: Number.isFinite(initial.budgetCheck?.maxConcurrent)\n ? initial.budgetCheck!.maxConcurrent!\n : 6,\n enabled: initial.enabled !== false,\n };\n}\n\nfunction composeScheduleValue(s: EditorState): string {\n if (s.type === 'cron') return `${s.cronMinute} ${s.cronHour} * * ${s.cronDow}`;\n if (s.type === 'interval') return `${s.intervalN}${s.intervalUnit}`;\n if (!s.onceAt) return '';\n try {\n return new Date(s.onceAt).toISOString();\n } catch {\n return s.onceAt;\n }\n}\n\nfunction ScheduleEditor({\n open,\n initial,\n onClose,\n onSaved,\n}: {\n open: boolean;\n initial?: Schedule;\n onClose: () => void;\n onSaved: () => void;\n}) {\n const [state, setState] = useState<EditorState>(() => initialEditorState(initial));\n const [submitting, setSubmitting] = useState(false);\n\n // Re-seed state when the modal is reopened with a different initial.\n useEffect(() => {\n if (open) setState(initialEditorState(initial));\n }, [open, initial]);\n\n const update = <K extends keyof EditorState>(key: K, value: EditorState[K]) => {\n setState((cur) => ({ ...cur, [key]: value }));\n };\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n if (!state.name.trim()) return;\n const schedule = composeScheduleValue(state);\n if (!schedule) return;\n setSubmitting(true);\n try {\n const payload = {\n name: state.name.trim(),\n type: state.type,\n schedule,\n timezone: state.timezone,\n action: {\n type: state.actionType,\n target: state.actionTarget.trim(),\n ...(state.actionType === 'agent' && state.actionPrompt.trim()\n ? { prompt: state.actionPrompt.trim() }\n : {}),\n },\n budgetCheck: {\n maxConcurrent: state.maxConcurrent,\n skipIfBudgetLow: state.skipIfBudgetLow,\n },\n enabled: state.enabled,\n };\n if (initial) {\n await api.put(`/schedules/${encodeURIComponent(initial.id)}`, payload);\n } else {\n await api.post('/schedules', payload);\n }\n onSaved();\n } catch {\n // best-effort — modal stays open so the operator can retry.\n } finally {\n setSubmitting(false);\n }\n };\n\n return (\n <MobileModal\n open={open}\n onClose={onClose}\n title={initial ? `Edit ${initial.name}` : 'New schedule'}\n actions={\n <button\n type=\"submit\"\n form=\"mobile-schedule-form\"\n className=\"mobile-btn\"\n style={{ width: '100%' }}\n disabled={submitting}\n >\n <Plus size={14} /> {initial ? 'Save' : 'Create'}\n </button>\n }\n >\n <form id=\"mobile-schedule-form\" onSubmit={handleSubmit} className=\"mobile-task-form\">\n <label className=\"mobile-field-label\">Name *</label>\n <input\n className=\"mobile-input\"\n type=\"text\"\n placeholder=\"Weekly review\"\n value={state.name}\n onChange={(e) => update('name', e.target.value)}\n autoFocus\n required\n />\n\n <label className=\"mobile-field-label\">Type</label>\n <select\n className=\"mobile-input\"\n value={state.type}\n onChange={(e) => update('type', e.target.value as Schedule['type'])}\n >\n {TYPES.map((t) => <option key={t} value={t}>{t}</option>)}\n </select>\n\n {state.type === 'cron' && (\n <>\n <label className=\"mobile-field-label\">Day of week</label>\n <select\n className=\"mobile-input\"\n value={state.cronDow}\n onChange={(e) => update('cronDow', e.target.value)}\n >\n {DOWS.map((d) => <option key={d.value} value={d.value}>{d.label}</option>)}\n </select>\n\n <div className=\"mobile-form-row\">\n <div style={{ flex: 1 }}>\n <label className=\"mobile-field-label\">Hour</label>\n <select\n className=\"mobile-input\"\n value={String(state.cronHour)}\n onChange={(e) => update('cronHour', parseInt(e.target.value, 10))}\n >\n {HOURS.map((h) => <option key={h.value} value={String(h.value)}>{h.label}</option>)}\n </select>\n </div>\n <div style={{ flex: 1 }}>\n <label className=\"mobile-field-label\">Minute</label>\n <select\n className=\"mobile-input\"\n value={String(state.cronMinute)}\n onChange={(e) => update('cronMinute', parseInt(e.target.value, 10))}\n >\n {MINUTES.map((m) => <option key={m} value={String(m)}>{String(m).padStart(2, '0')}</option>)}\n </select>\n </div>\n </div>\n </>\n )}\n\n {state.type === 'interval' && (\n <div className=\"mobile-form-row\">\n <div style={{ flex: 1 }}>\n <label className=\"mobile-field-label\">Every</label>\n <input\n className=\"mobile-input\"\n type=\"number\"\n min={1}\n value={state.intervalN}\n onChange={(e) => update('intervalN', parseInt(e.target.value, 10) || 1)}\n />\n </div>\n <div style={{ flex: 1 }}>\n <label className=\"mobile-field-label\">Unit</label>\n <select\n className=\"mobile-input\"\n value={state.intervalUnit}\n onChange={(e) => update('intervalUnit', e.target.value)}\n >\n {INTERVAL_UNITS.map((u) => <option key={u.value} value={u.value}>{u.label}</option>)}\n </select>\n </div>\n </div>\n )}\n\n {state.type === 'once' && (\n <>\n <label className=\"mobile-field-label\">Run at</label>\n <input\n className=\"mobile-input\"\n type=\"datetime-local\"\n value={state.onceAt}\n onChange={(e) => update('onceAt', e.target.value)}\n />\n </>\n )}\n\n <label className=\"mobile-field-label\">Timezone</label>\n <select\n className=\"mobile-input\"\n value={TIMEZONES.includes(state.timezone) ? state.timezone : 'UTC'}\n onChange={(e) => update('timezone', e.target.value)}\n >\n {TIMEZONES.map((tz) => <option key={tz} value={tz}>{tz}</option>)}\n </select>\n\n <label className=\"mobile-field-label\">Action type</label>\n <select\n className=\"mobile-input\"\n value={state.actionType}\n onChange={(e) => update('actionType', e.target.value as Schedule['action']['type'])}\n >\n {ACTIONS.map((t) => <option key={t} value={t}>{t}</option>)}\n </select>\n\n <label className=\"mobile-field-label\">Target</label>\n <input\n className=\"mobile-input\"\n type=\"text\"\n placeholder={\n state.actionType === 'webhook'\n ? 'https://...'\n : state.actionType === 'agent'\n ? 'agent or task ref'\n : 'echo hello'\n }\n value={state.actionTarget}\n onChange={(e) => update('actionTarget', e.target.value)}\n />\n\n {state.actionType === 'agent' && (\n <>\n <label className=\"mobile-field-label\">Prompt</label>\n <textarea\n className=\"mobile-input\"\n rows={3}\n placeholder=\"What should the agent do?\"\n value={state.actionPrompt}\n onChange={(e) => update('actionPrompt', e.target.value)}\n />\n </>\n )}\n\n <fieldset className=\"mobile-budget-card\">\n <legend>Budget pre-flight</legend>\n <label className=\"mobile-checkbox-row\">\n <input\n type=\"checkbox\"\n checked={state.skipIfBudgetLow}\n onChange={(e) => update('skipIfBudgetLow', e.target.checked)}\n />\n <span>Skip if too many bg tasks are running</span>\n </label>\n <label className=\"mobile-field-label\">Cap</label>\n <input\n className=\"mobile-input\"\n type=\"number\"\n min={1}\n max={64}\n value={state.maxConcurrent}\n onChange={(e) => update('maxConcurrent', parseInt(e.target.value, 10) || 6)}\n disabled={!state.skipIfBudgetLow}\n />\n </fieldset>\n\n <label className=\"mobile-checkbox-row\">\n <input\n type=\"checkbox\"\n checked={state.enabled}\n onChange={(e) => update('enabled', e.target.checked)}\n />\n <span>Enabled</span>\n </label>\n </form>\n </MobileModal>\n );\n}\n\n/* ------------------------------------------------------------------ */\n/* Helpers */\n/* ------------------------------------------------------------------ */\n\nfunction formatRel(ts: string): string {\n const diff = Date.now() - new Date(ts).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(ts).toLocaleDateString();\n}\n\nfunction formatNextShort(iso: string, tz?: string | null): string {\n try {\n return new Date(iso).toLocaleString(undefined, {\n timeZone: tz || undefined,\n weekday: 'short',\n hour: 'numeric',\n minute: '2-digit',\n month: 'short',\n day: 'numeric',\n });\n } catch {\n return new Date(iso).toLocaleString();\n }\n}\n\nfunction parseSimpleCron(expr: string): { minute: number; hour: number; dow: string } | null {\n if (typeof expr !== 'string') return null;\n const parts = expr.trim().split(/\\s+/);\n if (parts.length !== 5) return null;\n const [m, h, , , dow] = parts;\n const minNum = parseInt(m, 10);\n const hourNum = parseInt(h, 10);\n if (!Number.isFinite(minNum) || !Number.isFinite(hourNum)) return null;\n if (m !== String(minNum) || h !== String(hourNum)) return null;\n return {\n minute: minNum,\n hour: hourNum,\n dow: dow === '*' ? '*' : (Number.isFinite(parseInt(dow, 10)) ? dow : '*'),\n };\n}\n\nfunction parseSimpleInterval(s: string): { n: number; unit: string } | null {\n if (typeof s !== 'string') return null;\n const m = /^(\\d+)([smhd])$/i.exec(s.trim());\n if (!m) return null;\n return { n: parseInt(m[1], 10), unit: m[2].toLowerCase() };\n}\n\nfunction toDatetimeLocal(iso: string): string {\n if (!iso) return '';\n try {\n const d = new Date(iso);\n if (Number.isNaN(d.getTime())) return '';\n const pad = (n: number) => String(n).padStart(2, '0');\n return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`;\n } catch {\n return '';\n }\n}\n\nfunction humanizeSchedule(s: Schedule): string {\n if (s.type !== 'cron') return s.schedule;\n const parsed = parseSimpleCron(s.schedule);\n if (!parsed) return s.schedule;\n const dow = DOWS.find((d) => d.value === parsed.dow)?.label || parsed.dow;\n const hourLabel = HOURS.find((h) => h.value === parsed.hour)?.label || `${parsed.hour}`;\n const minute = String(parsed.minute).padStart(2, '0');\n if (dow === 'Every day') return `Every day ${hourLabel} :${minute}`;\n return `Every ${dow} ${hourLabel} :${minute}`;\n}","// src/mobile/views/MobileSearchModal.tsx — fullscreen search modal for mobile.\nimport { useEffect, useRef, useState } from 'react';\nimport { Search, X, FileText, Bot, CheckSquare, Folder, Sliders } from 'lucide-react';\nimport { api } from '../../lib/api';\nimport type { SearchResult } from '../../lib/types';\nimport { MobileModal } from '../components/MobileModal';\n\ntype Props = {\n open: boolean;\n onClose: () => void;\n onNavigate: (type: string, id: string) => void;\n};\n\nconst SCOPES = ['all', 'tasks', 'agents', 'artifacts', 'projects', 'settings'] as const;\ntype Scope = typeof SCOPES[number];\n\nconst SCOPE_ICONS: Record<string, typeof FileText> = {\n tasks: CheckSquare,\n agents: Bot,\n plans: FileText,\n projects: Folder,\n settings: Sliders,\n};\n\nexport function MobileSearchModal({ open, onClose, onNavigate }: Props) {\n const [query, setQuery] = useState('');\n const [scope, setScope] = useState<Scope>('all');\n const [results, setResults] = useState<SearchResult[]>([]);\n const [loading, setLoading] = useState(false);\n const inputRef = useRef<HTMLInputElement>(null);\n\n useEffect(() => {\n if (open) {\n setQuery('');\n setResults([]);\n requestAnimationFrame(() => inputRef.current?.focus());\n }\n }, [open]);\n\n useEffect(() => {\n if (!query.trim()) {\n setResults([]);\n return;\n }\n const timer = setTimeout(async () => {\n setLoading(true);\n try {\n const data = await api.get<{ results: SearchResult[] }>(\n `/search?q=${encodeURIComponent(query)}&scope=${scope}`,\n );\n setResults(data.results || []);\n } catch {\n setResults([]);\n } finally {\n setLoading(false);\n }\n }, 250);\n return () => clearTimeout(timer);\n }, [query, scope]);\n\n const handleResultClick = (r: SearchResult) => {\n const item = r.item as Record<string, string>;\n const id = item.id || item.slug || item.name || '';\n onNavigate(r.type, id);\n onClose();\n };\n\n const grouped = results.reduce<Record<string, SearchResult[]>>((acc, r) => {\n if (!acc[r.type]) acc[r.type] = [];\n acc[r.type].push(r);\n return acc;\n }, {});\n\n return (\n <MobileModal open={open} onClose={onClose} title=\"Search\">\n <div className=\"mobile-search-container\">\n {/* Search input */}\n <div className=\"mobile-search-input-row\">\n <Search size={16} style={{ color: 'var(--text-muted)', flexShrink: 0 }} />\n <input\n ref={inputRef}\n className=\"mobile-search-field\"\n type=\"text\"\n placeholder=\"Search…\"\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n enterKeyHint=\"search\"\n onKeyDown={(e) => {\n if (e.key === 'Escape') onClose();\n if (e.key === 'Enter' && results.length > 0) {\n handleResultClick(results[0]);\n }\n }}\n />\n {query && (\n <button\n type=\"button\"\n className=\"mobile-icon-btn\"\n onClick={() => setQuery('')}\n aria-label=\"Clear\"\n >\n <X size={16} />\n </button>\n )}\n </div>\n\n {/* Scope chips */}\n <div className=\"mobile-search-scopes\">\n {SCOPES.map((s) => (\n <button\n key={s}\n type=\"button\"\n className={`mobile-scope-chip ${scope === s ? 'active' : ''}`}\n onClick={() => setScope(s)}\n >\n {s === 'all' ? 'All' : s.charAt(0).toUpperCase() + s.slice(1)}\n </button>\n ))}\n </div>\n\n {/* Results */}\n <div className=\"mobile-search-results\">\n {loading && <div className=\"mobile-search-loading\">Searching…</div>}\n\n {!loading && query && results.length === 0 && (\n <div className=\"mobile-empty\">\n <Search size={32} />\n <p>No results found</p>\n </div>\n )}\n\n {!loading && !query && (\n <div className=\"mobile-empty\">\n <p>Type to search across tasks, agents, plans, and more.</p>\n </div>\n )}\n\n {Object.entries(grouped).map(([type, items]) => {\n const Icon = SCOPE_ICONS[type] || FileText;\n return (\n <div key={type} className=\"mobile-search-group\">\n <h4 className=\"mobile-search-group-title\">\n <Icon size={12} /> {type.charAt(0).toUpperCase() + type.slice(1)} ({items.length})\n </h4>\n {items.map((r, i) => {\n const item = r.item as Record<string, string>;\n return (\n <button\n key={i}\n type=\"button\"\n className=\"mobile-search-result-item\"\n onClick={() => handleResultClick(r)}\n >\n <span className=\"mobile-search-result-title\">\n {item.title || item.name || item.slug || '(unnamed)'}\n </span>\n {item.description && (\n <span className=\"mobile-search-result-meta\">\n {item.description.slice(0, 80)}\n </span>\n )}\n </button>\n );\n })}\n </div>\n );\n })}\n </div>\n </div>\n </MobileModal>\n );\n}\n","// src/mobile/views/QrCodePanel.tsx — lazy-loaded QR code panel.\n// Split from MobileSettings to keep qrcode.react (~30 KB) out of the\n// mobile entry chunk; it is only loaded when the user navigates to\n// the Companion App section and taps \"Generate QR Code\".\nimport { useEffect, useState } from 'react';\nimport { RefreshCw } from 'lucide-react';\n\ntype PairSession = { token: string; qrPayload: string; publicUrl: string; expiresAt: number };\n\nfunction formatCountdown(ms: number): string {\n if (ms <= 0) return 'expired';\n const s = Math.floor(ms / 1000);\n const m = Math.floor(s / 60);\n const r = s % 60;\n return `${m}:${String(r).padStart(2, '0')}`;\n}\n\nexport function QrCodePanel({ pair, onStart }: {\n pair: PairSession;\n onStart: () => void;\n}) {\n const [QRCodeSVG, setQRCodeSVG] = useState<React.ComponentType<{ value: string; size: number; level: string }> | null>(null);\n const [remaining, setRemaining] = useState(() => pair.expiresAt - Date.now());\n const expired = remaining <= 0;\n\n useEffect(() => {\n import('qrcode.react').then((mod) => {\n setQRCodeSVG(mod.QRCodeSVG as React.ComponentType<{ value: string; size: number; level: string }>);\n });\n }, []);\n\n useEffect(() => {\n if (!pair) return;\n const tick = setInterval(() => {\n setRemaining(pair.expiresAt - Date.now());\n }, 1000);\n return () => clearInterval(tick);\n }, [pair]);\n\n if (!QRCodeSVG) {\n return (\n <div style={{ textAlign: 'center', padding: '16px' }}>\n <span style={{ color: 'var(--text-dim)', fontSize: 13 }}>Loading QR library…</span>\n </div>\n );\n }\n\n return (\n <div style={{ textAlign: 'center' }}>\n <div style={{ background: '#fff', padding: 12, borderRadius: 12, display: 'inline-block' }}>\n <QRCodeSVG value={pair.qrPayload} size={180} level=\"M\" />\n </div>\n <div style={{ marginTop: 8, fontSize: 12 }}>\n Expires in <strong>{formatCountdown(remaining)}</strong>\n </div>\n <div className=\"mono\" style={{ fontSize: 10, wordBreak: 'break-all', marginTop: 4 }}>{pair.publicUrl}</div>\n {expired && (\n <button\n type=\"button\"\n className=\"mobile-btn\"\n onClick={onStart}\n style={{ marginTop: 12, width: '100%' }}\n >\n <RefreshCw size={14} /> Generate new QR\n </button>\n )}\n </div>\n );\n}\n","// src/mobile/views/MobileSettings.tsx — mobile settings with full desktop parity.\nimport { useEffect, useState } from 'react';\nimport { Smartphone, Sun, Moon, Monitor, Save } from 'lucide-react';\nimport { api } from '../../lib/api';\nimport { applyTheme, applyThemeTokens, type Settings, type Snapshot, type ThemeName } from '../../lib/types';\nimport { QrCodePanel } from './QrCodePanel';\n\ntype Props = {\n settings: Settings;\n snapshot: Snapshot | null;\n onRefresh: () => Promise<void>;\n};\n\ntype PairSession = { token: string; qrPayload: string; publicUrl: string; expiresAt: number };\n\nconst THEMES: { id: ThemeName; label: string; Icon: typeof Sun }[] = [\n { id: 'dark', label: 'Dark', Icon: Moon },\n { id: 'light', label: 'Light', Icon: Sun },\n { id: 'system', label: 'System', Icon: Monitor },\n];\n\nconst PRESET_ACCENTS = [\n { name: 'Purple', accent: '#8b5cf6' },\n { name: 'Blue', accent: '#3b82f6' },\n { name: 'Green', accent: '#10b981' },\n { name: 'Orange', accent: '#f97316' },\n { name: 'Red', accent: '#ef4444' },\n { name: 'Pink', accent: '#ec4899' },\n { name: 'Cyan', accent: '#06b6d4' },\n { name: 'Mono', accent: '#6b7280' },\n];\n\nexport function MobileSettings({ settings: initial, snapshot, onRefresh }: Props) {\n const [settings, setSettings] = useState<Settings>(initial);\n const [dirty, setDirty] = useState(false);\n const [saving, setSaving] = useState(false);\n const [pair, setPair] = useState<PairSession | null>(null);\n const [pairing, setPairing] = useState(false);\n\n useEffect(() => {\n setSettings(initial);\n setDirty(false);\n if (initial.theme) applyThemeTokens(initial.theme);\n }, [initial]);\n\n const patchTheme = (patch: Partial<Settings['theme']>) => {\n setSettings((cur) => {\n const next = { ...cur, theme: { ...cur.theme, ...patch } };\n applyTheme(next.theme);\n applyThemeTokens(next.theme);\n return next;\n });\n setDirty(true);\n };\n\n const patchTop = <K extends keyof Settings>(key: K, value: Settings[K]) => {\n setSettings((cur) => ({ ...cur, [key]: value }));\n setDirty(true);\n };\n\n const onSave = async () => {\n setSaving(true);\n try {\n const r = await api.put<{ data: Settings }>('/settings', settings);\n setSettings(r.data);\n setDirty(false);\n applyTheme(r.data.theme);\n applyThemeTokens(r.data.theme);\n onRefresh();\n } catch {\n // best-effort\n } finally {\n setSaving(false);\n }\n };\n\n const startPair = async () => {\n setPairing(true);\n try {\n const res = await api.post<PairSession>('/pair/start');\n setPair(res);\n } catch {\n // best-effort\n } finally {\n setPairing(false);\n }\n };\n\n // remaining/expired are now managed inside QrCodePanel to keep\n // qrcode.react out of the main mobile bundle chunk.\n\n return (\n <div className=\"mobile-view\">\n {/* Save bar */}\n {dirty && (\n <div className=\"mobile-settings-save-bar\">\n <span>Unsaved changes</span>\n <button type=\"button\" className=\"mobile-btn\" disabled={saving} onClick={onSave}>\n <Save size={14} /> {saving ? 'Saving…' : 'Save'}\n </button>\n </div>\n )}\n\n {/* Theme */}\n <section className=\"mobile-section\">\n <h3 className=\"mobile-section-title\">Appearance</h3>\n <div className=\"mobile-card\">\n {/* Theme mode */}\n <div className=\"mobile-setting-row\">\n <span className=\"mobile-setting-label\">Theme</span>\n <div className=\"mobile-theme-btns\">\n {THEMES.map(({ id, label, Icon }) => (\n <button\n key={id}\n type=\"button\"\n className={`mobile-theme-btn ${settings.theme.mode === id ? 'active' : ''}`}\n onClick={() => patchTheme({ mode: id })}\n >\n <Icon size={14} /> {label}\n </button>\n ))}\n </div>\n </div>\n\n {/* Accent swatches */}\n <div className=\"mobile-setting-row\" style={{ flexDirection: 'column', alignItems: 'flex-start', gap: 8 }}>\n <span className=\"mobile-setting-label\">Accent color</span>\n <div className=\"mobile-accent-swatches\">\n {PRESET_ACCENTS.map((p) => (\n <button\n key={p.name}\n type=\"button\"\n className={`mobile-accent-swatch ${settings.theme.accent === p.accent ? 'active' : ''}`}\n style={{ background: p.accent }}\n onClick={() => patchTheme({ accent: p.accent })}\n title={p.name}\n />\n ))}\n </div>\n </div>\n\n {/* Font size slider */}\n <div className=\"mobile-setting-row\" style={{ flexDirection: 'column', alignItems: 'flex-start', gap: 8 }}>\n <span className=\"mobile-setting-label\">Font size: {settings.theme.fontSize}px</span>\n <input\n type=\"range\"\n min={12}\n max={20}\n value={settings.theme.fontSize}\n onChange={(e) => patchTheme({ fontSize: Number(e.target.value) })}\n style={{ width: '100%' }}\n />\n </div>\n\n {/* Toggles */}\n <div className=\"mobile-setting-row\">\n <span className=\"mobile-setting-label\">Compact mode</span>\n <label className=\"mobile-toggle\">\n <input\n type=\"checkbox\"\n checked={settings.theme.compactMode}\n onChange={(e) => patchTheme({ compactMode: e.target.checked })}\n />\n <span className=\"mobile-toggle-slider\" />\n </label>\n </div>\n <div className=\"mobile-setting-row\">\n <span className=\"mobile-setting-label\">Animations</span>\n <label className=\"mobile-toggle\">\n <input\n type=\"checkbox\"\n checked={settings.theme.animations}\n onChange={(e) => patchTheme({ animations: e.target.checked })}\n />\n <span className=\"mobile-toggle-slider\" />\n </label>\n </div>\n </div>\n </section>\n\n {/* Chat defaults */}\n <section className=\"mobile-section\">\n <h3 className=\"mobile-section-title\">Chat Defaults</h3>\n <div className=\"mobile-card\">\n <label className=\"mobile-field-label\">Default Agent</label>\n <select\n className=\"mobile-input\"\n value={settings.defaultAgent || 'odin'}\n onChange={(e) => patchTop('defaultAgent', e.target.value)}\n >\n {(snapshot?.agents || []).map((a) => (\n <option key={a.name} value={a.name}>@{a.name}</option>\n ))}\n </select>\n <label className=\"mobile-field-label\" style={{ marginTop: 8 }}>Default Model</label>\n <input\n className=\"mobile-input\"\n type=\"text\"\n placeholder=\"(auto)\"\n value={settings.defaultModel || ''}\n onChange={(e) => patchTop('defaultModel', e.target.value)}\n />\n </div>\n </section>\n\n {/* Notifications */}\n <section className=\"mobile-section\">\n <h3 className=\"mobile-section-title\">Notifications</h3>\n <div className=\"mobile-card\">\n <div className=\"mobile-setting-row\">\n <span className=\"mobile-setting-label\">Agent completion</span>\n <label className=\"mobile-toggle\">\n <input\n type=\"checkbox\"\n checked={!!settings.notifications.onAgentComplete}\n onChange={(e) => {\n setSettings((cur) => ({\n ...cur,\n notifications: { ...cur.notifications, onAgentComplete: e.target.checked },\n }));\n setDirty(true);\n }}\n />\n <span className=\"mobile-toggle-slider\" />\n </label>\n </div>\n <div className=\"mobile-setting-row\">\n <span className=\"mobile-setting-label\">Plan approval</span>\n <label className=\"mobile-toggle\">\n <input\n type=\"checkbox\"\n checked={!!settings.notifications.onPlanApproval}\n onChange={(e) => {\n setSettings((cur) => ({\n ...cur,\n notifications: { ...cur.notifications, onPlanApproval: e.target.checked },\n }));\n setDirty(true);\n }}\n />\n <span className=\"mobile-toggle-slider\" />\n </label>\n </div>\n </div>\n </section>\n\n {/* Agent behavior */}\n <section className=\"mobile-section\">\n <h3 className=\"mobile-section-title\">Agent Behavior</h3>\n <div className=\"mobile-card\">\n <label className=\"mobile-field-label\">Max parallel agents</label>\n <input\n className=\"mobile-input\"\n type=\"number\"\n inputMode=\"numeric\"\n min={1}\n max={20}\n value={settings.agents?.maxParallel ?? 6}\n onChange={(e) => {\n setSettings((cur) => ({\n ...cur,\n agents: { ...cur.agents, maxParallel: Math.max(1, Math.min(20, parseInt(e.target.value, 10) || 6)) },\n }));\n setDirty(true);\n }}\n />\n <label className=\"mobile-field-label\" style={{ marginTop: 8 }}>Stuck threshold (ms)</label>\n <input\n className=\"mobile-input\"\n type=\"number\"\n inputMode=\"numeric\"\n min={60000}\n max={3600000}\n step={60000}\n value={settings.agents?.stuckThresholdMs ?? 600000}\n onChange={(e) => {\n setSettings((cur) => ({\n ...cur,\n agents: { ...cur.agents, stuckThresholdMs: Math.max(60000, parseInt(e.target.value, 10) || 600000) },\n }));\n setDirty(true);\n }}\n />\n <div className=\"mobile-setting-row\" style={{ marginTop: 8 }}>\n <span className=\"mobile-setting-label\">Auto-restart stuck agents</span>\n <label className=\"mobile-toggle\">\n <input\n type=\"checkbox\"\n checked={!!settings.agents?.autoRestart}\n onChange={(e) => {\n setSettings((cur) => ({\n ...cur,\n agents: { ...cur.agents, autoRestart: e.target.checked },\n }));\n setDirty(true);\n }}\n />\n <span className=\"mobile-toggle-slider\" />\n </label>\n </div>\n </div>\n </section>\n\n {/* Tailscale serve */}\n <section className=\"mobile-section\">\n <h3 className=\"mobile-section-title\">Tailscale Serve</h3>\n <div className=\"mobile-card\">\n <p className=\"muted\" style={{ fontSize: 13 }}>\n Use <code>bizar service</code> commands in the terminal to configure Tailscale serve.\n </p>\n </div>\n </section>\n\n {/* Companion App */}\n <section className=\"mobile-section\">\n <h3 className=\"mobile-section-title\"><Smartphone size={14} /> Companion App</h3>\n <div className=\"mobile-card\">\n {!pair && (\n <button type=\"button\" className=\"mobile-btn\" onClick={startPair} disabled={pairing} style={{ width: '100%' }}>\n <Smartphone size={14} /> {pairing ? 'Generating…' : 'Generate QR Code'}\n </button>\n )}\n {pair && (\n <QrCodePanel\n pair={pair}\n onStart={startPair}\n />\n )}\n </div>\n </section>\n\n {/* About */}\n <section className=\"mobile-section\">\n <h3 className=\"mobile-section-title\">About</h3>\n <div className=\"mobile-card\">\n <div className=\"mobile-setting-row\">\n <span className=\"mobile-setting-label\">Version</span>\n <span className=\"mobile-setting-value mono\">{settings.about?.version || '—'}</span>\n </div>\n <div className=\"mobile-setting-row\">\n <span className=\"mobile-setting-label\">Agents</span>\n <span className=\"mobile-setting-value\">{snapshot?.agents?.length || 0}</span>\n </div>\n <div className=\"mobile-setting-row\">\n <span className=\"mobile-setting-label\">Tasks</span>\n <span className=\"mobile-setting-value\">{snapshot?.tasks?.length || 0}</span>\n </div>\n <div className=\"mobile-setting-row\">\n <span className=\"mobile-setting-label\">Plans</span>\n <span className=\"mobile-setting-value\">{snapshot?.artifacts.length || 0}</span>\n </div>\n </div>\n </section>\n\n {/* Switch to desktop */}\n <div className=\"mobile-view-footer\">\n <a href=\"/?desktop=1\" className=\"mobile-btn mobile-btn-secondary\">\n Switch to Desktop\n </a>\n </div>\n </div>\n );\n}\n","// src/mobile/views/MobileSkills.tsx — skills list with detail sheet.\nimport { useEffect, useState } from 'react';\nimport { Sparkles, RefreshCw, Power } from 'lucide-react';\nimport { api } from '../../lib/api';\nimport type { Snapshot } from '../../lib/types';\nimport { MobileBottomSheet } from '../components/MobileBottomSheet';\n\ntype Skill = {\n id: string;\n name: string;\n description: string;\n category: string;\n tags: string[];\n version: string;\n source: string;\n enabled?: boolean;\n};\n\ntype Props = {\n snapshot: Snapshot;\n onBack: () => void;\n};\n\nconst CATEGORIES = ['languages', 'framework', 'design', 'testing', 'devops', 'data', 'security', 'integration'];\n\nexport function MobileSkills({ snapshot, onBack }: Props) {\n const [skills, setSkills] = useState<Skill[]>([]);\n const [loading, setLoading] = useState(true);\n const [filter, setFilter] = useState('');\n const [categoryFilter, setCategoryFilter] = useState('');\n const [detailSkill, setDetailSkill] = useState<Skill | null>(null);\n\n const load = async () => {\n try {\n const data = await api.get<{ skills: Skill[] }>('/skills');\n setSkills(data.skills || []);\n } catch {\n // best-effort\n } finally {\n setLoading(false);\n }\n };\n\n useEffect(() => { load(); }, []);\n\n const toggleSkill = async (id: string, enabled: boolean) => {\n try {\n await api.patch(`/skills/${encodeURIComponent(id)}`, { enabled });\n setSkills((cur) => cur.map((s) => (s.id === id ? { ...s, enabled } : s)));\n if (detailSkill?.id === id) setDetailSkill((s) => s ? { ...s, enabled } : s);\n } catch {\n // best-effort\n }\n };\n\n const filtered = skills.filter((s) => {\n if (categoryFilter && s.category !== categoryFilter) return false;\n if (filter) {\n const q = filter.toLowerCase();\n return s.name.toLowerCase().includes(q) || s.description.toLowerCase().includes(q);\n }\n return true;\n });\n\n return (\n <div className=\"mobile-view\">\n <div className=\"mobile-tasks-toolbar\">\n <input\n className=\"mobile-search-input\"\n type=\"text\"\n placeholder=\"Search skills…\"\n value={filter}\n onChange={(e) => setFilter(e.target.value)}\n style={{ flex: 1 }}\n />\n <button type=\"button\" className=\"mobile-icon-btn\" onClick={() => load()} aria-label=\"Refresh\">\n <RefreshCw size={16} />\n </button>\n </div>\n\n <div className=\"mobile-search-scopes\">\n <button type=\"button\" className={`mobile-scope-chip ${!categoryFilter ? 'active' : ''}`} onClick={() => setCategoryFilter('')}>All</button>\n {CATEGORIES.map((c) => (\n <button key={c} type=\"button\" className={`mobile-scope-chip ${categoryFilter === c ? 'active' : ''}`} onClick={() => setCategoryFilter(c)}>\n {c}\n </button>\n ))}\n </div>\n\n {loading ? (\n <div className=\"mobile-loading\"><p>Loading…</p></div>\n ) : filtered.length === 0 ? (\n <div className=\"mobile-empty\">\n <Sparkles size={40} />\n <p>No skills found.</p>\n </div>\n ) : (\n <div className=\"mobile-card-list\">\n {filtered.map((s) => (\n <button\n key={s.id}\n type=\"button\"\n className=\"mobile-list-item mobile-list-item-interactive\"\n onClick={() => setDetailSkill(s)}\n >\n <div className=\"mobile-list-icon\"><Sparkles size={16} /></div>\n <div className=\"mobile-list-content\">\n <span className=\"mobile-list-title\">{s.name}</span>\n <span className=\"mobile-list-meta\">{s.category} · v{s.version}</span>\n </div>\n <span className={`mobile-list-badge ${s.enabled ? 'badge-on' : 'badge-off'}`}>\n {s.enabled ? 'on' : 'off'}\n </span>\n </button>\n ))}\n </div>\n )}\n\n {detailSkill && (\n <MobileBottomSheet\n open={true}\n onClose={() => setDetailSkill(null)}\n title={detailSkill.name}\n actions={\n <button\n type=\"button\"\n className={`mobile-btn ${detailSkill.enabled ? 'mobile-btn-secondary' : ''}`}\n style={{ width: '100%' }}\n onClick={() => { if (detailSkill) toggleSkill(detailSkill.id, !detailSkill.enabled); }}\n >\n <Power size={14} /> {detailSkill.enabled ? 'Disable' : 'Enable'}\n </button>\n }\n >\n <div className=\"mobile-agent-detail\">\n <p className=\"mobile-agent-detail-desc\">{detailSkill.description || 'No description.'}</p>\n <div className=\"mobile-agent-detail-meta\">\n <div className=\"mobile-task-detail-row\"><span>Category</span><span>{detailSkill.category}</span></div>\n <div className=\"mobile-task-detail-row\"><span>Version</span><span className=\"mono\">{detailSkill.version}</span></div>\n <div className=\"mobile-task-detail-row\"><span>Source</span><span>{detailSkill.source}</span></div>\n {detailSkill.tags.length > 0 && (\n <div className=\"mobile-task-detail-row\">\n <span>Tags</span>\n <span>{detailSkill.tags.join(', ')}</span>\n </div>\n )}\n </div>\n </div>\n </MobileBottomSheet>\n )}\n </div>\n );\n}\n","// src/mobile/views/MobileTasks.tsx — kanban-style task board with task detail.\nimport { useEffect, useState } from 'react';\nimport { RefreshCw, Plus, Trash2, Archive, MessageSquare, FileText } from 'lucide-react';\nimport { api } from '../../lib/api';\nimport { formatRelative, priorityColors } from '../../lib/utils';\nimport type { Snapshot, Task } from '../../lib/types';\nimport { MobileBottomSheet } from '../components/MobileBottomSheet';\nimport { MobileModal } from '../components/MobileModal';\n\ntype Props = {\n snapshot: Snapshot;\n onRefresh: () => Promise<void>;\n selectedTaskId?: string;\n onCloseDetail?: () => void;\n // v3.6.2 — Pipeline: callbacks to open chat or artifact for a task.\n onOpenChat?: (taskId: string) => void;\n onOpenArtifact?: (artifactId: string) => void;\n};\n\nconst STATUS_ORDER = ['queued', 'doing', 'blocked', 'done'];\nconst STATUS_LABELS: Record<string, string> = {\n queued: 'Queued',\n doing: 'Doing',\n blocked: 'Blocked',\n done: 'Done',\n};\nconst NEXT_STATUS: Record<string, string> = {\n queued: 'doing',\n doing: 'done',\n blocked: 'queued',\n done: 'queued',\n};\n\ntype TaskChatMeta = { sessionId?: string; bgInstanceId?: string };\ntype TaskArtifactMeta = { artifactIds?: string[]; artifactId?: string };\n\nfunction hasTaskChat(task: Task): boolean {\n const metadata = (task.metadata ?? {}) as TaskChatMeta;\n return Boolean(metadata.sessionId || metadata.bgInstanceId);\n}\n\nfunction hasTaskArtifacts(task: Task): boolean {\n const metadata = (task.metadata ?? {}) as TaskArtifactMeta;\n return Boolean(metadata.artifactIds?.length || metadata.artifactId);\n}\n\nexport function MobileTasks({ snapshot, onRefresh, selectedTaskId, onCloseDetail, onOpenChat, onOpenArtifact }: Props) {\n const [tasks, setTasks] = useState<Task[]>(snapshot.tasks || []);\n const [loading, setLoading] = useState(!snapshot.tasks);\n const [filter, setFilter] = useState('');\n const [activeStatus, setActiveStatus] = useState<string>('queued');\n const [detailTask, setDetailTask] = useState<Task | null>(null);\n const [newTaskOpen, setNewTaskOpen] = useState(false);\n // v3.6.2 — state for the mobile artifact viewer sheet.\n const [artifactTaskId, setArtifactTaskId] = useState<string | null>(null);\n const [artifactIds, setArtifactIds] = useState<string[]>([]);\n const [artifactContent, setArtifactContent] = useState<string>('');\n const [artifactMeta, setArtifactMeta] = useState<{ name?: string; contentType?: string; size?: number } | null>(null);\n const [artifactLoading, setArtifactLoading] = useState(false);\n const [assigneeFilter, setAssigneeFilter] = useState('');\n const [sortBy, setSortBy] = useState<'updated' | 'created' | 'priority'>('updated');\n\n useEffect(() => {\n if (snapshot.tasks) {\n setTasks(snapshot.tasks);\n setLoading(false);\n }\n if (selectedTaskId) {\n const t = snapshot.tasks?.find((t) => t.id === selectedTaskId);\n if (t) setDetailTask(t);\n }\n }, [snapshot.tasks, selectedTaskId]);\n\n useEffect(() => {\n if (selectedTaskId) {\n const t = tasks.find((t) => t.id === selectedTaskId);\n if (t) setDetailTask(t);\n }\n }, [selectedTaskId, tasks]);\n\n const reload = async () => {\n try {\n const data = await api.get<Task[]>('/tasks');\n setTasks(Array.isArray(data) ? data : []);\n } catch {\n // best-effort\n } finally {\n setLoading(false);\n }\n };\n\n const moveTask = async (taskId: string, newStatus: string) => {\n setTasks((cur) =>\n cur.map((t) => (t.id === taskId ? { ...t, status: newStatus } : t)),\n );\n if (detailTask?.id === taskId) {\n setDetailTask((t) => t ? { ...t, status: newStatus } : t);\n }\n try {\n await api.patch(`/tasks/${encodeURIComponent(taskId)}/status`, { status: newStatus });\n } catch {\n await reload();\n }\n };\n\n const deleteTask = async (taskId: string) => {\n if (!confirm('Delete this task?')) return;\n try {\n await api.del(`/tasks/${encodeURIComponent(taskId)}`);\n setTasks((cur) => cur.filter((t) => t.id !== taskId));\n if (detailTask?.id === taskId) setDetailTask(null);\n } catch {\n await reload();\n }\n };\n\n const archiveTask = async (taskId: string) => {\n try {\n await api.post(`/tasks/${encodeURIComponent(taskId)}/archive`);\n setTasks((cur) => cur.filter((t) => t.id !== taskId));\n if (detailTask?.id === taskId) setDetailTask(null);\n } catch {\n // best-effort\n }\n };\n\n const filtered = tasks.filter((t) => {\n if (filter.trim()) {\n const q = filter.toLowerCase();\n if (!(t.title || '').toLowerCase().includes(q) && !(t.description || '').toLowerCase().includes(q)) {\n return false;\n }\n }\n if (assigneeFilter.trim()) {\n const targetAssignee = assigneeFilter === 'mine'\n ? snapshot.settings?.data?.defaultAgent || settingsDefaultAgent(snapshot)\n : assigneeFilter.replace(/^@/, '');\n if ((t.assignee || '').toLowerCase() !== targetAssignee.toLowerCase()) return false;\n }\n return true;\n });\n\n const sorted = [...filtered].sort((a, b) => {\n switch (sortBy) {\n case 'priority': {\n const order = { high: 0, normal: 1, low: 2 };\n return (order[a.priority as keyof typeof order] ?? 1) - (order[b.priority as keyof typeof order] ?? 1);\n }\n case 'created': return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();\n case 'updated':\n default:\n return new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime();\n }\n });\n\n const activeTasks = sorted.filter((t) => t.status === activeStatus);\n const agents = snapshot.agents || [];\n\n const createTask = async (title: string, description: string, assignee: string, priority: string) => {\n try {\n const task = await api.post<Task>('/tasks', { title, description, assignee: assignee || null, priority });\n setTasks((cur) => [task, ...cur]);\n setNewTaskOpen(false);\n } catch {\n // best-effort\n }\n };\n\n // v3.6.2 — Open the artifact viewer for a task's artifact.\n const openTaskArtifact = async (taskId: string) => {\n setArtifactLoading(true);\n setArtifactContent('');\n setArtifactMeta(null);\n setArtifactTaskId(taskId);\n try {\n const artR = await api.get<{ artifacts: { id: string; name?: string; contentType?: string; size?: number }[] }>(\n `/tasks/${encodeURIComponent(taskId)}/artifacts`,\n );\n const ids = (artR.artifacts || []).map((a) => a.id);\n setArtifactIds(ids);\n if (ids.length > 0) {\n const [meta, content] = await Promise.all([\n api.get<{ name?: string; contentType?: string; size?: number }>(`/artifacts/${encodeURIComponent(ids[0])}`),\n fetch(api.urlWithToken(`/artifacts/${encodeURIComponent(ids[0])}/content`)).then((r) => r.text()),\n ]);\n setArtifactMeta(meta);\n setArtifactContent(content);\n }\n } catch {\n // best-effort\n } finally {\n setArtifactLoading(false);\n }\n };\n\n if (loading) {\n return <div className=\"mobile-loading\"><p>Loading…</p></div>;\n }\n\n return (\n <div className=\"mobile-view mobile-view-tasks\">\n {/* Toolbar */}\n <div className=\"mobile-tasks-toolbar\">\n <input\n className=\"mobile-search-input\"\n type=\"text\"\n placeholder=\"Search tasks…\"\n value={filter}\n onChange={(e) => setFilter(e.target.value)}\n style={{ flex: 1 }}\n />\n <button type=\"button\" className=\"mobile-icon-btn\" onClick={() => reload()} aria-label=\"Refresh\">\n <RefreshCw size={16} />\n </button>\n </div>\n\n {/* Filter + Sort row */}\n <div className=\"mobile-tasks-filters\">\n <select\n className=\"mobile-filter-select\"\n value={assigneeFilter}\n onChange={(e) => setAssigneeFilter(e.target.value)}\n >\n <option value=\"\">All</option>\n <option value=\"mine\">Mine</option>\n {agents.map((a) => (\n <option key={a.name} value={`@${a.name}`}>@{a.name}</option>\n ))}\n </select>\n <select\n className=\"mobile-filter-select\"\n value={sortBy}\n onChange={(e) => setSortBy(e.target.value as 'updated' | 'created' | 'priority')}\n >\n <option value=\"updated\">Updated</option>\n <option value=\"created\">Created</option>\n <option value=\"priority\">Priority</option>\n </select>\n </div>\n\n {/* Kanban segments */}\n <div className=\"mobile-kanban-tabs\">\n {STATUS_ORDER.map((s) => {\n const count = sorted.filter((t) => t.status === s).length;\n return (\n <button\n key={s}\n type=\"button\"\n className={`mobile-kanban-tab ${activeStatus === s ? 'active' : ''}`}\n onClick={() => setActiveStatus(s)}\n >\n {STATUS_LABELS[s]} ({count})\n </button>\n );\n })}\n </div>\n\n {/* Task list */}\n <div className=\"mobile-card-list\">\n {activeTasks.length === 0 && (\n <div className=\"mobile-empty mobile-empty-compact\">\n <p>No {STATUS_LABELS[activeStatus].toLowerCase()} tasks.</p>\n <p className=\"muted\">Create one or switch columns.</p>\n </div>\n )}\n {activeTasks.map((t) => (\n <button\n key={t.id}\n type=\"button\"\n className=\"mobile-task-card\"\n onClick={() => setDetailTask(t)}\n >\n <div\n className=\"priority-dot\"\n style={{ background: priorityColors[t.priority] || 'var(--info)' }}\n />\n <div className=\"task-content\">\n <div className=\"task-title\">{t.title}</div>\n <div className=\"task-meta\">\n {t.assignee ? `@${t.assignee}` : 'unassigned'}\n {' · '}\n {formatRelative(t.updatedAt || t.createdAt)}\n {t.dependencies?.length ? ` · ${t.dependencies.length} deps` : ''}\n </div>\n </div>\n </button>\n ))}\n </div>\n\n {/* FAB */}\n <button\n type=\"button\"\n className=\"mobile-fab\"\n onClick={() => setNewTaskOpen(true)}\n aria-label=\"Add task\"\n >\n <Plus size={24} />\n </button>\n\n {/* Task Detail Sheet */}\n <MobileBottomSheet\n open={!!detailTask}\n onClose={() => { setDetailTask(null); onCloseDetail?.(); }}\n title=\"Task Detail\"\n actions={\n detailTask && (\n <div className=\"mobile-task-detail-actions\">\n {detailTask.status !== 'done' && (\n <button\n type=\"button\"\n className=\"mobile-btn\"\n style={{ flex: 1 }}\n onClick={() => {\n if (detailTask) moveTask(detailTask.id, NEXT_STATUS[detailTask.status]);\n }}\n >\n Move to {STATUS_LABELS[NEXT_STATUS[detailTask.status] || 'queued']}\n </button>\n )}\n <button\n type=\"button\"\n className=\"mobile-btn mobile-btn-secondary\"\n onClick={() => {\n if (detailTask) archiveTask(detailTask.id);\n }}\n >\n <Archive size={14} /> Archive\n </button>\n {/* v3.6.2 — Pipeline: Chat button */}\n {hasTaskChat(detailTask) && (\n <button\n type=\"button\"\n className=\"mobile-btn mobile-btn-secondary\"\n onClick={() => { onOpenChat?.(detailTask.id); }}\n >\n <MessageSquare size={14} /> Chat\n </button>\n )}\n {/* v3.6.2 — Pipeline: Artifact button */}\n {hasTaskArtifacts(detailTask) && (\n <button\n type=\"button\"\n className=\"mobile-btn mobile-btn-secondary\"\n onClick={() => { openTaskArtifact(detailTask.id); }}\n >\n <FileText size={14} /> Artifact\n </button>\n )}\n <button\n type=\"button\"\n className=\"mobile-btn mobile-btn-danger\"\n onClick={() => {\n if (detailTask) deleteTask(detailTask.id);\n }}\n >\n <Trash2 size={14} />\n </button>\n </div>\n )\n }\n >\n {detailTask && (\n <div className=\"mobile-task-detail\">\n <div className=\"mobile-task-detail-header\">\n <span className={`mobile-task-status-badge status-${detailTask.status}`}>\n {STATUS_LABELS[detailTask.status] || detailTask.status}\n </span>\n <span className={`mobile-task-priority priority-${detailTask.priority}`}>\n {detailTask.priority}\n </span>\n </div>\n <h3 className=\"mobile-task-detail-title\">{detailTask.title}</h3>\n {detailTask.description && (\n <p className=\"mobile-task-detail-desc\">{detailTask.description}</p>\n )}\n <div className=\"mobile-task-detail-meta\">\n {detailTask.assignee && (\n <div className=\"mobile-task-detail-row\">\n <span>Assignee</span>\n <span>@{detailTask.assignee}</span>\n </div>\n )}\n {detailTask.dueDate && (\n <div className=\"mobile-task-detail-row\">\n <span>Due</span>\n <span>{new Date(detailTask.dueDate).toLocaleDateString()}</span>\n </div>\n )}\n {detailTask.timeSpent != null && (\n <div className=\"mobile-task-detail-row\">\n <span>Time spent</span>\n <span>{Math.round(detailTask.timeSpent / 60000)}min</span>\n </div>\n )}\n {detailTask.dependencies?.length ? (\n <div className=\"mobile-task-detail-row\">\n <span>Dependencies</span>\n <span>{detailTask.dependencies.length}</span>\n </div>\n ) : null}\n {detailTask.comments?.length ? (\n <div className=\"mobile-task-detail-row\">\n <span>Comments</span>\n <span>{detailTask.comments.length}</span>\n </div>\n ) : null}\n <div className=\"mobile-task-detail-row\">\n <span>Created</span>\n <span>{formatRelative(detailTask.createdAt)}</span>\n </div>\n <div className=\"mobile-task-detail-row\">\n <span>Updated</span>\n <span>{formatRelative(detailTask.updatedAt)}</span>\n </div>\n </div>\n </div>\n )}\n </MobileBottomSheet>\n\n {/* New Task Modal */}\n <NewTaskModal\n open={newTaskOpen}\n onClose={() => setNewTaskOpen(false)}\n onCreate={createTask}\n agents={agents}\n />\n\n {/* v3.6.2 — Mobile artifact viewer sheet */}\n <MobileBottomSheet\n open={!!artifactTaskId}\n onClose={() => { setArtifactTaskId(null); setArtifactContent(''); setArtifactMeta(null); }}\n title={artifactMeta?.name || 'Artifact'}\n actions={\n artifactIds.length > 1 ? (\n <div className=\"mobile-task-detail-actions\">\n {artifactIds.map((id, i) => (\n <button\n key={id}\n type=\"button\"\n className=\"mobile-btn mobile-btn-secondary\"\n style={{ flex: 1 }}\n onClick={() => {\n // Reload for the selected artifact\n setArtifactLoading(true);\n Promise.all([\n api.get<{ name?: string; contentType?: string; size?: number }>(`/artifacts/${encodeURIComponent(id)}`),\n fetch(api.urlWithToken(`/artifacts/${encodeURIComponent(id)}/content`)).then((r) => r.text()),\n ]).then(([meta, content]) => {\n setArtifactMeta(meta);\n setArtifactContent(content);\n setArtifactLoading(false);\n }).catch(() => setArtifactLoading(false));\n }}\n >\n Artifact {i + 1}\n </button>\n ))}\n </div>\n ) : null\n }\n >\n <div className=\"mobile-artifact-viewer\">\n {artifactLoading ? (\n <div className=\"mobile-loading\"><p>Loading artifact…</p></div>\n ) : artifactContent ? (\n <iframe\n srcDoc={artifactContent}\n className=\"mobile-artifact-iframe\"\n sandbox=\"allow-scripts allow-forms allow-popups allow-same-origin\"\n title={artifactMeta?.name || 'Artifact'}\n loading=\"lazy\"\n />\n ) : (\n <div className=\"mobile-empty\">\n <FileText size={32} />\n <p className=\"muted\">No artifact content available.</p>\n </div>\n )}\n </div>\n </MobileBottomSheet>\n </div>\n );\n}\n\nfunction settingsDefaultAgent(snapshot: Snapshot): string {\n return snapshot.settings?.data?.defaultAgent || 'odin';\n}\n\nfunction NewTaskModal({\n open,\n onClose,\n onCreate,\n agents,\n}: {\n open: boolean;\n onClose: () => void;\n onCreate: (title: string, description: string, assignee: string, priority: string) => void;\n agents: Snapshot['agents'];\n}) {\n const [title, setTitle] = useState('');\n const [description, setDescription] = useState('');\n const [assignee, setAssignee] = useState('');\n const [priority, setPriority] = useState('normal');\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n if (!title.trim()) return;\n onCreate(title.trim(), description.trim(), assignee, priority);\n setTitle('');\n setDescription('');\n setAssignee('');\n setPriority('normal');\n };\n\n return (\n <MobileModal open={open} onClose={onClose} title=\"New Task\" actions={\n <button type=\"submit\" form=\"new-task-form\" className=\"mobile-btn\" style={{ width: '100%' }}>\n <Plus size={14} /> Create Task\n </button>\n }>\n <form id=\"new-task-form\" onSubmit={handleSubmit} className=\"mobile-task-form\">\n <label className=\"mobile-field-label\">Title *</label>\n <input\n className=\"mobile-input\"\n type=\"text\"\n placeholder=\"Task title\"\n value={title}\n onChange={(e) => setTitle(e.target.value)}\n autoFocus\n required\n />\n <label className=\"mobile-field-label\">Description</label>\n <textarea\n className=\"mobile-input\"\n rows={3}\n placeholder=\"Optional description\"\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n />\n <label className=\"mobile-field-label\">Assignee</label>\n <select\n className=\"mobile-input\"\n value={assignee}\n onChange={(e) => setAssignee(e.target.value)}\n >\n <option value=\"\">Unassigned</option>\n {agents.map((a) => (\n <option key={a.name} value={a.name}>@{a.name}</option>\n ))}\n </select>\n <label className=\"mobile-field-label\">Priority</label>\n <select\n className=\"mobile-input\"\n value={priority}\n onChange={(e) => setPriority(e.target.value)}\n >\n <option value=\"low\">Low</option>\n <option value=\"normal\">Normal</option>\n <option value=\"high\">High</option>\n </select>\n </form>\n </MobileModal>\n );\n}\n","// src/MobileApp.tsx — mobile root with state-based routing + stack navigation.\nimport { useCallback, useEffect, useMemo, useState } from 'react';\nimport {\n Activity,\n CheckSquare,\n Grid,\n MessageSquare,\n Settings as SettingsIcon,\n} from 'lucide-react';\nimport { Spinner } from './components/Spinner';\nimport { api } from './lib/api';\nimport type { Notification, Settings, Snapshot, WsMessage } from './lib/types';\nimport { applyTheme, applyThemeTokens } from './lib/types';\nimport { Ws } from './lib/ws';\nimport { MobileBottomNav, type MobileTab } from './mobile/MobileBottomNav';\nimport { MobileTopbar } from './mobile/MobileTopbar';\nimport { MobileActivity } from './mobile/views/MobileActivity';\nimport { MobileAgents } from './mobile/views/MobileAgents';\nimport { MobileChat } from './mobile/views/MobileChat';\nimport { MobileConfig } from './mobile/views/MobileConfig';\nimport { MobileHistory } from './mobile/views/MobileHistory';\nimport { MobileMods } from './mobile/views/MobileMods';\nimport { MobileMore } from './mobile/views/MobileMore';\nimport { MobileArtifactCanvas } from './mobile/views/MobileArtifactCanvas';\nimport { MobileArtifacts } from './mobile/views/MobileArtifacts';\nimport { MobileSchedules } from './mobile/views/MobileSchedules';\nimport { MobileSearchModal } from './mobile/views/MobileSearchModal';\nimport { MobileSettings } from './mobile/views/MobileSettings';\nimport { MobileSkills } from './mobile/views/MobileSkills';\nimport { MobileTasks } from './mobile/views/MobileTasks';\n\ntype MainTabId = 'activity' | 'chat' | 'tasks' | 'settings' | 'more';\n\nexport type MobileView =\n | { id: MainTabId }\n | { id: 'artifacts?' }\n | { id: 'agents' }\n | { id: 'skills' }\n | { id: 'mods' }\n | { id: 'schedules' }\n | { id: 'history' }\n | { id: 'config' }\n | { id: 'artifacts?-detail'; slug: string }\n | { id: 'agent-detail'; name: string }\n | { id: 'task-detail'; taskId: string };\n\nconst MAIN_TABS: MobileTab[] = [\n { id: 'activity', label: 'Activity', icon: Activity },\n { id: 'chat', label: 'Chat', icon: MessageSquare },\n { id: 'tasks', label: 'Tasks', icon: CheckSquare },\n { id: 'settings', label: 'Settings', icon: SettingsIcon },\n { id: 'more', label: 'More', icon: Grid },\n];\n\nfunction parseNotificationTarget(notification: Notification): MobileView | null {\n const meta = notification.meta ?? {};\n const taskId = typeof meta.taskId === 'string' ? meta.taskId : null;\n const slug = typeof meta.slug === 'string' ? meta.slug : null;\n const agentName = typeof meta.agent === 'string' ? meta.agent : null;\n if (taskId) return { id: 'task-detail', taskId };\n if (slug) return { id: 'artifacts?-detail', slug };\n if (agentName) return { id: 'agent-detail', name: agentName };\n\n const link = notification.link ?? '';\n if (link.startsWith('/artifacts?/')) return { id: 'artifacts?-detail', slug: decodeURIComponent(link.slice('/artifacts?/'.length)) };\n if (link.startsWith('/tasks/')) return { id: 'task-detail', taskId: decodeURIComponent(link.slice('/tasks/'.length)) };\n if (link.startsWith('/agents/')) return { id: 'agent-detail', name: decodeURIComponent(link.slice('/agents/'.length)) };\n\n return null;\n}\n\nexport function MobileApp() {\n const [activeTab, setActiveTab] = useState<MainTabId>('activity');\n const [stack, setStack] = useState<MobileView[]>([]);\n const [searchOpen, setSearchOpen] = useState(false);\n const [pendingChatTaskId, setPendingChatTaskId] = useState<string | null>(null);\n const [snapshot, setSnapshot] = useState<Snapshot | null>(null);\n const [settings, setSettings] = useState<Settings | null>(null);\n const [bootError, setBootError] = useState<string | null>(null);\n const [loading, setLoading] = useState(true);\n const [wsEpoch, setWsEpoch] = useState(0);\n\n const currentView = useMemo<MobileView>(() => {\n return stack[stack.length - 1] ?? { id: activeTab };\n }, [activeTab, stack]);\n\n const pushView = useCallback((view: MobileView) => {\n setStack((cur) => [...cur, view]);\n }, []);\n\n const popView = useCallback(() => {\n setStack((cur) => cur.slice(0, -1));\n }, []);\n\n const goToTab = useCallback((id: MainTabId) => {\n setStack([]);\n setActiveTab(id);\n }, []);\n\n const refreshSnapshot = useCallback(async (): Promise<void> => {\n try {\n const next = await api.get<Snapshot>('/snapshot');\n setSnapshot(next);\n if (next.settings?.data) {\n setSettings(next.settings.data);\n }\n setBootError(null);\n } catch (err) {\n setBootError((err as Error).message || 'Dashboard server unreachable.');\n throw err;\n }\n }, []);\n\n useEffect(() => {\n let cancelled = false;\n\n Promise.all([\n api.get<Snapshot>('/snapshot').catch(() => null),\n api.get<{ data: Settings }>('/settings').catch(() => null),\n api.probeAuthStatus().catch(() => null),\n ])\n .then(([snap, settingsResponse]) => {\n if (cancelled) return;\n const nextSettings = settingsResponse?.data ?? snap?.settings?.data ?? null;\n if (snap) setSnapshot(snap);\n if (nextSettings) setSettings(nextSettings);\n if (!snap && !nextSettings) {\n setBootError('Dashboard server unreachable.');\n } else {\n setBootError(null);\n }\n })\n .catch((err) => {\n if (!cancelled) setBootError((err as Error).message || 'Dashboard server unreachable.');\n })\n .finally(() => {\n if (!cancelled) setLoading(false);\n });\n\n return () => {\n cancelled = true;\n };\n }, []);\n\n useEffect(() => {\n if (!settings?.theme) return;\n applyTheme(settings.theme);\n applyThemeTokens(settings.theme);\n }, [settings?.theme]);\n\n useEffect(() => {\n if (settings?.theme?.mode !== 'system') return;\n const mql = window.matchMedia('(prefers-color-scheme: light)');\n const onChange = () => {\n if (!settings?.theme) return;\n applyTheme(settings.theme);\n applyThemeTokens(settings.theme);\n };\n mql.addEventListener('change', onChange);\n return () => mql.removeEventListener('change', onChange);\n }, [settings?.theme]);\n\n useEffect(() => {\n const reconnect = () => {\n setWsEpoch((cur) => cur + 1);\n refreshSnapshot().catch(() => undefined);\n };\n const onVisibilityChange = () => {\n if (document.visibilityState === 'visible') reconnect();\n };\n\n window.addEventListener('online', reconnect);\n window.addEventListener('pageshow', reconnect);\n document.addEventListener('visibilitychange', onVisibilityChange);\n\n return () => {\n window.removeEventListener('online', reconnect);\n window.removeEventListener('pageshow', reconnect);\n document.removeEventListener('visibilitychange', onVisibilityChange);\n };\n }, [refreshSnapshot]);\n\n useEffect(() => {\n const ws = new Ws();\n const offMessage = ws.on((msg: WsMessage) => {\n if (msg.type === 'snapshot' && 'data' in msg && msg.data) {\n setSnapshot((cur) => ({ ...(cur ?? ({} as Snapshot)), ...msg.data }));\n } else if (msg.type === 'change') {\n refreshSnapshot().catch(() => undefined);\n } else if (msg.type === 'tasks:change') {\n const nextTask = msg.task;\n setSnapshot((cur) => {\n if (!cur) return cur;\n const tasks = (cur.tasks || []).map((task) => (task.id === nextTask.id ? nextTask : task));\n const exists = tasks.some((task) => task.id === nextTask.id);\n return { ...cur, tasks: exists ? tasks : [nextTask, ...tasks] };\n });\n } else if (msg.type === 'tasks:delete') {\n setSnapshot((cur) => (\n cur\n ? { ...cur, tasks: (cur.tasks || []).filter((task) => task.id !== msg.id) }\n : cur\n ));\n } else if (msg.type === 'settings:change') {\n if (msg.settings) setSettings(msg.settings);\n } else if (\n msg.type === 'project:change' ||\n msg.type === 'agents:change' ||\n msg.type === 'schedules:change' ||\n msg.type === 'artifact:change'\n ) {\n refreshSnapshot().catch(() => undefined);\n } else if (msg.type === 'agent:status' || msg.type === 'agent:restarted') {\n setSnapshot((cur) => {\n if (!cur) return cur;\n const agents = (cur.agents || []).map((agent) => (\n agent.name === msg.agent.name ? msg.agent : agent\n ));\n return { ...cur, agents };\n });\n }\n });\n\n return () => {\n offMessage();\n ws.close();\n };\n }, [refreshSnapshot, wsEpoch]);\n\n const handleTabChange = useCallback((id: string) => {\n if (id === 'activity' || id === 'chat' || id === 'tasks' || id === 'settings' || id === 'more') {\n goToTab(id);\n }\n }, [goToTab]);\n\n const handleNavigate = useCallback((type: string, id: string, meta?: Record<string, unknown> | null) => {\n if (type === 'notification') {\n const target = parseNotificationTarget({\n id: '',\n ts: '',\n severity: 'info',\n message: '',\n source: '',\n link: id || null,\n meta: meta ?? null,\n });\n if (target) {\n pushView(target);\n }\n return;\n }\n\n if (type === 'task' && id) {\n pushView({ id: 'task-detail', taskId: id });\n return;\n }\n if (type === 'artifacts?' && id) {\n pushView({ id: 'artifacts?-detail', slug: id });\n return;\n }\n if (type === 'agent' && id) {\n pushView({ id: 'agent-detail', name: id });\n return;\n }\n if (type === 'project') {\n goToTab('activity');\n return;\n }\n if (type === 'setting') {\n goToTab('settings');\n }\n }, [goToTab, pushView]);\n\n const renderView = (): React.ReactNode => {\n if (!snapshot || !settings) return null;\n\n if (stack.length > 0) {\n const stackedView = stack[stack.length - 1];\n switch (stackedView.id) {\n case 'artifacts?':\n return <MobileArtifacts snapshot={snapshot} onBack={popView} onOpenArtifact={(slug) => pushView({ id: 'artifacts?-detail', slug })} />;\n case 'agents':\n return <MobileAgents snapshot={snapshot} onBack={popView} onOpenAgent={(name) => pushView({ id: 'agent-detail', name })} onRefresh={refreshSnapshot} />;\n case 'skills':\n return <MobileSkills snapshot={snapshot} onBack={popView} />;\n case 'mods':\n return <MobileMods snapshot={snapshot} onBack={popView} />;\n case 'schedules':\n return <MobileSchedules snapshot={snapshot} onBack={popView} />;\n case 'history':\n return <MobileHistory onBack={popView} />;\n case 'config':\n return <MobileConfig onBack={popView} />;\n case 'artifacts?-detail':\n return <MobileArtifactCanvas slug={stackedView.slug} onBack={popView} />;\n case 'agent-detail':\n return (\n <MobileAgents\n snapshot={snapshot}\n onBack={popView}\n onOpenAgent={() => undefined}\n onRefresh={refreshSnapshot}\n selectedAgent={stackedView.name}\n />\n );\n case 'task-detail':\n return (\n <MobileTasks\n snapshot={snapshot}\n onRefresh={refreshSnapshot}\n selectedTaskId={stackedView.taskId}\n onCloseDetail={popView}\n onOpenChat={(taskId) => {\n setPendingChatTaskId(taskId);\n goToTab('chat');\n }}\n />\n );\n default:\n return null;\n }\n }\n\n switch (activeTab) {\n case 'activity':\n return <MobileActivity snapshot={snapshot} onRefresh={refreshSnapshot} />;\n case 'chat':\n return (\n <MobileChat\n snapshot={snapshot}\n settings={settings}\n initialTaskId={pendingChatTaskId}\n onClearTaskId={() => setPendingChatTaskId(null)}\n />\n );\n case 'tasks':\n return (\n <MobileTasks\n snapshot={snapshot}\n onRefresh={refreshSnapshot}\n onOpenChat={(taskId) => {\n setPendingChatTaskId(taskId);\n goToTab('chat');\n }}\n />\n );\n case 'settings':\n return <MobileSettings settings={settings} snapshot={snapshot} onRefresh={refreshSnapshot} />;\n case 'more':\n return (\n <MobileMore\n snapshot={snapshot}\n onNavigate={(id) => {\n if (id === 'artifacts?' || id === 'agents' || id === 'skills' || id === 'mods' || id === 'schedules' || id === 'history' || id === 'config') {\n pushView({ id });\n }\n }}\n />\n );\n default:\n return null;\n }\n };\n\n if (loading) {\n return (\n <div className=\"mobile-loading\">\n <Spinner size=\"lg\" />\n <p>Loading Bizar…</p>\n </div>\n );\n }\n\n if (bootError || !snapshot || !settings) {\n return (\n <div className=\"mobile-loading\">\n <h2>Dashboard unavailable</h2>\n <p>{bootError || 'Dashboard server unreachable.'}</p>\n <p className=\"muted\">Make sure the Bizar dashboard server is running.</p>\n </div>\n );\n }\n\n return (\n <div className=\"mobile-app\">\n <MobileTopbar\n activeTab={currentView.id}\n snapshot={snapshot}\n onSearch={() => setSearchOpen(true)}\n onNavigate={handleNavigate}\n />\n\n <main className=\"mobile-content\">{renderView()}</main>\n\n {stack.length === 0 && (\n <MobileBottomNav tabs={MAIN_TABS} activeTab={activeTab} onChange={handleTabChange} />\n )}\n\n {stack.length > 0 && (\n <button type=\"button\" className=\"mobile-back-btn\" onClick={popView} aria-label=\"Go back\">\n ← Back\n </button>\n )}\n\n <MobileSearchModal open={searchOpen} onClose={() => setSearchOpen(false)} onNavigate={handleNavigate} />\n </div>\n );\n}\n"],"file":"assets/mobile-BtxQJftK.js"}
|