From 87bdd9376cd47a91f81c20a139eab0dd958c72ef Mon Sep 17 00:00:00 2001 From: arkon Date: Sun, 25 Jan 2026 18:33:43 +0100 Subject: [PATCH] ui: make Subagents panel detachable and position on right side - Move Subagents panel to right side (same as Monitor) - Add detach/float functionality with drag support - Add resize handle for detached mode - Clean up duplicate CSS - Bump version to 0.1358 Co-Authored-By: Claude Opus 4.5 --- CLAUDE.md | 2 +- package.json | 2 +- src/web/public/app.js | 97 +++++++++++++++++++- src/web/public/index.html | 4 +- src/web/public/styles.css | 187 +++++++++++++------------------------- 5 files changed, 164 insertions(+), 128 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 55ac3d5e..0ce4668b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -21,7 +21,7 @@ When user says "COM": 1) Increment version in BOTH `package.json` AND `CLAUDE.md Claudeman is a Claude Code session manager with a web interface and autonomous Ralph Loop. It spawns Claude CLI processes via PTY, streams output in real-time via SSE, and supports scheduled/timed runs. -**Version**: 0.1357 (must match `package.json`) +**Version**: 0.1358 (must match `package.json`) **Tech Stack**: TypeScript (ES2022/NodeNext, strict mode), Node.js, Fastify, Server-Sent Events, node-pty diff --git a/package.json b/package.json index 626c2feb..2b235c99 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "claudeman", - "version": "0.1357", + "version": "0.1358", "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/src/web/public/app.js b/src/web/public/app.js index 3b074857..777850eb 100644 --- a/src/web/public/app.js +++ b/src/web/public/app.js @@ -3235,7 +3235,12 @@ class ClaudemanApp { this.closeAppSettings(); this.cancelCloseSession(); document.getElementById('monitorPanel').classList.remove('open'); - this.closeSubagentsPanel(); + // Collapse subagents panel (don't hide it permanently) + const subagentsPanel = document.getElementById('subagentsPanel'); + if (subagentsPanel) { + subagentsPanel.classList.remove('open'); + } + this.subagentPanelVisible = false; } // ========== Monitor Panel (combined Screen Sessions + Background Tasks) ========== @@ -3349,6 +3354,92 @@ class ClaudemanApp { header.addEventListener('mousedown', onMouseDown); } + // ========== Subagents Panel Detach & Drag ========== + + toggleSubagentsDetach() { + const panel = document.getElementById('subagentsPanel'); + const detachBtn = document.getElementById('subagentsDetachBtn'); + + if (panel.classList.contains('detached')) { + // Re-attach to bottom + panel.classList.remove('detached'); + panel.style.top = ''; + panel.style.left = ''; + panel.style.width = ''; + panel.style.height = ''; + if (detachBtn) { + detachBtn.innerHTML = '⧉'; // Detach icon + detachBtn.title = 'Detach panel'; + } + } else { + // Detach as floating window + panel.classList.add('detached'); + panel.classList.add('open'); // Ensure it's visible + if (detachBtn) { + detachBtn.innerHTML = '⊞'; // Attach icon + detachBtn.title = 'Attach panel'; + } + // Setup drag functionality + this.setupSubagentsDrag(); + } + } + + setupSubagentsDrag() { + const panel = document.getElementById('subagentsPanel'); + const header = document.getElementById('subagentsPanelHeader'); + + if (!panel || !header) return; + + let isDragging = false; + let startX, startY, startLeft, startTop; + + const onMouseDown = (e) => { + // Only drag from header, not from buttons + if (e.target.closest('button')) return; + if (!panel.classList.contains('detached')) return; + + isDragging = true; + startX = e.clientX; + startY = e.clientY; + const rect = panel.getBoundingClientRect(); + startLeft = rect.left; + startTop = rect.top; + + document.addEventListener('mousemove', onMouseMove); + document.addEventListener('mouseup', onMouseUp); + e.preventDefault(); + }; + + const onMouseMove = (e) => { + if (!isDragging) return; + + const dx = e.clientX - startX; + const dy = e.clientY - startY; + + let newLeft = startLeft + dx; + let newTop = startTop + dy; + + // Keep within viewport bounds + const rect = panel.getBoundingClientRect(); + newLeft = Math.max(0, Math.min(window.innerWidth - rect.width, newLeft)); + newTop = Math.max(0, Math.min(window.innerHeight - rect.height, newTop)); + + panel.style.left = newLeft + 'px'; + panel.style.top = newTop + 'px'; + }; + + const onMouseUp = () => { + isDragging = false; + document.removeEventListener('mousemove', onMouseMove); + document.removeEventListener('mouseup', onMouseUp); + }; + + // Remove existing listeners before adding new ones + header.removeEventListener('mousedown', header._dragHandler); + header._dragHandler = onMouseDown; + header.addEventListener('mousedown', onMouseDown); + } + renderTaskPanel() { // Debounce renders at 100ms to prevent excessive DOM updates if (this.renderTaskPanelTimeout) { @@ -3874,8 +3965,8 @@ class ClaudemanApp { const list = this.$('subagentList'); if (!list) return; - // Always update toolbar button visibility - this.updateSubagentTabButton(); + // Always update badge count + this.updateSubagentBadge(); // If panel is not visible, don't render content if (!this.subagentPanelVisible) { diff --git a/src/web/public/index.html b/src/web/public/index.html index 7cbc9cb0..de5722c0 100644 --- a/src/web/public/index.html +++ b/src/web/public/index.html @@ -276,15 +276,17 @@
-
+
Subagents
+
+
diff --git a/src/web/public/styles.css b/src/web/public/styles.css index 183fe184..605a261d 100644 --- a/src/web/public/styles.css +++ b/src/web/public/styles.css @@ -2903,6 +2903,12 @@ kbd { flex-shrink: 0; } +.monitor-panel-title { + font-weight: 600; + font-size: 0.8rem; + color: var(--text); +} + .monitor-tabs { display: flex; gap: 0.25rem; @@ -3090,130 +3096,21 @@ kbd { display: none; } -/* Main subagents panel - positioned on left side */ -.subagents-panel { - position: fixed; - bottom: var(--toolbar-height); - left: 0.5rem; - width: 500px; - 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; -} - -.subagents-panel.open { - transform: translateY(0); -} - -/* Detached floating state */ -.subagents-panel.detached { - bottom: auto; - left: auto; - top: 100px; - left: 50px; - width: 600px; - height: 400px; - max-height: none; - border-radius: 8px; - border-bottom: 1px solid var(--border); - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(255, 255, 255, 0.05); - transform: none; - resize: both; - overflow: hidden; - min-width: 400px; - min-height: 250px; -} - -.subagents-panel.detached .subagents-panel-header { - cursor: move; - background: linear-gradient(180deg, var(--bg-hover) 0%, var(--bg-card) 100%); - user-select: none; -} - -.subagents-panel.detached .subagents-resize-handle { - display: block; -} - -.subagents-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; -} - -.subagents-panel-actions { - display: flex; - align-items: center; - gap: 0.5rem; -} - -.subagents-panel-body { - flex: 1; - overflow-y: auto; - padding: 0.5rem; -} - -/* Resize handle for detached panel */ -.subagents-resize-handle { - display: none; - position: absolute; - bottom: 0; - right: 0; - width: 16px; - height: 16px; - cursor: nwse-resize; - background: linear-gradient(135deg, transparent 50%, var(--border-light) 50%); - border-radius: 0 0 8px 0; -} - -.subagents-resize-handle::before { - content: ''; - position: absolute; - bottom: 3px; - right: 3px; - width: 8px; - height: 8px; - border-right: 2px solid var(--text-muted); - border-bottom: 2px solid var(--text-muted); -} - -@media (max-width: 600px) { - .subagents-panel { - width: 100%; - max-width: 100%; - left: 0; - border-radius: 8px 8px 0 0; - } -} - /* ========== Separate Subagents Panel ========== */ .subagents-panel { position: fixed; bottom: var(--toolbar-height); - right: 400px; /* Position to the left of monitor panel */ + right: 0.5rem; width: 500px; - max-width: calc(100vw - 420px); + max-width: calc(100vw - 1rem); max-height: 50vh; background: var(--bg-card); border: 1px solid var(--border); border-bottom: none; border-radius: 8px 8px 0 0; - z-index: 90; + z-index: 91; /* Above monitor panel */ transform: translateY(calc(100% - 36px)); /* Collapsed by default, show header */ - transition: transform 0.2s ease; + transition: transform 0.2s ease, width 0.2s ease; display: flex; flex-direction: column; } @@ -3226,6 +3123,35 @@ kbd { display: none; } +/* Detached floating state for subagents panel */ +.subagents-panel.detached { + bottom: auto; + right: auto; + top: 80px; + left: calc(50% - 350px); + width: 700px; + height: 500px; + max-height: none; + border-radius: 8px; + border-bottom: 1px solid var(--border); + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(255, 255, 255, 0.05); + transform: none; + resize: both; + overflow: hidden; + min-width: 400px; + min-height: 300px; +} + +.subagents-panel.detached .subagents-panel-header { + cursor: move; + background: linear-gradient(180deg, var(--bg-hover) 0%, var(--bg-card) 100%); + user-select: none; +} + +.subagents-panel.detached .subagents-resize-handle { + display: block; +} + .subagents-panel-header { display: flex; align-items: center; @@ -3256,18 +3182,35 @@ kbd { padding: 0.5rem; } -/* Adjust monitor panel position when subagents panel is visible */ -.subagents-panel:not(.hidden) ~ .monitor-panel { - /* Monitor stays in place */ +.subagents-resize-handle { + display: none; + position: absolute; + bottom: 0; + right: 0; + width: 16px; + height: 16px; + cursor: se-resize; } -/* Responsive: stack panels on small screens */ -@media (max-width: 900px) { +.subagents-resize-handle::after { + content: ''; + position: absolute; + bottom: 3px; + right: 3px; + width: 8px; + height: 8px; + border-right: 2px solid var(--text-muted); + border-bottom: 2px solid var(--text-muted); +} + +/* Responsive: full width on small screens */ +@media (max-width: 600px) { .subagents-panel { - right: 0.5rem; - width: calc(100vw - 1rem); - max-width: calc(100vw - 1rem); - bottom: calc(var(--toolbar-height) + 280px); /* Stack above monitor */ + width: 100%; + max-width: 100%; + left: 0; + right: 0; + border-radius: 8px 8px 0 0; } }