From 04fb3b7ee3acf958fe0563342e584876505ed617 Mon Sep 17 00:00:00 2001 From: catlog22 Date: Thu, 4 Dec 2025 22:55:56 +0800 Subject: [PATCH] feat(dashboard): add plan data loading and lite task detail styling - Add plan.json data loading to getSessionDetailData function for lite tasks - Implement plan tab content styling with summary and approach sections - Add plan metadata grid layout for displaying task planning information - Create lite task detail page styles with task stats bar and status indicators - Add context tab content styling with improved section hierarchy - Implement path tags and JSON content display styles for plan details - Add collapsible sections for organizing plan information - Create comprehensive styling for context fields, modification points, and implementation steps - Add array and nested object rendering styles for JSON data visualization - Implement button styles for JSON view toggle functionality --- ccw/src/core/server.js | 12 + ccw/src/templates/dashboard.css | 530 +++++++++++++++++++++++++++++++- ccw/src/templates/dashboard.js | 506 +++++++++++++++++++++++++++--- 3 files changed, 1007 insertions(+), 41 deletions(-) diff --git a/ccw/src/core/server.js b/ccw/src/core/server.js index 4b214e8a..d084e831 100644 --- a/ccw/src/core/server.js +++ b/ccw/src/core/server.js @@ -214,6 +214,18 @@ async function getSessionDetailData(sessionPath, dataType) { } } + // Load plan.json (for lite tasks) + if (dataType === 'plan' || dataType === 'all') { + const planFile = join(normalizedPath, 'plan.json'); + if (existsSync(planFile)) { + try { + result.plan = JSON.parse(readFileSync(planFile, 'utf8')); + } catch (e) { + result.plan = null; + } + } + } + // Load IMPL_PLAN.md if (dataType === 'impl-plan' || dataType === 'all') { const implPlanFile = join(normalizedPath, 'IMPL_PLAN.md'); diff --git a/ccw/src/templates/dashboard.css b/ccw/src/templates/dashboard.css index 5976b710..2f63d58b 100644 --- a/ccw/src/templates/dashboard.css +++ b/ccw/src/templates/dashboard.css @@ -601,15 +601,104 @@ } .context-section, -.summary-section { +.summary-section, +.plan-section { margin-bottom: 1.5rem; } .context-section:last-child, -.summary-section:last-child { +.summary-section:last-child, +.plan-section:last-child { margin-bottom: 0; } +/* Plan Tab Styles */ +.plan-tab-content { + padding: 1rem 0; +} + +.plan-section-title { + font-size: 1rem; + font-weight: 600; + color: hsl(var(--foreground)); + margin-bottom: 0.75rem; + display: flex; + align-items: center; + gap: 0.5rem; +} + +.plan-summary-text, +.plan-approach-text { + color: hsl(var(--foreground)); + line-height: 1.6; + padding: 0.75rem 1rem; + background: hsl(var(--muted)); + border-radius: 0.5rem; + border-left: 3px solid hsl(var(--primary)); +} + +.plan-meta-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 0.75rem; +} + +.plan-meta-grid .meta-item { + padding: 0.5rem 0.75rem; + background: hsl(var(--muted)); + border-radius: 0.25rem; + font-size: 0.875rem; +} + +.plan-meta-grid .meta-label { + font-weight: 600; + color: hsl(var(--muted-foreground)); +} + +/* Lite Task Detail Page */ +.lite-task-detail-page .detail-header { + margin-bottom: 1.5rem; +} + +.lite-task-detail-page .task-stats-bar { + display: flex; + gap: 1rem; + margin-bottom: 1rem; + padding: 0.75rem 1rem; + background: hsl(var(--muted)); + border-radius: 0.5rem; +} + +.lite-task-detail-page .task-stat { + font-size: 0.875rem; + color: hsl(var(--muted-foreground)); +} + +.lite-task-detail-page .task-stat.completed { + color: hsl(var(--success)); +} + +.lite-task-detail-page .task-stat.in-progress { + color: hsl(var(--warning)); +} + +/* Context Tab Content */ +.context-tab-content { + padding: 1rem 0; +} + +.context-tab-content .context-section h4 { + font-size: 0.9rem; + font-weight: 600; + color: hsl(var(--foreground)); + margin-bottom: 0.5rem; +} + +.context-tab-content .context-section p { + color: hsl(var(--foreground)); + line-height: 1.6; +} + .section-title { font-size: 1rem; font-weight: 600; @@ -1152,3 +1241,440 @@ code { max-height: 500px; overflow-y: auto; } + + +/* =================================== + Lite Task Detail Page Additions + =================================== */ + +/* Path Tags */ +.path-tags { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; +} + +.path-tag { + padding: 0.25rem 0.5rem; + background: hsl(var(--muted)); + border-radius: 0.25rem; + font-family: monospace; + font-size: 0.8rem; + color: hsl(var(--foreground)); +} + +/* JSON Content */ +.json-content { + background: hsl(var(--muted)); + padding: 1rem; + border-radius: 0.5rem; + overflow-x: auto; + font-size: 0.75rem; + line-height: 1.6; + color: hsl(var(--foreground)); + max-height: 500px; + overflow-y: auto; + white-space: pre-wrap; + word-break: break-word; +} + +/* Button View JSON */ +.btn-view-json { + padding: 0.25rem 0.5rem; + background: hsl(var(--muted)); + border: 1px solid hsl(var(--border)); + border-radius: 0.25rem; + font-size: 0.7rem; + font-family: monospace; + color: hsl(var(--muted-foreground)); + cursor: pointer; + transition: all 0.15s; +} + +.btn-view-json:hover { + background: hsl(var(--hover)); + color: hsl(var(--foreground)); +} + +/* Context Fields */ +.context-fields { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.context-field { + padding: 0.75rem; + background: hsl(var(--muted)); + border-radius: 0.375rem; +} + +.context-field label { + display: block; + font-size: 0.75rem; + font-weight: 600; + color: hsl(var(--muted-foreground)); + margin-bottom: 0.5rem; + text-transform: uppercase; +} + +.context-field p { + margin: 0; + font-size: 0.875rem; + color: hsl(var(--foreground)); + line-height: 1.5; +} + +.context-field ul { + margin: 0; + padding-left: 1.25rem; + font-size: 0.875rem; + color: hsl(var(--foreground)); +} + +.context-field ul li { + margin-bottom: 0.25rem; +} + +/* Modification Points */ +.mod-points { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.mod-point { + padding: 0.5rem; + background: hsl(var(--card)); + border: 1px solid hsl(var(--border)); + border-radius: 0.25rem; +} + +.mod-target { + font-size: 0.75rem; + color: hsl(var(--muted-foreground)); + margin-left: 0.5rem; +} + +.mod-change { + margin: 0.5rem 0 0 0; + font-size: 0.8rem; + color: hsl(var(--foreground)); +} + +/* Implementation Steps */ +.impl-steps { + margin: 0; + padding-left: 1.25rem; + font-size: 0.875rem; + color: hsl(var(--foreground)); +} + +.impl-steps li { + margin-bottom: 0.5rem; + line-height: 1.5; +} + +/* Field Groups */ +.field-group { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.field-row { + display: flex; + gap: 0.5rem; + font-size: 0.875rem; +} + +.field-label { + font-weight: 500; + color: hsl(var(--muted-foreground)); + min-width: 100px; +} + +.field-value { + color: hsl(var(--foreground)); + flex: 1; +} + +.json-value-null { + color: hsl(var(--muted-foreground)); + font-style: italic; +} + +.json-value-boolean { + color: hsl(220 80% 60%); +} + +.json-value-number { + color: hsl(142 71% 45%); +} + +.json-value-string { + color: hsl(var(--foreground)); +} + +/* Array Items */ +.array-value { + display: flex; + flex-wrap: wrap; + gap: 0.375rem; +} + +.array-item { + padding: 0.125rem 0.375rem; + background: hsl(var(--muted)); + border-radius: 0.25rem; + font-size: 0.8rem; +} + +.array-item.path-item { + font-family: monospace; + background: hsl(var(--accent)); +} + +/* Nested Array */ +.nested-array { + display: flex; + flex-direction: column; + gap: 0.5rem; + margin-top: 0.25rem; +} + +.array-object { + padding: 0.5rem; + background: hsl(var(--card)); + border: 1px solid hsl(var(--border)); + border-radius: 0.25rem; +} + +.array-object-header { + font-size: 0.7rem; + font-weight: 600; + color: hsl(var(--muted-foreground)); + margin-bottom: 0.25rem; +} + +/* Collapsible Sections */ +.collapsible-section { + border-top: 1px solid hsl(var(--border)); +} + +.collapsible-header { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.75rem 1rem; + cursor: pointer; + transition: background 0.15s; +} + +.collapsible-header:hover { + background: hsl(var(--hover)); +} + +.collapse-icon { + font-size: 0.75rem; + color: hsl(var(--muted-foreground)); + transition: transform 0.2s; +} + +.collapsible-header.expanded .collapse-icon { + transform: rotate(90deg); +} + +.section-label { + font-size: 0.75rem; + font-weight: 600; + color: hsl(var(--foreground)); + text-transform: uppercase; +} + +.section-preview { + flex: 1; + font-size: 0.75rem; + color: hsl(var(--muted-foreground)); + text-align: right; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.collapsible-content { + padding: 1rem; + background: hsl(var(--muted)); +} + +.collapsible-content.collapsed { + display: none; +} + +/* Summary Tab */ +.summary-tab-content { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.summary-item-collapsible { + border: 1px solid hsl(var(--border)); + border-radius: 0.5rem; + overflow: hidden; +} + +.summary-collapsible-header { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.75rem 1rem; + background: hsl(var(--card)); + cursor: pointer; + transition: background 0.15s; +} + +.summary-collapsible-header:hover { + background: hsl(var(--hover)); +} + +.summary-name { + font-weight: 600; + color: hsl(var(--foreground)); +} + +.summary-preview { + flex: 1; + font-size: 0.75rem; + color: hsl(var(--muted-foreground)); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.summary-collapsible-content { + padding: 1rem; + background: hsl(var(--muted)); +} + +.summary-collapsible-content.collapsed { + display: none; +} + +.summary-content-pre { + margin: 0; + white-space: pre-wrap; + word-break: break-word; + font-size: 0.8rem; + line-height: 1.6; +} + +/* JSON Modal */ +.json-modal-overlay { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 100; + opacity: 0; + transition: opacity 0.2s; +} + +.json-modal-overlay.active { + opacity: 1; +} + +.json-modal { + background: hsl(var(--card)); + border-radius: 0.5rem; + width: 90%; + max-width: 700px; + max-height: 80vh; + display: flex; + flex-direction: column; + box-shadow: 0 8px 24px rgb(0 0 0 / 0.2); +} + +.json-modal-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 1rem; + border-bottom: 1px solid hsl(var(--border)); +} + +.json-modal-title { + display: flex; + align-items: center; + gap: 0.5rem; + font-weight: 600; + color: hsl(var(--foreground)); +} + +.json-modal-close { + width: 2rem; + height: 2rem; + display: flex; + align-items: center; + justify-content: center; + background: none; + border: none; + font-size: 1.5rem; + color: hsl(var(--muted-foreground)); + cursor: pointer; + border-radius: 0.25rem; +} + +.json-modal-close:hover { + background: hsl(var(--hover)); + color: hsl(var(--foreground)); +} + +.json-modal-body { + flex: 1; + overflow: auto; + padding: 1rem; +} + +.json-modal-content { + margin: 0; + white-space: pre-wrap; + word-break: break-word; + font-size: 0.75rem; + line-height: 1.6; + color: hsl(var(--foreground)); +} + +.json-modal-footer { + padding: 1rem; + border-top: 1px solid hsl(var(--border)); + display: flex; + justify-content: flex-end; +} + +.btn-copy-json { + padding: 0.5rem 1rem; + background: hsl(var(--primary)); + color: hsl(var(--primary-foreground)); + border: none; + border-radius: 0.375rem; + font-size: 0.875rem; + cursor: pointer; + transition: opacity 0.15s; +} + +.btn-copy-json:hover { + opacity: 0.9; +} + +/* Flowchart Fallback */ +.flowchart-fallback { + display: flex; + align-items: center; + justify-content: center; + height: 200px; + color: hsl(var(--muted-foreground)); + font-size: 0.875rem; +} diff --git a/ccw/src/templates/dashboard.js b/ccw/src/templates/dashboard.js index dab2bf9c..b1b4fb45 100644 --- a/ccw/src/templates/dashboard.js +++ b/ccw/src/templates/dashboard.js @@ -385,6 +385,8 @@ function updateContentTitle() { titleEl.textContent = names[currentLiteType] || 'Lite Tasks'; } else if (currentView === 'sessionDetail') { titleEl.textContent = 'Session Detail'; + } else if (currentView === 'liteTaskDetail') { + titleEl.textContent = 'Lite Task Detail'; } else { const names = { 'all': 'All Sessions', 'active': 'Active Sessions', 'archived': 'Archived Sessions' }; titleEl.textContent = names[currentFilter] || 'Sessions'; @@ -1613,12 +1615,19 @@ function renderLiteTasks() { }); } +// Store lite task session data for detail page access +const liteTaskDataStore = {}; + function renderLiteTaskCard(session) { const progress = session.progress || { total: 0, completed: 0, percentage: 0 }; const tasks = session.tasks || []; + // Store session data for detail page + const sessionKey = `lite-${session.type}-${session.id}`.replace(/[^a-zA-Z0-9-]/g, '-'); + liteTaskDataStore[sessionKey] = session; + return ` -
+
${escapeHtml(session.id)}
@@ -1641,16 +1650,410 @@ function renderLiteTaskCard(session) {
` : ''} - ${tasks.length > 0 ? ` -
- ${tasks.map(task => renderTaskDetail(session.id, task)).join('')} -
- ` : ''}
`; } +// Lite Task Detail Page +function showLiteTaskDetailPage(sessionKey) { + const session = liteTaskDataStore[sessionKey]; + if (!session) return; + + currentView = 'liteTaskDetail'; + currentSessionDetailKey = sessionKey; + + // Also store in sessionDataStore for tab switching compatibility + sessionDataStore[sessionKey] = { + ...session, + session_id: session.id, + created_at: session.createdAt, + path: session.path, + type: session.type + }; + + const container = document.getElementById('mainContent'); + const tasks = session.tasks || []; + const plan = session.plan || {}; + const progress = session.progress || { total: 0, completed: 0, percentage: 0 }; + + const completed = tasks.filter(t => t.status === 'completed').length; + const inProgress = tasks.filter(t => t.status === 'in_progress').length; + const pending = tasks.filter(t => t.status === 'pending').length; + + container.innerHTML = ` +
+ +
+ +
+

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

