Files
Codeman/src/web/public/index.html
T
Codeman maintainer a6cf4c2b2a feat(settings): reorder App Settings, tighten the rows, add a live layout preview
The document side of the settings modal was wider than it needed to be: every
row is text on the left and a switch pinned to the right, so a 960px shell plus
a 62ch cap on the description left a dead gap of ~350px between the two. The
shell is now 840px, the rail 196px, and descriptions run to 78ch, which closes
the gap and makes the right side sit proportionally with the rail.

Section order now leads with what you look at first: System (the version this
install runs and whether an update is waiting, with Updates promoted above
Paths/Automation/Remote access), then Terminal & Input, then Header & Panels.
The modal opens scrolled to System instead of Terminal & Input.

Header & Panels gains two things:

- every chip carries the icon of the button it switches on, so the list reads
  as the header itself rather than as a column of names (File Viewer shows the
  folder button, Cron the clock, and so on);
- a live preview above the chips: a scale model of the app with a header bar,
  right-docked panels, a toolbar and floating windows, rebuilt on every chip
  change so "what does this add" is answered in place, before saving.

The preview owns no icons of its own - it CLONES `.set-chip-ico` out of the
chip - so each icon has exactly one copy in index.html and a chip can never
drift from the button it previews. A chip joins the preview by carrying
`data-preview` (which slot) and `data-preview-order` (where in it); readouts
that are not buttons (plan usage, CPU, font size) use `data-preview-text`
instead. The frame is painted from skin tokens only, since hardcoded black
alphas turned it into a grey slab on the four light skins, and it is marked
`data-i18n-skip`: the mock tab names are decoration, and the labels inside are
copies of chip text i18n has already translated.

Cron moved into its own Scheduling group (it is a toolbar button, not a header
one, and the preview places it accordingly).

test/app-settings-structure.test.ts pins the new contract: the rail and the
document agree on order, System leads with the version above the paths, and
every previewed chip has both an icon to clone and a slot that exists.
2026-08-10 10:58:05 +02:00

