Files
Codeman/src/web/public/index.html
T
Codeman maintainer 25c67f9415 Merge master into PR #156 (remote tmux durability)
Resolutions:
- session.ts: keep the extracted _buildRespawnPaneOptions() helper (COD-108)
  and add master's docker/owner fields to it
- tmux-manager.ts: docker branch first, then remote via buildRemoteSessionCommand
  (now an options object threading claudeMode/allowedTools into
  buildRemoteLaunchCommand, preserving the 6.3 multi-user permission downgrade)
- case-routes.ts: keep master's adminOnly helper; gate the new COD-105 discovery
  endpoint admin-only in multi-user mode (hosts are machine-level infra)
- settings-ui.js: union of remoteAutoReconnect + master's header-button defaults
- session-routes.ts: union of imports; session gets remote + owner

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 14:27:02 +02:00

2526 lines
150 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/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');if(s!=='og'&&s!=='daylight-green'&&s!=='daylight-blue')s='daylight-blue';document.documentElement.dataset.skin=s;window.__codemanSkin=s;}catch(e){document.documentElement.dataset.skin='daylight-blue';window.__codemanSkin='daylight-blue';}</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:#11151c}
.skeleton-header{height:40px;background:rgba(31,38,48,0.85);border-bottom:1px solid rgba(255,255,255,0.08);display:flex;align-items:center;padding:0 12px}
.skeleton-brand{color:#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:rgba(255,255,255,0.04);border-radius:6px}
.skeleton-terminal{flex:1;background:#161b23}
.skeleton-toolbar{height:42px;background:rgba(31,38,48,0.85);border-top:1px solid 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">Codeman</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-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-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 btn-file-viewer--hidden" 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" 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" title="Total tokens across all sessions">0 tokens</div>
</div>
</header>
<!-- 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>
<!-- Welcome Overlay (shown when no session active) -->
<div class="welcome-overlay" id="welcomeOverlay">
<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" 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" 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" 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-gemini" 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>
<h3 class="history-title" id="historyTitle">Resume Conversation</h3>
<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>
</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" 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" 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-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>
<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>
<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" 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>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>
<!-- 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>
</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">
<div class="modal-header">
<h3>App Settings</h3>
<button class="modal-close" onclick="app.closeAppSettings()" aria-label="Close app settings">&times;</button>
</div>
<div class="modal-tabs">
<button class="modal-tab-btn active" data-tab="settings-display">Display</button>
<button class="modal-tab-btn" data-tab="settings-claude">Claude CLI</button>
<button class="modal-tab-btn" data-tab="settings-codex">Codex CLI</button>
<button class="modal-tab-btn" data-tab="settings-models">Models</button>
<button class="modal-tab-btn" data-tab="settings-paths">Paths</button>
<button class="modal-tab-btn" data-tab="settings-notifications">Notifications</button>
<button class="modal-tab-btn" data-tab="settings-voice">Voice</button>
<button class="modal-tab-btn" data-tab="settings-shortcuts">Shortcuts</button>
</div>
<div class="modal-body">
<!-- Display Tab -->
<div class="modal-tab-content" id="settings-display">
<div class="settings-grid">
<!-- Appearance Section -->
<div class="settings-section-header">Appearance</div>
<div class="settings-item settings-item-skin" title="Visual theme for this device (not synced)">
<span class="settings-item-label">Skin</span>
<select id="appSettingsSkin" class="form-select">
<option value="daylight-blue">Daylight Blue</option>
<option value="daylight-green">Daylight Green</option>
<option value="og">OG Codeman</option>
</select>
</div>
<div class="settings-item" id="appSettingsWebglRendererItem" title="Use the GPU-accelerated WebGL terminal renderer (desktop only). Turn off to force the DOM renderer if you hit GPU glitches. Codeman also auto-falls-back to the DOM renderer after repeated GPU stalls.">
<span class="settings-item-label">WebGL Renderer</span>
<label class="switch switch-sm">
<input type="checkbox" id="appSettingsWebglRenderer">
<span class="slider"></span>
</label>
</div>
<!-- Input Section -->
<div class="settings-section-header">Input</div>
<div class="settings-item settings-item-multiline" title="Scroll the terminal's own local scrollback with a plain mouse wheel / two-finger swipe, instead of forwarding the wheel to the CLI's transcript. Turn on if scrolling back through history doesn't work (e.g. macOS trackpad in Claude sessions). Shift+wheel always reaches local scrollback regardless.">
<div class="settings-item-text">
<span class="settings-item-label">Wheel Scrolls Local History</span>
<span class="settings-item-desc">Plain wheel/trackpad pages the terminal scrollback</span>
</div>
<label class="switch switch-sm">
<input type="checkbox" id="appSettingsTerminalWheelLocal">
<span class="slider"></span>
</label>
</div>
<div class="settings-item settings-item-multiline" title="Shows typed characters instantly via overlay while forwarding keystrokes to the server in the background. Enables Tab completion, preserves input across tab switches, and protects against session crashes. Recommended for mobile and high-latency connections.">
<div class="settings-item-text">
<span class="settings-item-label">Local Echo</span>
<span class="settings-item-desc">Instant typing feedback with local echo</span>
</div>
<label class="switch switch-sm">
<input type="checkbox" id="appSettingsLocalEcho">
<span class="slider"></span>
</label>
</div>
<div class="settings-item settings-item-multiline" title="Show a dedicated input field below the terminal for CJK (Chinese/Japanese/Korean) IME composition. Recommended for mobile devices with Chinese input methods where xterm's native input handling may drop characters.">
<div class="settings-item-text">
<span class="settings-item-label">CJK Input</span>
<span class="settings-item-desc">Dedicated IME input field for CJK languages</span>
</div>
<label class="switch switch-sm">
<input type="checkbox" id="appSettingsCjkInput">
<span class="slider"></span>
</label>
</div>
<div class="settings-item settings-item-multiline" title="Show additional buttons (Tab, Shift+Tab, Ctrl+O, Esc, Alt+Enter, left/right arrows) in the mobile keyboard accessory bar.">
<div class="settings-item-text">
<span class="settings-item-label">Extended Keyboard Bar</span>
<span class="settings-item-desc">Extra keys: Tab, Esc, arrows, Ctrl+O</span>
</div>
<label class="switch switch-sm">
<input type="checkbox" id="appSettingsExtendedKeyboardBar">
<span class="slider"></span>
</label>
</div>
<div class="settings-item settings-item-multiline" id="appSettingsGestureControlItem" title="Enable the camera hand-tracking gesture overlay (applied on reload). The instance must run with CODEMAN_GESTURE=1.">
<div class="settings-item-text">
<span class="settings-item-label">Gesture Control (beta)</span>
<span class="settings-item-desc">Camera hand-tracking overlay (applied on reload)</span>
</div>
<label class="switch switch-sm">
<input type="checkbox" id="appSettingsGestureControl">
<span class="slider"></span>
</label>
</div>
<!-- Header Displays Section -->
<div class="settings-section-header">Header Displays</div>
<div class="settings-item" title="Show Claude plan usage limits (5-hour & weekly) in the header. Applies to newly created sessions.">
<span class="settings-item-label">Plan Usage Limits</span>
<label class="switch switch-sm">
<input type="checkbox" id="appSettingsShowPlanUsageLimits">
<span class="slider"></span>
</label>
</div>
<div class="settings-item" title="Show A-/A+ font size buttons in header">
<span class="settings-item-label">Font Controls</span>
<label class="switch switch-sm">
<input type="checkbox" id="appSettingsShowFontControls">
<span class="slider"></span>
</label>
</div>
<div class="settings-item" title="Show CPU and memory usage in header">
<span class="settings-item-label">System Stats</span>
<label class="switch switch-sm">
<input type="checkbox" id="appSettingsShowSystemStats" checked>
<span class="slider"></span>
</label>
</div>
<div class="settings-item" title="Show session lifecycle log button in header">
<span class="settings-item-label">Lifecycle Log</span>
<label class="switch switch-sm">
<input type="checkbox" id="appSettingsShowLifecycleLog">
<span class="slider"></span>
</label>
</div>
<div class="settings-item" title="Show the response viewer (eye) button in header">
<span class="settings-item-label">Response Viewer</span>
<label class="switch switch-sm">
<input type="checkbox" id="appSettingsShowResponseViewer">
<span class="slider"></span>
</label>
</div>
<div class="settings-item" title="Show the file viewer button in header (opens the file browser panel for the active session)">
<span class="settings-item-label">File Viewer</span>
<label class="switch switch-sm">
<input type="checkbox" id="appSettingsShowFileViewerButton">
<span class="slider"></span>
</label>
</div>
<div class="settings-item" title="Show the attachments button in header (opens the attachment history drawer)">
<span class="settings-item-label">Attachments Button</span>
<label class="switch switch-sm">
<input type="checkbox" id="appSettingsShowAttachmentsButton">
<span class="slider"></span>
</label>
</div>
<div class="settings-item" title="Show the multi-monitor button in the header (opens Codeman spanned across all displays)">
<span class="settings-item-label">Multi-monitor Button</span>
<label class="switch switch-sm">
<input type="checkbox" id="appSettingsShowMultiMonitorButton">
<span class="slider"></span>
</label>
</div>
<div class="settings-item" title="Show the session manager button in the header (opens the session manager — sessions also stay reachable via the Ctrl+K palette)">
<span class="settings-item-label">Session Manager Button</span>
<label class="switch switch-sm">
<input type="checkbox" id="appSettingsShowSessionButton">
<span class="slider"></span>
</label>
</div>
<div class="settings-item" title="Show the away digest button in the header (opens the 'what happened while you were away' summary)">
<span class="settings-item-label">Away Digest Button</span>
<label class="switch switch-sm">
<input type="checkbox" id="appSettingsShowAwayDigestButton">
<span class="slider"></span>
</label>
</div>
<div class="settings-item" title="Show the Cron button in the footer toolbar (opens the cron jobs manager)">
<span class="settings-item-label">Cron Button</span>
<label class="switch switch-sm">
<input type="checkbox" id="appSettingsShowCronButton">
<span class="slider"></span>
</label>
</div>
<div class="settings-item" title="Show a terminal redraw button in the header — refit the terminal to the current screen size (useful when switching between devices)">
<span class="settings-item-label">Redraw Terminal Button</span>
<label class="switch switch-sm">
<input type="checkbox" id="appSettingsShowRedrawButton">
<span class="slider"></span>
</label>
</div>
<!-- Tab Bar Section -->
<div class="settings-section-header">Tab Bar</div>
<div class="settings-item" title="Show folder path below tab name and allow tab bar to wrap into multiple rows">
<span class="settings-item-label">Tall Tabs (Name + Folder)</span>
<label class="switch switch-sm">
<input type="checkbox" id="appSettingsTabTwoRows">
<span class="slider"></span>
</label>
</div>
<!-- Panels Section -->
<div class="settings-section-header">Panels</div>
<div class="settings-item" title="Show Monitor panel at bottom right">
<span class="settings-item-label">Monitor</span>
<label class="switch switch-sm">
<input type="checkbox" id="appSettingsShowMonitor" checked>
<span class="slider"></span>
</label>
</div>
<div class="settings-item" title="Show active tools and file viewers in a floating panel">
<span class="settings-item-label">Project Insights</span>
<label class="switch switch-sm">
<input type="checkbox" id="appSettingsShowProjectInsights">
<span class="slider"></span>
</label>
</div>
<div class="settings-item" title="Show file browser panel on the right side">
<span class="settings-item-label">File Browser</span>
<label class="switch switch-sm">
<input type="checkbox" id="appSettingsShowFileBrowser">
<span class="slider"></span>
</label>
</div>
<div class="settings-item" title="Show the Subagents panel (independent from Monitor)">
<span class="settings-item-label">Subagents</span>
<label class="switch switch-sm">
<input type="checkbox" id="appSettingsShowSubagents">
<span class="slider"></span>
</label>
</div>
<div class="settings-item" title="Show ultracode / Workflow runs as a master-detail tab (tasks on the left, agents with tokens + tool calls on the right)">
<span class="settings-item-label">Ultracode Agents</span>
<label class="switch switch-sm">
<input type="checkbox" id="appSettingsShowUltracodeAgents">
<span class="slider"></span>
</label>
</div>
<div class="settings-item" title="Pop a floating window for each active ultracode / Workflow run, connected by a line to its session tab (additional to the Ultracode Agents panel)">
<span class="settings-item-label">Ultracode Floating Windows</span>
<label class="switch switch-sm">
<input type="checkbox" id="appSettingsUltracodeFloatingWindows">
<span class="slider"></span>
</label>
</div>
<!-- Subagent Options Section -->
<div class="settings-section-header">Subagent Options</div>
<div class="settings-item" title="Monitor Claude Code background agents in real-time">
<span class="settings-item-label">Enable Tracking</span>
<label class="switch switch-sm">
<input type="checkbox" id="appSettingsSubagentTracking" checked>
<span class="slider"></span>
</label>
</div>
<div class="settings-item" title="Only show subagent windows when their parent tab is selected">
<span class="settings-item-label">Active Tab Only</span>
<label class="switch switch-sm">
<input type="checkbox" id="appSettingsSubagentActiveTabOnly" checked>
<span class="slider"></span>
</label>
</div>
<!-- Image Watcher Options Section -->
<div class="settings-section-header">Image Watcher</div>
<div class="settings-item" title="Automatically detect and popup new images in session directories">
<span class="settings-item-label">Enable Globally</span>
<label class="switch switch-sm">
<input type="checkbox" id="appSettingsImageWatcherEnabled">
<span class="slider"></span>
</label>
</div>
<!-- Remote Access Section -->
<div class="settings-section-header">Remote Access</div>
<div class="settings-item" title="Expose Codeman via Cloudflare Tunnel for remote access">
<span class="settings-item-label">Cloudflare Tunnel</span>
<label class="switch switch-sm">
<input type="checkbox" id="appSettingsTunnelEnabled">
<span class="slider"></span>
</label>
</div>
<div class="settings-item" id="tunnelUrlRow" style="display:none">
<span class="settings-item-label">Tunnel URL</span>
<div style="display:flex; align-items:center; gap:8px">
<span id="tunnelUrlDisplay" class="settings-item-value" style="cursor:pointer; text-decoration:underline; font-family:monospace; font-size:12px" title="Click to copy"></span>
<button class="btn-icon-sm" id="tunnelQrBtn" onclick="app.showTunnelQR()" title="Show QR code" style="font-size:16px; padding:2px 6px; background:none; border:1px solid var(--border); border-radius:4px; cursor:pointer; color:var(--text-secondary)">⊞</button>
</div>
</div>
<div class="settings-item" id="tunnelUploadUrlRow" style="display:none">
<span class="settings-item-label">Upload URL</span>
<span id="tunnelUploadUrlDisplay" class="settings-item-value" style="cursor:pointer; text-decoration:underline; font-family:monospace; font-size:12px" title="Click to copy"></span>
</div>
<!-- Updates Section -->
<div class="settings-section-header">Updates</div>
<div class="settings-item" title="Codeman version currently running">
<span class="settings-item-label">Current Version</span>
<span class="settings-item-value" id="updateCurrentVersion" style="font-family:monospace">&mdash;</span>
</div>
<div class="settings-item" id="updateCheckRow" title="Check GitHub for a newer Codeman release">
<span class="settings-item-label">Check for Updates</span>
<button class="btn-toolbar btn-sm" id="updateCheckBtn" onclick="app.checkForUpdate()">Check now</button>
</div>
<div id="updateResult" style="display:none; padding:4px 2px 8px; font-size:13px; color:var(--text-secondary)"></div>
<div class="settings-item" id="updateActionRow" style="display:none">
<span class="settings-item-label" id="updateActionLabel">Update available</span>
<button class="btn-toolbar btn-sm btn-primary" id="updateNowBtn" onclick="app.startSelfUpdate()">Update now</button>
</div>
<div id="updateNotes" style="display:none; max-height:160px; overflow:auto; padding:8px 10px; margin:4px 0 8px; font-size:12px; line-height:1.45; white-space:pre-wrap; word-break:break-word; background:rgba(127,127,127,0.08); border:1px solid var(--border); border-radius:6px"></div>
<div id="updateProgress" style="display:none; padding:8px 10px; margin:4px 0 8px; font-size:13px; border:1px solid var(--border); border-radius:6px"></div>
</div>
</div>
<!-- Tab-Switch Tab -->
<!-- Claude CLI Tab -->
<div class="modal-tab-content hidden" id="settings-claude">
<div class="form-row">
<label>Startup Mode</label>
<select id="appSettingsClaudeMode" class="form-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>
<span class="form-hint">How Claude CLI is started in screen sessions. Auto Mode runs without routine prompts behind a background safety classifier (needs Claude Code 2.1.207+ and Opus 4.6+/Sonnet 4.6+/Fable 5)</span>
</div>
<div class="form-row" id="allowedToolsRow" style="display: none;">
<label>Allowed Tools</label>
<input type="text" id="appSettingsAllowedTools" placeholder="Read,Glob,Grep,Bash">
<span class="form-hint">Comma-separated list of tools to allow</span>
</div>
<div class="form-row form-row-switch">
<label>Enable Ralph / Todo Tracker</label>
<label class="switch">
<input type="checkbox" id="appSettingsRalphEnabled">
<span class="slider"></span>
</label>
<span class="form-hint">Auto-enable for new sessions (otherwise auto-enables on Ralph pattern detection)</span>
</div>
<!-- Claude Permissions Section -->
<div class="form-section-header">Claude Permissions</div>
<div class="form-row form-row-switch">
<label>Agent Teams</label>
<label class="switch">
<input type="checkbox" id="appSettingsAgentTeams">
<span class="slider"></span>
</label>
<span class="form-hint">Enable experimental Agent Teams for all new Claude sessions (disabled by default)</span>
</div>
<div class="form-row">
<label>Claude Model</label>
<select id="appSettingsClaudeModel" class="form-select">
<option value="">Default (CLI setting)</option>
<option value="claude-fable-5[1m]">Fable 5 (1M context)</option>
<option value="claude-fable-5">Fable 5</option>
<option value="opus[1m]">Opus (1M context)</option>
<option value="opus">Opus</option>
<option value="claude-opus-4-6[1m]">Opus 4.6 (1M context)</option>
<option value="claude-opus-4-6">Opus 4.6</option>
<option value="sonnet">Sonnet</option>
<option value="haiku">Haiku</option>
</select>
<span class="form-hint">Model for new Claude sessions (pinned via the case's .claude/settings.local.json) — takes precedence over the 1M Opus toggle below</span>
</div>
<div class="form-row form-row-switch">
<label>1M Opus Context</label>
<label class="switch">
<input type="checkbox" id="appSettingsOpusContext1m">
<span class="slider"></span>
</label>
<span class="form-hint">Use 1M token context window (model: opus[1m]) for all new sessions — ignored when a Claude Model is selected above</span>
</div>
<div class="form-row form-row-switch">
<label>Remote auto-reconnect</label>
<label class="switch">
<input type="checkbox" id="appSettingsRemoteAutoReconnect">
<span class="slider"></span>
</label>
<span class="form-hint">Automatically re-establish remote (SSH) sessions when the connection drops, reattaching to the durable remote tmux session (on by default; bounded backoff)</span>
</div>
<div class="form-row">
<label>Thinking Effort</label>
<select id="appSettingsThinkingEffort" class="form-select">
<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 (multi-agent workflows)</option>
</select>
<span class="form-hint">Default effort for new Claude sessions — soft default, switchable anytime in-session via /effort (e.g. /effort ultracode)</span>
</div>
<!-- Nice Priority Section -->
<div class="form-section-header">Nice Priority</div>
<div class="form-row form-row-switch">
<label>Enable Nice Priority Reduction</label>
<label class="switch">
<input type="checkbox" id="appSettingsNiceEnabled">
<span class="slider"></span>
</label>
<span class="form-hint">Lower priority of Claude sessions (reduces system impact, only affects new sessions)</span>
</div>
<div class="form-row" id="niceValueRow">
<label>Nice Value</label>
<input type="number" id="appSettingsNiceValue" min="-20" max="19" value="10" style="width: 80px;">
<span class="form-hint">Process priority (-20 to 19, higher = lower priority, default: 10)</span>
</div>
</div>
<!-- Codex CLI Tab -->
<div class="modal-tab-content hidden" id="settings-codex">
<div class="form-section-header">Codex CLI</div>
<div class="form-row form-row-switch">
<label>Bypass Approvals and Sandbox</label>
<label class="switch">
<input type="checkbox" id="appSettingsCodexDangerouslyBypassApprovals">
<span class="slider"></span>
</label>
<span class="form-hint">Start new Codex sessions with --dangerously-bypass-approvals-and-sandbox</span>
</div>
</div>
<!-- Models Tab -->
<div class="modal-tab-content hidden" id="settings-models">
<div class="form-row">
<label>Default Model</label>
<select id="appSettingsDefaultModel" class="form-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 & cheap)</option>
</select>
<span class="form-hint">Model used for execution tasks. Optimizer suggestions are advisory only.</span>
</div>
<div class="form-row form-row-switch">
<label>Show Optimizer Recommendations</label>
<label class="switch">
<input type="checkbox" id="appSettingsShowModelRecommendations" checked>
<span class="slider"></span>
</label>
<span class="form-hint">Show what the optimizer recommends (doesn't override your choice)</span>
</div>
<div class="form-section-header">Agent Type Overrides</div>
<p class="form-hint" style="margin: 0 0 12px 0;">Optionally set specific models for each task type. Leave as "Use Default" to use your default model.</p>
<div class="form-row">
<label>Explore Tasks</label>
<select id="appSettingsModelExplore" class="form-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>
<span class="form-hint">Quick searches, codebase exploration</span>
</div>
<div class="form-row">
<label>Implement Tasks</label>
<select id="appSettingsModelImplement" class="form-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>
<span class="form-hint">Code writing, feature implementation</span>
</div>
<div class="form-row">
<label>Test Tasks</label>
<select id="appSettingsModelTest" class="form-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>
<span class="form-hint">Writing and running tests</span>
</div>
<div class="form-row">
<label>Review Tasks</label>
<select id="appSettingsModelReview" class="form-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>
<span class="form-hint">Code review, quality checks</span>
</div>
</div>
<!-- Paths Tab -->
<div class="modal-tab-content hidden" id="settings-paths">
<div class="form-row">
<label>Default CLAUDE.md Template</label>
<input type="text" id="appSettingsClaudeMdPath" placeholder="/path/to/CLAUDE.md template">
<span class="form-hint">Used when creating new cases. Leave empty for built-in template.</span>
</div>
<div class="form-row">
<label>Default Working Directory</label>
<input type="text" id="appSettingsDefaultDir" placeholder="~/projects">
<span class="form-hint">Default directory for new sessions.</span>
</div>
</div>
<!-- Notifications Tab -->
<div class="modal-tab-content hidden" id="settings-notifications">
<div class="settings-grid">
<!-- Master Control Section -->
<div class="settings-section-header">Master Control</div>
<div class="settings-item" title="Master toggle for all notification layers">
<span class="settings-item-label">Enable Notifications</span>
<label class="switch switch-sm">
<input type="checkbox" id="appSettingsNotifEnabled" checked>
<span class="slider"></span>
</label>
</div>
<div class="settings-item" title="Show OS-level notifications when tab is hidden">
<span class="settings-item-label">Browser</span>
<div class="settings-item-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>
<span class="form-hint" style="grid-column: 1 / -1; margin-top: -8px;">For remote access, HTTPS is required. Start with: <code>codeman web --https</code></span>
<div class="settings-item" title="OS-level push notifications — works even when tab is closed">
<span class="settings-item-label">Push</span>
<div class="settings-item-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/unsubscribe to push notifications">Subscribe</button>
<span class="settings-status" id="pushSubscriptionStatus">off</span>
</div>
</div>
<span class="form-hint" style="grid-column: 1 / -1; margin-top: -8px;">Works through Cloudflare tunnel. Requires HTTPS or localhost. iOS requires PWA (Add to Home Screen).</span>
<!-- Alerts Section -->
<div class="settings-section-header">Alerts</div>
<div class="settings-item" title="Play a short beep for critical events">
<span class="settings-item-label">Audio Alerts</span>
<label class="switch switch-sm">
<input type="checkbox" id="appSettingsNotifAudio">
<span class="slider"></span>
</label>
</div>
<div class="settings-item" title="Notify when a session is idle longer than this">
<span class="settings-item-label">Idle Threshold</span>
<div class="settings-item-actions">
<input type="number" id="appSettingsNotifStuckMins" class="settings-item-input" value="10" min="1" max="120">
<span style="font-size: 0.65rem; color: var(--text-muted);">min</span>
</div>
</div>
<!-- Notification Levels Section -->
<div class="settings-section-header">Notification Levels</div>
</div>
<div class="settings-grid settings-grid-3col">
<div class="settings-item" title="Errors, crashes, agent failures">
<span class="settings-item-label">Critical</span>
<label class="switch switch-sm">
<input type="checkbox" id="appSettingsNotifCritical" checked>
<span class="slider"></span>
</label>
</div>
<div class="settings-item" title="Completions, budget warnings, stuck sessions">
<span class="settings-item-label">Warning</span>
<label class="switch switch-sm">
<input type="checkbox" id="appSettingsNotifWarning" checked>
<span class="slider"></span>
</label>
</div>
<div class="settings-item" title="Auto-accepts, auto-clears, agent completions">
<span class="settings-item-label">Info</span>
<label class="switch switch-sm">
<input type="checkbox" id="appSettingsNotifInfo" checked>
<span class="slider"></span>
</label>
</div>
</div>
<!-- Event Type Preferences -->
<div class="settings-grid" style="margin-top: 12px;">
<div class="settings-section-header">Per-Event Settings</div>
</div>
<div class="event-type-grid">
<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" checked>
<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>
<!-- Voice Tab -->
<div class="modal-tab-content hidden" id="settings-voice">
<div class="settings-grid">
<div class="settings-section-header">Provider</div>
<div class="settings-item">
<span class="settings-item-label">Active Provider</span>
<span class="voice-provider-status" id="voiceProviderStatus">—</span>
</div>
<div class="settings-section-header">Behavior</div>
<div class="settings-item" style="grid-column: 1 / -1;">
<span class="settings-item-label">Insert mode</span>
<select id="voiceInsertMode" class="form-select" style="width: auto;">
<option value="direct">Direct to input</option>
<option value="compose">Compose dialog</option>
</select>
</div>
<div class="settings-section-header">Deepgram Nova-3</div>
<div class="form-row" style="grid-column: 1 / -1;">
<label for="voiceDeepgramKey">API Key</label>
<div style="display: flex; gap: 6px; align-items: center;">
<input type="password" id="voiceDeepgramKey" placeholder="Enter Deepgram API key" autocomplete="off" style="flex: 1; width: 100%; padding: 0.5rem 0.75rem; background: var(--bg-input); border: 1px solid var(--border); border-radius: 4px; color: var(--text); font-size: 0.8rem; font-family: inherit;">
<button class="btn-toolbar btn-sm" onclick="app.toggleDeepgramKeyVisibility()" id="voiceKeyToggleBtn" type="button">Show</button>
</div>
<span class="form-hint">Stored locally only, never sent to server. Get a key at <a href="https://console.deepgram.com" target="_blank" rel="noopener" style="color: var(--accent);">console.deepgram.com</a></span>
</div>
<div class="form-row" style="grid-column: 1 / -1;">
<label for="voiceLanguage">Language</label>
<select id="voiceLanguage" class="form-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="form-row" style="grid-column: 1 / -1;">
<label for="voiceKeyterms">Domain Keywords</label>
<input type="text" id="voiceKeyterms" placeholder="codeman, tmux, respawn, subagent" style="width: 100%; padding: 0.5rem 0.75rem; background: var(--bg-input); border: 1px solid var(--border); border-radius: 4px; color: var(--text); font-size: 0.8rem; font-family: inherit;">
<span class="form-hint">Comma-separated terms to boost recognition accuracy</span>
</div>
</div>
</div>
<!-- Shortcuts tab -->
<div class="modal-tab-content hidden" id="settings-shortcuts">
<div class="settings-grid">
<div class="settings-section-header" style="grid-column: 1 / -1;">Keyboard Shortcuts</div>
<p class="form-hint" style="grid-column: 1 / -1; margin: 0 0 0.5rem;">
Customize keyboard shortcuts. Click the binding to capture a new key combination.
</p>
<div id="appSettingsShortcutsList" style="grid-column: 1 / -1;">
<!-- Populated by app.renderShortcutSettingsList() -->
</div>
</div>
</div>
</div>
<div class="form-actions">
<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-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>
<!-- 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>
<input type="text" id="linkCasePath" placeholder="/home/user/projects/my-project" autocomplete="off" autocapitalize="off" autocorrect="off" spellcheck="false">
<span class="form-hint">Absolute path to an existing project folder, e.g. /home/you/my-project</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 + 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>
<script defer src="constants.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="ultracode-panel.js"></script>
<script defer src="admin-ui.js"></script>
<script defer src="session-ui.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>