claude-code-workflow 6.3.27 → 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.
Files changed (133) hide show
  1. package/.claude/CLAUDE.md +7 -1
  2. package/.claude/agents/action-planning-agent.md +1 -0
  3. package/.claude/agents/cli-discuss-agent.md +391 -0
  4. package/.claude/agents/cli-execution-agent.md +2 -0
  5. package/.claude/agents/cli-explore-agent.md +2 -1
  6. package/.claude/agents/cli-lite-planning-agent.md +1 -0
  7. package/.claude/agents/cli-planning-agent.md +1 -0
  8. package/.claude/agents/code-developer.md +1 -0
  9. package/.claude/agents/conceptual-planning-agent.md +2 -0
  10. package/.claude/agents/context-search-agent.md +1 -0
  11. package/.claude/agents/debug-explore-agent.md +2 -0
  12. package/.claude/agents/doc-generator.md +1 -0
  13. package/.claude/agents/issue-plan-agent.md +2 -1
  14. package/.claude/agents/issue-queue-agent.md +2 -1
  15. package/.claude/agents/memory-bridge.md +2 -0
  16. package/.claude/agents/test-context-search-agent.md +2 -0
  17. package/.claude/agents/test-fix-agent.md +1 -0
  18. package/.claude/agents/ui-design-agent.md +2 -0
  19. package/.claude/agents/universal-executor.md +1 -0
  20. package/.claude/commands/issue/execute.md +269 -176
  21. package/.claude/commands/workflow/debug.md +6 -0
  22. package/.claude/commands/workflow/execute.md +4 -0
  23. package/.claude/commands/workflow/lite-execute.md +4 -0
  24. package/.claude/commands/workflow/lite-lite-lite.md +433 -0
  25. package/.claude/commands/workflow/multi-cli-plan.md +510 -0
  26. package/.claude/commands/workflow/review-fix.md +4 -0
  27. package/.claude/commands/workflow/test-cycle-execute.md +4 -0
  28. package/.claude/skills/ccw/SKILL.md +262 -372
  29. package/.claude/skills/ccw/command.json +547 -0
  30. package/.claude/skills/ccw-help/SKILL.md +46 -107
  31. package/.claude/skills/ccw-help/command.json +511 -0
  32. package/.claude/skills/skill-tuning/SKILL.md +303 -0
  33. package/.claude/skills/skill-tuning/phases/actions/action-abort.md +164 -0
  34. package/.claude/skills/skill-tuning/phases/actions/action-analyze-requirements.md +406 -0
  35. package/.claude/skills/skill-tuning/phases/actions/action-apply-fix.md +206 -0
  36. package/.claude/skills/skill-tuning/phases/actions/action-complete.md +195 -0
  37. package/.claude/skills/skill-tuning/phases/actions/action-diagnose-agent.md +317 -0
  38. package/.claude/skills/skill-tuning/phases/actions/action-diagnose-context.md +243 -0
  39. package/.claude/skills/skill-tuning/phases/actions/action-diagnose-dataflow.md +318 -0
  40. package/.claude/skills/skill-tuning/phases/actions/action-diagnose-docs.md +299 -0
  41. package/.claude/skills/skill-tuning/phases/actions/action-diagnose-memory.md +269 -0
  42. package/.claude/skills/skill-tuning/phases/actions/action-diagnose-token-consumption.md +200 -0
  43. package/.claude/skills/skill-tuning/phases/actions/action-gemini-analysis.md +322 -0
  44. package/.claude/skills/skill-tuning/phases/actions/action-generate-report.md +228 -0
  45. package/.claude/skills/skill-tuning/phases/actions/action-init.md +149 -0
  46. package/.claude/skills/skill-tuning/phases/actions/action-propose-fixes.md +317 -0
  47. package/.claude/skills/skill-tuning/phases/actions/action-verify.md +222 -0
  48. package/.claude/skills/skill-tuning/phases/orchestrator.md +377 -0
  49. package/.claude/skills/skill-tuning/phases/state-schema.md +378 -0
  50. package/.claude/skills/skill-tuning/specs/category-mappings.json +284 -0
  51. package/.claude/skills/skill-tuning/specs/dimension-mapping.md +212 -0
  52. package/.claude/skills/skill-tuning/specs/problem-taxonomy.md +318 -0
  53. package/.claude/skills/skill-tuning/specs/quality-gates.md +263 -0
  54. package/.claude/skills/skill-tuning/specs/skill-authoring-principles.md +189 -0
  55. package/.claude/skills/skill-tuning/specs/tuning-strategies.md +1537 -0
  56. package/.claude/skills/skill-tuning/templates/diagnosis-report.md +153 -0
  57. package/.claude/skills/skill-tuning/templates/fix-proposal.md +204 -0
  58. package/.claude/workflows/cli-templates/schemas/multi-cli-discussion-schema.json +421 -0
  59. package/.claude/workflows/cli-tools-usage.md +0 -41
  60. package/.codex/prompts/issue-execute.md +72 -12
  61. package/README.md +3 -0
  62. package/ccw/dist/core/data-aggregator.d.ts +2 -0
  63. package/ccw/dist/core/data-aggregator.d.ts.map +1 -1
  64. package/ccw/dist/core/data-aggregator.js +5 -2
  65. package/ccw/dist/core/data-aggregator.js.map +1 -1
  66. package/ccw/dist/core/lite-scanner.d.ts +2 -1
  67. package/ccw/dist/core/lite-scanner.d.ts.map +1 -1
  68. package/ccw/dist/core/lite-scanner.js +348 -6
  69. package/ccw/dist/core/lite-scanner.js.map +1 -1
  70. package/ccw/dist/core/routes/session-routes.d.ts.map +1 -1
  71. package/ccw/dist/core/routes/session-routes.js +166 -48
  72. package/ccw/dist/core/routes/session-routes.js.map +1 -1
  73. package/ccw/dist/core/routes/system-routes.d.ts.map +1 -1
  74. package/ccw/dist/core/routes/system-routes.js +87 -0
  75. package/ccw/dist/core/routes/system-routes.js.map +1 -1
  76. package/ccw/dist/core/server.js +2 -2
  77. package/ccw/dist/core/server.js.map +1 -1
  78. package/ccw/dist/tools/memory-update-queue.d.ts.map +1 -1
  79. package/ccw/dist/tools/memory-update-queue.js +5 -11
  80. package/ccw/dist/tools/memory-update-queue.js.map +1 -1
  81. package/ccw/scripts/IMPLEMENTATION-SUMMARY.md +226 -0
  82. package/ccw/scripts/QUICK-REFERENCE.md +135 -0
  83. package/ccw/scripts/README-memory-embedder.md +157 -0
  84. package/ccw/scripts/__pycache__/memory_embedder.cpython-313.pyc +0 -0
  85. package/ccw/scripts/__pycache__/test_memory_embedder.cpython-313-pytest-8.4.2.pyc +0 -0
  86. package/ccw/scripts/memory-embedder-example.ts +184 -0
  87. package/ccw/scripts/memory_embedder.py +428 -0
  88. package/ccw/scripts/test_memory_embedder.py +245 -0
  89. package/ccw/src/core/data-aggregator.ts +7 -2
  90. package/ccw/src/core/lite-scanner.ts +495 -6
  91. package/ccw/src/core/routes/session-routes.ts +201 -48
  92. package/ccw/src/core/routes/system-routes.ts +102 -0
  93. package/ccw/src/core/server.ts +2 -2
  94. package/ccw/src/templates/dashboard-css/01-base.css +8 -0
  95. package/ccw/src/templates/dashboard-css/02-session.css +81 -0
  96. package/ccw/src/templates/dashboard-css/03-tasks.css +5 -0
  97. package/ccw/src/templates/dashboard-css/04-lite-tasks.css +3071 -0
  98. package/ccw/src/templates/dashboard-css/21-cli-toolmgmt.css +157 -0
  99. package/ccw/src/templates/dashboard-css/32-issue-manager.css +23 -0
  100. package/ccw/src/templates/dashboard-js/components/cli-stream-viewer.js +38 -4
  101. package/ccw/src/templates/dashboard-js/components/hook-manager.js +68 -34
  102. package/ccw/src/templates/dashboard-js/components/navigation.js +24 -4
  103. package/ccw/src/templates/dashboard-js/i18n.js +278 -4
  104. package/ccw/src/templates/dashboard-js/views/api-settings.js +32 -0
  105. package/ccw/src/templates/dashboard-js/views/claude-manager.js +44 -3
  106. package/ccw/src/templates/dashboard-js/views/cli-manager.js +303 -31
  107. package/ccw/src/templates/dashboard-js/views/history.js +44 -6
  108. package/ccw/src/templates/dashboard-js/views/home.js +1 -0
  109. package/ccw/src/templates/dashboard-js/views/issue-manager.js +54 -7
  110. package/ccw/src/templates/dashboard-js/views/lite-tasks.js +2621 -4
  111. package/ccw/src/templates/dashboard.html +5 -0
  112. package/ccw/src/tools/memory-update-queue.js +5 -11
  113. package/package.json +2 -1
  114. package/.claude/skills/ccw/index/command-capabilities.json +0 -127
  115. package/.claude/skills/ccw/index/intent-rules.json +0 -136
  116. package/.claude/skills/ccw/index/workflow-chains.json +0 -451
  117. package/.claude/skills/ccw/phases/actions/bugfix.md +0 -218
  118. package/.claude/skills/ccw/phases/actions/coupled.md +0 -194
  119. package/.claude/skills/ccw/phases/actions/docs.md +0 -93
  120. package/.claude/skills/ccw/phases/actions/full.md +0 -154
  121. package/.claude/skills/ccw/phases/actions/issue.md +0 -201
  122. package/.claude/skills/ccw/phases/actions/rapid.md +0 -104
  123. package/.claude/skills/ccw/phases/actions/review-fix.md +0 -84
  124. package/.claude/skills/ccw/phases/actions/tdd.md +0 -66
  125. package/.claude/skills/ccw/phases/actions/ui.md +0 -79
  126. package/.claude/skills/ccw/phases/orchestrator.md +0 -435
  127. package/.claude/skills/ccw/specs/intent-classification.md +0 -336
  128. package/.claude/skills/ccw-help/index/all-agents.json +0 -82
  129. package/.claude/skills/ccw-help/index/all-commands.json +0 -882
  130. package/.claude/skills/ccw-help/index/by-category.json +0 -914
  131. package/.claude/skills/ccw-help/index/by-use-case.json +0 -896
  132. package/.claude/skills/ccw-help/index/command-relationships.json +0 -160
  133. package/.claude/skills/ccw-help/index/essential-commands.json +0 -112