2993 lines
198 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!-- Resolve all relative assets against the site root so the same shell can be
served at /session/:id (detached single-session window) without 404ing
on relative <script>/<link> URLs. Must precede the first resource tag. -->
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<meta name="description" content="Claude Code session manager with web interface">
<meta name="theme-color" content="#11151c">
<meta name="google" content="notranslate">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<link rel="apple-touch-icon" href="icon-192.png">
<link rel="manifest" href="manifest.json">
<title>Codeman</title>
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0%25' y1='0%25' x2='100%25' y2='100%25'%3E%3Cstop offset='0%25' stop-color='%2360a5fa'/%3E%3Cstop offset='100%25' stop-color='%233b82f6'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='32' height='32' rx='6' fill='%230a0a0a'/%3E%3Cpath d='M18 4L8 18h6l-2 10 10-14h-6z' fill='url(%23g)'/%3E%3C/svg%3E">
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="mobile.css" media="(max-width: 1023px)">
<!-- xterm.css loaded async — terminal won't display until xterm.js runs anyway -->
<link rel="preload" href="vendor/xterm.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="vendor/xterm.css"></noscript>
<!-- Preload critical resources — lets browser discover these during HTML parse
instead of waiting until <script> tags at bottom-of-body are reached. -->
<link rel="preload" href="vendor/xterm.min.js" as="script">
<link rel="preload" href="constants.js" as="script">
<link rel="preload" href="app.js" as="script">
<!-- Self-hosted xterm.js — eliminates CDN DNS/TLS latency (~100ms).
'defer' preserves execution order (xterm loads before fit addon). -->
<script defer src="vendor/xterm.min.js"></script>
<script defer src="vendor/xterm-addon-fit.min.js"></script>
<!-- SerializeAddon: snapshots xterm state (viewport + scrollback + attrs) for
per-session restore on tab switches. Lets codex tabs survive switch-away
without codeman having to replay codex's byte stream (which loses earlier
conversation because codex's TUI redraws drop it from the viewport). -->
<script defer src="vendor/xterm-addon-serialize.min.js"></script>
<!-- WebGL addon lazy-loaded by app.js on desktop only (skipped on mobile, saving 244KB) -->
<script defer src="vendor/xterm-addon-unicode11.min.js"></script>
<script defer src="vendor/xterm-zerolag-input.js"></script>
<script defer src="vendor/xterm-predictive-echo.js"></script>
<script defer src="vendor/marked.min.js"></script>
<!-- DOMPurify (allowlist HTML sanitizer for rendered markdown).
Must load before sanitize-html.js (which wires it) and app.js (which calls it). -->
<script defer src="vendor/dompurify.min.js"></script>
<!-- Synchronous mobile detection — runs before first paint to prevent panel flash -->
<script>if(window.innerWidth<768||(('ontouchstart' in window||navigator.maxTouchPoints>0)&&window.innerWidth<1024))document.documentElement.classList.add('mobile-init');</script>
<!-- Synchronous skin selection — runs before first paint to prevent theme flash -->
<script>try{var s=localStorage.getItem('codeman:skin'),a=['og','daylight-green','daylight-blue','paper-gray','solarized-light','catppuccin-latte','rose-pine-dawn'];if(a.indexOf(s)<0)s='daylight-blue';document.documentElement.dataset.skin=s;window.__codemanSkin=s;}catch(e){document.documentElement.dataset.skin='daylight-blue';window.__codemanSkin='daylight-blue';}</script>
<!-- Apply the saved per-device language before first paint. The full translation
layer loads below; setting lang/dir here prevents an English accessibility
tree from flashing while the deferred scripts start. -->
<script>try{var m=window.innerWidth<768||(('ontouchstart' in window||navigator.maxTouchPoints>0)&&window.innerWidth<1024);var k=m?'codeman-app-settings-mobile':'codeman-app-settings';var l=JSON.parse(localStorage.getItem(k)||'{}').language;l=l==='zh-CN'?'zh-CN':'en';document.documentElement.lang=l;window.__codemanLanguage=l;}catch(e){document.documentElement.lang='en';window.__codemanLanguage='en';}</script>
<!-- Inline critical CSS for instant skeleton paint (before styles.css loads) -->
<style>
.loading-skeleton{display:flex;flex-direction:column;height:100vh;height:100dvh;background:var(--bg-dark,#11151c)}
.skeleton-header{height:40px;background:var(--glass-bg,rgba(31,38,48,0.85));border-bottom:1px solid var(--glass-border,rgba(255,255,255,0.08));display:flex;align-items:center;padding:0 12px}
.skeleton-brand{color:var(--accent,#38b6f0);font-size:14px;font-weight:700;font-family:'Manrope',-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;opacity:.85}
.skeleton-tabs{display:flex;gap:4px;margin-left:16px}
.skeleton-tab{width:80px;height:24px;background:var(--control-bg,rgba(255,255,255,0.04));border-radius:6px}
.skeleton-terminal{flex:1;background:var(--term-bg,#161b23)}
.skeleton-toolbar{height:42px;background:var(--glass-bg,rgba(31,38,48,0.85));border-top:1px solid var(--glass-border,rgba(255,255,255,0.08))}
.app-loaded .loading-skeleton{display:none}
</style>
</head>
<body>
<!-- Loading skeleton — visible instantly before CSS/JS loads, hidden by app-loaded class -->
<div class="loading-skeleton">
<div class="skeleton-header">
<span class="skeleton-brand">Codeman</span>
<div class="skeleton-tabs"><div class="skeleton-tab"></div></div>
</div>
<div class="skeleton-terminal"></div>
<div class="skeleton-toolbar"></div>
</div>
<!-- Skip link for keyboard users -->
<!-- onclick scrolls/focuses directly: with <base href="/"> a bare href="#..." would
navigate to /#... (the dashboard) from a /session/:id solo window. -->
<a href="#terminalContainer" class="skip-link" onclick="event.preventDefault(); var t=document.getElementById('terminalContainer'); if(t){t.scrollIntoView(); var f=t.querySelector('textarea,[tabindex]'); (f||t).focus&&(f||t).focus();}">Skip to terminal</a>
<div class="app">
<!-- Compact Header with Session Tabs -->
<header class="header">
<div class="header-brand">
<span class="logo" onclick="app.goHome()" title="Go to main page"
><span class="logo-text">Codeman</span><span class="logo-compact" aria-hidden="true">C</span></span
>
</div>
<!-- Session Tabs -->
<div class="session-tabs" id="sessionTabs" role="tablist" aria-label="Session tabs">
</div>
<!-- Detached single-session window title (shown only in solo mode) -->
<div class="solo-session-title" id="soloSessionTitle" style="display: none;" aria-live="polite"></div>
<div class="header-right" id="headerRight">
<button class="btn-admin-panel btn-admin-panel--hidden" id="adminPanelBtn" onclick="window.codemanAdmin.openAdminPanel()" title="Admin Panel (multi-user administration)" aria-label="Open admin panel">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
<span>Admin Panel</span>
</button>
<button class="btn-icon-header btn-solo-redock" id="soloRedockBtn" style="display: none;" onclick="window.close()" title="Re-dock to dashboard (close window)" aria-label="Re-dock session to dashboard">&#x229E;</button>
<button class="tunnel-indicator" id="tunnelIndicator" style="display: none;" onclick="app.toggleTunnelPanel()" title="Cloudflare Tunnel" aria-label="Tunnel status">
<span class="tunnel-dot"></span>
</button>
<div class="connection-indicator" id="connectionIndicator" style="display: none;">
<span class="connection-dot" id="connectionDot"></span>
<span class="connection-text" id="connectionText"></span>
</div>
<div class="header-font-controls">
<button class="btn-icon-header btn-sm" onclick="app.decreaseFontSize()" title="Decrease font (Ctrl+-)" aria-label="Decrease font size">A-</button>
<span class="font-size-display" id="fontSizeDisplay" aria-label="Current font size">14</span>
<button class="btn-icon-header btn-sm" onclick="app.increaseFontSize()" title="Increase font (Ctrl++)" aria-label="Increase font size">A+</button>
</div>
<div class="header-system-stats" id="headerSystemStats" title="System resource usage">
<div class="stat-item">
<span class="stat-label">CPU</span>
<div class="stat-bar">
<div class="stat-bar-fill stat-bar-cpu" id="statCpuBar"></div>
</div>
<span class="stat-value" id="statCpu">--%</span>
</div>
<div class="stat-item">
<span class="stat-label">MEM</span>
<div class="stat-bar">
<div class="stat-bar-fill stat-bar-mem" id="statMemBar"></div>
</div>
<span class="stat-value" id="statMem">--</span>
</div>
</div>
<button class="btn-icon-header btn-redraw-terminal btn-redraw-terminal--hidden" onclick="app.restoreTerminalSize()" title="Redraw terminal to fit current screen (Ctrl+Shift+R)" aria-label="Redraw terminal"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="1 4 1 10 7 10"/><polyline points="23 20 23 14 17 14"/><path d="M20.49 9A9 9 0 0 0 5.64 5.64L1 10m22 4l-4.64 4.36A9 9 0 0 1 3.51 15"/></svg></button>
<button class="btn-icon-header btn-response-viewer-header btn-response-viewer-header--hidden" onclick="app.toggleResponseViewer()" title="View last response" aria-label="View last response"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg></button>
<button class="btn-icon-header btn-away-digest btn-away-digest--hidden" onclick="app.openAwayDigest()" title="Away Digest" aria-label="Open away digest"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8 6h13"/><path d="M8 12h13"/><path d="M8 18h13"/><path d="M3 6h.01"/><path d="M3 12h.01"/><path d="M3 18h.01"/></svg></button>
<button class="btn-icon-header btn-session-manager btn-session-manager--hidden" onclick="app.openSessionManager()" title="Session Manager" aria-label="Open session manager"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="12 2 2 7 12 12 22 7 12 2"/><polyline points="2 17 12 22 22 17"/><polyline points="2 12 12 17 22 12"/></svg></button>
<button class="btn-icon-header btn-approvals btn-approvals--hidden" id="approvalsBtn" onclick="app.toggleApprovalsInbox()" title="Approvals waiting on you" aria-label="Open approvals inbox" aria-expanded="false">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></svg>
<span class="approvals-badge" id="approvalsBadge">0</span>
</button>
<button class="btn-icon-header btn-readmymind btn-readmymind--hidden" id="readMyMindBtn" onclick="app.openReadMyMind()" title="Read My Mind: predict your next prompt" aria-label="Predict my next prompt"><span class="readmymind-icon" aria-hidden="true">🧠</span></button>
<button class="btn-icon-header btn-attachments-history btn-attachments-history--hidden" id="attachmentsHistoryBtn" onclick="app.toggleAttachmentHistory()" title="Attachments" aria-label="Open attachment history" aria-expanded="false">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg>
<span class="attachment-history-badge" id="attachmentHistoryBadge" style="display:none;">0</span>
</button>
<button class="btn-icon-header btn-file-viewer" onclick="app.toggleFileBrowserButton()" title="File Viewer" aria-label="Open file viewer" aria-expanded="false"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg></button>
<button class="btn-icon-header btn-multimonitor btn-multimonitor--hidden" onclick="app.launchMultiMonitor()" title="Open Codeman across all displays" aria-label="Open Codeman across all displays"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="2" y="4" width="13" height="9" rx="1.5"/><rect x="11" y="9" width="11" height="8" rx="1.5"/></svg></button>
<button class="btn-icon-header btn-ultracode-agents btn-ultracode-agents--hidden" onclick="app.toggleUltracodeAgentsPanel()" title="Ultracode / Workflow agents" aria-label="Open ultracode workflow agents"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="6" cy="6" r="2.5"/><circle cx="6" cy="18" r="2.5"/><circle cx="18" cy="12" r="2.5"/><path d="M8.2 7.2 15.6 11M8.2 16.8 15.6 13"/></svg></button>
<div class="header-plan-usage header-plan-usage--hidden" id="planUsageChip" title="Claude plan usage limits">—</div>
<button class="btn-icon-header btn-notifications" onclick="app.toggleNotifications()" title="Notifications" aria-label="Toggle notifications" style="display:none;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></svg>
<span class="notification-badge" id="notifBadge" style="display:none;">0</span>
</button>
<button class="btn-icon-header btn-lifecycle-log" style="display: none" onclick="app.openLifecycleLog()" title="Session Lifecycle Log" aria-label="Open session lifecycle log"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/></svg></button>
<button class="btn-icon-header btn-settings" onclick="app.openAppSettings()" title="App Settings" aria-label="Open app settings"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg></button>
<div class="header-tokens" id="headerTokens" style="display: none" title="Total tokens across all sessions">0 tokens</div>
</div>
</header>
<!-- Connection-loss banner: shown once session state HAS loaded and the
link then drops, so the terminal stays readable behind it.
The blocking variant is #offlineOverlay at the end of <body>. -->
<div class="offline-banner" id="offlineBanner" role="status" hidden>
<span class="offline-banner-dot" aria-hidden="true"></span>
<span class="offline-banner-text" id="offlineBannerText">No connection to the Codeman server</span>
<span class="offline-banner-detail" id="offlineBannerDetail"></span>
<button class="offline-banner-retry" id="offlineBannerRetry" onclick="app.retryConnection()">Retry now</button>
</div>
<!-- Timer Banner (shown when timed run is active) -->
<div class="timer-banner" id="timerBanner" style="display: none;">
<div class="timer-content">
<span class="timer-value" id="timerValue">00:00:00</span>
<div class="timer-progress"><div class="timer-progress-fill" id="timerProgress"></div></div>
<span class="timer-meta" id="timerMeta">0 tasks | $0.00</span>
</div>
<button class="btn-icon-only" onclick="app.stopCurrentRun()" title="Stop" aria-label="Stop current run">&#x25A0;</button>
</div>
<!-- Respawn Banner - Compact 2-column layout -->
<div class="respawn-banner" id="respawnBanner" style="display: none;">
<div class="respawn-compact-layout">
<div class="respawn-status-col">
<div class="respawn-status-row1">
<span class="respawn-indicator">&#x21BB;</span>
<span class="respawn-state" id="respawnState">Watching</span>
<span class="detection-confidence" id="detectionConfidence"></span>
<span class="respawn-cycles">#<span id="respawnCycleCount">0</span></span>
<span class="respawn-timer" id="respawnTimer" style="display: none;"></span>
<span class="respawn-tokens" id="respawnTokens" style="display: none;"></span>
<button class="btn-icon-only" onclick="app.stopRespawn()" title="Stop Respawn" aria-label="Stop respawn">&#x25A0;</button>
</div>
<div class="respawn-status-row2" id="respawnStatusRow2">
<span class="detection-hook" id="detectionHook" style="display: none;"></span>
<span class="detection-ai-check" id="detectionAiCheck" style="display: none;"></span>
<span class="detection-status" id="detectionStatus"></span>
<span class="detection-waiting" id="detectionWaiting" style="display: none;"></span>
<div class="respawn-countdown-timers" id="respawnCountdownTimers"></div>
</div>
</div>
<div class="respawn-action-log" id="respawnActionLog"></div>
</div>
</div>
<!-- Orchestrator Loop Panel -->
<div class="orchestrator-panel" id="orchestratorPanel" style="display: none;">
<div class="orchestrator-header">
<span class="orchestrator-title">Orchestrator</span>
<span class="orchestrator-state-badge" id="orchestratorStateBadge">idle</span>
<div class="orchestrator-actions" id="orchestratorActions"></div>
<button class="orchestrator-close-btn" onclick="app.closeOrchestratorPanel()" title="Close">&times;</button>
</div>
<div class="orchestrator-body" id="orchestratorBody"></div>
</div>
<!-- Ralph / Todo Tracker Panel -->
<div class="ralph-panel collapsed" id="ralphStatePanel" style="display: none;">
<!-- Collapsed Summary Bar -->
<div class="ralph-summary" id="ralphSummary">
<div class="ralph-summary-content" onclick="app.toggleRalphStatePanel()">
<div class="ralph-ring-mini" id="ralphRingMini">
<svg viewBox="0 0 36 36" class="ralph-ring-svg">
<defs>
<linearGradient id="ralphGradientMini" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#3ec8ee" />
<stop offset="100%" stop-color="#2b8fd9" />
</linearGradient>
</defs>
<circle class="ralph-ring-bg" cx="18" cy="18" r="15.9" />
<circle class="ralph-ring-progress-mini" cx="18" cy="18" r="15.9" id="ralphRingMiniProgress" style="stroke-dashoffset: 100" />
</svg>
<span class="ralph-ring-text" id="ralphRingMiniText">0%</span>
</div>
<div class="ralph-summary-info">
<span class="ralph-title">Ralph / Todo Tracker</span>
<span class="ralph-status-badge" id="ralphStatusBadge">
<span class="ralph-status-dot"></span>
<span class="ralph-status-text">Idle</span>
</span>
</div>
<div class="ralph-summary-stats">
<span class="ralph-stat"><span class="ralph-stat-icon">&#x23F1;</span><span id="ralphStatTime">0m</span></span>
<span class="ralph-stat"><span class="ralph-stat-icon">&#x2713;</span><span id="ralphStatTasks">0/0</span></span>
</div>
<span class="ralph-toggle" id="ralphToggle">&#x25BC;</span>
</div>
<div class="ralph-controls">
<div class="ralph-menu-container">
<button class="btn-icon-sm" onclick="event.stopPropagation(); app.toggleRalphMenu()" title="Fix Plan Menu" id="ralphMenuBtn">&#x2630;</button>
<div class="ralph-dropdown" id="ralphDropdown">
<button onclick="event.stopPropagation(); app.showFixPlan(); app.closeRalphMenu()">View Fix Plan</button>
<button onclick="event.stopPropagation(); app.writeFixPlanToFile(); app.closeRalphMenu()">Write to File</button>
<button onclick="event.stopPropagation(); app.importFixPlanFromFile(); app.closeRalphMenu()">Import from File</button>
<hr>
<button onclick="event.stopPropagation(); app.resetCircuitBreaker(); app.closeRalphMenu()">Reset Circuit Breaker</button>
</div>
</div>
<button class="btn-icon-sm" onclick="event.stopPropagation(); app.toggleRalphDetach()" title="Detach/Attach panel" id="ralphDetachBtn">&#x29C9;</button>
<button class="btn-icon-sm" onclick="event.stopPropagation(); app.closeRalphTracker()" title="Close tracker">&times;</button>
</div>
</div>
<!-- Expanded Detail View - Compact horizontal layout -->
<div class="ralph-detail" id="ralphDetail">
<div class="ralph-detail-row">
<!-- Progress Ring -->
<div class="ralph-ring-compact">
<svg viewBox="0 0 100 100" class="ralph-ring-svg-large">
<defs>
<linearGradient id="ralphGradient" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#3ec8ee" />
<stop offset="100%" stop-color="#2b8fd9" />
</linearGradient>
</defs>
<circle class="ralph-ring-track" cx="50" cy="50" r="42" />
<circle class="ralph-ring-fill" cx="50" cy="50" r="42" id="ralphRingProgress" style="stroke-dashoffset: 264" />
</svg>
<div class="ralph-ring-center">
<div class="ralph-ring-percent" id="ralphRingPercent">0%</div>
<div class="ralph-ring-label">Complete</div>
</div>
</div>
<!-- Meta Info + Tasks unified -->
<div class="ralph-info-unified">
<div class="ralph-info-labels">
<div class="ralph-info-row">
<span class="ralph-info-label">Tasks</span>
<span class="ralph-tasks-count" id="ralphTasksCount">0/0</span>
</div>
<div class="ralph-info-row">
<span class="ralph-info-label">Phrase</span>
<code id="ralphPhrase">--</code>
</div>
<div class="ralph-info-row">
<span class="ralph-info-label">Elapsed</span>
<span id="ralphElapsed">0m</span>
</div>
<div class="ralph-info-row" id="ralphVersionRow" style="display: none;">
<span class="ralph-info-label">Plan</span>
<div class="ralph-version-info">
<span class="version-badge" id="ralphPlanVersion">v1</span>
<button class="rollback-btn" id="ralphRollbackBtn" onclick="app.showPlanHistory()" title="View history">history</button>
</div>
</div>
</div>
<div class="ralph-tasks-grid" id="ralphTasksGrid"></div>
</div>
</div>
</div>
</div>
<!-- Main Terminal Area -->
<main class="main">
<div class="terminal-wrap">
<div class="terminal-container" id="terminalContainer"></div>
<textarea id="cjkInput" rows="1" placeholder="CJK input (Enter = send, Esc = clear)"
maxlength="65536" aria-label="CJK IME input field"
autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"></textarea>
</div>
<!-- Web tab layer: one iframe per open dashboard, shown in place of the
terminal while a web tab is active. Frames stay mounted while hidden so
switching tabs does not reload (and re-authenticate) a dashboard. -->
<div class="webview-layer" id="webviewLayer"></div>
<!-- Welcome Overlay (shown when no session active) -->
<div class="welcome-overlay" id="welcomeOverlay">
<!-- Open tabs as a rail docked to the left edge (home-sessions.js).
Absolutely positioned so the centered content below never moves, and
therefore only rendered where the gutter is wider than the rail;
ships `hidden` and only that module reveals it. -->
<aside class="home-sessions" id="homeSessions" hidden></aside>
<div class="welcome-content">
<h1 class="welcome-title">Codeman</h1>
<p class="welcome-desc">Manage AI Coding tools in persistent tmux sessions.</p>
<div class="welcome-actions">
<button class="welcome-btn welcome-btn-claude" id="welcomeClaudeBtn" style="display: none;" onclick="app.setRunMode('claude'); app.runClaude()">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="5 3 19 12 5 21 5 3"/></svg>
Run Claude Code
</button>
<button class="welcome-btn welcome-btn-tunnel" id="welcomeTunnelBtn" style="display: none;" onclick="app.toggleTunnelFromWelcome()">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/></svg>
Cloudflare Tunnel
</button>
<button class="welcome-btn welcome-btn-opencode" id="welcomeOpencodeBtn" style="display: none;" onclick="app.setRunMode('opencode'); app.runOpenCode()">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="5 3 19 12 5 21 5 3"/></svg>
Run OpenCode
</button>
<button class="welcome-btn welcome-btn-antigravity" id="welcomeAntigravityBtn" style="display: none;" onclick="app.setRunMode('antigravity'); app.runAntigravity()">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="5 3 19 12 5 21 5 3"/></svg>
Run Antigravity
</button>
<button class="welcome-btn welcome-btn-gemini" id="welcomeGeminiBtn" style="display: none;" onclick="app.setRunMode('gemini'); app.runGemini()">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="5 3 19 12 5 21 5 3"/></svg>
Run Gemini
</button>
</div>
<div class="welcome-qr" id="welcomeQr" onclick="app.toggleWelcomeQrSize()">
<div class="welcome-qr-inner" id="welcomeQrInner"></div>
<div class="welcome-qr-url" id="welcomeQrUrl"></div>
</div>
<div class="history-sessions" id="historySessions" style="display:none">
<div class="search-panel" id="searchPanel">
<div class="search-input-row">
<input
type="search"
id="searchInput"
class="search-input"
placeholder="Search sessions, events, files…"
autocomplete="off"
spellcheck="false"
maxlength="200"
aria-label="Search across sessions"
/>
<button type="button" id="searchClearBtn" class="search-clear-btn" aria-label="Clear search" hidden>×</button>
</div>
<div class="search-filters" id="searchFilters">
<div class="search-filter-group" role="group" aria-label="Source type filter">
<button type="button" class="search-filter-chip active" data-type-filter="session">Sessions</button>
<button type="button" class="search-filter-chip active" data-type-filter="event">Events</button>
<button type="button" class="search-filter-chip active" data-type-filter="file">Files</button>
</div>
<div class="search-filter-group search-filter-secondary">
<select id="searchCaseFilter" class="search-select" aria-label="Filter by case">
<option value="">All cases</option>
</select>
<select id="searchStatusFilter" class="search-select" aria-label="Filter by session status">
<option value="">Any status</option>
<option value="active">Active</option>
<option value="history">History</option>
</select>
<select id="searchDateFilter" class="search-select" aria-label="Filter by date range">
<option value="">Any time</option>
<option value="1">Past 24h</option>
<option value="7">Past 7 days</option>
<option value="30">Past 30 days</option>
</select>
</div>
</div>
<div class="search-results" id="searchResults" hidden></div>
</div>
<div class="history-header" id="historyHeader">
<h3 class="history-title" id="historyTitle">Resume Conversation</h3>
<span class="history-count" id="historyCount" data-i18n-skip></span>
<div class="history-controls">
<input
type="search"
id="historyFilter"
class="history-filter"
placeholder="Filter…"
autocomplete="off"
spellcheck="false"
maxlength="100"
aria-label="Filter past conversations"
/>
<select id="historySort" class="search-select history-sort" aria-label="Sort past conversations">
<option value="recent">Recent</option>
<option value="name">Name A–Z</option>
<option value="folder">Folder A–Z</option>
</select>
</div>
</div>
<div class="history-list" id="historyList"></div>
</div>
<p class="welcome-hint">Or click Run to start</p>
<button class="welcome-ralph-link" onclick="app.showRalphWizard()">Start Ralph Loop &rarr;</button>
</div>
</div>
<!-- Mobile Overview (phone home screen, shown in place of the welcome
overlay when the "C" logo is tapped). Ships `hidden`: only
mobile-overview.js removes it, and only when the phone gate passes, so
desktop (which never loads mobile.css) can never render it. -->
<div class="mobile-overview" id="mobileOverview" hidden></div>
</main>
<!-- Project Insights Panel (shows file-viewing Bash commands) -->
<div class="project-insights-panel" id="projectInsightsPanel">
<div class="project-insights-header">
<span class="project-insights-title">Project Insights</span>
<button class="btn-icon-sm" onclick="app.closeProjectInsightsPanel()" title="Close">&times;</button>
</div>
<div class="project-insights-body" id="projectInsightsList"></div>
<div class="project-insights-resize-handle"></div>
</div>
<!-- File Browser Panel -->
<div class="file-browser-panel" id="fileBrowserPanel">
<div class="file-browser-header">
<span class="file-browser-title">Files</span>
<div class="file-browser-actions">
<button class="btn-icon-sm btn-file-browser-hidden" onclick="app.toggleFileBrowserHidden()" title="Show hidden files and folders" aria-label="Show hidden files and folders" aria-pressed="false" id="fileBrowserHiddenBtn">.*</button>
<button class="btn-icon-sm" onclick="app.refreshFileBrowser()" title="Refresh">&#x21BB;</button>
<button class="btn-icon-sm" onclick="app.toggleFileBrowserExpand()" title="Expand/Collapse All" id="fileBrowserExpandBtn">&#x229E;</button>
<button class="btn-icon-sm" onclick="app.closeFileBrowserPanel()" title="Close">&times;</button>
</div>
</div>
<div class="file-browser-search">
<input type="text" id="fileBrowserSearch" placeholder="Filter files..." oninput="app.filterFileBrowser(this.value)">
</div>
<div class="file-browser-body" id="fileBrowserTree">
<div class="file-browser-empty">Select a session to view files</div>
</div>
<div class="file-browser-status" id="fileBrowserStatus"></div>
</div>
<!-- File Preview Overlay -->
<div class="file-preview-overlay" id="filePreviewOverlay">
<div class="file-preview-window">
<div class="file-preview-header">
<span class="file-preview-title" id="filePreviewTitle">file.ts</span>
<div class="file-preview-actions">
<button class="btn-icon-sm file-preview-edit-btn" id="filePreviewEditBtn" onclick="app.enterFilePreviewEdit()" title="Edit file" aria-label="Edit file" hidden><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5z"/></svg></button>
<button class="btn-icon-sm" onclick="app.copyFilePreviewContent()" title="Copy content">&#x2398;</button>
<button class="btn-icon-sm" onclick="app.closeFilePreview()" title="Close">&times;</button>
</div>
</div>
<div class="file-preview-body" id="filePreviewBody"></div>
<div class="file-preview-editbar" id="filePreviewEditBar" hidden>
<span class="file-preview-dirty" id="filePreviewDirty" hidden>Unsaved changes</span>
<span class="file-preview-editbar-spacer"></span>
<button class="file-preview-editbar-btn" onclick="app.cancelFilePreviewEdit()">Cancel</button>
<button class="file-preview-editbar-btn file-preview-editbar-btn--save" id="filePreviewSaveBtn" onclick="app.saveFilePreviewEdit()" disabled>Save</button>
</div>
<div class="file-preview-footer" id="filePreviewFooter"></div>
</div>
</div>
<!-- Response Viewer (mobile: native-scroll overlay for reading full Claude responses) -->
<div class="response-viewer" id="responseViewer">
<div class="response-viewer-header">
<span id="responseViewerTitle">Last Response</span>
<div class="response-viewer-actions">
<button class="response-viewer-more" id="responseViewerMore" onclick="app.loadFullContext()">More</button>
<button class="response-viewer-close" onclick="app.toggleResponseViewer()">&times;</button>
</div>
</div>
<div class="response-viewer-body" id="responseViewerBody"></div>
</div>
<div class="response-viewer-backdrop" id="responseViewerBackdrop" onclick="app.toggleResponseViewer()"></div>
<!-- Bottom Toolbar -->
<footer class="toolbar">
<div class="toolbar-left">
<!-- Run AI -->
<div class="toolbar-group">
<div class="run-btn-group">
<button class="btn-toolbar btn-run" id="runBtn" onclick="app.run()" title="Run">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polygon points="5 3 19 12 5 21 5 3"/></svg>
<span id="runBtnLabel">Run</span>
</button>
<button class="btn-toolbar btn-run-gear" onclick="app.toggleRunModeMenu(event)" title="Select AI backend">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M6 9l6 6 6-6"/></svg>
</button>
<div class="run-mode-menu" id="runModeMenu">
<button class="run-mode-option" data-mode="claude" onclick="app.setRunMode('claude')">
<span class="run-mode-dot claude"></span>Claude Code
</button>
<button class="run-mode-option" data-mode="opencode" onclick="app.setRunMode('opencode')">
<span class="run-mode-dot opencode"></span>OpenCode
</button>
<button class="run-mode-option" data-mode="codex" onclick="app.setRunMode('codex')">
<span class="run-mode-dot codex"></span>Codex
</button>
<button class="run-mode-option" data-mode="gemini" onclick="app.setRunMode('gemini')">
<span class="run-mode-dot gemini"></span>Gemini
</button>
<button class="run-mode-option" data-mode="antigravity" onclick="app.setRunMode('antigravity')">
<span class="run-mode-dot antigravity"></span>Antigravity
</button>
<div class="run-mode-sep"></div>
<button class="run-mode-option" data-mode="shell" onclick="app.setRunMode('shell')">
<span class="run-mode-dot shell"></span>Terminal / Shell
</button>
<div class="run-mode-sep"></div>
<!-- Web tabs: dashboards open as tabs beside agent sessions. These do NOT
set runMode: the Run button always means "start an agent". -->
<div class="run-mode-header">Web / URL</div>
<div class="run-mode-webviews" id="runModeWebviews"></div>
<button class="run-mode-option run-mode-option--add" onclick="app.showWebviewModal()">
<span class="run-mode-dot web"></span>Add URL&hellip;
</button>
<div class="run-mode-sep"></div>
<div class="run-mode-header">Recent Sessions</div>
<div class="run-mode-history" id="runModeHistory"></div>
</div>
</div>
<div class="tab-count-group" title="Instance count">
<button class="tab-count-btn" onclick="app.decrementTabCount()">−</button>
<input type="number" id="tabCount" class="tab-count-input" value="1" min="1" max="20" readonly>
<button class="tab-count-btn" onclick="app.incrementTabCount()">+</button>
</div>
<button class="btn-toolbar btn-stop" onclick="app.stopClaude()" title="Stop (Ctrl+C)">
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><rect x="4" y="4" width="16" height="16" rx="2"/></svg>
</button>
<button class="btn-toolbar btn-shell" onclick="app.runShell()" title="Run Shell">
Run Shell
</button>
<!-- Phone-only: replaces the Shell button on ≤430px (Shell moves into the Run
dropdown there). Sends a bare Enter to the active session, the complement
to the accessory bar's Esc. Hidden everywhere else — see styles.css. -->
<button class="btn-toolbar btn-enter" onclick="app.sendEnterKey()" title="Send Enter">
Enter
</button>
<div class="tab-count-group" title="Instance count">
<button class="tab-count-btn" onclick="app.decrementShellCount()">−</button>
<input type="number" id="shellCount" class="tab-count-input" value="1" min="1" max="20" readonly>
<button class="tab-count-btn" onclick="app.incrementShellCount()">+</button>
</div>
<div class="case-select-group">
<div class="case-combobox" id="quickStartCasePicker">
<input
type="text"
id="quickStartCaseSearch"
class="case-combobox-input"
role="combobox"
aria-controls="quickStartCaseList"
aria-expanded="false"
aria-autocomplete="list"
autocomplete="off"
spellcheck="false"
title="Select case"
>
<div id="quickStartCaseList" class="case-combobox-list hidden" role="listbox"></div>
</div>
<select id="quickStartCase" class="toolbar-select case-native-select" title="Select case" aria-hidden="true" tabindex="-1">
<option value="testcase">testcase</option>
</select>
<button class="btn-case-add" onclick="app.showCreateCaseModal()" title="Create new case">+</button>
<button class="btn-case-settings" onclick="app.toggleCaseSettings()" title="Case settings">&#x2699;</button>
<div class="case-settings-popover hidden" id="caseSettingsPopover">
<label class="checkbox-inline">
<input type="checkbox" id="caseAgentTeams" onchange="app.onCaseSettingChanged()">
<span>Agent Teams</span>
</label>
<span class="form-hint">Enable experimental Agent Teams for new sessions in this case</span>
<label class="checkbox-inline" style="margin-top: 6px;">
<input type="checkbox" id="caseOpusContext1m" onchange="app.onCaseSettingChanged()">
<span>1M Opus Context</span>
</label>
<span class="form-hint">Use 1M token context window for new sessions</span>
</div>
</div>
<!-- Mobile-only case button + gear -->
<button class="btn-voice-mobile" id="voiceInputBtnMobile" onclick="VoiceInput.toggle()"
title="Voice input" aria-label="Start voice input" aria-pressed="false">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/>
<path d="M19 10v2a7 7 0 0 1-14 0v-2"/>
<line x1="12" y1="19" x2="12" y2="23"/>
<line x1="8" y1="23" x2="16" y2="23"/>
</svg>
</button>
<button class="btn-settings-mobile" onclick="app.openAppSettings()" title="App Settings">&#x2699;</button>
<button class="btn-toolbar btn-case-mobile" onclick="app.showMobileCasePicker()" title="Select case">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>
<span id="mobileCaseName">Case</span>
</button>
<button class="btn-case-settings-mobile" id="btnCaseSettingsMobile" onclick="app.toggleCaseSettingsMobile()" title="Case settings"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg></button>
<div class="case-settings-popover-mobile hidden" id="caseSettingsPopoverMobile">
<label class="checkbox-inline">
<input type="checkbox" id="caseAgentTeamsMobile" onchange="app.onCaseSettingChangedMobile()">
<span>Agent Teams</span>
</label>
<span class="form-hint">Enable Agent Teams for new sessions</span>
<label class="checkbox-inline" style="margin-top: 6px;">
<input type="checkbox" id="caseOpusContext1mMobile" onchange="app.onCaseSettingChangedMobile()">
<span>1M Opus Context</span>
</label>
<span class="form-hint">Use 1M token context window</span>
</div>
</div>
<!-- Directory (hidden) -->
<div class="toolbar-group" style="display: none;">
<button class="btn-toolbar" onclick="app.toggleDirInput()" title="Set working directory">
<span id="dirDisplay">No directory</span>
</button>
<input type="text" id="dirInput" class="toolbar-input hidden" placeholder="Working directory..." onblur="app.hideDirInput()">
</div>
</div>
<div class="toolbar-center">
<button class="btn-toolbar btn-sm btn-voice" id="voiceInputBtn" onclick="VoiceInput.toggle()"
title="Voice input (Ctrl+Shift+V)" aria-label="Start voice input" aria-pressed="false">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/>
<path d="M19 10v2a7 7 0 0 1-14 0v-2"/>
<line x1="12" y1="19" x2="12" y2="23"/>
<line x1="8" y1="23" x2="16" y2="23"/>
</svg>
</button>
</div>
<div class="toolbar-right">
<!-- Orchestrator button hidden until feature is ready -->
<!-- <button class="btn-toolbar btn-sm" onclick="app.toggleOrchestratorPanel()" title="Orchestrator Loop">&#x2699; Orchestrator</button> -->
<button class="btn-toolbar btn-sm btn-cron btn-cron--hidden" onclick="app.openCron()" title="Cron Jobs">&#x23F0; Cron</button>
<span class="version-display" id="versionDisplay" title="Codeman version">v0.0.0</span>
</div>
</footer>
<!-- Help Modal -->
<div class="modal" id="helpModal">
<div class="modal-backdrop" onclick="app.closeHelp()"></div>
<div class="modal-content">
<div class="modal-header">
<h3>Keyboard Shortcuts</h3>
<button class="modal-close" onclick="app.closeHelp()" aria-label="Close help">&times;</button>
</div>
<div class="modal-body">
<section class="shortcut-section">
<h4>Session</h4>
<div class="shortcuts-grid">
<div><kbd>Ctrl</kbd>+<kbd>W</kbd></div><div>Close Session</div>
<div><kbd>Ctrl/Cmd/Option</kbd>+<kbd>K</kbd></div><div>Find Open Session</div>
<div><kbd>Ctrl</kbd>+<kbd>Tab</kbd></div><div>Next Session</div>
<div><kbd>Alt/Option</kbd>+<kbd>[</kbd> / <kbd>Alt/Option</kbd>+<kbd>]</kbd></div><div>Previous / Next Session</div>
<div><kbd>Alt/Option</kbd>+<kbd>1-9</kbd></div><div>Switch to Tab N</div>
</div>
</section>
<section class="shortcut-section">
<h4>Tabs</h4>
<div class="shortcuts-grid">
<div><kbd>Ctrl</kbd>+<kbd>{</kbd></div><div>Move Active Tab Left</div>
<div><kbd>Ctrl</kbd>+<kbd>}</kbd></div><div>Move Active Tab Right</div>
<div><kbd>ArrowLeft</kbd></div><div>Focus Previous Tab</div>
<div><kbd>ArrowRight</kbd></div><div>Focus Next Tab</div>
<div><kbd>Home</kbd></div><div>Focus First Tab</div>
<div><kbd>End</kbd></div><div>Focus Last Tab</div>
<div><kbd>Enter</kbd> / <kbd>Space</kbd></div><div>Activate Focused Tab</div>
</div>
</section>
<section class="shortcut-section">
<h4>Terminal</h4>
<div class="shortcuts-grid">
<div><kbd>Ctrl</kbd>+<kbd>C</kbd></div><div>Copy Selection (interrupts when nothing is selected)</div>
<div><kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>C</kbd></div><div>Copy Selection</div>
<div><kbd>Ctrl</kbd>+<kbd>L</kbd></div><div>Clear Terminal</div>
<div><kbd>Ctrl</kbd>+<kbd>+</kbd></div><div>Increase Font</div>
<div><kbd>Ctrl</kbd>+<kbd>-</kbd></div><div>Decrease Font</div>
<div><kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>R</kbd></div><div>Restore Terminal Size</div>
<div><kbd>Shift</kbd>+<kbd>Enter</kbd></div><div>Insert Newline</div>
<div><kbd>Ctrl</kbd>+<kbd>Enter</kbd></div><div>Insert Newline</div>
<div><kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>V</kbd></div><div>Voice Input</div>
<div><kbd>Shift</kbd>+<kbd>Wheel</kbd></div><div>Scroll local history (when mouse passthrough is active)</div>
</div>
</section>
<section class="shortcut-section">
<h4>Panels</h4>
<div class="shortcuts-grid">
<div><kbd>Ctrl</kbd>+<kbd>?</kbd></div><div>Show Shortcuts</div>
<div><kbd>Alt/Option</kbd>+<kbd>?</kbd></div><div>Show Shortcuts</div>
<div><kbd>Escape</kbd></div><div>Close Panels</div>
</div>
</section>
</div>
</div>
</div>
<!-- Web Tab (dashboard URL) editor -->
<div class="modal" id="webviewModal">
<div class="modal-backdrop" onclick="app.closeWebviewModal()"></div>
<div class="modal-content">
<div class="modal-header">
<h3 id="webviewModalTitle">Add URL</h3>
<button class="modal-close" onclick="app.closeWebviewModal()" aria-label="Close URL editor">&times;</button>
</div>
<div class="modal-body">
<div class="form-row">
<label for="webviewName">Name</label>
<input type="text" id="webviewName" placeholder="Grafana" autocomplete="off" spellcheck="false">
</div>
<div class="form-row">
<label for="webviewUrl">URL</label>
<input type="text" id="webviewUrl" placeholder="http://100.70.56.18:4000/" autocomplete="off"
autocapitalize="off" spellcheck="false">
<span class="form-hint">
Reached from the Codeman server, so a tailnet or localhost address works even when
this browser cannot see it. Plain HTTP is fine: the dashboard is proxied through
Codeman, which is also what gets past dashboards that refuse to be embedded.
</span>
</div>
<div class="form-row">
<label for="webviewIcon">Icon</label>
<!-- Click to pick; the field stays editable so any emoji still works. -->
<div class="webview-icon-picker" id="webviewIconPicker" role="group" aria-label="Choose an icon"></div>
<input type="text" id="webviewIcon" placeholder="Or paste any emoji" maxlength="8" autocomplete="off">
</div>
<div class="form-row">
<label class="checkbox-row"><input type="checkbox" id="webviewSandboxed" checked> Open sandboxed</label>
<span class="form-hint">
Recommended. A proxied dashboard is served from Codeman's own address, so unchecking
this lets its JavaScript read this page and call the API that starts agents. Uncheck
only for a dashboard you fully trust, or one whose own login needs cookies. Also
uncheck it if Codeman itself sits behind a cookie-authenticated reverse proxy
(e.g. Cloudflare Access): a sandboxed frame carries no auth cookie, so its asset
and API requests bounce to the login provider and the page loads broken.
</span>
</div>
<div class="form-row">
<button class="btn-secondary" onclick="app.testWebviewUrl()">Test</button>
<span class="form-hint webview-probe-result" id="webviewProbeResult"></span>
</div>
</div>
<div class="form-actions webview-modal-actions">
<button class="btn-danger" id="webviewDeleteBtn" onclick="app.deleteWebview()">Delete</button>
<button class="btn-secondary" onclick="app.closeWebviewModal()">Cancel</button>
<button class="btn-primary" onclick="app.saveWebview()">Save</button>
</div>
</div>
</div>
<!-- Cron Jobs Modal -->
<div class="modal" id="cronModal">
<div class="modal-backdrop" onclick="app.closeCron()"></div>
<div class="modal-content modal-lg">
<div class="modal-header">
<h3>Cron Jobs</h3>
<button class="modal-close" onclick="app.closeCron()" aria-label="Close cron">&times;</button>
</div>
<div class="modal-body">
<p class="form-hint cron-modal-hint">Times use the server's local timezone.</p>
<div class="cron-toolbar">
<button class="btn-toolbar btn-primary" onclick="app.openCronJobForm()">+ New Job</button>
<button class="btn-toolbar" onclick="app.refreshCron()">Refresh</button>
</div>
<!-- Job list -->
<div id="cronJobList" class="cron-job-list"></div>
<!-- Create/Edit form (hidden until New/Edit) -->
<div id="cronJobForm" class="cron-job-form hidden">
<div class="cron-form-title" id="cronFormTitle">New Cron Job</div>
<input type="hidden" id="schJobId">
<div class="form-section-header">Basics</div>
<div class="form-row"><label>Name</label><input type="text" id="schName" placeholder="My nightly job"></div>
<div class="form-row"><label>Agent Type</label>
<select id="schAgentType" class="form-select" onchange="app.onCronAgentTypeChange()">
<option value="claude">Claude</option>
<option value="shell">Terminal / Shell</option>
<option value="opencode">OpenCode</option>
<option value="codex">Codex</option>
<option value="gemini">Gemini</option>
<option value="antigravity">Antigravity</option>
</select>
</div>
<div class="form-row"><label>Working Directory</label><input type="text" id="schWorkingDir" placeholder="/absolute/path"></div>
<div class="form-row hidden" id="schLaunchCommandRow"><label>Launch Command</label><input type="text" id="schLaunchCommand" placeholder="Optional — runs as the first command in the new shell"></div>
<div class="form-section-header">Prompt</div>
<div class="form-row"><label>Prompt Source</label>
<select id="schPromptMode" class="form-select" onchange="app.onCronPromptModeChange()">
<option value="inline_text">Inline text</option>
<option value="prompt_file_path">Prompt file path</option>
</select>
</div>
<div class="form-row" id="schPromptTextRow"><label>Prompt</label><textarea id="schPromptText" rows="4" placeholder="Prompt to send into the session"></textarea></div>
<div class="form-row hidden" id="schPromptFileRow"><label>Prompt File Path</label><input type="text" id="schPromptFilePath" placeholder="/absolute/path/to/prompt.md"></div>
<div class="form-row"><label>Input Mode</label>
<select id="schInputMode" class="form-select">
<option value="typed">Typed (via tmux)</option>
<option value="paste">Paste (direct)</option>
</select>
</div>
<div class="form-section-header">Schedule</div>
<div class="form-row"><label>Schedule Type</label>
<select id="schScheduleType" class="form-select" onchange="app.onCronScheduleTypeChange()">
<option value="once">Once</option>
<option value="interval">Interval</option>
<option value="daily">Daily</option>
<option value="weekly">Weekly</option>
</select>
</div>
<div class="form-row" id="schRunAtRow"><label>Run At</label><input type="datetime-local" id="schRunAt"></div>
<div class="form-row hidden" id="schIntervalRow"><label>Every (minutes)</label><input type="number" id="schIntervalMinutes" min="1" value="60"></div>
<div class="form-row hidden" id="schDailyRow"><label>Daily Time (HH:MM)</label><input type="time" id="schDailyTime"></div>
<div class="form-row hidden" id="schWeeklyDaysRow"><label>Weekdays</label>
<span id="schWeeklyDays" class="cron-weekdays">
<label><input type="checkbox" value="0">Sun</label>
<label><input type="checkbox" value="1">Mon</label>
<label><input type="checkbox" value="2">Tue</label>
<label><input type="checkbox" value="3">Wed</label>
<label><input type="checkbox" value="4">Thu</label>
<label><input type="checkbox" value="5">Fri</label>
<label><input type="checkbox" value="6">Sat</label>
</span>
</div>
<div class="form-row hidden" id="schWeeklyTimeRow"><label>Weekly Time (HH:MM)</label><input type="time" id="schWeeklyTime"></div>
<div class="form-section-header">Options</div>
<div class="form-row"><label>On auto-run, if same agent running</label>
<select id="schConcurrencyPolicy" class="form-select">
<option value="warn_only">Run anyway</option>
<option value="skip_if_same_agent_running">Skip this run</option>
</select>
</div>
<div class="form-row form-row-switch cron-switch-row">
<div class="cron-switch-text">
<span class="cron-switch-label">Auto-close previous run's session</span>
<span class="cron-switch-desc">Recurring schedules only: close the prior run's still-open session before the next run fires</span>
</div>
<label class="switch switch-sm"><input type="checkbox" id="schAutoClosePrev" checked><span class="slider"></span></label>
</div>
<div class="form-row form-row-switch cron-switch-row">
<div class="cron-switch-text">
<span class="cron-switch-label">Enabled</span>
<span class="cron-switch-desc">Off keeps the job saved but skips its schedule</span>
</div>
<label class="switch switch-sm"><input type="checkbox" id="schEnabled" checked><span class="slider"></span></label>
</div>
<div class="form-row"><label>Notes</label><input type="text" id="schNotes" placeholder="Optional"></div>
<div id="cronFormError" class="form-hint cron-form-error"></div>
<div class="cron-form-actions">
<button class="btn-toolbar" onclick="app.cancelCronJobForm()">Cancel</button>
<button class="btn-toolbar btn-primary" onclick="app.saveCronJob()">Save</button>
</div>
</div>
</div>
</div>
</div>
<!-- Monitor Panel (Tmux Sessions + Background Tasks) -->
<div class="monitor-panel" id="monitorPanel">
<div class="monitor-panel-header" id="monitorPanelHeader">
<div class="monitor-panel-title">Monitor</div>
<div class="monitor-panel-actions">
<button class="btn-toolbar btn-sm btn-danger" onclick="app.killAllMuxSessions()" title="Kill all sessions and their tmux processes">Kill All</button>
<button class="btn-icon-sm" onclick="app.reconcileMuxSessions()" title="Refresh tmux sessions">&#x21BB;</button>
<button class="btn-icon-sm" onclick="app.toggleMonitorDetach()" title="Detach panel" id="monitorDetachBtn">&#x29C9;</button>
<button class="btn-icon-sm" onclick="app.toggleMonitorPanel()" title="Toggle panel" id="monitorToggleBtn">&#x25B2;</button>
<button class="btn-icon-sm" onclick="app.closeMonitor()" title="Close monitor">&times;</button>
</div>
</div>
<div class="monitor-resize-handle" id="monitorResizeHandle"></div>
<div class="monitor-panel-body" id="monitorTabContent">
<div class="monitor-section">
<div class="monitor-section-header">
<span>Tmux Sessions</span>
</div>
<div class="monitor-section-body" id="muxSessionsBody">
<div class="monitor-empty">No mux sessions</div>
</div>
</div>
<div class="monitor-section" id="monitorPlanAgentsSection" style="display: none;">
<div class="monitor-section-header">
<span>Plan Wizard Agents</span>
<span class="task-stats" id="monitorPlanAgentStats">0 active</span>
<button class="btn-toolbar btn-sm btn-danger" onclick="app.cancelPlanFromMonitor()" title="Cancel plan generation">Cancel</button>
</div>
<div class="monitor-section-body" id="monitorPlanAgentsBody">
<div class="monitor-empty">No plan agents</div>
</div>
</div>
<div class="monitor-section" id="monitorSubagentsSection">
<div class="monitor-section-header">
<span>Background Agents</span>
<span class="task-stats" id="monitorSubagentStats">0 tracked</span>
<button class="btn-toolbar btn-sm btn-danger" onclick="app.clearAllSubagents()" title="Clear all">Clear</button>
</div>
<div class="monitor-section-body" id="monitorSubagentsBody">
<div class="monitor-empty">No background agents</div>
</div>
</div>
<div class="monitor-section" id="backgroundTasksSection" style="display: none;">
<div class="monitor-section-header">
<span>Background Tasks</span>
<span class="task-stats" id="taskPanelStats">0 running</span>
</div>
<div class="monitor-section-body" id="backgroundTasksBody">
</div>
</div>
</div>
</div>
<!-- Subagents Panel (separate from Monitor) -->
<div class="subagents-panel" id="subagentsPanel">
<div class="subagents-panel-header" id="subagentsPanelHeader">
<div class="subagents-panel-title">
Subagents <span id="subagentCountBadge" class="subagent-badge"></span>
</div>
<div class="subagents-panel-actions">
<button class="btn-toolbar btn-sm btn-danger" onclick="app.clearAllSubagents()" title="Clear all tracked subagents">Clear All</button>
<button class="btn-icon-sm" onclick="app.toggleSubagentsDetach()" title="Detach panel" id="subagentsDetachBtn">&#x29C9;</button>
<button class="btn-icon-sm" onclick="app.toggleSubagentsPanel()" title="Toggle panel" id="subagentsToggleBtn">&#x25B2;</button>
<button class="btn-icon-sm" onclick="app.closeSubagentsPanel()" title="Close">&times;</button>
</div>
</div>
<div class="subagents-resize-handle" id="subagentsResizeHandle"></div>
<div class="subagents-panel-body" id="subagentsTabContent">
<div class="subagent-container">
<div class="subagent-list" id="subagentList">
<div class="subagent-empty">No background agents detected</div>
</div>
<div class="subagent-detail" id="subagentDetail">
<div class="subagent-empty">Select an agent to view details</div>
</div>
</div>
</div>
</div>
<!-- Ultracode / Workflow agents master-detail panel (opt-in via showUltracodeAgents) -->
<div class="subagents-panel ultracode-agents-panel hidden" id="ultracodeAgentsPanel">
<div class="subagents-panel-header">
<div class="subagents-panel-title">
Ultracode Agents <span id="ultracodeCountBadge" class="subagent-badge"></span>
</div>
<div class="subagents-panel-actions">
<button class="btn-icon-sm" onclick="app.toggleUltracodeAgentsPanel()" title="Toggle panel">&#x25B2;</button>
<button class="btn-icon-sm" onclick="app.closeUltracodeAgentsPanel()" title="Close">&times;</button>
</div>
</div>
<div class="subagents-panel-body">
<div class="subagent-container">
<div class="subagent-list ultracode-run-list" id="ultracodeRunList">
<div class="subagent-empty">No ultracode runs detected</div>
</div>
<div class="subagent-detail ultracode-agent-grid" id="ultracodeAgentGrid">
<div class="subagent-empty">Select a run to view its agents</div>
</div>
</div>
</div>
</div>
<!-- Session Options Modal (includes Respawn Settings) -->
<div class="modal" id="sessionOptionsModal">
<div class="modal-backdrop" onclick="app.closeSessionOptions()"></div>
<div class="modal-content">
<div class="modal-header">
<h3>Session Options</h3>
<button class="modal-close" onclick="app.closeSessionOptions()" aria-label="Close session options">&times;</button>
</div>
<!-- Modal Tabs -->
<div class="modal-tabs">
<button class="modal-tab-btn active" data-tab="respawn" data-claude-only onclick="app.switchOptionsTab('respawn')">Respawn</button>
<button class="modal-tab-btn" data-tab="context" onclick="app.switchOptionsTab('context')">Context</button>
<button class="modal-tab-btn" data-tab="ralph" data-claude-only onclick="app.switchOptionsTab('ralph')">Ralph / Todo</button>
<button class="modal-tab-btn" data-tab="summary" onclick="app.switchOptionsTab('summary')">Summary</button>
</div>
<div class="modal-body">
<!-- Respawn Tab Content -->
<div class="modal-tab-content" id="respawn-tab">
<!-- Respawn Settings Section -->
<div class="session-respawn-section" id="sessionRespawnSection">
<div class="auto-resume-box">
<label class="checkbox-inline">
<input type="checkbox" id="modalAutoResumeEnabled" onchange="app.autoSaveAutoResume()">
<span>Auto-resume when usage limit resets</span>
</label>
<span class="auto-resume-status" id="autoResumeStatus"></span>
<span class="form-hint">If Claude pauses on a usage limit ("limit reached &middot; resets 3pm"), Codeman waits for the reset time and automatically continues the work. Independent of the respawn loop below.</span>
</div>
<div class="respawn-loop-box">
<div class="respawn-loop-title">Respawn loop</div>
<span class="form-hint respawn-loop-hint">One autonomous work cycle: whenever Claude goes idle, Codeman sends the update prompt, optionally runs /clear + /init, and kickstarts the next round &mdash; repeating for the chosen duration. All settings below belong to this loop; configure them, then press Enable.</span>
<div class="form-row">
<label>Duration</label>
<div class="duration-presets">
<button type="button" class="duration-preset-btn" data-minutes="30" onclick="app.selectDurationPreset('30')">30m</button>
<button type="button" class="duration-preset-btn" data-minutes="60" onclick="app.selectDurationPreset('60')">1h</button>
<button type="button" class="duration-preset-btn" data-minutes="120" onclick="app.selectDurationPreset('120')">2h</button>
<button type="button" class="duration-preset-btn" data-minutes="240" onclick="app.selectDurationPreset('240')">4h</button>
<button type="button" class="duration-preset-btn" data-minutes="480" onclick="app.selectDurationPreset('480')">8h</button>
<button type="button" class="duration-preset-btn active" data-minutes="" onclick="app.selectDurationPreset('')">∞</button>
<div class="duration-custom">
<button type="button" class="duration-preset-btn" data-minutes="custom" onclick="app.selectDurationPreset('custom')">Custom</button>
<div class="input-suffix input-suffix-sm duration-custom-input">
<input type="number" id="modalRespawnDuration" value="" min="1" placeholder="min">
<span>min</span>
</div>
</div>
</div>
</div>
<div class="form-section-header">Presets</div>
<div class="form-row">
<label>Load Preset</label>
<div class="preset-selector">
<select id="respawnPresetSelect" onchange="app.updatePresetDescription()">
<option value="">-- Select preset --</option>
<optgroup label="Built-in" id="builtinPresetsGroup">
</optgroup>
<optgroup label="Custom" id="customPresetsGroup">
</optgroup>
</select>
<button type="button" class="btn btn-sm" onclick="app.loadRespawnPreset()" title="Apply preset">Apply</button>
<button type="button" class="btn btn-sm btn-secondary" onclick="app.saveCurrentAsPreset()" title="Save current config as preset">Save As...</button>
</div>
<p class="form-hint" id="presetDescriptionHint"></p>
</div>
<div class="form-section-header">Cycle Steps</div>
<div class="form-row">
<label>1. Update Prompt</label>
<textarea id="modalRespawnPrompt" rows="1" placeholder="Prompt to send when idle" onchange="app.autoSaveRespawnConfig()" style="resize: vertical; min-height: 30px;">update all the docs and CLAUDE.md</textarea>
</div>
<div class="respawn-options-row" style="margin: 8px 0;">
<label class="checkbox-inline">
<input type="checkbox" id="modalRespawnSendClear" checked onchange="app.autoSaveRespawnConfig()">
<span>2. Send /clear</span>
</label>
<label class="checkbox-inline">
<input type="checkbox" id="modalRespawnSendInit" checked onchange="app.autoSaveRespawnConfig()">
<span>3. Send /init</span>
</label>
<label class="checkbox-inline" title="Presses Enter for plan approvals and default question options">
<input type="checkbox" id="modalRespawnAutoAccept" checked onchange="app.autoSaveRespawnConfig()">
<span>Auto-accept prompts</span>
</label>
</div>
<div class="form-row">
<label>4. Kickstart Prompt</label>
<textarea id="modalRespawnKickstart" rows="1" placeholder="Optional: prompt if /init doesn't trigger work" onchange="app.autoSaveRespawnConfig()" style="resize: vertical; min-height: 30px;"></textarea>
<span class="form-hint">Sent only when /init completes but Claude stays idle &middot; Auto-accept presses Enter for plan approvals and default options</span>
</div>
<div class="respawn-header">
<div class="session-respawn-status" id="sessionRespawnStatus">
<span class="respawn-status-indicator"></span>
<span class="respawn-status-text">Not active</span>
</div>
<div class="respawn-actions">
<button class="btn-toolbar btn-success btn-sm" onclick="app.enableRespawnFromModal()" id="modalEnableRespawnBtn">Enable</button>
<button class="btn-toolbar btn-danger btn-sm" onclick="app.stopRespawnFromModal()" id="modalStopRespawnBtn" style="display: none;">Stop</button>
</div>
</div>
</div><!-- End respawn-loop-box -->
</div>
</div><!-- End respawn-tab -->
<!-- Context Management Tab Content -->
<div class="modal-tab-content hidden" id="context-tab">
<div class="form-section-header">Appearance</div>
<div class="form-row">
<label>Session Name</label>
<div style="display: flex; align-items: center; gap: 4px;">
<span id="modalSessionPrefix" style="color: var(--text-muted); white-space: nowrap; display: none;"></span>
<input type="text" id="modalSessionName" maxlength="128" placeholder="Auto (directory name)" onblur="app.saveSessionName()" style="flex: 1;">
</div>
<span class="form-hint">Custom name shown in the tab (right-click tab to rename inline)</span>
</div>
<div class="form-row">
<label>Session Color</label>
<div class="color-picker" id="sessionColorPicker">
<button type="button" class="color-swatch" data-color="default" title="Default">
<span class="swatch-inner" style="background: var(--bg-hover)"></span>
</button>
<button type="button" class="color-swatch" data-color="red" title="Red">
<span class="swatch-inner" style="background: var(--session-red)"></span>
</button>
<button type="button" class="color-swatch" data-color="orange" title="Orange">
<span class="swatch-inner" style="background: var(--session-orange)"></span>
</button>
<button type="button" class="color-swatch" data-color="yellow" title="Yellow">
<span class="swatch-inner" style="background: var(--session-yellow)"></span>
</button>
<button type="button" class="color-swatch" data-color="green" title="Green">
<span class="swatch-inner" style="background: var(--session-green)"></span>
</button>
<button type="button" class="color-swatch" data-color="blue" title="Blue">
<span class="swatch-inner" style="background: var(--session-blue)"></span>
</button>
<button type="button" class="color-swatch" data-color="purple" title="Purple">
<span class="swatch-inner" style="background: var(--session-purple)"></span>
</button>
<button type="button" class="color-swatch" data-color="pink" title="Pink">
<span class="swatch-inner" style="background: var(--session-pink)"></span>
</button>
</div>
</div>
<div class="form-section-header">Token Management</div>
<div class="context-settings-grid">
<div class="context-setting">
<div class="context-setting-header">
<label class="checkbox-inline">
<input type="checkbox" id="modalAutoCompactEnabled" onchange="app.autoSaveAutoCompact()">
<span>Auto-Compact</span>
</label>
<div class="input-suffix input-suffix-sm">
<input type="number" id="modalAutoCompactThreshold" value="110000" min="10000" max="500000" step="10000" onchange="app.autoSaveAutoCompact()">
<span>tokens</span>
</div>
</div>
<input type="text" id="modalAutoCompactPrompt" value="" placeholder="Focus prompt (optional)" onchange="app.autoSaveAutoCompact()">
<span class="form-hint">Summarizes context while preserving key information</span>
</div>
<div class="context-setting">
<div class="context-setting-header">
<label class="checkbox-inline">
<input type="checkbox" id="modalAutoClearEnabled" onchange="app.autoSaveAutoClear()">
<span>Auto-Clear</span>
</label>
<div class="input-suffix input-suffix-sm">
<input type="number" id="modalAutoClearThreshold" value="140000" min="10000" max="500000" step="10000" onchange="app.autoSaveAutoClear()">
<span>tokens</span>
</div>
</div>
<span class="form-hint">Full context reset at threshold (use higher than compact)</span>
</div>
</div>
<div class="form-section-header">File Watching</div>
<div class="form-row form-row-switch">
<label>Image Watcher</label>
<label class="switch">
<input type="checkbox" id="modalImageWatcherEnabled" checked onchange="app.toggleSessionImageWatcher()">
<span class="slider"></span>
</label>
<span class="form-hint">Auto-popup new images (screenshots, generated files) in this session's directory</span>
</div>
<div class="form-section-header">Performance</div>
<div class="form-row form-row-switch">
<label>Flicker Filter</label>
<label class="switch">
<input type="checkbox" id="modalFlickerFilterEnabled" onchange="app.toggleFlickerFilter()">
<span class="slider"></span>
</label>
<span class="form-hint">Buffers output after screen clears to reduce Ink UI flickering (adds ~50ms latency)</span>
</div>
</div><!-- End context-tab -->
<!-- Ralph Wiggum Tab Content -->
<div class="modal-tab-content hidden" id="ralph-tab">
<div class="form-row form-row-switch">
<label>Enable Tracker</label>
<label class="switch">
<input type="checkbox" id="modalRalphEnabled">
<span class="slider"></span>
</label>
<span class="form-hint">Auto-detects loops and todos in Claude output</span>
</div>
<div class="form-row">
<label>Completion Phrase</label>
<input type="text" id="modalRalphPhrase" placeholder="e.g., COMPLETE, DONE, FINISHED">
<span class="form-hint">Phrase Claude outputs when loop is complete (without &lt;promise&gt; tags)</span>
</div>
<div class="form-section-header">Limits</div>
<div class="ralph-limits-grid">
<div class="form-col">
<label>Max Iterations</label>
<input type="number" id="modalRalphMaxIterations" value="0" min="0" max="1000">
<span class="form-hint">0 = unlimited</span>
</div>
<div class="form-col">
<label>Max Todos</label>
<input type="number" id="modalRalphMaxTodos" value="50" min="1" max="500">
<span class="form-hint">Per session</span>
</div>
<div class="form-col">
<label>Todo Expiration</label>
<div class="input-suffix input-suffix-sm">
<input type="number" id="modalRalphTodoExpiration" value="60" min="1" max="1440">
<span>min</span>
</div>
</div>
</div>
<div class="ralph-config-actions">
<button class="btn-toolbar btn-primary" onclick="app.saveRalphConfig()">Save Ralph Config</button>
</div>
</div><!-- End ralph-tab -->
<!-- Summary Tab Content -->
<div class="modal-tab-content hidden" id="summary-tab">
<!-- Filter Buttons -->
<div class="run-summary-filters">
<button class="filter-btn active" data-filter="all" onclick="app.filterRunSummary('all')">All</button>
<button class="filter-btn" data-filter="errors" onclick="app.filterRunSummary('errors')">Errors</button>
<button class="filter-btn" data-filter="warnings" onclick="app.filterRunSummary('warnings')">Warnings</button>
<button class="filter-btn" data-filter="respawn" onclick="app.filterRunSummary('respawn')">Respawn</button>
<button class="filter-btn" data-filter="idle" onclick="app.filterRunSummary('idle')">Idle/Working</button>
</div>
<!-- Timeline -->
<div class="run-summary-timeline" id="runSummaryTimeline">
<p class="empty-message">Select a session to view summary</p>
</div>
<!-- Footer with actions -->
<div class="run-summary-footer">
<span class="run-summary-session-info" id="runSummarySessionInfo"></span>
<div class="run-summary-actions">
<label class="auto-refresh-label">
<input type="checkbox" id="runSummaryAutoRefresh" onchange="app.toggleRunSummaryAutoRefresh()">
Auto-refresh
</label>
<button class="btn-toolbar btn-sm" onclick="app.refreshRunSummary()" title="Refresh summary">&#x21BB; Refresh</button>
<button class="btn-toolbar btn-sm" onclick="app.exportRunSummary('json')" title="Export as JSON">Export JSON</button>
<button class="btn-toolbar btn-sm" onclick="app.exportRunSummary('md')" title="Export as Markdown">Export MD</button>
</div>
</div>
</div><!-- End summary-tab -->
</div>
</div>
</div>
<!-- Close Session Confirmation Modal -->
<div class="modal" id="closeConfirmModal">
<div class="modal-backdrop" onclick="app.cancelCloseSession()"></div>
<div class="modal-content modal-sm">
<div class="modal-header">
<h3>Close Session</h3>
<button class="modal-close" onclick="app.cancelCloseSession()" aria-label="Cancel close session">&times;</button>
</div>
<div class="modal-body">
<p class="modal-session-name" id="closeConfirmSessionName"></p>
</div>
<div class="close-options">
<button class="close-option" onclick="app.confirmCloseSession(false)">
<span class="close-option-title">Remove Tab</span>
<span class="close-option-desc">Tmux session keeps running in background</span>
</button>
<button class="close-option close-option-danger" onclick="app.confirmCloseSession(true)">
<span class="close-option-title" id="closeConfirmKillTitle">Kill Tmux & Claude Code</span>
<span class="close-option-desc">Terminate the session completely</span>
</button>
</div>
<div class="modal-footer-cancel">
<button class="btn-toolbar btn-sm" onclick="app.cancelCloseSession()">Cancel</button>
</div>
</div>
</div>
<!-- App Settings Modal -->
<div class="modal" id="appSettingsModal">
<div class="modal-backdrop" onclick="app.closeAppSettings()"></div>
<div class="modal-content modal-lg set-shell">
<div class="modal-header set-shell-head">
<h3>Settings</h3>
<!-- Close first in the DOM so the focus trap still lands on it, not on
Save; row-reverse keeps Save to the left of it on phones. -->
<div class="set-head-actions">
<button class="modal-close" onclick="app.closeAppSettings()" aria-label="Close app settings">&times;</button>
<button class="set-head-save" onclick="app.saveAppSettings()">Save</button>
</div>
</div>
<div class="set-body">
<!-- Left rail: a table of contents over the single scrolling document on
the right. On phones it collapses and #appSettingsJump takes over. -->
<nav class="set-rail" id="appSettingsRail" aria-label="Settings sections">
<div class="set-search">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/></svg>
<input type="search" id="appSettingsSearch" placeholder="Search settings" autocomplete="off" aria-label="Search settings">
</div>
<div class="set-rail-items" id="appSettingsRailItems">
<button type="button" class="set-rail-item active" data-section="settings-system">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.6a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9c.26.6.86 1 1.51 1H21a2 2 0 0 1 0 4h-.09c-.65 0-1.25.4-1.51 1z"/></svg>
<span>System</span>
</button>
<button type="button" class="set-rail-item" data-section="settings-terminal">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 17l6-6-6-6"/><path d="M12 19h8"/></svg>
<span>Terminal &amp; Input</span>
</button>
<button type="button" class="set-rail-item" data-section="settings-layout">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18M9 21V9"/></svg>
<span>Header &amp; Panels</span>
</button>
<button type="button" class="set-rail-item" data-section="settings-appearance">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M12 3a9 9 0 0 0 0 18 4.5 4.5 0 0 0 0-9 4.5 4.5 0 0 1 0-9z"/></svg>
<span>Appearance</span>
</button>
<button type="button" class="set-rail-item" data-section="settings-models">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3l8 4.5v9L12 21l-8-4.5v-9L12 3z"/><path d="M12 12l8-4.5M12 12v9M12 12L4 7.5"/></svg>
<span>Models</span>
</button>
<button type="button" class="set-rail-item" data-section="settings-clis">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8 3H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h2"/><path d="M16 3h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-2"/><circle cx="12" cy="12" r="2.5"/></svg>
<span>Agents &amp; CLIs</span>
</button>
<button type="button" class="set-rail-item" data-section="settings-notifications">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 8a6 6 0 1 0-12 0c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.7 21a2 2 0 0 1-3.4 0"/></svg>
<span>Notifications</span>
</button>
<button type="button" class="set-rail-item" data-section="settings-voice">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="9" y="2" width="6" height="12" rx="3"/><path d="M5 11a7 7 0 0 0 14 0"/><path d="M12 18v4"/></svg>
<span>Voice</span>
</button>
<button type="button" class="set-rail-item" data-section="settings-shortcuts">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="2" y="5" width="20" height="14" rx="2"/><path d="M6 9h.01M10 9h.01M14 9h.01M18 9h.01M6 13h.01M18 13h.01M9 13h6"/></svg>
<span>Shortcuts</span>
</button>
</div>
<p class="set-rail-foot">Settings marked <b>device</b> stay on this browser. The rest sync to every device.</p>
</nav>
<div class="set-doc" id="appSettingsDoc">
<!-- Phone only: names the section you are in and opens the jump list -->
<button type="button" class="set-jump" id="appSettingsJump" aria-haspopup="menu" aria-expanded="false">
<svg class="set-jump-ico" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 17l6-6-6-6"/><path d="M12 19h8"/></svg>
<span class="set-jump-label">Terminal &amp; Input</span>
<svg class="set-jump-chev" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.3" stroke-linecap="round" aria-hidden="true"><path d="M6 9l6 6 6-6"/></svg>
</button>
<div class="set-jump-veil" id="appSettingsJumpVeil"></div>
<div class="set-jump-menu" id="appSettingsJumpMenu" role="menu"></div>
<p class="set-empty" id="appSettingsSearchEmpty" hidden>No settings match that search.</p>
<!-- ══ System ═══════════════════════════════════════════════════ -->
<section class="set-section" id="settings-system" data-label="System">
<div class="set-section-head">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.6a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9c.26.6.86 1 1.51 1H21a2 2 0 0 1 0 4h-.09c-.65 0-1.25.4-1.51 1z"/></svg>
<h2>System</h2>
</div>
<p class="set-section-blurb">What this install is running, plus paths, automation and remote access.</p>
<div class="set-group">
<div class="set-group-head"><h4>Updates</h4></div>
<div class="set-group-body">
<div class="set-row" data-search="version current release">
<div class="set-row-text"><span class="set-row-label">Current version</span></div>
<span class="set-static set-static-strong mono" id="updateCurrentVersion">&mdash;</span>
</div>
<div class="set-row" id="updateCheckRow" data-search="check for updates github release">
<div class="set-row-text">
<span class="set-row-label">Check for updates</span>
<span class="set-row-desc">Looks at GitHub for a newer Codeman release.</span>
</div>
<button class="btn-toolbar btn-sm" id="updateCheckBtn" onclick="app.checkForUpdate()">Check now</button>
</div>
<div id="updateResult" class="set-note" style="display:none"></div>
<div class="set-row" id="updateActionRow" style="display:none">
<div class="set-row-text"><span class="set-row-label" id="updateActionLabel">Update available</span></div>
<button class="btn-toolbar btn-sm btn-primary" id="updateNowBtn" onclick="app.startSelfUpdate()">Update now</button>
</div>
<div id="updateNotes" class="set-notes" style="display:none"></div>
<div id="updateProgress" class="set-progress" style="display:none"></div>
</div>
</div>
<div class="set-group">
<div class="set-group-head"><h4>Paths</h4><span class="set-scope">synced</span></div>
<div class="set-group-body">
<div class="set-row has-field" data-search="claude.md template path new cases">
<div class="set-row-text">
<span class="set-row-label">CLAUDE.md template</span>
<span class="set-row-desc">Used when creating new cases. Leave empty for the built-in template.</span>
</div>
<input type="text" id="appSettingsClaudeMdPath" class="set-input" placeholder="/path/to/CLAUDE.md template">
</div>
<div class="set-row has-field" data-search="default working directory">
<div class="set-row-text">
<span class="set-row-label">Default working directory</span>
<span class="set-row-desc">Starting directory for new sessions.</span>
</div>
<input type="text" id="appSettingsDefaultDir" class="set-input" placeholder="~/projects">
</div>
</div>
</div>
<div class="set-group">
<div class="set-group-head"><h4>Automation</h4><span class="set-scope">synced</span></div>
<div class="set-group-body">
<div class="set-row" data-search="image watcher popup screenshots">
<div class="set-row-text">
<span class="set-row-label">Image watcher</span>
<span class="set-row-desc">Detect and pop up new images found in session directories.</span>
</div>
<label class="switch switch-sm"><input type="checkbox" id="appSettingsImageWatcherEnabled"><span class="slider"></span></label>
</div>
</div>
</div>
<div class="set-group">
<div class="set-group-head"><h4>Remote access</h4><span class="set-scope">synced</span></div>
<div class="set-group-body">
<div class="set-row" data-search="cloudflare tunnel remote access url">
<div class="set-row-text">
<span class="set-row-label">Cloudflare Tunnel</span>
<span class="set-row-desc">Expose Codeman on a public URL. Set CODEMAN_PASSWORD first.</span>
</div>
<label class="switch switch-sm"><input type="checkbox" id="appSettingsTunnelEnabled"><span class="slider"></span></label>
</div>
<div class="set-row" id="tunnelUrlRow" style="display:none">
<div class="set-row-text"><span class="set-row-label">Tunnel URL</span></div>
<div class="set-row-actions">
<span id="tunnelUrlDisplay" class="set-copy" title="Click to copy"></span>
<button class="btn-icon-sm" id="tunnelQrBtn" onclick="app.showTunnelQR()" title="Show QR code">&#x229E;</button>
</div>
</div>
<div class="set-row" id="tunnelUploadUrlRow" style="display:none">
<div class="set-row-text"><span class="set-row-label">Upload URL</span></div>
<span id="tunnelUploadUrlDisplay" class="set-copy" title="Click to copy"></span>
</div>
</div>
</div>
</section>
<!-- ══ Terminal &amp; Input ══════════════════════════════════════ -->
<section class="set-section" id="settings-terminal" data-label="Terminal &amp; Input">
<div class="set-section-head">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 17l6-6-6-6"/><path d="M12 19h8"/></svg>
<h2>Terminal &amp; Input</h2>
</div>
<p class="set-section-blurb">How keystrokes reach the agent, and how the terminal draws.</p>
<div class="set-group">
<div class="set-group-head"><h4>Typing</h4><span class="set-scope">device</span></div>
<div class="set-group-body">
<div class="set-row" data-search="local echo typing latency mobile">
<div class="set-row-text">
<span class="set-row-label">Local Echo</span>
<span class="set-row-desc">Paint keystrokes instantly instead of waiting for the round trip. Recommended on phones and high-latency links.</span>
</div>
<label class="switch switch-sm"><input type="checkbox" id="appSettingsLocalEcho"><span class="slider"></span></label>
</div>
<div class="set-row" data-search="cjk input ime chinese japanese korean">
<div class="set-row-text">
<span class="set-row-label">CJK Input</span>
<span class="set-row-desc">Dedicated IME field for Chinese, Japanese and Korean composition.</span>
</div>
<label class="switch switch-sm"><input type="checkbox" id="appSettingsCjkInput"><span class="slider"></span></label>
</div>
<div class="set-row" data-search="extended keyboard bar tab esc arrows phone">
<div class="set-row-text">
<span class="set-row-label">Extended Keyboard Bar <span class="set-tag">phone</span></span>
<span class="set-row-desc">Extra phone keys: Tab, Shift+Tab, Esc, arrows, Ctrl+O.</span>
</div>
<label class="switch switch-sm"><input type="checkbox" id="appSettingsExtendedKeyboardBar"><span class="slider"></span></label>
</div>
</div>
</div>
<div class="set-group">
<div class="set-group-head"><h4>Scrolling &amp; rendering</h4></div>
<div class="set-group-body">
<div class="set-row" data-search="wheel scroll local history scrollback transcript">
<div class="set-row-text">
<span class="set-row-label">Wheel Scrolls Local History <span class="set-scope">device</span></span>
<span class="set-row-desc">Plain wheel or two-finger swipe pages the terminal's own scrollback instead of the CLI transcript. Shift+wheel always reaches local scrollback.</span>
</div>
<label class="switch switch-sm"><input type="checkbox" id="appSettingsTerminalWheelLocal"><span class="slider"></span></label>
</div>
<div class="set-row" id="appSettingsWebglRendererItem" data-search="webgl renderer gpu terminal">
<div class="set-row-text">
<span class="set-row-label">WebGL Renderer <span class="set-tag">desktop</span> <span class="set-scope">device</span></span>
<span class="set-row-desc">GPU-accelerated terminal drawing. Codeman falls back to the DOM renderer after repeated GPU stalls.</span>
</div>
<label class="switch switch-sm"><input type="checkbox" id="appSettingsWebglRenderer"><span class="slider"></span></label>
</div>
<div class="set-row" id="appSettingsGestureControlItem" data-search="gesture control camera hand tracking">
<div class="set-row-text">
<span class="set-row-label">Gesture Control <span class="set-tag set-tag-beta">beta</span> <span class="set-scope">synced</span></span>
<span class="set-row-desc">Camera hand-tracking overlay. Applied on reload; the instance must run with CODEMAN_GESTURE=1.</span>
</div>
<label class="switch switch-sm"><input type="checkbox" id="appSettingsGestureControl"><span class="slider"></span></label>
</div>
</div>
</div>
</section>
<!-- ══ Header &amp; Panels ═══════════════════════════════════════ -->
<section class="set-section" id="settings-layout" data-label="Header &amp; Panels">
<div class="set-section-head">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18M9 21V9"/></svg>
<h2>Header &amp; Panels</h2>
</div>
<p class="set-section-blurb">Pick the buttons and panels you actually use. Everything else stays out of the way.</p>
<!-- Live preview: a scale model of the app that redraws from the
chips below on every change, so "what does this add" is answered
in place instead of after a save. Icons are CLONED from the
chips (settings-ui.js: _syncLayoutPreview), so each icon has
exactly one copy in this file and the two can never drift. -->
<div class="set-preview" id="appSettingsLayoutPreview">
<div class="set-preview-head">
<span class="set-preview-title">Live preview</span>
<span class="set-preview-hint">Updates as you pick, before you save.</span>
</div>
<div class="set-preview-frame" data-i18n-skip>
<div class="set-preview-bar">
<span class="set-preview-logo">C</span>
<span class="set-preview-tab">w1-codeman</span>
<span class="set-preview-tab set-preview-tab-dim">w2-docs</span>
<span class="set-preview-gap"></span>
<span class="set-preview-btns" id="appSettingsPreviewHeader"></span>
<span class="set-preview-btn set-preview-btn-fixed" title="App Settings"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg></span>
</div>
<div class="set-preview-stage">
<div class="set-preview-term"><span class="set-preview-caret">&#10095;</span> npm run dev</div>
<div class="set-preview-panels" id="appSettingsPreviewPanels"></div>
<div class="set-preview-floats" id="appSettingsPreviewFloats"></div>
</div>
<div class="set-preview-tray">
<span class="set-preview-run">Run</span>
<span class="set-preview-tools" id="appSettingsPreviewToolbar"></span>
</div>
</div>
<p class="set-preview-empty" id="appSettingsPreviewEmpty" hidden>Nothing extra switched on: header keeps the gear only.</p>
</div>
<div class="set-group">
<div class="set-group-head"><h4>Header buttons</h4><span class="set-scope">device</span></div>
<p class="set-group-hint">Tap to show a control in the header. Multi-monitor is the one entry here that syncs across devices.</p>
<div class="set-group-body">
<div class="set-chips" data-search="header buttons plan usage font stats lifecycle response file viewer attachments monitor session away cron redraw">
<label class="set-chip" data-preview="header" data-preview-order="1" data-preview-text="A+"><input type="checkbox" id="appSettingsShowFontControls"><svg class="set-chip-ico" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 20 10 5l6 15"/><path d="M6.5 15h7"/><path d="M18 12h4M20 10v4"/></svg><span>Font Size</span></label>
<label class="set-chip" data-preview="header" data-preview-order="2" data-preview-text="CPU 12%"><input type="checkbox" id="appSettingsShowSystemStats" checked><svg class="set-chip-ico" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 12h4l2.5-7 4 14L16 12h5"/></svg><span>System Stats</span></label>
<label class="set-chip" data-preview="header" data-preview-order="3"><input type="checkbox" id="appSettingsShowRedrawButton"><svg class="set-chip-ico" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="1 4 1 10 7 10"/><polyline points="23 20 23 14 17 14"/><path d="M20.49 9A9 9 0 0 0 5.64 5.64L1 10m22 4l-4.64 4.36A9 9 0 0 1 3.51 15"/></svg><span>Redraw Terminal</span></label>
<label class="set-chip" data-preview="header" data-preview-order="4"><input type="checkbox" id="appSettingsShowResponseViewer"><svg class="set-chip-ico" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg><span>Response Viewer</span></label>
<label class="set-chip" data-preview="header" data-preview-order="5"><input type="checkbox" id="appSettingsShowAwayDigestButton"><svg class="set-chip-ico" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8 6h13"/><path d="M8 12h13"/><path d="M8 18h13"/><path d="M3 6h.01"/><path d="M3 12h.01"/><path d="M3 18h.01"/></svg><span>Away Digest</span></label>
<label class="set-chip" data-preview="header" data-preview-order="6"><input type="checkbox" id="appSettingsShowSessionButton"><svg class="set-chip-ico" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="12 2 2 7 12 12 22 7 12 2"/><polyline points="2 17 12 22 22 17"/><polyline points="2 12 12 17 22 12"/></svg><span>Session Manager</span></label>
<label class="set-chip" data-preview="header" data-preview-order="9"><input type="checkbox" id="appSettingsShowAttachmentsButton"><svg class="set-chip-ico" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg><span>Attachments</span></label>
<label class="set-chip" data-preview="header" data-preview-order="10"><input type="checkbox" id="appSettingsShowFileViewerButton"><svg class="set-chip-ico" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg><span>File Viewer</span></label>
<label class="set-chip" data-preview="header" data-preview-order="11"><input type="checkbox" id="appSettingsShowMultiMonitorButton"><svg class="set-chip-ico" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="2" y="4" width="13" height="9" rx="1.5"/><rect x="11" y="9" width="11" height="8" rx="1.5"/></svg><span>Multi-monitor</span></label>
<label class="set-chip" data-preview="header" data-preview-order="13" data-preview-text="42%"><input type="checkbox" id="appSettingsShowPlanUsageLimits"><svg class="set-chip-ico" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 18a8 8 0 1 1 16 0"/><path d="M12 18l4.5-5"/></svg><span>Plan Usage</span></label>
<label class="set-chip" data-preview="header" data-preview-order="14"><input type="checkbox" id="appSettingsShowLifecycleLog"><svg class="set-chip-ico" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg><span>Lifecycle Log</span></label>
</div>
</div>
</div>
<div class="set-group">
<div class="set-group-head"><h4>Panels</h4><span class="set-scope">device</span></div>
<div class="set-group-body">
<div class="set-chips" data-search="panels monitor project insights file browser subagents">
<label class="set-chip" data-preview="panel" data-preview-order="1"><input type="checkbox" id="appSettingsShowMonitor" checked><svg class="set-chip-ico" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 12h4l2.5-7 4 14L16 12h5"/></svg><span>Monitor</span></label>
<label class="set-chip" data-preview="panel" data-preview-order="2"><input type="checkbox" id="appSettingsShowProjectInsights"><svg class="set-chip-ico" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 19V9"/><path d="M10 19V5"/><path d="M16 19v-7"/><path d="M22 19H2"/></svg><span>Project Insights</span></label>
<label class="set-chip" data-preview="panel" data-preview-order="3"><input type="checkbox" id="appSettingsShowFileBrowser"><svg class="set-chip-ico" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg><span>File Browser</span></label>
<label class="set-chip" data-preview="panel" data-preview-order="4"><input type="checkbox" id="appSettingsShowSubagents"><svg class="set-chip-ico" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="6" cy="6" r="2.5"/><circle cx="6" cy="18" r="2.5"/><circle cx="18" cy="12" r="2.5"/><path d="M8.2 7.2 15.6 11M8.2 16.8 15.6 13"/></svg><span>Subagents</span></label>
</div>
</div>
</div>
<div class="set-group">
<div class="set-group-head"><h4>Cross-session features</h4><span class="set-scope">synced</span></div>
<p class="set-group-hint">Approvals answers prompts from any session in one place. Read My Mind captures your prompts and predicts the next one on demand; both cost tokens and never auto-send.</p>
<div class="set-group-body">
<div class="set-chips" data-search="approvals inbox read my mind ultracode agents floating windows workflow">
<label class="set-chip" data-preview="header" data-preview-order="7"><input type="checkbox" id="appSettingsApprovalsInbox"><svg class="set-chip-ico" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></svg><span>Approvals Inbox</span></label>
<label class="set-chip" data-preview="header" data-preview-order="8"><input type="checkbox" id="appSettingsReadMyMind"><span class="set-chip-ico set-chip-emoji" aria-hidden="true">🧠</span><span>Read My Mind</span></label>
<label class="set-chip" data-preview="header" data-preview-order="12"><input type="checkbox" id="appSettingsShowUltracodeAgents"><svg class="set-chip-ico" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="6" cy="6" r="2.5"/><circle cx="6" cy="18" r="2.5"/><circle cx="18" cy="12" r="2.5"/><path d="M8.2 7.2 15.6 11M8.2 16.8 15.6 13"/></svg><span>Ultracode Agents</span></label>
<label class="set-chip" data-preview="float" data-preview-order="1"><input type="checkbox" id="appSettingsUltracodeFloatingWindows"><svg class="set-chip-ico" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M3 9h18"/><circle cx="6" cy="6.5" r="0.6"/></svg><span>Ultracode Windows</span></label>
</div>
</div>
</div>
<div class="set-group">
<div class="set-group-head"><h4>Scheduling</h4><span class="set-scope">device</span></div>
<div class="set-group-body">
<div class="set-chips" data-search="cron scheduled jobs toolbar">
<label class="set-chip" data-preview="toolbar" data-preview-order="1"><input type="checkbox" id="appSettingsShowCronButton"><svg class="set-chip-ico" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="9"/><polyline points="12 7 12 12 15 14"/></svg><span>Cron</span></label>
</div>
</div>
</div>
<div class="set-group">
<div class="set-group-head"><h4>Subagent windows</h4></div>
<div class="set-group-body">
<div class="set-row" data-search="subagent tracking background agents">
<div class="set-row-text">
<span class="set-row-label">Track background agents <span class="set-scope">synced</span></span>
<span class="set-row-desc">Follow Claude Code subagents in real time.</span>
</div>
<label class="switch switch-sm"><input type="checkbox" id="appSettingsSubagentTracking" checked><span class="slider"></span></label>
</div>
<div class="set-row" data-search="subagent active tab only windows">
<div class="set-row-text">
<span class="set-row-label">Active tab only <span class="set-scope">device</span></span>
<span class="set-row-desc">Only show agent windows for the selected session.</span>
</div>
<label class="switch switch-sm"><input type="checkbox" id="appSettingsSubagentActiveTabOnly" checked><span class="slider"></span></label>
</div>
</div>
</div>
</section>
<!-- ══ Appearance ═══════════════════════════════════════════════ -->
<section class="set-section" id="settings-appearance" data-label="Appearance">
<div class="set-section-head">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M12 3a9 9 0 0 0 0 18 4.5 4.5 0 0 0 0-9 4.5 4.5 0 0 1 0-9z"/></svg>
<h2>Appearance</h2>
</div>
<p class="set-section-blurb">Theme, motion, and what this install calls itself.</p>
<div class="set-group">
<div class="set-group-head"><h4>Theme</h4><span class="set-scope">device</span></div>
<div class="set-group-body">
<div class="set-row has-field" data-search="skin theme palette colors dark light">
<div class="set-row-text">
<span class="set-row-label">Skin</span>
<span class="set-row-desc">Palette for the terminal and the UI.</span>
</div>
<select id="appSettingsSkin" class="set-select">
<optgroup label="Light">
<option value="paper-gray">Paper Gray</option>
<option value="solarized-light">Solarized Light</option>
<option value="catppuccin-latte">Catppuccin Latte</option>
<option value="rose-pine-dawn">Rosé Pine Dawn</option>
</optgroup>
<optgroup label="Dark">
<option value="daylight-blue">Daylight Blue</option>
<option value="daylight-green">Daylight Green</option>
<option value="og">OG Codeman</option>
</optgroup>
</select>
</div>
<div class="set-row has-field" data-search="entrance animations motion tabs windows">
<div class="set-row-text">
<span class="set-row-label">Entrance Animations</span>
<span class="set-row-desc">How new tabs, panes and agent windows arrive. Add ?animlab=1 to the URL for per-surface control.</span>
</div>
<select id="appSettingsEntranceAnim" class="set-select">
<option value="legacy">Off (default)</option>
<option value="terminal">Terminal (CRT)</option>
<option value="beamdown">Beam down</option>
<option value="quiet">Quiet</option>
<option value="playful">Playful</option>
<option value="custom">Custom (set in the lab)</option>
</select>
</div>
</div>
</div>
<div class="set-group">
<div class="set-group-head"><h4>Identity</h4></div>
<div class="set-group-body">
<div class="set-row has-field" data-search="display name branding title">
<div class="set-row-text">
<span class="set-row-label">Display Name <span class="set-scope">synced</span></span>
<span class="set-row-desc">Shown in the browser UI and the window title. Unicode is fine.</span>
</div>
<input type="text" id="appSettingsDisplayName" class="set-input" maxlength="40" placeholder="Codeman" autocomplete="off">
</div>
<div class="set-row has-field" data-search="language interface chinese english">
<div class="set-row-text">
<span class="set-row-label">Interface Language <span class="set-scope">device</span></span>
<span class="set-row-desc">Dialogs and status messages use the same language.</span>
</div>
<select id="appSettingsLanguage" class="set-select">
<option value="en">English</option>
<option value="zh-CN">简体中文</option>
</select>
</div>
</div>
</div>
<div class="set-group">
<div class="set-group-head"><h4>Tabs</h4><span class="set-scope">device</span></div>
<div class="set-group-body">
<div class="set-row" data-search="tall tabs folder name two rows">
<div class="set-row-text">
<span class="set-row-label">Tall Tabs</span>
<span class="set-row-desc">Show the folder under the session name and let the tab bar wrap.</span>
</div>
<label class="switch switch-sm"><input type="checkbox" id="appSettingsTabTwoRows"><span class="slider"></span></label>
</div>
<div class="set-row" data-search="pop out detach tab window">
<div class="set-row-text">
<span class="set-row-label">Pop-out Button on Tabs</span>
<span class="set-row-desc">Open a session in its own window from the tab.</span>
</div>
<label class="switch switch-sm"><input type="checkbox" id="appSettingsShowTabDetachButton"><span class="slider"></span></label>
</div>
<div class="set-row" id="appSettingsMobileOverviewItem" data-search="overview home screen phone logo">
<div class="set-row-text">
<span class="set-row-label">Overview Home Screen <span class="set-tag">phone</span></span>
<span class="set-row-desc">The C logo opens the session overview instead of the welcome screen.</span>
</div>
<label class="switch switch-sm"><input type="checkbox" id="appSettingsMobileOverview" checked><span class="slider"></span></label>
</div>
</div>
</div>
</section>
<!-- ══ Models ═══════════════════════════════════════════════════ -->
<section class="set-section" id="settings-models" data-label="Models">
<div class="set-section-head">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3l8 4.5v9L12 21l-8-4.5v-9L12 3z"/><path d="M12 12l8-4.5M12 12v9M12 12L4 7.5"/></svg>
<h2>Models</h2>
</div>
<p class="set-section-blurb">One place for what new sessions run on, and what background tasks are routed to.</p>
<div class="set-group">
<div class="set-group-head"><h4>New Claude sessions</h4><span class="set-scope">synced</span></div>
<p class="set-group-hint">A soft default, pinned via the case's .claude/settings.local.json. /model still switches inside a running session.</p>
<div class="set-group-body">
<!-- Cards are built from the select below by settings-ui.js, which keeps
the select as the single source of truth for load/save. -->
<div class="set-modelgrid" id="appSettingsModelCards" role="radiogroup" aria-label="Model for new Claude sessions" data-search="model opus sonnet haiku fable claude"></div>
<select id="appSettingsClaudeModel" class="set-select set-field-hidden" aria-hidden="true" tabindex="-1">
<option value="" data-meta="Whatever the CLI picks">Default (CLI setting)</option>
<option value="claude-fable-5" data-meta="Most powerful" data-base="claude-fable-5" data-ctx="1">Fable 5</option>
<option value="claude-fable-5[1m]" data-variant="1m" data-base="claude-fable-5">Fable 5 (1M context)</option>
<option value="opus" data-meta="Most capable" data-base="opus" data-ctx="1">Opus</option>
<option value="opus[1m]" data-variant="1m" data-base="opus">Opus (1M context)</option>
<option value="claude-opus-4-6" data-meta="Previous generation" data-base="claude-opus-4-6" data-ctx="1">Opus 4.6</option>
<option value="claude-opus-4-6[1m]" data-variant="1m" data-base="claude-opus-4-6">Opus 4.6 (1M context)</option>
<option value="sonnet" data-meta="Balanced" data-base="sonnet">Sonnet</option>
<option value="haiku" data-meta="Fast &amp; cheap" data-base="haiku">Haiku</option>
</select>
<div class="set-row" id="appSettingsContextRow" data-search="1m context window opus long">
<div class="set-row-text">
<span class="set-row-label">1M context window</span>
<span class="set-row-desc" id="appSettingsContextDesc">Available for Fable 5, Opus and Opus 4.6.</span>
</div>
<label class="switch switch-sm"><input type="checkbox" id="appSettingsOpusContext1m"><span class="slider"></span></label>
</div>
<div class="set-row set-row-block" data-search="thinking effort ultracode xhigh max reasoning">
<div class="set-row-text">
<span class="set-row-label">Thinking effort</span>
<span class="set-row-desc">Switchable in-session with /effort. Ultracode runs multi-agent workflows.</span>
</div>
<div class="set-segment" id="appSettingsEffortSegment" role="radiogroup" aria-label="Thinking effort"></div>
<select id="appSettingsThinkingEffort" class="set-select set-field-hidden" aria-hidden="true" tabindex="-1">
<option value="">Default</option>
<option value="low">Low</option>
<option value="medium">Medium</option>
<option value="high">High</option>
<option value="xhigh">XHigh</option>
<option value="max">Max</option>
<option value="ultracode">Ultracode</option>
</select>
</div>
</div>
</div>
<div class="set-group set-group-advanced" id="appSettingsTaskRouting">
<div class="set-group-head set-group-head-toggle" role="button" tabindex="0" aria-expanded="false">
<h4>Task routing</h4>
<span class="set-scope">synced</span>
<span class="set-adv">Advanced
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" aria-hidden="true"><path d="M6 9l6 6 6-6"/></svg>
</span>
</div>
<p class="set-group-hint">Models for orchestrator and background work. Leave on the default unless you are tuning cost.</p>
<div class="set-group-body">
<div class="set-row has-field" data-search="default model tasks orchestrator">
<div class="set-row-text">
<span class="set-row-label">Default for tasks</span>
<span class="set-row-desc">Used for execution tasks. Optimizer suggestions stay advisory.</span>
</div>
<select id="appSettingsDefaultModel" class="set-select">
<option value="">Default (CLI default)</option>
<option value="claude-fable-5">Fable 5 (Most powerful)</option>
<option value="opus">Opus (Most capable)</option>
<option value="sonnet">Sonnet (Balanced)</option>
<option value="haiku">Haiku (Fast &amp; cheap)</option>
</select>
</div>
<div class="set-minigrid" data-search="explore implement test review agent type overrides">
<div class="set-mini">
<span class="set-mini-label">Explore</span>
<select id="appSettingsModelExplore" class="set-select">
<option value="">Use default</option>
<option value="haiku">Haiku</option>
<option value="sonnet">Sonnet</option>
<option value="opus">Opus</option>
<option value="claude-fable-5">Fable 5</option>
</select>
</div>
<div class="set-mini">
<span class="set-mini-label">Implement</span>
<select id="appSettingsModelImplement" class="set-select">
<option value="">Use default</option>
<option value="haiku">Haiku</option>
<option value="sonnet">Sonnet</option>
<option value="opus">Opus</option>
<option value="claude-fable-5">Fable 5</option>
</select>
</div>
<div class="set-mini">
<span class="set-mini-label">Test</span>
<select id="appSettingsModelTest" class="set-select">
<option value="">Use default</option>
<option value="haiku">Haiku</option>
<option value="sonnet">Sonnet</option>
<option value="opus">Opus</option>
<option value="claude-fable-5">Fable 5</option>
</select>
</div>
<div class="set-mini">
<span class="set-mini-label">Review</span>
<select id="appSettingsModelReview" class="set-select">
<option value="">Use default</option>
<option value="haiku">Haiku</option>
<option value="sonnet">Sonnet</option>
<option value="opus">Opus</option>
<option value="claude-fable-5">Fable 5</option>
</select>
</div>
</div>
<div class="set-row" data-search="optimizer recommendations advisory">
<div class="set-row-text">
<span class="set-row-label">Show optimizer recommendations</span>
<span class="set-row-desc">Advisory only. Never overrides your choice.</span>
</div>
<label class="switch switch-sm"><input type="checkbox" id="appSettingsShowModelRecommendations" checked><span class="slider"></span></label>
</div>
</div>
</div>
</section>
<!-- ══ Agents &amp; CLIs ═════════════════════════════════════════ -->
<section class="set-section" id="settings-clis" data-label="Agents &amp; CLIs">
<div class="set-section-head">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8 3H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h2"/><path d="M16 3h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-2"/><circle cx="12" cy="12" r="2.5"/></svg>
<h2>Agents &amp; CLIs</h2>
</div>
<p class="set-section-blurb">Launch flags for the CLIs Codeman spawns.</p>
<div class="set-group">
<div class="set-group-head"><h4>Claude</h4><span class="set-scope">synced</span></div>
<div class="set-group-body">
<div class="set-row has-field" data-search="startup mode permissions auto allowed tools">
<div class="set-row-text">
<span class="set-row-label">Startup Mode</span>
<span class="set-row-desc">How the CLI is launched in new sessions. Auto Mode runs without routine prompts behind a background safety classifier.</span>
</div>
<select id="appSettingsClaudeMode" class="set-select">
<option value="dangerously-skip-permissions">Skip Permissions (default)</option>
<option value="auto">Auto (classifier-guarded, low prompts)</option>
<option value="normal">Normal (with prompts)</option>
<option value="allowedTools">Allowed Tools Only</option>
</select>
</div>
<div class="set-row has-field" id="allowedToolsRow" style="display: none;" data-search="allowed tools list">
<div class="set-row-text">
<span class="set-row-label">Allowed Tools</span>
<span class="set-row-desc">Comma-separated list of tools to allow.</span>
</div>
<input type="text" id="appSettingsAllowedTools" class="set-input" placeholder="Read,Glob,Grep,Bash">
</div>
<div class="set-row" data-search="ralph todo tracker loop">
<div class="set-row-text">
<span class="set-row-label">Ralph / Todo Tracker</span>
<span class="set-row-desc">Auto-enable for new sessions. Otherwise it enables itself on Ralph pattern detection.</span>
</div>
<label class="switch switch-sm"><input type="checkbox" id="appSettingsRalphEnabled"><span class="slider"></span></label>
</div>
<div class="set-row" data-search="agent teams experimental">
<div class="set-row-text">
<span class="set-row-label">Agent Teams</span>
<span class="set-row-desc">Experimental multi-agent teams for all new Claude sessions.</span>
</div>
<label class="switch switch-sm"><input type="checkbox" id="appSettingsAgentTeams"><span class="slider"></span></label>
</div>
<div class="set-row" data-search="agent skill codeman api workers">
<div class="set-row-text">
<span class="set-row-label">Codeman Agent Skill</span>
<span class="set-row-desc">Let new sessions start workers, send prompts and wait for results over the API.</span>
</div>
<label class="switch switch-sm"><input type="checkbox" id="appSettingsAgentSkill"><span class="slider"></span></label>
</div>
<div class="set-row" data-search="remote auto reconnect ssh">
<div class="set-row-text">
<span class="set-row-label">Remote auto-reconnect</span>
<span class="set-row-desc">Reattach remote SSH sessions to their durable tmux after a dropped link.</span>
</div>
<label class="switch switch-sm"><input type="checkbox" id="appSettingsRemoteAutoReconnect"><span class="slider"></span></label>
</div>
<div class="set-row" data-search="nice priority cpu">
<div class="set-row-text">
<span class="set-row-label">Nice priority</span>
<span class="set-row-desc">Lower the CPU priority of new sessions to reduce system impact.</span>
</div>
<label class="switch switch-sm"><input type="checkbox" id="appSettingsNiceEnabled"><span class="slider"></span></label>
</div>
<div class="set-row" id="niceValueRow" data-search="nice value priority number">
<div class="set-row-text">
<span class="set-row-label">Nice value</span>
<span class="set-row-desc">-20 to 19. Higher means lower priority. Default 10.</span>
</div>
<input type="number" id="appSettingsNiceValue" class="set-num" min="-20" max="19" value="10">
</div>
</div>
</div>
<div class="set-group" id="appSettingsCodexGroup">
<div class="set-group-head"><h4>Codex</h4><span class="set-scope">synced</span></div>
<div class="set-group-body">
<div class="set-row" data-search="codex bypass approvals sandbox">
<div class="set-row-text">
<span class="set-row-label">Bypass approvals and sandbox</span>
<span class="set-row-desc">Start new Codex sessions with --dangerously-bypass-approvals-and-sandbox.</span>
</div>
<label class="switch switch-sm"><input type="checkbox" id="appSettingsCodexDangerouslyBypassApprovals"><span class="slider"></span></label>
</div>
<div class="set-row" data-search="codex animations status effects">
<div class="set-row-text">
<span class="set-row-label">Animated status effects</span>
<span class="set-row-desc">Decorative Codex TUI motion. Leave off to reduce remote and mobile redraws.</span>
</div>
<label class="switch switch-sm"><input type="checkbox" id="appSettingsCodexAnimations"><span class="slider"></span></label>
</div>
</div>
</div>
</section>
<!-- ══ Notifications ════════════════════════════════════════════ -->
<section class="set-section" id="settings-notifications" data-label="Notifications">
<div class="set-section-head">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 8a6 6 0 1 0-12 0c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.7 21a2 2 0 0 1-3.4 0"/></svg>
<h2>Notifications</h2>
</div>
<p class="set-section-blurb">What reaches you, and on which channel.</p>
<div class="set-group">
<div class="set-group-head"><h4>Channels</h4><span class="set-scope">device</span></div>
<div class="set-group-body">
<div class="set-row" data-search="enable notifications master">
<div class="set-row-text">
<span class="set-row-label">Notifications</span>
<span class="set-row-desc">Master switch for every layer below.</span>
</div>
<label class="switch switch-sm"><input type="checkbox" id="appSettingsNotifEnabled" checked><span class="slider"></span></label>
</div>
<div class="set-row" data-search="browser notifications os permission">
<div class="set-row-text">
<span class="set-row-label">Browser</span>
<span class="set-row-desc">OS notifications while the tab is hidden. Remote access needs HTTPS: codeman web --https</span>
</div>
<div class="set-row-actions">
<label class="switch switch-sm"><input type="checkbox" id="appSettingsNotifBrowser"><span class="slider"></span></label>
<button class="btn-toolbar btn-sm" onclick="app.notificationManager?.requestPermission()" title="Request browser notification permission">Ask</button>
<span class="settings-status" id="notifPermissionStatus">?</span>
</div>
</div>
<div class="set-row" data-search="push notifications subscribe pwa">
<div class="set-row-text">
<span class="set-row-label">Push</span>
<span class="set-row-desc">Works with the tab closed, and through a Cloudflare tunnel. Requires HTTPS or localhost; iOS requires the PWA.</span>
</div>
<div class="set-row-actions">
<label class="switch switch-sm"><input type="checkbox" id="appSettingsPushEnabled"><span class="slider"></span></label>
<button class="btn-toolbar btn-sm" id="pushSubscribeBtn" onclick="app.togglePushSubscription()" title="Subscribe or unsubscribe to push notifications">Subscribe</button>
<span class="settings-status" id="pushSubscriptionStatus">off</span>
</div>
</div>
<div class="set-row" data-search="audio alerts beep sound">
<div class="set-row-text">
<span class="set-row-label">Audio alerts</span>
<span class="set-row-desc">Short beep on critical events.</span>
</div>
<label class="switch switch-sm"><input type="checkbox" id="appSettingsNotifAudio"><span class="slider"></span></label>
</div>
<div class="set-row" data-search="idle threshold stuck minutes">
<div class="set-row-text">
<span class="set-row-label">Idle threshold</span>
<span class="set-row-desc">Notify when a session sits idle longer than this.</span>
</div>
<div class="set-row-actions">
<input type="number" id="appSettingsNotifStuckMins" class="set-num" value="10" min="1" max="120">
<span class="set-unit">min</span>
</div>
</div>
</div>
</div>
<div class="set-group">
<div class="set-group-head"><h4>Levels</h4><span class="set-scope">device</span></div>
<div class="set-group-body">
<div class="set-chips" data-search="critical warning info levels">
<label class="set-chip"><input type="checkbox" id="appSettingsNotifCritical" checked><span>Critical</span></label>
<label class="set-chip"><input type="checkbox" id="appSettingsNotifWarning" checked><span>Warning</span></label>
<label class="set-chip"><input type="checkbox" id="appSettingsNotifInfo" checked><span>Info</span></label>
</div>
</div>
</div>
<div class="set-group">
<div class="set-group-head"><h4>Per event</h4><span class="set-scope">device</span></div>
<div class="set-group-body">
<div class="event-type-grid" data-search="per event permission question idle response respawn task subagent">
<div class="event-header">Event</div>
<div class="event-header">On</div>
<div class="event-header">Browser</div>
<div class="event-header">Push</div>
<div class="event-header">Sound</div>
<div class="event-label">Permission prompts</div>
<input type="checkbox" id="eventPermissionEnabled" checked>
<input type="checkbox" id="eventPermissionBrowser" checked>
<input type="checkbox" id="eventPermissionPush" checked>
<input type="checkbox" id="eventPermissionAudio" checked>
<div class="event-label">Questions from Claude</div>
<input type="checkbox" id="eventQuestionEnabled" checked>
<input type="checkbox" id="eventQuestionBrowser" checked>
<input type="checkbox" id="eventQuestionPush" checked>
<input type="checkbox" id="eventQuestionAudio" checked>
<div class="event-label">Session idle</div>
<input type="checkbox" id="eventIdleEnabled" checked>
<input type="checkbox" id="eventIdleBrowser" checked>
<input type="checkbox" id="eventIdlePush">
<input type="checkbox" id="eventIdleAudio">
<div class="event-label">Response complete</div>
<input type="checkbox" id="eventStopEnabled">
<input type="checkbox" id="eventStopBrowser">
<input type="checkbox" id="eventStopPush">
<input type="checkbox" id="eventStopAudio">
<div class="event-label">Respawn cycles</div>
<input type="checkbox" id="eventRespawnEnabled" checked>
<input type="checkbox" id="eventRespawnBrowser">
<input type="checkbox" id="eventRespawnPush">
<input type="checkbox" id="eventRespawnAudio">
<div class="event-label">Task complete</div>
<input type="checkbox" id="eventRalphEnabled" checked>
<input type="checkbox" id="eventRalphBrowser" checked>
<input type="checkbox" id="eventRalphPush">
<input type="checkbox" id="eventRalphAudio" checked>
<div class="event-label">Subagent activity</div>
<input type="checkbox" id="eventSubagentEnabled">
<input type="checkbox" id="eventSubagentBrowser">
<input type="checkbox" id="eventSubagentPush">
<input type="checkbox" id="eventSubagentAudio">
</div>
</div>
</div>
</section>
<!-- ══ Voice ════════════════════════════════════════════════════ -->
<section class="set-section" id="settings-voice" data-label="Voice">
<div class="set-section-head">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="9" y="2" width="6" height="12" rx="3"/><path d="M5 11a7 7 0 0 0 14 0"/><path d="M12 18v4"/></svg>
<h2>Voice</h2>
</div>
<p class="set-section-blurb">Dictation into the composer.</p>
<div class="set-group">
<div class="set-group-head"><h4>Provider</h4><span class="set-scope">device</span></div>
<div class="set-group-body">
<div class="set-row" data-search="voice provider active">
<div class="set-row-text"><span class="set-row-label">Active provider</span></div>
<span class="voice-provider-status" id="voiceProviderStatus">&mdash;</span>
</div>
<div class="set-row has-field" data-search="voice insert mode compose">
<div class="set-row-text">
<span class="set-row-label">Insert mode</span>
<span class="set-row-desc">Where dictated text lands.</span>
</div>
<select id="voiceInsertMode" class="set-select">
<option value="direct">Direct to input</option>
<option value="compose">Compose dialog</option>
</select>
</div>
</div>
</div>
<div class="set-group">
<div class="set-group-head"><h4>Deepgram Nova-3</h4><span class="set-scope">device</span></div>
<div class="set-group-body">
<div class="set-row has-field" data-search="deepgram api key">
<div class="set-row-text">
<span class="set-row-label">API key</span>
<span class="set-row-desc">Stored on this device only, never sent to the server. Get a key at console.deepgram.com</span>
</div>
<div class="set-row-actions set-row-actions-wide">
<input type="password" id="voiceDeepgramKey" class="set-input" placeholder="Enter Deepgram API key" autocomplete="off">
<button class="btn-toolbar btn-sm" onclick="app.toggleDeepgramKeyVisibility()" id="voiceKeyToggleBtn" type="button">Show</button>
</div>
</div>
<div class="set-row has-field" data-search="voice language">
<div class="set-row-text"><span class="set-row-label">Language</span></div>
<select id="voiceLanguage" class="set-select">
<option value="en-US">English (US)</option>
<option value="en-GB">English (UK)</option>
<option value="es">Spanish</option>
<option value="fr">French</option>
<option value="de">German</option>
<option value="ja">Japanese</option>
<option value="multi">Auto-detect (multi)</option>
</select>
</div>
<div class="set-row has-field" data-search="voice keyterms domain keywords">
<div class="set-row-text">
<span class="set-row-label">Domain keywords</span>
<span class="set-row-desc">Comma-separated terms to boost recognition accuracy.</span>
</div>
<input type="text" id="voiceKeyterms" class="set-input" placeholder="codeman, tmux, respawn, subagent">
</div>
</div>
</div>
</section>
<!-- ══ Shortcuts ════════════════════════════════════════════════ -->
<section class="set-section" id="settings-shortcuts" data-label="Shortcuts">
<div class="set-section-head">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="2" y="5" width="20" height="14" rx="2"/><path d="M6 9h.01M10 9h.01M14 9h.01M18 9h.01M6 13h.01M18 13h.01M9 13h6"/></svg>
<h2>Shortcuts</h2>
</div>
<p class="set-section-blurb">Click a binding to capture a new key combination.</p>
<div class="set-group">
<div class="set-group-head"><h4>Keyboard</h4><span class="set-scope">device</span></div>
<div class="set-group-body">
<div id="appSettingsShortcutsList" data-search="keyboard shortcuts bindings keys">
<!-- Populated by app.renderShortcutSettingsList() -->
</div>
</div>
</div>
</section>
</div>
</div>
<div class="form-actions set-foot">
<button class="btn-toolbar" onclick="app.closeAppSettings()">Cancel</button>
<button class="btn-toolbar btn-primary" onclick="app.saveAppSettings()">Save</button>
</div>
</div>
</div>
<!-- Shortcut Overlay Modal -->
<div class="modal shortcut-overlay-modal" id="shortcutOverlayModal" tabindex="-1">
<div class="modal-backdrop" onclick="app.closeShortcutOverlay()"></div>
<div class="modal-content">
<div class="modal-header">
<span class="modal-title">Keyboard Shortcuts</span>
<button class="modal-close" onclick="app.closeShortcutOverlay()" aria-label="Close">&#x2715;</button>
</div>
<div class="modal-body">
<div id="shortcutOverlayList"></div>
<div class="shortcut-overlay-footer">
<button class="btn btn-sm" onclick="app.closeShortcutOverlay(); app.showHelp()">Full shortcut reference</button>
</div>
</div>
</div>
</div>
<!-- Create Case Modal -->
<div class="modal" id="createCaseModal">
<div class="modal-backdrop" onclick="app.closeCreateCaseModal()"></div>
<div class="modal-content modal-lg">
<div class="modal-header">
<h3>Add Case</h3>
<button class="modal-close" onclick="app.closeCreateCaseModal()" aria-label="Close create case">&times;</button>
</div>
<div class="modal-tabs">
<button class="modal-tab-btn active" data-tab="case-create">Create New</button>
<button class="modal-tab-btn" data-tab="case-clone" id="caseCloneTabBtn">Clone Repo</button>
<button class="modal-tab-btn" data-tab="case-link">Link Existing</button>
<button class="modal-tab-btn" data-tab="case-remote">Remote</button>
<button class="modal-tab-btn" data-tab="case-docker">Docker</button>
<button class="modal-tab-btn" data-tab="case-manage">Manage</button>
</div>
<div class="modal-body">
<!-- Create New Tab -->
<div class="modal-tab-content" id="case-create">
<div class="form-row">
<label>Case Name</label>
<input type="text" id="newCaseName" placeholder="my-project" pattern="[a-zA-Z0-9_-]+" autocomplete="off" autocapitalize="off" spellcheck="false">
<span class="form-hint">Letters, numbers, hyphens, underscores only. Created in ~/codeman-cases/</span>
</div>
<div class="form-row">
<label>Description (optional)</label>
<input type="text" id="newCaseDescription" placeholder="A brief description..." autocomplete="off">
</div>
<div class="form-row docker-quick-row">
<label class="checkbox-row"><input type="checkbox" id="newCaseDocker"> 🐳 Run in an isolated Docker container</label>
<span class="form-hint">Runs this case in a hardened, isolated container. The base image is built automatically on first use. Docker/Podman must be installed.</span>
</div>
<details class="advanced-options docker-quick-settings" id="dockerQuickSettings">
<summary>Container settings (optional, sensible defaults)</summary>
<div class="advanced-options-content">
<div class="form-row">
<label>Template</label>
<select id="quickDockerTemplate" onchange="app.applyDockerTemplate()">
<option value="small">Small — 2 GB RAM, 1 CPU</option>
<option value="medium" selected>Medium — 4 GB RAM, 2 CPU (default)</option>
<option value="large">Large — 8 GB RAM, 4 CPU</option>
<option value="gpu">GPU — 8 GB RAM, 4 CPU, all GPUs</option>
<option value="custom">Custom</option>
</select>
<span class="form-hint">Disk is elastic: storage grows automatically as data flows in (no fixed cap).</span>
</div>
<div class="form-row">
<label>Memory</label>
<input type="text" id="quickDockerMemory" placeholder="4g" autocomplete="off" spellcheck="false">
</div>
<div class="form-row">
<label>CPUs</label>
<input type="text" id="quickDockerCpus" placeholder="2" autocomplete="off" spellcheck="false">
</div>
<div class="form-row">
<label>GPUs</label>
<input type="text" id="quickDockerGpus" placeholder="none (e.g. all, or 1)" autocomplete="off" spellcheck="false">
<span class="form-hint">Needs the NVIDIA container toolkit on the host.</span>
</div>
<div class="form-row">
<label>Network</label>
<select id="quickDockerNetwork">
<option value="bridge">bridge (internet on)</option>
<option value="none">none (fully isolated)</option>
</select>
</div>
<div class="form-row">
<label>Image</label>
<input type="text" id="quickDockerImage" placeholder="codeman/agent:base" autocomplete="off" spellcheck="false">
</div>
<div class="form-row">
<label class="checkbox-row"><input type="checkbox" id="quickDockerMountCreds" checked> Mount host credentials (~/.claude etc.)</label>
</div>
</div>
</details>
</div>
<!-- Clone Repo Tab (issue #236) -->
<div class="modal-tab-content hidden" id="case-clone">
<div class="form-row">
<label>Repository URL</label>
<input type="url" id="cloneRepoUrl" placeholder="https://github.com/owner/repo.git" autocomplete="off" autocapitalize="off" autocorrect="off" spellcheck="false" oninput="app.onCloneUrlInput()">
<span class="form-hint clone-status" id="cloneRepoStatus">Public repositories only: Codeman clones with no credentials.</span>
</div>
<div class="form-row">
<label>Case Name</label>
<input type="text" id="cloneCaseName" placeholder="repo" pattern="[a-zA-Z0-9_-]+" autocomplete="off" autocapitalize="off" spellcheck="false" oninput="app.onCloneNameEdited()">
<span class="form-hint">Filled in from the URL. Cloned into ~/codeman-cases/&lt;name&gt;, so deleting the case deletes this working tree.</span>
</div>
<div class="form-row">
<label>Branch or Tag (optional)</label>
<input type="text" id="cloneRepoRef" list="cloneRepoRefOptions" placeholder="default branch" autocomplete="off" autocapitalize="off" spellcheck="false">
<datalist id="cloneRepoRefOptions"></datalist>
<span class="form-hint" id="cloneRefHint">Leave blank for the repository's default branch.</span>
</div>
<div class="form-row">
<label>Brain</label>
<select id="cloneCaseBrain" class="form-select">
<option value="">Leave the Run button as it is</option>
<option value="claude" data-cli="claude">Claude Code</option>
<option value="codex" data-cli="codex">Codex</option>
<option value="gemini" data-cli="gemini">Gemini</option>
<option value="opencode" data-cli="opencode">OpenCode</option>
<option value="antigravity" data-cli="antigravity">Antigravity</option>
<option value="shell">Shell (no agent)</option>
</select>
<span class="form-hint">Which CLI to point the Run button at once the clone finishes. Changeable any time from the Run dropdown.</span>
</div>
<details class="advanced-options">
<summary>Clone options</summary>
<div class="advanced-options-content">
<div class="form-row">
<label class="checkbox-row"><input type="checkbox" id="cloneShallow"> Shallow clone (--depth 1)</label>
<span class="form-hint">Much faster on big repositories, but there is no history to read afterwards.</span>
</div>
<div class="form-row">
<label class="checkbox-row"><input type="checkbox" id="cloneStartSession"> Start a session when the clone finishes</label>
</div>
</div>
</details>
<span class="form-hint" id="cloneCaseNote" style="margin-top: 8px; display: block;">The clone runs while this request is open, so a large repository takes a while. The case appears as soon as git finishes, even if the browser gave up waiting.</span>
</div>
<!-- Link Existing Tab -->
<div class="modal-tab-content hidden" id="case-link">
<div class="form-row">
<label>Case Name</label>
<input type="text" id="linkCaseName" placeholder="my-project" pattern="[a-zA-Z0-9_-]+" autocomplete="off" autocapitalize="off" spellcheck="false">
<span class="form-hint">Name to identify this case in Codeman</span>
</div>
<div class="form-row">
<label>Folder Path</label>
<div class="path-input-group">
<input type="text" id="linkCasePath" placeholder="/mnt/d/AI/my-project" autocomplete="off" autocapitalize="off" autocorrect="off" spellcheck="false">
<button type="button" class="btn path-input-browse" onclick="app.openLinkCasePathPicker()">Browse&hellip;</button>
</div>
<span class="form-hint">Choose an existing folder from this computer or enter its absolute path</span>
</div>
</div>
<!-- Remote Tab -->
<div class="modal-tab-content hidden" id="case-remote">
<div class="form-row">
<label>Case Name</label>
<input type="text" id="remoteCaseName" placeholder="gpu-work" pattern="[a-zA-Z0-9_-]+" autocomplete="off" autocapitalize="off" spellcheck="false">
<span class="form-hint">Name to identify this remote case in Codeman</span>
</div>
<div class="form-row">
<label>Remote Path</label>
<input type="text" id="remoteCasePath" placeholder="/home/user/projects/work" autocomplete="off" autocapitalize="off" autocorrect="off" spellcheck="false">
<span class="form-hint">Absolute path on the remote host. Codeman will not create or delete it.</span>
</div>
<div class="form-row">
<label>Host ID</label>
<input type="text" id="remoteHostId" placeholder="gpu-box" pattern="[a-zA-Z0-9_-]+" autocomplete="off" autocapitalize="off" spellcheck="false">
</div>
<div class="form-row">
<label>SSH Host/IP</label>
<input type="text" id="remoteHostAddress" placeholder="10.0.0.42" autocomplete="off" autocapitalize="off" spellcheck="false">
</div>
<div class="form-row">
<label>SSH Username</label>
<input type="text" id="remoteHostUsername" placeholder="ubuntu" autocomplete="off" autocapitalize="off" spellcheck="false">
</div>
<div class="form-row">
<label>SSH Port</label>
<input type="number" id="remoteHostPort" placeholder="22" min="1" max="65535" autocomplete="off">
<span class="form-hint">Optional. Leave blank for the default port 22.</span>
</div>
<div class="form-row">
<label>Codex Command Override</label>
<input type="text" id="remoteHostCodexCommand" placeholder="exec codx personal" autocomplete="off" autocapitalize="off" spellcheck="false">
<span class="form-hint">Optional. Leave blank to use exec codex on the remote host.</span>
</div>
<details class="advanced-options">
<summary>Advanced SSH</summary>
<div class="advanced-options-content">
<div class="form-row">
<label>Identity File</label>
<input type="text" id="remoteHostIdentityFile" placeholder="~/.ssh/remote_ed25519" autocomplete="off" autocapitalize="off" autocorrect="off" spellcheck="false">
<span class="form-hint">Optional. Path to a private key on this machine (passed to ssh -i). Never the key contents.</span>
</div>
<div class="form-row">
<label>SOCKS Proxy</label>
<input type="text" id="remoteHostSocksProxy" placeholder="127.0.0.1:1080" autocomplete="off" autocapitalize="off" spellcheck="false">
<span class="form-hint">Optional. host:port of a SOCKS5 proxy (e.g. cloudflared). Routes ssh through it via a ProxyCommand.</span>
</div>
<div class="form-row">
<label>Jump Host</label>
<input type="text" id="remoteHostJumpHost" placeholder="bastion@10.0.0.1:22" autocomplete="off" autocapitalize="off" spellcheck="false">
<span class="form-hint">Optional. [user@]host[:port] for ssh -J (jump/bastion host).</span>
</div>
<div class="form-row">
<label>Extra -o Options</label>
<textarea id="remoteHostExtraSshOptions" rows="3" placeholder="StrictHostKeyChecking=accept-new&#10;ConnectTimeout=10" autocomplete="off" autocapitalize="off" spellcheck="false"></textarea>
<span class="form-hint">Optional. One KEY=VALUE per line; each becomes an ssh -o option.</span>
</div>
</div>
</details>
<!-- COD-105 — discover + attach existing remote tmux sessions this Codeman didn't create. -->
<details class="advanced-options" id="remoteDiscoverSection">
<summary>Discover existing sessions</summary>
<div class="advanced-options-content">
<span class="form-hint">Find <code>codeman-*</code> tmux sessions already running on this host (started by the remote's own Codeman or another instance) and attach to one. Attaching shares the session; closing the tab detaches it — it is never killed.</span>
<div class="form-row" style="margin-top: 8px;">
<button type="button" class="btn-toolbar" id="remoteDiscoverBtn" onclick="app.discoverRemoteSessions()">Discover existing sessions</button>
</div>
<div id="remoteDiscoverResults" class="remote-discover-results"></div>
</div>
</details>
</div>
<!-- Docker Tab -->
<div class="modal-tab-content hidden" id="case-docker">
<div class="form-row">
<label>Case Name</label>
<input type="text" id="dockerCaseName" placeholder="sandbox" pattern="[a-zA-Z0-9_-]+" autocomplete="off" autocapitalize="off" spellcheck="false">
<span class="form-hint">Runs inside an isolated container. Multiple sessions can share the same container.</span>
</div>
<div class="form-row">
<label>Workspace Path</label>
<input type="text" id="dockerWorkspacePath" placeholder="/home/user/projects/sandbox" autocomplete="off" autocapitalize="off" autocorrect="off" spellcheck="false">
<span class="form-hint">Absolute HOST directory, bind-mounted into the container. Codeman scaffolds CLAUDE.md + hooks into it.</span>
</div>
<div class="form-row">
<label>Host ID</label>
<input type="text" id="dockerHostId" placeholder="local" pattern="[a-zA-Z0-9_-]+" autocomplete="off" autocapitalize="off" spellcheck="false">
<span class="form-hint">A reusable docker host profile. Reuse the same ID across cases to share settings.</span>
</div>
<div class="form-row">
<label>Image</label>
<input type="text" id="dockerImage" placeholder="codeman/agent:base" autocomplete="off" autocapitalize="off" spellcheck="false">
<span class="form-hint">Build it once with <code>node scripts/build-agent-image.mjs</code>. Contains node + claude/codex/gemini/opencode/agy + tmux.</span>
</div>
<div class="form-row">
<label>Network</label>
<select id="dockerNetwork">
<option value="bridge">bridge (internet on, default)</option>
<option value="none">none (fully isolated, no network)</option>
<option value="custom">custom bridge</option>
</select>
</div>
<details class="advanced-options">
<summary>Advanced container settings</summary>
<div class="advanced-options-content">
<div class="form-row">
<label>Memory</label>
<input type="text" id="dockerMemory" placeholder="4g" autocomplete="off" spellcheck="false">
<span class="form-hint">Optional, e.g. 4g / 512m. Enforced as a hard OOM cap where the engine supports it.</span>
</div>
<div class="form-row">
<label>CPUs</label>
<input type="text" id="dockerCpus" placeholder="2" autocomplete="off" spellcheck="false">
</div>
<div class="form-row">
<label class="checkbox-row"><input type="checkbox" id="dockerMountCredentials" checked> Mount host credentials (~/.claude etc.)</label>
<span class="form-hint">On: your existing login just works (creds stay on the host, never in exports). Off: sealed sandbox, log in inside the container.</span>
</div>
<div class="form-row">
<label class="checkbox-row"><input type="checkbox" id="dockerResumeOnStart" checked> Resume last conversation on relaunch</label>
</div>
</div>
</details>
<span class="form-hint" id="dockerLinkStatus" style="margin-top: 8px; display: block;"></span>
</div>
<!-- Manage Tab -->
<div class="modal-tab-content hidden" id="case-manage">
<div class="case-manage-list" id="caseManageList">
<!-- Populated by JS -->
</div>
<span class="form-hint" style="margin-top: 8px; display: block;">Use arrows to reorder. Changes are saved automatically.</span>
<div id="dockerExportsSection" style="margin-top: 16px; border-top: 1px solid var(--border, #333); padding-top: 12px;">
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom:8px;">
<strong style="font-size: 13px;">Docker exports</strong>
<button class="btn-toolbar" onclick="app.refreshDockerExports()">Refresh</button>
</div>
<div class="case-manage-list" id="dockerExportsList"><span class="form-hint">No exports yet. Export a docker case from its tab.</span></div>
</div>
</div>
</div>
<div class="form-actions">
<button class="btn-toolbar" onclick="app.closeCreateCaseModal()">Cancel</button>
<button class="btn-toolbar btn-primary" id="caseModalSubmit" onclick="app.submitCaseModal()">Create</button>
</div>
</div>
</div>
<!-- Mobile Case Picker (Bottom Sheet) -->
<div class="modal mobile-case-picker" id="mobileCasePickerModal">
<div class="modal-backdrop" onclick="app.closeMobileCasePicker()"></div>
<div class="mobile-case-picker-sheet">
<div class="mobile-case-picker-header">
<h3>Select Case</h3>
<button class="modal-close" onclick="app.closeMobileCasePicker()" aria-label="Close case picker">&times;</button>
</div>
<div class="mobile-case-picker-body">
<div class="mobile-case-list" id="mobileCaseList">
<!-- Cases populated by JS -->
</div>
</div>
<div class="mobile-case-picker-footer">
<button class="btn-toolbar btn-case-create-mobile" onclick="app.showCreateCaseFromMobile()">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
Create New Case
</button>
</div>
</div>
</div>
<!-- Ralph Loop Wizard Modal -->
<div class="modal" id="ralphWizardModal">
<div class="modal-backdrop"></div>
<div class="modal-content modal-wizard">
<div class="modal-header">
<h3>Start Ralph Loop</h3>
<div class="modal-header-actions">
<button class="modal-close" onclick="app.closeRalphWizard()" aria-label="Close Ralph wizard">&times;</button>
</div>
</div>
<!-- Wizard Progress Steps -->
<div class="wizard-progress">
<div class="wizard-step active" data-step="1">
<span class="wizard-step-number">1</span>
<span class="wizard-step-label">Describe</span>
</div>
<div class="wizard-step" data-step="2">
<span class="wizard-step-number">2</span>
<span class="wizard-step-label">Plan</span>
</div>
<div class="wizard-step" data-step="3">
<span class="wizard-step-number">3</span>
<span class="wizard-step-label">Launch</span>
</div>
</div>
<div class="modal-body">
<!-- Step 1: Task & Completion Setup -->
<div class="wizard-page wizard-page-compact" id="ralphWizardStep1">
<div class="wizard-step-intro">
<p>Describe your task below. Claude will generate an implementation plan with testing steps.</p>
</div>
<div class="form-row">
<label>What do you want to build?</label>
<div class="textarea-with-assist">
<textarea
id="ralphTaskDescription"
rows="3"
placeholder="Build a REST API for user authentication with JWT tokens, login/logout endpoints, and password reset flow."></textarea>
</div>
</div>
<div class="wizard-inline-row">
<div class="wizard-inline-field">
<label>Working Directory <span id="existingPlanBadge" class="existing-plan-badge" style="display:none"></span></label>
<div class="case-select-inline">
<select id="ralphCaseSelect" class="form-select" onchange="app.onRalphCaseChange()"></select>
<button class="btn-toolbar btn-sm" onclick="app.showCreateCaseModal()">+</button>
</div>
</div>
<div class="wizard-inline-field wizard-inline-field-small">
<label>Iterations</label>
<div class="iteration-presets-compact">
<button type="button" class="iteration-preset-btn active" data-iterations="10" onclick="app.selectIterationPreset(10)">10</button>
<button type="button" class="iteration-preset-btn" data-iterations="25" onclick="app.selectIterationPreset(25)">25</button>
<button type="button" class="iteration-preset-btn" data-iterations="50" onclick="app.selectIterationPreset(50)">50</button>
<button type="button" class="iteration-preset-btn" data-iterations="0" onclick="app.selectIterationPreset(0)">&infin;</button>
</div>
</div>
</div>
<!-- Hidden completion phrase with default -->
<input type="hidden" id="ralphCompletionPhrase" value="COMPLETE">
</div>
<!-- Step 2: Plan Generation -->
<div class="wizard-page wizard-page-compact hidden" id="ralphWizardStep2">
<!-- Existing plan detected (shown when @fix_plan.md exists) -->
<div id="existingPlanSection" class="plan-section hidden">
<!-- Content populated by app.updateExistingPlanUI() -->
</div>
<!-- Loading state (shown during generation) -->
<div id="planGenerationLoading" class="plan-loading-state hidden">
<div class="plan-loading-content">
<div class="plan-spinner"></div>
<div class="plan-loading-text">
<span class="plan-loading-title" id="planLoadingTitle">Starting Opus 4.5...</span>
<span class="plan-loading-time" id="planLoadingTime">0s</span>
</div>
</div>
<p class="plan-loading-hint" id="planLoadingHint">Initializing deep reasoning model</p>
<div class="plan-loading-actions">
<button class="btn-toolbar btn-sm plan-cancel-btn" onclick="app.cancelPlanGeneration()">Stop</button>
<button class="btn-toolbar btn-sm" onclick="app.regeneratePlan()">Regenerate</button>
</div>
<!-- Auto-start toggle visible during generation -->
<div class="plan-autostart-toggle">
<label class="plan-autostart-label">
<input type="checkbox" id="ralphAutoStartStep2" onchange="app.ralphWizardConfig.autoStart = this.checked">
<span class="plan-autostart-text">Auto-launch when plan completes</span>
</label>
</div>
</div>
<!-- Stopped/Error state -->
<div id="planGenerationError" class="plan-section hidden">
<div class="plan-stopped-indicator" id="planStoppedIndicator">
<span class="plan-stopped-icon">&#x25A0;</span>
<span class="plan-stopped-text">Stopped</span>
</div>
<p class="plan-error-msg" id="planErrorMsg"></p>
<div class="plan-actions">
<button class="btn-toolbar btn-primary" onclick="app.generatePlan()">Regenerate Plan</button>
</div>
<p class="plan-skip-hint">Use <strong>Back</strong> to edit your task, or <strong>Next</strong> to continue without a plan</p>
</div>
<!-- Editor state -->
<div id="planEditor" class="plan-section hidden">
<div class="plan-editor-header">
<span>Implementation Plan <span class="plan-stats" id="planStats"></span></span>
<div class="plan-editor-actions">
<div class="plan-option-group">
<label>Detail:</label>
<div class="plan-detail-btns">
<button type="button" class="plan-detail-btn" data-detail="standard" onclick="app.setPlanDetail('standard')" title="Single-pass generation with Opus 4.5">Standard</button>
<button type="button" class="plan-detail-btn active" data-detail="detailed" onclick="app.setPlanDetail('detailed')" title="Enhanced: parallel subagents + verification (slower but more thorough)">Enhanced</button>
</div>
</div>
<button class="btn-toolbar btn-sm" onclick="app.regeneratePlan()">Regenerate</button>
</div>
</div>
<div id="planItemsList" class="plan-items-list"></div>
</div>
</div>
<!-- Step 3: Preview & Launch -->
<div class="wizard-page hidden" id="ralphWizardStep3">
<div class="ralph-preview-section">
<label>Prompt Preview</label>
<div class="ralph-prompt-preview" id="ralphPromptPreview"></div>
</div>
<div class="ralph-config-summary">
<div class="config-summary-row">
<span class="config-label">Completion Phrase:</span>
<code id="summaryPhrase">COMPLETE</code>
</div>
<div class="config-summary-row">
<span class="config-label">Max Iterations:</span>
<span id="summaryIterations">10</span>
</div>
<div class="config-summary-row">
<span class="config-label">Case:</span>
<span id="summaryCase">testcase</span>
</div>
<div class="config-summary-row" style="display: none;">
<span class="config-label">Plan:</span>
<span id="summaryPlan">-</span>
</div>
</div>
<!-- Advanced Options (Collapsible) -->
<details class="advanced-options">
<summary>Advanced Options</summary>
<div class="advanced-options-content">
<div class="form-row form-row-switch">
<label>Enable Respawn</label>
<label class="switch">
<input type="checkbox" id="ralphEnableRespawn">
<span class="slider"></span>
</label>
<span class="form-hint">Auto-restart sessions when context fills up (usually not needed)</span>
</div>
</div>
</details>
</div>
</div>
<!-- Wizard Footer -->
<div class="wizard-footer">
<button class="btn-toolbar" id="ralphBackBtn" onclick="app.ralphWizardBack()" style="display: none;">Back</button>
<div class="wizard-footer-right">
<button class="btn-toolbar" onclick="app.closeRalphWizard()">Cancel</button>
<button class="btn-toolbar btn-primary" id="ralphNextBtn" onclick="app.ralphWizardNext()">Next</button>
<button class="btn-toolbar btn-success" id="ralphStartBtn" onclick="app.startRalphLoop()" style="display: none;">Start Loop</button>
</div>
</div>
</div>
</div>
<!-- Kill All Confirmation Modal -->
<div class="modal" id="killAllModal">
<div class="modal-backdrop" onclick="app.closeKillAllModal()"></div>
<div class="modal-content modal-sm">
<div class="modal-header">
<h3>Kill All Sessions</h3>
<button class="modal-close" onclick="app.closeKillAllModal()" aria-label="Close kill all modal">&times;</button>
</div>
<div class="modal-body">
<p class="modal-warning-text">Choose how to close <span id="killAllCount">0</span> session(s):</p>
</div>
<div class="close-options">
<button class="close-option" onclick="app.confirmKillAll(false)">
<span class="close-option-title">Remove All Tabs</span>
<span class="close-option-desc">Tmux sessions keep running in background</span>
</button>
<button class="close-option close-option-danger" onclick="app.confirmKillAll(true)">
<span class="close-option-title">Kill All Claude + Tmux</span>
<span class="close-option-desc">Terminate everything completely</span>
</button>
</div>
<div class="modal-footer-cancel">
<button class="btn-toolbar btn-sm" onclick="app.closeKillAllModal()">Cancel</button>
</div>
</div>
</div>
<!-- Notification Drawer -->
<div class="notification-drawer" id="notifDrawer">
<div class="notif-drawer-header">
<span class="notif-drawer-title">Notifications</span>
<div class="notif-drawer-actions">
<button class="btn-notif-action" onclick="app.notificationManager?.markAllRead()" title="Mark all read">&#x2713;</button>
<button class="btn-notif-action" onclick="app.notificationManager?.clearAll()" title="Clear all">&#x1F5D1;</button>
<button class="btn-notif-action" onclick="app.toggleNotifications()" title="Close">&times;</button>
</div>
</div>
<div class="notif-drawer-list" id="notifList"></div>
<div class="notif-drawer-empty" id="notifEmpty">No notifications</div>
</div>
<!-- Away Digest Modal -->
<div class="modal" id="awayDigestModal">
<div class="modal-backdrop" onclick="app.closeAwayDigest()"></div>
<div class="modal-content away-digest-modal">
<div class="modal-header">
<h3>Away Digest</h3>
<div class="modal-header-actions">
<button class="btn-toolbar btn-sm" onclick="app.loadAwayDigest()" title="Refresh away digest">&#x21BB; Refresh</button>
<button class="modal-close" onclick="app.closeAwayDigest()" aria-label="Close away digest">&times;</button>
</div>
</div>
<div class="modal-body">
<div class="away-digest-ranges" role="group" aria-label="Away digest range">
<button class="filter-btn active" data-away-range="since-last-visit" onclick="app.setAwayDigestRange('since-last-visit')">Since last visit</button>
<button class="filter-btn" data-away-range="1h" onclick="app.setAwayDigestRange('1h')">Last hour</button>
<button class="filter-btn" data-away-range="today" onclick="app.setAwayDigestRange('today')">Today</button>
<button class="filter-btn" data-away-range="24h" onclick="app.setAwayDigestRange('24h')">24h</button>
<button class="filter-btn" data-away-range="custom" onclick="app.setAwayDigestRange('custom')">Custom</button>
</div>
<div class="away-digest-custom-range" id="awayDigestCustomRange">
<label>
Since
<input type="datetime-local" id="awayDigestCustomSince">
</label>
<label>
Until
<input type="datetime-local" id="awayDigestCustomUntil">
</label>
</div>
<div class="away-digest-summary" id="awayDigestSummary"></div>
<div class="away-digest-freshness" id="awayDigestFreshness"></div>
<div class="away-digest-sections" id="awayDigestSections">
<p class="empty-message">Open the digest to load recent activity</p>
</div>
</div>
</div>
</div>
<!-- Command Palette Modal -->
<div class="modal command-palette-modal" id="commandPaletteModal">
<div class="modal-backdrop" onclick="app.closeCommandPalette()"></div>
<div class="command-palette-shell" role="dialog" aria-modal="true" aria-labelledby="commandPaletteTitle">
<div class="command-palette-input-row">
<span class="command-palette-search-icon" aria-hidden="true">⌕</span>
<input type="search" id="commandPaletteSearch" class="command-palette-search" placeholder="Search open sessions or start a new one" autocomplete="off" maxlength="160" aria-labelledby="commandPaletteTitle">
<kbd>Esc</kbd>
</div>
<div class="command-palette-label" id="commandPaletteTitle">Open sessions</div>
<div id="commandPaletteList" class="command-palette-list"></div>
</div>
</div>
<!-- Session Manager Modal -->
<div class="modal" id="sessionManagerModal">
<div class="modal-backdrop" onclick="app.closeSessionManager()"></div>
<div class="modal-content session-manager-modal">
<div class="modal-header">
<h3>Sessions</h3>
<button class="modal-close" onclick="app.closeSessionManager()" aria-label="Close session manager">&times;</button>
</div>
<div class="modal-body">
<input type="search" id="sessionManagerSearch" class="search-input" placeholder="Search sessions by name, prompt, or path&hellip;" autocomplete="off" maxlength="200">
<div id="sessionManagerList" class="session-manager-list"></div>
</div>
</div>
</div>
<!-- Token Stats Modal -->
<div class="modal" id="tokenStatsModal">
<div class="modal-backdrop" onclick="app.closeTokenStats()"></div>
<div class="modal-content token-stats-modal">
<div class="modal-header">
<h3>Token Usage Statistics</h3>
<button class="modal-close" onclick="app.closeTokenStats()" aria-label="Close token statistics">&times;</button>
</div>
<div class="modal-body">
<div class="stats-summary" id="statsSummary"></div>
<div class="stats-chart-container">
<div class="stats-chart-label">Last 7 Days</div>
<div class="stats-chart" id="statsChart"></div>
<div class="stats-chart-days" id="statsChartDays"></div>
</div>
<div class="stats-table-container">
<div class="stats-table-label">Daily Breakdown</div>
<div class="stats-table" id="statsTable"></div>
</div>
</div>
</div>
</div>
</div>
<!-- Save Preset Modal -->
<div class="modal" id="savePresetModal">
<div class="modal-backdrop" onclick="app.closeSavePresetModal()"></div>
<div class="modal-content modal-sm">
<div class="modal-header">
<h3>Save Respawn Preset</h3>
<button class="modal-close" onclick="app.closeSavePresetModal()" aria-label="Close">&times;</button>
</div>
<div class="modal-body">
<div class="form-row">
<label>Preset Name</label>
<input type="text" id="presetNameInput" placeholder="My Custom Preset" maxlength="30">
</div>
<div class="form-row">
<label>Description (optional)</label>
<input type="text" id="presetDescriptionInput" placeholder="When to use this preset" maxlength="100">
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" onclick="app.closeSavePresetModal()">Cancel</button>
<button class="btn btn-primary" onclick="app.confirmSavePreset()">Save Preset</button>
</div>
</div>
</div>
<!-- Session Lifecycle Audit Log (floating window) -->
<div id="lifecycleWindow" style="display:none;position:fixed;z-index:2000;top:60px;left:50%;transform:translateX(-50%);width:900px;max-width:95vw;min-width:400px;min-height:250px;height:520px;background:#111;border:1px solid #333;border-radius:8px;box-shadow:0 8px 32px rgba(0,0,0,0.6);resize:both;overflow:hidden">
<div id="lifecycleWindowHeader" style="display:flex;align-items:center;justify-content:space-between;padding:6px 12px;background:#1a1a2e;border-bottom:1px solid #333;cursor:move;user-select:none;border-radius:8px 8px 0 0">
<span style="color:#e0e0e0;font-size:13px;font-weight:600">Session Lifecycle Log</span>
<div style="display:flex;gap:8px;align-items:center">
<div style="display:flex;gap:6px;flex-wrap:nowrap">
<select id="lifecycleFilterEvent" style="background:#0d0d0d;color:#e0e0e0;border:1px solid #444;border-radius:4px;padding:2px 6px;font-size:11px">
<option value="">All Events</option>
<option value="created">Created</option>
<option value="started">Started</option>
<option value="exit">Exit</option>
<option value="deleted">Deleted</option>
<option value="recovered">Recovered</option>
<option value="stale_cleaned">Stale Cleaned</option>
<option value="mux_died">Mux Died</option>
<option value="server_started">Server Started</option>
<option value="server_stopped">Server Stopped</option>
</select>
<input id="lifecycleFilterSession" type="text" placeholder="Session ID..." style="background:#0d0d0d;color:#e0e0e0;border:1px solid #444;border-radius:4px;padding:2px 6px;font-size:11px;width:120px">
<button onclick="app.loadLifecycleLog()" style="background:#333;color:#ccc;border:1px solid #444;border-radius:4px;padding:2px 8px;font-size:11px;cursor:pointer">Refresh</button>
</div>
<button onclick="app.closeLifecycleLog()" style="background:none;border:none;color:#888;font-size:18px;cursor:pointer;padding:0 4px;line-height:1" aria-label="Close">&times;</button>
</div>
</div>
<div style="overflow:auto;height:calc(100% - 36px);padding:4px 0">
<table id="lifecycleTable" style="width:100%;border-collapse:collapse;font-size:12px;font-family:monospace">
<thead>
<tr style="color:#888;text-align:left;border-bottom:1px solid #333;position:sticky;top:0;background:#111">
<th style="padding:4px 8px">Time</th>
<th style="padding:4px 8px">Event</th>
<th style="padding:4px 8px">Session</th>
<th style="padding:4px 8px">Reason</th>
<th style="padding:4px 8px">Extra</th>
</tr>
</thead>
<tbody id="lifecycleTableBody"></tbody>
</table>
<div id="lifecycleEmpty" style="color:#666;text-align:center;padding:20px;display:none">No lifecycle entries found</div>
</div>
</div>
<!-- SVG Overlay for Subagent Connection Lines -->
<svg id="connectionLines" class="connection-lines-svg">
<!-- Lines drawn dynamically -->
</svg>
<!-- Connection-loss overlay: the app shell is served from the service-worker
cache, so Codeman renders even with nothing reachable. Without this, that
looks like an empty dashboard rather than a dead connection. Only shown
while no server state has loaded this page load. -->
<div class="offline-overlay" id="offlineOverlay" hidden>
<div class="offline-overlay-card" role="alertdialog" aria-labelledby="offlineOverlayTitle" aria-describedby="offlineOverlayBody">
<div class="offline-overlay-icon" aria-hidden="true">
<svg width="46" height="46" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">
<path d="M1 1l22 22"/>
<path d="M16.72 11.06A10.94 10.94 0 0 1 19 12.55"/>
<path d="M5 12.55a10.94 10.94 0 0 1 5.17-2.39"/>
<path d="M10.71 5.05A16 16 0 0 1 22.58 9"/>
<path d="M1.42 9a15.91 15.91 0 0 1 4.7-2.88"/>
<path d="M8.53 16.11a6 6 0 0 1 6.95 0"/>
<line x1="12" y1="20" x2="12.01" y2="20"/>
</svg>
</div>
<h2 class="offline-overlay-title" id="offlineOverlayTitle">Can't reach the Codeman server</h2>
<p class="offline-overlay-body" id="offlineOverlayBody"></p>
<div class="offline-overlay-host" id="offlineOverlayHost"></div>
<ul class="offline-overlay-hints">
<li>Check Wi-Fi or mobile data</li>
<li>Check your VPN / Tailscale is connected</li>
<li>Check the Codeman server is still running</li>
</ul>
<div class="offline-overlay-actions">
<button class="offline-overlay-btn offline-overlay-btn--primary" id="offlineOverlayRetry" onclick="app.retryConnection()">Retry now</button>
<button class="offline-overlay-btn" onclick="app.dismissOfflineOverlay()">Show cached view</button>
</div>
<div class="offline-overlay-status" id="offlineOverlayStatus">Retrying…</div>
</div>
</div>
<!-- Approvals Inbox drawer (populated by approvals-ui.js; opened from the header bell) -->
<!-- Read My Mind: predicted-next-prompt modal (readmymind-ui.js). Suggestion
text is set via value/textContent only: predictor output derives from
observable (injectable) content, and the explicit click here is the
security boundary (nothing is ever auto-sent). -->
<div class="modal" id="readMyMindModal">
<div class="modal-backdrop" onclick="app.closeReadMyMind()"></div>
<div class="modal-content readmymind-modal">
<div class="modal-header">
<h3><span aria-hidden="true">🧠</span> Read My Mind</h3>
<button class="modal-close" onclick="app.closeReadMyMind()" aria-label="Close">&times;</button>
</div>
<div class="modal-body">
<div class="readmymind-loading">Reading your mind…</div>
<div class="readmymind-result" style="display:none">
<div class="readmymind-suggestion-row">
<span class="readmymind-kind" id="readMyMindKind" data-i18n-skip>continue</span>
<input type="text" id="readMyMindPrompt" class="readmymind-prompt-input" data-i18n-skip aria-label="Predicted prompt, editable" onkeydown="if(event.key==='Enter')app.sendReadMyMind(true)">
</div>
<div class="readmymind-why" id="readMyMindWhy" data-i18n-skip></div>
<div class="readmymind-alternates" id="readMyMindAlternates" data-i18n-skip style="display:none"></div>
</div>
<div class="readmymind-error" style="display:none">No suggestion this time. Rethink to try again.</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" onclick="app.closeReadMyMind()">Dismiss</button>
<button class="btn btn-secondary" id="readMyMindRethink" onclick="app.rethinkReadMyMind()">Rethink</button>
<button class="btn btn-secondary" onclick="app.sendReadMyMind(false)" title="Put the text on the session's composer without submitting it">Insert</button>
<button class="btn btn-primary" onclick="app.sendReadMyMind(true)">Send</button>
</div>
</div>
</div>
<div class="approvals-drawer" id="approvalsDrawer" role="complementary" aria-label="Approvals inbox">
<div class="approvals-header">
<div>
<div class="approvals-title">Approvals</div>
<div class="approvals-subtitle">Prompts waiting on you, across all sessions</div>
</div>
<button class="approvals-close" onclick="app.closeApprovalsInbox()" title="Close" aria-label="Close approvals inbox">✕</button>
</div>
<div class="approvals-list"></div>
</div>
<script defer src="constants.js"></script>
<script defer src="i18n.js"></script>
<script defer src="mobile-handlers.js"></script>
<script defer src="voice-input.js"></script>
<script defer src="notification-manager.js"></script>
<script defer src="keyboard-accessory.js"></script>
<script defer src="input-cjk.js"></script>
<!-- Hardened markdown HTML sanitizer (wires DOMPurify). Must precede app.js. -->
<script defer src="sanitize-html.js"></script>
<script defer src="app.js"></script>
<script defer src="terminal-ui.js"></script>
<script defer src="respawn-ui.js"></script>
<script defer src="ralph-panel.js"></script>
<script defer src="orchestrator-panel.js"></script>
<script defer src="cron-ui.js"></script>
<script defer src="settings-ui.js"></script>
<script defer src="panels-ui.js"></script>
<script defer src="readmymind-ui.js"></script>
<script defer src="ultracode-panel.js"></script>
<script defer src="approvals-ui.js"></script>
<script defer src="admin-ui.js"></script>
<script defer src="session-ui.js"></script>
<script defer src="webview-tabs.js"></script>
<script defer src="mobile-overview.js"></script>
<script defer src="home-sessions.js"></script>
<script defer src="entrance-animations.js"></script>
<script defer src="ralph-wizard.js"></script>
<script defer src="api-client.js"></script>
<script defer src="subagent-windows.js"></script>
<script defer src="ultracode-windows.js"></script>
<script defer src="image-input.js"></script>
</body>
</html>