feat: merge Process Monitor and Background Tasks into single Monitor panel

- Combine two separate panels into unified Monitor panel with sections
- Move font size controls from toolbar to header
- Add CSS styles for new monitor panel with section headers
- Update JS to handle combined panel with toggleMonitorPanel()
- Keep legacy toggleTaskPanel() as alias for backward compatibility

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
arkon
2026-01-19 02:48:28 +01:00
co-authored by Claude Opus 4.5
parent c2177b659e
commit 89c9b0fe85
3 changed files with 174 additions and 58 deletions
+27 -30
View File
@@ -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 = '<div class="process-empty">No screen sessions tracked</div>';
body.innerHTML = '<div class="monitor-empty">No screen sessions</div>';
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');
+30 -28
View File
@@ -30,6 +30,11 @@
<span class="status-text">Connecting</span>
</div>
<div class="header-tokens" id="headerTokens" title="Total tokens across all sessions">0 tokens</div>
<div class="header-font-controls">
<button class="btn-icon-header btn-sm" onclick="app.decreaseFontSize()" title="Decrease font (Ctrl+-)">A-</button>
<span class="font-size-display" id="fontSizeDisplay">14</span>
<button class="btn-icon-header btn-sm" onclick="app.increaseFontSize()" title="Increase font (Ctrl++)">A+</button>
</div>
<button class="btn-icon-header" onclick="app.openAppSettings()" title="App Settings">&#x2699;</button>
</div>
</header>
@@ -86,16 +91,9 @@
</div>
<div class="toolbar-right">
<!-- Font Controls -->
<button class="btn-toolbar btn-sm" onclick="app.decreaseFontSize()" title="Decrease font (Ctrl+-)">A-</button>
<span class="font-size-display" id="fontSizeDisplay">14</span>
<button class="btn-toolbar btn-sm" onclick="app.increaseFontSize()" title="Increase font (Ctrl++)">A+</button>
<div class="toolbar-divider"></div>
<button class="btn-toolbar" onclick="app.copyTerminal()" title="Copy terminal">Copy</button>
<button class="btn-toolbar" onclick="app.clearTerminal()" title="Clear terminal (Ctrl+L)">Clear</button>
<button class="btn-toolbar" onclick="app.toggleProcessPanel()" title="Process Monitor">Processes</button>
<button class="btn-toolbar" onclick="app.toggleMonitorPanel()" title="Monitor">Monitor</button>
<div class="toolbar-divider"></div>
@@ -199,29 +197,33 @@
</div>
</div>
<!-- Process Monitor Panel (slide-up) -->
<div class="process-panel" id="processPanel">
<div class="process-panel-header">
<span>Process Monitor</span>
<div class="process-panel-actions">
<!-- Monitor Panel (combined Process Monitor + Background Tasks) -->
<div class="monitor-panel" id="monitorPanel">
<div class="monitor-panel-header">
<span>Monitor</span>
<div class="monitor-panel-actions">
<button class="btn-toolbar btn-sm" onclick="app.reconcileScreens()" title="Check for dead screens">Reconcile</button>
<button class="btn-close" onclick="app.toggleProcessPanel()">&times;</button>
<button class="btn-close" onclick="app.toggleMonitorPanel()">&times;</button>
</div>
</div>
<div class="process-panel-body" id="processPanelBody">
<div class="process-empty">No screen sessions tracked</div>
</div>
</div>
<!-- Task Tree Panel (slide-up) -->
<div class="task-panel" id="taskPanel">
<div class="task-panel-header">
<span>Background Tasks</span>
<div class="task-panel-stats" id="taskPanelStats">0 running</div>
<button class="btn-close" onclick="app.toggleTaskPanel()">&times;</button>
</div>
<div class="task-panel-body" id="taskPanelBody">
<div class="task-empty">No background tasks</div>
<div class="monitor-panel-body">
<div class="monitor-section">
<div class="monitor-section-header">
<span>Screen Sessions</span>
</div>
<div class="monitor-section-body" id="screenSessionsBody">
<div class="monitor-empty">No screen sessions</div>
</div>
</div>
<div class="monitor-section">
<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 class="monitor-empty">No background tasks</div>
</div>
</div>
</div>
</div>
+117
View File
@@ -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 {