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 @@