claude-code-workflow 6.3.28 → 6.3.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/commands/issue/execute.md +133 -18
- package/.claude/commands/workflow/debug.md +6 -0
- package/.claude/commands/workflow/execute.md +4 -0
- package/.claude/commands/workflow/lite-execute.md +4 -0
- package/.claude/commands/workflow/lite-lite-lite.md +179 -544
- package/.claude/commands/workflow/review-fix.md +4 -0
- package/.claude/commands/workflow/test-cycle-execute.md +4 -0
- package/.codex/prompts/issue-execute.md +72 -12
- package/README.md +3 -0
- package/ccw/dist/core/lite-scanner.d.ts.map +1 -1
- package/ccw/dist/core/lite-scanner.js +56 -3
- package/ccw/dist/core/lite-scanner.js.map +1 -1
- package/ccw/dist/tools/memory-update-queue.d.ts.map +1 -1
- package/ccw/dist/tools/memory-update-queue.js +5 -11
- package/ccw/dist/tools/memory-update-queue.js.map +1 -1
- package/ccw/src/core/lite-scanner.ts +58 -3
- package/ccw/src/templates/dashboard-css/03-tasks.css +5 -0
- package/ccw/src/templates/dashboard-css/04-lite-tasks.css +630 -1
- package/ccw/src/templates/dashboard-js/components/hook-manager.js +30 -21
- package/ccw/src/templates/dashboard-js/i18n.js +100 -0
- package/ccw/src/templates/dashboard-js/views/lite-tasks.js +1138 -334
- package/ccw/src/tools/memory-update-queue.js +5 -11
- package/package.json +1 -1
|
@@ -347,11 +347,8 @@ function extractTimelineFromSynthesis(synthesis) {
|
|
|
347
347
|
return timeline;
|
|
348
348
|
}
|
|
349
349
|
|
|
350
|
-
// Track multi-cli toolbar state
|
|
351
|
-
let isMultiCliToolbarExpanded = false;
|
|
352
|
-
|
|
353
350
|
/**
|
|
354
|
-
* Show multi-cli detail page with tabs
|
|
351
|
+
* Show multi-cli detail page with tabs (same layout as lite-plan)
|
|
355
352
|
*/
|
|
356
353
|
function showMultiCliDetailPage(sessionKey) {
|
|
357
354
|
const session = liteTaskDataStore[sessionKey];
|
|
@@ -364,115 +361,84 @@ function showMultiCliDetailPage(sessionKey) {
|
|
|
364
361
|
|
|
365
362
|
const container = document.getElementById('mainContent');
|
|
366
363
|
const metadata = session.metadata || {};
|
|
367
|
-
const
|
|
368
|
-
|
|
364
|
+
const plan = session.plan || {};
|
|
365
|
+
// Use session.tasks (normalized from backend) with fallback to plan.tasks
|
|
366
|
+
const tasks = session.tasks?.length > 0 ? session.tasks : (plan.tasks || []);
|
|
369
367
|
const roundCount = metadata.roundId || session.roundCount || 1;
|
|
370
|
-
const
|
|
371
|
-
const status = latestSynthesis.status || session.status || 'analyzing';
|
|
368
|
+
const status = session.status || 'analyzing';
|
|
372
369
|
|
|
373
370
|
container.innerHTML = `
|
|
374
|
-
<div class="session-detail-page
|
|
375
|
-
<!--
|
|
376
|
-
<div class="
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
<
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
</
|
|
383
|
-
<div class="detail-
|
|
384
|
-
<
|
|
385
|
-
<div class="detail-badges">
|
|
386
|
-
<span class="session-type-badge multi-cli-plan">multi-cli-plan</span>
|
|
387
|
-
<span class="session-status-badge ${status}">${escapeHtml(status)}</span>
|
|
388
|
-
</div>
|
|
389
|
-
</div>
|
|
390
|
-
</div>
|
|
391
|
-
|
|
392
|
-
<!-- Session Info Bar -->
|
|
393
|
-
<div class="detail-info-bar">
|
|
394
|
-
<div class="info-item">
|
|
395
|
-
<span class="info-label">${t('detail.created') || 'Created'}</span>
|
|
396
|
-
<span class="info-value">${formatDate(metadata.timestamp || session.createdAt)}</span>
|
|
397
|
-
</div>
|
|
398
|
-
<div class="info-item">
|
|
399
|
-
<span class="info-label">${t('multiCli.roundCount') || 'Rounds'}</span>
|
|
400
|
-
<span class="info-value">${roundCount}</span>
|
|
401
|
-
</div>
|
|
402
|
-
<div class="info-item">
|
|
403
|
-
<span class="info-label">${t('multiCli.topic') || 'Topic'}</span>
|
|
404
|
-
<span class="info-value">${escapeHtml(topicTitle)}</span>
|
|
371
|
+
<div class="session-detail-page lite-task-detail-page">
|
|
372
|
+
<!-- Header -->
|
|
373
|
+
<div class="detail-header">
|
|
374
|
+
<button class="btn-back" onclick="goBackToLiteTasks()">
|
|
375
|
+
<span class="back-icon">←</span>
|
|
376
|
+
<span>${t('multiCli.backToList') || 'Back to Multi-CLI Plan'}</span>
|
|
377
|
+
</button>
|
|
378
|
+
<div class="detail-title-row">
|
|
379
|
+
<h2 class="detail-session-id"><i data-lucide="messages-square" class="w-5 h-5 inline mr-2"></i> ${escapeHtml(session.id)}</h2>
|
|
380
|
+
<div class="detail-badges">
|
|
381
|
+
<span class="session-type-badge multi-cli-plan">MULTI-CLI</span>
|
|
405
382
|
</div>
|
|
406
383
|
</div>
|
|
384
|
+
</div>
|
|
407
385
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
</button>
|
|
414
|
-
<button class="detail-tab" data-tab="discussion" onclick="switchMultiCliDetailTab('discussion')">
|
|
415
|
-
<span class="tab-icon"><i data-lucide="messages-square" class="w-4 h-4"></i></span>
|
|
416
|
-
<span class="tab-text">${t('multiCli.tab.discussion') || 'Discussion'}</span>
|
|
417
|
-
<span class="tab-count">${roundCount}</span>
|
|
418
|
-
</button>
|
|
419
|
-
<button class="detail-tab" data-tab="association" onclick="switchMultiCliDetailTab('association')">
|
|
420
|
-
<span class="tab-icon"><i data-lucide="link-2" class="w-4 h-4"></i></span>
|
|
421
|
-
<span class="tab-text">${t('multiCli.tab.association') || 'Association'}</span>
|
|
422
|
-
</button>
|
|
386
|
+
<!-- Session Info Bar -->
|
|
387
|
+
<div class="detail-info-bar">
|
|
388
|
+
<div class="info-item">
|
|
389
|
+
<span class="info-label">${t('detail.created') || 'Created'}</span>
|
|
390
|
+
<span class="info-value">${formatDate(metadata.timestamp || session.createdAt)}</span>
|
|
423
391
|
</div>
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
${renderMultiCliPlanningTab(session)}
|
|
392
|
+
<div class="info-item">
|
|
393
|
+
<span class="info-label">${t('detail.tasks') || 'Tasks'}</span>
|
|
394
|
+
<span class="info-value">${tasks.length} ${t('session.tasks') || 'tasks'}</span>
|
|
428
395
|
</div>
|
|
429
396
|
</div>
|
|
430
397
|
|
|
431
|
-
<!--
|
|
432
|
-
<div class="
|
|
433
|
-
<button class="
|
|
434
|
-
<i data-lucide="
|
|
398
|
+
<!-- Tab Navigation (same as lite-plan) -->
|
|
399
|
+
<div class="detail-tabs">
|
|
400
|
+
<button class="detail-tab active" data-tab="tasks" onclick="switchMultiCliDetailTab('tasks')">
|
|
401
|
+
<span class="tab-icon"><i data-lucide="list-checks" class="w-4 h-4"></i></span>
|
|
402
|
+
<span class="tab-text">${t('tab.tasks') || 'Tasks'}</span>
|
|
403
|
+
<span class="tab-count">${tasks.length}</span>
|
|
435
404
|
</button>
|
|
436
|
-
<
|
|
437
|
-
|
|
438
|
-
|
|
405
|
+
<button class="detail-tab" data-tab="discussion" onclick="switchMultiCliDetailTab('discussion')">
|
|
406
|
+
<span class="tab-icon"><i data-lucide="messages-square" class="w-4 h-4"></i></span>
|
|
407
|
+
<span class="tab-text">${t('multiCli.tab.discussion') || 'Discussion'}</span>
|
|
408
|
+
<span class="tab-count">${roundCount}</span>
|
|
409
|
+
</button>
|
|
410
|
+
<button class="detail-tab" data-tab="context" onclick="switchMultiCliDetailTab('context')">
|
|
411
|
+
<span class="tab-icon"><i data-lucide="package" class="w-4 h-4"></i></span>
|
|
412
|
+
<span class="tab-text">${t('tab.context') || 'Context'}</span>
|
|
413
|
+
</button>
|
|
414
|
+
<button class="detail-tab" data-tab="summary" onclick="switchMultiCliDetailTab('summary')">
|
|
415
|
+
<span class="tab-icon"><i data-lucide="file-text" class="w-4 h-4"></i></span>
|
|
416
|
+
<span class="tab-text">${t('tab.summary') || 'Summary'}</span>
|
|
417
|
+
</button>
|
|
418
|
+
</div>
|
|
419
|
+
|
|
420
|
+
<!-- Tab Content -->
|
|
421
|
+
<div class="detail-tab-content" id="multiCliDetailTabContent">
|
|
422
|
+
${renderMultiCliTasksTab(session)}
|
|
439
423
|
</div>
|
|
440
424
|
</div>
|
|
441
425
|
`;
|
|
442
426
|
|
|
443
|
-
// Initialize icons and
|
|
427
|
+
// Initialize icons, collapsible sections, and task click handlers
|
|
444
428
|
setTimeout(() => {
|
|
445
429
|
if (typeof lucide !== 'undefined') lucide.createIcons();
|
|
446
430
|
initCollapsibleSections(container);
|
|
431
|
+
initMultiCliTaskClickHandlers();
|
|
447
432
|
}, 50);
|
|
448
433
|
}
|
|
449
434
|
|
|
450
|
-
/**
|
|
451
|
-
* Toggle multi-cli toolbar expanded/collapsed state
|
|
452
|
-
*/
|
|
453
|
-
function toggleMultiCliToolbar() {
|
|
454
|
-
isMultiCliToolbarExpanded = !isMultiCliToolbarExpanded;
|
|
455
|
-
const toolbar = document.getElementById('multiCliToolbar');
|
|
456
|
-
const toggleBtn = toolbar?.querySelector('.toolbar-toggle-btn i');
|
|
457
|
-
|
|
458
|
-
if (toolbar) {
|
|
459
|
-
toolbar.classList.toggle('expanded', isMultiCliToolbarExpanded);
|
|
460
|
-
toolbar.classList.toggle('collapsed', !isMultiCliToolbarExpanded);
|
|
461
|
-
|
|
462
|
-
// Update icon
|
|
463
|
-
if (toggleBtn) {
|
|
464
|
-
toggleBtn.setAttribute('data-lucide', isMultiCliToolbarExpanded ? 'panel-right-close' : 'panel-right-open');
|
|
465
|
-
if (typeof lucide !== 'undefined') lucide.createIcons();
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
|
|
470
435
|
/**
|
|
471
436
|
* Render the multi-cli toolbar content
|
|
472
437
|
*/
|
|
473
438
|
function renderMultiCliToolbar(session) {
|
|
474
439
|
const plan = session.plan;
|
|
475
|
-
|
|
440
|
+
// Use session.tasks (normalized from backend) with fallback to plan.tasks
|
|
441
|
+
const tasks = session.tasks?.length > 0 ? session.tasks : (plan?.tasks || []);
|
|
476
442
|
const taskCount = tasks.length;
|
|
477
443
|
|
|
478
444
|
let toolbarHtml = `
|
|
@@ -505,12 +471,12 @@ function renderMultiCliToolbar(session) {
|
|
|
505
471
|
toolbarHtml += `
|
|
506
472
|
<div class="toolbar-task-list">
|
|
507
473
|
${tasks.map((task, idx) => {
|
|
508
|
-
const taskTitle = task.title || task.summary || `Task ${idx + 1}`;
|
|
509
|
-
const taskScope = task.scope || '';
|
|
510
|
-
const
|
|
474
|
+
const taskTitle = task.title || task.name || task.summary || `Task ${idx + 1}`;
|
|
475
|
+
const taskScope = task.meta?.scope || task.scope || '';
|
|
476
|
+
const taskIdValue = task.id || `T${idx + 1}`;
|
|
511
477
|
|
|
512
478
|
return `
|
|
513
|
-
<div class="toolbar-task-item" onclick="
|
|
479
|
+
<div class="toolbar-task-item" onclick="openToolbarTaskDrawer('${escapeHtml(session.id)}', '${escapeHtml(taskIdValue)}')" data-task-idx="${idx}">
|
|
514
480
|
<span class="toolbar-task-num">#${idx + 1}</span>
|
|
515
481
|
<div class="toolbar-task-info">
|
|
516
482
|
<span class="toolbar-task-title" title="${escapeHtml(taskTitle)}">${escapeHtml(taskTitle)}</span>
|
|
@@ -564,6 +530,13 @@ function scrollToMultiCliTask(taskIdx) {
|
|
|
564
530
|
}
|
|
565
531
|
}
|
|
566
532
|
|
|
533
|
+
/**
|
|
534
|
+
* Open task drawer from toolbar (wrapper for openTaskDrawerForMultiCli)
|
|
535
|
+
*/
|
|
536
|
+
function openToolbarTaskDrawer(sessionId, taskId) {
|
|
537
|
+
openTaskDrawerForMultiCli(sessionId, taskId);
|
|
538
|
+
}
|
|
539
|
+
|
|
567
540
|
/**
|
|
568
541
|
* Scroll to task element in the DOM
|
|
569
542
|
*/
|
|
@@ -672,21 +645,28 @@ function switchMultiCliDetailTab(tabName) {
|
|
|
672
645
|
const contentArea = document.getElementById('multiCliDetailTabContent');
|
|
673
646
|
|
|
674
647
|
switch (tabName) {
|
|
675
|
-
case '
|
|
676
|
-
contentArea.innerHTML =
|
|
648
|
+
case 'tasks':
|
|
649
|
+
contentArea.innerHTML = renderMultiCliTasksTab(session);
|
|
677
650
|
break;
|
|
678
651
|
case 'discussion':
|
|
679
652
|
contentArea.innerHTML = renderMultiCliDiscussionSection(session);
|
|
680
653
|
break;
|
|
681
|
-
case '
|
|
682
|
-
|
|
683
|
-
|
|
654
|
+
case 'context':
|
|
655
|
+
loadAndRenderMultiCliContextTab(session, contentArea);
|
|
656
|
+
return; // Early return as this is async
|
|
657
|
+
case 'summary':
|
|
658
|
+
loadAndRenderMultiCliSummaryTab(session, contentArea);
|
|
659
|
+
return; // Early return as this is async
|
|
684
660
|
}
|
|
685
661
|
|
|
686
662
|
// Re-initialize after tab switch
|
|
687
663
|
setTimeout(() => {
|
|
688
664
|
if (typeof lucide !== 'undefined') lucide.createIcons();
|
|
689
665
|
initCollapsibleSections(contentArea);
|
|
666
|
+
// Initialize task click handlers for tasks tab
|
|
667
|
+
if (tabName === 'tasks') {
|
|
668
|
+
initMultiCliTaskClickHandlers();
|
|
669
|
+
}
|
|
690
670
|
}, 50);
|
|
691
671
|
}
|
|
692
672
|
|
|
@@ -695,222 +675,104 @@ function switchMultiCliDetailTab(tabName) {
|
|
|
695
675
|
// ============================================
|
|
696
676
|
|
|
697
677
|
/**
|
|
698
|
-
* Render
|
|
699
|
-
*
|
|
678
|
+
* Render Tasks tab - displays plan summary + tasks (same style as lite-plan)
|
|
679
|
+
* Uses session.tasks (normalized tasks) with fallback to session.plan.tasks
|
|
700
680
|
*/
|
|
701
|
-
function
|
|
702
|
-
const
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
return `
|
|
706
|
-
<div class="tab-empty-state">
|
|
707
|
-
<div class="empty-icon"><i data-lucide="message-circle" class="w-12 h-12"></i></div>
|
|
708
|
-
<div class="empty-title">${t('multiCli.empty.topic') || 'No Discussion Topic'}</div>
|
|
709
|
-
<div class="empty-text">${t('multiCli.empty.topicText') || 'No discussion topic data available for this session.'}</div>
|
|
710
|
-
</div>
|
|
711
|
-
`;
|
|
712
|
-
}
|
|
681
|
+
function renderMultiCliTasksTab(session) {
|
|
682
|
+
const plan = session.plan || {};
|
|
683
|
+
// Use session.tasks (normalized from backend) with fallback to plan.tasks
|
|
684
|
+
const tasks = session.tasks?.length > 0 ? session.tasks : (plan.tasks || []);
|
|
713
685
|
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
const scope = topic.scope || {};
|
|
717
|
-
const keyQuestions = topic.keyQuestions || [];
|
|
718
|
-
const status = topic.status || 'unknown';
|
|
719
|
-
const tags = topic.tags || [];
|
|
686
|
+
// Populate drawer tasks for click-to-open functionality
|
|
687
|
+
currentDrawerTasks = tasks;
|
|
720
688
|
|
|
721
689
|
let sections = [];
|
|
722
690
|
|
|
723
|
-
//
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
if (
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
</
|
|
757
|
-
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
// Key Questions
|
|
761
|
-
if (keyQuestions.length) {
|
|
762
|
-
sections.push(`
|
|
763
|
-
<div class="multi-cli-section questions-section">
|
|
764
|
-
<h4 class="section-title"><i data-lucide="help-circle" class="w-4 h-4 inline mr-1"></i> ${t('multiCli.keyQuestions') || 'Key Questions'}</h4>
|
|
765
|
-
<ol class="key-questions-list">
|
|
766
|
-
${keyQuestions.map(q => `<li>${escapeHtml(getI18nText(q))}</li>`).join('')}
|
|
767
|
-
</ol>
|
|
768
|
-
</div>
|
|
769
|
-
`);
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
return `<div class="multi-cli-topic-tab">${sections.join('')}</div>`;
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
/**
|
|
776
|
-
* Render Related Files tab
|
|
777
|
-
* Shows: fileTree, impactSummary
|
|
778
|
-
*/
|
|
779
|
-
function renderMultiCliFilesTab(session) {
|
|
780
|
-
// Use helper to extract files from synthesis data structure
|
|
781
|
-
const relatedFiles = extractFilesFromSynthesis(session.latestSynthesis);
|
|
782
|
-
|
|
783
|
-
if (!relatedFiles || (!relatedFiles.fileTree?.length && !relatedFiles.impactSummary?.length)) {
|
|
784
|
-
return `
|
|
785
|
-
<div class="tab-empty-state">
|
|
786
|
-
<div class="empty-icon"><i data-lucide="folder-tree" class="w-12 h-12"></i></div>
|
|
787
|
-
<div class="empty-title">${t('multiCli.empty.files') || 'No Related Files'}</div>
|
|
788
|
-
<div class="empty-text">${t('multiCli.empty.filesText') || 'No file analysis data available for this session.'}</div>
|
|
789
|
-
</div>
|
|
790
|
-
`;
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
const fileTree = relatedFiles.fileTree || [];
|
|
794
|
-
const impactSummary = relatedFiles.impactSummary || [];
|
|
795
|
-
const dependencyGraph = relatedFiles.dependencyGraph || [];
|
|
691
|
+
// Extract plan info from multiple sources (plan.json, synthesis, or session)
|
|
692
|
+
// plan.json: task_description, solution.name, execution_flow
|
|
693
|
+
// synthesis: solutions[].summary, solutions[].implementation_plan.approach
|
|
694
|
+
const taskDescription = plan.task_description || session.topicTitle || '';
|
|
695
|
+
const solutionName = plan.solution?.name || (plan.solutions?.[0]?.name) || '';
|
|
696
|
+
const solutionSummary = plan.solutions?.[0]?.summary || '';
|
|
697
|
+
const approach = plan.solutions?.[0]?.implementation_plan?.approach || plan.execution_flow || '';
|
|
698
|
+
const feasibility = plan.solution?.feasibility || plan.solutions?.[0]?.feasibility;
|
|
699
|
+
const effort = plan.solution?.effort || plan.solutions?.[0]?.effort || '';
|
|
700
|
+
const risk = plan.solution?.risk || plan.solutions?.[0]?.risk || '';
|
|
701
|
+
|
|
702
|
+
// Plan Summary Section (if any info available)
|
|
703
|
+
const hasInfo = taskDescription || solutionName || solutionSummary || approach || plan.summary;
|
|
704
|
+
if (hasInfo) {
|
|
705
|
+
let planInfo = [];
|
|
706
|
+
|
|
707
|
+
// Task description (main objective)
|
|
708
|
+
if (taskDescription) {
|
|
709
|
+
planInfo.push(`<p class="plan-summary-text"><strong>${t('multiCli.plan.objective')}:</strong> ${escapeHtml(taskDescription)}</p>`);
|
|
710
|
+
}
|
|
711
|
+
// Solution name and summary
|
|
712
|
+
if (solutionName) {
|
|
713
|
+
planInfo.push(`<p class="plan-solution-text"><strong>${t('multiCli.plan.solution')}:</strong> ${escapeHtml(solutionName)}</p>`);
|
|
714
|
+
}
|
|
715
|
+
if (solutionSummary) {
|
|
716
|
+
planInfo.push(`<p class="plan-summary-text">${escapeHtml(solutionSummary)}</p>`);
|
|
717
|
+
}
|
|
718
|
+
// Legacy summary field
|
|
719
|
+
if (plan.summary && !taskDescription && !solutionSummary) {
|
|
720
|
+
planInfo.push(`<p class="plan-summary-text">${escapeHtml(plan.summary)}</p>`);
|
|
721
|
+
}
|
|
722
|
+
// Approach/execution flow
|
|
723
|
+
if (approach) {
|
|
724
|
+
planInfo.push(`<p class="plan-approach-text"><strong>${t('multiCli.plan.approach')}:</strong> ${escapeHtml(approach)}</p>`);
|
|
725
|
+
}
|
|
796
726
|
|
|
797
|
-
|
|
727
|
+
// Metadata badges - concise format
|
|
728
|
+
let metaBadges = [];
|
|
729
|
+
if (feasibility) metaBadges.push(`<span class="meta-badge feasibility">${Math.round(feasibility * 100)}%</span>`);
|
|
730
|
+
if (effort) metaBadges.push(`<span class="meta-badge effort ${escapeHtml(effort)}">${escapeHtml(effort)}</span>`);
|
|
731
|
+
if (risk) metaBadges.push(`<span class="meta-badge risk ${escapeHtml(risk)}">${escapeHtml(risk)} ${t('multiCli.plan.risk')}</span>`);
|
|
732
|
+
// Legacy badges
|
|
733
|
+
if (plan.severity) metaBadges.push(`<span class="meta-badge severity ${escapeHtml(plan.severity)}">${escapeHtml(plan.severity)}</span>`);
|
|
734
|
+
if (plan.complexity) metaBadges.push(`<span class="meta-badge complexity">${escapeHtml(plan.complexity)}</span>`);
|
|
735
|
+
if (plan.estimated_time) metaBadges.push(`<span class="meta-badge time">${escapeHtml(plan.estimated_time)}</span>`);
|
|
798
736
|
|
|
799
|
-
// File Tree
|
|
800
|
-
if (fileTree.length) {
|
|
801
737
|
sections.push(`
|
|
802
|
-
<div class="
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
<span class="section-label"><i data-lucide="folder-tree" class="w-4 h-4 inline mr-1"></i> ${t('multiCli.fileTree') || 'File Tree'} (${fileTree.length})</span>
|
|
806
|
-
</div>
|
|
807
|
-
<div class="collapsible-content collapsed">
|
|
808
|
-
<div class="file-tree-list">
|
|
809
|
-
${renderFileTreeNodes(fileTree)}
|
|
810
|
-
</div>
|
|
811
|
-
</div>
|
|
738
|
+
<div class="plan-summary-section">
|
|
739
|
+
${planInfo.join('')}
|
|
740
|
+
${metaBadges.length ? `<div class="plan-meta-badges">${metaBadges.join(' ')}</div>` : ''}
|
|
812
741
|
</div>
|
|
813
742
|
`);
|
|
814
743
|
}
|
|
815
744
|
|
|
816
|
-
//
|
|
817
|
-
if (
|
|
745
|
+
// Tasks Section
|
|
746
|
+
if (tasks.length === 0) {
|
|
818
747
|
sections.push(`
|
|
819
|
-
<div class="
|
|
820
|
-
<div class="
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
</div>
|
|
824
|
-
<div class="collapsible-content collapsed">
|
|
825
|
-
<div class="impact-list">
|
|
826
|
-
${impactSummary.map(impact => `
|
|
827
|
-
<div class="impact-item impact-${impact.score || 'medium'}">
|
|
828
|
-
<div class="impact-header">
|
|
829
|
-
<code class="impact-file">${escapeHtml(impact.filePath || '')}</code>
|
|
830
|
-
${impact.line ? `<span class="impact-line">:${impact.line}</span>` : ''}
|
|
831
|
-
<span class="impact-score ${impact.score || 'medium'}">${escapeHtml(impact.score || 'medium')}</span>
|
|
832
|
-
</div>
|
|
833
|
-
${impact.reasoning ? `<div class="impact-reason">${escapeHtml(getI18nText(impact.reasoning))}</div>` : ''}
|
|
834
|
-
</div>
|
|
835
|
-
`).join('')}
|
|
836
|
-
</div>
|
|
837
|
-
</div>
|
|
748
|
+
<div class="tab-empty-state">
|
|
749
|
+
<div class="empty-icon"><i data-lucide="clipboard-list" class="w-12 h-12"></i></div>
|
|
750
|
+
<div class="empty-title">${t('empty.noTasks') || 'No Tasks'}</div>
|
|
751
|
+
<div class="empty-text">${t('empty.noTasksText') || 'No tasks available for this session.'}</div>
|
|
838
752
|
</div>
|
|
839
753
|
`);
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
// Dependency Graph
|
|
843
|
-
if (dependencyGraph.length) {
|
|
754
|
+
} else {
|
|
844
755
|
sections.push(`
|
|
845
|
-
<div class="
|
|
846
|
-
|
|
847
|
-
<span class="collapse-icon">►</span>
|
|
848
|
-
<span class="section-label"><i data-lucide="git-branch" class="w-4 h-4 inline mr-1"></i> ${t('multiCli.dependencies') || 'Dependencies'} (${dependencyGraph.length})</span>
|
|
849
|
-
</div>
|
|
850
|
-
<div class="collapsible-content collapsed">
|
|
851
|
-
<div class="deps-list">
|
|
852
|
-
${dependencyGraph.map(edge => `
|
|
853
|
-
<div class="dep-edge">
|
|
854
|
-
<code>${escapeHtml(edge.source || '')}</code>
|
|
855
|
-
<span class="dep-arrow">→</span>
|
|
856
|
-
<code>${escapeHtml(edge.target || '')}</code>
|
|
857
|
-
<span class="dep-relationship">(${escapeHtml(edge.relationship || 'depends')})</span>
|
|
858
|
-
</div>
|
|
859
|
-
`).join('')}
|
|
860
|
-
</div>
|
|
861
|
-
</div>
|
|
756
|
+
<div class="tasks-list" id="multiCliTasksListContent">
|
|
757
|
+
${tasks.map((task, idx) => renderMultiCliTaskItem(session.id, task, idx)).join('')}
|
|
862
758
|
</div>
|
|
863
759
|
`);
|
|
864
760
|
}
|
|
865
761
|
|
|
866
|
-
return
|
|
867
|
-
<div class="tab-empty-state">
|
|
868
|
-
<div class="empty-icon"><i data-lucide="folder-tree" class="w-12 h-12"></i></div>
|
|
869
|
-
<div class="empty-title">${t('multiCli.empty.files') || 'No Related Files'}</div>
|
|
870
|
-
</div>
|
|
871
|
-
`;
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
/**
|
|
875
|
-
* Render file tree nodes recursively
|
|
876
|
-
*/
|
|
877
|
-
function renderFileTreeNodes(nodes, depth = 0) {
|
|
878
|
-
return nodes.map(node => {
|
|
879
|
-
const indent = depth * 16;
|
|
880
|
-
const isDir = node.type === 'directory';
|
|
881
|
-
const icon = isDir ? 'folder' : 'file';
|
|
882
|
-
const modStatus = node.modificationStatus || 'unchanged';
|
|
883
|
-
const impactScore = node.impactScore || '';
|
|
884
|
-
|
|
885
|
-
let html = `
|
|
886
|
-
<div class="file-tree-node" style="margin-left: ${indent}px;">
|
|
887
|
-
<i data-lucide="${icon}" class="w-4 h-4 inline mr-1 file-icon ${modStatus}"></i>
|
|
888
|
-
<span class="file-path ${modStatus}">${escapeHtml(node.path || '')}</span>
|
|
889
|
-
${modStatus !== 'unchanged' ? `<span class="mod-status ${modStatus}">${modStatus}</span>` : ''}
|
|
890
|
-
${impactScore ? `<span class="impact-badge ${impactScore}">${impactScore}</span>` : ''}
|
|
891
|
-
</div>
|
|
892
|
-
`;
|
|
893
|
-
|
|
894
|
-
if (node.children?.length) {
|
|
895
|
-
html += renderFileTreeNodes(node.children, depth + 1);
|
|
896
|
-
}
|
|
897
|
-
|
|
898
|
-
return html;
|
|
899
|
-
}).join('');
|
|
762
|
+
return `<div class="tasks-tab-content">${sections.join('')}</div>`;
|
|
900
763
|
}
|
|
901
764
|
|
|
902
765
|
/**
|
|
903
|
-
* Render
|
|
904
|
-
* Reuses renderLitePlanTab style with Summary, Approach, Focus Paths, Metadata, and Tasks
|
|
766
|
+
* Render Plan tab - displays plan.json content (summary, approach, metadata)
|
|
905
767
|
*/
|
|
906
|
-
function
|
|
768
|
+
function renderMultiCliPlanTab(session) {
|
|
907
769
|
const plan = session.plan;
|
|
908
770
|
|
|
909
771
|
if (!plan) {
|
|
910
772
|
return `
|
|
911
773
|
<div class="tab-empty-state">
|
|
912
774
|
<div class="empty-icon"><i data-lucide="ruler" class="w-12 h-12"></i></div>
|
|
913
|
-
<div class="empty-title">${t('multiCli.empty.planning') || 'No
|
|
775
|
+
<div class="empty-title">${t('multiCli.empty.planning') || 'No Plan Data'}</div>
|
|
914
776
|
<div class="empty-text">${t('multiCli.empty.planningText') || 'No plan.json found for this session.'}</div>
|
|
915
777
|
</div>
|
|
916
778
|
`;
|
|
@@ -980,55 +842,568 @@ function renderMultiCliPlanningTab(session) {
|
|
|
980
842
|
</div>
|
|
981
843
|
</div>
|
|
982
844
|
|
|
983
|
-
<!--
|
|
984
|
-
|
|
985
|
-
<div class="
|
|
986
|
-
<
|
|
987
|
-
<
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
845
|
+
<!-- Raw JSON -->
|
|
846
|
+
<div class="plan-section collapsible-section">
|
|
847
|
+
<div class="collapsible-header">
|
|
848
|
+
<span class="collapse-icon">▶</span>
|
|
849
|
+
<span class="section-label">{ } Raw JSON</span>
|
|
850
|
+
</div>
|
|
851
|
+
<div class="collapsible-content collapsed">
|
|
852
|
+
<pre class="json-content">${escapeHtml(JSON.stringify(plan, null, 2))}</pre>
|
|
853
|
+
</div>
|
|
854
|
+
</div>
|
|
855
|
+
</div>
|
|
856
|
+
`;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
/**
|
|
860
|
+
* Load and render Context tab - displays context-package content
|
|
861
|
+
*/
|
|
862
|
+
async function loadAndRenderMultiCliContextTab(session, contentArea) {
|
|
863
|
+
contentArea.innerHTML = `<div class="tab-loading">${t('common.loading') || 'Loading...'}</div>`;
|
|
864
|
+
|
|
865
|
+
try {
|
|
866
|
+
if (window.SERVER_MODE && session.path) {
|
|
867
|
+
const response = await fetch(`/api/session-detail?path=${encodeURIComponent(session.path)}&type=context`);
|
|
868
|
+
if (response.ok) {
|
|
869
|
+
const data = await response.json();
|
|
870
|
+
contentArea.innerHTML = renderMultiCliContextContent(data.context, session);
|
|
871
|
+
initCollapsibleSections(contentArea);
|
|
872
|
+
if (typeof lucide !== 'undefined') lucide.createIcons();
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
// Fallback: show session context_package if available
|
|
877
|
+
contentArea.innerHTML = renderMultiCliContextContent(session.context_package, session);
|
|
878
|
+
initCollapsibleSections(contentArea);
|
|
879
|
+
if (typeof lucide !== 'undefined') lucide.createIcons();
|
|
880
|
+
} catch (err) {
|
|
881
|
+
contentArea.innerHTML = `<div class="tab-error">Failed to load context: ${err.message}</div>`;
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
/**
|
|
886
|
+
* Render context content for multi-cli
|
|
887
|
+
*/
|
|
888
|
+
function renderMultiCliContextContent(context, session) {
|
|
889
|
+
// Also check for context_package in session
|
|
890
|
+
const ctx = context || session.context_package || {};
|
|
891
|
+
|
|
892
|
+
if (!ctx || Object.keys(ctx).length === 0) {
|
|
893
|
+
return `
|
|
894
|
+
<div class="tab-empty-state">
|
|
895
|
+
<div class="empty-icon"><i data-lucide="package" class="w-12 h-12"></i></div>
|
|
896
|
+
<div class="empty-title">${t('empty.noContext') || 'No Context Data'}</div>
|
|
897
|
+
<div class="empty-text">${t('empty.noContextText') || 'No context package available for this session.'}</div>
|
|
898
|
+
</div>
|
|
899
|
+
`;
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
let sections = [];
|
|
903
|
+
|
|
904
|
+
// Task Description
|
|
905
|
+
if (ctx.task_description) {
|
|
906
|
+
sections.push(`
|
|
907
|
+
<div class="context-section">
|
|
908
|
+
<h4 class="context-section-title"><i data-lucide="file-text" class="w-4 h-4 inline mr-1"></i> ${t('multiCli.context.taskDescription')}</h4>
|
|
909
|
+
<p class="context-description">${escapeHtml(ctx.task_description)}</p>
|
|
910
|
+
</div>
|
|
911
|
+
`);
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
// Constraints
|
|
915
|
+
if (ctx.constraints?.length) {
|
|
916
|
+
sections.push(`
|
|
917
|
+
<div class="context-section">
|
|
918
|
+
<h4 class="context-section-title"><i data-lucide="alert-triangle" class="w-4 h-4 inline mr-1"></i> ${t('multiCli.context.constraints')}</h4>
|
|
919
|
+
<ul class="constraints-list">
|
|
920
|
+
${ctx.constraints.map(c => `<li>${escapeHtml(c)}</li>`).join('')}
|
|
921
|
+
</ul>
|
|
922
|
+
</div>
|
|
923
|
+
`);
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
// Focus Paths
|
|
927
|
+
if (ctx.focus_paths?.length) {
|
|
928
|
+
sections.push(`
|
|
929
|
+
<div class="context-section">
|
|
930
|
+
<h4 class="context-section-title"><i data-lucide="folder" class="w-4 h-4 inline mr-1"></i> ${t('multiCli.context.focusPaths')}</h4>
|
|
931
|
+
<div class="path-tags">
|
|
932
|
+
${ctx.focus_paths.map(p => `<span class="path-tag">${escapeHtml(p)}</span>`).join('')}
|
|
933
|
+
</div>
|
|
934
|
+
</div>
|
|
935
|
+
`);
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
// Relevant Files
|
|
939
|
+
if (ctx.relevant_files?.length) {
|
|
940
|
+
sections.push(`
|
|
941
|
+
<div class="context-section collapsible-section">
|
|
942
|
+
<div class="collapsible-header">
|
|
943
|
+
<span class="collapse-icon">▶</span>
|
|
944
|
+
<span class="section-label"><i data-lucide="files" class="w-4 h-4 inline mr-1"></i> ${t('multiCli.context.relevantFiles')} (${ctx.relevant_files.length})</span>
|
|
945
|
+
</div>
|
|
946
|
+
<div class="collapsible-content collapsed">
|
|
947
|
+
<ul class="files-list">
|
|
948
|
+
${ctx.relevant_files.map(f => `
|
|
949
|
+
<li class="file-item">
|
|
950
|
+
<span class="file-icon">📄</span>
|
|
951
|
+
<code>${escapeHtml(typeof f === 'string' ? f : f.path || f.file || '')}</code>
|
|
952
|
+
${f.reason ? `<span class="file-reason">${escapeHtml(f.reason)}</span>` : ''}
|
|
953
|
+
</li>
|
|
954
|
+
`).join('')}
|
|
955
|
+
</ul>
|
|
956
|
+
</div>
|
|
957
|
+
</div>
|
|
958
|
+
`);
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
// Dependencies
|
|
962
|
+
if (ctx.dependencies?.length) {
|
|
963
|
+
sections.push(`
|
|
964
|
+
<div class="context-section collapsible-section">
|
|
965
|
+
<div class="collapsible-header">
|
|
966
|
+
<span class="collapse-icon">▶</span>
|
|
967
|
+
<span class="section-label"><i data-lucide="git-branch" class="w-4 h-4 inline mr-1"></i> ${t('multiCli.context.dependencies')} (${ctx.dependencies.length})</span>
|
|
968
|
+
</div>
|
|
969
|
+
<div class="collapsible-content collapsed">
|
|
970
|
+
<ul class="deps-list">
|
|
971
|
+
${ctx.dependencies.map(d => `<li>${escapeHtml(typeof d === 'string' ? d : d.name || JSON.stringify(d))}</li>`).join('')}
|
|
972
|
+
</ul>
|
|
973
|
+
</div>
|
|
974
|
+
</div>
|
|
975
|
+
`);
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
// Conflict Risks
|
|
979
|
+
if (ctx.conflict_risks?.length) {
|
|
980
|
+
sections.push(`
|
|
981
|
+
<div class="context-section">
|
|
982
|
+
<h4 class="context-section-title"><i data-lucide="alert-circle" class="w-4 h-4 inline mr-1"></i> ${t('multiCli.context.conflictRisks')}</h4>
|
|
983
|
+
<ul class="risks-list">
|
|
984
|
+
${ctx.conflict_risks.map(r => `<li class="risk-item">${escapeHtml(typeof r === 'string' ? r : r.description || JSON.stringify(r))}</li>`).join('')}
|
|
985
|
+
</ul>
|
|
986
|
+
</div>
|
|
987
|
+
`);
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
// Session ID
|
|
991
|
+
if (ctx.session_id) {
|
|
992
|
+
sections.push(`
|
|
993
|
+
<div class="context-section">
|
|
994
|
+
<h4 class="context-section-title"><i data-lucide="hash" class="w-4 h-4 inline mr-1"></i> ${t('multiCli.context.sessionId')}</h4>
|
|
995
|
+
<code class="session-id-code">${escapeHtml(ctx.session_id)}</code>
|
|
996
|
+
</div>
|
|
997
|
+
`);
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
// Raw JSON
|
|
1001
|
+
sections.push(`
|
|
1002
|
+
<div class="context-section collapsible-section">
|
|
1003
|
+
<div class="collapsible-header">
|
|
1004
|
+
<span class="collapse-icon">▶</span>
|
|
1005
|
+
<span class="section-label">{ } ${t('multiCli.context.rawJson')}</span>
|
|
1006
|
+
</div>
|
|
1007
|
+
<div class="collapsible-content collapsed">
|
|
1008
|
+
<pre class="json-content">${escapeHtml(JSON.stringify(ctx, null, 2))}</pre>
|
|
1009
|
+
</div>
|
|
1010
|
+
</div>
|
|
1011
|
+
`);
|
|
1012
|
+
|
|
1013
|
+
return `<div class="context-tab-content">${sections.join('')}</div>`;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
/**
|
|
1017
|
+
* Load and render Summary tab
|
|
1018
|
+
*/
|
|
1019
|
+
async function loadAndRenderMultiCliSummaryTab(session, contentArea) {
|
|
1020
|
+
contentArea.innerHTML = `<div class="tab-loading">${t('common.loading') || 'Loading...'}</div>`;
|
|
1021
|
+
|
|
1022
|
+
try {
|
|
1023
|
+
if (window.SERVER_MODE && session.path) {
|
|
1024
|
+
const response = await fetch(`/api/session-detail?path=${encodeURIComponent(session.path)}&type=summary`);
|
|
1025
|
+
if (response.ok) {
|
|
1026
|
+
const data = await response.json();
|
|
1027
|
+
contentArea.innerHTML = renderMultiCliSummaryContent(data.summary, session);
|
|
1028
|
+
initCollapsibleSections(contentArea);
|
|
1029
|
+
if (typeof lucide !== 'undefined') lucide.createIcons();
|
|
1030
|
+
return;
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
// Fallback: show synthesis summary
|
|
1034
|
+
contentArea.innerHTML = renderMultiCliSummaryContent(null, session);
|
|
1035
|
+
initCollapsibleSections(contentArea);
|
|
1036
|
+
if (typeof lucide !== 'undefined') lucide.createIcons();
|
|
1037
|
+
} catch (err) {
|
|
1038
|
+
contentArea.innerHTML = `<div class="tab-error">Failed to load summary: ${err.message}</div>`;
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
/**
|
|
1043
|
+
* Render summary content for multi-cli
|
|
1044
|
+
*/
|
|
1045
|
+
function renderMultiCliSummaryContent(summary, session) {
|
|
1046
|
+
const synthesis = session.latestSynthesis || session.discussionTopic || {};
|
|
1047
|
+
const plan = session.plan || {};
|
|
1048
|
+
|
|
1049
|
+
// Use summary from file or build from synthesis
|
|
1050
|
+
const summaryText = summary || synthesis.convergence?.summary || plan.summary;
|
|
1051
|
+
|
|
1052
|
+
if (!summaryText && !synthesis.solutions?.length) {
|
|
1053
|
+
return `
|
|
1054
|
+
<div class="tab-empty-state">
|
|
1055
|
+
<div class="empty-icon"><i data-lucide="file-text" class="w-12 h-12"></i></div>
|
|
1056
|
+
<div class="empty-title">${t('empty.noSummary') || 'No Summary'}</div>
|
|
1057
|
+
<div class="empty-text">${t('empty.noSummaryText') || 'No summary available for this session.'}</div>
|
|
1058
|
+
</div>
|
|
1059
|
+
`;
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
let sections = [];
|
|
1063
|
+
|
|
1064
|
+
// Main Summary
|
|
1065
|
+
if (summaryText) {
|
|
1066
|
+
const summaryContent = typeof summaryText === 'string' ? summaryText : getI18nText(summaryText);
|
|
1067
|
+
sections.push(`
|
|
1068
|
+
<div class="summary-section">
|
|
1069
|
+
<h4 class="summary-section-title"><i data-lucide="file-text" class="w-4 h-4 inline mr-1"></i> ${t('multiCli.summary.title')}</h4>
|
|
1070
|
+
<div class="summary-content">${escapeHtml(summaryContent)}</div>
|
|
1071
|
+
</div>
|
|
1072
|
+
`);
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
// Convergence Status
|
|
1076
|
+
if (synthesis.convergence) {
|
|
1077
|
+
const conv = synthesis.convergence;
|
|
1078
|
+
sections.push(`
|
|
1079
|
+
<div class="summary-section">
|
|
1080
|
+
<h4 class="summary-section-title"><i data-lucide="git-merge" class="w-4 h-4 inline mr-1"></i> ${t('multiCli.summary.convergence')}</h4>
|
|
1081
|
+
<div class="convergence-info">
|
|
1082
|
+
<span class="convergence-level ${conv.level || ''}">${escapeHtml(conv.level || 'unknown')}</span>
|
|
1083
|
+
<span class="convergence-rec ${conv.recommendation || ''}">${escapeHtml(conv.recommendation || '')}</span>
|
|
1084
|
+
</div>
|
|
1085
|
+
</div>
|
|
1086
|
+
`);
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
// Solutions Summary
|
|
1090
|
+
if (synthesis.solutions?.length) {
|
|
1091
|
+
sections.push(`
|
|
1092
|
+
<div class="summary-section collapsible-section">
|
|
1093
|
+
<div class="collapsible-header">
|
|
1094
|
+
<span class="collapse-icon">▶</span>
|
|
1095
|
+
<span class="section-label"><i data-lucide="lightbulb" class="w-4 h-4 inline mr-1"></i> ${t('multiCli.summary.solutions')} (${synthesis.solutions.length})</span>
|
|
1096
|
+
</div>
|
|
1097
|
+
<div class="collapsible-content collapsed">
|
|
1098
|
+
${synthesis.solutions.map((sol, idx) => `
|
|
1099
|
+
<div class="solution-summary-item">
|
|
1100
|
+
<span class="solution-num">#${idx + 1}</span>
|
|
1101
|
+
<span class="solution-name">${escapeHtml(getI18nText(sol.title) || sol.id || `${t('multiCli.summary.solution')} ${idx + 1}`)}</span>
|
|
1102
|
+
${sol.feasibility?.score ? `<span class="feasibility-badge">${Math.round(sol.feasibility.score * 100)}%</span>` : ''}
|
|
1103
|
+
</div>
|
|
1104
|
+
`).join('')}
|
|
1105
|
+
</div>
|
|
1106
|
+
</div>
|
|
1107
|
+
`);
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
return `<div class="summary-tab-content">${sections.join('')}</div>`;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
/**
|
|
1114
|
+
* Render Discussion Topic tab
|
|
1115
|
+
* Shows: title, description, scope, keyQuestions, status, tags
|
|
1116
|
+
*/
|
|
1117
|
+
function renderMultiCliTopicTab(session) {
|
|
1118
|
+
const topic = session.discussionTopic || session.latestSynthesis?.discussionTopic || {};
|
|
1119
|
+
|
|
1120
|
+
if (!topic || Object.keys(topic).length === 0) {
|
|
1121
|
+
return `
|
|
1122
|
+
<div class="tab-empty-state">
|
|
1123
|
+
<div class="empty-icon"><i data-lucide="message-circle" class="w-12 h-12"></i></div>
|
|
1124
|
+
<div class="empty-title">${t('multiCli.empty.topic') || 'No Discussion Topic'}</div>
|
|
1125
|
+
<div class="empty-text">${t('multiCli.empty.topicText') || 'No discussion topic data available for this session.'}</div>
|
|
1126
|
+
</div>
|
|
1127
|
+
`;
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
const title = getI18nText(topic.title) || 'Untitled';
|
|
1131
|
+
const description = getI18nText(topic.description) || '';
|
|
1132
|
+
const scope = topic.scope || {};
|
|
1133
|
+
const keyQuestions = topic.keyQuestions || [];
|
|
1134
|
+
const status = topic.status || 'unknown';
|
|
1135
|
+
const tags = topic.tags || [];
|
|
1136
|
+
|
|
1137
|
+
let sections = [];
|
|
1138
|
+
|
|
1139
|
+
// Title and Description
|
|
1140
|
+
sections.push(`
|
|
1141
|
+
<div class="multi-cli-topic-section">
|
|
1142
|
+
<h3 class="multi-cli-topic-title">${escapeHtml(title)}</h3>
|
|
1143
|
+
${description ? `<p class="multi-cli-topic-description">${escapeHtml(description)}</p>` : ''}
|
|
1144
|
+
<div class="topic-meta">
|
|
1145
|
+
<span class="multi-cli-status ${status}">${escapeHtml(status)}</span>
|
|
1146
|
+
${tags.length ? tags.map(tag => `<span class="tag-badge">${escapeHtml(tag)}</span>`).join('') : ''}
|
|
1147
|
+
</div>
|
|
1148
|
+
</div>
|
|
1149
|
+
`);
|
|
1150
|
+
|
|
1151
|
+
// Scope (included/excluded)
|
|
1152
|
+
if (scope.included?.length || scope.excluded?.length) {
|
|
1153
|
+
sections.push(`
|
|
1154
|
+
<div class="multi-cli-section scope-section">
|
|
1155
|
+
<h4 class="section-title"><i data-lucide="target" class="w-4 h-4 inline mr-1"></i> ${t('multiCli.scope') || 'Scope'}</h4>
|
|
1156
|
+
${scope.included?.length ? `
|
|
1157
|
+
<div class="scope-included">
|
|
1158
|
+
<strong>${t('multiCli.scope.included') || 'Included'}:</strong>
|
|
1159
|
+
<ul class="scope-list">
|
|
1160
|
+
${scope.included.map(item => `<li>${escapeHtml(getI18nText(item))}</li>`).join('')}
|
|
1161
|
+
</ul>
|
|
1162
|
+
</div>
|
|
1163
|
+
` : ''}
|
|
1164
|
+
${scope.excluded?.length ? `
|
|
1165
|
+
<div class="scope-excluded">
|
|
1166
|
+
<strong>${t('multiCli.scope.excluded') || 'Excluded'}:</strong>
|
|
1167
|
+
<ul class="scope-list excluded">
|
|
1168
|
+
${scope.excluded.map(item => `<li>${escapeHtml(getI18nText(item))}</li>`).join('')}
|
|
1169
|
+
</ul>
|
|
1170
|
+
</div>
|
|
1171
|
+
` : ''}
|
|
1172
|
+
</div>
|
|
1173
|
+
`);
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
// Key Questions
|
|
1177
|
+
if (keyQuestions.length) {
|
|
1178
|
+
sections.push(`
|
|
1179
|
+
<div class="multi-cli-section questions-section">
|
|
1180
|
+
<h4 class="section-title"><i data-lucide="help-circle" class="w-4 h-4 inline mr-1"></i> ${t('multiCli.keyQuestions') || 'Key Questions'}</h4>
|
|
1181
|
+
<ol class="key-questions-list">
|
|
1182
|
+
${keyQuestions.map(q => `<li>${escapeHtml(getI18nText(q))}</li>`).join('')}
|
|
1183
|
+
</ol>
|
|
1184
|
+
</div>
|
|
1185
|
+
`);
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
return `<div class="multi-cli-topic-tab">${sections.join('')}</div>`;
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
/**
|
|
1192
|
+
* Render Related Files tab
|
|
1193
|
+
* Shows: fileTree, impactSummary
|
|
1194
|
+
*/
|
|
1195
|
+
function renderMultiCliFilesTab(session) {
|
|
1196
|
+
// Use helper to extract files from synthesis data structure
|
|
1197
|
+
const relatedFiles = extractFilesFromSynthesis(session.latestSynthesis);
|
|
1198
|
+
|
|
1199
|
+
if (!relatedFiles || (!relatedFiles.fileTree?.length && !relatedFiles.impactSummary?.length)) {
|
|
1200
|
+
return `
|
|
1201
|
+
<div class="tab-empty-state">
|
|
1202
|
+
<div class="empty-icon"><i data-lucide="folder-tree" class="w-12 h-12"></i></div>
|
|
1203
|
+
<div class="empty-title">${t('multiCli.empty.files') || 'No Related Files'}</div>
|
|
1204
|
+
<div class="empty-text">${t('multiCli.empty.filesText') || 'No file analysis data available for this session.'}</div>
|
|
1205
|
+
</div>
|
|
1206
|
+
`;
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
const fileTree = relatedFiles.fileTree || [];
|
|
1210
|
+
const impactSummary = relatedFiles.impactSummary || [];
|
|
1211
|
+
const dependencyGraph = relatedFiles.dependencyGraph || [];
|
|
1212
|
+
|
|
1213
|
+
let sections = [];
|
|
1214
|
+
|
|
1215
|
+
// File Tree
|
|
1216
|
+
if (fileTree.length) {
|
|
1217
|
+
sections.push(`
|
|
1218
|
+
<div class="multi-cli-section file-tree-section collapsible-section">
|
|
1219
|
+
<div class="collapsible-header">
|
|
1220
|
+
<span class="collapse-icon">►</span>
|
|
1221
|
+
<span class="section-label"><i data-lucide="folder-tree" class="w-4 h-4 inline mr-1"></i> ${t('multiCli.fileTree') || 'File Tree'} (${fileTree.length})</span>
|
|
1222
|
+
</div>
|
|
1223
|
+
<div class="collapsible-content collapsed">
|
|
1224
|
+
<div class="file-tree-list">
|
|
1225
|
+
${renderFileTreeNodes(fileTree)}
|
|
1226
|
+
</div>
|
|
1227
|
+
</div>
|
|
1228
|
+
</div>
|
|
1229
|
+
`);
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
// Impact Summary
|
|
1233
|
+
if (impactSummary.length) {
|
|
1234
|
+
sections.push(`
|
|
1235
|
+
<div class="multi-cli-section impact-section collapsible-section">
|
|
1236
|
+
<div class="collapsible-header">
|
|
1237
|
+
<span class="collapse-icon">►</span>
|
|
1238
|
+
<span class="section-label"><i data-lucide="alert-triangle" class="w-4 h-4 inline mr-1"></i> ${t('multiCli.impactSummary') || 'Impact Summary'} (${impactSummary.length})</span>
|
|
1239
|
+
</div>
|
|
1240
|
+
<div class="collapsible-content collapsed">
|
|
1241
|
+
<div class="impact-list">
|
|
1242
|
+
${impactSummary.map(impact => `
|
|
1243
|
+
<div class="impact-item impact-${impact.score || 'medium'}">
|
|
1244
|
+
<div class="impact-header">
|
|
1245
|
+
<code class="impact-file">${escapeHtml(impact.filePath || '')}</code>
|
|
1246
|
+
${impact.line ? `<span class="impact-line">:${impact.line}</span>` : ''}
|
|
1247
|
+
<span class="impact-score ${impact.score || 'medium'}">${escapeHtml(impact.score || 'medium')}</span>
|
|
1027
1248
|
</div>
|
|
1249
|
+
${impact.reasoning ? `<div class="impact-reason">${escapeHtml(getI18nText(impact.reasoning))}</div>` : ''}
|
|
1250
|
+
</div>
|
|
1251
|
+
`).join('')}
|
|
1252
|
+
</div>
|
|
1253
|
+
</div>
|
|
1254
|
+
</div>
|
|
1255
|
+
`);
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
// Dependency Graph
|
|
1259
|
+
if (dependencyGraph.length) {
|
|
1260
|
+
sections.push(`
|
|
1261
|
+
<div class="multi-cli-section deps-section collapsible-section">
|
|
1262
|
+
<div class="collapsible-header">
|
|
1263
|
+
<span class="collapse-icon">►</span>
|
|
1264
|
+
<span class="section-label"><i data-lucide="git-branch" class="w-4 h-4 inline mr-1"></i> ${t('multiCli.dependencies') || 'Dependencies'} (${dependencyGraph.length})</span>
|
|
1265
|
+
</div>
|
|
1266
|
+
<div class="collapsible-content collapsed">
|
|
1267
|
+
<div class="deps-list">
|
|
1268
|
+
${dependencyGraph.map(edge => `
|
|
1269
|
+
<div class="dep-edge">
|
|
1270
|
+
<code>${escapeHtml(edge.source || '')}</code>
|
|
1271
|
+
<span class="dep-arrow">→</span>
|
|
1272
|
+
<code>${escapeHtml(edge.target || '')}</code>
|
|
1273
|
+
<span class="dep-relationship">(${escapeHtml(edge.relationship || 'depends')})</span>
|
|
1028
1274
|
</div>
|
|
1029
1275
|
`).join('')}
|
|
1030
1276
|
</div>
|
|
1031
1277
|
</div>
|
|
1278
|
+
</div>
|
|
1279
|
+
`);
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
return sections.length ? `<div class="multi-cli-files-tab">${sections.join('')}</div>` : `
|
|
1283
|
+
<div class="tab-empty-state">
|
|
1284
|
+
<div class="empty-icon"><i data-lucide="folder-tree" class="w-12 h-12"></i></div>
|
|
1285
|
+
<div class="empty-title">${t('multiCli.empty.files') || 'No Related Files'}</div>
|
|
1286
|
+
</div>
|
|
1287
|
+
`;
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
/**
|
|
1291
|
+
* Render file tree nodes recursively
|
|
1292
|
+
*/
|
|
1293
|
+
function renderFileTreeNodes(nodes, depth = 0) {
|
|
1294
|
+
return nodes.map(node => {
|
|
1295
|
+
const indent = depth * 16;
|
|
1296
|
+
const isDir = node.type === 'directory';
|
|
1297
|
+
const icon = isDir ? 'folder' : 'file';
|
|
1298
|
+
const modStatus = node.modificationStatus || 'unchanged';
|
|
1299
|
+
const impactScore = node.impactScore || '';
|
|
1300
|
+
|
|
1301
|
+
let html = `
|
|
1302
|
+
<div class="file-tree-node" style="margin-left: ${indent}px;">
|
|
1303
|
+
<i data-lucide="${icon}" class="w-4 h-4 inline mr-1 file-icon ${modStatus}"></i>
|
|
1304
|
+
<span class="file-path ${modStatus}">${escapeHtml(node.path || '')}</span>
|
|
1305
|
+
${modStatus !== 'unchanged' ? `<span class="mod-status ${modStatus}">${modStatus}</span>` : ''}
|
|
1306
|
+
${impactScore ? `<span class="impact-badge ${impactScore}">${impactScore}</span>` : ''}
|
|
1307
|
+
</div>
|
|
1308
|
+
`;
|
|
1309
|
+
|
|
1310
|
+
if (node.children?.length) {
|
|
1311
|
+
html += renderFileTreeNodes(node.children, depth + 1);
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
return html;
|
|
1315
|
+
}).join('');
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
/**
|
|
1319
|
+
* Render Planning tab - displays session.plan (plan.json content)
|
|
1320
|
+
* Reuses renderLitePlanTab style with Summary, Approach, Focus Paths, Metadata, and Tasks
|
|
1321
|
+
*/
|
|
1322
|
+
function renderMultiCliPlanningTab(session) {
|
|
1323
|
+
const plan = session.plan;
|
|
1324
|
+
|
|
1325
|
+
if (!plan) {
|
|
1326
|
+
return `
|
|
1327
|
+
<div class="tab-empty-state">
|
|
1328
|
+
<div class="empty-icon"><i data-lucide="ruler" class="w-12 h-12"></i></div>
|
|
1329
|
+
<div class="empty-title">${t('multiCli.empty.planning') || 'No Planning Data'}</div>
|
|
1330
|
+
<div class="empty-text">${t('multiCli.empty.planningText') || 'No plan.json found for this session.'}</div>
|
|
1331
|
+
</div>
|
|
1332
|
+
`;
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
return `
|
|
1336
|
+
<div class="plan-tab-content">
|
|
1337
|
+
<!-- Summary -->
|
|
1338
|
+
${plan.summary ? `
|
|
1339
|
+
<div class="plan-section">
|
|
1340
|
+
<h4 class="plan-section-title"><i data-lucide="clipboard-list" class="w-4 h-4 inline mr-1"></i> Summary</h4>
|
|
1341
|
+
<p class="plan-summary-text">${escapeHtml(plan.summary)}</p>
|
|
1342
|
+
</div>
|
|
1343
|
+
` : ''}
|
|
1344
|
+
|
|
1345
|
+
<!-- Root Cause (fix-plan specific) -->
|
|
1346
|
+
${plan.root_cause ? `
|
|
1347
|
+
<div class="plan-section">
|
|
1348
|
+
<h4 class="plan-section-title"><i data-lucide="search" class="w-4 h-4 inline mr-1"></i> Root Cause</h4>
|
|
1349
|
+
<p class="plan-root-cause-text">${escapeHtml(plan.root_cause)}</p>
|
|
1350
|
+
</div>
|
|
1351
|
+
` : ''}
|
|
1352
|
+
|
|
1353
|
+
<!-- Strategy (fix-plan specific) -->
|
|
1354
|
+
${plan.strategy ? `
|
|
1355
|
+
<div class="plan-section">
|
|
1356
|
+
<h4 class="plan-section-title"><i data-lucide="route" class="w-4 h-4 inline mr-1"></i> Fix Strategy</h4>
|
|
1357
|
+
<p class="plan-strategy-text">${escapeHtml(plan.strategy)}</p>
|
|
1358
|
+
</div>
|
|
1359
|
+
` : ''}
|
|
1360
|
+
|
|
1361
|
+
<!-- Approach -->
|
|
1362
|
+
${plan.approach ? `
|
|
1363
|
+
<div class="plan-section">
|
|
1364
|
+
<h4 class="plan-section-title"><i data-lucide="target" class="w-4 h-4 inline mr-1"></i> Approach</h4>
|
|
1365
|
+
<p class="plan-approach-text">${escapeHtml(plan.approach)}</p>
|
|
1366
|
+
</div>
|
|
1367
|
+
` : ''}
|
|
1368
|
+
|
|
1369
|
+
<!-- User Requirements -->
|
|
1370
|
+
${plan.user_requirements ? `
|
|
1371
|
+
<div class="plan-section">
|
|
1372
|
+
<h4 class="plan-section-title"><i data-lucide="user" class="w-4 h-4 inline mr-1"></i> User Requirements</h4>
|
|
1373
|
+
<p class="plan-requirements-text">${escapeHtml(plan.user_requirements)}</p>
|
|
1374
|
+
</div>
|
|
1375
|
+
` : ''}
|
|
1376
|
+
|
|
1377
|
+
<!-- Focus Paths -->
|
|
1378
|
+
${plan.focus_paths?.length ? `
|
|
1379
|
+
<div class="plan-section">
|
|
1380
|
+
<h4 class="plan-section-title"><i data-lucide="folder" class="w-4 h-4 inline mr-1"></i> Focus Paths</h4>
|
|
1381
|
+
<div class="path-tags">
|
|
1382
|
+
${plan.focus_paths.map(p => `<span class="path-tag">${escapeHtml(p)}</span>`).join('')}
|
|
1383
|
+
</div>
|
|
1384
|
+
</div>
|
|
1385
|
+
` : ''}
|
|
1386
|
+
|
|
1387
|
+
<!-- Metadata -->
|
|
1388
|
+
<div class="plan-section">
|
|
1389
|
+
<h4 class="plan-section-title"><i data-lucide="info" class="w-4 h-4 inline mr-1"></i> Metadata</h4>
|
|
1390
|
+
<div class="plan-meta-grid">
|
|
1391
|
+
${plan.severity ? `<div class="meta-item"><span class="meta-label">Severity:</span> <span class="severity-badge ${escapeHtml(plan.severity)}">${escapeHtml(plan.severity)}</span></div>` : ''}
|
|
1392
|
+
${plan.risk_level ? `<div class="meta-item"><span class="meta-label">Risk Level:</span> <span class="risk-badge ${escapeHtml(plan.risk_level)}">${escapeHtml(plan.risk_level)}</span></div>` : ''}
|
|
1393
|
+
${plan.estimated_time ? `<div class="meta-item"><span class="meta-label">Estimated Time:</span> ${escapeHtml(plan.estimated_time)}</div>` : ''}
|
|
1394
|
+
${plan.complexity ? `<div class="meta-item"><span class="meta-label">Complexity:</span> ${escapeHtml(plan.complexity)}</div>` : ''}
|
|
1395
|
+
${plan.recommended_execution ? `<div class="meta-item"><span class="meta-label">Execution:</span> ${escapeHtml(plan.recommended_execution)}</div>` : ''}
|
|
1396
|
+
</div>
|
|
1397
|
+
</div>
|
|
1398
|
+
|
|
1399
|
+
<!-- Tasks (Click to view details) -->
|
|
1400
|
+
${plan.tasks?.length ? `
|
|
1401
|
+
<div class="plan-section">
|
|
1402
|
+
<h4 class="plan-section-title"><i data-lucide="list-checks" class="w-4 h-4 inline mr-1"></i> Tasks (${plan.tasks.length})</h4>
|
|
1403
|
+
<div class="tasks-list multi-cli-tasks-list">
|
|
1404
|
+
${plan.tasks.map((task, idx) => renderMultiCliTaskItem(session.id, task, idx)).join('')}
|
|
1405
|
+
</div>
|
|
1406
|
+
</div>
|
|
1032
1407
|
` : ''}
|
|
1033
1408
|
|
|
1034
1409
|
<!-- Raw JSON -->
|
|
@@ -1045,6 +1420,435 @@ function renderMultiCliPlanningTab(session) {
|
|
|
1045
1420
|
`;
|
|
1046
1421
|
}
|
|
1047
1422
|
|
|
1423
|
+
/**
|
|
1424
|
+
* Render a single task item for multi-cli-plan (reuses lite-plan style)
|
|
1425
|
+
* Supports click to open drawer for details
|
|
1426
|
+
*/
|
|
1427
|
+
function renderMultiCliTaskItem(sessionId, task, idx) {
|
|
1428
|
+
const taskId = task.id || `T${idx + 1}`;
|
|
1429
|
+
const taskJsonId = `multi-cli-task-${sessionId}-${taskId}`.replace(/[^a-zA-Z0-9-]/g, '-');
|
|
1430
|
+
taskJsonStore[taskJsonId] = task;
|
|
1431
|
+
|
|
1432
|
+
// Get preview info - handle both normalized and raw formats
|
|
1433
|
+
// Normalized: meta.type, meta.scope, context.focus_paths, context.acceptance, flow_control.implementation_approach
|
|
1434
|
+
// Raw: action, scope, file, modification_points, implementation, acceptance
|
|
1435
|
+
const taskType = task.meta?.type || task.action || '';
|
|
1436
|
+
const scope = task.meta?.scope || task.scope || task.file || '';
|
|
1437
|
+
const filesCount = task.context?.focus_paths?.length || task.files?.length || task.modification_points?.length || 0;
|
|
1438
|
+
const implCount = task.flow_control?.implementation_approach?.length || task.implementation?.length || 0;
|
|
1439
|
+
const acceptCount = task.context?.acceptance?.length || task.acceptance?.length || task.acceptance_criteria?.length || 0;
|
|
1440
|
+
const dependsCount = task.context?.depends_on?.length || task.depends_on?.length || 0;
|
|
1441
|
+
|
|
1442
|
+
// Escape for data attributes
|
|
1443
|
+
const safeSessionId = escapeHtml(sessionId);
|
|
1444
|
+
const safeTaskId = escapeHtml(taskId);
|
|
1445
|
+
|
|
1446
|
+
return `
|
|
1447
|
+
<div class="detail-task-item-full multi-cli-task-item" data-session-id="${safeSessionId}" data-task-id="${safeTaskId}" style="cursor: pointer;" title="Click to view details">
|
|
1448
|
+
<div class="task-item-header-lite">
|
|
1449
|
+
<span class="task-id-badge">${escapeHtml(taskId)}</span>
|
|
1450
|
+
<span class="task-title">${escapeHtml(task.title || task.name || task.summary || 'Untitled')}</span>
|
|
1451
|
+
<button class="btn-view-json" data-task-json-id="${taskJsonId}" data-task-display-id="${safeTaskId}">{ } JSON</button>
|
|
1452
|
+
</div>
|
|
1453
|
+
<div class="task-item-meta-lite">
|
|
1454
|
+
${taskType ? `<span class="meta-badge action">${escapeHtml(taskType)}</span>` : ''}
|
|
1455
|
+
${scope ? `<span class="meta-badge scope">${escapeHtml(scope)}</span>` : ''}
|
|
1456
|
+
${filesCount > 0 ? `<span class="meta-badge files">${filesCount} files</span>` : ''}
|
|
1457
|
+
${implCount > 0 ? `<span class="meta-badge impl">${implCount} steps</span>` : ''}
|
|
1458
|
+
${acceptCount > 0 ? `<span class="meta-badge accept">${acceptCount} criteria</span>` : ''}
|
|
1459
|
+
${dependsCount > 0 ? `<span class="meta-badge depends">${dependsCount} deps</span>` : ''}
|
|
1460
|
+
</div>
|
|
1461
|
+
</div>
|
|
1462
|
+
`;
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
/**
|
|
1466
|
+
* Initialize click handlers for multi-cli task items
|
|
1467
|
+
*/
|
|
1468
|
+
function initMultiCliTaskClickHandlers() {
|
|
1469
|
+
// Task item click handlers
|
|
1470
|
+
document.querySelectorAll('.multi-cli-task-item').forEach(item => {
|
|
1471
|
+
if (!item._clickBound) {
|
|
1472
|
+
item._clickBound = true;
|
|
1473
|
+
item.addEventListener('click', function(e) {
|
|
1474
|
+
// Don't trigger if clicking on JSON button
|
|
1475
|
+
if (e.target.closest('.btn-view-json')) return;
|
|
1476
|
+
|
|
1477
|
+
const sessionId = this.dataset.sessionId;
|
|
1478
|
+
const taskId = this.dataset.taskId;
|
|
1479
|
+
openTaskDrawerForMultiCli(sessionId, taskId);
|
|
1480
|
+
});
|
|
1481
|
+
}
|
|
1482
|
+
});
|
|
1483
|
+
|
|
1484
|
+
// JSON button click handlers
|
|
1485
|
+
document.querySelectorAll('.multi-cli-task-item .btn-view-json').forEach(btn => {
|
|
1486
|
+
if (!btn._clickBound) {
|
|
1487
|
+
btn._clickBound = true;
|
|
1488
|
+
btn.addEventListener('click', function(e) {
|
|
1489
|
+
e.stopPropagation();
|
|
1490
|
+
const taskJsonId = this.dataset.taskJsonId;
|
|
1491
|
+
const displayId = this.dataset.taskDisplayId;
|
|
1492
|
+
showJsonModal(taskJsonId, displayId);
|
|
1493
|
+
});
|
|
1494
|
+
}
|
|
1495
|
+
});
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
/**
|
|
1499
|
+
* Open task drawer for multi-cli task
|
|
1500
|
+
*/
|
|
1501
|
+
function openTaskDrawerForMultiCli(sessionId, taskId) {
|
|
1502
|
+
const session = liteTaskDataStore[currentSessionDetailKey];
|
|
1503
|
+
if (!session) return;
|
|
1504
|
+
|
|
1505
|
+
// Use session.tasks (normalized from backend) with fallback to plan.tasks
|
|
1506
|
+
const tasks = session.tasks?.length > 0 ? session.tasks : (session.plan?.tasks || []);
|
|
1507
|
+
const task = tasks.find(t => (t.id || `T${tasks.indexOf(t) + 1}`) === taskId);
|
|
1508
|
+
if (!task) return;
|
|
1509
|
+
|
|
1510
|
+
// Set current drawer tasks
|
|
1511
|
+
currentDrawerTasks = tasks;
|
|
1512
|
+
window._currentDrawerSession = session;
|
|
1513
|
+
|
|
1514
|
+
document.getElementById('drawerTaskTitle').textContent = task.title || task.name || task.summary || taskId;
|
|
1515
|
+
document.getElementById('drawerContent').innerHTML = renderMultiCliTaskDrawerContent(task, session);
|
|
1516
|
+
document.getElementById('taskDetailDrawer').classList.add('open');
|
|
1517
|
+
document.getElementById('drawerOverlay').classList.add('active');
|
|
1518
|
+
|
|
1519
|
+
// Re-init lucide icons in drawer
|
|
1520
|
+
setTimeout(() => {
|
|
1521
|
+
if (typeof lucide !== 'undefined') lucide.createIcons();
|
|
1522
|
+
}, 50);
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
/**
|
|
1526
|
+
* Render drawer content for multi-cli task
|
|
1527
|
+
*/
|
|
1528
|
+
function renderMultiCliTaskDrawerContent(task, session) {
|
|
1529
|
+
const taskId = task.id || 'Task';
|
|
1530
|
+
const action = task.action || '';
|
|
1531
|
+
|
|
1532
|
+
return `
|
|
1533
|
+
<!-- Task Header -->
|
|
1534
|
+
<div class="drawer-task-header">
|
|
1535
|
+
<span class="task-id-badge">${escapeHtml(taskId)}</span>
|
|
1536
|
+
${action ? `<span class="action-badge">${escapeHtml(action.toUpperCase())}</span>` : ''}
|
|
1537
|
+
</div>
|
|
1538
|
+
|
|
1539
|
+
<!-- Tab Navigation -->
|
|
1540
|
+
<div class="drawer-tabs">
|
|
1541
|
+
<button class="drawer-tab active" data-tab="overview" onclick="switchMultiCliDrawerTab('overview')">Overview</button>
|
|
1542
|
+
<button class="drawer-tab" data-tab="implementation" onclick="switchMultiCliDrawerTab('implementation')">Implementation</button>
|
|
1543
|
+
<button class="drawer-tab" data-tab="files" onclick="switchMultiCliDrawerTab('files')">Files</button>
|
|
1544
|
+
<button class="drawer-tab" data-tab="raw" onclick="switchMultiCliDrawerTab('raw')">Raw JSON</button>
|
|
1545
|
+
</div>
|
|
1546
|
+
|
|
1547
|
+
<!-- Tab Content -->
|
|
1548
|
+
<div class="drawer-tab-content">
|
|
1549
|
+
<!-- Overview Tab (default) -->
|
|
1550
|
+
<div class="drawer-panel active" data-tab="overview">
|
|
1551
|
+
${renderMultiCliTaskOverview(task)}
|
|
1552
|
+
</div>
|
|
1553
|
+
|
|
1554
|
+
<!-- Implementation Tab -->
|
|
1555
|
+
<div class="drawer-panel" data-tab="implementation">
|
|
1556
|
+
${renderMultiCliTaskImplementation(task)}
|
|
1557
|
+
</div>
|
|
1558
|
+
|
|
1559
|
+
<!-- Files Tab -->
|
|
1560
|
+
<div class="drawer-panel" data-tab="files">
|
|
1561
|
+
${renderMultiCliTaskFiles(task)}
|
|
1562
|
+
</div>
|
|
1563
|
+
|
|
1564
|
+
<!-- Raw JSON Tab -->
|
|
1565
|
+
<div class="drawer-panel" data-tab="raw">
|
|
1566
|
+
<pre class="json-view">${escapeHtml(JSON.stringify(task, null, 2))}</pre>
|
|
1567
|
+
</div>
|
|
1568
|
+
</div>
|
|
1569
|
+
`;
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
/**
|
|
1573
|
+
* Switch drawer tab for multi-cli task
|
|
1574
|
+
*/
|
|
1575
|
+
function switchMultiCliDrawerTab(tabName) {
|
|
1576
|
+
document.querySelectorAll('.drawer-tab').forEach(tab => {
|
|
1577
|
+
tab.classList.toggle('active', tab.dataset.tab === tabName);
|
|
1578
|
+
});
|
|
1579
|
+
document.querySelectorAll('.drawer-panel').forEach(panel => {
|
|
1580
|
+
panel.classList.toggle('active', panel.dataset.tab === tabName);
|
|
1581
|
+
});
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
/**
|
|
1585
|
+
* Render multi-cli task overview section
|
|
1586
|
+
* Handles both normalized format (meta, context, flow_control) and raw format
|
|
1587
|
+
*/
|
|
1588
|
+
function renderMultiCliTaskOverview(task) {
|
|
1589
|
+
let sections = [];
|
|
1590
|
+
|
|
1591
|
+
// Extract from both normalized and raw formats
|
|
1592
|
+
const description = task.description || (task.context?.requirements?.length > 0 ? task.context.requirements.join('\n') : '');
|
|
1593
|
+
const scope = task.meta?.scope || task.scope || task.file || '';
|
|
1594
|
+
const acceptance = task.context?.acceptance || task.acceptance || task.acceptance_criteria || [];
|
|
1595
|
+
const dependsOn = task.context?.depends_on || task.depends_on || [];
|
|
1596
|
+
const focusPaths = task.context?.focus_paths || task.files?.map(f => typeof f === 'string' ? f : f.file) || [];
|
|
1597
|
+
const keyPoint = task._raw?.task?.key_point || task.key_point || '';
|
|
1598
|
+
|
|
1599
|
+
// Description/Key Point Card
|
|
1600
|
+
if (description || keyPoint) {
|
|
1601
|
+
sections.push(`
|
|
1602
|
+
<div class="lite-card">
|
|
1603
|
+
<div class="lite-card-header">
|
|
1604
|
+
<span class="lite-card-icon">📝</span>
|
|
1605
|
+
<h4 class="lite-card-title">${t('multiCli.task.description')}</h4>
|
|
1606
|
+
</div>
|
|
1607
|
+
<div class="lite-card-body">
|
|
1608
|
+
${keyPoint ? `<p class="lite-key-point"><strong>${t('multiCli.task.keyPoint')}:</strong> ${escapeHtml(keyPoint)}</p>` : ''}
|
|
1609
|
+
${description ? `<p class="lite-description">${escapeHtml(description)}</p>` : ''}
|
|
1610
|
+
</div>
|
|
1611
|
+
</div>
|
|
1612
|
+
`);
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
// Scope Card
|
|
1616
|
+
if (scope) {
|
|
1617
|
+
sections.push(`
|
|
1618
|
+
<div class="lite-card">
|
|
1619
|
+
<div class="lite-card-header">
|
|
1620
|
+
<span class="lite-card-icon">📂</span>
|
|
1621
|
+
<h4 class="lite-card-title">${t('multiCli.task.scope')}</h4>
|
|
1622
|
+
</div>
|
|
1623
|
+
<div class="lite-card-body">
|
|
1624
|
+
<div class="lite-scope-box">
|
|
1625
|
+
<code>${escapeHtml(scope)}</code>
|
|
1626
|
+
</div>
|
|
1627
|
+
</div>
|
|
1628
|
+
</div>
|
|
1629
|
+
`);
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
// Dependencies Card
|
|
1633
|
+
if (dependsOn.length > 0) {
|
|
1634
|
+
sections.push(`
|
|
1635
|
+
<div class="lite-card">
|
|
1636
|
+
<div class="lite-card-header">
|
|
1637
|
+
<span class="lite-card-icon">🔗</span>
|
|
1638
|
+
<h4 class="lite-card-title">${t('multiCli.task.dependencies')}</h4>
|
|
1639
|
+
</div>
|
|
1640
|
+
<div class="lite-card-body">
|
|
1641
|
+
<div class="lite-deps-list">
|
|
1642
|
+
${dependsOn.map(dep => `<span class="dep-badge">${escapeHtml(dep)}</span>`).join('')}
|
|
1643
|
+
</div>
|
|
1644
|
+
</div>
|
|
1645
|
+
</div>
|
|
1646
|
+
`);
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
// Focus Paths / Files Card
|
|
1650
|
+
if (focusPaths.length > 0) {
|
|
1651
|
+
sections.push(`
|
|
1652
|
+
<div class="lite-card">
|
|
1653
|
+
<div class="lite-card-header">
|
|
1654
|
+
<span class="lite-card-icon">📁</span>
|
|
1655
|
+
<h4 class="lite-card-title">${t('multiCli.task.targetFiles')}</h4>
|
|
1656
|
+
</div>
|
|
1657
|
+
<div class="lite-card-body">
|
|
1658
|
+
<ul class="lite-file-list">
|
|
1659
|
+
${focusPaths.map(f => `<li><code>${escapeHtml(f)}</code></li>`).join('')}
|
|
1660
|
+
</ul>
|
|
1661
|
+
</div>
|
|
1662
|
+
</div>
|
|
1663
|
+
`);
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
// Acceptance Criteria Card
|
|
1667
|
+
if (acceptance.length > 0) {
|
|
1668
|
+
sections.push(`
|
|
1669
|
+
<div class="lite-card">
|
|
1670
|
+
<div class="lite-card-header">
|
|
1671
|
+
<span class="lite-card-icon">✅</span>
|
|
1672
|
+
<h4 class="lite-card-title">${t('multiCli.task.acceptanceCriteria')}</h4>
|
|
1673
|
+
</div>
|
|
1674
|
+
<div class="lite-card-body">
|
|
1675
|
+
<ul class="lite-acceptance-list">
|
|
1676
|
+
${acceptance.map(ac => `<li>${escapeHtml(ac)}</li>`).join('')}
|
|
1677
|
+
</ul>
|
|
1678
|
+
</div>
|
|
1679
|
+
</div>
|
|
1680
|
+
`);
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
// Reference Card
|
|
1684
|
+
if (task.reference) {
|
|
1685
|
+
const ref = task.reference;
|
|
1686
|
+
sections.push(`
|
|
1687
|
+
<div class="lite-card">
|
|
1688
|
+
<div class="lite-card-header">
|
|
1689
|
+
<span class="lite-card-icon">📚</span>
|
|
1690
|
+
<h4 class="lite-card-title">${t('multiCli.task.reference')}</h4>
|
|
1691
|
+
</div>
|
|
1692
|
+
<div class="lite-card-body">
|
|
1693
|
+
${ref.pattern ? `<div class="ref-item"><strong>${t('multiCli.task.pattern')}:</strong> ${escapeHtml(ref.pattern)}</div>` : ''}
|
|
1694
|
+
${ref.files?.length ? `<div class="ref-item"><strong>${t('multiCli.task.files')}:</strong><br><code class="ref-files">${ref.files.map(f => escapeHtml(f)).join('\n')}</code></div>` : ''}
|
|
1695
|
+
${ref.examples ? `<div class="ref-item"><strong>${t('multiCli.task.examples')}:</strong> ${escapeHtml(ref.examples)}</div>` : ''}
|
|
1696
|
+
</div>
|
|
1697
|
+
</div>
|
|
1698
|
+
`);
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
return sections.length ? sections.join('') : `<div class="empty-section">${t('multiCli.task.noOverviewData')}</div>`;
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
/**
|
|
1705
|
+
* Render multi-cli task implementation section
|
|
1706
|
+
* Handles both normalized format (flow_control.implementation_approach) and raw format
|
|
1707
|
+
*/
|
|
1708
|
+
function renderMultiCliTaskImplementation(task) {
|
|
1709
|
+
let sections = [];
|
|
1710
|
+
|
|
1711
|
+
// Get implementation steps from normalized or raw format
|
|
1712
|
+
const implApproach = task.flow_control?.implementation_approach || [];
|
|
1713
|
+
const rawImpl = task.implementation || [];
|
|
1714
|
+
const modPoints = task.modification_points || [];
|
|
1715
|
+
|
|
1716
|
+
// Modification Points / Flow Control Implementation Approach
|
|
1717
|
+
if (implApproach.length > 0) {
|
|
1718
|
+
sections.push(`
|
|
1719
|
+
<div class="drawer-section">
|
|
1720
|
+
<h4 class="drawer-section-title">
|
|
1721
|
+
<i data-lucide="list-ordered" class="w-4 h-4"></i>
|
|
1722
|
+
${t('multiCli.task.implementationSteps')}
|
|
1723
|
+
</h4>
|
|
1724
|
+
<ol class="impl-steps-detail-list">
|
|
1725
|
+
${implApproach.map((step, idx) => `
|
|
1726
|
+
<li class="impl-step-item">
|
|
1727
|
+
<span class="step-num">${step.step || (idx + 1)}</span>
|
|
1728
|
+
<span class="step-text">${escapeHtml(step.action || step)}</span>
|
|
1729
|
+
</li>
|
|
1730
|
+
`).join('')}
|
|
1731
|
+
</ol>
|
|
1732
|
+
</div>
|
|
1733
|
+
`);
|
|
1734
|
+
} else if (modPoints.length > 0) {
|
|
1735
|
+
sections.push(`
|
|
1736
|
+
<div class="drawer-section">
|
|
1737
|
+
<h4 class="drawer-section-title">
|
|
1738
|
+
<i data-lucide="file-edit" class="w-4 h-4"></i>
|
|
1739
|
+
${t('multiCli.task.modificationPoints')}
|
|
1740
|
+
</h4>
|
|
1741
|
+
<ul class="mod-points-detail-list">
|
|
1742
|
+
${modPoints.map(mp => `
|
|
1743
|
+
<li class="mod-point-item">
|
|
1744
|
+
<code class="mod-file">${escapeHtml(mp.file || '')}</code>
|
|
1745
|
+
${mp.target ? `<span class="mod-target">→ ${escapeHtml(mp.target)}</span>` : ''}
|
|
1746
|
+
${mp.function_name ? `<span class="mod-func">→ ${escapeHtml(mp.function_name)}</span>` : ''}
|
|
1747
|
+
${mp.change || mp.change_type ? `<span class="mod-change">(${escapeHtml(mp.change || mp.change_type)})</span>` : ''}
|
|
1748
|
+
</li>
|
|
1749
|
+
`).join('')}
|
|
1750
|
+
</ul>
|
|
1751
|
+
</div>
|
|
1752
|
+
`);
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
// Raw Implementation Steps (if not already rendered via implApproach)
|
|
1756
|
+
if (rawImpl.length > 0 && implApproach.length === 0) {
|
|
1757
|
+
sections.push(`
|
|
1758
|
+
<div class="drawer-section">
|
|
1759
|
+
<h4 class="drawer-section-title">
|
|
1760
|
+
<i data-lucide="list-ordered" class="w-4 h-4"></i>
|
|
1761
|
+
${t('multiCli.task.implementationSteps')}
|
|
1762
|
+
</h4>
|
|
1763
|
+
<ol class="impl-steps-detail-list">
|
|
1764
|
+
${rawImpl.map((step, idx) => `
|
|
1765
|
+
<li class="impl-step-item">
|
|
1766
|
+
<span class="step-num">${idx + 1}</span>
|
|
1767
|
+
<span class="step-text">${escapeHtml(step)}</span>
|
|
1768
|
+
</li>
|
|
1769
|
+
`).join('')}
|
|
1770
|
+
</ol>
|
|
1771
|
+
</div>
|
|
1772
|
+
`);
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
// Verification
|
|
1776
|
+
if (task.verification?.length) {
|
|
1777
|
+
sections.push(`
|
|
1778
|
+
<div class="drawer-section">
|
|
1779
|
+
<h4 class="drawer-section-title">
|
|
1780
|
+
<i data-lucide="check-circle" class="w-4 h-4"></i>
|
|
1781
|
+
${t('multiCli.task.verification')}
|
|
1782
|
+
</h4>
|
|
1783
|
+
<ul class="verification-list">
|
|
1784
|
+
${task.verification.map(v => `<li>${escapeHtml(v)}</li>`).join('')}
|
|
1785
|
+
</ul>
|
|
1786
|
+
</div>
|
|
1787
|
+
`);
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1790
|
+
return sections.length ? sections.join('') : `<div class="empty-section">${t('multiCli.task.noImplementationData')}</div>`;
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
/**
|
|
1794
|
+
* Render multi-cli task files section
|
|
1795
|
+
* Handles both normalized format (context.focus_paths) and raw format
|
|
1796
|
+
*/
|
|
1797
|
+
function renderMultiCliTaskFiles(task) {
|
|
1798
|
+
const files = [];
|
|
1799
|
+
|
|
1800
|
+
// Collect from normalized format (context.focus_paths)
|
|
1801
|
+
if (task.context?.focus_paths) {
|
|
1802
|
+
task.context.focus_paths.forEach(f => {
|
|
1803
|
+
if (f && !files.includes(f)) files.push(f);
|
|
1804
|
+
});
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1807
|
+
// Collect from raw files array (plan.json format)
|
|
1808
|
+
if (task.files) {
|
|
1809
|
+
task.files.forEach(f => {
|
|
1810
|
+
const filePath = typeof f === 'string' ? f : f.file;
|
|
1811
|
+
if (filePath && !files.includes(filePath)) files.push(filePath);
|
|
1812
|
+
});
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
// Collect from modification_points
|
|
1816
|
+
if (task.modification_points) {
|
|
1817
|
+
task.modification_points.forEach(mp => {
|
|
1818
|
+
if (mp.file && !files.includes(mp.file)) files.push(mp.file);
|
|
1819
|
+
});
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1822
|
+
// Collect from scope/file (legacy)
|
|
1823
|
+
if (task.scope && !files.includes(task.scope)) files.push(task.scope);
|
|
1824
|
+
if (task.file && !files.includes(task.file)) files.push(task.file);
|
|
1825
|
+
|
|
1826
|
+
// Collect from reference.files
|
|
1827
|
+
if (task.reference?.files) {
|
|
1828
|
+
task.reference.files.forEach(f => {
|
|
1829
|
+
if (f && !files.includes(f)) files.push(f);
|
|
1830
|
+
});
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1833
|
+
if (files.length === 0) {
|
|
1834
|
+
return `<div class="empty-section">${t('multiCli.task.noFilesSpecified')}</div>`;
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
return `
|
|
1838
|
+
<div class="drawer-section">
|
|
1839
|
+
<h4 class="drawer-section-title">${t('multiCli.task.targetFiles')}</h4>
|
|
1840
|
+
<ul class="target-files-list">
|
|
1841
|
+
${files.map(f => `
|
|
1842
|
+
<li class="file-item">
|
|
1843
|
+
<span class="file-icon">📄</span>
|
|
1844
|
+
<code>${escapeHtml(f)}</code>
|
|
1845
|
+
</li>
|
|
1846
|
+
`).join('')}
|
|
1847
|
+
</ul>
|
|
1848
|
+
</div>
|
|
1849
|
+
`;
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1048
1852
|
/**
|
|
1049
1853
|
* Render a single requirement item
|
|
1050
1854
|
*/
|
|
@@ -1587,7 +2391,7 @@ function renderMultiCliDiscussionSection(session) {
|
|
|
1587
2391
|
|
|
1588
2392
|
// If no rounds, show topic summary and current synthesis
|
|
1589
2393
|
if (!rounds.length) {
|
|
1590
|
-
const title = getI18nText(topic.title) || '
|
|
2394
|
+
const title = getI18nText(topic.title) || t('multiCli.discussion.discussionTopic');
|
|
1591
2395
|
const description = getI18nText(topic.description) || '';
|
|
1592
2396
|
const status = topic.status || session.status || 'analyzing';
|
|
1593
2397
|
|
|
@@ -1600,7 +2404,7 @@ function renderMultiCliDiscussionSection(session) {
|
|
|
1600
2404
|
${description ? `<p class="discussion-description">${escapeHtml(description)}</p>` : ''}
|
|
1601
2405
|
<div class="discussion-empty-state">
|
|
1602
2406
|
<i data-lucide="message-circle" class="w-8 h-8"></i>
|
|
1603
|
-
<p>${t('multiCli.singleRoundInfo')
|
|
2407
|
+
<p>${t('multiCli.singleRoundInfo')}</p>
|
|
1604
2408
|
</div>
|
|
1605
2409
|
</div>
|
|
1606
2410
|
`;
|
|
@@ -1652,16 +2456,16 @@ function renderMultiCliDiscussionSection(session) {
|
|
|
1652
2456
|
<!-- Solutions -->
|
|
1653
2457
|
${solutions.length ? `
|
|
1654
2458
|
<div class="round-solutions-summary">
|
|
1655
|
-
<h4 class="round-section-title"><i data-lucide="lightbulb" class="w-4 h-4 inline"></i> ${t('multiCli.solutions')
|
|
2459
|
+
<h4 class="round-section-title"><i data-lucide="lightbulb" class="w-4 h-4 inline"></i> ${t('multiCli.solutions')} (${solutions.length})</h4>
|
|
1656
2460
|
<div class="solution-cards-grid">
|
|
1657
2461
|
${solutions.map((sol, sidx) => `
|
|
1658
2462
|
<div class="solution-mini-card">
|
|
1659
2463
|
<div class="solution-mini-header">
|
|
1660
2464
|
<span class="solution-number">${sidx + 1}</span>
|
|
1661
|
-
<span class="solution-name">${escapeHtml(sol.name || '
|
|
2465
|
+
<span class="solution-name">${escapeHtml(sol.name || `${t('multiCli.summary.solution')} ${sidx + 1}`)}</span>
|
|
1662
2466
|
</div>
|
|
1663
2467
|
<div class="solution-mini-scores">
|
|
1664
|
-
<span class="score-pill feasibility" title="
|
|
2468
|
+
<span class="score-pill feasibility" title="${t('multiCli.feasibility')}">${Math.round((sol.feasibility || 0) * 100)}%</span>
|
|
1665
2469
|
<span class="score-pill effort-${sol.effort || 'medium'}">${escapeHtml(sol.effort || 'M')}</span>
|
|
1666
2470
|
<span class="score-pill risk-${sol.risk || 'medium'}">${escapeHtml(sol.risk || 'M')}</span>
|
|
1667
2471
|
</div>
|
|
@@ -1675,7 +2479,7 @@ function renderMultiCliDiscussionSection(session) {
|
|
|
1675
2479
|
<!-- Decision/Recommendation -->
|
|
1676
2480
|
${convergence.reasoning || round.decision ? `
|
|
1677
2481
|
<div class="round-decision">
|
|
1678
|
-
<h4 class="round-section-title"><i data-lucide="check-circle" class="w-4 h-4 inline"></i> ${t('multiCli.decision')
|
|
2482
|
+
<h4 class="round-section-title"><i data-lucide="check-circle" class="w-4 h-4 inline"></i> ${t('multiCli.decision')}</h4>
|
|
1679
2483
|
<p class="decision-text">${escapeHtml(convergence.reasoning || round.decision || '')}</p>
|
|
1680
2484
|
</div>
|
|
1681
2485
|
` : ''}
|
|
@@ -1687,8 +2491,8 @@ function renderMultiCliDiscussionSection(session) {
|
|
|
1687
2491
|
return `
|
|
1688
2492
|
<div class="multi-cli-discussion-section">
|
|
1689
2493
|
<div class="discussion-header">
|
|
1690
|
-
<h3 class="discussion-title">${escapeHtml(getI18nText(topic.title) || '
|
|
1691
|
-
<span class="rounds-count">${totalRounds} ${t('multiCli.tab.rounds')
|
|
2494
|
+
<h3 class="discussion-title">${escapeHtml(getI18nText(topic.title) || t('multiCli.discussion.title'))}</h3>
|
|
2495
|
+
<span class="rounds-count">${totalRounds} ${t('multiCli.tab.rounds')}</span>
|
|
1692
2496
|
</div>
|
|
1693
2497
|
<div class="discussion-accordion">
|
|
1694
2498
|
${accordionItems}
|