+
+ ${session.type} +
+
+
+ + +
+
+ Created: + ${formatDate(session.createdAt)} +
+
+ Tasks: + ${completed}/${tasks.length} completed +
+
+ Progress: + ${progress.percentage}% +
+
+ + +
+ + + + +
+ + +
+ ${renderLiteTasksTab(session, tasks, completed, inProgress, pending)} +
+
+ `; + + // Initialize collapsible sections + setTimeout(() => { + document.querySelectorAll('.collapsible-header').forEach(header => { + header.addEventListener('click', () => toggleSection(header)); + }); + }, 50); +} + +function goBackToLiteTasks() { + currentView = 'liteTasks'; + currentSessionDetailKey = null; + updateContentTitle(); + renderLiteTasks(); +} + +function switchLiteDetailTab(tabName) { + // Update active tab + document.querySelectorAll('.detail-tab').forEach(tab => { + tab.classList.toggle('active', tab.dataset.tab === tabName); + }); + + const session = liteTaskDataStore[currentSessionDetailKey]; + if (!session) return; + + const contentArea = document.getElementById('liteDetailTabContent'); + const tasks = session.tasks || []; + const completed = tasks.filter(t => t.status === 'completed').length; + const inProgress = tasks.filter(t => t.status === 'in_progress').length; + const pending = tasks.filter(t => t.status === 'pending').length; + + switch (tabName) { + case 'tasks': + contentArea.innerHTML = renderLiteTasksTab(session, tasks, completed, inProgress, pending); + // Re-initialize collapsible sections + setTimeout(() => { + document.querySelectorAll('.collapsible-header').forEach(header => { + header.addEventListener('click', () => toggleSection(header)); + }); + }, 50); + break; + case 'plan': + contentArea.innerHTML = renderLitePlanTab(session); + break; + case 'context': + loadAndRenderLiteContextTab(session, contentArea); + break; + case 'summary': + loadAndRenderLiteSummaryTab(session, contentArea); + break; + } +} + +function renderLiteTasksTab(session, tasks, completed, inProgress, pending) { + // Populate drawer tasks for click-to-open functionality + currentDrawerTasks = tasks; + + if (tasks.length === 0) { + return ` +
+
📋
+
No Tasks
+
This session has no tasks defined.
+
+ `; + } + + return ` +
+
+ ✓ ${completed} completed + âŸŗ ${inProgress} in progress + ○ ${pending} pending +
+
+ ${tasks.map(task => renderLiteTaskDetailItem(session.id, task)).join('')} +
+
+ `; +} + +function renderLiteTaskDetailItem(sessionId, task) { + const rawTask = task._raw || task; + const taskJsonId = `task-json-${sessionId}-${task.id}`.replace(/[^a-zA-Z0-9-]/g, '-'); + taskJsonStore[taskJsonId] = rawTask; + + const statusIcon = task.status === 'completed' ? '✓' : task.status === 'in_progress' ? 'âŸŗ' : '○'; + + return ` +
+
+ ${statusIcon} + ${escapeHtml(task.id)} + ${escapeHtml(task.title || 'Untitled')} + ${task.status} + +
+ + +
+
+ â–ļ + + ${escapeHtml(getMetaPreviewForLite(task, rawTask))} +
+ +
+ + +
+
+ â–ļ + + ${escapeHtml(getContextPreview(task.context, rawTask))} +
+ +
+ + +
+
+ â–ļ + + ${escapeHtml(getFlowControlPreview(task.flow_control, rawTask))} +
+ +
+
+ `; +} + +function getMetaPreviewForLite(task, rawTask) { + const meta = task.meta || {}; + const parts = []; + if (meta.type || rawTask.action) parts.push(meta.type || rawTask.action); + if (meta.scope || rawTask.scope) parts.push(meta.scope || rawTask.scope); + return parts.join(' | ') || 'No meta'; +} + +function openTaskDrawerForLite(sessionId, taskId) { + const session = liteTaskDataStore[currentSessionDetailKey]; + if (!session) return; + + const task = session.tasks?.find(t => t.id === taskId); + if (!task) return; + + // Set current drawer tasks + currentDrawerTasks = session.tasks || []; + + document.getElementById('drawerTaskTitle').textContent = task.title || taskId; + document.getElementById('drawerContent').innerHTML = renderTaskDrawerContent(task); + document.getElementById('taskDetailDrawer').classList.add('open'); + document.getElementById('drawerOverlay').classList.add('active'); + + // Initialize flowchart after DOM is updated + setTimeout(() => { + renderFullFlowchart(task.flow_control || task._raw?.flow_control); + }, 100); +} + +function renderLitePlanTab(session) { + const plan = session.plan; + + if (!plan) { + return ` +
+
📐
+
No Plan Data
+
No plan.json found for this session.
+
+ `; + } + + return ` +
+ + ${plan.summary ? ` +
+

📋 Summary

+

${escapeHtml(plan.summary)}

+
+ ` : ''} + + + ${plan.approach ? ` +
+

đŸŽ¯ Approach

+

${escapeHtml(plan.approach)}

+
+ ` : ''} + + + ${plan.focus_paths?.length ? ` +
+

📁 Focus Paths

+
+ ${plan.focus_paths.map(p => `${escapeHtml(p)}`).join('')} +
+
+ ` : ''} + + +
+

