diff --git a/src/web/public/app.js b/src/web/public/app.js index 5cb5c766..3a642011 100644 --- a/src/web/public/app.js +++ b/src/web/public/app.js @@ -405,26 +405,26 @@ class ClaudemanApp { this.eventSource.addEventListener('screen:created', (e) => { const screen = JSON.parse(e.data); this.screenSessions.push(screen); - this.renderProcessPanel(); + this.renderScreenSessions(); }); this.eventSource.addEventListener('screen:killed', (e) => { const data = JSON.parse(e.data); this.screenSessions = this.screenSessions.filter(s => s.sessionId !== data.sessionId); - this.renderProcessPanel(); + this.renderScreenSessions(); }); this.eventSource.addEventListener('screen:died', (e) => { const data = JSON.parse(e.data); this.screenSessions = this.screenSessions.filter(s => s.sessionId !== data.sessionId); - this.renderProcessPanel(); + this.renderScreenSessions(); this.showToast('Screen session died: ' + data.sessionId.slice(0, 8), 'warning'); }); this.eventSource.addEventListener('screen:statsUpdated', (e) => { this.screenSessions = JSON.parse(e.data); if (document.getElementById('processPanel').classList.contains('open')) { - this.renderProcessPanel(); + this.renderScreenSessions(); } }); } @@ -1374,23 +1374,34 @@ class ClaudemanApp { this.closeAppSettings(); this.cancelCloseSession(); document.getElementById('respawnPanel').classList.remove('open'); - document.getElementById('taskPanel').classList.remove('open'); - document.getElementById('processPanel').classList.remove('open'); + document.getElementById('monitorPanel').classList.remove('open'); } - // ========== Task Panel ========== + // ========== Monitor Panel (combined Screen Sessions + Background Tasks) ========== - toggleTaskPanel() { - const panel = document.getElementById('taskPanel'); + async toggleMonitorPanel() { + const panel = document.getElementById('monitorPanel'); panel.classList.toggle('open'); + if (panel.classList.contains('open')) { + // Load screens and start stats collection + await this.loadScreens(); + await fetch('/api/screens/stats/start', { method: 'POST' }); this.renderTaskPanel(); + } else { + // Stop stats collection when panel is closed + await fetch('/api/screens/stats/stop', { method: 'POST' }); } } + // Legacy alias for task panel toggle (used by session tab badge) + toggleTaskPanel() { + this.toggleMonitorPanel(); + } + renderTaskPanel() { const session = this.sessions.get(this.activeSessionId); - const body = document.getElementById('taskPanelBody'); + const body = document.getElementById('backgroundTasksBody'); const stats = document.getElementById('taskPanelStats'); if (!session || !session.taskTree || session.taskTree.length === 0) { @@ -1460,38 +1471,24 @@ class ClaudemanApp { return result; } - // ========== Process Panel ========== - - async toggleProcessPanel() { - const panel = document.getElementById('processPanel'); - panel.classList.toggle('open'); - - if (panel.classList.contains('open')) { - // Load screens and start stats collection - await this.loadScreens(); - await fetch('/api/screens/stats/start', { method: 'POST' }); - } else { - // Stop stats collection when panel is closed - await fetch('/api/screens/stats/stop', { method: 'POST' }); - } - } + // ========== Screen Sessions (in Monitor Panel) ========== async loadScreens() { try { const res = await fetch('/api/screens'); const data = await res.json(); this.screenSessions = data.screens || []; - this.renderProcessPanel(); + this.renderScreenSessions(); } catch (err) { console.error('Failed to load screens:', err); } } - renderProcessPanel() { - const body = document.getElementById('processPanelBody'); + renderScreenSessions() { + const body = document.getElementById('screenSessionsBody'); if (!this.screenSessions || this.screenSessions.length === 0) { - body.innerHTML = '
No screen sessions tracked
'; + body.innerHTML = '
No screen sessions
'; return; } @@ -1528,7 +1525,7 @@ class ClaudemanApp { try { await fetch(`/api/screens/${sessionId}`, { method: 'DELETE' }); this.screenSessions = this.screenSessions.filter(s => s.sessionId !== sessionId); - this.renderProcessPanel(); + this.renderScreenSessions(); this.showToast('Screen killed', 'success'); } catch (err) { this.showToast('Failed to kill screen', 'error'); diff --git a/src/web/public/index.html b/src/web/public/index.html index ebf185ae..7115e8f4 100644 --- a/src/web/public/index.html +++ b/src/web/public/index.html @@ -30,6 +30,11 @@ Connecting
0 tokens
+
+ + 14 + +
@@ -86,16 +91,9 @@
- - - 14 - - -
- - +
@@ -199,29 +197,33 @@
- -
-
- Process Monitor -
+ +
+
+ Monitor +
- +
-
-
No screen sessions tracked
-
-
- - -
-
- Background Tasks -
0 running
- -
-
-
No background tasks
+
+
+
+ Screen Sessions +
+
+
No screen sessions
+
+
+
+
+ Background Tasks + 0 running +
+
+
No background tasks
+
+
diff --git a/src/web/public/styles.css b/src/web/public/styles.css index 19d4af98..f9eab3d7 100644 --- a/src/web/public/styles.css +++ b/src/web/public/styles.css @@ -208,6 +208,34 @@ body { transform: rotate(45deg); } +.btn-icon-header.btn-sm { + width: 20px; + height: 20px; + font-size: 0.7rem; + font-weight: 600; +} + +.btn-icon-header.btn-sm:hover { + transform: none; +} + +.header-font-controls { + display: flex; + align-items: center; + gap: 0.25rem; + padding: 0.15rem 0.4rem; + background: var(--bg-input); + border-radius: 4px; +} + +.header-font-controls .font-size-display { + font-size: 0.7rem; + color: var(--text-muted); + min-width: 18px; + text-align: center; + font-family: 'SF Mono', Monaco, monospace; +} + /* Session tab gear icon */ .session-tab .tab-gear { opacity: 0; @@ -1236,6 +1264,95 @@ kbd { color: var(--yellow); } +/* Combined Monitor Panel (Screen Sessions + Background Tasks) */ +.monitor-panel { + position: fixed; + bottom: var(--toolbar-height); + right: 0.5rem; + width: 420px; + max-width: calc(100vw - 1rem); + max-height: 60vh; + background: var(--bg-card); + border: 1px solid var(--border); + border-bottom: none; + border-radius: 8px 8px 0 0; + box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.4); + z-index: 100; + transform: translateY(100%); + transition: transform 0.2s ease; + display: flex; + flex-direction: column; +} + +.monitor-panel.open { + transform: translateY(0); +} + +.monitor-panel-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0.6rem 1rem; + border-bottom: 1px solid var(--border); + font-size: 0.85rem; + font-weight: 500; + flex-shrink: 0; +} + +.monitor-panel-actions { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.monitor-panel-body { + flex: 1; + overflow-y: auto; + padding: 0; +} + +.monitor-section { + border-bottom: 1px solid var(--border); +} + +.monitor-section:last-child { + border-bottom: none; +} + +.monitor-section-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0.5rem 1rem; + background: var(--bg-input); + font-size: 0.75rem; + font-weight: 500; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.monitor-section-body { + padding: 0.5rem; + max-height: 25vh; + overflow-y: auto; +} + +.monitor-empty { + padding: 1rem; + text-align: center; + color: var(--text-muted); + font-size: 0.8rem; +} + +.task-stats { + font-size: 0.65rem; + color: var(--text-dim); + padding: 0.15rem 0.4rem; + background: var(--bg-card); + border-radius: 8px; +} + /* Responsive */ @media (max-width: 600px) { .header-brand {