diff --git a/CLAUDE.md b/CLAUDE.md index 90a15f74..280a6fab 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -35,7 +35,7 @@ When user says "COM": 1. Increment version in BOTH `package.json` AND `CLAUDE.md` (verify they match with `grep version package.json && grep Version CLAUDE.md`) 2. Run: `git add -A && git commit -m "chore: bump version to X.XXXX" && git push && npm run build && systemctl --user restart claudeman-web` -**Version**: 0.1556 (must match `package.json` for npm publish) +**Version**: 0.1557 (must match `package.json` for npm publish) ## Project Overview @@ -120,6 +120,7 @@ journalctl --user -u claudeman-web -f | `src/bash-tool-parser.ts` | Parses Claude's bash tool invocations from output | | `src/transcript-watcher.ts` | Watches Claude's transcript files for changes | | `src/hooks-config.ts` | Manages `.claude/settings.local.json` hook configuration | +| `src/session-lifecycle-log.ts` | Append-only JSONL audit log at `~/.claudeman/session-lifecycle.jsonl` | | `src/image-watcher.ts` | Watches for image file creation (screenshots, etc.) | | `src/file-stream-manager.ts` | Manages `tail -f` processes for live log viewing | | `src/plan-orchestrator.ts` | Multi-agent plan generation with research and planning phases | diff --git a/docs/images/claude-overview.png b/docs/images/claude-overview.png index 737374e3..e792315f 100644 Binary files a/docs/images/claude-overview.png and b/docs/images/claude-overview.png differ diff --git a/docs/images/ralph-tracker-8tasks-44percent.png b/docs/images/ralph-tracker-8tasks-44percent.png index fdc1355f..c41cd14d 100644 Binary files a/docs/images/ralph-tracker-8tasks-44percent.png and b/docs/images/ralph-tracker-8tasks-44percent.png differ diff --git a/docs/images/subagent-spawn.png b/docs/images/subagent-spawn.png index 50015d94..7a22f457 100644 Binary files a/docs/images/subagent-spawn.png and b/docs/images/subagent-spawn.png differ diff --git a/docs/screenshots/multi-session-dashboard.png b/docs/screenshots/multi-session-dashboard.png index 39a79689..dfecbcfe 100644 Binary files a/docs/screenshots/multi-session-dashboard.png and b/docs/screenshots/multi-session-dashboard.png differ diff --git a/docs/screenshots/multi-session-monitor.png b/docs/screenshots/multi-session-monitor.png index 6431da2e..e792315f 100644 Binary files a/docs/screenshots/multi-session-monitor.png and b/docs/screenshots/multi-session-monitor.png differ diff --git a/package.json b/package.json index 116e0911..e05b294c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "claudeman", - "version": "0.1556", + "version": "0.1557", "description": "The missing control plane for Claude Code - run 20 autonomous agents with real-time monitoring and session persistence", "type": "module", "main": "dist/index.js", diff --git a/scripts/capture-readme-screenshots.mjs b/scripts/capture-readme-screenshots.mjs index 108defb2..c758948b 100644 --- a/scripts/capture-readme-screenshots.mjs +++ b/scripts/capture-readme-screenshots.mjs @@ -22,6 +22,7 @@ const PROJECT_ROOT = join(__dirname, '..'); const PUBLIC_DIR = join(PROJECT_ROOT, 'src', 'web', 'public'); const PORT = 3199; const VIEWPORT = { width: 1280, height: 720 }; +const DEVICE_SCALE_FACTOR = 1; const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); @@ -117,10 +118,10 @@ function makeSession(id, name, mode, status, extra = {}) { niceValue: 10, color: 'default', flickerFilterEnabled: false, - cliVersion: '2.1.14', - cliModel: 'Opus 4.5', + cliVersion: '2.1.47', + cliModel: 'Opus 4.6', cliAccountType: 'Claude Max', - cliLatestVersion: '2.1.14', + cliLatestVersion: '2.1.47', messageCount: mode === 'claude' ? 15 : 0, isWorking: status === 'busy', lastPromptTime: Date.now() - 30000, @@ -298,7 +299,7 @@ function buildInitPayload(sessions, subagents = []) { } return { - version: '0.1540', + version: '0.1556', sessions, scheduledRuns: [], respawnStatus, @@ -333,8 +334,8 @@ const DIM = '\x1b[2m'; // Claude Code init banner (matching the overview screenshot) const TERMINAL_INIT = [ '', - ` ${RED}████${RST} ${BOLD}Claude Code${RST} v2.1.14`, - ` ${RED}████${RST} ${GRY}Opus 4.5${RST} ${DIM}·${RST} ${GRY}Claude Max${RST}`, + ` ${RED}████${RST} ${BOLD}Claude Code${RST} v2.1.47`, + ` ${RED}████${RST} ${GRY}Opus 4.6${RST} ${DIM}·${RST} ${GRY}Claude Max${RST}`, ` ${RED}██${RST} ${RED}██${RST} ${GRY}~/claudeman-cases/testcase${RST}`, '', '', @@ -343,7 +344,7 @@ const TERMINAL_INIT = [ '', `${YEL}»»${RST} ${BOLD}bypass permissions on${RST} ${GRY}(shift+tab to cycle)${RST}`, ` ${BOLD}0 tokens${RST}`, - ` ${GRY}current: 2.1.14${RST} ${DIM}·${RST} ${GRY}latest: 2.1.14${RST}`, + ` ${GRY}current: 2.1.47${RST} ${DIM}·${RST} ${GRY}latest: 2.1.47${RST}`, ].join('\r\n'); // Claude working output (for multi-session-dashboard / subagent-spawn) @@ -380,7 +381,7 @@ const TERMINAL_WORKING = [ `────────────────────────────────────────────────────────────────────────────────────────────────────────────────`, ` ${YEL}»»${RST} ${BOLD}bypass permissions on${RST} ${GRY}(shift+tab to cycle)${RST}`, ` ${BOLD}28163 tokens${RST}`, - ` ${GRY}current: 2.1.14${RST} ${DIM}·${RST} ${GRY}latest: 2.1.14${RST}`, + ` ${GRY}current: 2.1.47${RST} ${DIM}·${RST} ${GRY}latest: 2.1.47${RST}`, ].join('\r\n'); // Ralph terminal content (matching ralph-tracker screenshot) @@ -412,7 +413,7 @@ const TERMINAL_RALPH = [ `────────────────────────────────────────────────────────────────────────────────────────────────────────────────`, ` ${YEL}»»${RST} ${BOLD}bypass permissions on${RST} ${GRY}(shift+tab to cycle)${RST}`, ` ${BOLD}37267 tokens${RST}`, - ` ${GRY}current: 2.1.14${RST} ${DIM}·${RST} ${GRY}latest: 2.1.14${RST}`, + ` ${GRY}current: 2.1.47${RST} ${DIM}·${RST} ${GRY}latest: 2.1.47${RST}`, ].join('\r\n'); // Subagent window content (tool call activity) @@ -470,7 +471,7 @@ const TERMINAL_SUBAGENT = [ '', '', ` ${YEL}»»${RST} ${BOLD}bypass permissions on${RST} ${GRY}(shift+tab to cycle)${RST}`, - ` ${GRY}current: 2.1.14${RST} ${DIM}·${RST} ${GRY}latest: 2.1.14${RST}`, + ` ${GRY}current: 2.1.47${RST} ${DIM}·${RST} ${GRY}latest: 2.1.47${RST}`, ].join('\r\n'); // ─── Route Interceptors ────────────────────────────────────────────────────── @@ -589,7 +590,8 @@ async function setupRoutes(page, initPayload, terminalContent) { */ async function injectState(page, initPayload, terminalContent, activeSessionId) { // Wait for app to be ready - await page.waitForFunction(() => window.app && window.app.terminal, { timeout: 10000 }); + await page.waitForFunction(() => window.app && window.app.terminal, { timeout: 15000 }); + await sleep(1000); // Cancel the SSE fallback timer and inject state directly await page.evaluate((payload) => { @@ -603,7 +605,7 @@ async function injectState(page, initPayload, terminalContent, activeSessionId) app.handleInit(payload); }, initPayload); - await sleep(500); + await sleep(1500); // Select the target session (this triggers terminal fetch via our mocked route) if (activeSessionId) { @@ -611,7 +613,7 @@ async function injectState(page, initPayload, terminalContent, activeSessionId) window.app.activeSessionId = null; // Force re-select window.app.selectSession(sid); }, activeSessionId); - await sleep(800); + await sleep(2000); } // Write terminal content directly as backup (in case fetch didn't work) @@ -630,11 +632,25 @@ async function injectState(page, initPayload, terminalContent, activeSessionId) } }, terminalContent); + await sleep(1000); + // Clean up UI elements that look wrong in screenshots await page.evaluate(() => { - // Hide connection indicator (SSE is blocked so it shows "Reconnecting...") + // Kill SSE reconnection entirely and hide the connection indicator + const app2 = window.app; + if (app2) { + // Stop reconnection timers + if (app2.sseReconnectTimeout) clearTimeout(app2.sseReconnectTimeout); + if (app2.eventSource) { app2.eventSource.close(); app2.eventSource = null; } + app2._connectionStatus = 'connected'; + // Monkey-patch so it never re-shows + app2._updateConnectionIndicator = () => {}; + app2.connectSSE = () => {}; + app2.setConnectionStatus = () => {}; + } + // Remove the indicator element from the DOM entirely const indicator = document.getElementById('connectionIndicator'); - if (indicator) indicator.style.display = 'none'; + if (indicator) indicator.remove(); // Hide respawn banner (shows "idle" state which isn't needed in screenshots) const respawnBanner = document.getElementById('respawnBanner'); @@ -676,7 +692,8 @@ async function injectState(page, initPayload, terminalContent, activeSessionId) } }); - await sleep(300); + // Final settle — let xterm.js WebGL renderer, fonts, and layout stabilize + await sleep(2000); } // ─── Screenshot Scenarios ──────────────────────────────────────────────────── @@ -701,7 +718,7 @@ async function captureOverview(page) { app._renderMuxSessionsImmediate(); } }, MUX_SESSIONS); - await sleep(500); + await sleep(1500); await page.screenshot({ path: join(PROJECT_ROOT, 'docs', 'images', 'claude-overview.png'), @@ -733,9 +750,9 @@ async function captureSubagentSpawn(page) { app.openSubagentWindow('agent-001'); app.openSubagentWindow('agent-002'); }, SUBAGENT_ACTIVITY); - await sleep(800); + await sleep(2000); - // Position windows nicely (matching existing screenshot layout) + // Position windows nicely for 1280x720 viewport await page.evaluate(() => { const app = window.app; if (!app) return; @@ -755,7 +772,7 @@ async function captureSubagentSpawn(page) { w2.style.height = '320px'; } }); - await sleep(500); + await sleep(1500); await page.screenshot({ path: join(PROJECT_ROOT, 'docs', 'images', 'subagent-spawn.png'), @@ -789,7 +806,7 @@ async function captureRalphTracker(page) { app._renderRalphStatePanelImmediate(); } }, RALPH_SESSION_ID); - await sleep(500); + await sleep(1500); await page.screenshot({ path: join(PROJECT_ROOT, 'docs', 'images', 'ralph-tracker-8tasks-44percent.png'), @@ -834,7 +851,7 @@ async function captureMultiSessionMonitor(page) { app._renderMuxSessionsImmediate(); } }, MUX_SESSIONS); - await sleep(500); + await sleep(1500); await page.screenshot({ path: join(PROJECT_ROOT, 'docs', 'screenshots', 'multi-session-monitor.png'), @@ -874,9 +891,9 @@ async function main() { captureMultiSessionDashboard, captureMultiSessionMonitor, ]) { - const context = await browser.newContext({ viewport: VIEWPORT }); + const context = await browser.newContext({ viewport: VIEWPORT, deviceScaleFactor: DEVICE_SCALE_FACTOR }); const page = await context.newPage(); - page.setDefaultTimeout(15000); + page.setDefaultTimeout(30000); try { await scenario(page); diff --git a/src/web/public/app.js b/src/web/public/app.js index 590a04ec..dbb55589 100644 --- a/src/web/public/app.js +++ b/src/web/public/app.js @@ -9909,12 +9909,51 @@ class ClaudemanApp { // ========== Session Lifecycle Log ========== openLifecycleLog() { - document.getElementById('lifecycleModal').classList.add('active'); + const win = document.getElementById('lifecycleWindow'); + win.style.display = 'block'; + // Reset transform so it appears centered initially + if (!win._dragInitialized) { + win.style.left = '50%'; + win.style.transform = 'translateX(-50%)'; + this._initLifecycleDrag(win); + win._dragInitialized = true; + } this.loadLifecycleLog(); } closeLifecycleLog() { - document.getElementById('lifecycleModal').classList.remove('active'); + document.getElementById('lifecycleWindow').style.display = 'none'; + } + + _initLifecycleDrag(win) { + const header = document.getElementById('lifecycleWindowHeader'); + let isDragging = false; + let startX, startY, startLeft, startTop; + + header.addEventListener('mousedown', (e) => { + if (e.target.tagName === 'SELECT' || e.target.tagName === 'INPUT' || e.target.tagName === 'BUTTON') return; + isDragging = true; + // Clear transform so left/top work in absolute pixels + const rect = win.getBoundingClientRect(); + win.style.transform = 'none'; + win.style.left = rect.left + 'px'; + win.style.top = rect.top + 'px'; + startX = e.clientX; + startY = e.clientY; + startLeft = rect.left; + startTop = rect.top; + e.preventDefault(); + }); + + document.addEventListener('mousemove', (e) => { + if (!isDragging) return; + win.style.left = (startLeft + e.clientX - startX) + 'px'; + win.style.top = (startTop + e.clientY - startY) + 'px'; + }); + + document.addEventListener('mouseup', () => { + isDragging = false; + }); } async loadLifecycleLog() { diff --git a/src/web/public/index.html b/src/web/public/index.html index 7d4878b3..32891a95 100644 --- a/src/web/public/index.html +++ b/src/web/public/index.html @@ -8,8 +8,8 @@ Claudeman - - + + @@ -84,7 +84,7 @@ - +
0 tokens
@@ -1501,17 +1501,13 @@ - - @@ -1548,6 +1547,6 @@ - + diff --git a/src/web/public/mobile.css b/src/web/public/mobile.css index 27ccf40e..117af99a 100644 --- a/src/web/public/mobile.css +++ b/src/web/public/mobile.css @@ -308,8 +308,9 @@ html.mobile-init .file-browser-panel { height: 12px; } - /* Hide header settings gear on mobile - moved to toolbar */ - .btn-icon-header.btn-settings { + /* Hide header settings gear and lifecycle log on mobile - settings moved to toolbar */ + .btn-icon-header.btn-settings, + .btn-icon-header.btn-lifecycle-log { display: none !important; }