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.
@@ -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 discussionTopic = session.discussionTopic || {};
368
- const latestSynthesis = session.latestSynthesis || discussionTopic;
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 topicTitle = getI18nText(latestSynthesis.title) || session.topicTitle || 'Discussion Topic';
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 multi-cli-detail-page multi-cli-detail-with-toolbar">
375
- <!-- Main Content Area -->
376
- <div class="multi-cli-main-content">
377
- <!-- Header -->
378
- <div class="detail-header">
379
- <button class="btn-back" onclick="goBackToLiteTasks()">
380
- <span class="back-icon">&larr;</span>
381
- <span>${t('multiCli.backToList') || 'Back to Multi-CLI Plan'}</span>
382
- </button>
383
- <div class="detail-title-row">
384
- <h2 class="detail-session-id"><i data-lucide="messages-square" class="w-5 h-5 inline mr-2"></i> ${escapeHtml(session.id)}</h2>
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
- <!-- Tab Navigation -->
409
- <div class="detail-tabs">
410
- <button class="detail-tab active" data-tab="planning" onclick="switchMultiCliDetailTab('planning')">
411
- <span class="tab-icon"><i data-lucide="list-checks" class="w-4 h-4"></i></span>
412
- <span class="tab-text">${t('multiCli.tab.planning') || 'Planning'}</span>
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
- <!-- Tab Content -->
426
- <div class="detail-tab-content" id="multiCliDetailTabContent">
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
- <!-- Right Toolbar -->
432
- <div class="multi-cli-toolbar ${isMultiCliToolbarExpanded ? 'expanded' : 'collapsed'}" id="multiCliToolbar">
433
- <button class="toolbar-toggle-btn" onclick="toggleMultiCliToolbar()" title="${t('multiCli.toolbar.toggle') || 'Toggle Task Panel'}">
434
- <i data-lucide="${isMultiCliToolbarExpanded ? 'panel-right-close' : 'panel-right-open'}" class="w-5 h-5"></i>
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
- <div class="toolbar-content">
437
- ${renderMultiCliToolbar(session)}
438
- </div>
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 collapsible sections
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
- const tasks = plan?.tasks || [];
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 taskId = `task-${idx}`;
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="scrollToMultiCliTask(${idx})" data-task-idx="${idx}">
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 'planning':
676
- contentArea.innerHTML = renderMultiCliPlanningTab(session);
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 'association':
682
- contentArea.innerHTML = renderMultiCliAssociationSection(session);
683
- break;
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 Discussion Topic tab
699
- * Shows: title, description, scope, keyQuestions, status, tags
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 renderMultiCliTopicTab(session) {
702
- const topic = session.discussionTopic || session.latestSynthesis?.discussionTopic || {};
703
-
704
- if (!topic || Object.keys(topic).length === 0) {
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
- const title = getI18nText(topic.title) || 'Untitled';
715
- const description = getI18nText(topic.description) || '';
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
- // Title and Description
724
- sections.push(`
725
- <div class="multi-cli-topic-section">
726
- <h3 class="multi-cli-topic-title">${escapeHtml(title)}</h3>
727
- ${description ? `<p class="multi-cli-topic-description">${escapeHtml(description)}</p>` : ''}
728
- <div class="topic-meta">
729
- <span class="multi-cli-status ${status}">${escapeHtml(status)}</span>
730
- ${tags.length ? tags.map(tag => `<span class="tag-badge">${escapeHtml(tag)}</span>`).join('') : ''}
731
- </div>
732
- </div>
733
- `);
734
-
735
- // Scope (included/excluded)
736
- if (scope.included?.length || scope.excluded?.length) {
737
- sections.push(`
738
- <div class="multi-cli-section scope-section">
739
- <h4 class="section-title"><i data-lucide="target" class="w-4 h-4 inline mr-1"></i> ${t('multiCli.scope') || 'Scope'}</h4>
740
- ${scope.included?.length ? `
741
- <div class="scope-included">
742
- <strong>${t('multiCli.scope.included') || 'Included'}:</strong>
743
- <ul class="scope-list">
744
- ${scope.included.map(item => `<li>${escapeHtml(getI18nText(item))}</li>`).join('')}
745
- </ul>
746
- </div>
747
- ` : ''}
748
- ${scope.excluded?.length ? `
749
- <div class="scope-excluded">
750
- <strong>${t('multiCli.scope.excluded') || 'Excluded'}:</strong>
751
- <ul class="scope-list excluded">
752
- ${scope.excluded.map(item => `<li>${escapeHtml(getI18nText(item))}</li>`).join('')}
753
- </ul>
754
- </div>
755
- ` : ''}
756
- </div>
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
- let sections = [];
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="multi-cli-section file-tree-section collapsible-section">
803
- <div class="collapsible-header">
804
- <span class="collapse-icon">&#9658;</span>
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
- // Impact Summary
817
- if (impactSummary.length) {
745
+ // Tasks Section
746
+ if (tasks.length === 0) {
818
747
  sections.push(`
819
- <div class="multi-cli-section impact-section collapsible-section">
820
- <div class="collapsible-header">
821
- <span class="collapse-icon">&#9658;</span>
822
- <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>
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="multi-cli-section deps-section collapsible-section">
846
- <div class="collapsible-header">
847
- <span class="collapse-icon">&#9658;</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">&rarr;</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 sections.length ? `<div class="multi-cli-files-tab">${sections.join('')}</div>` : `
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 Planning tab - displays session.plan (plan.json content)
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 renderMultiCliPlanningTab(session) {
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 Planning Data'}</div>
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
- <!-- Tasks -->
984
- ${plan.tasks?.length ? `
985
- <div class="plan-section">
986
- <h4 class="plan-section-title"><i data-lucide="list-checks" class="w-4 h-4 inline mr-1"></i> Tasks (${plan.tasks.length})</h4>
987
- <div class="fix-tasks-summary">
988
- ${plan.tasks.map((task, idx) => `
989
- <div class="fix-task-summary-item collapsible-section">
990
- <div class="collapsible-header">
991
- <span class="collapse-icon">&#9654;</span>
992
- <span class="task-num">#${idx + 1}</span>
993
- <span class="task-title-brief">${escapeHtml(task.title || task.summary || 'Untitled')}</span>
994
- ${task.scope ? `<span class="task-scope-badge">${escapeHtml(task.scope)}</span>` : ''}
995
- </div>
996
- <div class="collapsible-content collapsed">
997
- ${task.modification_points?.length ? `
998
- <div class="task-detail-section">
999
- <strong>Modification Points:</strong>
1000
- <ul class="mod-points-list">
1001
- ${task.modification_points.map(mp => `
1002
- <li>
1003
- <code>${escapeHtml(mp.file || '')}</code>
1004
- ${mp.function_name ? `<span class="func-name">-> ${escapeHtml(mp.function_name)}</span>` : ''}
1005
- ${mp.change_type ? `<span class="change-type">(${escapeHtml(mp.change_type)})</span>` : ''}
1006
- </li>
1007
- `).join('')}
1008
- </ul>
1009
- </div>
1010
- ` : ''}
1011
- ${task.implementation?.length ? `
1012
- <div class="task-detail-section">
1013
- <strong>Implementation Steps:</strong>
1014
- <ol class="impl-steps-list">
1015
- ${task.implementation.map(step => `<li>${escapeHtml(step)}</li>`).join('')}
1016
- </ol>
1017
- </div>
1018
- ` : ''}
1019
- ${task.verification?.length ? `
1020
- <div class="task-detail-section">
1021
- <strong>Verification:</strong>
1022
- <ul class="verify-list">
1023
- ${task.verification.map(v => `<li>${escapeHtml(v)}</li>`).join('')}
1024
- </ul>
1025
- </div>
1026
- ` : ''}
845
+ <!-- Raw JSON -->
846
+ <div class="plan-section collapsible-section">
847
+ <div class="collapsible-header">
848
+ <span class="collapse-icon">&#9654;</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">&#9654;</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">&#9654;</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">&#9654;</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">&#9654;</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">&#9658;</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">&#9658;</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">&#9658;</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">&rarr;</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) || 'Discussion Topic';
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') || 'This is a single-round discussion. View Planning tab for execution details.'}</p>
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') || 'Solutions'} (${solutions.length})</h4>
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 || 'Solution ' + (sidx + 1))}</span>
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="Feasibility">${Math.round((sol.feasibility || 0) * 100)}%</span>
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') || 'Decision'}</h4>
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) || 'Discussion')}</h3>
1691
- <span class="rounds-count">${totalRounds} ${t('multiCli.tab.rounds') || 'Rounds'}</span>
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}