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
This commit is contained in:
catlog22
2025-12-08 23:14:48 +08:00
parent 5f31c9ad7e
commit b0bc53646e
10 changed files with 307 additions and 164 deletions

View File

@@ -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<Object>}
*/
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);
});
}

View File

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

View File

@@ -271,3 +271,13 @@ function getHookEventIcon(event) {
};
return icons[event] || '🪝';
}
function getHookEventIconLucide(event) {
const icons = {
'PreToolUse': '<i data-lucide="clock" class="w-5 h-5"></i>',
'PostToolUse': '<i data-lucide="check-circle" class="w-5 h-5"></i>',
'Notification': '<i data-lucide="bell" class="w-5 h-5"></i>',
'Stop': '<i data-lucide="octagon-x" class="w-5 h-5"></i>'
};
return icons[event] || '<i data-lucide="webhook" class="w-5 h-5"></i>';
}

View File

@@ -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() {
<span class="task-queue-title"><i data-lucide="clipboard-list" class="w-4 h-4 inline-block mr-1"></i> Update Tasks</span>
<button class="task-queue-close" onclick="toggleTaskQueue()">×</button>
</div>
<div class="task-queue-actions">
<button class="queue-action-btn" onclick="openAddTaskModal()" title="Add update task">
<i data-lucide="plus" class="w-4 h-4"></i> Add
</button>
<button class="queue-action-btn queue-start-btn" onclick="startTaskQueue()" id="startQueueBtn" disabled>
<i data-lucide="play" class="w-4 h-4"></i> Start
</button>
<button class="queue-action-btn queue-clear-btn" onclick="clearCompletedTasks()" title="Clear completed">
<i data-lucide="trash-2" class="w-4 h-4"></i> Clear
</button>
<div class="task-queue-toolbar">
<div class="queue-cli-selector">
<label>CLI:</label>
<select id="queueCliTool" onchange="updateDefaultCliTool(this.value)">
<option value="gemini">Gemini</option>
<option value="qwen">Qwen</option>
<option value="codex">Codex</option>
</select>
</div>
<div class="task-queue-actions">
<button class="queue-action-btn" onclick="openAddTaskModal()" title="Add update task">
<i data-lucide="plus" class="w-4 h-4"></i>
</button>
<button class="queue-action-btn queue-start-btn" onclick="startTaskQueue()" id="startQueueBtn" disabled title="Start all tasks">
<i data-lucide="play" class="w-4 h-4"></i>
</button>
<button class="queue-action-btn queue-clear-btn" onclick="clearCompletedTasks()" title="Clear completed">
<i data-lucide="trash-2" class="w-4 h-4"></i>
</button>
</div>
</div>
<div class="task-queue-list" id="taskQueueList">
<div class="task-queue-empty">
@@ -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;

View File

@@ -73,11 +73,12 @@ function renderSessions() {
if (sessions.length === 0) {
container.innerHTML = `
<div class="empty-state" style="grid-column: 1/-1;">
<div class="empty-icon">📭</div>
<div class="empty-icon"><i data-lucide="inbox" class="w-12 h-12"></i></div>
<div class="empty-title">No Sessions Found</div>
<div class="empty-text">No workflow sessions match your current filter.</div>
</div>
`;
if (typeof lucide !== 'undefined') lucide.createIcons();
return;
}
@@ -120,8 +121,8 @@ function renderSessionCard(session) {
</div>
<div class="session-body">
<div class="session-meta">
<span class="session-meta-item">📅 ${formatDate(date)}</span>
<span class="session-meta-item">📋 ${taskCount} tasks</span>
<span class="session-meta-item"><i data-lucide="calendar" class="w-3.5 h-3.5 inline mr-1"></i>${formatDate(date)}</span>
<span class="session-meta-item"><i data-lucide="list-checks" class="w-3.5 h-3.5 inline mr-1"></i>${taskCount} tasks</span>
</div>
${taskCount > 0 ? `
<div class="progress-container">
@@ -171,16 +172,16 @@ function renderReviewSessionCard(session, sessionKey, typeBadge, isActive, date)
</div>
<div class="session-body">
<div class="session-meta">
<span class="session-meta-item">📅 ${formatDate(date)}</span>
<span class="session-meta-item">🔍 ${totalFindings} findings</span>
<span class="session-meta-item"><i data-lucide="calendar" class="w-3.5 h-3.5 inline mr-1"></i>${formatDate(date)}</span>
<span class="session-meta-item"><i data-lucide="search" class="w-3.5 h-3.5 inline mr-1"></i>${totalFindings} findings</span>
</div>
${totalFindings > 0 ? `
<div class="review-findings-summary">
<div class="findings-severity-row">
${criticalCount > 0 ? `<span class="finding-count critical">🔴 ${criticalCount}</span>` : ''}
${highCount > 0 ? `<span class="finding-count high">🟠 ${highCount}</span>` : ''}
${mediumCount > 0 ? `<span class="finding-count medium">🟡 ${mediumCount}</span>` : ''}
${lowCount > 0 ? `<span class="finding-count low">🟢 ${lowCount}</span>` : ''}
${criticalCount > 0 ? `<span class="finding-count critical"><i data-lucide="alert-circle" class="w-3 h-3 inline"></i> ${criticalCount}</span>` : ''}
${highCount > 0 ? `<span class="finding-count high"><i data-lucide="alert-triangle" class="w-3 h-3 inline"></i> ${highCount}</span>` : ''}
${mediumCount > 0 ? `<span class="finding-count medium"><i data-lucide="info" class="w-3 h-3 inline"></i> ${mediumCount}</span>` : ''}
${lowCount > 0 ? `<span class="finding-count low"><i data-lucide="check-circle" class="w-3 h-3 inline"></i> ${lowCount}</span>` : ''}
</div>
<div class="dimensions-info">
${dimensions.length} dimensions

View File

@@ -41,7 +41,7 @@ async function renderHookManager() {
${projectHookCount === 0 ? `
<div class="hook-empty-state bg-card border border-border rounded-lg p-6 text-center">
<div class="text-3xl mb-3">🪝</div>
<div class="text-muted-foreground mb-3"><i data-lucide="webhook" class="w-10 h-10 mx-auto"></i></div>
<p class="text-muted-foreground">No hooks configured for this project</p>
<p class="text-sm text-muted-foreground mt-1">Create a hook to automate actions on tool usage</p>
</div>
@@ -160,7 +160,7 @@ function renderHooksByEvent(hooks, scope) {
<div class="hook-card bg-card border border-border rounded-lg p-4 hover:shadow-md transition-all">
<div class="flex items-start justify-between mb-3">
<div class="flex items-center gap-2">
<span class="text-xl">${getHookEventIcon(event)}</span>
${getHookEventIconLucide(event)}
<div>
<h4 class="font-semibold text-foreground">${event}</h4>
<p class="text-xs text-muted-foreground">${getHookEventDescription(event)}</p>
@@ -173,7 +173,7 @@ function renderHooksByEvent(hooks, scope) {
data-index="${index}"
data-action="edit"
title="Edit hook">
✏️
<i data-lucide="pencil" class="w-4 h-4"></i>
</button>
<button class="p-1.5 text-muted-foreground hover:text-destructive hover:bg-destructive/10 rounded transition-colors"
data-scope="${scope}"
@@ -181,7 +181,7 @@ function renderHooksByEvent(hooks, scope) {
data-index="${index}"
data-action="delete"
title="Delete hook">
🗑️
<i data-lucide="trash-2" class="w-4 h-4"></i>
</button>
</div>
</div>
@@ -215,7 +215,7 @@ function renderQuickInstallCard(templateId, title, description, event, matcher)
<div class="hook-template-card bg-card border border-border rounded-lg p-4 hover:shadow-md transition-all ${isInstalled ? 'border-success bg-success-light/30' : ''}">
<div class="flex items-start justify-between mb-3">
<div class="flex items-center gap-2">
<span class="text-xl">${isInstalled ? '✅' : '🪝'}</span>
${isInstalled ? '<i data-lucide="check-circle" class="w-5 h-5 text-success"></i>' : '<i data-lucide="webhook" class="w-5 h-5"></i>'}
<div>
<h4 class="font-semibold text-foreground">${escapeHtml(title)}</h4>
<p class="text-xs text-muted-foreground">${escapeHtml(description)}</p>

View File

@@ -14,11 +14,12 @@ function renderLiteTasks() {
if (sessions.length === 0) {
container.innerHTML = `
<div class="empty-state">
<div class="empty-icon"></div>
<div class="empty-icon"><i data-lucide="zap" class="w-12 h-12"></i></div>
<div class="empty-title">No ${currentLiteType} Sessions</div>
<div class="empty-text">No sessions found in .workflow/.${currentLiteType}/</div>
</div>
`;
if (typeof lucide !== 'undefined') lucide.createIcons();
return;
}
@@ -51,13 +52,13 @@ function renderLiteTaskCard(session) {
<div class="session-header">
<div class="session-title">${escapeHtml(session.id)}</div>
<span class="session-status ${session.type}">
${session.type === 'lite-plan' ? '📝 PLAN' : '🔧 FIX'}
${session.type === 'lite-plan' ? '<i data-lucide="file-edit" class="w-3 h-3 inline"></i> PLAN' : '<i data-lucide="wrench" class="w-3 h-3 inline"></i> FIX'}
</span>
</div>
<div class="session-body">
<div class="session-meta">
<span class="session-meta-item">📅 ${formatDate(session.createdAt)}</span>
<span class="session-meta-item">📋 ${tasks.length} tasks</span>
<span class="session-meta-item"><i data-lucide="calendar" class="w-3.5 h-3.5 inline mr-1"></i>${formatDate(session.createdAt)}</span>
<span class="session-meta-item"><i data-lucide="list-checks" class="w-3.5 h-3.5 inline mr-1"></i>${tasks.length} tasks</span>
</div>
</div>
</div>
@@ -102,7 +103,7 @@ function showLiteTaskDetailPage(sessionKey) {
<span>Back to ${session.type === 'lite-plan' ? 'Lite Plan' : 'Lite Fix'}</span>
</button>
<div class="detail-title-row">
<h2 class="detail-session-id">${session.type === 'lite-plan' ? '📝' : '🔧'} ${escapeHtml(session.id)}</h2>
<h2 class="detail-session-id">${session.type === 'lite-plan' ? '<i data-lucide="file-edit" class="w-5 h-5 inline mr-2"></i>' : '<i data-lucide="wrench" class="w-5 h-5 inline mr-2"></i>'} ${escapeHtml(session.id)}</h2>
<div class="detail-badges">
<span class="session-type-badge ${session.type}">${session.type}</span>
</div>
@@ -124,20 +125,20 @@ function showLiteTaskDetailPage(sessionKey) {
<!-- Tab Navigation -->
<div class="detail-tabs">
<button class="detail-tab active" data-tab="tasks" onclick="switchLiteDetailTab('tasks')">
<span class="tab-icon">📋</span>
<span class="tab-icon"><i data-lucide="list-checks" class="w-4 h-4"></i></span>
<span class="tab-text">Tasks</span>
<span class="tab-count">${tasks.length}</span>
</button>
<button class="detail-tab" data-tab="plan" onclick="switchLiteDetailTab('plan')">
<span class="tab-icon">📐</span>
<span class="tab-icon"><i data-lucide="ruler" class="w-4 h-4"></i></span>
<span class="tab-text">Plan</span>
</button>
<button class="detail-tab" data-tab="context" onclick="switchLiteDetailTab('context')">
<span class="tab-icon">📦</span>
<span class="tab-icon"><i data-lucide="package" class="w-4 h-4"></i></span>
<span class="tab-text">Context</span>
</button>
<button class="detail-tab" data-tab="summary" onclick="switchLiteDetailTab('summary')">
<span class="tab-icon">📝</span>
<span class="tab-icon"><i data-lucide="file-text" class="w-4 h-4"></i></span>
<span class="tab-text">Summary</span>
</button>
</div>
@@ -209,7 +210,7 @@ function renderLiteTasksTab(session, tasks, completed, inProgress, pending) {
if (tasks.length === 0) {
return `
<div class="tab-empty-state">
<div class="empty-icon">📋</div>
<div class="empty-icon"><i data-lucide="clipboard-list" class="w-12 h-12"></i></div>
<div class="empty-title">No Tasks</div>
<div class="empty-text">This session has no tasks defined.</div>
</div>
@@ -287,7 +288,7 @@ function renderLitePlanTab(session) {
if (!plan) {
return `
<div class="tab-empty-state">
<div class="empty-icon">📐</div>
<div class="empty-icon"><i data-lucide="ruler" class="w-12 h-12"></i></div>
<div class="empty-title">No Plan Data</div>
<div class="empty-text">No plan.json found for this session.</div>
</div>
@@ -299,7 +300,7 @@ function renderLitePlanTab(session) {
<!-- Summary -->
${plan.summary ? `
<div class="plan-section">
<h4 class="plan-section-title">📋 Summary</h4>
<h4 class="plan-section-title"><i data-lucide="clipboard-list" class="w-4 h-4 inline mr-1"></i> Summary</h4>
<p class="plan-summary-text">${escapeHtml(plan.summary)}</p>
</div>
` : ''}
@@ -307,7 +308,7 @@ function renderLitePlanTab(session) {
<!-- Approach -->
${plan.approach ? `
<div class="plan-section">
<h4 class="plan-section-title">🎯 Approach</h4>
<h4 class="plan-section-title"><i data-lucide="target" class="w-4 h-4 inline mr-1"></i> Approach</h4>
<p class="plan-approach-text">${escapeHtml(plan.approach)}</p>
</div>
` : ''}
@@ -315,7 +316,7 @@ function renderLitePlanTab(session) {
<!-- Focus Paths -->
${plan.focus_paths?.length ? `
<div class="plan-section">
<h4 class="plan-section-title">📁 Focus Paths</h4>
<h4 class="plan-section-title"><i data-lucide="folder" class="w-4 h-4 inline mr-1"></i> Focus Paths</h4>
<div class="path-tags">
${plan.focus_paths.map(p => `<span class="path-tag">${escapeHtml(p)}</span>`).join('')}
</div>
@@ -324,7 +325,7 @@ function renderLitePlanTab(session) {
<!-- Metadata -->
<div class="plan-section">
<h4 class="plan-section-title"> Metadata</h4>
<h4 class="plan-section-title"><i data-lucide="info" class="w-4 h-4 inline mr-1"></i> Metadata</h4>
<div class="plan-meta-grid">
${plan.estimated_time ? `<div class="meta-item"><span class="meta-label">Estimated Time:</span> ${escapeHtml(plan.estimated_time)}</div>` : ''}
${plan.complexity ? `<div class="meta-item"><span class="meta-label">Complexity:</span> ${escapeHtml(plan.complexity)}</div>` : ''}
@@ -379,11 +380,12 @@ async function loadAndRenderLiteSummaryTab(session, contentArea) {
// Fallback
contentArea.innerHTML = `
<div class="tab-empty-state">
<div class="empty-icon">📝</div>
<div class="empty-icon"><i data-lucide="file-text" class="w-12 h-12"></i></div>
<div class="empty-title">No Summaries</div>
<div class="empty-text">No summaries found in .summaries/</div>
</div>
`;
if (typeof lucide !== 'undefined') lucide.createIcons();
} catch (err) {
contentArea.innerHTML = `<div class="tab-error">Failed to load summaries: ${err.message}</div>`;
}

View File

@@ -52,7 +52,7 @@ async function renderMcpManager() {
${currentProjectServerNames.length === 0 ? `
<div class="mcp-empty-state bg-card border border-border rounded-lg p-6 text-center">
<div class="text-3xl mb-3">🔌</div>
<div class="text-muted-foreground mb-3"><i data-lucide="plug" class="w-10 h-10 mx-auto"></i></div>
<p class="text-muted-foreground">No MCP servers configured for this project</p>
<p class="text-sm text-muted-foreground mt-1">Add servers from the available list below</p>
</div>
@@ -72,7 +72,7 @@ async function renderMcpManager() {
<div class="mcp-section mb-6">
<div class="flex items-center justify-between mb-4">
<div class="flex items-center gap-2">
<span class="text-lg">🏢</span>
<i data-lucide="building-2" class="w-5 h-5"></i>
<h3 class="text-lg font-semibold text-foreground">Enterprise MCP Servers</h3>
<span class="text-xs px-2 py-0.5 bg-warning/20 text-warning rounded-full">Managed</span>
</div>
@@ -92,7 +92,7 @@ async function renderMcpManager() {
<div class="mcp-section mb-6">
<div class="flex items-center justify-between mb-4">
<div class="flex items-center gap-2">
<span class="text-lg">👤</span>
<i data-lucide="user" class="w-5 h-5"></i>
<h3 class="text-lg font-semibold text-foreground">User MCP Servers</h3>
</div>
<span class="text-sm text-muted-foreground">${userServerEntries.length} servers from ~/.claude.json</span>
@@ -154,7 +154,7 @@ async function renderMcpManager() {
<tr class="border-b border-border last:border-b-0 ${isCurrentProject ? 'bg-primary/5' : 'hover:bg-hover/50'}">
<td class="px-4 py-3">
<div class="flex items-center gap-2 min-w-0">
<span class="text-base shrink-0">${isCurrentProject ? '📍' : '📁'}</span>
<span class="shrink-0">${isCurrentProject ? '<i data-lucide="map-pin" class="w-4 h-4 text-primary"></i>' : '<i data-lucide="folder" class="w-4 h-4"></i>'}</span>
<div class="min-w-0">
<div class="font-medium text-foreground truncate text-sm" title="${escapeHtml(path)}">
${escapeHtml(path.split('\\').pop() || path)}
@@ -208,7 +208,7 @@ function renderMcpServerCard(serverName, serverConfig, isEnabled, isInCurrentPro
<div class="mcp-server-card bg-card border border-border rounded-lg p-4 hover:shadow-md transition-all ${isEnabled ? '' : 'opacity-60'}">
<div class="flex items-start justify-between mb-3">
<div class="flex items-center gap-2">
<span class="text-xl">${isEnabled ? '🟢' : '🔴'}</span>
<span>${isEnabled ? '<i data-lucide="check-circle" class="w-5 h-5 text-success"></i>' : '<i data-lucide="x-circle" class="w-5 h-5 text-destructive"></i>'}</span>
<h4 class="font-semibold text-foreground">${escapeHtml(serverName)}</h4>
</div>
<label class="mcp-toggle relative inline-flex items-center cursor-pointer">
@@ -261,7 +261,7 @@ function renderAvailableServerCard(serverName, serverInfo) {
<div class="mcp-server-card mcp-server-available bg-card border border-border border-dashed rounded-lg p-4 hover:shadow-md hover:border-solid transition-all">
<div class="flex items-start justify-between mb-3">
<div class="flex items-center gap-2">
<span class="text-xl">⚪</span>
<span><i data-lucide="circle-dashed" class="w-5 h-5 text-muted-foreground"></i></span>
<h4 class="font-semibold text-foreground">${escapeHtml(serverName)}</h4>
</div>
<button class="px-3 py-1 text-xs bg-primary text-primary-foreground rounded hover:opacity-90 transition-opacity"
@@ -295,7 +295,7 @@ function renderGlobalServerCard(serverName, serverConfig, source = 'user') {
<div class="mcp-server-card mcp-server-global bg-card border border-primary/30 rounded-lg p-4 hover:shadow-md transition-all">
<div class="flex items-start justify-between mb-3">
<div class="flex items-center gap-2">
<span class="text-xl">👤</span>
<i data-lucide="user" class="w-5 h-5"></i>
<h4 class="font-semibold text-foreground">${escapeHtml(serverName)}</h4>
<span class="text-xs px-2 py-0.5 bg-primary/10 text-primary rounded-full">User</span>
</div>
@@ -342,10 +342,10 @@ function renderEnterpriseServerCard(serverName, serverConfig) {
<div class="mcp-server-card mcp-server-enterprise bg-card border border-warning/30 rounded-lg p-4 hover:shadow-md transition-all">
<div class="flex items-start justify-between mb-3">
<div class="flex items-center gap-2">
<span class="text-xl">🏢</span>
<i data-lucide="building-2" class="w-5 h-5"></i>
<h4 class="font-semibold text-foreground">${escapeHtml(serverName)}</h4>
<span class="text-xs px-2 py-0.5 bg-warning/20 text-warning rounded-full">Enterprise</span>
<span class="text-xs text-muted-foreground">🔒</span>
<i data-lucide="lock" class="w-3 h-3 text-muted-foreground"></i>
</div>
<span class="px-3 py-1 text-xs bg-muted text-muted-foreground rounded cursor-not-allowed">
Read-only

View File

@@ -12,13 +12,14 @@ function renderProjectOverview() {
if (!project) {
container.innerHTML = `
<div class="flex flex-col items-center justify-center py-16 text-center">
<div class="text-6xl mb-4">📋</div>
<div class="text-muted-foreground mb-4"><i data-lucide="clipboard-list" class="w-16 h-16"></i></div>
<h3 class="text-xl font-semibold text-foreground mb-2">No Project Overview</h3>
<p class="text-muted-foreground mb-4">
Run <code class="px-2 py-1 bg-muted rounded text-sm font-mono">/workflow:init</code> to initialize project analysis
</p>
</div>
`;
if (typeof lucide !== 'undefined') lucide.createIcons();
return;
}
@@ -40,7 +41,7 @@ function renderProjectOverview() {
<!-- Technology Stack -->
<div class="bg-card border border-border rounded-lg p-6 mb-6">
<h3 class="text-lg font-semibold text-foreground mb-4 flex items-center gap-2">
<span>💻</span> Technology Stack
<i data-lucide="code-2" class="w-5 h-5"></i> Technology Stack
</h3>
<!-- Languages -->
@@ -91,7 +92,7 @@ function renderProjectOverview() {
<!-- Architecture -->
<div class="bg-card border border-border rounded-lg p-6 mb-6">
<h3 class="text-lg font-semibold text-foreground mb-4 flex items-center gap-2">
<span>🏗️</span> Architecture
<i data-lucide="blocks" class="w-5 h-5"></i> Architecture
</h3>
<div class="grid grid-cols-1 md:grid-cols-3 gap-5">
@@ -128,7 +129,7 @@ function renderProjectOverview() {
<!-- Key Components -->
<div class="bg-card border border-border rounded-lg p-6 mb-6">
<h3 class="text-lg font-semibold text-foreground mb-4 flex items-center gap-2">
<span>⚙️</span> Key Components
<i data-lucide="component" class="w-5 h-5"></i> Key Components
</h3>
${project.keyComponents.length > 0 ? `
@@ -162,7 +163,7 @@ function renderProjectOverview() {
<!-- Development Index -->
<div class="bg-card border border-border rounded-lg p-6 mb-6">
<h3 class="text-lg font-semibold text-foreground mb-4 flex items-center gap-2">
<span>📝</span> Development History
<i data-lucide="git-branch" class="w-5 h-5"></i> Development History
</h3>
${renderDevelopmentIndex(project.developmentIndex)}
@@ -171,7 +172,7 @@ function renderProjectOverview() {
<!-- Statistics -->
<div class="bg-card border border-border rounded-lg p-6">
<h3 class="text-lg font-semibold text-foreground mb-4 flex items-center gap-2">
<span>📊</span> Statistics
<i data-lucide="bar-chart-3" class="w-5 h-5"></i> Statistics
</h3>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
@@ -196,11 +197,11 @@ function renderDevelopmentIndex(devIndex) {
if (!devIndex) return '<p class="text-muted-foreground text-sm">No development history available</p>';
const categories = [
{ key: 'feature', label: 'Features', icon: '', badgeClass: 'bg-primary-light text-primary' },
{ key: 'enhancement', label: 'Enhancements', icon: '', badgeClass: 'bg-success-light text-success' },
{ key: 'bugfix', label: 'Bug Fixes', icon: '🐛', badgeClass: 'bg-destructive/10 text-destructive' },
{ key: 'refactor', label: 'Refactorings', icon: '🔧', badgeClass: 'bg-warning-light text-warning' },
{ key: 'docs', label: 'Documentation', icon: '📚', badgeClass: 'bg-muted text-muted-foreground' }
{ key: 'feature', label: 'Features', icon: '<i data-lucide="sparkles" class="w-4 h-4 inline"></i>', badgeClass: 'bg-primary-light text-primary' },
{ key: 'enhancement', label: 'Enhancements', icon: '<i data-lucide="zap" class="w-4 h-4 inline"></i>', badgeClass: 'bg-success-light text-success' },
{ key: 'bugfix', label: 'Bug Fixes', icon: '<i data-lucide="bug" class="w-4 h-4 inline"></i>', badgeClass: 'bg-destructive/10 text-destructive' },
{ key: 'refactor', label: 'Refactorings', icon: '<i data-lucide="wrench" class="w-4 h-4 inline"></i>', badgeClass: 'bg-warning-light text-warning' },
{ key: 'docs', label: 'Documentation', icon: '<i data-lucide="book-open" class="w-4 h-4 inline"></i>', badgeClass: 'bg-muted text-muted-foreground' }
];
const totalEntries = categories.reduce((sum, cat) => sum + (devIndex[cat.key]?.length || 0), 0);

View File

@@ -81,26 +81,26 @@ function showSessionDetailPage(sessionKey) {
<!-- Tab Navigation -->
<div class="detail-tabs">
<button class="detail-tab active" data-tab="tasks" onclick="switchDetailTab('tasks')">
<span class="tab-icon">📋</span>
<span class="tab-icon"><i data-lucide="list-checks" class="w-4 h-4"></i></span>
<span class="tab-text">Tasks</span>
<span class="tab-count">${tasks.length}</span>
</button>
<button class="detail-tab" data-tab="context" onclick="switchDetailTab('context')">
<span class="tab-icon">📦</span>
<span class="tab-icon"><i data-lucide="package" class="w-4 h-4"></i></span>
<span class="tab-text">Context</span>
</button>
<button class="detail-tab" data-tab="summary" onclick="switchDetailTab('summary')">
<span class="tab-icon">📝</span>
<span class="tab-icon"><i data-lucide="file-text" class="w-4 h-4"></i></span>
<span class="tab-text">Summary</span>
</button>
<button class="detail-tab" data-tab="impl-plan" onclick="switchDetailTab('impl-plan')">
<span class="tab-icon">📐</span>
<span class="tab-icon"><i data-lucide="ruler" class="w-4 h-4"></i></span>
<span class="tab-text">IMPL Plan</span>
</button>
<button class="detail-tab" data-tab="conflict" onclick="switchDetailTab('conflict')"> <span class="tab-icon">⚖️</span> <span class="tab-text">Conflict</span> </button>
<button class="detail-tab" data-tab="conflict" onclick="switchDetailTab('conflict')"> <span class="tab-icon"><i data-lucide="scale" class="w-4 h-4"></i></span> <span class="tab-text">Conflict</span> </button>
${session.hasReview ? `
<button class="detail-tab" data-tab="review" onclick="switchDetailTab('review')">
<span class="tab-icon">🔍</span>
<span class="tab-icon"><i data-lucide="search" class="w-4 h-4"></i></span>
<span class="tab-text">Review</span>
</button>
` : ''}
@@ -199,7 +199,7 @@ function renderTasksTab(session, tasks, completed, inProgress, pending) {
<div class="tab-loading">Loading task details...</div>
` : (tasks.length === 0 ? `
<div class="tab-empty-state">
<div class="empty-icon">📋</div>
<div class="empty-icon"><i data-lucide="clipboard-list" class="w-12 h-12"></i></div>
<div class="empty-title">No Tasks</div>
<div class="empty-text">This session has no tasks defined.</div>
</div>
@@ -227,11 +227,12 @@ async function loadFullTaskDetails() {
} else {
tasksContainer.innerHTML = `
<div class="tab-empty-state">
<div class="empty-icon">📋</div>
<div class="empty-icon"><i data-lucide="clipboard-list" class="w-12 h-12"></i></div>
<div class="empty-title">No Task Files</div>
<div class="empty-text">No IMPL-*.json files found in .task/</div>
</div>
`;
if (typeof lucide !== 'undefined') lucide.createIcons();
}
}
} catch (err) {
@@ -429,11 +430,12 @@ async function loadAndRenderContextTab(session, contentArea) {
// Fallback: show placeholder
contentArea.innerHTML = `
<div class="tab-empty-state">
<div class="empty-icon">📦</div>
<div class="empty-icon"><i data-lucide="package" class="w-12 h-12"></i></div>
<div class="empty-title">Context Data</div>
<div class="empty-text">Context data will be loaded from context-package.json</div>
</div>
`;
if (typeof lucide !== 'undefined') lucide.createIcons();
} catch (err) {
contentArea.innerHTML = `<div class="tab-error">Failed to load context: ${err.message}</div>`;
}
@@ -453,11 +455,12 @@ async function loadAndRenderSummaryTab(session, contentArea) {
}
contentArea.innerHTML = `
<div class="tab-empty-state">
<div class="empty-icon">📝</div>
<div class="empty-icon"><i data-lucide="file-text" class="w-12 h-12"></i></div>
<div class="empty-title">Summaries</div>
<div class="empty-text">Session summaries will be loaded from .summaries/</div>
</div>
`;
if (typeof lucide !== 'undefined') lucide.createIcons();
} catch (err) {
contentArea.innerHTML = `<div class="tab-error">Failed to load summaries: ${err.message}</div>`;
}
@@ -477,11 +480,12 @@ async function loadAndRenderImplPlanTab(session, contentArea) {
}
contentArea.innerHTML = `
<div class="tab-empty-state">
<div class="empty-icon">📐</div>
<div class="empty-icon"><i data-lucide="ruler" class="w-12 h-12"></i></div>
<div class="empty-title">IMPL Plan</div>
<div class="empty-text">IMPL plan will be loaded from IMPL_PLAN.md</div>
</div>
`;
if (typeof lucide !== 'undefined') lucide.createIcons();
} catch (err) {
contentArea.innerHTML = `<div class="tab-error">Failed to load IMPL plan: ${err.message}</div>`;
}
@@ -501,11 +505,12 @@ async function loadAndRenderReviewTab(session, contentArea) {
}
contentArea.innerHTML = `
<div class="tab-empty-state">
<div class="empty-icon">🔍</div>
<div class="empty-icon"><i data-lucide="search" class="w-12 h-12"></i></div>
<div class="empty-title">Review Data</div>
<div class="empty-text">Review data will be loaded from review files</div>
</div>
`;
if (typeof lucide !== 'undefined') lucide.createIcons();
} catch (err) {
contentArea.innerHTML = `<div class="tab-error">Failed to load review: ${err.message}</div>`;
}