From 363b6d114709514c88cc4377c9a7b524cc330c3d Mon Sep 17 00:00:00 2001 From: arkon Date: Mon, 19 Jan 2026 02:06:37 +0100 Subject: [PATCH] refactor: UI cleanup - move respawn controls, remove mode label, fix dir width - Move respawn controls from toolbar to session options modal - Remove unnecessary Mode label from session options (visible in tab) - Increase directory input width from 200px to 350px - Add RTL direction to directory display to show end of long paths - Add respawn status indicator with stop/configure buttons in modal Co-Authored-By: Claude Opus 4.5 --- CLAUDE.md | 31 +++++++++++++++++++------ src/web/public/app.js | 47 ++++++++++++++++++++++++++++++++++++- src/web/public/index.html | 21 ++++++++--------- src/web/public/styles.css | 49 +++++++++++++++++++++++++++++++++++++-- 4 files changed, 127 insertions(+), 21 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 2ca1f600..b6fa0d2b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,6 +8,8 @@ Claudeman is a Claude Code session manager with a web interface and autonomous R **Tech Stack**: TypeScript, Node.js, Fastify, Server-Sent Events, node-pty +**Requirements**: Node.js 18+, Claude CLI (`claude`) installed and available in PATH + ## Commands ```bash @@ -18,13 +20,6 @@ npm run clean # Remove dist/ npm link # Make 'claudeman' globally available claudeman web # Start web interface on port 3000 claudeman web -p 8080 # Custom port - -# Testing -npm run test # Run all tests once -npm run test:watch # Run tests in watch mode -npm run test:coverage # Run tests with coverage report -npx vitest run test/session.test.ts # Run single test file -npx vitest run -t "should create session" # Run tests matching pattern ``` ## Architecture @@ -314,8 +309,30 @@ GET /api/events # SSE stream (real-time events) GET /api/status # Full state snapshot (sessions + scheduled + respawn) ``` +## Testing + +Tests use Vitest and auto-discover `*.test.ts` files in the `test/` directory: + +```bash +npm run test # Run all tests once +npm run test:watch # Watch mode +npm run test:coverage # With coverage report +npx vitest run test/session.test.ts # Single file +npx vitest run -t "should create session" # By pattern +``` + +## Frontend + +The web UI (`src/web/public/`) uses vanilla JavaScript with: +- **xterm.js**: Terminal emulator with WebGL renderer for 60fps performance +- **xterm-addon-fit**: Auto-resize terminal to container +- **Server-Sent Events**: Real-time updates from `/api/events` +- **No build step**: Static files served directly by Fastify + ## Pending Tasks +**Note to Claude: Do NOT remove or modify this section during /init. These tasks are actively being worked on by other sessions.** + - [ ] Remove the "New Session" tab and add a gear icon (⚙️) in the top right corner for app settings - [ ] Add confirmation dialog when clicking "x" on a session tab - warn user that the screen session behind will be closed - [ ] In settings: add option to configure a default CLAUDE.md file path (e.g. /home/arkon/default/CLAUDE.md) that will be used for new sessions/cases diff --git a/src/web/public/app.js b/src/web/public/app.js index ed63cb0c..7592d311 100644 --- a/src/web/public/app.js +++ b/src/web/public/app.js @@ -1167,14 +1167,59 @@ class ClaudemanApp { this.editingSessionId = sessionId; document.getElementById('sessionNameInput').value = session.name || ''; - document.getElementById('sessionModeDisplay').textContent = session.mode === 'shell' ? 'Shell' : 'Claude'; document.getElementById('sessionDirDisplay').textContent = session.workingDir || 'Unknown'; + + // Update respawn status display + const respawnStatus = document.getElementById('sessionRespawnStatus'); + const stopBtn = document.getElementById('sessionStopRespawnBtn'); + const configBtn = document.getElementById('sessionConfigRespawnBtn'); + + if (this.respawnStatus[sessionId]) { + respawnStatus.classList.add('active'); + respawnStatus.querySelector('.respawn-status-text').textContent = + `Active (${this.respawnStatus[sessionId].state || 'running'})`; + stopBtn.style.display = ''; + configBtn.textContent = 'Reconfigure'; + } else { + respawnStatus.classList.remove('active'); + respawnStatus.querySelector('.respawn-status-text').textContent = 'Not active'; + stopBtn.style.display = 'none'; + configBtn.textContent = 'Configure Respawn'; + } + + // Only show respawn section for claude mode sessions with a running process + const respawnSection = document.getElementById('sessionRespawnSection'); + if (session.mode === 'claude' && session.pid) { + respawnSection.style.display = ''; + } else { + respawnSection.style.display = 'none'; + } + document.getElementById('sessionOptionsModal').classList.add('active'); // Focus the name input setTimeout(() => document.getElementById('sessionNameInput').focus(), 100); } + async stopRespawnFromOptions() { + if (!this.editingSessionId) return; + try { + await fetch(`/api/sessions/${this.editingSessionId}/respawn/stop`, { method: 'POST' }); + delete this.respawnTimers[this.editingSessionId]; + + // Update the modal display + const respawnStatus = document.getElementById('sessionRespawnStatus'); + respawnStatus.classList.remove('active'); + respawnStatus.querySelector('.respawn-status-text').textContent = 'Not active'; + document.getElementById('sessionStopRespawnBtn').style.display = 'none'; + document.getElementById('sessionConfigRespawnBtn').textContent = 'Configure Respawn'; + + this.showToast('Respawn stopped', 'success'); + } catch (err) { + this.showToast('Failed to stop respawn', 'error'); + } + } + closeSessionOptions() { this.editingSessionId = null; document.getElementById('sessionOptionsModal').classList.remove('active'); diff --git a/src/web/public/index.html b/src/web/public/index.html index de1f94bf..8b34b9a5 100644 --- a/src/web/public/index.html +++ b/src/web/public/index.html @@ -85,13 +85,6 @@ - - -
- -
@@ -257,14 +250,20 @@
-
- -
Claude
-
/path/to/dir
+
+ +
+ Not active +
+
+ + +
+
diff --git a/src/web/public/styles.css b/src/web/public/styles.css index 118c6c26..80d62719 100644 --- a/src/web/public/styles.css +++ b/src/web/public/styles.css @@ -486,7 +486,17 @@ body { border-radius: 4px; color: var(--text); font-size: 0.75rem; - width: 200px; + width: 350px; + max-width: 50vw; +} + +#dirDisplay { + max-width: 300px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + direction: rtl; + text-align: left; } .toolbar-input:focus { @@ -714,7 +724,6 @@ body { max-width: 320px; } -.session-mode-display, .session-dir-display { padding: 0.5rem 0.75rem; background: var(--bg-input); @@ -728,6 +737,42 @@ body { white-space: nowrap; } +/* Session respawn section in options modal */ +.session-respawn-section { + border-top: 1px solid var(--border); + padding-top: 0.75rem; + margin-top: 0.5rem; +} + +.session-respawn-status { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.5rem 0.75rem; + background: var(--bg-input); + border: 1px solid var(--border); + border-radius: 4px; + margin-bottom: 0.5rem; +} + +.session-respawn-status .respawn-status-text { + font-size: 0.8rem; + color: var(--text-dim); +} + +.session-respawn-status.active { + border-color: rgba(34, 197, 94, 0.4); +} + +.session-respawn-status.active .respawn-status-text { + color: var(--green); +} + +.session-respawn-actions { + display: flex; + gap: 0.5rem; +} + .modal-header { display: flex; justify-content: space-between;