diff --git a/README.md b/README.md index 20e7e6e4..c8013508 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,9 @@ The web interface provides: - **Resource Display**: Real-time memory usage and message count per session - **Respawn Controls**: Start/stop respawn controller with configurable settings - **Case Management**: Create new project workspaces with CLAUDE.md templates +- **Keyboard Shortcuts**: Quick access to common actions (Ctrl+Enter, Ctrl+K, Ctrl+L) +- **Toast Notifications**: Non-intrusive status updates and alerts +- **Mobile Support**: Responsive design optimized for tablets and phones - **Modern UI**: Gradient backgrounds, smooth animations, and polished styling #### Quick Start Button @@ -74,6 +77,16 @@ The Quick Start button will: - Start an interactive Claude terminal - Focus the terminal so you can start working immediately +#### Keyboard Shortcuts + +| Shortcut | Action | +|----------|--------| +| `Ctrl+Enter` | Quick Start (create case + interactive session) | +| `Ctrl+K` | Kill all sessions | +| `Ctrl+L` | Clear terminal | +| `Ctrl+1/2/3` | Switch tabs (Run/Cases/Settings) | +| `Escape` | Close modals | + ### CLI Usage ```bash diff --git a/src/web/public/app.js b/src/web/public/app.js index 0f801c15..ece7adc9 100644 --- a/src/web/public/app.js +++ b/src/web/public/app.js @@ -346,6 +346,7 @@ class ClaudemanApp { const data = JSON.parse(e.data); this.respawnStatus[data.sessionId] = data.status; this.showRespawnBanner(); + this.updatePageTitle(); this.terminal.writeln(`\x1b[1;32m🔄 Respawn loop started for session ${data.sessionId.slice(0, 8)}\x1b[0m`); }); @@ -353,6 +354,7 @@ class ClaudemanApp { const data = JSON.parse(e.data); delete this.respawnStatus[data.sessionId]; this.hideRespawnBanner(); + this.updatePageTitle(); this.terminal.writeln(`\x1b[1;33m⏹ Respawn loop stopped\x1b[0m`); }); @@ -969,7 +971,10 @@ class ClaudemanApp { } updateTimer() { - if (!this.currentRun || this.currentRun.status !== 'running') return; + if (!this.currentRun || this.currentRun.status !== 'running') { + this.updatePageTitle(); + return; + } const now = Date.now(); const remaining = Math.max(0, this.currentRun.endAt - now); @@ -981,6 +986,26 @@ class ClaudemanApp { document.getElementById('timerProgress').style.width = `${percent}%`; document.getElementById('timerTasks').textContent = `${this.currentRun.completedTasks} tasks completed`; document.getElementById('timerCost').textContent = `$${this.currentRun.totalCost.toFixed(4)}`; + + // Update page title with timer + document.title = `${this.formatTime(remaining)} - Claudeman`; + } + + // Update page title based on current state + updatePageTitle() { + const sessionCount = this.sessions.size; + const hasRespawn = Object.values(this.respawnStatus).some(s => s.state !== 'stopped'); + + if (this.currentRun && this.currentRun.status === 'running') { + // Title updated by updateTimer() + return; + } else if (hasRespawn) { + document.title = `🔄 Respawning - Claudeman`; + } else if (sessionCount > 0) { + document.title = `(${sessionCount}) Claudeman`; + } else { + document.title = 'Claudeman'; + } } startTimerUpdates() { @@ -1012,6 +1037,7 @@ class ClaudemanApp { renderSessions() { const count = this.sessions.size; document.getElementById('sessionCount').textContent = count; + this.updatePageTitle(); const list = document.getElementById('sessionsList'); diff --git a/src/web/public/index.html b/src/web/public/index.html index b6c44a8f..bfe13990 100644 --- a/src/web/public/index.html +++ b/src/web/public/index.html @@ -3,7 +3,10 @@ + + Claudeman + diff --git a/src/web/public/styles.css b/src/web/public/styles.css index d95fc071..6a630763 100644 --- a/src/web/public/styles.css +++ b/src/web/public/styles.css @@ -752,7 +752,22 @@ body { border-top-color: #1a1a1a; } -/* Responsive */ +/* Responsive - Tablets */ +@media (max-width: 1024px) { + .main { + grid-template-columns: 300px 1fr; + } + + .quick-start-section { + padding: 1rem; + } + + .cases-grid { + grid-template-columns: 1fr; + } +} + +/* Responsive - Small Tablets / Large Phones */ @media (max-width: 768px) { .main { grid-template-columns: 1fr; @@ -762,16 +777,36 @@ body { .control-panel { border-right: none; border-bottom: 1px solid var(--border); + max-height: 50vh; + overflow-y: auto; } .form-row { grid-template-columns: 1fr; } + .header { + padding: 0.75rem 1rem; + flex-wrap: wrap; + gap: 0.75rem; + } + + .header-left { + gap: 0.75rem; + } + + .logo { + font-size: 1.1rem; + } + .header-stats { display: none; } + .connection-status { + font-size: 0.65rem; + } + .sessions-panel { position: relative; max-height: none; @@ -779,11 +814,225 @@ body { .sessions-list { flex-direction: column; + padding: 0 1rem 1rem; } .session-card { width: 100%; } + + .timer-banner { + padding: 1rem; + } + + .timer-value { + font-size: 2rem; + } + + .timer-meta { + flex-wrap: wrap; + gap: 0.75rem; + } + + .output-panel { + padding: 1rem; + } + + .terminal-container { + min-height: 300px; + border-radius: 8px; + } + + .quick-start-controls { + flex-direction: column; + } + + .btn-quick-start { + width: 100%; + justify-content: center; + } + + .tabs { + overflow-x: auto; + -webkit-overflow-scrolling: touch; + padding-bottom: 0.5rem; + } + + .tab { + white-space: nowrap; + padding: 0.5rem 1rem; + } + + .respawn-banner { + flex-wrap: wrap; + padding: 0.75rem 1rem; + gap: 0.5rem; + } + + .respawn-step { + width: 100%; + order: 3; + } + + .modal-content { + width: 95%; + max-height: 90vh; + } + + .modal-body { + max-height: 70vh; + } +} + +/* Responsive - Phones */ +@media (max-width: 480px) { + .header { + padding: 0.5rem 0.75rem; + } + + .logo { + font-size: 1rem; + } + + .connection-status span:last-child { + display: none; + } + + .control-panel { + padding: 1rem; + max-height: 40vh; + } + + .output-panel { + padding: 0.75rem; + } + + .terminal-container { + min-height: 250px; + border-radius: 6px; + } + + .quick-start-section { + padding: 0.875rem; + border-radius: 8px; + } + + .quick-start-header { + margin-bottom: 0.5rem; + } + + .quick-start-title { + font-size: 0.9rem; + } + + .form-group label { + font-size: 0.7rem; + } + + .form-group input, + .form-group textarea { + padding: 0.625rem 0.875rem; + font-size: 16px; /* Prevents iOS zoom on focus */ + } + + select { + padding: 0.625rem 2rem 0.625rem 0.75rem; + font-size: 16px; /* Prevents iOS zoom on focus */ + } + + .btn { + padding: 0.75rem 1rem; + font-size: 0.875rem; + } + + .btn-large { + padding: 1rem 1.25rem; + } + + .sessions-header { + padding: 0.5rem 1rem; + } + + .sessions-list { + padding: 0 0.75rem 0.75rem; + } + + .session-card-header { + padding: 0.75rem; + } + + .session-output { + height: 100px; + padding: 0.5rem; + } + + .timer-banner { + padding: 0.75rem 1rem; + } + + .timer-value { + font-size: 1.75rem; + } + + .timer-info { + gap: 0.5rem; + } + + .toast-container { + top: auto; + bottom: 1rem; + right: 0.5rem; + left: 0.5rem; + } + + .toast { + padding: 0.75rem 1rem; + font-size: 0.8rem; + } +} + +/* Touch optimizations */ +@media (hover: none) and (pointer: coarse) { + .btn { + min-height: 44px; + min-width: 44px; + } + + .btn-sm { + min-height: 36px; + padding: 0.5rem 0.75rem; + } + + .btn-xs { + min-height: 32px; + padding: 0.375rem 0.625rem; + font-size: 0.7rem; + } + + .tab { + min-height: 44px; + padding: 0.75rem 1.25rem; + } + + .case-card { + padding: 1rem; + } + + .session-card:hover { + transform: none; + } + + .case-card:hover { + transform: none; + } + + .modal-close { + min-width: 44px; + min-height: 44px; + display: flex; + align-items: center; + justify-content: center; + } } /* Loading state */