mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-09-30 12:39:42 +02:00
The Read My Mind modal grows up and reaches phones: - Alternate suggestions (the predictor's verify/redirect kinds) now render as tappable rows below the main field. Tapping one swaps it into the editable field; the edit you were making folds back into the row you leave, so toggling between alternates never loses typing. Rethink now records the WHOLE shown set (main + alternates) as rejected. - Phones get a 🧠 key on the keyboard accessory bar (both simple and extended layouts), gated on the same synced readMyMindEnabled setting via an rmm-enabled marker class on the BAR element: setMode() rebuilds the buttons' innerHTML, so per-key state would be wiped. Synced at init and re-synced by applyHeaderVisibilitySettings() on every settings apply, so a live toggle needs no reload. The header button stays off phones. - On phones the modal renders as a small dialog (mirrors modal-sm) instead of the full-screen default, with wrap-friendly finger-sized footer buttons. Not modal-sm itself: that caps desktop width at 340px and this modal wants 560px there. - On touch devices the ready/swap paths no longer focus the field, so the OS keyboard does not pop over the alternates that just rendered. - New static guard test/readmymind-phone-key.test.ts pins the dual-template key, the marker-class gating, the phone-hidden header button, the small-dialog phone modal, and the no-innerHTML discipline. Part 2 of phase 3 (rethink steering, the free-text steer note) is next; the API already accepts steer. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2873 lines
174 KiB
HTML
2873 lines
174 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<!-- Resolve all relative assets against the site root so the same shell can be
|
||
served at /session/:id (detached single-session window) without 404ing
|
||
on relative <script>/<link> URLs. Must precede the first resource tag. -->
|
||
<base href="/">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
|
||
<meta name="description" content="Claude Code session manager with web interface">
|
||
<meta name="theme-color" content="#11151c">
|
||
<meta name="google" content="notranslate">
|
||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||
<link rel="apple-touch-icon" href="icon-192.png">
|
||
<link rel="manifest" href="manifest.json">
|
||
<title>Codeman</title>
|
||
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0%25' y1='0%25' x2='100%25' y2='100%25'%3E%3Cstop offset='0%25' stop-color='%2360a5fa'/%3E%3Cstop offset='100%25' stop-color='%233b82f6'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='32' height='32' rx='6' fill='%230a0a0a'/%3E%3Cpath d='M18 4L8 18h6l-2 10 10-14h-6z' fill='url(%23g)'/%3E%3C/svg%3E">
|
||
<link rel="stylesheet" href="styles.css">
|
||
<link rel="stylesheet" href="mobile.css" media="(max-width: 1023px)">
|
||
<!-- xterm.css loaded async — terminal won't display until xterm.js runs anyway -->
|
||
<link rel="preload" href="vendor/xterm.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
|
||
<noscript><link rel="stylesheet" href="vendor/xterm.css"></noscript>
|
||
<!-- Preload critical resources — lets browser discover these during HTML parse
|
||
instead of waiting until <script> tags at bottom-of-body are reached. -->
|
||
<link rel="preload" href="vendor/xterm.min.js" as="script">
|
||
<link rel="preload" href="constants.js" as="script">
|
||
<link rel="preload" href="app.js" as="script">
|
||
<!-- Self-hosted xterm.js — eliminates CDN DNS/TLS latency (~100ms).
|
||
'defer' preserves execution order (xterm loads before fit addon). -->
|
||
<script defer src="vendor/xterm.min.js"></script>
|
||
<script defer src="vendor/xterm-addon-fit.min.js"></script>
|
||
<!-- SerializeAddon: snapshots xterm state (viewport + scrollback + attrs) for
|
||
per-session restore on tab switches. Lets codex tabs survive switch-away
|
||
without codeman having to replay codex's byte stream (which loses earlier
|
||
conversation because codex's TUI redraws drop it from the viewport). -->
|
||
<script defer src="vendor/xterm-addon-serialize.min.js"></script>
|
||
<!-- WebGL addon lazy-loaded by app.js on desktop only (skipped on mobile, saving 244KB) -->
|
||
<script defer src="vendor/xterm-addon-unicode11.min.js"></script>
|
||
<script defer src="vendor/xterm-zerolag-input.js"></script>
|
||
<script defer src="vendor/xterm-predictive-echo.js"></script>
|
||
<script defer src="vendor/marked.min.js"></script>
|
||
<!-- DOMPurify (allowlist HTML sanitizer for rendered markdown).
|
||
Must load before sanitize-html.js (which wires it) and app.js (which calls it). -->
|
||
<script defer src="vendor/dompurify.min.js"></script>
|
||
<!-- Synchronous mobile detection — runs before first paint to prevent panel flash -->
|
||
<script>if(window.innerWidth<768||(('ontouchstart' in window||navigator.maxTouchPoints>0)&&window.innerWidth<1024))document.documentElement.classList.add('mobile-init');</script>
|
||
<!-- Synchronous skin selection — runs before first paint to prevent theme flash -->
|
||
<script>try{var s=localStorage.getItem('codeman:skin'),a=['og','daylight-green','daylight-blue','paper-gray','solarized-light','catppuccin-latte','rose-pine-dawn'];if(a.indexOf(s)<0)s='daylight-blue';document.documentElement.dataset.skin=s;window.__codemanSkin=s;}catch(e){document.documentElement.dataset.skin='daylight-blue';window.__codemanSkin='daylight-blue';}</script>
|
||
<!-- Apply the saved per-device language before first paint. The full translation
|
||
layer loads below; setting lang/dir here prevents an English accessibility
|
||
tree from flashing while the deferred scripts start. -->
|
||
<script>try{var m=window.innerWidth<768||(('ontouchstart' in window||navigator.maxTouchPoints>0)&&window.innerWidth<1024);var k=m?'codeman-app-settings-mobile':'codeman-app-settings';var l=JSON.parse(localStorage.getItem(k)||'{}').language;l=l==='zh-CN'?'zh-CN':'en';document.documentElement.lang=l;window.__codemanLanguage=l;}catch(e){document.documentElement.lang='en';window.__codemanLanguage='en';}</script>
|
||
<!-- Inline critical CSS for instant skeleton paint (before styles.css loads) -->
|
||
<style>
|
||
.loading-skeleton{display:flex;flex-direction:column;height:100vh;height:100dvh;background:var(--bg-dark,#11151c)}
|
||
.skeleton-header{height:40px;background:var(--glass-bg,rgba(31,38,48,0.85));border-bottom:1px solid var(--glass-border,rgba(255,255,255,0.08));display:flex;align-items:center;padding:0 12px}
|
||
.skeleton-brand{color:var(--accent,#38b6f0);font-size:14px;font-weight:700;font-family:'Manrope',-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;opacity:.85}
|
||
.skeleton-tabs{display:flex;gap:4px;margin-left:16px}
|
||
.skeleton-tab{width:80px;height:24px;background:var(--control-bg,rgba(255,255,255,0.04));border-radius:6px}
|
||
.skeleton-terminal{flex:1;background:var(--term-bg,#161b23)}
|
||
.skeleton-toolbar{height:42px;background:var(--glass-bg,rgba(31,38,48,0.85));border-top:1px solid var(--glass-border,rgba(255,255,255,0.08))}
|
||
.app-loaded .loading-skeleton{display:none}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<!-- Loading skeleton — visible instantly before CSS/JS loads, hidden by app-loaded class -->
|
||
<div class="loading-skeleton">
|
||
<div class="skeleton-header">
|
||
<span class="skeleton-brand">Codeman</span>
|
||
<div class="skeleton-tabs"><div class="skeleton-tab"></div></div>
|
||
</div>
|
||
<div class="skeleton-terminal"></div>
|
||
<div class="skeleton-toolbar"></div>
|
||
</div>
|
||
<!-- Skip link for keyboard users -->
|
||
<!-- onclick scrolls/focuses directly: with <base href="/"> a bare href="#..." would
|
||
navigate to /#... (the dashboard) from a /session/:id solo window. -->
|
||
<a href="#terminalContainer" class="skip-link" onclick="event.preventDefault(); var t=document.getElementById('terminalContainer'); if(t){t.scrollIntoView(); var f=t.querySelector('textarea,[tabindex]'); (f||t).focus&&(f||t).focus();}">Skip to terminal</a>
|
||
<div class="app">
|
||
<!-- Compact Header with Session Tabs -->
|
||
<header class="header">
|
||
<div class="header-brand">
|
||
<span class="logo" onclick="app.goHome()" title="Go to main page"
|
||
><span class="logo-text">Codeman</span><span class="logo-compact" aria-hidden="true">C</span></span
|
||
>
|
||
</div>
|
||
|
||
<!-- Session Tabs -->
|
||
<div class="session-tabs" id="sessionTabs" role="tablist" aria-label="Session tabs">
|
||
</div>
|
||
|
||
<!-- Detached single-session window title (shown only in solo mode) -->
|
||
<div class="solo-session-title" id="soloSessionTitle" style="display: none;" aria-live="polite"></div>
|
||
|
||
<div class="header-right" id="headerRight">
|
||
<button class="btn-admin-panel btn-admin-panel--hidden" id="adminPanelBtn" onclick="window.codemanAdmin.openAdminPanel()" title="Admin Panel (multi-user administration)" aria-label="Open admin panel">
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
|
||
<span>Admin Panel</span>
|
||
</button>
|
||
<button class="btn-icon-header btn-solo-redock" id="soloRedockBtn" style="display: none;" onclick="window.close()" title="Re-dock to dashboard (close window)" aria-label="Re-dock session to dashboard">⊞</button>
|
||
<button class="tunnel-indicator" id="tunnelIndicator" style="display: none;" onclick="app.toggleTunnelPanel()" title="Cloudflare Tunnel" aria-label="Tunnel status">
|
||
<span class="tunnel-dot"></span>
|
||
</button>
|
||
<div class="connection-indicator" id="connectionIndicator" style="display: none;">
|
||
<span class="connection-dot" id="connectionDot"></span>
|
||
<span class="connection-text" id="connectionText"></span>
|
||
</div>
|
||
<div class="header-font-controls">
|
||
<button class="btn-icon-header btn-sm" onclick="app.decreaseFontSize()" title="Decrease font (Ctrl+-)" aria-label="Decrease font size">A-</button>
|
||
<span class="font-size-display" id="fontSizeDisplay" aria-label="Current font size">14</span>
|
||
<button class="btn-icon-header btn-sm" onclick="app.increaseFontSize()" title="Increase font (Ctrl++)" aria-label="Increase font size">A+</button>
|
||
</div>
|
||
<div class="header-system-stats" id="headerSystemStats" title="System resource usage">
|
||
<div class="stat-item">
|
||
<span class="stat-label">CPU</span>
|
||
<div class="stat-bar">
|
||
<div class="stat-bar-fill stat-bar-cpu" id="statCpuBar"></div>
|
||
</div>
|
||
<span class="stat-value" id="statCpu">--%</span>
|
||
</div>
|
||
<div class="stat-item">
|
||
<span class="stat-label">MEM</span>
|
||
<div class="stat-bar">
|
||
<div class="stat-bar-fill stat-bar-mem" id="statMemBar"></div>
|
||
</div>
|
||
<span class="stat-value" id="statMem">--</span>
|
||
</div>
|
||
</div>
|
||
<button class="btn-icon-header btn-redraw-terminal btn-redraw-terminal--hidden" onclick="app.restoreTerminalSize()" title="Redraw terminal to fit current screen (Ctrl+Shift+R)" aria-label="Redraw terminal"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="1 4 1 10 7 10"/><polyline points="23 20 23 14 17 14"/><path d="M20.49 9A9 9 0 0 0 5.64 5.64L1 10m22 4l-4.64 4.36A9 9 0 0 1 3.51 15"/></svg></button>
|
||
<button class="btn-icon-header btn-response-viewer-header btn-response-viewer-header--hidden" onclick="app.toggleResponseViewer()" title="View last response" aria-label="View last response"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg></button>
|
||
<button class="btn-icon-header btn-away-digest btn-away-digest--hidden" onclick="app.openAwayDigest()" title="Away Digest" aria-label="Open away digest"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8 6h13"/><path d="M8 12h13"/><path d="M8 18h13"/><path d="M3 6h.01"/><path d="M3 12h.01"/><path d="M3 18h.01"/></svg></button>
|
||
<button class="btn-icon-header btn-session-manager btn-session-manager--hidden" onclick="app.openSessionManager()" title="Session Manager" aria-label="Open session manager"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="12 2 2 7 12 12 22 7 12 2"/><polyline points="2 17 12 22 22 17"/><polyline points="2 12 12 17 22 12"/></svg></button>
|
||
<button class="btn-icon-header btn-approvals btn-approvals--hidden" id="approvalsBtn" onclick="app.toggleApprovalsInbox()" title="Approvals waiting on you" aria-label="Open approvals inbox" aria-expanded="false">
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></svg>
|
||
<span class="approvals-badge" id="approvalsBadge">0</span>
|
||
</button>
|
||
<button class="btn-icon-header btn-readmymind btn-readmymind--hidden" id="readMyMindBtn" onclick="app.openReadMyMind()" title="Read My Mind: predict your next prompt" aria-label="Predict my next prompt"><span class="readmymind-icon" aria-hidden="true">🧠</span></button>
|
||
<button class="btn-icon-header btn-attachments-history btn-attachments-history--hidden" id="attachmentsHistoryBtn" onclick="app.toggleAttachmentHistory()" title="Attachments" aria-label="Open attachment history" aria-expanded="false">
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg>
|
||
<span class="attachment-history-badge" id="attachmentHistoryBadge" style="display:none;">0</span>
|
||
</button>
|
||
<button class="btn-icon-header btn-file-viewer" onclick="app.toggleFileBrowserButton()" title="File Viewer" aria-label="Open file viewer" aria-expanded="false"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg></button>
|
||
<button class="btn-icon-header btn-multimonitor btn-multimonitor--hidden" onclick="app.launchMultiMonitor()" title="Open Codeman across all displays" aria-label="Open Codeman across all displays"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="2" y="4" width="13" height="9" rx="1.5"/><rect x="11" y="9" width="11" height="8" rx="1.5"/></svg></button>
|
||
<button class="btn-icon-header btn-ultracode-agents btn-ultracode-agents--hidden" onclick="app.toggleUltracodeAgentsPanel()" title="Ultracode / Workflow agents" aria-label="Open ultracode workflow agents"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="6" cy="6" r="2.5"/><circle cx="6" cy="18" r="2.5"/><circle cx="18" cy="12" r="2.5"/><path d="M8.2 7.2 15.6 11M8.2 16.8 15.6 13"/></svg></button>
|
||
<div class="header-plan-usage header-plan-usage--hidden" id="planUsageChip" title="Claude plan usage limits">—</div>
|
||
<button class="btn-icon-header btn-notifications" onclick="app.toggleNotifications()" title="Notifications" aria-label="Toggle notifications" style="display:none;">
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></svg>
|
||
<span class="notification-badge" id="notifBadge" style="display:none;">0</span>
|
||
</button>
|
||
<button class="btn-icon-header btn-lifecycle-log" style="display: none" onclick="app.openLifecycleLog()" title="Session Lifecycle Log" aria-label="Open session lifecycle log"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/></svg></button>
|
||
<button class="btn-icon-header btn-settings" onclick="app.openAppSettings()" title="App Settings" aria-label="Open app settings"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg></button>
|
||
<div class="header-tokens" id="headerTokens" style="display: none" title="Total tokens across all sessions">0 tokens</div>
|
||
</div>
|
||
</header>
|
||
|
||
<!-- Connection-loss banner: shown once session state HAS loaded and the
|
||
link then drops, so the terminal stays readable behind it.
|
||
The blocking variant is #offlineOverlay at the end of <body>. -->
|
||
<div class="offline-banner" id="offlineBanner" role="status" hidden>
|
||
<span class="offline-banner-dot" aria-hidden="true"></span>
|
||
<span class="offline-banner-text" id="offlineBannerText">No connection to the Codeman server</span>
|
||
<span class="offline-banner-detail" id="offlineBannerDetail"></span>
|
||
<button class="offline-banner-retry" id="offlineBannerRetry" onclick="app.retryConnection()">Retry now</button>
|
||
</div>
|
||
|
||
<!-- Timer Banner (shown when timed run is active) -->
|
||
<div class="timer-banner" id="timerBanner" style="display: none;">
|
||
<div class="timer-content">
|
||
<span class="timer-value" id="timerValue">00:00:00</span>
|
||
<div class="timer-progress"><div class="timer-progress-fill" id="timerProgress"></div></div>
|
||
<span class="timer-meta" id="timerMeta">0 tasks | $0.00</span>
|
||
</div>
|
||
<button class="btn-icon-only" onclick="app.stopCurrentRun()" title="Stop" aria-label="Stop current run">■</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">↻</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">■</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">×</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">⏱</span><span id="ralphStatTime">0m</span></span>
|
||
<span class="ralph-stat"><span class="ralph-stat-icon">✓</span><span id="ralphStatTasks">0/0</span></span>
|
||
</div>
|
||
<span class="ralph-toggle" id="ralphToggle">▼</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">☰</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">⧉</button>
|
||
<button class="btn-icon-sm" onclick="event.stopPropagation(); app.closeRalphTracker()" title="Close tracker">×</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Expanded Detail View - Compact horizontal layout -->
|
||
<div class="ralph-detail" id="ralphDetail">
|
||
<div class="ralph-detail-row">
|
||
<!-- Progress Ring -->
|
||
<div class="ralph-ring-compact">
|
||
<svg viewBox="0 0 100 100" class="ralph-ring-svg-large">
|
||
<defs>
|
||
<linearGradient id="ralphGradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
||
<stop offset="0%" stop-color="#3ec8ee" />
|
||
<stop offset="100%" stop-color="#2b8fd9" />
|
||
</linearGradient>
|
||
</defs>
|
||
<circle class="ralph-ring-track" cx="50" cy="50" r="42" />
|
||
<circle class="ralph-ring-fill" cx="50" cy="50" r="42" id="ralphRingProgress" style="stroke-dashoffset: 264" />
|
||
</svg>
|
||
<div class="ralph-ring-center">
|
||
<div class="ralph-ring-percent" id="ralphRingPercent">0%</div>
|
||
<div class="ralph-ring-label">Complete</div>
|
||
</div>
|
||
</div>
|
||
<!-- Meta Info + Tasks unified -->
|
||
<div class="ralph-info-unified">
|
||
<div class="ralph-info-labels">
|
||
<div class="ralph-info-row">
|
||
<span class="ralph-info-label">Tasks</span>
|
||
<span class="ralph-tasks-count" id="ralphTasksCount">0/0</span>
|
||
</div>
|
||
<div class="ralph-info-row">
|
||
<span class="ralph-info-label">Phrase</span>
|
||
<code id="ralphPhrase">--</code>
|
||
</div>
|
||
<div class="ralph-info-row">
|
||
<span class="ralph-info-label">Elapsed</span>
|
||
<span id="ralphElapsed">0m</span>
|
||
</div>
|
||
<div class="ralph-info-row" id="ralphVersionRow" style="display: none;">
|
||
<span class="ralph-info-label">Plan</span>
|
||
<div class="ralph-version-info">
|
||
<span class="version-badge" id="ralphPlanVersion">v1</span>
|
||
<button class="rollback-btn" id="ralphRollbackBtn" onclick="app.showPlanHistory()" title="View history">history</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="ralph-tasks-grid" id="ralphTasksGrid"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<!-- Main Terminal Area -->
|
||
<main class="main">
|
||
<div class="terminal-wrap">
|
||
<div class="terminal-container" id="terminalContainer"></div>
|
||
<textarea id="cjkInput" rows="1" placeholder="CJK input (Enter = send, Esc = clear)"
|
||
maxlength="65536" aria-label="CJK IME input field"
|
||
autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"></textarea>
|
||
</div>
|
||
|
||
<!-- Web tab layer: one iframe per open dashboard, shown in place of the
|
||
terminal while a web tab is active. Frames stay mounted while hidden so
|
||
switching tabs does not reload (and re-authenticate) a dashboard. -->
|
||
<div class="webview-layer" id="webviewLayer"></div>
|
||
|
||
<!-- Welcome Overlay (shown when no session active) -->
|
||
<div class="welcome-overlay" id="welcomeOverlay">
|
||
<!-- Open tabs as a rail docked to the left edge (home-sessions.js).
|
||
Absolutely positioned so the centered content below never moves, and
|
||
therefore only rendered where the gutter is wider than the rail;
|
||
ships `hidden` and only that module reveals it. -->
|
||
<aside class="home-sessions" id="homeSessions" hidden></aside>
|
||
<div class="welcome-content">
|
||
<h1 class="welcome-title">Codeman</h1>
|
||
<p class="welcome-desc">Manage AI Coding tools in persistent tmux sessions.</p>
|
||
<div class="welcome-actions">
|
||
<button class="welcome-btn welcome-btn-claude" id="welcomeClaudeBtn" style="display: none;" onclick="app.setRunMode('claude'); app.runClaude()">
|
||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="5 3 19 12 5 21 5 3"/></svg>
|
||
Run Claude Code
|
||
</button>
|
||
<button class="welcome-btn welcome-btn-tunnel" id="welcomeTunnelBtn" style="display: none;" onclick="app.toggleTunnelFromWelcome()">
|
||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/></svg>
|
||
Cloudflare Tunnel
|
||
</button>
|
||
<button class="welcome-btn welcome-btn-opencode" id="welcomeOpencodeBtn" style="display: none;" onclick="app.setRunMode('opencode'); app.runOpenCode()">
|
||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="5 3 19 12 5 21 5 3"/></svg>
|
||
Run OpenCode
|
||
</button>
|
||
<button class="welcome-btn welcome-btn-antigravity" id="welcomeAntigravityBtn" style="display: none;" onclick="app.setRunMode('antigravity'); app.runAntigravity()">
|
||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="5 3 19 12 5 21 5 3"/></svg>
|
||
Run Antigravity
|
||
</button>
|
||
<button class="welcome-btn welcome-btn-gemini" id="welcomeGeminiBtn" style="display: none;" onclick="app.setRunMode('gemini'); app.runGemini()">
|
||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="5 3 19 12 5 21 5 3"/></svg>
|
||
Run Gemini
|
||
</button>
|
||
</div>
|
||
<div class="welcome-qr" id="welcomeQr" onclick="app.toggleWelcomeQrSize()">
|
||
<div class="welcome-qr-inner" id="welcomeQrInner"></div>
|
||
<div class="welcome-qr-url" id="welcomeQrUrl"></div>
|
||
</div>
|
||
<div class="history-sessions" id="historySessions" style="display:none">
|
||
<div class="search-panel" id="searchPanel">
|
||
<div class="search-input-row">
|
||
<input
|
||
type="search"
|
||
id="searchInput"
|
||
class="search-input"
|
||
placeholder="Search sessions, events, files…"
|
||
autocomplete="off"
|
||
spellcheck="false"
|
||
maxlength="200"
|
||
aria-label="Search across sessions"
|
||
/>
|
||
<button type="button" id="searchClearBtn" class="search-clear-btn" aria-label="Clear search" hidden>×</button>
|
||
</div>
|
||
<div class="search-filters" id="searchFilters">
|
||
<div class="search-filter-group" role="group" aria-label="Source type filter">
|
||
<button type="button" class="search-filter-chip active" data-type-filter="session">Sessions</button>
|
||
<button type="button" class="search-filter-chip active" data-type-filter="event">Events</button>
|
||
<button type="button" class="search-filter-chip active" data-type-filter="file">Files</button>
|
||
</div>
|
||
<div class="search-filter-group search-filter-secondary">
|
||
<select id="searchCaseFilter" class="search-select" aria-label="Filter by case">
|
||
<option value="">All cases</option>
|
||
</select>
|
||
<select id="searchStatusFilter" class="search-select" aria-label="Filter by session status">
|
||
<option value="">Any status</option>
|
||
<option value="active">Active</option>
|
||
<option value="history">History</option>
|
||
</select>
|
||
<select id="searchDateFilter" class="search-select" aria-label="Filter by date range">
|
||
<option value="">Any time</option>
|
||
<option value="1">Past 24h</option>
|
||
<option value="7">Past 7 days</option>
|
||
<option value="30">Past 30 days</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div class="search-results" id="searchResults" hidden></div>
|
||
</div>
|
||
<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 →</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Mobile Overview (phone home screen, shown in place of the welcome
|
||
overlay when the "C" logo is tapped). Ships `hidden`: only
|
||
mobile-overview.js removes it, and only when the phone gate passes, so
|
||
desktop (which never loads mobile.css) can never render it. -->
|
||
<div class="mobile-overview" id="mobileOverview" hidden></div>
|
||
</main>
|
||
|
||
<!-- Project Insights Panel (shows file-viewing Bash commands) -->
|
||
<div class="project-insights-panel" id="projectInsightsPanel">
|
||
<div class="project-insights-header">
|
||
<span class="project-insights-title">Project Insights</span>
|
||
<button class="btn-icon-sm" onclick="app.closeProjectInsightsPanel()" title="Close">×</button>
|
||
</div>
|
||
<div class="project-insights-body" id="projectInsightsList"></div>
|
||
<div class="project-insights-resize-handle"></div>
|
||
</div>
|
||
|
||
<!-- File Browser Panel -->
|
||
<div class="file-browser-panel" id="fileBrowserPanel">
|
||
<div class="file-browser-header">
|
||
<span class="file-browser-title">Files</span>
|
||
<div class="file-browser-actions">
|
||
<button class="btn-icon-sm btn-file-browser-hidden" onclick="app.toggleFileBrowserHidden()" title="Show hidden files and folders" aria-label="Show hidden files and folders" aria-pressed="false" id="fileBrowserHiddenBtn">.*</button>
|
||
<button class="btn-icon-sm" onclick="app.refreshFileBrowser()" title="Refresh">↻</button>
|
||
<button class="btn-icon-sm" onclick="app.toggleFileBrowserExpand()" title="Expand/Collapse All" id="fileBrowserExpandBtn">⊞</button>
|
||
<button class="btn-icon-sm" onclick="app.closeFileBrowserPanel()" title="Close">×</button>
|
||
</div>
|
||
</div>
|
||
<div class="file-browser-search">
|
||
<input type="text" id="fileBrowserSearch" placeholder="Filter files..." oninput="app.filterFileBrowser(this.value)">
|
||
</div>
|
||
<div class="file-browser-body" id="fileBrowserTree">
|
||
<div class="file-browser-empty">Select a session to view files</div>
|
||
</div>
|
||
<div class="file-browser-status" id="fileBrowserStatus"></div>
|
||
</div>
|
||
|
||
<!-- File Preview Overlay -->
|
||
<div class="file-preview-overlay" id="filePreviewOverlay">
|
||
<div class="file-preview-window">
|
||
<div class="file-preview-header">
|
||
<span class="file-preview-title" id="filePreviewTitle">file.ts</span>
|
||
<div class="file-preview-actions">
|
||
<button class="btn-icon-sm file-preview-edit-btn" id="filePreviewEditBtn" onclick="app.enterFilePreviewEdit()" title="Edit file" aria-label="Edit file" hidden><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5z"/></svg></button>
|
||
<button class="btn-icon-sm" onclick="app.copyFilePreviewContent()" title="Copy content">⎘</button>
|
||
<button class="btn-icon-sm" onclick="app.closeFilePreview()" title="Close">×</button>
|
||
</div>
|
||
</div>
|
||
<div class="file-preview-body" id="filePreviewBody"></div>
|
||
<div class="file-preview-editbar" id="filePreviewEditBar" hidden>
|
||
<span class="file-preview-dirty" id="filePreviewDirty" hidden>Unsaved changes</span>
|
||
<span class="file-preview-editbar-spacer"></span>
|
||
<button class="file-preview-editbar-btn" onclick="app.cancelFilePreviewEdit()">Cancel</button>
|
||
<button class="file-preview-editbar-btn file-preview-editbar-btn--save" id="filePreviewSaveBtn" onclick="app.saveFilePreviewEdit()" disabled>Save</button>
|
||
</div>
|
||
<div class="file-preview-footer" id="filePreviewFooter"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Response Viewer (mobile: native-scroll overlay for reading full Claude responses) -->
|
||
<div class="response-viewer" id="responseViewer">
|
||
<div class="response-viewer-header">
|
||
<span id="responseViewerTitle">Last Response</span>
|
||
<div class="response-viewer-actions">
|
||
<button class="response-viewer-more" id="responseViewerMore" onclick="app.loadFullContext()">More</button>
|
||
<button class="response-viewer-close" onclick="app.toggleResponseViewer()">×</button>
|
||
</div>
|
||
</div>
|
||
<div class="response-viewer-body" id="responseViewerBody"></div>
|
||
</div>
|
||
<div class="response-viewer-backdrop" id="responseViewerBackdrop" onclick="app.toggleResponseViewer()"></div>
|
||
|
||
<!-- Bottom Toolbar -->
|
||
<footer class="toolbar">
|
||
<div class="toolbar-left">
|
||
<!-- Run AI -->
|
||
<div class="toolbar-group">
|
||
<div class="run-btn-group">
|
||
<button class="btn-toolbar btn-run" id="runBtn" onclick="app.run()" title="Run">
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polygon points="5 3 19 12 5 21 5 3"/></svg>
|
||
<span id="runBtnLabel">Run</span>
|
||
</button>
|
||
<button class="btn-toolbar btn-run-gear" onclick="app.toggleRunModeMenu(event)" title="Select AI backend">
|
||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M6 9l6 6 6-6"/></svg>
|
||
</button>
|
||
<div class="run-mode-menu" id="runModeMenu">
|
||
<button class="run-mode-option" data-mode="claude" onclick="app.setRunMode('claude')">
|
||
<span class="run-mode-dot claude"></span>Claude Code
|
||
</button>
|
||
<button class="run-mode-option" data-mode="opencode" onclick="app.setRunMode('opencode')">
|
||
<span class="run-mode-dot opencode"></span>OpenCode
|
||
</button>
|
||
<button class="run-mode-option" data-mode="codex" onclick="app.setRunMode('codex')">
|
||
<span class="run-mode-dot codex"></span>Codex
|
||
</button>
|
||
<button class="run-mode-option" data-mode="gemini" onclick="app.setRunMode('gemini')">
|
||
<span class="run-mode-dot gemini"></span>Gemini
|
||
</button>
|
||
<button class="run-mode-option" data-mode="antigravity" onclick="app.setRunMode('antigravity')">
|
||
<span class="run-mode-dot antigravity"></span>Antigravity
|
||
</button>
|
||
<div class="run-mode-sep"></div>
|
||
<button class="run-mode-option" data-mode="shell" onclick="app.setRunMode('shell')">
|
||
<span class="run-mode-dot shell"></span>Terminal / Shell
|
||
</button>
|
||
<div class="run-mode-sep"></div>
|
||
<!-- Web tabs: dashboards open as tabs beside agent sessions. These do NOT
|
||
set runMode: the Run button always means "start an agent". -->
|
||
<div class="run-mode-header">Web / URL</div>
|
||
<div class="run-mode-webviews" id="runModeWebviews"></div>
|
||
<button class="run-mode-option run-mode-option--add" onclick="app.showWebviewModal()">
|
||
<span class="run-mode-dot web"></span>Add URL…
|
||
</button>
|
||
<div class="run-mode-sep"></div>
|
||
<div class="run-mode-header">Recent Sessions</div>
|
||
<div class="run-mode-history" id="runModeHistory"></div>
|
||
</div>
|
||
</div>
|
||
<div class="tab-count-group" title="Instance count">
|
||
<button class="tab-count-btn" onclick="app.decrementTabCount()">−</button>
|
||
<input type="number" id="tabCount" class="tab-count-input" value="1" min="1" max="20" readonly>
|
||
<button class="tab-count-btn" onclick="app.incrementTabCount()">+</button>
|
||
</div>
|
||
<button class="btn-toolbar btn-stop" onclick="app.stopClaude()" title="Stop (Ctrl+C)">
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><rect x="4" y="4" width="16" height="16" rx="2"/></svg>
|
||
</button>
|
||
<button class="btn-toolbar btn-shell" onclick="app.runShell()" title="Run Shell">
|
||
Run Shell
|
||
</button>
|
||
<!-- Phone-only: replaces the Shell button on ≤430px (Shell moves into the Run
|
||
dropdown there). Sends a bare Enter to the active session, the complement
|
||
to the accessory bar's Esc. Hidden everywhere else — see styles.css. -->
|
||
<button class="btn-toolbar btn-enter" onclick="app.sendEnterKey()" title="Send Enter">
|
||
Enter
|
||
</button>
|
||
<div class="tab-count-group" title="Instance count">
|
||
<button class="tab-count-btn" onclick="app.decrementShellCount()">−</button>
|
||
<input type="number" id="shellCount" class="tab-count-input" value="1" min="1" max="20" readonly>
|
||
<button class="tab-count-btn" onclick="app.incrementShellCount()">+</button>
|
||
</div>
|
||
<div class="case-select-group">
|
||
<div class="case-combobox" id="quickStartCasePicker">
|
||
<input
|
||
type="text"
|
||
id="quickStartCaseSearch"
|
||
class="case-combobox-input"
|
||
role="combobox"
|
||
aria-controls="quickStartCaseList"
|
||
aria-expanded="false"
|
||
aria-autocomplete="list"
|
||
autocomplete="off"
|
||
spellcheck="false"
|
||
title="Select case"
|
||
>
|
||
<div id="quickStartCaseList" class="case-combobox-list hidden" role="listbox"></div>
|
||
</div>
|
||
<select id="quickStartCase" class="toolbar-select case-native-select" title="Select case" aria-hidden="true" tabindex="-1">
|
||
<option value="testcase">testcase</option>
|
||
</select>
|
||
<button class="btn-case-add" onclick="app.showCreateCaseModal()" title="Create new case">+</button>
|
||
<button class="btn-case-settings" onclick="app.toggleCaseSettings()" title="Case settings">⚙</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">⚙</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">⚙ Orchestrator</button> -->
|
||
<button class="btn-toolbar btn-sm btn-cron btn-cron--hidden" onclick="app.openCron()" title="Cron Jobs">⏰ 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">×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<section class="shortcut-section">
|
||
<h4>Session</h4>
|
||
<div class="shortcuts-grid">
|
||
<div><kbd>Ctrl</kbd>+<kbd>W</kbd></div><div>Close Session</div>
|
||
<div><kbd>Ctrl/Cmd/Option</kbd>+<kbd>K</kbd></div><div>Find Open Session</div>
|
||
<div><kbd>Ctrl</kbd>+<kbd>Tab</kbd></div><div>Next Session</div>
|
||
<div><kbd>Alt/Option</kbd>+<kbd>[</kbd> / <kbd>Alt/Option</kbd>+<kbd>]</kbd></div><div>Previous / Next Session</div>
|
||
<div><kbd>Alt/Option</kbd>+<kbd>1-9</kbd></div><div>Switch to Tab N</div>
|
||
</div>
|
||
</section>
|
||
<section class="shortcut-section">
|
||
<h4>Tabs</h4>
|
||
<div class="shortcuts-grid">
|
||
<div><kbd>Ctrl</kbd>+<kbd>{</kbd></div><div>Move Active Tab Left</div>
|
||
<div><kbd>Ctrl</kbd>+<kbd>}</kbd></div><div>Move Active Tab Right</div>
|
||
<div><kbd>ArrowLeft</kbd></div><div>Focus Previous Tab</div>
|
||
<div><kbd>ArrowRight</kbd></div><div>Focus Next Tab</div>
|
||
<div><kbd>Home</kbd></div><div>Focus First Tab</div>
|
||
<div><kbd>End</kbd></div><div>Focus Last Tab</div>
|
||
<div><kbd>Enter</kbd> / <kbd>Space</kbd></div><div>Activate Focused Tab</div>
|
||
</div>
|
||
</section>
|
||
<section class="shortcut-section">
|
||
<h4>Terminal</h4>
|
||
<div class="shortcuts-grid">
|
||
<div><kbd>Ctrl</kbd>+<kbd>C</kbd></div><div>Copy Selection (interrupts when nothing is selected)</div>
|
||
<div><kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>C</kbd></div><div>Copy Selection</div>
|
||
<div><kbd>Ctrl</kbd>+<kbd>L</kbd></div><div>Clear Terminal</div>
|
||
<div><kbd>Ctrl</kbd>+<kbd>+</kbd></div><div>Increase Font</div>
|
||
<div><kbd>Ctrl</kbd>+<kbd>-</kbd></div><div>Decrease Font</div>
|
||
<div><kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>R</kbd></div><div>Restore Terminal Size</div>
|
||
<div><kbd>Shift</kbd>+<kbd>Enter</kbd></div><div>Insert Newline</div>
|
||
<div><kbd>Ctrl</kbd>+<kbd>Enter</kbd></div><div>Insert Newline</div>
|
||
<div><kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>V</kbd></div><div>Voice Input</div>
|
||
<div><kbd>Shift</kbd>+<kbd>Wheel</kbd></div><div>Scroll local history (when mouse passthrough is active)</div>
|
||
</div>
|
||
</section>
|
||
<section class="shortcut-section">
|
||
<h4>Panels</h4>
|
||
<div class="shortcuts-grid">
|
||
<div><kbd>Ctrl</kbd>+<kbd>?</kbd></div><div>Show Shortcuts</div>
|
||
<div><kbd>Alt/Option</kbd>+<kbd>?</kbd></div><div>Show Shortcuts</div>
|
||
<div><kbd>Escape</kbd></div><div>Close Panels</div>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Web Tab (dashboard URL) editor -->
|
||
<div class="modal" id="webviewModal">
|
||
<div class="modal-backdrop" onclick="app.closeWebviewModal()"></div>
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h3 id="webviewModalTitle">Add URL</h3>
|
||
<button class="modal-close" onclick="app.closeWebviewModal()" aria-label="Close URL editor">×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="form-row">
|
||
<label for="webviewName">Name</label>
|
||
<input type="text" id="webviewName" placeholder="Grafana" autocomplete="off" spellcheck="false">
|
||
</div>
|
||
<div class="form-row">
|
||
<label for="webviewUrl">URL</label>
|
||
<input type="text" id="webviewUrl" placeholder="http://100.70.56.18:4000/" autocomplete="off"
|
||
autocapitalize="off" spellcheck="false">
|
||
<span class="form-hint">
|
||
Reached from the Codeman server, so a tailnet or localhost address works even when
|
||
this browser cannot see it. Plain HTTP is fine: the dashboard is proxied through
|
||
Codeman, which is also what gets past dashboards that refuse to be embedded.
|
||
</span>
|
||
</div>
|
||
<div class="form-row">
|
||
<label for="webviewIcon">Icon</label>
|
||
<!-- Click to pick; the field stays editable so any emoji still works. -->
|
||
<div class="webview-icon-picker" id="webviewIconPicker" role="group" aria-label="Choose an icon"></div>
|
||
<input type="text" id="webviewIcon" placeholder="Or paste any emoji" maxlength="8" autocomplete="off">
|
||
</div>
|
||
<div class="form-row">
|
||
<label class="checkbox-row"><input type="checkbox" id="webviewSandboxed" checked> Open sandboxed</label>
|
||
<span class="form-hint">
|
||
Recommended. A proxied dashboard is served from Codeman's own address, so unchecking
|
||
this lets its JavaScript read this page and call the API that starts agents. Uncheck
|
||
only for a dashboard you fully trust, or one whose own login needs cookies. Also
|
||
uncheck it if Codeman itself sits behind a cookie-authenticated reverse proxy
|
||
(e.g. Cloudflare Access): a sandboxed frame carries no auth cookie, so its asset
|
||
and API requests bounce to the login provider and the page loads broken.
|
||
</span>
|
||
</div>
|
||
<div class="form-row">
|
||
<button class="btn-secondary" onclick="app.testWebviewUrl()">Test</button>
|
||
<span class="form-hint webview-probe-result" id="webviewProbeResult"></span>
|
||
</div>
|
||
</div>
|
||
<div class="form-actions webview-modal-actions">
|
||
<button class="btn-danger" id="webviewDeleteBtn" onclick="app.deleteWebview()">Delete</button>
|
||
<button class="btn-secondary" onclick="app.closeWebviewModal()">Cancel</button>
|
||
<button class="btn-primary" onclick="app.saveWebview()">Save</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Cron Jobs Modal -->
|
||
<div class="modal" id="cronModal">
|
||
<div class="modal-backdrop" onclick="app.closeCron()"></div>
|
||
<div class="modal-content modal-lg">
|
||
<div class="modal-header">
|
||
<h3>Cron Jobs</h3>
|
||
<button class="modal-close" onclick="app.closeCron()" aria-label="Close cron">×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<p class="form-hint cron-modal-hint">Times use the server's local timezone.</p>
|
||
<div class="cron-toolbar">
|
||
<button class="btn-toolbar btn-primary" onclick="app.openCronJobForm()">+ New Job</button>
|
||
<button class="btn-toolbar" onclick="app.refreshCron()">Refresh</button>
|
||
</div>
|
||
<!-- Job list -->
|
||
<div id="cronJobList" class="cron-job-list"></div>
|
||
|
||
<!-- Create/Edit form (hidden until New/Edit) -->
|
||
<div id="cronJobForm" class="cron-job-form hidden">
|
||
<div class="cron-form-title" id="cronFormTitle">New Cron Job</div>
|
||
<input type="hidden" id="schJobId">
|
||
|
||
<div class="form-section-header">Basics</div>
|
||
<div class="form-row"><label>Name</label><input type="text" id="schName" placeholder="My nightly job"></div>
|
||
<div class="form-row"><label>Agent Type</label>
|
||
<select id="schAgentType" class="form-select" onchange="app.onCronAgentTypeChange()">
|
||
<option value="claude">Claude</option>
|
||
<option value="shell">Terminal / Shell</option>
|
||
<option value="opencode">OpenCode</option>
|
||
<option value="codex">Codex</option>
|
||
<option value="gemini">Gemini</option>
|
||
<option value="antigravity">Antigravity</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-row"><label>Working Directory</label><input type="text" id="schWorkingDir" placeholder="/absolute/path"></div>
|
||
<div class="form-row hidden" id="schLaunchCommandRow"><label>Launch Command</label><input type="text" id="schLaunchCommand" placeholder="Optional — runs as the first command in the new shell"></div>
|
||
|
||
<div class="form-section-header">Prompt</div>
|
||
<div class="form-row"><label>Prompt Source</label>
|
||
<select id="schPromptMode" class="form-select" onchange="app.onCronPromptModeChange()">
|
||
<option value="inline_text">Inline text</option>
|
||
<option value="prompt_file_path">Prompt file path</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-row" id="schPromptTextRow"><label>Prompt</label><textarea id="schPromptText" rows="4" placeholder="Prompt to send into the session"></textarea></div>
|
||
<div class="form-row hidden" id="schPromptFileRow"><label>Prompt File Path</label><input type="text" id="schPromptFilePath" placeholder="/absolute/path/to/prompt.md"></div>
|
||
<div class="form-row"><label>Input Mode</label>
|
||
<select id="schInputMode" class="form-select">
|
||
<option value="typed">Typed (via tmux)</option>
|
||
<option value="paste">Paste (direct)</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="form-section-header">Schedule</div>
|
||
<div class="form-row"><label>Schedule Type</label>
|
||
<select id="schScheduleType" class="form-select" onchange="app.onCronScheduleTypeChange()">
|
||
<option value="once">Once</option>
|
||
<option value="interval">Interval</option>
|
||
<option value="daily">Daily</option>
|
||
<option value="weekly">Weekly</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-row" id="schRunAtRow"><label>Run At</label><input type="datetime-local" id="schRunAt"></div>
|
||
<div class="form-row hidden" id="schIntervalRow"><label>Every (minutes)</label><input type="number" id="schIntervalMinutes" min="1" value="60"></div>
|
||
<div class="form-row hidden" id="schDailyRow"><label>Daily Time (HH:MM)</label><input type="time" id="schDailyTime"></div>
|
||
<div class="form-row hidden" id="schWeeklyDaysRow"><label>Weekdays</label>
|
||
<span id="schWeeklyDays" class="cron-weekdays">
|
||
<label><input type="checkbox" value="0">Sun</label>
|
||
<label><input type="checkbox" value="1">Mon</label>
|
||
<label><input type="checkbox" value="2">Tue</label>
|
||
<label><input type="checkbox" value="3">Wed</label>
|
||
<label><input type="checkbox" value="4">Thu</label>
|
||
<label><input type="checkbox" value="5">Fri</label>
|
||
<label><input type="checkbox" value="6">Sat</label>
|
||
</span>
|
||
</div>
|
||
<div class="form-row hidden" id="schWeeklyTimeRow"><label>Weekly Time (HH:MM)</label><input type="time" id="schWeeklyTime"></div>
|
||
|
||
<div class="form-section-header">Options</div>
|
||
<div class="form-row"><label>On auto-run, if same agent running</label>
|
||
<select id="schConcurrencyPolicy" class="form-select">
|
||
<option value="warn_only">Run anyway</option>
|
||
<option value="skip_if_same_agent_running">Skip this run</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-row form-row-switch cron-switch-row">
|
||
<div class="cron-switch-text">
|
||
<span class="cron-switch-label">Auto-close previous run's session</span>
|
||
<span class="cron-switch-desc">Recurring schedules only: close the prior run's still-open session before the next run fires</span>
|
||
</div>
|
||
<label class="switch switch-sm"><input type="checkbox" id="schAutoClosePrev" checked><span class="slider"></span></label>
|
||
</div>
|
||
<div class="form-row form-row-switch cron-switch-row">
|
||
<div class="cron-switch-text">
|
||
<span class="cron-switch-label">Enabled</span>
|
||
<span class="cron-switch-desc">Off keeps the job saved but skips its schedule</span>
|
||
</div>
|
||
<label class="switch switch-sm"><input type="checkbox" id="schEnabled" checked><span class="slider"></span></label>
|
||
</div>
|
||
<div class="form-row"><label>Notes</label><input type="text" id="schNotes" placeholder="Optional"></div>
|
||
|
||
<div id="cronFormError" class="form-hint cron-form-error"></div>
|
||
<div class="cron-form-actions">
|
||
<button class="btn-toolbar" onclick="app.cancelCronJobForm()">Cancel</button>
|
||
<button class="btn-toolbar btn-primary" onclick="app.saveCronJob()">Save</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Monitor Panel (Tmux Sessions + Background Tasks) -->
|
||
<div class="monitor-panel" id="monitorPanel">
|
||
<div class="monitor-panel-header" id="monitorPanelHeader">
|
||
<div class="monitor-panel-title">Monitor</div>
|
||
<div class="monitor-panel-actions">
|
||
<button class="btn-toolbar btn-sm btn-danger" onclick="app.killAllMuxSessions()" title="Kill all sessions and their tmux processes">Kill All</button>
|
||
<button class="btn-icon-sm" onclick="app.reconcileMuxSessions()" title="Refresh tmux sessions">↻</button>
|
||
<button class="btn-icon-sm" onclick="app.toggleMonitorDetach()" title="Detach panel" id="monitorDetachBtn">⧉</button>
|
||
<button class="btn-icon-sm" onclick="app.toggleMonitorPanel()" title="Toggle panel" id="monitorToggleBtn">▲</button>
|
||
<button class="btn-icon-sm" onclick="app.closeMonitor()" title="Close monitor">×</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">⧉</button>
|
||
<button class="btn-icon-sm" onclick="app.toggleSubagentsPanel()" title="Toggle panel" id="subagentsToggleBtn">▲</button>
|
||
<button class="btn-icon-sm" onclick="app.closeSubagentsPanel()" title="Close">×</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">▲</button>
|
||
<button class="btn-icon-sm" onclick="app.closeUltracodeAgentsPanel()" title="Close">×</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">×</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 · 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 — 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 · 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 <promise> 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">↻ 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">×</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">×</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">
|
||
<!-- Branding & Language Section -->
|
||
<div class="settings-section-header">Branding & Language</div>
|
||
<div class="settings-item" title="Name shown in the browser UI and window title. Supports Unicode, including Chinese.">
|
||
<span class="settings-item-label">Display Name</span>
|
||
<input type="text" id="appSettingsDisplayName" class="settings-inline-input" maxlength="40" placeholder="Codeman" autocomplete="off">
|
||
</div>
|
||
<div class="settings-item" title="Language for this device. Dynamic status messages and dialogs use the same language.">
|
||
<span class="settings-item-label">Interface Language</span>
|
||
<select id="appSettingsLanguage" class="form-select settings-inline-select">
|
||
<option value="en">English</option>
|
||
<option value="zh-CN">简体中文</option>
|
||
</select>
|
||
</div>
|
||
|
||
<!-- 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">
|
||
<optgroup label="Light">
|
||
<option value="paper-gray">Paper Gray</option>
|
||
<option value="solarized-light">Solarized Light</option>
|
||
<option value="catppuccin-latte">Catppuccin Latte</option>
|
||
<option value="rose-pine-dawn">Rosé Pine Dawn</option>
|
||
</optgroup>
|
||
<optgroup label="Dark">
|
||
<option value="daylight-blue">Daylight Blue</option>
|
||
<option value="daylight-green">Daylight Green</option>
|
||
<option value="og">OG Codeman</option>
|
||
</optgroup>
|
||
</select>
|
||
</div>
|
||
<div class="settings-item settings-item-multiline" title="How new session tabs, the terminal pane, agent windows and their connection lines appear. This device only. For per-surface control and a live preview, add ?animlab=1 to the URL.">
|
||
<div class="settings-item-text">
|
||
<span class="settings-item-label">Entrance Animations</span>
|
||
<span class="settings-item-desc">How new tabs, panes and agent windows arrive</span>
|
||
</div>
|
||
<select id="appSettingsEntranceAnim" class="form-select settings-inline-select">
|
||
<option value="legacy">Off (default)</option>
|
||
<option value="terminal">Terminal (CRT)</option>
|
||
<option value="beamdown">Beam down</option>
|
||
<option value="quiet">Quiet</option>
|
||
<option value="playful">Playful</option>
|
||
<option value="custom">Custom (set in the lab)</option>
|
||
</select>
|
||
</div>
|
||
<div 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. Only Claude sessions forward, so this setting only affects them: Claude redraws the screen in place and keeps almost no local scrollback, so with this ON the wheel has little to scroll and Codeman falls back to paging Claude's own transcript. Codex, Gemini, shell and OpenCode sessions always scroll local scrollback. 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>
|
||
|
||
<!-- Phone Section (only meaningful under 430px, hidden elsewhere by settings-ui.js) -->
|
||
<div class="settings-section-header" id="appSettingsPhoneSection">Phone</div>
|
||
<div class="settings-item" id="appSettingsMobileOverviewItem" title="On phones, the C logo opens a session overview (needs you / spaces / idle) instead of the welcome screen">
|
||
<span class="settings-item-label">Overview Home Screen</span>
|
||
<label class="switch switch-sm">
|
||
<input type="checkbox" id="appSettingsMobileOverview" checked>
|
||
<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>
|
||
<div class="settings-item" title="Show the open-in-new-window (pop-out) button when hovering a session tab">
|
||
<span class="settings-item-label">Pop-out Button on Tabs</span>
|
||
<label class="switch switch-sm">
|
||
<input type="checkbox" id="appSettingsShowTabDetachButton">
|
||
<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="Cross-session inbox of prompts waiting on you (permission dialogs, questions, idle prompts) with answer-in-place buttons; the header bell appears only while something is pending">
|
||
<span class="settings-item-label">Approvals Inbox</span>
|
||
<label class="switch switch-sm">
|
||
<input type="checkbox" id="appSettingsApprovalsInbox">
|
||
<span class="slider"></span>
|
||
</label>
|
||
</div>
|
||
<div class="settings-item" title="Read My Mind: capture your submitted prompts into a per-case intent profile and predict your next prompt on demand (header 🧠 button on Claude sessions). Predictions cost tokens and are never auto-sent">
|
||
<span class="settings-item-label">Read My Mind</span>
|
||
<label class="switch switch-sm">
|
||
<input type="checkbox" id="appSettingsReadMyMind">
|
||
<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">—</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 form-row-switch">
|
||
<label>Agent Skill</label>
|
||
<label class="switch">
|
||
<input type="checkbox" id="appSettingsAgentSkill">
|
||
<span class="slider"></span>
|
||
</label>
|
||
<span class="form-hint">Give new Claude sessions the Codeman skill (start workers, send prompts, wait for results via the API)</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 class="form-row form-row-switch">
|
||
<label>Animated Status Effects</label>
|
||
<label class="switch">
|
||
<input type="checkbox" id="appSettingsCodexAnimations">
|
||
<span class="slider"></span>
|
||
</label>
|
||
<span class="form-hint">Decorative Codex TUI motion for new local sessions. Leave off to reduce remote and mobile redraws.</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">
|
||
<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">✕</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">×</button>
|
||
</div>
|
||
<div class="modal-tabs">
|
||
<button class="modal-tab-btn active" data-tab="case-create">Create New</button>
|
||
<button class="modal-tab-btn" data-tab="case-clone" id="caseCloneTabBtn">Clone Repo</button>
|
||
<button class="modal-tab-btn" data-tab="case-link">Link Existing</button>
|
||
<button class="modal-tab-btn" data-tab="case-remote">Remote</button>
|
||
<button class="modal-tab-btn" data-tab="case-docker">Docker</button>
|
||
<button class="modal-tab-btn" data-tab="case-manage">Manage</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<!-- Create New Tab -->
|
||
<div class="modal-tab-content" id="case-create">
|
||
<div class="form-row">
|
||
<label>Case Name</label>
|
||
<input type="text" id="newCaseName" placeholder="my-project" pattern="[a-zA-Z0-9_-]+" autocomplete="off" autocapitalize="off" spellcheck="false">
|
||
<span class="form-hint">Letters, numbers, hyphens, underscores only. Created in ~/codeman-cases/</span>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Description (optional)</label>
|
||
<input type="text" id="newCaseDescription" placeholder="A brief description..." autocomplete="off">
|
||
</div>
|
||
<div class="form-row docker-quick-row">
|
||
<label class="checkbox-row"><input type="checkbox" id="newCaseDocker"> 🐳 Run in an isolated Docker container</label>
|
||
<span class="form-hint">Runs this case in a hardened, isolated container. The base image is built automatically on first use. Docker/Podman must be installed.</span>
|
||
</div>
|
||
<details class="advanced-options docker-quick-settings" id="dockerQuickSettings">
|
||
<summary>Container settings (optional, sensible defaults)</summary>
|
||
<div class="advanced-options-content">
|
||
<div class="form-row">
|
||
<label>Template</label>
|
||
<select id="quickDockerTemplate" onchange="app.applyDockerTemplate()">
|
||
<option value="small">Small — 2 GB RAM, 1 CPU</option>
|
||
<option value="medium" selected>Medium — 4 GB RAM, 2 CPU (default)</option>
|
||
<option value="large">Large — 8 GB RAM, 4 CPU</option>
|
||
<option value="gpu">GPU — 8 GB RAM, 4 CPU, all GPUs</option>
|
||
<option value="custom">Custom</option>
|
||
</select>
|
||
<span class="form-hint">Disk is elastic: storage grows automatically as data flows in (no fixed cap).</span>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Memory</label>
|
||
<input type="text" id="quickDockerMemory" placeholder="4g" autocomplete="off" spellcheck="false">
|
||
</div>
|
||
<div class="form-row">
|
||
<label>CPUs</label>
|
||
<input type="text" id="quickDockerCpus" placeholder="2" autocomplete="off" spellcheck="false">
|
||
</div>
|
||
<div class="form-row">
|
||
<label>GPUs</label>
|
||
<input type="text" id="quickDockerGpus" placeholder="none (e.g. all, or 1)" autocomplete="off" spellcheck="false">
|
||
<span class="form-hint">Needs the NVIDIA container toolkit on the host.</span>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Network</label>
|
||
<select id="quickDockerNetwork">
|
||
<option value="bridge">bridge (internet on)</option>
|
||
<option value="none">none (fully isolated)</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Image</label>
|
||
<input type="text" id="quickDockerImage" placeholder="codeman/agent:base" autocomplete="off" spellcheck="false">
|
||
</div>
|
||
<div class="form-row">
|
||
<label class="checkbox-row"><input type="checkbox" id="quickDockerMountCreds" checked> Mount host credentials (~/.claude etc.)</label>
|
||
</div>
|
||
</div>
|
||
</details>
|
||
</div>
|
||
<!-- Clone Repo Tab (issue #236) -->
|
||
<div class="modal-tab-content hidden" id="case-clone">
|
||
<div class="form-row">
|
||
<label>Repository URL</label>
|
||
<input type="url" id="cloneRepoUrl" placeholder="https://github.com/owner/repo.git" autocomplete="off" autocapitalize="off" autocorrect="off" spellcheck="false" oninput="app.onCloneUrlInput()">
|
||
<span class="form-hint clone-status" id="cloneRepoStatus">Public repositories only: Codeman clones with no credentials.</span>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Case Name</label>
|
||
<input type="text" id="cloneCaseName" placeholder="repo" pattern="[a-zA-Z0-9_-]+" autocomplete="off" autocapitalize="off" spellcheck="false" oninput="app.onCloneNameEdited()">
|
||
<span class="form-hint">Filled in from the URL. Cloned into ~/codeman-cases/<name>, so deleting the case deletes this working tree.</span>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Branch or Tag (optional)</label>
|
||
<input type="text" id="cloneRepoRef" list="cloneRepoRefOptions" placeholder="default branch" autocomplete="off" autocapitalize="off" spellcheck="false">
|
||
<datalist id="cloneRepoRefOptions"></datalist>
|
||
<span class="form-hint" id="cloneRefHint">Leave blank for the repository's default branch.</span>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Brain</label>
|
||
<select id="cloneCaseBrain" class="form-select">
|
||
<option value="">Leave the Run button as it is</option>
|
||
<option value="claude" data-cli="claude">Claude Code</option>
|
||
<option value="codex" data-cli="codex">Codex</option>
|
||
<option value="gemini" data-cli="gemini">Gemini</option>
|
||
<option value="opencode" data-cli="opencode">OpenCode</option>
|
||
<option value="antigravity" data-cli="antigravity">Antigravity</option>
|
||
<option value="shell">Shell (no agent)</option>
|
||
</select>
|
||
<span class="form-hint">Which CLI to point the Run button at once the clone finishes. Changeable any time from the Run dropdown.</span>
|
||
</div>
|
||
<details class="advanced-options">
|
||
<summary>Clone options</summary>
|
||
<div class="advanced-options-content">
|
||
<div class="form-row">
|
||
<label class="checkbox-row"><input type="checkbox" id="cloneShallow"> Shallow clone (--depth 1)</label>
|
||
<span class="form-hint">Much faster on big repositories, but there is no history to read afterwards.</span>
|
||
</div>
|
||
<div class="form-row">
|
||
<label class="checkbox-row"><input type="checkbox" id="cloneStartSession"> Start a session when the clone finishes</label>
|
||
</div>
|
||
</div>
|
||
</details>
|
||
<span class="form-hint" id="cloneCaseNote" style="margin-top: 8px; display: block;">The clone runs while this request is open, so a large repository takes a while. The case appears as soon as git finishes, even if the browser gave up waiting.</span>
|
||
</div>
|
||
<!-- Link Existing Tab -->
|
||
<div class="modal-tab-content hidden" id="case-link">
|
||
<div class="form-row">
|
||
<label>Case Name</label>
|
||
<input type="text" id="linkCaseName" placeholder="my-project" pattern="[a-zA-Z0-9_-]+" autocomplete="off" autocapitalize="off" spellcheck="false">
|
||
<span class="form-hint">Name to identify this case in Codeman</span>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Folder Path</label>
|
||
<div class="path-input-group">
|
||
<input type="text" id="linkCasePath" placeholder="/mnt/d/AI/my-project" autocomplete="off" autocapitalize="off" autocorrect="off" spellcheck="false">
|
||
<button type="button" class="btn path-input-browse" onclick="app.openLinkCasePathPicker()">Browse…</button>
|
||
</div>
|
||
<span class="form-hint">Choose an existing folder from this computer or enter its absolute path</span>
|
||
</div>
|
||
</div>
|
||
<!-- Remote Tab -->
|
||
<div class="modal-tab-content hidden" id="case-remote">
|
||
<div class="form-row">
|
||
<label>Case Name</label>
|
||
<input type="text" id="remoteCaseName" placeholder="gpu-work" pattern="[a-zA-Z0-9_-]+" autocomplete="off" autocapitalize="off" spellcheck="false">
|
||
<span class="form-hint">Name to identify this remote case in Codeman</span>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Remote Path</label>
|
||
<input type="text" id="remoteCasePath" placeholder="/home/user/projects/work" autocomplete="off" autocapitalize="off" autocorrect="off" spellcheck="false">
|
||
<span class="form-hint">Absolute path on the remote host. Codeman will not create or delete it.</span>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Host ID</label>
|
||
<input type="text" id="remoteHostId" placeholder="gpu-box" pattern="[a-zA-Z0-9_-]+" autocomplete="off" autocapitalize="off" spellcheck="false">
|
||
</div>
|
||
<div class="form-row">
|
||
<label>SSH Host/IP</label>
|
||
<input type="text" id="remoteHostAddress" placeholder="10.0.0.42" autocomplete="off" autocapitalize="off" spellcheck="false">
|
||
</div>
|
||
<div class="form-row">
|
||
<label>SSH Username</label>
|
||
<input type="text" id="remoteHostUsername" placeholder="ubuntu" autocomplete="off" autocapitalize="off" spellcheck="false">
|
||
</div>
|
||
<div class="form-row">
|
||
<label>SSH Port</label>
|
||
<input type="number" id="remoteHostPort" placeholder="22" min="1" max="65535" autocomplete="off">
|
||
<span class="form-hint">Optional. Leave blank for the default port 22.</span>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Codex Command Override</label>
|
||
<input type="text" id="remoteHostCodexCommand" placeholder="exec codx personal" autocomplete="off" autocapitalize="off" spellcheck="false">
|
||
<span class="form-hint">Optional. Leave blank to use exec codex on the remote host.</span>
|
||
</div>
|
||
<details class="advanced-options">
|
||
<summary>Advanced SSH</summary>
|
||
<div class="advanced-options-content">
|
||
<div class="form-row">
|
||
<label>Identity File</label>
|
||
<input type="text" id="remoteHostIdentityFile" placeholder="~/.ssh/remote_ed25519" autocomplete="off" autocapitalize="off" autocorrect="off" spellcheck="false">
|
||
<span class="form-hint">Optional. Path to a private key on this machine (passed to ssh -i). Never the key contents.</span>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>SOCKS Proxy</label>
|
||
<input type="text" id="remoteHostSocksProxy" placeholder="127.0.0.1:1080" autocomplete="off" autocapitalize="off" spellcheck="false">
|
||
<span class="form-hint">Optional. host:port of a SOCKS5 proxy (e.g. cloudflared). Routes ssh through it via a ProxyCommand.</span>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Jump Host</label>
|
||
<input type="text" id="remoteHostJumpHost" placeholder="bastion@10.0.0.1:22" autocomplete="off" autocapitalize="off" spellcheck="false">
|
||
<span class="form-hint">Optional. [user@]host[:port] for ssh -J (jump/bastion host).</span>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Extra -o Options</label>
|
||
<textarea id="remoteHostExtraSshOptions" rows="3" placeholder="StrictHostKeyChecking=accept-new ConnectTimeout=10" autocomplete="off" autocapitalize="off" spellcheck="false"></textarea>
|
||
<span class="form-hint">Optional. One KEY=VALUE per line; each becomes an ssh -o option.</span>
|
||
</div>
|
||
</div>
|
||
</details>
|
||
<!-- COD-105 — discover + attach existing remote tmux sessions this Codeman didn't create. -->
|
||
<details class="advanced-options" id="remoteDiscoverSection">
|
||
<summary>Discover existing sessions</summary>
|
||
<div class="advanced-options-content">
|
||
<span class="form-hint">Find <code>codeman-*</code> tmux sessions already running on this host (started by the remote's own Codeman or another instance) and attach to one. Attaching shares the session; closing the tab detaches it — it is never killed.</span>
|
||
<div class="form-row" style="margin-top: 8px;">
|
||
<button type="button" class="btn-toolbar" id="remoteDiscoverBtn" onclick="app.discoverRemoteSessions()">Discover existing sessions</button>
|
||
</div>
|
||
<div id="remoteDiscoverResults" class="remote-discover-results"></div>
|
||
</div>
|
||
</details>
|
||
</div>
|
||
<!-- Docker Tab -->
|
||
<div class="modal-tab-content hidden" id="case-docker">
|
||
<div class="form-row">
|
||
<label>Case Name</label>
|
||
<input type="text" id="dockerCaseName" placeholder="sandbox" pattern="[a-zA-Z0-9_-]+" autocomplete="off" autocapitalize="off" spellcheck="false">
|
||
<span class="form-hint">Runs inside an isolated container. Multiple sessions can share the same container.</span>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Workspace Path</label>
|
||
<input type="text" id="dockerWorkspacePath" placeholder="/home/user/projects/sandbox" autocomplete="off" autocapitalize="off" autocorrect="off" spellcheck="false">
|
||
<span class="form-hint">Absolute HOST directory, bind-mounted into the container. Codeman scaffolds CLAUDE.md + hooks into it.</span>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Host ID</label>
|
||
<input type="text" id="dockerHostId" placeholder="local" pattern="[a-zA-Z0-9_-]+" autocomplete="off" autocapitalize="off" spellcheck="false">
|
||
<span class="form-hint">A reusable docker host profile. Reuse the same ID across cases to share settings.</span>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Image</label>
|
||
<input type="text" id="dockerImage" placeholder="codeman/agent:base" autocomplete="off" autocapitalize="off" spellcheck="false">
|
||
<span class="form-hint">Build it once with <code>node scripts/build-agent-image.mjs</code>. Contains node + claude/codex/gemini/opencode/agy + tmux.</span>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Network</label>
|
||
<select id="dockerNetwork">
|
||
<option value="bridge">bridge (internet on, default)</option>
|
||
<option value="none">none (fully isolated, no network)</option>
|
||
<option value="custom">custom bridge</option>
|
||
</select>
|
||
</div>
|
||
<details class="advanced-options">
|
||
<summary>Advanced container settings</summary>
|
||
<div class="advanced-options-content">
|
||
<div class="form-row">
|
||
<label>Memory</label>
|
||
<input type="text" id="dockerMemory" placeholder="4g" autocomplete="off" spellcheck="false">
|
||
<span class="form-hint">Optional, e.g. 4g / 512m. Enforced as a hard OOM cap where the engine supports it.</span>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>CPUs</label>
|
||
<input type="text" id="dockerCpus" placeholder="2" autocomplete="off" spellcheck="false">
|
||
</div>
|
||
<div class="form-row">
|
||
<label class="checkbox-row"><input type="checkbox" id="dockerMountCredentials" checked> Mount host credentials (~/.claude etc.)</label>
|
||
<span class="form-hint">On: your existing login just works (creds stay on the host, never in exports). Off: sealed sandbox, log in inside the container.</span>
|
||
</div>
|
||
<div class="form-row">
|
||
<label class="checkbox-row"><input type="checkbox" id="dockerResumeOnStart" checked> Resume last conversation on relaunch</label>
|
||
</div>
|
||
</div>
|
||
</details>
|
||
<span class="form-hint" id="dockerLinkStatus" style="margin-top: 8px; display: block;"></span>
|
||
</div>
|
||
<!-- Manage Tab -->
|
||
<div class="modal-tab-content hidden" id="case-manage">
|
||
<div class="case-manage-list" id="caseManageList">
|
||
<!-- Populated by JS -->
|
||
</div>
|
||
<span class="form-hint" style="margin-top: 8px; display: block;">Use arrows to reorder. Changes are saved automatically.</span>
|
||
<div id="dockerExportsSection" style="margin-top: 16px; border-top: 1px solid var(--border, #333); padding-top: 12px;">
|
||
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom:8px;">
|
||
<strong style="font-size: 13px;">Docker exports</strong>
|
||
<button class="btn-toolbar" onclick="app.refreshDockerExports()">Refresh</button>
|
||
</div>
|
||
<div class="case-manage-list" id="dockerExportsList"><span class="form-hint">No exports yet. Export a docker case from its tab.</span></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="form-actions">
|
||
<button class="btn-toolbar" onclick="app.closeCreateCaseModal()">Cancel</button>
|
||
<button class="btn-toolbar btn-primary" id="caseModalSubmit" onclick="app.submitCaseModal()">Create</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Mobile Case Picker (Bottom Sheet) -->
|
||
<div class="modal mobile-case-picker" id="mobileCasePickerModal">
|
||
<div class="modal-backdrop" onclick="app.closeMobileCasePicker()"></div>
|
||
<div class="mobile-case-picker-sheet">
|
||
<div class="mobile-case-picker-header">
|
||
<h3>Select Case</h3>
|
||
<button class="modal-close" onclick="app.closeMobileCasePicker()" aria-label="Close case picker">×</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">×</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)">∞</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">■</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">×</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">✓</button>
|
||
<button class="btn-notif-action" onclick="app.notificationManager?.clearAll()" title="Clear all">🗑</button>
|
||
<button class="btn-notif-action" onclick="app.toggleNotifications()" title="Close">×</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">↻ Refresh</button>
|
||
<button class="modal-close" onclick="app.closeAwayDigest()" aria-label="Close away digest">×</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">×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<input type="search" id="sessionManagerSearch" class="search-input" placeholder="Search sessions by name, prompt, or path…" 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">×</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">×</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">×</button>
|
||
</div>
|
||
</div>
|
||
<div style="overflow:auto;height:calc(100% - 36px);padding:4px 0">
|
||
<table id="lifecycleTable" style="width:100%;border-collapse:collapse;font-size:12px;font-family:monospace">
|
||
<thead>
|
||
<tr style="color:#888;text-align:left;border-bottom:1px solid #333;position:sticky;top:0;background:#111">
|
||
<th style="padding:4px 8px">Time</th>
|
||
<th style="padding:4px 8px">Event</th>
|
||
<th style="padding:4px 8px">Session</th>
|
||
<th style="padding:4px 8px">Reason</th>
|
||
<th style="padding:4px 8px">Extra</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="lifecycleTableBody"></tbody>
|
||
</table>
|
||
<div id="lifecycleEmpty" style="color:#666;text-align:center;padding:20px;display:none">No lifecycle entries found</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- SVG Overlay for Subagent Connection Lines -->
|
||
<svg id="connectionLines" class="connection-lines-svg">
|
||
<!-- Lines drawn dynamically -->
|
||
</svg>
|
||
|
||
<!-- Connection-loss overlay: the app shell is served from the service-worker
|
||
cache, so Codeman renders even with nothing reachable. Without this, that
|
||
looks like an empty dashboard rather than a dead connection. Only shown
|
||
while no server state has loaded this page load. -->
|
||
<div class="offline-overlay" id="offlineOverlay" hidden>
|
||
<div class="offline-overlay-card" role="alertdialog" aria-labelledby="offlineOverlayTitle" aria-describedby="offlineOverlayBody">
|
||
<div class="offline-overlay-icon" aria-hidden="true">
|
||
<svg width="46" height="46" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="M1 1l22 22"/>
|
||
<path d="M16.72 11.06A10.94 10.94 0 0 1 19 12.55"/>
|
||
<path d="M5 12.55a10.94 10.94 0 0 1 5.17-2.39"/>
|
||
<path d="M10.71 5.05A16 16 0 0 1 22.58 9"/>
|
||
<path d="M1.42 9a15.91 15.91 0 0 1 4.7-2.88"/>
|
||
<path d="M8.53 16.11a6 6 0 0 1 6.95 0"/>
|
||
<line x1="12" y1="20" x2="12.01" y2="20"/>
|
||
</svg>
|
||
</div>
|
||
<h2 class="offline-overlay-title" id="offlineOverlayTitle">Can't reach the Codeman server</h2>
|
||
<p class="offline-overlay-body" id="offlineOverlayBody"></p>
|
||
<div class="offline-overlay-host" id="offlineOverlayHost"></div>
|
||
<ul class="offline-overlay-hints">
|
||
<li>Check Wi-Fi or mobile data</li>
|
||
<li>Check your VPN / Tailscale is connected</li>
|
||
<li>Check the Codeman server is still running</li>
|
||
</ul>
|
||
<div class="offline-overlay-actions">
|
||
<button class="offline-overlay-btn offline-overlay-btn--primary" id="offlineOverlayRetry" onclick="app.retryConnection()">Retry now</button>
|
||
<button class="offline-overlay-btn" onclick="app.dismissOfflineOverlay()">Show cached view</button>
|
||
</div>
|
||
<div class="offline-overlay-status" id="offlineOverlayStatus">Retrying…</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Approvals Inbox drawer (populated by approvals-ui.js; opened from the header bell) -->
|
||
<!-- Read My Mind: predicted-next-prompt modal (readmymind-ui.js). Suggestion
|
||
text is set via value/textContent only: predictor output derives from
|
||
observable (injectable) content, and the explicit click here is the
|
||
security boundary (nothing is ever auto-sent). -->
|
||
<div class="modal" id="readMyMindModal">
|
||
<div class="modal-backdrop" onclick="app.closeReadMyMind()"></div>
|
||
<div class="modal-content readmymind-modal">
|
||
<div class="modal-header">
|
||
<h3><span aria-hidden="true">🧠</span> Read My Mind</h3>
|
||
<button class="modal-close" onclick="app.closeReadMyMind()" aria-label="Close">×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="readmymind-loading">Reading your mind…</div>
|
||
<div class="readmymind-result" style="display:none">
|
||
<div class="readmymind-suggestion-row">
|
||
<span class="readmymind-kind" id="readMyMindKind" data-i18n-skip>continue</span>
|
||
<input type="text" id="readMyMindPrompt" class="readmymind-prompt-input" data-i18n-skip aria-label="Predicted prompt, editable" onkeydown="if(event.key==='Enter')app.sendReadMyMind(true)">
|
||
</div>
|
||
<div class="readmymind-why" id="readMyMindWhy" data-i18n-skip></div>
|
||
<div class="readmymind-alternates" id="readMyMindAlternates" data-i18n-skip style="display:none"></div>
|
||
</div>
|
||
<div class="readmymind-error" style="display:none">No suggestion this time. Rethink to try again.</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button class="btn btn-secondary" onclick="app.closeReadMyMind()">Dismiss</button>
|
||
<button class="btn btn-secondary" id="readMyMindRethink" onclick="app.rethinkReadMyMind()">Rethink</button>
|
||
<button class="btn btn-secondary" onclick="app.sendReadMyMind(false)" title="Put the text on the session's composer without submitting it">Insert</button>
|
||
<button class="btn btn-primary" onclick="app.sendReadMyMind(true)">Send</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="approvals-drawer" id="approvalsDrawer" role="complementary" aria-label="Approvals inbox">
|
||
<div class="approvals-header">
|
||
<div>
|
||
<div class="approvals-title">Approvals</div>
|
||
<div class="approvals-subtitle">Prompts waiting on you, across all sessions</div>
|
||
</div>
|
||
<button class="approvals-close" onclick="app.closeApprovalsInbox()" title="Close" aria-label="Close approvals inbox">✕</button>
|
||
</div>
|
||
<div class="approvals-list"></div>
|
||
</div>
|
||
|
||
<script defer src="constants.js"></script>
|
||
<script defer src="i18n.js"></script>
|
||
<script defer src="mobile-handlers.js"></script>
|
||
<script defer src="voice-input.js"></script>
|
||
<script defer src="notification-manager.js"></script>
|
||
<script defer src="keyboard-accessory.js"></script>
|
||
<script defer src="input-cjk.js"></script>
|
||
<!-- Hardened markdown HTML sanitizer (wires DOMPurify). Must precede app.js. -->
|
||
<script defer src="sanitize-html.js"></script>
|
||
<script defer src="app.js"></script>
|
||
<script defer src="terminal-ui.js"></script>
|
||
<script defer src="respawn-ui.js"></script>
|
||
<script defer src="ralph-panel.js"></script>
|
||
<script defer src="orchestrator-panel.js"></script>
|
||
<script defer src="cron-ui.js"></script>
|
||
<script defer src="settings-ui.js"></script>
|
||
<script defer src="panels-ui.js"></script>
|
||
<script defer src="readmymind-ui.js"></script>
|
||
<script defer src="ultracode-panel.js"></script>
|
||
<script defer src="approvals-ui.js"></script>
|
||
<script defer src="admin-ui.js"></script>
|
||
<script defer src="session-ui.js"></script>
|
||
<script defer src="webview-tabs.js"></script>
|
||
<script defer src="mobile-overview.js"></script>
|
||
<script defer src="home-sessions.js"></script>
|
||
<script defer src="entrance-animations.js"></script>
|
||
<script defer src="ralph-wizard.js"></script>
|
||
<script defer src="api-client.js"></script>
|
||
<script defer src="subagent-windows.js"></script>
|
||
<script defer src="ultracode-windows.js"></script>
|
||
<script defer src="image-input.js"></script>
|
||
</body>
|
||
</html>
|