â„šī¸ Metadata

+
+ ${plan.estimated_time ? `
Estimated Time: ${escapeHtml(plan.estimated_time)}
` : ''} + ${plan.complexity ? `
Complexity: ${escapeHtml(plan.complexity)}
` : ''} + ${plan.recommended_execution ? `
Execution: ${escapeHtml(plan.recommended_execution)}
` : ''} +
+
+ + +
+

{ } Raw JSON

+
${escapeHtml(JSON.stringify(plan, null, 2))}
+
+
+ `; +} + +async function loadAndRenderLiteContextTab(session, contentArea) { + contentArea.innerHTML = '
Loading context data...
'; + + try { + if (window.SERVER_MODE && session.path) { + const response = await fetch(`/api/session-detail?path=${encodeURIComponent(session.path)}&type=context`); + if (response.ok) { + const data = await response.json(); + contentArea.innerHTML = renderLiteContextContent(data.context, session); + return; + } + } + // Fallback: show plan context if available + contentArea.innerHTML = renderLiteContextContent(null, session); + } catch (err) { + contentArea.innerHTML = `
Failed to load context: ${err.message}
`; + } +} + +function renderLiteContextContent(context, session) { + const plan = session.plan || {}; + + // If we have context from context-package.json + if (context) { + return ` +
+
${escapeHtml(JSON.stringify(context, null, 2))}
+
+ `; + } + + // Fallback: show context from plan + if (plan.focus_paths?.length || plan.summary) { + return ` +
+ ${plan.summary ? ` +
+

Summary

+

${escapeHtml(plan.summary)}

+
+ ` : ''} + ${plan.focus_paths?.length ? ` +
+

Focus Paths

+
+ ${plan.focus_paths.map(p => `${escapeHtml(p)}`).join('')} +
+
+ ` : ''} +
+ `; + } + + return ` +
+
đŸ“Ļ
+
No Context Data
+
No context-package.json found for this session.
+
+ `; +} + +async function loadAndRenderLiteSummaryTab(session, contentArea) { + contentArea.innerHTML = '
Loading summaries...
'; + + try { + if (window.SERVER_MODE && session.path) { + const response = await fetch(`/api/session-detail?path=${encodeURIComponent(session.path)}&type=summary`); + if (response.ok) { + const data = await response.json(); + contentArea.innerHTML = renderSummaryContent(data.summaries); + return; + } + } + // Fallback + contentArea.innerHTML = ` +
+
📝
+
No Summaries
+
No summaries found in .summaries/
+
+ `; + } catch (err) { + contentArea.innerHTML = `
Failed to load summaries: ${err.message}
`; + } +} + // Store task JSON data in a global map instead of inline script tags const taskJsonStore = {}; @@ -2000,13 +2403,38 @@ function toggleSection(header) { const implId = parts.pop(); const sessionId = parts.join('-'); - const session = [...(workflowData.liteTasks?.litePlan || []), ...(workflowData.liteTasks?.liteFix || [])] - .find(s => s.id === sessionId); - const task = session?.tasks?.find(t => t.id === implId || t.id === 'IMPL-' + implId); + // Try to find task from multiple sources + let task = null; - if (task?.flow_control?.implementation_approach) { - renderFlowchart(container.id, task.flow_control.implementation_approach); - container.dataset.rendered = 'true'; + // 1. Try liteTaskDataStore (for lite task detail page) + if (currentSessionDetailKey && liteTaskDataStore[currentSessionDetailKey]) { + const session = liteTaskDataStore[currentSessionDetailKey]; + task = session?.tasks?.find(t => t.id === implId || t.id === 'IMPL-' + implId || t.id === 'T' + implId); + } + + // 2. Try workflowData.liteTasks (for lite tasks list view) + if (!task) { + const session = [...(workflowData.liteTasks?.litePlan || []), ...(workflowData.liteTasks?.liteFix || [])] + .find(s => s.id === sessionId); + task = session?.tasks?.find(t => t.id === implId || t.id === 'IMPL-' + implId || t.id === 'T' + implId); + } + + // 3. Try sessionDataStore (for regular session detail page) + if (!task && currentSessionDetailKey && sessionDataStore[currentSessionDetailKey]) { + const session = sessionDataStore[currentSessionDetailKey]; + task = session?.tasks?.find(t => (t.task_id || t.id) === implId || (t.task_id || t.id) === 'IMPL-' + implId); + } + + // Render flowchart if task found + if (task) { + const flowSteps = task.flow_control?.implementation_approach || + task._raw?.flow_control?.implementation_approach || + task._raw?.implementation || + task.implementation; + if (flowSteps && flowSteps.length > 0) { + renderFlowchart(container.id, flowSteps); + container.dataset.rendered = 'true'; + } } } } @@ -2050,7 +2478,7 @@ function renderFlowchart(containerId, steps) { .attr('orient', 'auto') .append('path') .attr('d', 'M0,-5L10,0L0,5') - .attr('fill', 'var(--border-color)'); + .attr('fill', 'hsl(var(--border))'); // Draw arrows for (let i = 0; i < steps.length - 1; i++) { @@ -2062,7 +2490,7 @@ function renderFlowchart(containerId, steps) { .attr('y1', y1) .attr('x2', width / 2) .attr('y2', y2) - .attr('stroke', 'var(--border-color)') + .attr('stroke', 'hsl(var(--border))') .attr('stroke-width', 2) .attr('marker-end', 'url(#arrow-' + containerId + ')'); } @@ -2080,8 +2508,8 @@ function renderFlowchart(containerId, steps) { .attr('width', nodeWidth) .attr('height', nodeHeight) .attr('rx', 6) - .attr('fill', (d, i) => i === 0 ? 'var(--accent-color)' : 'var(--bg-card)') - .attr('stroke', 'var(--border-color)') + .attr('fill', (d, i) => i === 0 ? 'hsl(var(--primary))' : 'hsl(var(--card))') + .attr('stroke', 'hsl(var(--border))') .attr('stroke-width', 1); // Step number circle @@ -2089,7 +2517,7 @@ function renderFlowchart(containerId, steps) { .attr('cx', 20) .attr('cy', nodeHeight / 2) .attr('r', 12) - .attr('fill', (d, i) => i === 0 ? 'rgba(255,255,255,0.2)' : 'var(--bg-secondary)'); + .attr('fill', (d, i) => i === 0 ? 'rgba(255,255,255,0.2)' : 'hsl(var(--muted))'); nodes.append('text') .attr('x', 20) @@ -2097,7 +2525,7 @@ function renderFlowchart(containerId, steps) { .attr('text-anchor', 'middle') .attr('dominant-baseline', 'central') .attr('font-size', '11px') - .attr('fill', (d, i) => i === 0 ? 'white' : 'var(--text-secondary)') + .attr('fill', (d, i) => i === 0 ? 'white' : 'hsl(var(--muted-foreground))') .text((d, i) => i + 1); // Node text (step name) @@ -2105,7 +2533,7 @@ function renderFlowchart(containerId, steps) { .attr('x', 45) .attr('y', nodeHeight / 2) .attr('dominant-baseline', 'central') - .attr('fill', (d, i) => i === 0 ? 'white' : 'var(--text-primary)') + .attr('fill', (d, i) => i === 0 ? 'white' : 'hsl(var(--foreground))') .attr('font-size', '12px') .text(d => { const text = d.step || d.action || 'Step'; @@ -2434,7 +2862,7 @@ function renderFullFlowchart(flowControl) { .attr('orient', 'auto') .append('path') .attr('d', 'M0,-5L10,0L0,5') - .attr('fill', 'var(--accent-color)'); + .attr('fill', 'hsl(var(--primary))'); let currentY = 20; @@ -2476,7 +2904,7 @@ function renderFullFlowchart(flowControl) { .attr('width', nodeWidth) .attr('height', nodeHeight) .attr('rx', 10) - .attr('fill', 'var(--bg-card)') + .attr('fill', 'hsl(var(--card))') .attr('stroke', '#f59e0b') .attr('stroke-width', 2) .attr('stroke-dasharray', '5,3'); @@ -2502,7 +2930,7 @@ function renderFullFlowchart(flowControl) { nodeG.append('text') .attr('x', 50) .attr('y', 28) - .attr('fill', 'var(--text-primary)') + .attr('fill', 'hsl(var(--foreground))') .attr('font-weight', '600') .attr('font-size', '13px') .text(truncateText(stepName, 40)); @@ -2512,7 +2940,7 @@ function renderFullFlowchart(flowControl) { nodeG.append('text') .attr('x', 15) .attr('y', 52) - .attr('fill', 'var(--text-secondary)') + .attr('fill', 'hsl(var(--muted-foreground))') .attr('font-size', '11px') .text(truncateText(step.action, 50)); } @@ -2539,7 +2967,7 @@ function renderFullFlowchart(flowControl) { .attr('y1', currentY) .attr('x2', width - 40) .attr('y2', currentY) - .attr('stroke', 'var(--border-color)') + .attr('stroke', 'hsl(var(--border))') .attr('stroke-width', 1) .attr('stroke-dasharray', '4,4'); @@ -2549,7 +2977,7 @@ function renderFullFlowchart(flowControl) { .attr('y1', currentY - nodeGap + 5) .attr('x2', width / 2) .attr('y2', currentY + sectionGap - 5) - .attr('stroke', 'var(--accent-color)') + .attr('stroke', 'hsl(var(--primary))') .attr('stroke-width', 2) .attr('marker-end', 'url(#arrowhead-impl)'); @@ -2562,7 +2990,7 @@ function renderFullFlowchart(flowControl) { svg.append('text') .attr('x', 20) .attr('y', currentY) - .attr('fill', 'var(--accent-color)') + .attr('fill', 'hsl(var(--primary))') .attr('font-weight', 'bold') .attr('font-size', '13px') .text('🔧 Implementation Steps'); @@ -2579,7 +3007,7 @@ function renderFullFlowchart(flowControl) { .attr('y1', currentY + nodeHeight) .attr('x2', width / 2) .attr('y2', currentY + nodeHeight + nodeGap - 10) - .attr('stroke', 'var(--accent-color)') + .attr('stroke', 'hsl(var(--primary))') .attr('stroke-width', 2) .attr('marker-end', 'url(#arrowhead-impl)'); } @@ -2594,8 +3022,8 @@ function renderFullFlowchart(flowControl) { .attr('width', nodeWidth) .attr('height', nodeHeight) .attr('rx', 10) - .attr('fill', 'var(--bg-card)') - .attr('stroke', 'var(--accent-color)') + .attr('fill', 'hsl(var(--card))') + .attr('stroke', 'hsl(var(--primary))') .attr('stroke-width', 2); // Step badge @@ -2603,7 +3031,7 @@ function renderFullFlowchart(flowControl) { .attr('cx', 25) .attr('cy', 25) .attr('r', 15) - .attr('fill', 'var(--accent-color)'); + .attr('fill', 'hsl(var(--primary))'); nodeG.append('text') .attr('x', 25) @@ -2618,7 +3046,7 @@ function renderFullFlowchart(flowControl) { nodeG.append('text') .attr('x', 50) .attr('y', 28) - .attr('fill', 'var(--text-primary)') + .attr('fill', 'hsl(var(--foreground))') .attr('font-weight', '600') .attr('font-size', '13px') .text(truncateText(step.title || 'Step ' + (idx + 1), 40)); @@ -2628,7 +3056,7 @@ function renderFullFlowchart(flowControl) { nodeG.append('text') .attr('x', 15) .attr('y', 52) - .attr('fill', 'var(--text-secondary)') + .attr('fill', 'hsl(var(--muted-foreground))') .attr('font-size', '11px') .text(truncateText(step.description, 50)); } @@ -2681,7 +3109,7 @@ function renderImplementationFlowchart(steps) { .attr('orient', 'auto') .append('path') .attr('d', 'M0,-5L10,0L0,5') - .attr('fill', 'var(--accent-color)'); + .attr('fill', 'hsl(var(--primary))'); // Draw nodes and connections steps.forEach((step, idx) => { @@ -2695,7 +3123,7 @@ function renderImplementationFlowchart(steps) { .attr('y1', y + nodeHeight) .attr('x2', width / 2) .attr('y2', y + nodeHeight + nodeGap - 10) - .attr('stroke', 'var(--accent-color)') + .attr('stroke', 'hsl(var(--primary))') .attr('stroke-width', 2) .attr('marker-end', 'url(#arrowhead)'); } @@ -2710,8 +3138,8 @@ function renderImplementationFlowchart(steps) { .attr('width', nodeWidth) .attr('height', nodeHeight) .attr('rx', 10) - .attr('fill', 'var(--bg-card)') - .attr('stroke', 'var(--accent-color)') + .attr('fill', 'hsl(var(--card))') + .attr('stroke', 'hsl(var(--primary))') .attr('stroke-width', 2) .attr('filter', 'drop-shadow(0 2px 4px rgba(0,0,0,0.1))'); @@ -2720,7 +3148,7 @@ function renderImplementationFlowchart(steps) { .attr('cx', 25) .attr('cy', 25) .attr('r', 15) - .attr('fill', 'var(--accent-color)'); + .attr('fill', 'hsl(var(--primary))'); nodeG.append('text') .attr('x', 25) @@ -2735,7 +3163,7 @@ function renderImplementationFlowchart(steps) { nodeG.append('text') .attr('x', 50) .attr('y', 30) - .attr('fill', 'var(--text-primary)') + .attr('fill', 'hsl(var(--foreground))') .attr('font-weight', '600') .attr('font-size', '14px') .text(truncateText(step.title || 'Step ' + (idx + 1), 35)); @@ -2745,7 +3173,7 @@ function renderImplementationFlowchart(steps) { nodeG.append('text') .attr('x', 15) .attr('y', 55) - .attr('fill', 'var(--text-secondary)') + .attr('fill', 'hsl(var(--muted-foreground))') .attr('font-size', '12px') .text(truncateText(step.description, 45)); }