feat: rename Active Tools to Project Insights panel

- Rename Active Tools panel to Project Insights with updated styling
- Make panel bigger (450x350px) and resizable with drag handle
- Add settings toggle in Display tab to enable/disable panel
- Close subagent windows when parent session is killed/closed

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
arkon
2026-01-26 00:15:11 +01:00
co-authored by Claude Opus 4.5
parent ac7238dff2
commit fbecbfb7cf
3 changed files with 146 additions and 88 deletions
+62 -35
View File
@@ -443,11 +443,11 @@ class ClaudemanApp {
this.subagentWindowZIndex = 1000;
this.ralphStatePanelCollapsed = true; // Default to collapsed
// Active Bash tool tracking (for clickable file paths)
this.activeTools = new Map(); // Map<sessionId, ActiveBashTool[]>
// Project Insights tracking (active Bash tools with clickable file paths)
this.projectInsights = new Map(); // Map<sessionId, ActiveBashTool[]>
this.logViewerWindows = new Map(); // Map<windowId, { element, eventSource, filePath }>
this.logViewerWindowZIndex = 2000;
this.activeToolsPanelVisible = false;
this.projectInsightsPanelVisible = false;
// Tab alert states: Map<sessionId, 'action' | 'idle'>
this.tabAlerts = new Map();
@@ -1073,8 +1073,9 @@ class ClaudemanApp {
this.sessions.delete(data.id);
this.terminalBuffers.delete(data.id);
this.ralphStates.delete(data.id); // Clean up ralph state for this session
this.activeTools.delete(data.id); // Clean up active tools for this session
this.projectInsights.delete(data.id); // Clean up project insights for this session
this.closeSessionLogViewerWindows(data.id); // Close log viewer windows for this session
this.closeSessionSubagentWindows(data.id); // Close subagent windows for this session
// Clean up idle timer for this session
const idleTimer = this.idleTimers.get(data.id);
if (idleTimer) {
@@ -1088,7 +1089,7 @@ class ClaudemanApp {
}
this.renderSessionTabs();
this.renderRalphStatePanel(); // Update ralph panel after session deleted
this.renderActiveToolsPanel(); // Update active tools panel after session deleted
this.renderProjectInsightsPanel(); // Update project insights panel after session deleted
});
this.eventSource.addEventListener('session:terminal', (e) => {
@@ -2032,8 +2033,8 @@ class ClaudemanApp {
}
this.renderRalphStatePanel();
// Update active tools panel for this session
this.renderActiveToolsPanel();
// Update project insights panel for this session
this.renderProjectInsightsPanel();
this.terminal.focus();
this.terminal.scrollToBottom();
@@ -3643,6 +3644,7 @@ class ClaudemanApp {
document.getElementById('appSettingsShowMonitor').checked = settings.showMonitor ?? true;
document.getElementById('appSettingsSubagentTracking').checked = settings.subagentTrackingEnabled ?? true;
document.getElementById('appSettingsShowSubagents').checked = settings.showSubagents ?? true;
document.getElementById('appSettingsShowProjectInsights').checked = settings.showProjectInsights ?? true;
// Claude CLI settings
const claudeModeSelect = document.getElementById('appSettingsClaudeMode');
const allowedToolsRow = document.getElementById('allowedToolsRow');
@@ -3704,6 +3706,7 @@ class ClaudemanApp {
showMonitor: document.getElementById('appSettingsShowMonitor').checked,
subagentTrackingEnabled: document.getElementById('appSettingsSubagentTracking').checked,
showSubagents: document.getElementById('appSettingsShowSubagents').checked,
showProjectInsights: document.getElementById('appSettingsShowProjectInsights').checked,
// Claude CLI settings
claudeMode: document.getElementById('appSettingsClaudeMode').value,
allowedTools: document.getElementById('appSettingsAllowedTools').value.trim(),
@@ -3730,6 +3733,7 @@ class ClaudemanApp {
// Apply header visibility immediately
this.applyHeaderVisibilitySettings();
this.applyMonitorVisibility();
this.renderProjectInsightsPanel(); // Re-render to apply visibility setting
// Save to server (includes notification prefs for cross-browser persistence)
try {
@@ -5214,6 +5218,20 @@ class ClaudemanApp {
}
}
// Close all subagent windows for a session
closeSessionSubagentWindows(sessionId) {
const toClose = [];
for (const [agentId, _windowData] of this.subagentWindows) {
const agent = this.subagents.get(agentId);
if (agent?.parentSessionId === sessionId) {
toClose.push(agentId);
}
}
for (const agentId of toClose) {
this.closeSubagentWindow(agentId);
}
}
minimizeSubagentWindow(agentId) {
const windowData = this.subagentWindows.get(agentId);
if (windowData) {
@@ -5335,76 +5353,85 @@ class ClaudemanApp {
}
}
// ========== Active Bash Tools (Clickable File Paths) ==========
// ========== Project Insights Panel (Bash Tools with Clickable File Paths) ==========
handleBashToolStart(sessionId, tool) {
let tools = this.activeTools.get(sessionId) || [];
let tools = this.projectInsights.get(sessionId) || [];
// Add new tool
tools = tools.filter(t => t.id !== tool.id);
tools.push(tool);
this.activeTools.set(sessionId, tools);
this.renderActiveToolsPanel();
this.projectInsights.set(sessionId, tools);
this.renderProjectInsightsPanel();
}
handleBashToolEnd(sessionId, tool) {
const tools = this.activeTools.get(sessionId) || [];
const tools = this.projectInsights.get(sessionId) || [];
const existing = tools.find(t => t.id === tool.id);
if (existing) {
existing.status = 'completed';
}
this.renderActiveToolsPanel();
this.renderProjectInsightsPanel();
// Remove after a short delay
setTimeout(() => {
const current = this.activeTools.get(sessionId) || [];
this.activeTools.set(sessionId, current.filter(t => t.id !== tool.id));
this.renderActiveToolsPanel();
const current = this.projectInsights.get(sessionId) || [];
this.projectInsights.set(sessionId, current.filter(t => t.id !== tool.id));
this.renderProjectInsightsPanel();
}, 2000);
}
handleBashToolsUpdate(sessionId, tools) {
this.activeTools.set(sessionId, tools);
this.renderActiveToolsPanel();
this.projectInsights.set(sessionId, tools);
this.renderProjectInsightsPanel();
}
renderActiveToolsPanel() {
const panel = this.$('activeToolsPanel');
const list = this.$('activeToolsList');
renderProjectInsightsPanel() {
const panel = this.$('projectInsightsPanel');
const list = this.$('projectInsightsList');
if (!panel || !list) return;
// Check if panel is enabled in settings
const settings = this.loadAppSettingsFromStorage();
const showProjectInsights = settings.showProjectInsights ?? true;
if (!showProjectInsights) {
panel.classList.remove('visible');
this.projectInsightsPanelVisible = false;
return;
}
// Get tools for active session only
const tools = this.activeTools.get(this.activeSessionId) || [];
const tools = this.projectInsights.get(this.activeSessionId) || [];
const runningTools = tools.filter(t => t.status === 'running');
if (runningTools.length === 0) {
panel.classList.remove('visible');
this.activeToolsPanelVisible = false;
this.projectInsightsPanelVisible = false;
return;
}
panel.classList.add('visible');
this.activeToolsPanelVisible = true;
this.projectInsightsPanelVisible = true;
const html = [];
for (const tool of runningTools) {
const cmdDisplay = tool.command.length > 40
? tool.command.substring(0, 40) + '...'
const cmdDisplay = tool.command.length > 50
? tool.command.substring(0, 50) + '...'
: tool.command;
html.push(`
<div class="active-tool-item" data-tool-id="${tool.id}">
<div class="active-tool-command">
<div class="project-insight-item" data-tool-id="${tool.id}">
<div class="project-insight-command">
<span class="icon">💻</span>
<span class="cmd" title="${this.escapeHtml(tool.command)}">${this.escapeHtml(cmdDisplay)}</span>
<span class="active-tool-status ${tool.status}">${tool.status}</span>
${tool.timeout ? `<span class="active-tool-timeout">${this.escapeHtml(tool.timeout)}</span>` : ''}
<span class="project-insight-status ${tool.status}">${tool.status}</span>
${tool.timeout ? `<span class="project-insight-timeout">${this.escapeHtml(tool.timeout)}</span>` : ''}
</div>
<div class="active-tool-paths">
<div class="project-insight-paths">
`);
for (const path of tool.filePaths) {
const fileName = path.split('/').pop();
html.push(`
<span class="active-tool-filepath"
<span class="project-insight-filepath"
onclick="app.openLogViewerWindow('${this.escapeHtml(path)}', '${tool.sessionId}')"
title="${this.escapeHtml(path)}">${this.escapeHtml(fileName)}</span>
`);
@@ -5419,11 +5446,11 @@ class ClaudemanApp {
list.innerHTML = html.join('');
}
closeActiveToolsPanel() {
const panel = this.$('activeToolsPanel');
closeProjectInsightsPanel() {
const panel = this.$('projectInsightsPanel');
if (panel) {
panel.classList.remove('visible');
this.activeToolsPanelVisible = false;
this.projectInsightsPanelVisible = false;
}
}
+15 -6
View File
@@ -172,13 +172,14 @@
<div class="terminal-container" id="terminalContainer"></div>
</main>
<!-- Active Tools Panel (shows file-viewing Bash commands) -->
<div class="active-tools-panel" id="activeToolsPanel">
<div class="active-tools-header">
<span class="active-tools-title">Active Tools</span>
<button class="btn-icon-sm" onclick="app.closeActiveToolsPanel()" title="Close">&times;</button>
<!-- Project Insights Panel (shows file-viewing Bash commands) -->
<div class="project-insights-panel" id="projectInsightsPanel">
<div class="project-insights-header">
<span class="project-insights-title">Project Insights</span>
<button class="btn-icon-sm" onclick="app.closeProjectInsightsPanel()" title="Close">&times;</button>
</div>
<div class="active-tools-body" id="activeToolsList"></div>
<div class="project-insights-body" id="projectInsightsList"></div>
<div class="project-insights-resize-handle"></div>
</div>
<!-- Bottom Toolbar -->
@@ -629,6 +630,14 @@
</label>
<span class="form-hint">Show the Subagents panel (independent from Monitor)</span>
</div>
<div class="form-row form-row-switch">
<label>Show Project Insights Panel</label>
<label class="switch">
<input type="checkbox" id="appSettingsShowProjectInsights" checked>
<span class="slider"></span>
</label>
<span class="form-hint">Show active tools and file viewers in a floating panel</span>
</div>
</div>
<!-- Claude CLI Tab -->
<div class="modal-tab-content hidden" id="settings-claude">
+69 -47
View File
@@ -3895,9 +3895,8 @@ kbd {
padding: 0.5rem;
font-family: var(--font-mono);
font-size: 0.75rem;
background: #0a0a0a;
color: #00ff41;
text-shadow: 0 0 2px #00ff41;
background: #111;
color: #c8c8c8;
}
.subagent-window-body .activity-line {
@@ -3908,26 +3907,24 @@ kbd {
}
.subagent-window-body .activity-line .time {
color: #00aa30;
color: #888;
flex: 0 0 auto;
white-space: nowrap;
}
.subagent-window-body .activity-line .tool-icon {
flex: 0 0 auto;
filter: brightness(1.2);
opacity: 0.9;
}
.subagent-window-body .activity-line .tool-name {
color: #00ff41;
font-weight: bold;
color: #b0b0b0;
flex: 0 0 auto;
white-space: nowrap;
text-shadow: 0 0 4px #00ff41;
}
.subagent-window-body .activity-line .tool-detail {
color: #33ff66;
color: #d0d0d0;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
@@ -3935,13 +3932,12 @@ kbd {
.subagent-window-body .message-line {
padding: 0.3rem 0;
color: #00ff41;
color: #c8c8c8;
}
.subagent-window-body .progress-line {
color: #66ff99;
color: #a0a0a0;
font-style: italic;
text-shadow: 0 0 3px #66ff99;
}
/* Subagent Window Parent Tab Header */
@@ -4007,14 +4003,16 @@ kbd {
stroke-dashoffset: 1000;
}
/* ========== Active Tools Panel (Bash File Viewers) ========== */
/* ========== Project Insights Panel (Bash File Viewers) ========== */
.active-tools-panel {
.project-insights-panel {
position: fixed;
right: 20px;
top: calc(var(--header-height) + 10px);
width: 350px;
max-height: 200px;
width: 450px;
height: 350px;
min-width: 300px;
min-height: 200px;
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 8px;
@@ -4022,13 +4020,15 @@ kbd {
z-index: 1000;
display: none;
flex-direction: column;
overflow: hidden;
resize: both;
}
.active-tools-panel.visible {
.project-insights-panel.visible {
display: flex;
}
.active-tools-header {
.project-insights-header {
display: flex;
align-items: center;
justify-content: space-between;
@@ -4036,95 +4036,117 @@ kbd {
background: var(--bg-input);
border-bottom: 1px solid var(--border);
border-radius: 8px 8px 0 0;
flex-shrink: 0;
}
.active-tools-title {
.project-insights-title {
font-size: 0.85rem;
font-weight: 600;
color: var(--text);
}
.active-tools-body {
.project-insights-body {
flex: 1;
overflow-y: auto;
padding: 0.5rem;
}
.active-tool-item {
display: flex;
flex-direction: column;
padding: 0.4rem 0.5rem;
border-radius: 4px;
background: var(--bg-input);
margin-bottom: 0.3rem;
.project-insights-resize-handle {
position: absolute;
bottom: 0;
right: 0;
width: 16px;
height: 16px;
cursor: se-resize;
opacity: 0.5;
}
.active-tool-item:last-child {
.project-insights-resize-handle::after {
content: '';
position: absolute;
right: 3px;
bottom: 3px;
width: 8px;
height: 8px;
border-right: 2px solid var(--text-muted);
border-bottom: 2px solid var(--text-muted);
}
.project-insight-item {
display: flex;
flex-direction: column;
padding: 0.5rem 0.6rem;
border-radius: 4px;
background: var(--bg-input);
margin-bottom: 0.4rem;
}
.project-insight-item:last-child {
margin-bottom: 0;
}
.active-tool-command {
.project-insight-command {
display: flex;
align-items: center;
gap: 0.4rem;
font-family: var(--font-mono);
font-size: 0.75rem;
font-size: 0.8rem;
color: var(--text);
}
.active-tool-command .icon {
font-size: 0.9rem;
.project-insight-command .icon {
font-size: 1rem;
}
.active-tool-command .cmd {
.project-insight-command .cmd {
flex: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.active-tool-paths {
.project-insight-paths {
display: flex;
flex-direction: column;
gap: 0.2rem;
margin-top: 0.3rem;
padding-left: 1.3rem;
gap: 0.25rem;
margin-top: 0.4rem;
padding-left: 1.4rem;
}
.active-tool-filepath {
.project-insight-filepath {
cursor: pointer;
color: var(--accent);
font-family: var(--font-mono);
font-size: 0.7rem;
font-size: 0.75rem;
text-decoration: underline;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.active-tool-filepath:hover {
.project-insight-filepath:hover {
color: var(--accent-hover);
}
.active-tool-timeout {
font-size: 0.65rem;
.project-insight-timeout {
font-size: 0.7rem;
color: var(--text-muted);
margin-left: auto;
}
.active-tool-status {
font-size: 0.65rem;
padding: 0.1rem 0.3rem;
.project-insight-status {
font-size: 0.7rem;
padding: 0.15rem 0.35rem;
border-radius: 3px;
margin-left: 0.3rem;
}
.active-tool-status.running {
.project-insight-status.running {
background: var(--success);
color: white;
}
.active-tool-status.completed {
.project-insight-status.completed {
background: var(--text-muted);
color: white;
}