@@ -7,9 +7,17 @@ function renderLiteTasks() {
7
7
  const container = document.getElementById('mainContent');
8
8
 
9
9
  const liteTasks = workflowData.liteTasks || {};
10
- const sessions = currentLiteType === 'lite-plan'
11
- ? liteTasks.litePlan || []
12
- : liteTasks.liteFix || [];
10
+ let sessions;
11
+
12
+ if (currentLiteType === 'lite-plan') {
13
+ sessions = liteTasks.litePlan || [];
14
+ } else if (currentLiteType === 'lite-fix') {
15
+ sessions = liteTasks.liteFix || [];
16
+ } else if (currentLiteType === 'multi-cli-plan') {
17
+ sessions = liteTasks.multiCliPlan || [];
18
+ } else {
19
+ sessions = [];
20
+ }
13
21
 
14
22
  if (sessions.length === 0) {
15
23
  container.innerHTML = `
@@ -23,7 +31,12 @@ function renderLiteTasks() {
23
31
  return;
24
32
  }
25
33
 
26
- container.innerHTML = `<div class="sessions-grid">${sessions.map(session => renderLiteTaskCard(session)).join('')}</div>`;
34
+ // Render based on type
35
+ if (currentLiteType === 'multi-cli-plan') {
36
+ container.innerHTML = `<div class="sessions-grid">${sessions.map(session => renderMultiCliCard(session)).join('')}</div>`;
37
+ } else {
38
+ container.innerHTML = `<div class="sessions-grid">${sessions.map(session => renderLiteTaskCard(session)).join('')}</div>`;
39
+ }
27
40
 
28
41
  // Initialize Lucide icons
29
42
  if (typeof lucide !== 'undefined') lucide.createIcons();
@@ -68,6 +81,2610 @@ function renderLiteTaskCard(session) {
68
81
  `;
69
82
  }
70
83
 
84
+ // ============================================
85
+ // MULTI-CLI PLAN VIEW
86
+ // ============================================
87
+
88
+ /**
89
+ * Render a card for multi-cli-plan session
90
+ * Shows: Session ID, round count, topic title, status, created date
91
+ */
92
+ function renderMultiCliCard(session) {
93
+ const sessionKey = `multi-cli-${session.id}`.replace(/[^a-zA-Z0-9-]/g, '-');
94
+ liteTaskDataStore[sessionKey] = session;
95
+
96
+ // Extract info from latest synthesis or metadata
97
+ const metadata = session.metadata || {};
98
+ const latestSynthesis = session.latestSynthesis || session.discussionTopic || {};
99
+ const roundCount = metadata.roundId || session.roundCount || 1;
100
+ const topicTitle = getI18nText(latestSynthesis.title) || session.topicTitle || 'Discussion Topic';
101
+ const status = latestSynthesis.status || session.status || 'analyzing';
102
+ const createdAt = metadata.timestamp || session.createdAt || '';
103
+
104
+ // Status badge color mapping
105
+ const statusColors = {
106
+ 'decided': 'success',
107
+ 'converged': 'success',
108
+ 'plan_generated': 'success',
109
+ 'completed': 'success',
110
+ 'exploring': 'info',
111
+ 'initialized': 'info',
112
+ 'analyzing': 'warning',
113
+ 'debating': 'warning',
114
+ 'blocked': 'error',
115
+ 'conflict': 'error'
116
+ };
117
+ const statusColor = statusColors[status] || 'default';
118
+
119
+ return `
120
+ <div class="session-card multi-cli-card" onclick="showMultiCliDetailPage('${sessionKey}')" style="cursor: pointer;">
121
+ <div class="session-header">
122
+ <div class="session-title">${escapeHtml(session.id)}</div>
123
+ <span class="session-status multi-cli-plan">
124
+ <i data-lucide="messages-square" class="w-3 h-3 inline"></i> ${t('lite.multiCli') || 'Multi-CLI'}
125
+ </span>
126
+ </div>
127
+ <div class="session-body">
128
+ <div class="multi-cli-topic">
129
+ <i data-lucide="message-circle" class="w-4 h-4 inline mr-1"></i>
130
+ <span class="topic-title">${escapeHtml(topicTitle)}</span>
131
+ </div>
132
+ <div class="session-meta">
133
+ <span class="session-meta-item"><i data-lucide="calendar" class="w-3.5 h-3.5 inline mr-1"></i>${formatDate(createdAt)}</span>
134
+ <span class="session-meta-item"><i data-lucide="repeat" class="w-3.5 h-3.5 inline mr-1"></i>${roundCount} ${t('multiCli.rounds') || 'rounds'}</span>
135
+ <span class="session-meta-item status-badge ${statusColor}"><i data-lucide="activity" class="w-3.5 h-3.5 inline mr-1"></i>${escapeHtml(status)}</span>
136
+ </div>
137
+ </div>
138
+ </div>
139
+ `;
140
+ }
141
+
142
+ /**
143
+ * Get text from i18n label object (supports {en, zh} format)
144
+ */
145
+ function getI18nText(label) {
146
+ if (!label) return '';
147
+ if (typeof label === 'string') return label;
148
+ // Return based on current language or default to English
149
+ const lang = window.currentLanguage || 'en';
150
+ return label[lang] || label.en || label.zh || '';
151
+ }
152
+
153
+ // ============================================
154
+ // SYNTHESIS DATA TRANSFORMATION HELPERS
155
+ // ============================================
156
+
157
+ /**
158
+ * Extract files from synthesis solutions[].implementation_plan.tasks[].files
159
+ * Returns object with fileTree and impactSummary arrays
160
+ */
161
+ function extractFilesFromSynthesis(synthesis) {
162
+ if (!synthesis || !synthesis.solutions) {
163
+ return { fileTree: [], impactSummary: [], dependencyGraph: [] };
164
+ }
165
+
166
+ const fileSet = new Set();
167
+ const impactMap = new Map();
168
+
169
+ synthesis.solutions.forEach(solution => {
170
+ const tasks = solution.implementation_plan?.tasks || [];
171
+ tasks.forEach(task => {
172
+ const files = task.files || [];
173
+ files.forEach(filePath => {
174
+ fileSet.add(filePath);
175
+ // Build impact summary based on task context
176
+ if (!impactMap.has(filePath)) {
177
+ impactMap.set(filePath, {
178
+ filePath: filePath,
179
+ score: 'medium',
180
+ reasoning: { en: `Part of ${solution.title?.en || solution.id} implementation`, zh: `${solution.title?.zh || solution.id} 实现的一部分` }
181
+ });
182
+ }
183
+ });
184
+ });
185
+ });
186
+
187
+ // Convert to fileTree format (flat list with file type)
188
+ const fileTree = Array.from(fileSet).map(path => ({
189
+ path: path,
190
+ type: 'file',
191
+ modificationStatus: 'modified',
192
+ impactScore: 'medium'
193
+ }));
194
+
195
+ return {
196
+ fileTree: fileTree,
197
+ impactSummary: Array.from(impactMap.values()),
198
+ dependencyGraph: []
199
+ };
200
+ }
201
+
202
+ /**
203
+ * Extract planning data from synthesis solutions[].implementation_plan
204
+ * Builds planning object with functional requirements format
205
+ */
206
+ function extractPlanningFromSynthesis(synthesis) {
207
+ if (!synthesis || !synthesis.solutions) {
208
+ return { functional: [], nonFunctional: [], acceptanceCriteria: [] };
209
+ }
210
+
211
+ const functional = [];
212
+ const acceptanceCriteria = [];
213
+ let reqCounter = 1;
214
+ let acCounter = 1;
215
+
216
+ synthesis.solutions.forEach(solution => {
217
+ const plan = solution.implementation_plan;
218
+ if (!plan) return;
219
+
220
+ // Extract approach as functional requirement
221
+ if (plan.approach) {
222
+ functional.push({
223
+ id: `FR-${String(reqCounter++).padStart(3, '0')}`,
224
+ description: plan.approach,
225
+ priority: solution.feasibility?.score >= 0.8 ? 'high' : 'medium',
226
+ source: solution.id
227
+ });
228
+ }
229
+
230
+ // Extract tasks as acceptance criteria
231
+ const tasks = plan.tasks || [];
232
+ tasks.forEach(task => {
233
+ acceptanceCriteria.push({
234
+ id: `AC-${String(acCounter++).padStart(3, '0')}`,
235
+ description: task.title || { en: task.id, zh: task.id },
236
+ isMet: false
237
+ });
238
+ });
239
+ });
240
+
241
+ return {
242
+ functional: functional,
243
+ nonFunctional: [],
244
+ acceptanceCriteria: acceptanceCriteria
245
+ };
246
+ }
247
+
248
+ /**
249
+ * Extract decision data from synthesis solutions
250
+ * Sorts by feasibility score, returns highest as selected, rest as rejected
251
+ */
252
+ function extractDecisionFromSynthesis(synthesis) {
253
+ if (!synthesis || !synthesis.solutions || synthesis.solutions.length === 0) {
254
+ return {};
255
+ }
256
+
257
+ // Sort solutions by feasibility score (highest first)
258
+ const sortedSolutions = [...synthesis.solutions].sort((a, b) => {
259
+ const scoreA = a.feasibility?.score || 0;
260
+ const scoreB = b.feasibility?.score || 0;
261
+ return scoreB - scoreA;
262
+ });
263
+
264
+ const selectedSolution = sortedSolutions[0];
265
+ const rejectedAlternatives = sortedSolutions.slice(1).map(sol => ({
266
+ ...sol,
267
+ rejectionReason: sol.cons?.length > 0 ? sol.cons[0] : { en: 'Lower feasibility score', zh: '可行性评分较低' }
268
+ }));
269
+
270
+ // Calculate confidence from convergence level
271
+ let confidenceScore = 0.5;
272
+ if (synthesis.convergence) {
273
+ const level = synthesis.convergence.level;
274
+ if (level === 'high' || level === 'converged') confidenceScore = 0.9;
275
+ else if (level === 'medium') confidenceScore = 0.7;
276
+ else if (level === 'low') confidenceScore = 0.4;
277
+ }
278
+
279
+ return {
280
+ status: synthesis.convergence?.recommendation || 'pending',
281
+ summary: synthesis.convergence?.summary || {},
282
+ selectedSolution: selectedSolution,
283
+ rejectedAlternatives: rejectedAlternatives,
284
+ confidenceScore: confidenceScore
285
+ };
286
+ }
287
+
288
+ /**
289
+ * Extract timeline data from synthesis convergence and cross_verification
290
+ * Builds timeline array with events from discussion process
291
+ */
292
+ function extractTimelineFromSynthesis(synthesis) {
293
+ if (!synthesis) {
294
+ return [];
295
+ }
296
+
297
+ const timeline = [];
298
+ const now = new Date().toISOString();
299
+
300
+ // Add convergence summary as decision event
301
+ if (synthesis.convergence?.summary) {
302
+ timeline.push({
303
+ type: 'decision',
304
+ timestamp: now,
305
+ summary: synthesis.convergence.summary,
306
+ contributor: { name: 'Synthesis', id: 'synthesis' },
307
+ reversibility: synthesis.convergence.recommendation === 'proceed' ? 'irreversible' : 'reversible'
308
+ });
309
+ }
310
+
311
+ // Add cross-verification agreements as agreement events
312
+ const agreements = synthesis.cross_verification?.agreements || [];
313
+ agreements.forEach((agreement, idx) => {
314
+ timeline.push({
315
+ type: 'agreement',
316
+ timestamp: now,
317
+ summary: typeof agreement === 'string' ? { en: agreement, zh: agreement } : agreement,
318
+ contributor: { name: 'Cross-Verification', id: 'cross-verify' },
319
+ evidence: []
320
+ });
321
+ });
322
+
323
+ // Add cross-verification disagreements as disagreement events
324
+ const disagreements = synthesis.cross_verification?.disagreements || [];
325
+ disagreements.forEach((disagreement, idx) => {
326
+ timeline.push({
327
+ type: 'disagreement',
328
+ timestamp: now,
329
+ summary: typeof disagreement === 'string' ? { en: disagreement, zh: disagreement } : disagreement,
330
+ contributor: { name: 'Cross-Verification', id: 'cross-verify' },
331
+ evidence: []
332
+ });
333
+ });
334
+
335
+ // Add solutions as proposal events
336
+ const solutions = synthesis.solutions || [];
337
+ solutions.forEach(solution => {
338
+ timeline.push({
339
+ type: 'proposal',
340
+ timestamp: now,
341
+ summary: solution.description || solution.title || {},
342
+ contributor: { name: solution.id, id: solution.id },
343
+ evidence: solution.pros?.map(p => ({ type: 'pro', description: p })) || []
344
+ });
345
+ });
346
+
347
+ return timeline;
348
+ }
349
+
350
+ /**
351
+ * Show multi-cli detail page with tabs (same layout as lite-plan)
352
+ */
353
+ function showMultiCliDetailPage(sessionKey) {
354
+ const session = liteTaskDataStore[sessionKey];
355
+ if (!session) return;
356
+
357
+ currentView = 'multiCliDetail';
358
+ currentSessionDetailKey = sessionKey;
359
+
360
+ hideStatsAndCarousel();
361
+
362
+ const container = document.getElementById('mainContent');
363
+ const metadata = session.metadata || {};
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 || []);
367
+ const roundCount = metadata.roundId || session.roundCount || 1;
368
+ const status = session.status || 'analyzing';
369
+
370
+ container.innerHTML = `
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>
382
+ </div>
383
+ </div>
384
+ </div>
385
+
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>
391
+ </div>
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>
395
+ </div>
396
+ </div>
397
+
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>
404
+ </button>
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)}
423
+ </div>
424
+ </div>
425
+ `;
426
+
427
+ // Initialize icons, collapsible sections, and task click handlers
428
+ setTimeout(() => {
429
+ if (typeof lucide !== 'undefined') lucide.createIcons();
430
+ initCollapsibleSections(container);
431
+ initMultiCliTaskClickHandlers();
432
+ }, 50);
433
+ }
434
+
435
+ /**
436
+ * Render the multi-cli toolbar content
437
+ */
438
+ function renderMultiCliToolbar(session) {
439
+ const plan = session.plan;
440
+ // Use session.tasks (normalized from backend) with fallback to plan.tasks
441
+ const tasks = session.tasks?.length > 0 ? session.tasks : (plan?.tasks || []);
442
+ const taskCount = tasks.length;
443
+
444
+ let toolbarHtml = `
445
+ <div class="toolbar-header">
446
+ <h4 class="toolbar-title">
447
+ <i data-lucide="list-checks" class="w-4 h-4"></i>
448
+ <span>${t('multiCli.toolbar.tasks') || 'Tasks'}</span>
449
+ <span class="toolbar-count">${taskCount}</span>
450
+ </h4>
451
+ </div>
452
+ `;
453
+
454
+ // Quick Actions
455
+ toolbarHtml += `
456
+ <div class="toolbar-actions">
457
+ <button class="toolbar-action-btn" onclick="refreshMultiCliToolbar()" title="${t('multiCli.toolbar.refresh') || 'Refresh'}">
458
+ <i data-lucide="refresh-cw" class="w-4 h-4"></i>
459
+ </button>
460
+ <button class="toolbar-action-btn" onclick="exportMultiCliPlanJson()" title="${t('multiCli.toolbar.export') || 'Export JSON'}">
461
+ <i data-lucide="download" class="w-4 h-4"></i>
462
+ </button>
463
+ <button class="toolbar-action-btn" onclick="viewMultiCliRawJson()" title="${t('multiCli.toolbar.viewRaw') || 'View Raw Data'}">
464
+ <i data-lucide="code" class="w-4 h-4"></i>
465
+ </button>
466
+ </div>
467
+ `;
468
+
469
+ // Task List
470
+ if (tasks.length > 0) {
471
+ toolbarHtml += `
472
+ <div class="toolbar-task-list">
473
+ ${tasks.map((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}`;
477
+
478
+ return `
479
+ <div class="toolbar-task-item" onclick="openToolbarTaskDrawer('${escapeHtml(session.id)}', '${escapeHtml(taskIdValue)}')" data-task-idx="${idx}">
480
+ <span class="toolbar-task-num">#${idx + 1}</span>
481
+ <div class="toolbar-task-info">
482
+ <span class="toolbar-task-title" title="${escapeHtml(taskTitle)}">${escapeHtml(taskTitle)}</span>
483
+ ${taskScope ? `<span class="toolbar-task-scope">${escapeHtml(taskScope)}</span>` : ''}
484
+ </div>
485
+ </div>
486
+ `;
487
+ }).join('')}
488
+ </div>
489
+ `;
490
+ } else {
491
+ toolbarHtml += `
492
+ <div class="toolbar-empty">
493
+ <i data-lucide="inbox" class="w-8 h-8"></i>
494
+ <span>${t('multiCli.toolbar.noTasks') || 'No tasks available'}</span>
495
+ </div>
496
+ `;
497
+ }
498
+
499
+ // Session Info
500
+ toolbarHtml += `
501
+ <div class="toolbar-session-info">
502
+ <div class="toolbar-info-item">
503
+ <span class="toolbar-info-label">${t('multiCli.toolbar.sessionId') || 'Session'}</span>
504
+ <span class="toolbar-info-value" title="${escapeHtml(session.id)}">${escapeHtml(session.id)}</span>
505
+ </div>
506
+ ${plan?.summary ? `
507
+ <div class="toolbar-info-item">
508
+ <span class="toolbar-info-label">${t('multiCli.toolbar.summary') || 'Summary'}</span>
509
+ <span class="toolbar-info-value toolbar-summary" title="${escapeHtml(plan.summary)}">${escapeHtml(plan.summary)}</span>
510
+ </div>
511
+ ` : ''}
512
+ </div>
513
+ `;
514
+
515
+ return toolbarHtml;
516
+ }
517
+
518
+ /**
519
+ * Scroll to a specific task in the planning tab
520
+ */
521
+ function scrollToMultiCliTask(taskIdx) {
522
+ // Switch to planning tab if not active
523
+ const planningTab = document.querySelector('.detail-tab[data-tab="planning"]');
524
+ if (planningTab && !planningTab.classList.contains('active')) {
525
+ switchMultiCliDetailTab('planning');
526
+ // Wait for tab content to render
527
+ setTimeout(() => scrollToTaskElement(taskIdx), 100);
528
+ } else {
529
+ scrollToTaskElement(taskIdx);
530
+ }
531
+ }
532
+
533
+ /**
534
+ * Open task drawer from toolbar (wrapper for openTaskDrawerForMultiCli)
535
+ */
536
+ function openToolbarTaskDrawer(sessionId, taskId) {
537
+ openTaskDrawerForMultiCli(sessionId, taskId);
538
+ }
539
+
540
+ /**
541
+ * Scroll to task element in the DOM
542
+ */
543
+ function scrollToTaskElement(taskIdx) {
544
+ const taskItems = document.querySelectorAll('.fix-task-summary-item');
545
+ if (taskItems[taskIdx]) {
546
+ taskItems[taskIdx].scrollIntoView({ behavior: 'smooth', block: 'center' });
547
+ // Highlight the task briefly
548
+ taskItems[taskIdx].classList.add('toolbar-highlight');
549
+ setTimeout(() => {
550
+ taskItems[taskIdx].classList.remove('toolbar-highlight');
551
+ }, 2000);
552
+ // Expand the collapsible if collapsed
553
+ const header = taskItems[taskIdx].querySelector('.collapsible-header');
554
+ const content = taskItems[taskIdx].querySelector('.collapsible-content');
555
+ if (header && content && content.classList.contains('collapsed')) {
556
+ header.click();
557
+ }
558
+ }
559
+ }
560
+
561
+ /**
562
+ * Refresh the toolbar content
563
+ */
564
+ function refreshMultiCliToolbar() {
565
+ const session = liteTaskDataStore[currentSessionDetailKey];
566
+ if (!session) return;
567
+
568
+ const toolbarContent = document.querySelector('.toolbar-content');
569
+ if (toolbarContent) {
570
+ toolbarContent.innerHTML = renderMultiCliToolbar(session);
571
+ if (typeof lucide !== 'undefined') lucide.createIcons();
572
+ }
573
+ }
574
+
575
+ /**
576
+ * Export plan.json content
577
+ */
578
+ function exportMultiCliPlanJson() {
579
+ const session = liteTaskDataStore[currentSessionDetailKey];
580
+ if (!session || !session.plan) {
581
+ if (typeof showRefreshToast === 'function') {
582
+ showRefreshToast(t('multiCli.toolbar.noPlan') || 'No plan data available', 'warning');
583
+ }
584
+ return;
585
+ }
586
+
587
+ const jsonStr = JSON.stringify(session.plan, null, 2);
588
+ const blob = new Blob([jsonStr], { type: 'application/json' });
589
+ const url = URL.createObjectURL(blob);
590
+ const a = document.createElement('a');
591
+ a.href = url;
592
+ a.download = `plan-${session.id}.json`;
593
+ document.body.appendChild(a);
594
+ a.click();
595
+ document.body.removeChild(a);
596
+ URL.revokeObjectURL(url);
597
+
598
+ if (typeof showRefreshToast === 'function') {
599
+ showRefreshToast(t('multiCli.toolbar.exported') || 'Plan exported successfully', 'success');
600
+ }
601
+ }
602
+
603
+ /**
604
+ * View raw session JSON in modal
605
+ */
606
+ function viewMultiCliRawJson() {
607
+ const session = liteTaskDataStore[currentSessionDetailKey];
608
+ if (!session) return;
609
+
610
+ // Reuse existing JSON modal pattern
611
+ const overlay = document.createElement('div');
612
+ overlay.className = 'json-modal-overlay active';
613
+ overlay.innerHTML = `
614
+ <div class="json-modal">
615
+ <div class="json-modal-header">
616
+ <div class="json-modal-title">
617
+ <span class="session-id-badge">${escapeHtml(session.id)}</span>
618
+ <span>${t('multiCli.toolbar.rawData') || 'Raw Session Data'}</span>
619
+ </div>
620
+ <button class="json-modal-close" onclick="closeJsonModal(this)">&times;</button>
621
+ </div>
622
+ <div class="json-modal-body">
623
+ <pre class="json-modal-content">${escapeHtml(JSON.stringify(session, null, 2))}</pre>
624
+ </div>
625
+ <div class="json-modal-footer">
626
+ <button class="btn-copy-json" onclick="copyJsonToClipboard(this)">${t('action.copy') || 'Copy to Clipboard'}</button>
627
+ </div>
628
+ </div>
629
+ `;
630
+ document.body.appendChild(overlay);
631
+ }
632
+
633
+ /**
634
+ * Switch between multi-cli detail tabs
635
+ */
636
+ function switchMultiCliDetailTab(tabName) {
637
+ // Update active tab
638
+ document.querySelectorAll('.detail-tab').forEach(tab => {
639
+ tab.classList.toggle('active', tab.dataset.tab === tabName);
640
+ });
641
+
642
+ const session = liteTaskDataStore[currentSessionDetailKey];
643
+ if (!session) return;
644
+
645
+ const contentArea = document.getElementById('multiCliDetailTabContent');
646
+
647
+ switch (tabName) {
648
+ case 'tasks':
649
+ contentArea.innerHTML = renderMultiCliTasksTab(session);
650
+ break;
651
+ case 'discussion':
652
+ contentArea.innerHTML = renderMultiCliDiscussionSection(session);
653
+ 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
660
+ }
661
+
662
+ // Re-initialize after tab switch
663
+ setTimeout(() => {
664
+ if (typeof lucide !== 'undefined') lucide.createIcons();
665
+ initCollapsibleSections(contentArea);
666
+ // Initialize task click handlers for tasks tab
667
+ if (tabName === 'tasks') {
668
+ initMultiCliTaskClickHandlers();
669
+ }
670
+ }, 50);
671
+ }
672
+
673
+ // ============================================
674
+ // MULTI-CLI TAB RENDERERS
675
+ // ============================================
676
+
677
+ /**
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
680
+ */
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 || []);
685
+
686
+ // Populate drawer tasks for click-to-open functionality
687
+ currentDrawerTasks = tasks;
688
+
689
+ let sections = [];
690
+
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
+ }
726
+
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>`);
736
+
737
+ sections.push(`
738
+ <div class="plan-summary-section">
739
+ ${planInfo.join('')}
740
+ ${metaBadges.length ? `<div class="plan-meta-badges">${metaBadges.join(' ')}</div>` : ''}
741
+ </div>
742
+ `);
743
+ }
744
+
745
+ // Tasks Section
746
+ if (tasks.length === 0) {
747
+ sections.push(`
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>
752
+ </div>
753
+ `);
754
+ } else {
755
+ sections.push(`
756
+ <div class="tasks-list" id="multiCliTasksListContent">
757
+ ${tasks.map((task, idx) => renderMultiCliTaskItem(session.id, task, idx)).join('')}
758
+ </div>
759
+ `);
760
+ }
761
+
762
+ return `<div class="tasks-tab-content">${sections.join('')}</div>`;
763
+ }
764
+
765
+ /**
766
+ * Render Plan tab - displays plan.json content (summary, approach, metadata)
767
+ */
768
+ function renderMultiCliPlanTab(session) {
769
+ const plan = session.plan;
770
+
771
+ if (!plan) {
772
+ return `
773
+ <div class="tab-empty-state">
774
+ <div class="empty-icon"><i data-lucide="ruler" class="w-12 h-12"></i></div>
775
+ <div class="empty-title">${t('multiCli.empty.planning') || 'No Plan Data'}</div>
776
+ <div class="empty-text">${t('multiCli.empty.planningText') || 'No plan.json found for this session.'}</div>
777
+ </div>
778
+ `;
779
+ }
780
+
781
+ return `
782
+ <div class="plan-tab-content">
783
+ <!-- Summary -->
784
+ ${plan.summary ? `
785
+ <div class="plan-section">
786
+ <h4 class="plan-section-title"><i data-lucide="clipboard-list" class="w-4 h-4 inline mr-1"></i> Summary</h4>
787
+ <p class="plan-summary-text">${escapeHtml(plan.summary)}</p>
788
+ </div>
789
+ ` : ''}
790
+
791
+ <!-- Root Cause (fix-plan specific) -->
792
+ ${plan.root_cause ? `
793
+ <div class="plan-section">
794
+ <h4 class="plan-section-title"><i data-lucide="search" class="w-4 h-4 inline mr-1"></i> Root Cause</h4>
795
+ <p class="plan-root-cause-text">${escapeHtml(plan.root_cause)}</p>
796
+ </div>
797
+ ` : ''}
798
+
799
+ <!-- Strategy (fix-plan specific) -->
800
+ ${plan.strategy ? `
801
+ <div class="plan-section">
802
+ <h4 class="plan-section-title"><i data-lucide="route" class="w-4 h-4 inline mr-1"></i> Fix Strategy</h4>
803
+ <p class="plan-strategy-text">${escapeHtml(plan.strategy)}</p>
804
+ </div>
805
+ ` : ''}
806
+
807
+ <!-- Approach -->
808
+ ${plan.approach ? `
809
+ <div class="plan-section">
810
+ <h4 class="plan-section-title"><i data-lucide="target" class="w-4 h-4 inline mr-1"></i> Approach</h4>
811
+ <p class="plan-approach-text">${escapeHtml(plan.approach)}</p>
812
+ </div>
813
+ ` : ''}
814
+
815
+ <!-- User Requirements -->
816
+ ${plan.user_requirements ? `
817
+ <div class="plan-section">
818
+ <h4 class="plan-section-title"><i data-lucide="user" class="w-4 h-4 inline mr-1"></i> User Requirements</h4>
819
+ <p class="plan-requirements-text">${escapeHtml(plan.user_requirements)}</p>
820
+ </div>
821
+ ` : ''}
822
+
823
+ <!-- Focus Paths -->
824
+ ${plan.focus_paths?.length ? `
825
+ <div class="plan-section">
826
+ <h4 class="plan-section-title"><i data-lucide="folder" class="w-4 h-4 inline mr-1"></i> Focus Paths</h4>
827
+ <div class="path-tags">
828
+ ${plan.focus_paths.map(p => `<span class="path-tag">${escapeHtml(p)}</span>`).join('')}
829
+ </div>
830
+ </div>
831
+ ` : ''}
832
+
833
+ <!-- Metadata -->
834
+ <div class="plan-section">
835
+ <h4 class="plan-section-title"><i data-lucide="info" class="w-4 h-4 inline mr-1"></i> Metadata</h4>
836
+ <div class="plan-meta-grid">
837
+ ${plan.severity ? `<div class="meta-item"><span class="meta-label">Severity:</span> <span class="severity-badge ${escapeHtml(plan.severity)}">${escapeHtml(plan.severity)}</span></div>` : ''}
838
+ ${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>` : ''}
839
+ ${plan.estimated_time ? `<div class="meta-item"><span class="meta-label">Estimated Time:</span> ${escapeHtml(plan.estimated_time)}</div>` : ''}
840
+ ${plan.complexity ? `<div class="meta-item"><span class="meta-label">Complexity:</span> ${escapeHtml(plan.complexity)}</div>` : ''}
841
+ ${plan.recommended_execution ? `<div class="meta-item"><span class="meta-label">Execution:</span> ${escapeHtml(plan.recommended_execution)}</div>` : ''}
842
+ </div>
843
+ </div>
844
+
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>
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>
1274
+ </div>
1275
+ `).join('')}
1276
+ </div>
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>
1407
+ ` : ''}
1408
+
1409
+ <!-- Raw JSON -->
1410
+ <div class="plan-section collapsible-section">
1411
+ <div class="collapsible-header">
1412
+ <span class="collapse-icon">&#9654;</span>
1413
+ <span class="section-label">{ } Raw JSON</span>
1414
+ </div>
1415
+ <div class="collapsible-content collapsed">
1416
+ <pre class="json-content">${escapeHtml(JSON.stringify(plan, null, 2))}</pre>
1417
+ </div>
1418
+ </div>
1419
+ </div>
1420
+ `;
1421
+ }
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
+
1852
+ /**
1853
+ * Render a single requirement item
1854
+ */
1855
+ function renderRequirementItem(req) {
1856
+ const priorityColors = {
1857
+ 'critical': 'error',
1858
+ 'high': 'warning',
1859
+ 'medium': 'info',
1860
+ 'low': 'default'
1861
+ };
1862
+ const priority = req.priority || 'medium';
1863
+ const colorClass = priorityColors[priority] || 'default';
1864
+
1865
+ return `
1866
+ <div class="requirement-item">
1867
+ <div class="requirement-header">
1868
+ <span class="requirement-id">${escapeHtml(req.id || '')}</span>
1869
+ <span class="priority-badge ${colorClass}">${escapeHtml(priority)}</span>
1870
+ </div>
1871
+ <div class="requirement-desc">${escapeHtml(getI18nText(req.description))}</div>
1872
+ ${req.source ? `<div class="requirement-source">${t('multiCli.source') || 'Source'}: ${escapeHtml(req.source)}</div>` : ''}
1873
+ </div>
1874
+ `;
1875
+ }
1876
+
1877
+ /**
1878
+ * Render Decision tab
1879
+ * Shows: selectedSolution, rejectedAlternatives, confidenceScore
1880
+ */
1881
+ function renderMultiCliDecisionTab(session) {
1882
+ // Use helper to extract decision from synthesis data structure
1883
+ const decision = extractDecisionFromSynthesis(session.latestSynthesis);
1884
+
1885
+ if (!decision || !decision.selectedSolution) {
1886
+ return `
1887
+ <div class="tab-empty-state">
1888
+ <div class="empty-icon"><i data-lucide="check-circle" class="w-12 h-12"></i></div>
1889
+ <div class="empty-title">${t('multiCli.empty.decision') || 'No Decision Yet'}</div>
1890
+ <div class="empty-text">${t('multiCli.empty.decisionText') || 'No decision has been made for this discussion yet.'}</div>
1891
+ </div>
1892
+ `;
1893
+ }
1894
+
1895
+ const status = decision.status || 'pending';
1896
+ const summary = getI18nText(decision.summary) || '';
1897
+ const selectedSolution = decision.selectedSolution || null;
1898
+ const rejectedAlternatives = decision.rejectedAlternatives || [];
1899
+ const confidenceScore = decision.confidenceScore || 0;
1900
+
1901
+ let sections = [];
1902
+
1903
+ // Decision Status and Summary
1904
+ sections.push(`
1905
+ <div class="multi-cli-section decision-header-section">
1906
+ <div class="decision-status-bar ${confidenceScore >= 0.7 ? 'converged' : 'divergent'}">
1907
+ <div class="decision-status-wrapper">
1908
+ <span class="decision-status ${status}">${escapeHtml(status)}</span>
1909
+ </div>
1910
+ <div class="decision-confidence">
1911
+ <span class="decision-confidence-label">${t('multiCli.confidence') || 'Confidence'}:</span>
1912
+ <div class="decision-confidence-bar">
1913
+ <div class="decision-confidence-fill" style="width: ${(confidenceScore * 100).toFixed(0)}%"></div>
1914
+ </div>
1915
+ <span class="decision-confidence-value">${(confidenceScore * 100).toFixed(0)}%</span>
1916
+ </div>
1917
+ </div>
1918
+ ${summary ? `<p class="decision-summary-text">${escapeHtml(summary)}</p>` : ''}
1919
+ </div>
1920
+ `);
1921
+
1922
+ // Selected Solution
1923
+ if (selectedSolution) {
1924
+ sections.push(`
1925
+ <div class="multi-cli-section selected-solution-section">
1926
+ <h4 class="section-title"><i data-lucide="check-circle" class="w-4 h-4 inline mr-1"></i> ${t('multiCli.selectedSolution') || 'Selected Solution'}</h4>
1927
+ ${renderSolutionCard(selectedSolution, true)}
1928
+ </div>
1929
+ `);
1930
+ }
1931
+
1932
+ // Rejected Alternatives
1933
+ if (rejectedAlternatives.length) {
1934
+ sections.push(`
1935
+ <div class="multi-cli-section rejected-section collapsible-section">
1936
+ <div class="collapsible-header">
1937
+ <span class="collapse-icon">&#9658;</span>
1938
+ <span class="section-label"><i data-lucide="x-circle" class="w-4 h-4 inline mr-1"></i> ${t('multiCli.rejectedAlternatives') || 'Rejected Alternatives'} (${rejectedAlternatives.length})</span>
1939
+ </div>
1940
+ <div class="collapsible-content collapsed">
1941
+ ${rejectedAlternatives.map(alt => renderSolutionCard(alt, false)).join('')}
1942
+ </div>
1943
+ </div>
1944
+ `);
1945
+ }
1946
+
1947
+ return `<div class="multi-cli-decision-tab">${sections.join('')}</div>`;
1948
+ }
1949
+
1950
+ /**
1951
+ * Render a solution card
1952
+ */
1953
+ function renderSolutionCard(solution, isSelected) {
1954
+ const title = getI18nText(solution.title) || 'Untitled Solution';
1955
+ const description = getI18nText(solution.description) || '';
1956
+ const pros = solution.pros || [];
1957
+ const cons = solution.cons || [];
1958
+ const risk = solution.risk || 'medium';
1959
+ const effort = getI18nText(solution.estimatedEffort) || '';
1960
+ const rejectionReason = solution.rejectionReason ? getI18nText(solution.rejectionReason) : '';
1961
+ const sourceCLIs = solution.sourceCLIs || [];
1962
+
1963
+ return `
1964
+ <div class="solution-card ${isSelected ? 'selected' : 'rejected'}">
1965
+ <div class="solution-header">
1966
+ <span class="solution-id">${escapeHtml(solution.id || '')}</span>
1967
+ <span class="solution-title">${escapeHtml(title)}</span>
1968
+ <span class="risk-badge ${risk}">${escapeHtml(risk)}</span>
1969
+ </div>
1970
+ ${description ? `<p class="solution-desc">${escapeHtml(description)}</p>` : ''}
1971
+ ${rejectionReason ? `<div class="rejection-reason"><strong>${t('multiCli.rejectionReason') || 'Reason'}:</strong> ${escapeHtml(rejectionReason)}</div>` : ''}
1972
+ <div class="solution-details">
1973
+ ${pros.length ? `
1974
+ <div class="pros-section">
1975
+ <strong>${t('multiCli.pros') || 'Pros'}:</strong>
1976
+ <ul class="pros-list">${pros.map(p => `<li class="pro-item">${escapeHtml(getI18nText(p))}</li>`).join('')}</ul>
1977
+ </div>
1978
+ ` : ''}
1979
+ ${cons.length ? `
1980
+ <div class="cons-section">
1981
+ <strong>${t('multiCli.cons') || 'Cons'}:</strong>
1982
+ <ul class="cons-list">${cons.map(c => `<li class="con-item">${escapeHtml(getI18nText(c))}</li>`).join('')}</ul>
1983
+ </div>
1984
+ ` : ''}
1985
+ ${effort ? `<div class="effort-estimate"><strong>${t('multiCli.effort') || 'Effort'}:</strong> ${escapeHtml(effort)}</div>` : ''}
1986
+ ${sourceCLIs.length ? `<div class="source-clis"><strong>${t('multiCli.sources') || 'Sources'}:</strong> ${sourceCLIs.map(s => `<span class="cli-badge">${escapeHtml(s)}</span>`).join('')}</div>` : ''}
1987
+ </div>
1988
+ </div>
1989
+ `;
1990
+ }
1991
+
1992
+ /**
1993
+ * Render Timeline tab
1994
+ * Shows: decisionRecords.timeline
1995
+ */
1996
+ function renderMultiCliTimelineTab(session) {
1997
+ // Use helper to extract timeline from synthesis data structure
1998
+ const timeline = extractTimelineFromSynthesis(session.latestSynthesis);
1999
+
2000
+ if (!timeline || !timeline.length) {
2001
+ return `
2002
+ <div class="tab-empty-state">
2003
+ <div class="empty-icon"><i data-lucide="git-commit" class="w-12 h-12"></i></div>
2004
+ <div class="empty-title">${t('multiCli.empty.timeline') || 'No Timeline Events'}</div>
2005
+ <div class="empty-text">${t('multiCli.empty.timelineText') || 'No decision timeline available for this session.'}</div>
2006
+ </div>
2007
+ `;
2008
+ }
2009
+
2010
+ const eventTypeIcons = {
2011
+ 'proposal': 'lightbulb',
2012
+ 'argument': 'message-square',
2013
+ 'agreement': 'thumbs-up',
2014
+ 'disagreement': 'thumbs-down',
2015
+ 'decision': 'check-circle',
2016
+ 'reversal': 'rotate-ccw'
2017
+ };
2018
+
2019
+ return `
2020
+ <div class="multi-cli-timeline-tab">
2021
+ <div class="timeline-container">
2022
+ ${timeline.map(event => {
2023
+ const icon = eventTypeIcons[event.type] || 'circle';
2024
+ const contributor = event.contributor || {};
2025
+ const summary = getI18nText(event.summary) || '';
2026
+ const evidence = event.evidence || [];
2027
+
2028
+ return `
2029
+ <div class="timeline-event event-${event.type || 'default'}">
2030
+ <div class="timeline-marker">
2031
+ <i data-lucide="${icon}" class="w-4 h-4"></i>
2032
+ </div>
2033
+ <div class="timeline-content">
2034
+ <div class="event-header">
2035
+ <span class="event-type ${event.type || ''}">${escapeHtml(event.type || 'event')}</span>
2036
+ <span class="event-contributor"><i data-lucide="user" class="w-3.5 h-3.5"></i>${escapeHtml(contributor.name || 'Unknown')}</span>
2037
+ <span class="event-time">${formatDate(event.timestamp)}</span>
2038
+ </div>
2039
+ <div class="event-summary">${escapeHtml(summary)}</div>
2040
+ ${event.reversibility ? `<span class="reversibility-badge ${event.reversibility}">${escapeHtml(event.reversibility)}</span>` : ''}
2041
+ ${evidence.length ? `
2042
+ <div class="event-evidence">
2043
+ ${evidence.map(ev => `
2044
+ <div class="evidence-item evidence-${ev.type || 'reference'}">
2045
+ <span class="evidence-type">${escapeHtml(ev.type || 'reference')}</span>
2046
+ <span class="evidence-desc">${escapeHtml(getI18nText(ev.description))}</span>
2047
+ </div>
2048
+ `).join('')}
2049
+ </div>
2050
+ ` : ''}
2051
+ </div>
2052
+ </div>
2053
+ `;
2054
+ }).join('')}
2055
+ </div>
2056
+ </div>
2057
+ `;
2058
+ }
2059
+
2060
+ /**
2061
+ * Render Rounds tab
2062
+ * Shows: navigation between round synthesis files
2063
+ */
2064
+ function renderMultiCliRoundsTab(session) {
2065
+ const rounds = session.rounds || [];
2066
+ const metadata = session.metadata || {};
2067
+ const totalRounds = metadata.roundId || rounds.length || 1;
2068
+
2069
+ if (!rounds.length && totalRounds <= 1) {
2070
+ // Show current synthesis as single round
2071
+ return `
2072
+ <div class="multi-cli-rounds-tab">
2073
+ <div class="rounds-nav">
2074
+ <div class="round-item active" data-round="1">
2075
+ <span class="round-number">Round 1</span>
2076
+ <span class="round-status">${t('multiCli.currentRound') || 'Current'}</span>
2077
+ </div>
2078
+ </div>
2079
+ <div class="round-content">
2080
+ <div class="round-info">
2081
+ <p>${t('multiCli.singleRoundInfo') || 'This is a single-round discussion. View other tabs for details.'}</p>
2082
+ </div>
2083
+ </div>
2084
+ </div>
2085
+ `;
2086
+ }
2087
+
2088
+ // Render round navigation and content
2089
+ return `
2090
+ <div class="multi-cli-rounds-tab">
2091
+ <div class="rounds-nav">
2092
+ ${rounds.map((round, idx) => {
2093
+ const roundNum = idx + 1;
2094
+ const isActive = roundNum === totalRounds;
2095
+ const roundStatus = round.convergence?.recommendation || 'continue';
2096
+
2097
+ return `
2098
+ <div class="round-item ${isActive ? 'active' : ''}" data-round="${roundNum}" onclick="loadMultiCliRound('${currentSessionDetailKey}', ${roundNum})">
2099
+ <span class="round-number">Round ${roundNum}</span>
2100
+ <span class="round-status ${roundStatus}">${escapeHtml(roundStatus)}</span>
2101
+ </div>
2102
+ `;
2103
+ }).join('')}
2104
+ </div>
2105
+ <div class="round-content" id="multiCliRoundContent">
2106
+ ${renderRoundContent(rounds[totalRounds - 1] || rounds[0] || session)}
2107
+ </div>
2108
+ </div>
2109
+ `;
2110
+ }
2111
+
2112
+ /**
2113
+ * Render content for a specific round
2114
+ */
2115
+ function renderRoundContent(round) {
2116
+ if (!round) {
2117
+ return `<div class="round-empty">${t('multiCli.noRoundData') || 'No data for this round.'}</div>`;
2118
+ }
2119
+
2120
+ const metadata = round.metadata || {};
2121
+ const agents = metadata.contributingAgents || [];
2122
+ const convergence = round._internal?.convergence || {};
2123
+ const crossVerification = round._internal?.cross_verification || {};
2124
+
2125
+ let sections = [];
2126
+
2127
+ // Round metadata
2128
+ sections.push(`
2129
+ <div class="round-metadata">
2130
+ <div class="meta-row">
2131
+ <span class="meta-label">${t('multiCli.roundId') || 'Round'}:</span>
2132
+ <span class="meta-value">${metadata.roundId || 1}</span>
2133
+ </div>
2134
+ <div class="meta-row">
2135
+ <span class="meta-label">${t('multiCli.timestamp') || 'Time'}:</span>
2136
+ <span class="meta-value">${formatDate(metadata.timestamp)}</span>
2137
+ </div>
2138
+ ${metadata.durationSeconds ? `
2139
+ <div class="meta-row">
2140
+ <span class="meta-label">${t('multiCli.duration') || 'Duration'}:</span>
2141
+ <span class="meta-value">${metadata.durationSeconds}s</span>
2142
+ </div>
2143
+ ` : ''}
2144
+ </div>
2145
+ `);
2146
+
2147
+ // Contributing agents
2148
+ if (agents.length) {
2149
+ sections.push(`
2150
+ <div class="round-agents">
2151
+ <strong>${t('multiCli.contributors') || 'Contributors'}:</strong>
2152
+ ${agents.map(agent => `<span class="agent-badge">${escapeHtml(agent.name || agent.id)}</span>`).join('')}
2153
+ </div>
2154
+ `);
2155
+ }
2156
+
2157
+ // Convergence metrics
2158
+ if (convergence.score !== undefined) {
2159
+ sections.push(`
2160
+ <div class="round-convergence">
2161
+ <strong>${t('multiCli.convergence') || 'Convergence'}:</strong>
2162
+ <span class="convergence-score">${(convergence.score * 100).toFixed(0)}%</span>
2163
+ <span class="convergence-rec ${convergence.recommendation || ''}">${escapeHtml(convergence.recommendation || '')}</span>
2164
+ ${convergence.new_insights ? `<span class="new-insights-badge">${t('multiCli.newInsights') || 'New Insights'}</span>` : ''}
2165
+ </div>
2166
+ `);
2167
+ }
2168
+
2169
+ // Cross-verification
2170
+ if (crossVerification.agreements?.length || crossVerification.disagreements?.length) {
2171
+ sections.push(`
2172
+ <div class="round-verification collapsible-section">
2173
+ <div class="collapsible-header">
2174
+ <span class="collapse-icon">&#9658;</span>
2175
+ <span class="section-label">${t('multiCli.crossVerification') || 'Cross-Verification'}</span>
2176
+ </div>
2177
+ <div class="collapsible-content collapsed">
2178
+ ${crossVerification.agreements?.length ? `
2179
+ <div class="agreements">
2180
+ <strong>${t('multiCli.agreements') || 'Agreements'}:</strong>
2181
+ <ul>${crossVerification.agreements.map(a => `<li class="agreement">${escapeHtml(a)}</li>`).join('')}</ul>
2182
+ </div>
2183
+ ` : ''}
2184
+ ${crossVerification.disagreements?.length ? `
2185
+ <div class="disagreements">
2186
+ <strong>${t('multiCli.disagreements') || 'Disagreements'}:</strong>
2187
+ <ul>${crossVerification.disagreements.map(d => `<li class="disagreement">${escapeHtml(d)}</li>`).join('')}</ul>
2188
+ </div>
2189
+ ` : ''}
2190
+ ${crossVerification.resolution ? `
2191
+ <div class="resolution">
2192
+ <strong>${t('multiCli.resolution') || 'Resolution'}:</strong>
2193
+ <p>${escapeHtml(crossVerification.resolution)}</p>
2194
+ </div>
2195
+ ` : ''}
2196
+ </div>
2197
+ </div>
2198
+ `);
2199
+ }
2200
+
2201
+ // Round solutions
2202
+ if (round.solutions && round.solutions.length > 0) {
2203
+ sections.push(`
2204
+ <div class="round-solutions">
2205
+ <strong>${t('multiCli.solutions') || 'Solutions'} (${round.solutions.length}):</strong>
2206
+ <div class="solutions-list">
2207
+ ${round.solutions.map((solution, idx) => `
2208
+ <div class="solution-card">
2209
+ <div class="solution-header">
2210
+ <div class="solution-title">
2211
+ <span class="solution-number">${idx + 1}</span>
2212
+ <span class="solution-name">${escapeHtml(solution.name || `Solution ${idx + 1}`)}</span>
2213
+ </div>
2214
+ <div class="solution-meta">
2215
+ ${solution.source_cli?.length ? `
2216
+ <div class="source-clis">
2217
+ ${solution.source_cli.map(cli => `<span class="cli-badge">${escapeHtml(cli)}</span>`).join('')}
2218
+ </div>
2219
+ ` : ''}
2220
+ <div class="solution-scores">
2221
+ <span class="score-badge feasibility" title="Feasibility">
2222
+ ${Math.round((solution.feasibility || 0) * 100)}%
2223
+ </span>
2224
+ <span class="score-badge effort-${solution.effort || 'medium'}" title="Effort">
2225
+ ${escapeHtml(solution.effort || 'medium')}
2226
+ </span>
2227
+ <span class="score-badge risk-${solution.risk || 'medium'}" title="Risk">
2228
+ ${escapeHtml(solution.risk || 'medium')}
2229
+ </span>
2230
+ </div>
2231
+ </div>
2232
+ </div>
2233
+
2234
+ ${solution.summary ? `
2235
+ <div class="solution-summary">
2236
+ ${escapeHtml(getI18nText(solution.summary))}
2237
+ </div>
2238
+ ` : ''}
2239
+
2240
+ ${solution.implementation_plan?.approach ? `
2241
+ <div class="solution-approach collapsible-section">
2242
+ <div class="collapsible-header">
2243
+ <span class="collapse-icon">&#9658;</span>
2244
+ <span class="section-label">${t('multiCli.implementation') || 'Implementation Approach'}</span>
2245
+ </div>
2246
+ <div class="collapsible-content collapsed">
2247
+ <p>${escapeHtml(getI18nText(solution.implementation_plan.approach))}</p>
2248
+
2249
+ ${solution.implementation_plan.tasks?.length ? `
2250
+ <div class="solution-tasks">
2251
+ <strong>${t('multiCli.tasks') || 'Tasks'}:</strong>
2252
+ <ul class="task-list">
2253
+ ${solution.implementation_plan.tasks.map(task => `
2254
+ <li class="task-item">
2255
+ <span class="task-id">${escapeHtml(task.id || '')}</span>
2256
+ <span class="task-name">${escapeHtml(getI18nText(task.name))}</span>
2257
+ ${task.key_point ? `<span class="task-key-point">${escapeHtml(getI18nText(task.key_point))}</span>` : ''}
2258
+ </li>
2259
+ `).join('')}
2260
+ </ul>
2261
+ </div>
2262
+ ` : ''}
2263
+
2264
+ ${solution.implementation_plan.execution_flow ? `
2265
+ <div class="execution-flow">
2266
+ <strong>${t('multiCli.executionFlow') || 'Execution Flow'}:</strong>
2267
+ <code class="flow-code">${escapeHtml(solution.implementation_plan.execution_flow)}</code>
2268
+ </div>
2269
+ ` : ''}
2270
+
2271
+ ${solution.implementation_plan.milestones?.length ? `
2272
+ <div class="solution-milestones">
2273
+ <strong>${t('multiCli.milestones') || 'Milestones'}:</strong>
2274
+ <ul class="milestone-list">
2275
+ ${solution.implementation_plan.milestones.map(milestone => `
2276
+ <li class="milestone-item">${escapeHtml(getI18nText(milestone))}</li>
2277
+ `).join('')}
2278
+ </ul>
2279
+ </div>
2280
+ ` : ''}
2281
+ </div>
2282
+ </div>
2283
+ ` : ''}
2284
+
2285
+ ${(solution.dependencies?.internal?.length || solution.dependencies?.external?.length) ? `
2286
+ <div class="solution-dependencies collapsible-section">
2287
+ <div class="collapsible-header">
2288
+ <span class="collapse-icon">&#9658;</span>
2289
+ <span class="section-label">${t('multiCli.dependencies') || 'Dependencies'}</span>
2290
+ </div>
2291
+ <div class="collapsible-content collapsed">
2292
+ ${solution.dependencies.internal?.length ? `
2293
+ <div class="internal-deps">
2294
+ <strong>${t('multiCli.internalDeps') || 'Internal'}:</strong>
2295
+ <ul class="dep-list">
2296
+ ${solution.dependencies.internal.map(dep => `
2297
+ <li class="dep-item">${escapeHtml(getI18nText(dep))}</li>
2298
+ `).join('')}
2299
+ </ul>
2300
+ </div>
2301
+ ` : ''}
2302
+ ${solution.dependencies.external?.length ? `
2303
+ <div class="external-deps">
2304
+ <strong>${t('multiCli.externalDeps') || 'External'}:</strong>
2305
+ <ul class="dep-list">
2306
+ ${solution.dependencies.external.map(dep => `
2307
+ <li class="dep-item">${escapeHtml(getI18nText(dep))}</li>
2308
+ `).join('')}
2309
+ </ul>
2310
+ </div>
2311
+ ` : ''}
2312
+ </div>
2313
+ </div>
2314
+ ` : ''}
2315
+
2316
+ ${solution.technical_concerns?.length ? `
2317
+ <div class="solution-concerns collapsible-section">
2318
+ <div class="collapsible-header">
2319
+ <span class="collapse-icon">&#9658;</span>
2320
+ <span class="section-label">${t('multiCli.technicalConcerns') || 'Technical Concerns'}</span>
2321
+ </div>
2322
+ <div class="collapsible-content collapsed">
2323
+ <ul class="concern-list">
2324
+ ${solution.technical_concerns.map(concern => `
2325
+ <li class="concern-item">${escapeHtml(getI18nText(concern))}</li>
2326
+ `).join('')}
2327
+ </ul>
2328
+ </div>
2329
+ </div>
2330
+ ` : ''}
2331
+ </div>
2332
+ `).join('')}
2333
+ </div>
2334
+ </div>
2335
+ `);
2336
+ }
2337
+
2338
+ return sections.join('');
2339
+ }
2340
+
2341
+ /**
2342
+ * Load a specific round's data (async, may fetch from server)
2343
+ */
2344
+ async function loadMultiCliRound(sessionKey, roundNum) {
2345
+ const session = liteTaskDataStore[sessionKey];
2346
+ if (!session) return;
2347
+
2348
+ // Update active state in nav
2349
+ document.querySelectorAll('.round-item').forEach(item => {
2350
+ item.classList.toggle('active', parseInt(item.dataset.round) === roundNum);
2351
+ });
2352
+
2353
+ const contentArea = document.getElementById('multiCliRoundContent');
2354
+
2355
+ // If we have rounds array, use it
2356
+ if (session.rounds && session.rounds[roundNum - 1]) {
2357
+ contentArea.innerHTML = renderRoundContent(session.rounds[roundNum - 1]);
2358
+ initCollapsibleSections(contentArea);
2359
+ return;
2360
+ }
2361
+
2362
+ // Otherwise try to fetch from server
2363
+ if (window.SERVER_MODE && session.path) {
2364
+ contentArea.innerHTML = `<div class="tab-loading">${t('common.loading') || 'Loading...'}</div>`;
2365
+ try {
2366
+ const response = await fetch(`/api/session-detail?path=${encodeURIComponent(session.path)}&type=round&round=${roundNum}`);
2367
+ if (response.ok) {
2368
+ const data = await response.json();
2369
+ contentArea.innerHTML = renderRoundContent(data.round || {});
2370
+ initCollapsibleSections(contentArea);
2371
+ return;
2372
+ }
2373
+ } catch (err) {
2374
+ console.error('Failed to load round:', err);
2375
+ }
2376
+ }
2377
+
2378
+ // Fallback
2379
+ contentArea.innerHTML = `<div class="round-empty">${t('multiCli.noRoundData') || 'No data for this round.'}</div>`;
2380
+ }
2381
+
2382
+ /**
2383
+ * Render Discussion Section (combines Topic, Rounds, Decision)
2384
+ * Uses accordion layout to display discussion rounds
2385
+ */
2386
+ function renderMultiCliDiscussionSection(session) {
2387
+ const rounds = session.rounds || [];
2388
+ const metadata = session.metadata || {};
2389
+ const totalRounds = metadata.roundId || rounds.length || 1;
2390
+ const topic = session.discussionTopic || session.latestSynthesis?.discussionTopic || {};
2391
+
2392
+ // If no rounds, show topic summary and current synthesis
2393
+ if (!rounds.length) {
2394
+ const title = getI18nText(topic.title) || t('multiCli.discussion.discussionTopic');
2395
+ const description = getI18nText(topic.description) || '';
2396
+ const status = topic.status || session.status || 'analyzing';
2397
+
2398
+ return `
2399
+ <div class="multi-cli-discussion-section">
2400
+ <div class="discussion-header">
2401
+ <h3 class="discussion-title">${escapeHtml(title)}</h3>
2402
+ <span class="discussion-status ${status}">${escapeHtml(status)}</span>
2403
+ </div>
2404
+ ${description ? `<p class="discussion-description">${escapeHtml(description)}</p>` : ''}
2405
+ <div class="discussion-empty-state">
2406
+ <i data-lucide="message-circle" class="w-8 h-8"></i>
2407
+ <p>${t('multiCli.singleRoundInfo')}</p>
2408
+ </div>
2409
+ </div>
2410
+ `;
2411
+ }
2412
+
2413
+ // Render accordion for multiple rounds
2414
+ const accordionItems = rounds.map((round, idx) => {
2415
+ const roundNum = idx + 1;
2416
+ const isLatest = roundNum === totalRounds;
2417
+ const roundMeta = round.metadata || {};
2418
+ const convergence = round._internal?.convergence || round.convergence || {};
2419
+ const recommendation = convergence.recommendation || 'continue';
2420
+ const score = convergence.score !== undefined ? Math.round(convergence.score * 100) : null;
2421
+ const solutions = round.solutions || [];
2422
+ const agents = roundMeta.contributingAgents || [];
2423
+
2424
+ return `
2425
+ <div class="discussion-round collapsible-section ${isLatest ? 'expanded' : ''}">
2426
+ <div class="collapsible-header discussion-round-header">
2427
+ <span class="collapse-icon">${isLatest ? '&#9660;' : '&#9658;'}</span>
2428
+ <div class="round-title-group">
2429
+ <span class="round-badge">${t('multiCli.round') || 'Round'} ${roundNum}</span>
2430
+ <span class="round-timestamp">${formatDate(roundMeta.timestamp)}</span>
2431
+ </div>
2432
+ <div class="round-indicators">
2433
+ ${score !== null ? `<span class="convergence-badge" title="${t('multiCli.convergence') || 'Convergence'}">${score}%</span>` : ''}
2434
+ <span class="recommendation-badge ${recommendation}">${escapeHtml(recommendation)}</span>
2435
+ </div>
2436
+ </div>
2437
+ <div class="collapsible-content ${isLatest ? '' : 'collapsed'}">
2438
+ <!-- Discussion Topic for this round -->
2439
+ ${round.discussionTopic ? `
2440
+ <div class="round-topic">
2441
+ <h4 class="round-section-title"><i data-lucide="message-circle" class="w-4 h-4 inline"></i> ${t('multiCli.topic') || 'Topic'}</h4>
2442
+ <p>${escapeHtml(getI18nText(round.discussionTopic.title || round.discussionTopic))}</p>
2443
+ </div>
2444
+ ` : ''}
2445
+
2446
+ <!-- Contributing Agents -->
2447
+ ${agents.length ? `
2448
+ <div class="round-agents">
2449
+ <h4 class="round-section-title"><i data-lucide="users" class="w-4 h-4 inline"></i> ${t('multiCli.contributors') || 'Contributors'}</h4>
2450
+ <div class="agent-badges">
2451
+ ${agents.map(agent => `<span class="agent-badge">${escapeHtml(agent.name || agent.id || agent)}</span>`).join('')}
2452
+ </div>
2453
+ </div>
2454
+ ` : ''}
2455
+
2456
+ <!-- Solutions -->
2457
+ ${solutions.length ? `
2458
+ <div class="round-solutions-summary">
2459
+ <h4 class="round-section-title"><i data-lucide="lightbulb" class="w-4 h-4 inline"></i> ${t('multiCli.solutions')} (${solutions.length})</h4>
2460
+ <div class="solution-cards-grid">
2461
+ ${solutions.map((sol, sidx) => `
2462
+ <div class="solution-mini-card">
2463
+ <div class="solution-mini-header">
2464
+ <span class="solution-number">${sidx + 1}</span>
2465
+ <span class="solution-name">${escapeHtml(sol.name || `${t('multiCli.summary.solution')} ${sidx + 1}`)}</span>
2466
+ </div>
2467
+ <div class="solution-mini-scores">
2468
+ <span class="score-pill feasibility" title="${t('multiCli.feasibility')}">${Math.round((sol.feasibility || 0) * 100)}%</span>
2469
+ <span class="score-pill effort-${sol.effort || 'medium'}">${escapeHtml(sol.effort || 'M')}</span>
2470
+ <span class="score-pill risk-${sol.risk || 'medium'}">${escapeHtml(sol.risk || 'M')}</span>
2471
+ </div>
2472
+ ${sol.summary ? `<p class="solution-mini-summary">${escapeHtml(getI18nText(sol.summary).substring(0, 100))}${getI18nText(sol.summary).length > 100 ? '...' : ''}</p>` : ''}
2473
+ </div>
2474
+ `).join('')}
2475
+ </div>
2476
+ </div>
2477
+ ` : ''}
2478
+
2479
+ <!-- Decision/Recommendation -->
2480
+ ${convergence.reasoning || round.decision ? `
2481
+ <div class="round-decision">
2482
+ <h4 class="round-section-title"><i data-lucide="check-circle" class="w-4 h-4 inline"></i> ${t('multiCli.decision')}</h4>
2483
+ <p class="decision-text">${escapeHtml(convergence.reasoning || round.decision || '')}</p>
2484
+ </div>
2485
+ ` : ''}
2486
+ </div>
2487
+ </div>
2488
+ `;
2489
+ }).join('');
2490
+
2491
+ return `
2492
+ <div class="multi-cli-discussion-section">
2493
+ <div class="discussion-header">
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>
2496
+ </div>
2497
+ <div class="discussion-accordion">
2498
+ ${accordionItems}
2499
+ </div>
2500
+ </div>
2501
+ `;
2502
+ }
2503
+
2504
+ /**
2505
+ * Render Association Section (context-package key fields)
2506
+ * Shows solution summary, dependencies, consensus
2507
+ */
2508
+ function renderMultiCliAssociationSection(session) {
2509
+ const contextPkg = session.contextPackage || session.context_package || session.latestSynthesis?.context_package || {};
2510
+ const solutions = contextPkg.solutions || session.latestSynthesis?.solutions || [];
2511
+ const dependencies = contextPkg.dependencies || {};
2512
+ const consensus = contextPkg.consensus || session.latestSynthesis?._internal?.cross_verification || {};
2513
+ const relatedFiles = extractFilesFromSynthesis(session.latestSynthesis);
2514
+
2515
+ // Check if we have any content to display
2516
+ const hasSolutions = solutions.length > 0;
2517
+ const hasDependencies = dependencies.internal?.length || dependencies.external?.length;
2518
+ const hasConsensus = consensus.agreements?.length || consensus.resolved_conflicts?.length || consensus.disagreements?.length;
2519
+ const hasFiles = relatedFiles?.fileTree?.length || relatedFiles?.impactSummary?.length;
2520
+
2521
+ if (!hasSolutions && !hasDependencies && !hasConsensus && !hasFiles) {
2522
+ return `
2523
+ <div class="tab-empty-state">
2524
+ <div class="empty-icon"><i data-lucide="link-2" class="w-12 h-12"></i></div>
2525
+ <div class="empty-title">${t('multiCli.empty.association') || 'No Association Data'}</div>
2526
+ <div class="empty-text">${t('multiCli.empty.associationText') || 'No context package or related files available for this session.'}</div>
2527
+ </div>
2528
+ `;
2529
+ }
2530
+
2531
+ let sections = [];
2532
+
2533
+ // Solution Summary Cards
2534
+ if (hasSolutions) {
2535
+ sections.push(`
2536
+ <div class="association-section solutions-section">
2537
+ <h4 class="association-section-title">
2538
+ <i data-lucide="lightbulb" class="w-4 h-4 inline"></i>
2539
+ ${t('multiCli.solutionSummary') || 'Solution Summary'}
2540
+ </h4>
2541
+ <div class="association-cards-grid">
2542
+ ${solutions.map((sol, idx) => `
2543
+ <div class="association-card solution-card">
2544
+ <div class="card-header">
2545
+ <span class="card-number">${idx + 1}</span>
2546
+ <span class="card-title">${escapeHtml(sol.name || 'Solution ' + (idx + 1))}</span>
2547
+ </div>
2548
+ <div class="card-metrics">
2549
+ <div class="metric">
2550
+ <span class="metric-label">${t('multiCli.feasibility') || 'Feasibility'}</span>
2551
+ <span class="metric-value">${Math.round((sol.feasibility || 0) * 100)}%</span>
2552
+ </div>
2553
+ <div class="metric">
2554
+ <span class="metric-label">${t('multiCli.effort') || 'Effort'}</span>
2555
+ <span class="metric-value effort-${sol.effort || 'medium'}">${escapeHtml(sol.effort || 'medium')}</span>
2556
+ </div>
2557
+ <div class="metric">
2558
+ <span class="metric-label">${t('multiCli.risk') || 'Risk'}</span>
2559
+ <span class="metric-value risk-${sol.risk || 'medium'}">${escapeHtml(sol.risk || 'medium')}</span>
2560
+ </div>
2561
+ </div>
2562
+ ${sol.summary ? `<p class="card-description">${escapeHtml(getI18nText(sol.summary))}</p>` : ''}
2563
+ </div>
2564
+ `).join('')}
2565
+ </div>
2566
+ </div>
2567
+ `);
2568
+ }
2569
+
2570
+ // Dependencies Card
2571
+ if (hasDependencies) {
2572
+ sections.push(`
2573
+ <div class="association-section dependencies-section">
2574
+ <h4 class="association-section-title">
2575
+ <i data-lucide="git-branch" class="w-4 h-4 inline"></i>
2576
+ ${t('multiCli.dependencies') || 'Dependencies'}
2577
+ </h4>
2578
+ <div class="dependencies-grid">
2579
+ ${dependencies.internal?.length ? `
2580
+ <div class="association-card dependency-card">
2581
+ <div class="card-header">
2582
+ <i data-lucide="folder" class="w-4 h-4"></i>
2583
+ <span class="card-title">${t('multiCli.internalDeps') || 'Internal'}</span>
2584
+ <span class="card-count">${dependencies.internal.length}</span>
2585
+ </div>
2586
+ <ul class="dependency-list">
2587
+ ${dependencies.internal.map(dep => `<li>${escapeHtml(getI18nText(dep))}</li>`).join('')}
2588
+ </ul>
2589
+ </div>
2590
+ ` : ''}
2591
+ ${dependencies.external?.length ? `
2592
+ <div class="association-card dependency-card">
2593
+ <div class="card-header">
2594
+ <i data-lucide="package" class="w-4 h-4"></i>
2595
+ <span class="card-title">${t('multiCli.externalDeps') || 'External'}</span>
2596
+ <span class="card-count">${dependencies.external.length}</span>
2597
+ </div>
2598
+ <ul class="dependency-list">
2599
+ ${dependencies.external.map(dep => `<li>${escapeHtml(getI18nText(dep))}</li>`).join('')}
2600
+ </ul>
2601
+ </div>
2602
+ ` : ''}
2603
+ </div>
2604
+ </div>
2605
+ `);
2606
+ }
2607
+
2608
+ // Consensus Card
2609
+ if (hasConsensus) {
2610
+ sections.push(`
2611
+ <div class="association-section consensus-section">
2612
+ <h4 class="association-section-title">
2613
+ <i data-lucide="check-check" class="w-4 h-4 inline"></i>
2614
+ ${t('multiCli.consensus') || 'Consensus'}
2615
+ </h4>
2616
+ <div class="consensus-grid">
2617
+ ${consensus.agreements?.length ? `
2618
+ <div class="association-card consensus-card agreements">
2619
+ <div class="card-header">
2620
+ <i data-lucide="thumbs-up" class="w-4 h-4"></i>
2621
+ <span class="card-title">${t('multiCli.agreements') || 'Agreements'}</span>
2622
+ <span class="card-count">${consensus.agreements.length}</span>
2623
+ </div>
2624
+ <ul class="consensus-list">
2625
+ ${consensus.agreements.map(item => `<li class="agreement-item">${escapeHtml(item)}</li>`).join('')}
2626
+ </ul>
2627
+ </div>
2628
+ ` : ''}
2629
+ ${(consensus.resolved_conflicts?.length || consensus.disagreements?.length) ? `
2630
+ <div class="association-card consensus-card conflicts">
2631
+ <div class="card-header">
2632
+ <i data-lucide="git-merge" class="w-4 h-4"></i>
2633
+ <span class="card-title">${t('multiCli.resolvedConflicts') || 'Resolved Conflicts'}</span>
2634
+ <span class="card-count">${(consensus.resolved_conflicts || consensus.disagreements || []).length}</span>
2635
+ </div>
2636
+ <ul class="consensus-list">
2637
+ ${(consensus.resolved_conflicts || consensus.disagreements || []).map(item => `<li class="conflict-item">${escapeHtml(item)}</li>`).join('')}
2638
+ </ul>
2639
+ </div>
2640
+ ` : ''}
2641
+ </div>
2642
+ </div>
2643
+ `);
2644
+ }
2645
+
2646
+ // Related Files (from existing Files tab logic)
2647
+ if (hasFiles) {
2648
+ sections.push(`
2649
+ <div class="association-section files-section">
2650
+ <h4 class="association-section-title">
2651
+ <i data-lucide="folder-tree" class="w-4 h-4 inline"></i>
2652
+ ${t('multiCli.tab.files') || 'Related Files'}
2653
+ </h4>
2654
+ <div class="files-summary">
2655
+ ${relatedFiles.fileTree?.length ? `
2656
+ <div class="files-list">
2657
+ ${relatedFiles.fileTree.slice(0, 10).map(file => `
2658
+ <div class="file-item">
2659
+ <i data-lucide="file" class="w-3 h-3"></i>
2660
+ <span class="file-path">${escapeHtml(typeof file === 'string' ? file : file.path || file.name)}</span>
2661
+ </div>
2662
+ `).join('')}
2663
+ ${relatedFiles.fileTree.length > 10 ? `
2664
+ <div class="files-more">+${relatedFiles.fileTree.length - 10} ${t('common.more') || 'more'}</div>
2665
+ ` : ''}
2666
+ </div>
2667
+ ` : ''}
2668
+ ${relatedFiles.impactSummary?.length ? `
2669
+ <div class="impact-summary">
2670
+ <h5>${t('multiCli.impactSummary') || 'Impact Summary'}</h5>
2671
+ <ul>
2672
+ ${relatedFiles.impactSummary.slice(0, 5).map(item => `<li>${escapeHtml(getI18nText(item))}</li>`).join('')}
2673
+ </ul>
2674
+ </div>
2675
+ ` : ''}
2676
+ </div>
2677
+ </div>
2678
+ `);
2679
+ }
2680
+
2681
+ return `
2682
+ <div class="multi-cli-association-section">
2683
+ ${sections.join('')}
2684
+ </div>
2685
+ `;
2686
+ }
2687
+
71
2688
  // Lite Task Detail Page
72
2689
  function showLiteTaskDetailPage(sessionKey) {
73
2690
  const session = liteTaskDataStore[sessionKey];