From b0bc53646e6b80d32b6262fb731aeaa4ae21a1d8 Mon Sep 17 00:00:00 2001 From: catlog22 Date: Mon, 8 Dec 2025 23:14:48 +0800 Subject: [PATCH] feat(dashboard): complete icon unification across all views - Update home.js: inbox, calendar, list-checks icons - Update project-overview.js: code-2, blocks, component, git-branch, sparkles, bug, wrench, book-open icons - Update session-detail.js: list-checks, package, file-text, ruler, scale, search icons for tabs - Update lite-tasks.js: zap, file-edit, wrench, calendar, list-checks, ruler, package, file-text icons - Update mcp-manager.js: plug, building-2, user, map-pin, check-circle, x-circle, circle-dashed, lock icons - Update hook-manager.js: webhook, pencil, trash-2, clock, check-circle, bell, octagon-x icons - Add getHookEventIconLucide() helper function - Initialize Lucide icons after dynamic content rendering All emoji icons replaced with consistent Lucide SVG icons --- ccw/src/core/server.js | 135 ++++++++++++----- .../templates/dashboard-css/09-explorer.css | 50 +++++- .../dashboard-js/components/hook-manager.js | 10 ++ .../templates/dashboard-js/views/explorer.js | 143 ++++++++++-------- ccw/src/templates/dashboard-js/views/home.js | 19 +-- .../dashboard-js/views/hook-manager.js | 10 +- .../dashboard-js/views/lite-tasks.js | 34 +++-- .../dashboard-js/views/mcp-manager.js | 18 +-- .../dashboard-js/views/project-overview.js | 23 +-- .../dashboard-js/views/session-detail.js | 29 ++-- 10 files changed, 307 insertions(+), 164 deletions(-) diff --git a/ccw/src/core/server.js b/ccw/src/core/server.js index 38f123f2..2ea951a0 100644 --- a/ccw/src/core/server.js +++ b/ccw/src/core/server.js @@ -1810,54 +1810,109 @@ async function getFileContent(filePath) { } /** - * Trigger update-module-claude tool + * Trigger update-module-claude tool (async execution) * @param {string} targetPath - Directory path to update * @param {string} tool - CLI tool to use (gemini, qwen, codex) * @param {string} strategy - Update strategy (single-layer, multi-layer) * @returns {Promise} */ async function triggerUpdateClaudeMd(targetPath, tool, strategy) { - const { execSync } = await import('child_process'); + const { spawn } = await import('child_process'); - try { - // Normalize path - let normalizedPath = targetPath.replace(/\\/g, '/'); - if (normalizedPath.match(/^\/[a-zA-Z]\//)) { - normalizedPath = normalizedPath.charAt(1).toUpperCase() + ':' + normalizedPath.slice(2); - } + // Normalize path + let normalizedPath = targetPath.replace(/\\/g, '/'); + if (normalizedPath.match(/^\/[a-zA-Z]\//)) { + normalizedPath = normalizedPath.charAt(1).toUpperCase() + ':' + normalizedPath.slice(2); + } + + if (!existsSync(normalizedPath)) { + return { error: 'Directory not found' }; + } + + if (!statSync(normalizedPath).isDirectory()) { + return { error: 'Not a directory' }; + } + + // Build ccw tool command with JSON parameters + const params = JSON.stringify({ + strategy, + path: normalizedPath, + tool + }); + + console.log(`[Explorer] Running async: ccw tool exec update_module_claude with ${tool} (${strategy})`); + + return new Promise((resolve) => { + const isWindows = process.platform === 'win32'; - if (!existsSync(normalizedPath)) { - return { error: 'Directory not found' }; - } - - if (!statSync(normalizedPath).isDirectory()) { - return { error: 'Not a directory' }; - } - - // Build ccw tool command - const ccwBin = join(import.meta.dirname, '../../bin/ccw.js'); - const command = `node "${ccwBin}" tool update_module_claude --strategy="${strategy}" --path="${normalizedPath}" --tool="${tool}"`; - - console.log(`[Explorer] Running: ${command}`); - - const output = execSync(command, { - encoding: 'utf8', - timeout: 300000, // 5 minutes - cwd: normalizedPath + // Spawn the process + const child = spawn('ccw', ['tool', 'exec', 'update_module_claude', params], { + cwd: normalizedPath, + shell: isWindows, + stdio: ['ignore', 'pipe', 'pipe'] }); - return { - success: true, - message: `CLAUDE.md updated successfully using ${tool} (${strategy})`, - output, - path: normalizedPath - }; - } catch (error) { - console.error('Error updating CLAUDE.md:', error); - return { - success: false, - error: error.message, - output: error.stdout || error.stderr || '' - }; - } + let stdout = ''; + let stderr = ''; + + child.stdout.on('data', (data) => { + stdout += data.toString(); + }); + + child.stderr.on('data', (data) => { + stderr += data.toString(); + }); + + child.on('close', (code) => { + if (code === 0) { + // Parse the JSON output from the tool + let result; + try { + result = JSON.parse(stdout); + } catch { + result = { output: stdout }; + } + + if (result.success === false || result.error) { + resolve({ + success: false, + error: result.error || result.message || 'Update failed', + output: stdout + }); + } else { + resolve({ + success: true, + message: result.message || `CLAUDE.md updated successfully using ${tool} (${strategy})`, + output: stdout, + path: normalizedPath + }); + } + } else { + resolve({ + success: false, + error: stderr || `Process exited with code ${code}`, + output: stdout + stderr + }); + } + }); + + child.on('error', (error) => { + console.error('Error spawning process:', error); + resolve({ + success: false, + error: error.message, + output: '' + }); + }); + + // Timeout after 5 minutes + setTimeout(() => { + child.kill(); + resolve({ + success: false, + error: 'Timeout: Process took longer than 5 minutes', + output: stdout + }); + }, 300000); + }); } diff --git a/ccw/src/templates/dashboard-css/09-explorer.css b/ccw/src/templates/dashboard-css/09-explorer.css index c665419e..a87222de 100644 --- a/ccw/src/templates/dashboard-css/09-explorer.css +++ b/ccw/src/templates/dashboard-css/09-explorer.css @@ -847,11 +847,55 @@ color: hsl(var(--foreground)); } +/* Task Queue Toolbar */ +.task-queue-toolbar { + display: flex; + align-items: center; + justify-content: space-between; + padding: 10px 12px; + border-bottom: 1px solid hsl(var(--border)); + gap: 8px; +} + +/* CLI Selector */ +.queue-cli-selector { + display: flex; + align-items: center; + gap: 6px; +} + +.queue-cli-selector label { + font-size: 12px; + font-weight: 500; + color: hsl(var(--muted-foreground)); +} + +.queue-cli-selector select { + padding: 5px 8px; + border: 1px solid hsl(var(--border)); + background: hsl(var(--background)); + color: hsl(var(--foreground)); + border-radius: 6px; + font-size: 12px; + font-weight: 500; + cursor: pointer; + transition: all 0.15s ease; + min-width: 100px; +} + +.queue-cli-selector select:hover { + border-color: hsl(var(--primary)); +} + +.queue-cli-selector select:focus { + outline: none; + border-color: hsl(var(--primary)); + box-shadow: 0 0 0 2px hsl(var(--primary) / 0.2); +} + .task-queue-actions { display: flex; - gap: 8px; - padding: 10px 16px; - border-bottom: 1px solid hsl(var(--border)); + gap: 4px; } .queue-action-btn { diff --git a/ccw/src/templates/dashboard-js/components/hook-manager.js b/ccw/src/templates/dashboard-js/components/hook-manager.js index 7d64f401..bd9cfdc8 100644 --- a/ccw/src/templates/dashboard-js/components/hook-manager.js +++ b/ccw/src/templates/dashboard-js/components/hook-manager.js @@ -271,3 +271,13 @@ function getHookEventIcon(event) { }; return icons[event] || 'đŸĒ'; } + +function getHookEventIconLucide(event) { + const icons = { + 'PreToolUse': '', + 'PostToolUse': '', + 'Notification': '', + 'Stop': '' + }; + return icons[event] || ''; +} \ No newline at end of file diff --git a/ccw/src/templates/dashboard-js/views/explorer.js b/ccw/src/templates/dashboard-js/views/explorer.js index cc48d453..6767476b 100644 --- a/ccw/src/templates/dashboard-js/views/explorer.js +++ b/ccw/src/templates/dashboard-js/views/explorer.js @@ -13,6 +13,7 @@ let explorerExpandedDirs = new Set(); let updateTaskQueue = []; let isTaskQueueVisible = false; let isTaskRunning = false; +let defaultCliTool = 'gemini'; // Default CLI tool for updates /** @@ -75,16 +76,26 @@ async function renderExplorer() { Update Tasks -
- - - +
+
+ + +
+
+ + + +
@@ -576,6 +587,13 @@ function toggleTaskQueue() { } } +/** + * Update default CLI tool + */ +function updateDefaultCliTool(tool) { + defaultCliTool = tool; +} + /** * Update the FAB badge count */ @@ -659,7 +677,8 @@ function addUpdateTask(path, tool = 'gemini', strategy = 'single-layer') { * Add task from folder context (right-click or button) */ function addFolderToQueue(folderPath, strategy = 'single-layer') { - addUpdateTask(folderPath, 'gemini', strategy); + // Use the selected CLI tool from the queue panel + addUpdateTask(folderPath, defaultCliTool, strategy); // Show task queue if not visible if (!isTaskQueueVisible) { @@ -740,7 +759,55 @@ function clearCompletedTasks() { } /** - * Start processing task queue + * Execute a single task asynchronously + */ +async function executeTask(task) { + const folderName = task.path.split('/').pop() || task.path; + + // Update status to running + task.status = 'running'; + task.message = 'Processing...'; + renderTaskQueue(); + + addGlobalNotification('info', `Processing: ${folderName}`, `Strategy: ${task.strategy}, Tool: ${task.tool}`, 'Explorer'); + + try { + const response = await fetch('/api/update-claude-md', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + path: task.path, + tool: task.tool, + strategy: task.strategy + }) + }); + + const result = await response.json(); + + if (result.success) { + task.status = 'completed'; + task.message = 'Updated successfully'; + addGlobalNotification('success', `Completed: ${folderName}`, result.message, 'Explorer'); + return { success: true }; + } else { + task.status = 'failed'; + task.message = result.error || 'Update failed'; + addGlobalNotification('error', `Failed: ${folderName}`, result.error || 'Unknown error', 'Explorer'); + return { success: false }; + } + } catch (error) { + task.status = 'failed'; + task.message = error.message; + addGlobalNotification('error', `Error: ${folderName}`, error.message, 'Explorer'); + return { success: false }; + } finally { + renderTaskQueue(); + updateFabBadge(); + } +} + +/** + * Start processing task queue - executes tasks asynchronously in parallel */ async function startTaskQueue() { if (isTaskRunning) return; @@ -751,55 +818,13 @@ async function startTaskQueue() { isTaskRunning = true; document.getElementById('startQueueBtn').disabled = true; - addGlobalNotification('info', `Starting ${pendingTasks.length} task(s)...`, null, 'Explorer'); + addGlobalNotification('info', `Starting ${pendingTasks.length} task(s) in parallel...`, null, 'Explorer'); - let successCount = 0; - let failCount = 0; + // Execute all tasks in parallel + const results = await Promise.all(pendingTasks.map(task => executeTask(task))); - for (const task of pendingTasks) { - const folderName = task.path.split('/').pop() || task.path; - - // Update status to running - task.status = 'running'; - task.message = 'Processing...'; - renderTaskQueue(); - - addGlobalNotification('info', `Processing: ${folderName}`, `Strategy: ${task.strategy}, Tool: ${task.tool}`, 'Explorer'); - - try { - const response = await fetch('/api/update-claude-md', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - path: task.path, - tool: task.tool, - strategy: task.strategy - }) - }); - - const result = await response.json(); - - if (result.success) { - task.status = 'completed'; - task.message = 'Updated successfully'; - successCount++; - addGlobalNotification('success', `Completed: ${folderName}`, result.message, 'Explorer'); - } else { - task.status = 'failed'; - task.message = result.error || 'Update failed'; - failCount++; - addGlobalNotification('error', `Failed: ${folderName}`, result.error || 'Unknown error', 'Explorer'); - } - } catch (error) { - task.status = 'failed'; - task.message = error.message; - failCount++; - addGlobalNotification('error', `Error: ${folderName}`, error.message, 'Explorer'); - } - - renderTaskQueue(); - updateFabBadge(); - } + const successCount = results.filter(r => r.success).length; + const failCount = results.filter(r => !r.success).length; isTaskRunning = false; diff --git a/ccw/src/templates/dashboard-js/views/home.js b/ccw/src/templates/dashboard-js/views/home.js index 17cf4604..0f003c38 100644 --- a/ccw/src/templates/dashboard-js/views/home.js +++ b/ccw/src/templates/dashboard-js/views/home.js @@ -73,11 +73,12 @@ function renderSessions() { if (sessions.length === 0) { container.innerHTML = `
-
📭
+
No Sessions Found
No workflow sessions match your current filter.
`; + if (typeof lucide !== 'undefined') lucide.createIcons(); return; } @@ -120,8 +121,8 @@ function renderSessionCard(session) {
- 📅 ${formatDate(date)} - 📋 ${taskCount} tasks + ${formatDate(date)} + ${taskCount} tasks
${taskCount > 0 ? `
@@ -171,16 +172,16 @@ function renderReviewSessionCard(session, sessionKey, typeBadge, isActive, date)
- 📅 ${formatDate(date)} - 🔍 ${totalFindings} findings + ${formatDate(date)} + ${totalFindings} findings
${totalFindings > 0 ? `
- ${criticalCount > 0 ? `🔴 ${criticalCount}` : ''} - ${highCount > 0 ? `🟠 ${highCount}` : ''} - ${mediumCount > 0 ? `🟡 ${mediumCount}` : ''} - ${lowCount > 0 ? `đŸŸĸ ${lowCount}` : ''} + ${criticalCount > 0 ? ` ${criticalCount}` : ''} + ${highCount > 0 ? ` ${highCount}` : ''} + ${mediumCount > 0 ? ` ${mediumCount}` : ''} + ${lowCount > 0 ? ` ${lowCount}` : ''}
${dimensions.length} dimensions diff --git a/ccw/src/templates/dashboard-js/views/hook-manager.js b/ccw/src/templates/dashboard-js/views/hook-manager.js index 4b4dafaa..f73ada56 100644 --- a/ccw/src/templates/dashboard-js/views/hook-manager.js +++ b/ccw/src/templates/dashboard-js/views/hook-manager.js @@ -41,7 +41,7 @@ async function renderHookManager() { ${projectHookCount === 0 ? `
-
đŸĒ
+

No hooks configured for this project

Create a hook to automate actions on tool usage

@@ -160,7 +160,7 @@ function renderHooksByEvent(hooks, scope) {
- ${getHookEventIcon(event)} + ${getHookEventIconLucide(event)}

${event}

${getHookEventDescription(event)}

@@ -173,7 +173,7 @@ function renderHooksByEvent(hooks, scope) { data-index="${index}" data-action="edit" title="Edit hook"> - âœī¸ +
@@ -215,7 +215,7 @@ function renderQuickInstallCard(templateId, title, description, event, matcher)
- ${isInstalled ? '✅' : 'đŸĒ'} + ${isInstalled ? '' : ''}

${escapeHtml(title)}

${escapeHtml(description)}

diff --git a/ccw/src/templates/dashboard-js/views/lite-tasks.js b/ccw/src/templates/dashboard-js/views/lite-tasks.js index d07c6842..ab005489 100644 --- a/ccw/src/templates/dashboard-js/views/lite-tasks.js +++ b/ccw/src/templates/dashboard-js/views/lite-tasks.js @@ -14,11 +14,12 @@ function renderLiteTasks() { if (sessions.length === 0) { container.innerHTML = `
-
⚡
+
No ${currentLiteType} Sessions
No sessions found in .workflow/.${currentLiteType}/
`; + if (typeof lucide !== 'undefined') lucide.createIcons(); return; } @@ -51,13 +52,13 @@ function renderLiteTaskCard(session) {
${escapeHtml(session.id)}
- ${session.type === 'lite-plan' ? '📝 PLAN' : '🔧 FIX'} + ${session.type === 'lite-plan' ? ' PLAN' : ' FIX'}
- 📅 ${formatDate(session.createdAt)} - 📋 ${tasks.length} tasks + ${formatDate(session.createdAt)} + ${tasks.length} tasks
@@ -102,7 +103,7 @@ function showLiteTaskDetailPage(sessionKey) { Back to ${session.type === 'lite-plan' ? 'Lite Plan' : 'Lite Fix'}
-

${session.type === 'lite-plan' ? '📝' : '🔧'} ${escapeHtml(session.id)}

+

${session.type === 'lite-plan' ? '' : ''} ${escapeHtml(session.id)}

${session.type}
@@ -124,20 +125,20 @@ function showLiteTaskDetailPage(sessionKey) {
@@ -209,7 +210,7 @@ function renderLiteTasksTab(session, tasks, completed, inProgress, pending) { if (tasks.length === 0) { return `
-
📋
+
No Tasks
This session has no tasks defined.
@@ -287,7 +288,7 @@ function renderLitePlanTab(session) { if (!plan) { return `
-
📐
+
No Plan Data
No plan.json found for this session.
@@ -299,7 +300,7 @@ function renderLitePlanTab(session) { ${plan.summary ? `
-

📋 Summary

+

Summary

${escapeHtml(plan.summary)}

` : ''} @@ -307,7 +308,7 @@ function renderLitePlanTab(session) { ${plan.approach ? `
-

đŸŽ¯ Approach

+

Approach

${escapeHtml(plan.approach)}

` : ''} @@ -315,7 +316,7 @@ function renderLitePlanTab(session) { ${plan.focus_paths?.length ? `
-

📁 Focus Paths

+

Focus Paths

${plan.focus_paths.map(p => `${escapeHtml(p)}`).join('')}
@@ -324,7 +325,7 @@ function renderLitePlanTab(session) {
-

â„šī¸ Metadata

+

Metadata

${plan.estimated_time ? `
Estimated Time: ${escapeHtml(plan.estimated_time)}
` : ''} ${plan.complexity ? `
Complexity: ${escapeHtml(plan.complexity)}
` : ''} @@ -379,11 +380,12 @@ async function loadAndRenderLiteSummaryTab(session, contentArea) { // Fallback contentArea.innerHTML = `
-
📝
+
No Summaries
No summaries found in .summaries/
`; + if (typeof lucide !== 'undefined') lucide.createIcons(); } catch (err) { contentArea.innerHTML = `
Failed to load summaries: ${err.message}
`; } diff --git a/ccw/src/templates/dashboard-js/views/mcp-manager.js b/ccw/src/templates/dashboard-js/views/mcp-manager.js index 0977aae7..39ee7d8d 100644 --- a/ccw/src/templates/dashboard-js/views/mcp-manager.js +++ b/ccw/src/templates/dashboard-js/views/mcp-manager.js @@ -52,7 +52,7 @@ async function renderMcpManager() { ${currentProjectServerNames.length === 0 ? `
-
🔌
+

No MCP servers configured for this project

Add servers from the available list below

@@ -72,7 +72,7 @@ async function renderMcpManager() {
- đŸĸ +

Enterprise MCP Servers

Managed
@@ -92,7 +92,7 @@ async function renderMcpManager() {
- 👤 +

User MCP Servers

${userServerEntries.length} servers from ~/.claude.json @@ -154,7 +154,7 @@ async function renderMcpManager() {
- ${isCurrentProject ? '📍' : '📁'} + ${isCurrentProject ? '' : ''}
${escapeHtml(path.split('\\').pop() || path)} @@ -208,7 +208,7 @@ function renderMcpServerCard(serverName, serverConfig, isEnabled, isInCurrentPro
- ${isEnabled ? 'đŸŸĸ' : '🔴'} + ${isEnabled ? '' : ''}

${escapeHtml(serverName)}