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 `
-
+
` : ''}
- ${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 = `
+
+
+
+
+
+
+
+ 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 `
+
+
+
+
+
+
+
+ ${renderDynamicFields(task.meta || rawTask, ['type', 'action', 'agent', 'scope', 'module'])}
+
+
+
+
+
+
+
+ ${renderContextFields(task.context, rawTask)}
+
+
+
+
+
+
+
+
+ ${renderFlowControlDetails(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('')}
+
+
+ ` : ''}
+
+
+
+
+
+
+
{ } 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));
}