// ============================================
// LITE TASKS VIEW
// ============================================
// Lite-plan and lite-fix task list and detail rendering
function renderLiteTasks() {
const container = document.getElementById('mainContent');
const liteTasks = workflowData.liteTasks || {};
let sessions;
if (currentLiteType === 'lite-plan') {
sessions = liteTasks.litePlan || [];
} else if (currentLiteType === 'lite-fix') {
sessions = liteTasks.liteFix || [];
} else if (currentLiteType === 'multi-cli-plan') {
sessions = liteTasks.multiCliPlan || [];
} else {
sessions = [];
}
if (sessions.length === 0) {
container.innerHTML = `
${t('empty.noLiteSessions', { type: currentLiteType })}
${t('empty.noLiteSessionsText', { type: currentLiteType })}
`;
if (typeof lucide !== 'undefined') lucide.createIcons();
return;
}
// Render based on type
if (currentLiteType === 'multi-cli-plan') {
container.innerHTML = `${sessions.map(session => renderMultiCliCard(session)).join('')}
`;
} else {
container.innerHTML = `${sessions.map(session => renderLiteTaskCard(session)).join('')}
`;
}
// Initialize Lucide icons
if (typeof lucide !== 'undefined') lucide.createIcons();
// Initialize collapsible sections
document.querySelectorAll('.collapsible-header').forEach(header => {
header.addEventListener('click', () => toggleSection(header));
});
// Render flowcharts for expanded tasks
sessions.forEach(session => {
session.tasks?.forEach(task => {
if (task.flow_control?.implementation_approach) {
renderFlowchartForTask(session.id, task);
}
});
});
}
function renderLiteTaskCard(session) {
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 `
${formatDate(session.createdAt)}
${tasks.length} ${t('session.tasks')}
`;
}
// ============================================
// MULTI-CLI PLAN VIEW
// ============================================
/**
* Render a card for multi-cli-plan session
* Shows: Session ID, round count, topic title, status, created date
*/
function renderMultiCliCard(session) {
const sessionKey = `multi-cli-${session.id}`.replace(/[^a-zA-Z0-9-]/g, '-');
liteTaskDataStore[sessionKey] = session;
// Extract info from latest synthesis or metadata
const metadata = session.metadata || {};
const latestSynthesis = session.latestSynthesis || session.discussionTopic || {};
const roundCount = metadata.roundId || session.roundCount || 1;
const topicTitle = getI18nText(latestSynthesis.title) || session.topicTitle || 'Discussion Topic';
const status = latestSynthesis.status || session.status || 'analyzing';
const createdAt = metadata.timestamp || session.createdAt || '';
// Status badge color mapping
const statusColors = {
'decided': 'success',
'converged': 'success',
'plan_generated': 'success',
'completed': 'success',
'exploring': 'info',
'initialized': 'info',
'analyzing': 'warning',
'debating': 'warning',
'blocked': 'error',
'conflict': 'error'
};
const statusColor = statusColors[status] || 'default';
return `
${escapeHtml(topicTitle)}
${formatDate(createdAt)}
${roundCount} ${t('multiCli.rounds') || 'rounds'}
${escapeHtml(status)}
`;
}
/**
* Get text from i18n label object (supports {en, zh} format)
*/
function getI18nText(label) {
if (!label) return '';
if (typeof label === 'string') return label;
// Return based on current language or default to English
const lang = window.currentLanguage || 'en';
return label[lang] || label.en || label.zh || '';
}
// ============================================
// SYNTHESIS DATA TRANSFORMATION HELPERS
// ============================================
/**
* Extract files from synthesis solutions[].implementation_plan.tasks[].files
* Returns object with fileTree and impactSummary arrays
*/
function extractFilesFromSynthesis(synthesis) {
if (!synthesis || !synthesis.solutions) {
return { fileTree: [], impactSummary: [], dependencyGraph: [] };
}
const fileSet = new Set();
const impactMap = new Map();
synthesis.solutions.forEach(solution => {
const tasks = solution.implementation_plan?.tasks || [];
tasks.forEach(task => {
const files = task.files || [];
files.forEach(filePath => {
fileSet.add(filePath);
// Build impact summary based on task context
if (!impactMap.has(filePath)) {
impactMap.set(filePath, {
filePath: filePath,
score: 'medium',
reasoning: { en: `Part of ${solution.title?.en || solution.id} implementation`, zh: `${solution.title?.zh || solution.id} 实现的一部分` }
});
}
});
});
});
// Convert to fileTree format (flat list with file type)
const fileTree = Array.from(fileSet).map(path => ({
path: path,
type: 'file',
modificationStatus: 'modified',
impactScore: 'medium'
}));
return {
fileTree: fileTree,
impactSummary: Array.from(impactMap.values()),
dependencyGraph: []
};
}
/**
* Extract planning data from synthesis solutions[].implementation_plan
* Builds planning object with functional requirements format
*/
function extractPlanningFromSynthesis(synthesis) {
if (!synthesis || !synthesis.solutions) {
return { functional: [], nonFunctional: [], acceptanceCriteria: [] };
}
const functional = [];
const acceptanceCriteria = [];
let reqCounter = 1;
let acCounter = 1;
synthesis.solutions.forEach(solution => {
const plan = solution.implementation_plan;
if (!plan) return;
// Extract approach as functional requirement
if (plan.approach) {
functional.push({
id: `FR-${String(reqCounter++).padStart(3, '0')}`,
description: plan.approach,
priority: solution.feasibility?.score >= 0.8 ? 'high' : 'medium',
source: solution.id
});
}
// Extract tasks as acceptance criteria
const tasks = plan.tasks || [];
tasks.forEach(task => {
acceptanceCriteria.push({
id: `AC-${String(acCounter++).padStart(3, '0')}`,
description: task.title || { en: task.id, zh: task.id },
isMet: false
});
});
});
return {
functional: functional,
nonFunctional: [],
acceptanceCriteria: acceptanceCriteria
};
}
/**
* Extract decision data from synthesis solutions
* Sorts by feasibility score, returns highest as selected, rest as rejected
*/
function extractDecisionFromSynthesis(synthesis) {
if (!synthesis || !synthesis.solutions || synthesis.solutions.length === 0) {
return {};
}
// Sort solutions by feasibility score (highest first)
const sortedSolutions = [...synthesis.solutions].sort((a, b) => {
const scoreA = a.feasibility?.score || 0;
const scoreB = b.feasibility?.score || 0;
return scoreB - scoreA;
});
const selectedSolution = sortedSolutions[0];
const rejectedAlternatives = sortedSolutions.slice(1).map(sol => ({
...sol,
rejectionReason: sol.cons?.length > 0 ? sol.cons[0] : { en: 'Lower feasibility score', zh: '可行性评分较低' }
}));
// Calculate confidence from convergence level
let confidenceScore = 0.5;
if (synthesis.convergence) {
const level = synthesis.convergence.level;
if (level === 'high' || level === 'converged') confidenceScore = 0.9;
else if (level === 'medium') confidenceScore = 0.7;
else if (level === 'low') confidenceScore = 0.4;
}
return {
status: synthesis.convergence?.recommendation || 'pending',
summary: synthesis.convergence?.summary || {},
selectedSolution: selectedSolution,
rejectedAlternatives: rejectedAlternatives,
confidenceScore: confidenceScore
};
}
/**
* Extract timeline data from synthesis convergence and cross_verification
* Builds timeline array with events from discussion process
*/
function extractTimelineFromSynthesis(synthesis) {
if (!synthesis) {
return [];
}
const timeline = [];
const now = new Date().toISOString();
// Add convergence summary as decision event
if (synthesis.convergence?.summary) {
timeline.push({
type: 'decision',
timestamp: now,
summary: synthesis.convergence.summary,
contributor: { name: 'Synthesis', id: 'synthesis' },
reversibility: synthesis.convergence.recommendation === 'proceed' ? 'irreversible' : 'reversible'
});
}
// Add cross-verification agreements as agreement events
const agreements = synthesis.cross_verification?.agreements || [];
agreements.forEach((agreement, idx) => {
timeline.push({
type: 'agreement',
timestamp: now,
summary: typeof agreement === 'string' ? { en: agreement, zh: agreement } : agreement,
contributor: { name: 'Cross-Verification', id: 'cross-verify' },
evidence: []
});
});
// Add cross-verification disagreements as disagreement events
const disagreements = synthesis.cross_verification?.disagreements || [];
disagreements.forEach((disagreement, idx) => {
timeline.push({
type: 'disagreement',
timestamp: now,
summary: typeof disagreement === 'string' ? { en: disagreement, zh: disagreement } : disagreement,
contributor: { name: 'Cross-Verification', id: 'cross-verify' },
evidence: []
});
});
// Add solutions as proposal events
const solutions = synthesis.solutions || [];
solutions.forEach(solution => {
timeline.push({
type: 'proposal',
timestamp: now,
summary: solution.description || solution.title || {},
contributor: { name: solution.id, id: solution.id },
evidence: solution.pros?.map(p => ({ type: 'pro', description: p })) || []
});
});
return timeline;
}
/**
* Show multi-cli detail page with tabs (same layout as lite-plan)
*/
function showMultiCliDetailPage(sessionKey) {
const session = liteTaskDataStore[sessionKey];
if (!session) return;
currentView = 'multiCliDetail';
currentSessionDetailKey = sessionKey;
hideStatsAndCarousel();
const container = document.getElementById('mainContent');
const metadata = session.metadata || {};
const plan = session.plan || {};
// Use session.tasks (normalized from backend) with fallback to plan.tasks
const tasks = session.tasks?.length > 0 ? session.tasks : (plan.tasks || []);
const roundCount = metadata.roundId || session.roundCount || 1;
const status = session.status || 'analyzing';
container.innerHTML = `
${t('detail.created') || 'Created'}
${formatDate(metadata.timestamp || session.createdAt)}
${t('detail.tasks') || 'Tasks'}
${tasks.length} ${t('session.tasks') || 'tasks'}
${t('tab.tasks') || 'Tasks'}
${tasks.length}
${t('multiCli.tab.discussion') || 'Discussion'}
${roundCount}
${t('tab.context') || 'Context'}
${t('tab.summary') || 'Summary'}
${renderMultiCliTasksTab(session)}
`;
// Initialize icons, collapsible sections, and task click handlers
setTimeout(() => {
if (typeof lucide !== 'undefined') lucide.createIcons();
initCollapsibleSections(container);
initMultiCliTaskClickHandlers();
}, 50);
}
/**
* Render the multi-cli toolbar content
*/
function renderMultiCliToolbar(session) {
const plan = session.plan;
// Use session.tasks (normalized from backend) with fallback to plan.tasks
const tasks = session.tasks?.length > 0 ? session.tasks : (plan?.tasks || []);
const taskCount = tasks.length;
let toolbarHtml = `
`;
// Quick Actions
toolbarHtml += `
`;
// Task List
if (tasks.length > 0) {
toolbarHtml += `
`;
} else {
toolbarHtml += `
${t('multiCli.toolbar.noTasks') || 'No tasks available'}
`;
}
// Session Info
toolbarHtml += `
`;
return toolbarHtml;
}
/**
* Scroll to a specific task in the planning tab
*/
function scrollToMultiCliTask(taskIdx) {
// Switch to planning tab if not active
const planningTab = document.querySelector('.detail-tab[data-tab="planning"]');
if (planningTab && !planningTab.classList.contains('active')) {
switchMultiCliDetailTab('planning');
// Wait for tab content to render
setTimeout(() => scrollToTaskElement(taskIdx), 100);
} else {
scrollToTaskElement(taskIdx);
}
}
/**
* Open task drawer from toolbar (wrapper for openTaskDrawerForMultiCli)
*/
function openToolbarTaskDrawer(sessionId, taskId) {
openTaskDrawerForMultiCli(sessionId, taskId);
}
/**
* Scroll to task element in the DOM
*/
function scrollToTaskElement(taskIdx) {
const taskItems = document.querySelectorAll('.fix-task-summary-item');
if (taskItems[taskIdx]) {
taskItems[taskIdx].scrollIntoView({ behavior: 'smooth', block: 'center' });
// Highlight the task briefly
taskItems[taskIdx].classList.add('toolbar-highlight');
setTimeout(() => {
taskItems[taskIdx].classList.remove('toolbar-highlight');
}, 2000);
// Expand the collapsible if collapsed
const header = taskItems[taskIdx].querySelector('.collapsible-header');
const content = taskItems[taskIdx].querySelector('.collapsible-content');
if (header && content && content.classList.contains('collapsed')) {
header.click();
}
}
}
/**
* Refresh the toolbar content
*/
function refreshMultiCliToolbar() {
const session = liteTaskDataStore[currentSessionDetailKey];
if (!session) return;
const toolbarContent = document.querySelector('.toolbar-content');
if (toolbarContent) {
toolbarContent.innerHTML = renderMultiCliToolbar(session);
if (typeof lucide !== 'undefined') lucide.createIcons();
}
}
/**
* Export plan.json content
*/
function exportMultiCliPlanJson() {
const session = liteTaskDataStore[currentSessionDetailKey];
if (!session || !session.plan) {
if (typeof showRefreshToast === 'function') {
showRefreshToast(t('multiCli.toolbar.noPlan') || 'No plan data available', 'warning');
}
return;
}
const jsonStr = JSON.stringify(session.plan, null, 2);
const blob = new Blob([jsonStr], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `plan-${session.id}.json`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
if (typeof showRefreshToast === 'function') {
showRefreshToast(t('multiCli.toolbar.exported') || 'Plan exported successfully', 'success');
}
}
/**
* View raw session JSON in modal
*/
function viewMultiCliRawJson() {
const session = liteTaskDataStore[currentSessionDetailKey];
if (!session) return;
// Reuse existing JSON modal pattern
const overlay = document.createElement('div');
overlay.className = 'json-modal-overlay active';
overlay.innerHTML = `
${escapeHtml(JSON.stringify(session, null, 2))}
`;
document.body.appendChild(overlay);
}
/**
* Switch between multi-cli detail tabs
*/
function switchMultiCliDetailTab(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('multiCliDetailTabContent');
switch (tabName) {
case 'tasks':
contentArea.innerHTML = renderMultiCliTasksTab(session);
break;
case 'discussion':
contentArea.innerHTML = renderMultiCliDiscussionSection(session);
break;
case 'context':
loadAndRenderMultiCliContextTab(session, contentArea);
return; // Early return as this is async
case 'summary':
loadAndRenderMultiCliSummaryTab(session, contentArea);
return; // Early return as this is async
}
// Re-initialize after tab switch
setTimeout(() => {
if (typeof lucide !== 'undefined') lucide.createIcons();
initCollapsibleSections(contentArea);
// Initialize task click handlers for tasks tab
if (tabName === 'tasks') {
initMultiCliTaskClickHandlers();
}
}, 50);
}
// ============================================
// MULTI-CLI TAB RENDERERS
// ============================================
/**
* Render Tasks tab - displays plan summary + tasks (same style as lite-plan)
* Uses session.tasks (normalized tasks) with fallback to session.plan.tasks
*/
function renderMultiCliTasksTab(session) {
const plan = session.plan || {};
// Use session.tasks (normalized from backend) with fallback to plan.tasks
const tasks = session.tasks?.length > 0 ? session.tasks : (plan.tasks || []);
// Populate drawer tasks for click-to-open functionality
currentDrawerTasks = tasks;
let sections = [];
// Extract plan info from multiple sources (plan.json, synthesis, or session)
// plan.json: task_description, solution.name, execution_flow
// synthesis: solutions[].summary, solutions[].implementation_plan.approach
const taskDescription = plan.task_description || session.topicTitle || '';
const solutionName = plan.solution?.name || (plan.solutions?.[0]?.name) || '';
const solutionSummary = plan.solutions?.[0]?.summary || '';
const approach = plan.solutions?.[0]?.implementation_plan?.approach || plan.execution_flow || '';
const feasibility = plan.solution?.feasibility || plan.solutions?.[0]?.feasibility;
const effort = plan.solution?.effort || plan.solutions?.[0]?.effort || '';
const risk = plan.solution?.risk || plan.solutions?.[0]?.risk || '';
// Plan Summary Section (if any info available)
const hasInfo = taskDescription || solutionName || solutionSummary || approach || plan.summary;
if (hasInfo) {
let planInfo = [];
// Task description (main objective)
if (taskDescription) {
planInfo.push(`${t('multiCli.plan.objective')}: ${escapeHtml(taskDescription)}
`);
}
// Solution name and summary
if (solutionName) {
planInfo.push(`${t('multiCli.plan.solution')}: ${escapeHtml(solutionName)}
`);
}
if (solutionSummary) {
planInfo.push(`${escapeHtml(solutionSummary)}
`);
}
// Legacy summary field
if (plan.summary && !taskDescription && !solutionSummary) {
planInfo.push(`${escapeHtml(plan.summary)}
`);
}
// Approach/execution flow
if (approach) {
planInfo.push(`${t('multiCli.plan.approach')}: ${escapeHtml(approach)}
`);
}
// Metadata badges - concise format
let metaBadges = [];
if (feasibility) metaBadges.push(`${Math.round(feasibility * 100)}% `);
if (effort) metaBadges.push(`${escapeHtml(effort)} `);
if (risk) metaBadges.push(`${escapeHtml(risk)} ${t('multiCli.plan.risk')} `);
// Legacy badges
if (plan.severity) metaBadges.push(`${escapeHtml(plan.severity)} `);
if (plan.complexity) metaBadges.push(`${escapeHtml(plan.complexity)} `);
if (plan.estimated_time) metaBadges.push(`${escapeHtml(plan.estimated_time)} `);
sections.push(`
${planInfo.join('')}
${metaBadges.length ? `
${metaBadges.join(' ')}
` : ''}
`);
}
// Tasks Section
if (tasks.length === 0) {
sections.push(`
${t('empty.noTasks') || 'No Tasks'}
${t('empty.noTasksText') || 'No tasks available for this session.'}
`);
} else {
sections.push(`
${tasks.map((task, idx) => renderMultiCliTaskItem(session.id, task, idx)).join('')}
`);
}
return `${sections.join('')}
`;
}
/**
* Render Plan tab - displays plan.json content (summary, approach, metadata)
*/
function renderMultiCliPlanTab(session) {
const plan = session.plan;
if (!plan) {
return `
${t('multiCli.empty.planning') || 'No Plan Data'}
${t('multiCli.empty.planningText') || 'No plan.json found for this session.'}
`;
}
return `
${plan.summary ? `
Summary
${escapeHtml(plan.summary)}
` : ''}
${plan.root_cause ? `
Root Cause
${escapeHtml(plan.root_cause)}
` : ''}
${plan.strategy ? `
Fix Strategy
${escapeHtml(plan.strategy)}
` : ''}
${plan.approach ? `
Approach
${escapeHtml(plan.approach)}
` : ''}
${plan.user_requirements ? `
User Requirements
${escapeHtml(plan.user_requirements)}
` : ''}
${plan.focus_paths?.length ? `
Focus Paths
${plan.focus_paths.map(p => `${escapeHtml(p)} `).join('')}
` : ''}
${escapeHtml(JSON.stringify(plan, null, 2))}
`;
}
/**
* Load and render Context tab - displays context-package content
*/
async function loadAndRenderMultiCliContextTab(session, contentArea) {
contentArea.innerHTML = `${t('common.loading') || 'Loading...'}
`;
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 = renderMultiCliContextContent(data.context, session);
initCollapsibleSections(contentArea);
if (typeof lucide !== 'undefined') lucide.createIcons();
return;
}
}
// Fallback: show session context_package if available
contentArea.innerHTML = renderMultiCliContextContent(session.context_package, session);
initCollapsibleSections(contentArea);
if (typeof lucide !== 'undefined') lucide.createIcons();
} catch (err) {
contentArea.innerHTML = `Failed to load context: ${err.message}
`;
}
}
/**
* Render context content for multi-cli
*/
function renderMultiCliContextContent(context, session) {
// Also check for context_package in session
const ctx = context || session.context_package || {};
if (!ctx || Object.keys(ctx).length === 0) {
return `
${t('empty.noContext') || 'No Context Data'}
${t('empty.noContextText') || 'No context package available for this session.'}
`;
}
let sections = [];
// Task Description
if (ctx.task_description) {
sections.push(`
${t('multiCli.context.taskDescription')}
${escapeHtml(ctx.task_description)}
`);
}
// Constraints
if (ctx.constraints?.length) {
sections.push(`
${t('multiCli.context.constraints')}
${ctx.constraints.map(c => `${escapeHtml(c)} `).join('')}
`);
}
// Focus Paths
if (ctx.focus_paths?.length) {
sections.push(`
${t('multiCli.context.focusPaths')}
${ctx.focus_paths.map(p => `${escapeHtml(p)} `).join('')}
`);
}
// Relevant Files
if (ctx.relevant_files?.length) {
sections.push(`
${ctx.relevant_files.map(f => `
📄
${escapeHtml(typeof f === 'string' ? f : f.path || f.file || '')}
${f.reason ? `${escapeHtml(f.reason)} ` : ''}
`).join('')}
`);
}
// Dependencies
if (ctx.dependencies?.length) {
sections.push(`
${ctx.dependencies.map(d => `${escapeHtml(typeof d === 'string' ? d : d.name || JSON.stringify(d))} `).join('')}
`);
}
// Conflict Risks
if (ctx.conflict_risks?.length) {
sections.push(`
${t('multiCli.context.conflictRisks')}
${ctx.conflict_risks.map(r => `${escapeHtml(typeof r === 'string' ? r : r.description || JSON.stringify(r))} `).join('')}
`);
}
// Session ID
if (ctx.session_id) {
sections.push(`
${t('multiCli.context.sessionId')}
${escapeHtml(ctx.session_id)}
`);
}
// Raw JSON
sections.push(`
${escapeHtml(JSON.stringify(ctx, null, 2))}
`);
return `${sections.join('')}
`;
}
/**
* Load and render Summary tab
*/
async function loadAndRenderMultiCliSummaryTab(session, contentArea) {
contentArea.innerHTML = `${t('common.loading') || 'Loading...'}
`;
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 = renderMultiCliSummaryContent(data.summary, session);
initCollapsibleSections(contentArea);
if (typeof lucide !== 'undefined') lucide.createIcons();
return;
}
}
// Fallback: show synthesis summary
contentArea.innerHTML = renderMultiCliSummaryContent(null, session);
initCollapsibleSections(contentArea);
if (typeof lucide !== 'undefined') lucide.createIcons();
} catch (err) {
contentArea.innerHTML = `Failed to load summary: ${err.message}
`;
}
}
/**
* Render summary content for multi-cli
*/
function renderMultiCliSummaryContent(summary, session) {
const synthesis = session.latestSynthesis || session.discussionTopic || {};
const plan = session.plan || {};
// Use summary from file or build from synthesis
const summaryText = summary || synthesis.convergence?.summary || plan.summary;
if (!summaryText && !synthesis.solutions?.length) {
return `
${t('empty.noSummary') || 'No Summary'}
${t('empty.noSummaryText') || 'No summary available for this session.'}
`;
}
let sections = [];
// Main Summary
if (summaryText) {
const summaryContent = typeof summaryText === 'string' ? summaryText : getI18nText(summaryText);
sections.push(`
${t('multiCli.summary.title')}
${escapeHtml(summaryContent)}
`);
}
// Convergence Status
if (synthesis.convergence) {
const conv = synthesis.convergence;
sections.push(`
${t('multiCli.summary.convergence')}
${escapeHtml(conv.level || 'unknown')}
${escapeHtml(conv.recommendation || '')}
`);
}
// Solutions Summary
if (synthesis.solutions?.length) {
sections.push(`
${synthesis.solutions.map((sol, idx) => `
#${idx + 1}
${escapeHtml(getI18nText(sol.title) || sol.id || `${t('multiCli.summary.solution')} ${idx + 1}`)}
${sol.feasibility?.score ? `${Math.round(sol.feasibility.score * 100)}% ` : ''}
`).join('')}
`);
}
return `${sections.join('')}
`;
}
/**
* Render Discussion Topic tab
* Shows: title, description, scope, keyQuestions, status, tags
*/
function renderMultiCliTopicTab(session) {
const topic = session.discussionTopic || session.latestSynthesis?.discussionTopic || {};
if (!topic || Object.keys(topic).length === 0) {
return `
${t('multiCli.empty.topic') || 'No Discussion Topic'}
${t('multiCli.empty.topicText') || 'No discussion topic data available for this session.'}
`;
}
const title = getI18nText(topic.title) || 'Untitled';
const description = getI18nText(topic.description) || '';
const scope = topic.scope || {};
const keyQuestions = topic.keyQuestions || [];
const status = topic.status || 'unknown';
const tags = topic.tags || [];
let sections = [];
// Title and Description
sections.push(`
${escapeHtml(title)}
${description ? `
${escapeHtml(description)}
` : ''}
${escapeHtml(status)}
${tags.length ? tags.map(tag => `${escapeHtml(tag)} `).join('') : ''}
`);
// Scope (included/excluded)
if (scope.included?.length || scope.excluded?.length) {
sections.push(`
${t('multiCli.scope') || 'Scope'}
${scope.included?.length ? `
${t('multiCli.scope.included') || 'Included'}:
${scope.included.map(item => `${escapeHtml(getI18nText(item))} `).join('')}
` : ''}
${scope.excluded?.length ? `
${t('multiCli.scope.excluded') || 'Excluded'}:
${scope.excluded.map(item => `${escapeHtml(getI18nText(item))} `).join('')}
` : ''}
`);
}
// Key Questions
if (keyQuestions.length) {
sections.push(`
${t('multiCli.keyQuestions') || 'Key Questions'}
${keyQuestions.map(q => `${escapeHtml(getI18nText(q))} `).join('')}
`);
}
return `${sections.join('')}
`;
}
/**
* Render Related Files tab
* Shows: fileTree, impactSummary
*/
function renderMultiCliFilesTab(session) {
// Use helper to extract files from synthesis data structure
const relatedFiles = extractFilesFromSynthesis(session.latestSynthesis);
if (!relatedFiles || (!relatedFiles.fileTree?.length && !relatedFiles.impactSummary?.length)) {
return `
${t('multiCli.empty.files') || 'No Related Files'}
${t('multiCli.empty.filesText') || 'No file analysis data available for this session.'}
`;
}
const fileTree = relatedFiles.fileTree || [];
const impactSummary = relatedFiles.impactSummary || [];
const dependencyGraph = relatedFiles.dependencyGraph || [];
let sections = [];
// File Tree
if (fileTree.length) {
sections.push(`
${renderFileTreeNodes(fileTree)}
`);
}
// Impact Summary
if (impactSummary.length) {
sections.push(`
${impactSummary.map(impact => `
${impact.reasoning ? `
${escapeHtml(getI18nText(impact.reasoning))}
` : ''}
`).join('')}
`);
}
// Dependency Graph
if (dependencyGraph.length) {
sections.push(`
${dependencyGraph.map(edge => `
${escapeHtml(edge.source || '')}
→
${escapeHtml(edge.target || '')}
(${escapeHtml(edge.relationship || 'depends')})
`).join('')}
`);
}
return sections.length ? `${sections.join('')}
` : `
${t('multiCli.empty.files') || 'No Related Files'}
`;
}
/**
* Render file tree nodes recursively
*/
function renderFileTreeNodes(nodes, depth = 0) {
return nodes.map(node => {
const indent = depth * 16;
const isDir = node.type === 'directory';
const icon = isDir ? 'folder' : 'file';
const modStatus = node.modificationStatus || 'unchanged';
const impactScore = node.impactScore || '';
let html = `
${escapeHtml(node.path || '')}
${modStatus !== 'unchanged' ? `${modStatus} ` : ''}
${impactScore ? `${impactScore} ` : ''}
`;
if (node.children?.length) {
html += renderFileTreeNodes(node.children, depth + 1);
}
return html;
}).join('');
}
/**
* Render Planning tab - displays session.plan (plan.json content)
* Reuses renderLitePlanTab style with Summary, Approach, Focus Paths, Metadata, and Tasks
*/
function renderMultiCliPlanningTab(session) {
const plan = session.plan;
if (!plan) {
return `
${t('multiCli.empty.planning') || 'No Planning Data'}
${t('multiCli.empty.planningText') || 'No plan.json found for this session.'}
`;
}
return `
${plan.summary ? `
Summary
${escapeHtml(plan.summary)}
` : ''}
${plan.root_cause ? `
Root Cause
${escapeHtml(plan.root_cause)}
` : ''}
${plan.strategy ? `
Fix Strategy
${escapeHtml(plan.strategy)}
` : ''}
${plan.approach ? `
Approach
${escapeHtml(plan.approach)}
` : ''}
${plan.user_requirements ? `
User Requirements
${escapeHtml(plan.user_requirements)}
` : ''}
${plan.focus_paths?.length ? `
Focus Paths
${plan.focus_paths.map(p => `${escapeHtml(p)} `).join('')}
` : ''}
${plan.tasks?.length ? `
Tasks (${plan.tasks.length})
${plan.tasks.map((task, idx) => renderMultiCliTaskItem(session.id, task, idx)).join('')}
` : ''}
${escapeHtml(JSON.stringify(plan, null, 2))}
`;
}
/**
* Render a single task item for multi-cli-plan (reuses lite-plan style)
* Supports click to open drawer for details
*/
function renderMultiCliTaskItem(sessionId, task, idx) {
const taskId = task.id || `T${idx + 1}`;
const taskJsonId = `multi-cli-task-${sessionId}-${taskId}`.replace(/[^a-zA-Z0-9-]/g, '-');
taskJsonStore[taskJsonId] = task;
// Get preview info - handle both normalized and raw formats
// Normalized: meta.type, meta.scope, context.focus_paths, context.acceptance, flow_control.implementation_approach
// Raw: action, scope, file, modification_points, implementation, acceptance
const taskType = task.meta?.type || task.action || '';
const scope = task.meta?.scope || task.scope || task.file || '';
const filesCount = task.context?.focus_paths?.length || task.files?.length || task.modification_points?.length || 0;
const implCount = task.flow_control?.implementation_approach?.length || task.implementation?.length || 0;
const acceptCount = task.context?.acceptance?.length || task.acceptance?.length || task.acceptance_criteria?.length || 0;
const dependsCount = task.context?.depends_on?.length || task.depends_on?.length || 0;
// Escape for data attributes
const safeSessionId = escapeHtml(sessionId);
const safeTaskId = escapeHtml(taskId);
return `
${taskType ? `${escapeHtml(taskType)} ` : ''}
${scope ? `${escapeHtml(scope)} ` : ''}
${filesCount > 0 ? `${filesCount} files ` : ''}
${implCount > 0 ? `${implCount} steps ` : ''}
${acceptCount > 0 ? `${acceptCount} criteria ` : ''}
${dependsCount > 0 ? `${dependsCount} deps ` : ''}
`;
}
/**
* Initialize click handlers for multi-cli task items
*/
function initMultiCliTaskClickHandlers() {
// Task item click handlers
document.querySelectorAll('.multi-cli-task-item').forEach(item => {
if (!item._clickBound) {
item._clickBound = true;
item.addEventListener('click', function(e) {
// Don't trigger if clicking on JSON button
if (e.target.closest('.btn-view-json')) return;
const sessionId = this.dataset.sessionId;
const taskId = this.dataset.taskId;
openTaskDrawerForMultiCli(sessionId, taskId);
});
}
});
// JSON button click handlers
document.querySelectorAll('.multi-cli-task-item .btn-view-json').forEach(btn => {
if (!btn._clickBound) {
btn._clickBound = true;
btn.addEventListener('click', function(e) {
e.stopPropagation();
const taskJsonId = this.dataset.taskJsonId;
const displayId = this.dataset.taskDisplayId;
showJsonModal(taskJsonId, displayId);
});
}
});
}
/**
* Open task drawer for multi-cli task
*/
function openTaskDrawerForMultiCli(sessionId, taskId) {
const session = liteTaskDataStore[currentSessionDetailKey];
if (!session) return;
// Use session.tasks (normalized from backend) with fallback to plan.tasks
const tasks = session.tasks?.length > 0 ? session.tasks : (session.plan?.tasks || []);
const task = tasks.find(t => (t.id || `T${tasks.indexOf(t) + 1}`) === taskId);
if (!task) return;
// Set current drawer tasks
currentDrawerTasks = tasks;
window._currentDrawerSession = session;
document.getElementById('drawerTaskTitle').textContent = task.title || task.name || task.summary || taskId;
document.getElementById('drawerContent').innerHTML = renderMultiCliTaskDrawerContent(task, session);
document.getElementById('taskDetailDrawer').classList.add('open');
document.getElementById('drawerOverlay').classList.add('active');
// Re-init lucide icons in drawer
setTimeout(() => {
if (typeof lucide !== 'undefined') lucide.createIcons();
}, 50);
}
/**
* Render drawer content for multi-cli task
*/
function renderMultiCliTaskDrawerContent(task, session) {
const taskId = task.id || 'Task';
const action = task.action || '';
return `
Overview
Implementation
Files
Raw JSON
${renderMultiCliTaskOverview(task)}
${renderMultiCliTaskImplementation(task)}
${renderMultiCliTaskFiles(task)}
${escapeHtml(JSON.stringify(task, null, 2))}
`;
}
/**
* Switch drawer tab for multi-cli task
*/
function switchMultiCliDrawerTab(tabName) {
document.querySelectorAll('.drawer-tab').forEach(tab => {
tab.classList.toggle('active', tab.dataset.tab === tabName);
});
document.querySelectorAll('.drawer-panel').forEach(panel => {
panel.classList.toggle('active', panel.dataset.tab === tabName);
});
}
/**
* Render multi-cli task overview section
* Handles both normalized format (meta, context, flow_control) and raw format
*/
function renderMultiCliTaskOverview(task) {
let sections = [];
// Extract from both normalized and raw formats
const description = task.description || (task.context?.requirements?.length > 0 ? task.context.requirements.join('\n') : '');
const scope = task.meta?.scope || task.scope || task.file || '';
const acceptance = task.context?.acceptance || task.acceptance || task.acceptance_criteria || [];
const dependsOn = task.context?.depends_on || task.depends_on || [];
const focusPaths = task.context?.focus_paths || task.files?.map(f => typeof f === 'string' ? f : f.file) || [];
const keyPoint = task._raw?.task?.key_point || task.key_point || '';
// Description/Key Point Card
if (description || keyPoint) {
sections.push(`
${keyPoint ? `
${t('multiCli.task.keyPoint')}: ${escapeHtml(keyPoint)}
` : ''}
${description ? `
${escapeHtml(description)}
` : ''}
`);
}
// Scope Card
if (scope) {
sections.push(`
`);
}
// Dependencies Card
if (dependsOn.length > 0) {
sections.push(`
${dependsOn.map(dep => `${escapeHtml(dep)} `).join('')}
`);
}
// Focus Paths / Files Card
if (focusPaths.length > 0) {
sections.push(`
${focusPaths.map(f => `${escapeHtml(f)} `).join('')}
`);
}
// Acceptance Criteria Card
if (acceptance.length > 0) {
sections.push(`
${acceptance.map(ac => `${escapeHtml(ac)} `).join('')}
`);
}
// Reference Card
if (task.reference) {
const ref = task.reference;
sections.push(`
${ref.pattern ? `
${t('multiCli.task.pattern')}: ${escapeHtml(ref.pattern)}
` : ''}
${ref.files?.length ? `
${t('multiCli.task.files')}: ${ref.files.map(f => escapeHtml(f)).join('\n')}
` : ''}
${ref.examples ? `
${t('multiCli.task.examples')}: ${escapeHtml(ref.examples)}
` : ''}
`);
}
return sections.length ? sections.join('') : `${t('multiCli.task.noOverviewData')}
`;
}
/**
* Render multi-cli task implementation section
* Handles both normalized format (flow_control.implementation_approach) and raw format
*/
function renderMultiCliTaskImplementation(task) {
let sections = [];
// Get implementation steps from normalized or raw format
const implApproach = task.flow_control?.implementation_approach || [];
const rawImpl = task.implementation || [];
const modPoints = task.modification_points || [];
// Modification Points / Flow Control Implementation Approach
if (implApproach.length > 0) {
sections.push(`
${t('multiCli.task.implementationSteps')}
${implApproach.map((step, idx) => `
${step.step || (idx + 1)}
${escapeHtml(step.action || step)}
`).join('')}
`);
} else if (modPoints.length > 0) {
sections.push(`
${t('multiCli.task.modificationPoints')}
${modPoints.map(mp => `
${escapeHtml(mp.file || '')}
${mp.target ? `→ ${escapeHtml(mp.target)} ` : ''}
${mp.function_name ? `→ ${escapeHtml(mp.function_name)} ` : ''}
${mp.change || mp.change_type ? `(${escapeHtml(mp.change || mp.change_type)}) ` : ''}
`).join('')}
`);
}
// Raw Implementation Steps (if not already rendered via implApproach)
if (rawImpl.length > 0 && implApproach.length === 0) {
sections.push(`
${t('multiCli.task.implementationSteps')}
${rawImpl.map((step, idx) => `
${idx + 1}
${escapeHtml(step)}
`).join('')}
`);
}
// Verification
if (task.verification?.length) {
sections.push(`
${t('multiCli.task.verification')}
${task.verification.map(v => `${escapeHtml(v)} `).join('')}
`);
}
return sections.length ? sections.join('') : `${t('multiCli.task.noImplementationData')}
`;
}
/**
* Render multi-cli task files section
* Handles both normalized format (context.focus_paths) and raw format
*/
function renderMultiCliTaskFiles(task) {
const files = [];
// Collect from normalized format (context.focus_paths)
if (task.context?.focus_paths) {
task.context.focus_paths.forEach(f => {
if (f && !files.includes(f)) files.push(f);
});
}
// Collect from raw files array (plan.json format)
if (task.files) {
task.files.forEach(f => {
const filePath = typeof f === 'string' ? f : f.file;
if (filePath && !files.includes(filePath)) files.push(filePath);
});
}
// Collect from modification_points
if (task.modification_points) {
task.modification_points.forEach(mp => {
if (mp.file && !files.includes(mp.file)) files.push(mp.file);
});
}
// Collect from scope/file (legacy)
if (task.scope && !files.includes(task.scope)) files.push(task.scope);
if (task.file && !files.includes(task.file)) files.push(task.file);
// Collect from reference.files
if (task.reference?.files) {
task.reference.files.forEach(f => {
if (f && !files.includes(f)) files.push(f);
});
}
if (files.length === 0) {
return `${t('multiCli.task.noFilesSpecified')}
`;
}
return `
${t('multiCli.task.targetFiles')}
${files.map(f => `
📄
${escapeHtml(f)}
`).join('')}
`;
}
/**
* Render a single requirement item
*/
function renderRequirementItem(req) {
const priorityColors = {
'critical': 'error',
'high': 'warning',
'medium': 'info',
'low': 'default'
};
const priority = req.priority || 'medium';
const colorClass = priorityColors[priority] || 'default';
return `
${escapeHtml(getI18nText(req.description))}
${req.source ? `
${t('multiCli.source') || 'Source'}: ${escapeHtml(req.source)}
` : ''}
`;
}
/**
* Render Decision tab
* Shows: selectedSolution, rejectedAlternatives, confidenceScore
*/
function renderMultiCliDecisionTab(session) {
// Use helper to extract decision from synthesis data structure
const decision = extractDecisionFromSynthesis(session.latestSynthesis);
if (!decision || !decision.selectedSolution) {
return `
${t('multiCli.empty.decision') || 'No Decision Yet'}
${t('multiCli.empty.decisionText') || 'No decision has been made for this discussion yet.'}
`;
}
const status = decision.status || 'pending';
const summary = getI18nText(decision.summary) || '';
const selectedSolution = decision.selectedSolution || null;
const rejectedAlternatives = decision.rejectedAlternatives || [];
const confidenceScore = decision.confidenceScore || 0;
let sections = [];
// Decision Status and Summary
sections.push(`
`);
// Selected Solution
if (selectedSolution) {
sections.push(`
${t('multiCli.selectedSolution') || 'Selected Solution'}
${renderSolutionCard(selectedSolution, true)}
`);
}
// Rejected Alternatives
if (rejectedAlternatives.length) {
sections.push(`
${rejectedAlternatives.map(alt => renderSolutionCard(alt, false)).join('')}
`);
}
return `${sections.join('')}
`;
}
/**
* Render a solution card
*/
function renderSolutionCard(solution, isSelected) {
const title = getI18nText(solution.title) || 'Untitled Solution';
const description = getI18nText(solution.description) || '';
const pros = solution.pros || [];
const cons = solution.cons || [];
const risk = solution.risk || 'medium';
const effort = getI18nText(solution.estimatedEffort) || '';
const rejectionReason = solution.rejectionReason ? getI18nText(solution.rejectionReason) : '';
const sourceCLIs = solution.sourceCLIs || [];
return `
${description ? `
${escapeHtml(description)}
` : ''}
${rejectionReason ? `
${t('multiCli.rejectionReason') || 'Reason'}: ${escapeHtml(rejectionReason)}
` : ''}
${pros.length ? `
${t('multiCli.pros') || 'Pros'}:
${pros.map(p => `${escapeHtml(getI18nText(p))} `).join('')}
` : ''}
${cons.length ? `
${t('multiCli.cons') || 'Cons'}:
${cons.map(c => `${escapeHtml(getI18nText(c))} `).join('')}
` : ''}
${effort ? `
${t('multiCli.effort') || 'Effort'}: ${escapeHtml(effort)}
` : ''}
${sourceCLIs.length ? `
${t('multiCli.sources') || 'Sources'}: ${sourceCLIs.map(s => `${escapeHtml(s)} `).join('')}
` : ''}
`;
}
/**
* Render Timeline tab
* Shows: decisionRecords.timeline
*/
function renderMultiCliTimelineTab(session) {
// Use helper to extract timeline from synthesis data structure
const timeline = extractTimelineFromSynthesis(session.latestSynthesis);
if (!timeline || !timeline.length) {
return `
${t('multiCli.empty.timeline') || 'No Timeline Events'}
${t('multiCli.empty.timelineText') || 'No decision timeline available for this session.'}
`;
}
const eventTypeIcons = {
'proposal': 'lightbulb',
'argument': 'message-square',
'agreement': 'thumbs-up',
'disagreement': 'thumbs-down',
'decision': 'check-circle',
'reversal': 'rotate-ccw'
};
return `
${timeline.map(event => {
const icon = eventTypeIcons[event.type] || 'circle';
const contributor = event.contributor || {};
const summary = getI18nText(event.summary) || '';
const evidence = event.evidence || [];
return `
${escapeHtml(summary)}
${event.reversibility ? `
${escapeHtml(event.reversibility)} ` : ''}
${evidence.length ? `
${evidence.map(ev => `
${escapeHtml(ev.type || 'reference')}
${escapeHtml(getI18nText(ev.description))}
`).join('')}
` : ''}
`;
}).join('')}
`;
}
/**
* Render Rounds tab
* Shows: navigation between round synthesis files
*/
function renderMultiCliRoundsTab(session) {
const rounds = session.rounds || [];
const metadata = session.metadata || {};
const totalRounds = metadata.roundId || rounds.length || 1;
if (!rounds.length && totalRounds <= 1) {
// Show current synthesis as single round
return `
Round 1
${t('multiCli.currentRound') || 'Current'}
${t('multiCli.singleRoundInfo') || 'This is a single-round discussion. View other tabs for details.'}
`;
}
// Render round navigation and content
return `
${rounds.map((round, idx) => {
const roundNum = idx + 1;
const isActive = roundNum === totalRounds;
const roundStatus = round.convergence?.recommendation || 'continue';
return `
Round ${roundNum}
${escapeHtml(roundStatus)}
`;
}).join('')}
${renderRoundContent(rounds[totalRounds - 1] || rounds[0] || session)}
`;
}
/**
* Render content for a specific round
*/
function renderRoundContent(round) {
if (!round) {
return `${t('multiCli.noRoundData') || 'No data for this round.'}
`;
}
const metadata = round.metadata || {};
const agents = metadata.contributingAgents || [];
const convergence = round._internal?.convergence || {};
const crossVerification = round._internal?.cross_verification || {};
let sections = [];
// Round metadata
sections.push(`
`);
// Contributing agents
if (agents.length) {
sections.push(`
${t('multiCli.contributors') || 'Contributors'}:
${agents.map(agent => `${escapeHtml(agent.name || agent.id)} `).join('')}
`);
}
// Convergence metrics
if (convergence.score !== undefined) {
sections.push(`
${t('multiCli.convergence') || 'Convergence'}:
${(convergence.score * 100).toFixed(0)}%
${escapeHtml(convergence.recommendation || '')}
${convergence.new_insights ? `${t('multiCli.newInsights') || 'New Insights'} ` : ''}
`);
}
// Cross-verification
if (crossVerification.agreements?.length || crossVerification.disagreements?.length) {
sections.push(`
${crossVerification.agreements?.length ? `
${t('multiCli.agreements') || 'Agreements'}:
${crossVerification.agreements.map(a => `${escapeHtml(a)} `).join('')}
` : ''}
${crossVerification.disagreements?.length ? `
${t('multiCli.disagreements') || 'Disagreements'}:
${crossVerification.disagreements.map(d => `${escapeHtml(d)} `).join('')}
` : ''}
${crossVerification.resolution ? `
${t('multiCli.resolution') || 'Resolution'}:
${escapeHtml(crossVerification.resolution)}
` : ''}
`);
}
// Round solutions
if (round.solutions && round.solutions.length > 0) {
sections.push(`
${t('multiCli.solutions') || 'Solutions'} (${round.solutions.length}):
${round.solutions.map((solution, idx) => `
${solution.summary ? `
${escapeHtml(getI18nText(solution.summary))}
` : ''}
${solution.implementation_plan?.approach ? `
${escapeHtml(getI18nText(solution.implementation_plan.approach))}
${solution.implementation_plan.tasks?.length ? `
${t('multiCli.tasks') || 'Tasks'}:
${solution.implementation_plan.tasks.map(task => `
${escapeHtml(task.id || '')}
${escapeHtml(getI18nText(task.name))}
${task.key_point ? `${escapeHtml(getI18nText(task.key_point))} ` : ''}
`).join('')}
` : ''}
${solution.implementation_plan.execution_flow ? `
${t('multiCli.executionFlow') || 'Execution Flow'}:
${escapeHtml(solution.implementation_plan.execution_flow)}
` : ''}
${solution.implementation_plan.milestones?.length ? `
${t('multiCli.milestones') || 'Milestones'}:
${solution.implementation_plan.milestones.map(milestone => `
${escapeHtml(getI18nText(milestone))}
`).join('')}
` : ''}
` : ''}
${(solution.dependencies?.internal?.length || solution.dependencies?.external?.length) ? `
${solution.dependencies.internal?.length ? `
${t('multiCli.internalDeps') || 'Internal'}:
${solution.dependencies.internal.map(dep => `
${escapeHtml(getI18nText(dep))}
`).join('')}
` : ''}
${solution.dependencies.external?.length ? `
${t('multiCli.externalDeps') || 'External'}:
${solution.dependencies.external.map(dep => `
${escapeHtml(getI18nText(dep))}
`).join('')}
` : ''}
` : ''}
${solution.technical_concerns?.length ? `
${solution.technical_concerns.map(concern => `
${escapeHtml(getI18nText(concern))}
`).join('')}
` : ''}
`).join('')}
`);
}
return sections.join('');
}
/**
* Load a specific round's data (async, may fetch from server)
*/
async function loadMultiCliRound(sessionKey, roundNum) {
const session = liteTaskDataStore[sessionKey];
if (!session) return;
// Update active state in nav
document.querySelectorAll('.round-item').forEach(item => {
item.classList.toggle('active', parseInt(item.dataset.round) === roundNum);
});
const contentArea = document.getElementById('multiCliRoundContent');
// If we have rounds array, use it
if (session.rounds && session.rounds[roundNum - 1]) {
contentArea.innerHTML = renderRoundContent(session.rounds[roundNum - 1]);
initCollapsibleSections(contentArea);
return;
}
// Otherwise try to fetch from server
if (window.SERVER_MODE && session.path) {
contentArea.innerHTML = `${t('common.loading') || 'Loading...'}
`;
try {
const response = await fetch(`/api/session-detail?path=${encodeURIComponent(session.path)}&type=round&round=${roundNum}`);
if (response.ok) {
const data = await response.json();
contentArea.innerHTML = renderRoundContent(data.round || {});
initCollapsibleSections(contentArea);
return;
}
} catch (err) {
console.error('Failed to load round:', err);
}
}
// Fallback
contentArea.innerHTML = `${t('multiCli.noRoundData') || 'No data for this round.'}
`;
}
/**
* Render Discussion Section (combines Topic, Rounds, Decision)
* Uses accordion layout to display discussion rounds
*/
function renderMultiCliDiscussionSection(session) {
const rounds = session.rounds || [];
const metadata = session.metadata || {};
const totalRounds = metadata.roundId || rounds.length || 1;
const topic = session.discussionTopic || session.latestSynthesis?.discussionTopic || {};
// If no rounds, show topic summary and current synthesis
if (!rounds.length) {
const title = getI18nText(topic.title) || t('multiCli.discussion.discussionTopic');
const description = getI18nText(topic.description) || '';
const status = topic.status || session.status || 'analyzing';
return `
${description ? `
${escapeHtml(description)}
` : ''}
${t('multiCli.singleRoundInfo')}
`;
}
// Render accordion for multiple rounds
const accordionItems = rounds.map((round, idx) => {
const roundNum = idx + 1;
const isLatest = roundNum === totalRounds;
const roundMeta = round.metadata || {};
const convergence = round._internal?.convergence || round.convergence || {};
const recommendation = convergence.recommendation || 'continue';
const score = convergence.score !== undefined ? Math.round(convergence.score * 100) : null;
const solutions = round.solutions || [];
const agents = roundMeta.contributingAgents || [];
return `
${round.discussionTopic ? `
${t('multiCli.topic') || 'Topic'}
${escapeHtml(getI18nText(round.discussionTopic.title || round.discussionTopic))}
` : ''}
${agents.length ? `
${t('multiCli.contributors') || 'Contributors'}
${agents.map(agent => `${escapeHtml(agent.name || agent.id || agent)} `).join('')}
` : ''}
${solutions.length ? `
${t('multiCli.solutions')} (${solutions.length})
${solutions.map((sol, sidx) => `
${Math.round((sol.feasibility || 0) * 100)}%
${escapeHtml(sol.effort || 'M')}
${escapeHtml(sol.risk || 'M')}
${sol.summary ? `
${escapeHtml(getI18nText(sol.summary).substring(0, 100))}${getI18nText(sol.summary).length > 100 ? '...' : ''}
` : ''}
`).join('')}
` : ''}
${convergence.reasoning || round.decision ? `
${t('multiCli.decision')}
${escapeHtml(convergence.reasoning || round.decision || '')}
` : ''}
`;
}).join('');
return `
`;
}
/**
* Render Association Section (context-package key fields)
* Shows solution summary, dependencies, consensus
*/
function renderMultiCliAssociationSection(session) {
const contextPkg = session.contextPackage || session.context_package || session.latestSynthesis?.context_package || {};
const solutions = contextPkg.solutions || session.latestSynthesis?.solutions || [];
const dependencies = contextPkg.dependencies || {};
const consensus = contextPkg.consensus || session.latestSynthesis?._internal?.cross_verification || {};
const relatedFiles = extractFilesFromSynthesis(session.latestSynthesis);
// Check if we have any content to display
const hasSolutions = solutions.length > 0;
const hasDependencies = dependencies.internal?.length || dependencies.external?.length;
const hasConsensus = consensus.agreements?.length || consensus.resolved_conflicts?.length || consensus.disagreements?.length;
const hasFiles = relatedFiles?.fileTree?.length || relatedFiles?.impactSummary?.length;
if (!hasSolutions && !hasDependencies && !hasConsensus && !hasFiles) {
return `
${t('multiCli.empty.association') || 'No Association Data'}
${t('multiCli.empty.associationText') || 'No context package or related files available for this session.'}
`;
}
let sections = [];
// Solution Summary Cards
if (hasSolutions) {
sections.push(`
${t('multiCli.solutionSummary') || 'Solution Summary'}
${solutions.map((sol, idx) => `
${t('multiCli.feasibility') || 'Feasibility'}
${Math.round((sol.feasibility || 0) * 100)}%
${t('multiCli.effort') || 'Effort'}
${escapeHtml(sol.effort || 'medium')}
${t('multiCli.risk') || 'Risk'}
${escapeHtml(sol.risk || 'medium')}
${sol.summary ? `
${escapeHtml(getI18nText(sol.summary))}
` : ''}
`).join('')}
`);
}
// Dependencies Card
if (hasDependencies) {
sections.push(`
${t('multiCli.dependencies') || 'Dependencies'}
${dependencies.internal?.length ? `
${dependencies.internal.map(dep => `${escapeHtml(getI18nText(dep))} `).join('')}
` : ''}
${dependencies.external?.length ? `
${dependencies.external.map(dep => `${escapeHtml(getI18nText(dep))} `).join('')}
` : ''}
`);
}
// Consensus Card
if (hasConsensus) {
sections.push(`
${t('multiCli.consensus') || 'Consensus'}
${consensus.agreements?.length ? `
${consensus.agreements.map(item => `${escapeHtml(item)} `).join('')}
` : ''}
${(consensus.resolved_conflicts?.length || consensus.disagreements?.length) ? `
${(consensus.resolved_conflicts || consensus.disagreements || []).map(item => `${escapeHtml(item)} `).join('')}
` : ''}
`);
}
// Related Files (from existing Files tab logic)
if (hasFiles) {
sections.push(`
${t('multiCli.tab.files') || 'Related Files'}
${relatedFiles.fileTree?.length ? `
${relatedFiles.fileTree.slice(0, 10).map(file => `
${escapeHtml(typeof file === 'string' ? file : file.path || file.name)}
`).join('')}
${relatedFiles.fileTree.length > 10 ? `
+${relatedFiles.fileTree.length - 10} ${t('common.more') || 'more'}
` : ''}
` : ''}
${relatedFiles.impactSummary?.length ? `
${t('multiCli.impactSummary') || 'Impact Summary'}
${relatedFiles.impactSummary.slice(0, 5).map(item => `${escapeHtml(getI18nText(item))} `).join('')}
` : ''}
`);
}
return `
${sections.join('')}
`;
}
// Lite Task Detail Page
function showLiteTaskDetailPage(sessionKey) {
const session = liteTaskDataStore[sessionKey];
if (!session) return;
currentView = 'liteTaskDetail';
currentSessionDetailKey = sessionKey;
// Hide stats grid and carousel on detail pages
hideStatsAndCarousel();
// 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 = `
${t('detail.created')}
${formatDate(session.createdAt)}
${t('detail.tasks')}
${tasks.length} ${t('session.tasks')}
${t('tab.tasks')}
${tasks.length}
${t('tab.plan')}
${session.type === 'lite-fix' ? `
${t('tab.diagnoses')}
${session.diagnoses?.items?.length ? `${session.diagnoses.items.length} ` : ''}
` : ''}
${t('tab.context')}
${t('tab.summary')}
${renderLiteTasksTab(session, tasks, completed, inProgress, pending)}
`;
// Initialize collapsible sections and task click handlers
setTimeout(() => {
document.querySelectorAll('.collapsible-header').forEach(header => {
header.addEventListener('click', () => toggleSection(header));
});
// Bind click events to lite task items on initial load
initLiteTaskClickHandlers();
}, 50);
}
function goBackToLiteTasks() {
currentView = 'liteTasks';
currentSessionDetailKey = null;
updateContentTitle();
showStatsAndSearch();
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 and task click handlers
setTimeout(() => {
document.querySelectorAll('.collapsible-header').forEach(header => {
header.addEventListener('click', () => toggleSection(header));
});
// Bind click events to lite task items
initLiteTaskClickHandlers();
}, 50);
break;
case 'plan':
contentArea.innerHTML = renderLitePlanTab(session);
// Re-initialize collapsible sections for plan tab
setTimeout(() => {
initCollapsibleSections(contentArea);
}, 50);
break;
case 'diagnoses':
contentArea.innerHTML = renderDiagnosesTab(session);
// Re-initialize collapsible sections for diagnoses tab
setTimeout(() => {
initCollapsibleSections(contentArea);
}, 50);
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 `
${t('empty.noTasks')}
${t('empty.noTasksText')}
`;
}
return `
${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;
// Get preview info for lite tasks
const action = rawTask.action || '';
const scope = rawTask.scope || '';
const modCount = rawTask.modification_points?.length || 0;
const implCount = rawTask.implementation?.length || 0;
const acceptCount = rawTask.acceptance?.length || 0;
// Escape for data attributes
const safeSessionId = escapeHtml(sessionId);
const safeTaskId = escapeHtml(task.id);
return `
${action ? `${escapeHtml(action)} ` : ''}
${scope ? `${escapeHtml(scope)} ` : ''}
${modCount > 0 ? `${modCount} mods ` : ''}
${implCount > 0 ? `${implCount} steps ` : ''}
${acceptCount > 0 ? `${acceptCount} acceptance ` : ''}
`;
}
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';
}
/**
* Initialize click handlers for lite task items
*/
function initLiteTaskClickHandlers() {
// Task item click handlers
document.querySelectorAll('.lite-task-item').forEach(item => {
if (!item._clickBound) {
item._clickBound = true;
item.addEventListener('click', function(e) {
// Don't trigger if clicking on JSON button
if (e.target.closest('.btn-view-json')) return;
const sessionId = this.dataset.sessionId;
const taskId = this.dataset.taskId;
openTaskDrawerForLite(sessionId, taskId);
});
}
});
// JSON button click handlers
document.querySelectorAll('.btn-view-json').forEach(btn => {
if (!btn._clickBound) {
btn._clickBound = true;
btn.addEventListener('click', function(e) {
e.stopPropagation();
const taskJsonId = this.dataset.taskJsonId;
const displayId = this.dataset.taskDisplayId;
showJsonModal(taskJsonId, displayId);
});
}
});
}
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 and session context
currentDrawerTasks = session.tasks || [];
window._currentDrawerSession = session;
document.getElementById('drawerTaskTitle').textContent = task.title || taskId;
// Use dedicated lite task drawer renderer
document.getElementById('drawerContent').innerHTML = renderLiteTaskDrawerContent(task, session);
document.getElementById('taskDetailDrawer').classList.add('open');
document.getElementById('drawerOverlay').classList.add('active');
}
function renderLitePlanTab(session) {
const plan = session.plan;
const isFixPlan = session.type === 'lite-fix';
if (!plan) {
return `
No Plan Data
No ${isFixPlan ? 'fix-plan.json' : 'plan.json'} found for this session.
`;
}
return `
${plan.summary ? `
Summary
${escapeHtml(plan.summary)}
` : ''}
${plan.root_cause ? `
Root Cause
${escapeHtml(plan.root_cause)}
` : ''}
${plan.strategy ? `
Fix Strategy
${escapeHtml(plan.strategy)}
` : ''}
${plan.approach ? `
Approach
${escapeHtml(plan.approach)}
` : ''}
${plan.user_requirements ? `
User Requirements
${escapeHtml(plan.user_requirements)}
` : ''}
${plan.focus_paths?.length ? `
Focus Paths
${plan.focus_paths.map(p => `${escapeHtml(p)} `).join('')}
` : ''}
${plan.tasks?.length ? `
Fix Tasks (${plan.tasks.length})
${plan.tasks.map((task, idx) => `
${task.modification_points?.length ? `
Modification Points:
${task.modification_points.map(mp => `
${escapeHtml(mp.file || '')}
${mp.function_name ? `→ ${escapeHtml(mp.function_name)} ` : ''}
${mp.change_type ? `(${escapeHtml(mp.change_type)}) ` : ''}
`).join('')}
` : ''}
${task.implementation?.length ? `
Implementation Steps:
${task.implementation.map(step => `${escapeHtml(step)} `).join('')}
` : ''}
${task.verification?.length ? `
Verification:
${task.verification.map(v => `${escapeHtml(v)} `).join('')}
` : ''}
`).join('')}
` : ''}
${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, data.explorations, session, data.diagnoses);
// Re-initialize collapsible sections for explorations and diagnoses (scoped to contentArea)
initCollapsibleSections(contentArea);
return;
}
}
// Fallback: show plan context if available
contentArea.innerHTML = renderLiteContextContent(null, null, session, null);
initCollapsibleSections(contentArea);
} catch (err) {
contentArea.innerHTML = `Failed to load context: ${err.message}
`;
}
}
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/
`;
if (typeof lucide !== 'undefined') lucide.createIcons();
} catch (err) {
contentArea.innerHTML = `Failed to load summaries: ${err.message}
`;
}
}
// ============================================
// DIAGNOSES TAB RENDERING (lite-fix specific)
// ============================================
function renderDiagnosesTab(session) {
const diagnoses = session.diagnoses;
if (!diagnoses || (!diagnoses.manifest && diagnoses.items?.length === 0)) {
return `
${t('empty.noDiagnoses')}
${t('empty.noDiagnosesText')}
`;
}
let sections = [];
// Manifest summary (if available)
if (diagnoses.manifest) {
sections.push(`
`);
}
// Individual diagnosis items
if (diagnoses.items && diagnoses.items.length > 0) {
const diagnosisCards = diagnoses.items.map((diag) => {
return renderDiagnosisCard(diag);
}).join('');
sections.push(`
Diagnosis Details (${diagnoses.items.length})
${diagnosisCards}
`);
}
return `${sections.join('')}
`;
}
function renderDiagnosisCard(diag) {
const diagJsonId = `diag-json-${diag.id}`.replace(/[^a-zA-Z0-9-]/g, '-');
taskJsonStore[diagJsonId] = diag;
return `
${renderDiagnosisContent(diag)}
`;
}
function renderDiagnosisContent(diag) {
let content = [];
// Symptom (for detailed diagnosis structure)
if (diag.symptom) {
const symptom = diag.symptom;
content.push(`
Symptom:
${symptom.description ? `
${escapeHtml(symptom.description)}
` : ''}
${symptom.user_impact ? `
User Impact: ${escapeHtml(symptom.user_impact)}
` : ''}
${symptom.frequency ? `
Frequency: ${escapeHtml(symptom.frequency)}
` : ''}
${symptom.error_message ? `
Error: ${escapeHtml(symptom.error_message)}
` : ''}
`);
}
// Summary/Overview (for simple diagnosis structure)
if (diag.summary || diag.overview) {
content.push(`
Summary:
${escapeHtml(diag.summary || diag.overview)}
`);
}
// Root Cause Analysis
if (diag.root_cause) {
const rootCause = diag.root_cause;
// Handle both object and string formats
if (typeof rootCause === 'object') {
content.push(`
Root Cause:
${rootCause.file ? `
File: ${escapeHtml(rootCause.file)}
` : ''}
${rootCause.line_range ? `
Lines: ${escapeHtml(rootCause.line_range)}
` : ''}
${rootCause.function ? `
Function: ${escapeHtml(rootCause.function)}
` : ''}
${rootCause.issue ? `
${escapeHtml(rootCause.issue)}
` : ''}
${rootCause.confidence ? `
Confidence: ${(rootCause.confidence * 100).toFixed(0)}%
` : ''}
${rootCause.category ? `
Category: ${escapeHtml(rootCause.category)}
` : ''}
`);
} else if (typeof rootCause === 'string') {
content.push(`
Root Cause:
${escapeHtml(rootCause)}
`);
}
} else if (diag.root_cause_analysis) {
content.push(`
Root Cause:
${escapeHtml(diag.root_cause_analysis)}
`);
}
// Issues/Findings
if (diag.issues && Array.isArray(diag.issues)) {
content.push(`
Issues Found (${diag.issues.length}):
`);
}
// Affected Files
if (diag.affected_files && Array.isArray(diag.affected_files)) {
content.push(`
Affected Files:
${diag.affected_files.map(f => `${escapeHtml(typeof f === 'string' ? f : f.path || f.file)} `).join('')}
`);
}
// API Contracts (for api-contracts diagnosis)
if (diag.contracts && Array.isArray(diag.contracts)) {
content.push(`
API Contracts (${diag.contracts.length}):
${diag.contracts.map(contract => `
${contract.description ? `
${escapeHtml(contract.description)}
` : ''}
${contract.issues?.length ? `
${contract.issues.length} issue(s)
` : ''}
`).join('')}
`);
}
// Dataflow Analysis (for dataflow diagnosis)
if (diag.dataflow || diag.data_flow) {
const df = diag.dataflow || diag.data_flow;
content.push(`
Data Flow Analysis:
${typeof df === 'string' ? `
${escapeHtml(df)}
` : `
${df.source ? `
Source: ${escapeHtml(df.source)}
` : ''}
${df.sink ? `
Sink: ${escapeHtml(df.sink)}
` : ''}
${df.transformations?.length ? `
Transformations:
${df.transformations.map(t => `${escapeHtml(t)} `).join('')}
` : ''}
`}
`);
}
// Reproduction Steps
if (diag.reproduction_steps && Array.isArray(diag.reproduction_steps)) {
content.push(`
Reproduction Steps:
${diag.reproduction_steps.map(step => `${escapeHtml(step)} `).join('')}
`);
}
// Fix Hints
if (diag.fix_hints && Array.isArray(diag.fix_hints)) {
content.push(`
Fix Hints (${diag.fix_hints.length}):
${diag.fix_hints.map((hint, idx) => `
${hint.approach ? `
Approach: ${escapeHtml(hint.approach)}
` : ''}
${hint.risk ? `
Risk: ${escapeHtml(hint.risk)}
` : ''}
${hint.code_example ? `
Code Example: ${escapeHtml(hint.code_example)}` : ''}
`).join('')}
`);
}
// Recommendations
if (diag.recommendations && Array.isArray(diag.recommendations)) {
content.push(`
Recommendations:
${diag.recommendations.map(rec => `${escapeHtml(typeof rec === 'string' ? rec : rec.description || rec.action)} `).join('')}
`);
}
// Dependencies
if (diag.dependencies && typeof diag.dependencies === 'string') {
content.push(`
Dependencies:
${escapeHtml(diag.dependencies)}
`);
}
// Constraints
if (diag.constraints && typeof diag.constraints === 'string') {
content.push(`
Constraints:
${escapeHtml(diag.constraints)}
`);
}
// Clarification Needs
if (diag.clarification_needs && Array.isArray(diag.clarification_needs)) {
content.push(`
Clarification Needs:
${diag.clarification_needs.map(clar => `
Q: ${escapeHtml(clar.question)}
${clar.context ? `
Context: ${escapeHtml(clar.context)}
` : ''}
${clar.options && Array.isArray(clar.options) ? `
Options:
${clar.options.map(opt => `${escapeHtml(opt)} `).join('')}
` : ''}
`).join('')}
`);
}
// Related Issues
if (diag.related_issues && Array.isArray(diag.related_issues)) {
content.push(`
`);
}
// If no specific content was rendered, show raw JSON preview
if (content.length === 0) {
console.warn('[DEBUG] No content rendered for diagnosis:', diag);
content.push(`
Debug: Raw JSON
${escapeHtml(JSON.stringify(diag, null, 2))}
`);
}
return content.join('');
}