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.
- package/.claude/CLAUDE.md +7 -1
- package/.claude/agents/action-planning-agent.md +1 -0
- package/.claude/agents/cli-discuss-agent.md +391 -0
- package/.claude/agents/cli-execution-agent.md +2 -0
- package/.claude/agents/cli-explore-agent.md +2 -1
- package/.claude/agents/cli-lite-planning-agent.md +1 -0
- package/.claude/agents/cli-planning-agent.md +1 -0
- package/.claude/agents/code-developer.md +1 -0
- package/.claude/agents/conceptual-planning-agent.md +2 -0
- package/.claude/agents/context-search-agent.md +1 -0
- package/.claude/agents/debug-explore-agent.md +2 -0
- package/.claude/agents/doc-generator.md +1 -0
- package/.claude/agents/issue-plan-agent.md +2 -1
- package/.claude/agents/issue-queue-agent.md +2 -1
- package/.claude/agents/memory-bridge.md +2 -0
- package/.claude/agents/test-context-search-agent.md +2 -0
- package/.claude/agents/test-fix-agent.md +1 -0
- package/.claude/agents/ui-design-agent.md +2 -0
- package/.claude/agents/universal-executor.md +1 -0
- package/.claude/commands/issue/execute.md +269 -176
- package/.claude/commands/workflow/debug.md +6 -0
- package/.claude/commands/workflow/execute.md +4 -0
- package/.claude/commands/workflow/lite-execute.md +4 -0
- package/.claude/commands/workflow/lite-lite-lite.md +433 -0
- package/.claude/commands/workflow/multi-cli-plan.md +510 -0
- package/.claude/commands/workflow/review-fix.md +4 -0
- package/.claude/commands/workflow/test-cycle-execute.md +4 -0
- package/.claude/skills/ccw/SKILL.md +262 -372
- package/.claude/skills/ccw/command.json +547 -0
- package/.claude/skills/ccw-help/SKILL.md +46 -107
- package/.claude/skills/ccw-help/command.json +511 -0
- package/.claude/skills/skill-tuning/SKILL.md +303 -0
- package/.claude/skills/skill-tuning/phases/actions/action-abort.md +164 -0
- package/.claude/skills/skill-tuning/phases/actions/action-analyze-requirements.md +406 -0
- package/.claude/skills/skill-tuning/phases/actions/action-apply-fix.md +206 -0
- package/.claude/skills/skill-tuning/phases/actions/action-complete.md +195 -0
- package/.claude/skills/skill-tuning/phases/actions/action-diagnose-agent.md +317 -0
- package/.claude/skills/skill-tuning/phases/actions/action-diagnose-context.md +243 -0
- package/.claude/skills/skill-tuning/phases/actions/action-diagnose-dataflow.md +318 -0
- package/.claude/skills/skill-tuning/phases/actions/action-diagnose-docs.md +299 -0
- package/.claude/skills/skill-tuning/phases/actions/action-diagnose-memory.md +269 -0
- package/.claude/skills/skill-tuning/phases/actions/action-diagnose-token-consumption.md +200 -0
- package/.claude/skills/skill-tuning/phases/actions/action-gemini-analysis.md +322 -0
- package/.claude/skills/skill-tuning/phases/actions/action-generate-report.md +228 -0
- package/.claude/skills/skill-tuning/phases/actions/action-init.md +149 -0
- package/.claude/skills/skill-tuning/phases/actions/action-propose-fixes.md +317 -0
- package/.claude/skills/skill-tuning/phases/actions/action-verify.md +222 -0
- package/.claude/skills/skill-tuning/phases/orchestrator.md +377 -0
- package/.claude/skills/skill-tuning/phases/state-schema.md +378 -0
- package/.claude/skills/skill-tuning/specs/category-mappings.json +284 -0
- package/.claude/skills/skill-tuning/specs/dimension-mapping.md +212 -0
- package/.claude/skills/skill-tuning/specs/problem-taxonomy.md +318 -0
- package/.claude/skills/skill-tuning/specs/quality-gates.md +263 -0
- package/.claude/skills/skill-tuning/specs/skill-authoring-principles.md +189 -0
- package/.claude/skills/skill-tuning/specs/tuning-strategies.md +1537 -0
- package/.claude/skills/skill-tuning/templates/diagnosis-report.md +153 -0
- package/.claude/skills/skill-tuning/templates/fix-proposal.md +204 -0
- package/.claude/workflows/cli-templates/schemas/multi-cli-discussion-schema.json +421 -0
- package/.claude/workflows/cli-tools-usage.md +0 -41
- package/.codex/prompts/issue-execute.md +72 -12
- package/README.md +3 -0
- package/ccw/dist/core/data-aggregator.d.ts +2 -0
- package/ccw/dist/core/data-aggregator.d.ts.map +1 -1
- package/ccw/dist/core/data-aggregator.js +5 -2
- package/ccw/dist/core/data-aggregator.js.map +1 -1
- package/ccw/dist/core/lite-scanner.d.ts +2 -1
- package/ccw/dist/core/lite-scanner.d.ts.map +1 -1
- package/ccw/dist/core/lite-scanner.js +348 -6
- package/ccw/dist/core/lite-scanner.js.map +1 -1
- package/ccw/dist/core/routes/session-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/session-routes.js +166 -48
- package/ccw/dist/core/routes/session-routes.js.map +1 -1
- package/ccw/dist/core/routes/system-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/system-routes.js +87 -0
- package/ccw/dist/core/routes/system-routes.js.map +1 -1
- package/ccw/dist/core/server.js +2 -2
- package/ccw/dist/core/server.js.map +1 -1
- package/ccw/dist/tools/memory-update-queue.d.ts.map +1 -1
- package/ccw/dist/tools/memory-update-queue.js +5 -11
- package/ccw/dist/tools/memory-update-queue.js.map +1 -1
- package/ccw/scripts/IMPLEMENTATION-SUMMARY.md +226 -0
- package/ccw/scripts/QUICK-REFERENCE.md +135 -0
- package/ccw/scripts/README-memory-embedder.md +157 -0
- package/ccw/scripts/__pycache__/memory_embedder.cpython-313.pyc +0 -0
- package/ccw/scripts/__pycache__/test_memory_embedder.cpython-313-pytest-8.4.2.pyc +0 -0
- package/ccw/scripts/memory-embedder-example.ts +184 -0
- package/ccw/scripts/memory_embedder.py +428 -0
- package/ccw/scripts/test_memory_embedder.py +245 -0
- package/ccw/src/core/data-aggregator.ts +7 -2
- package/ccw/src/core/lite-scanner.ts +495 -6
- package/ccw/src/core/routes/session-routes.ts +201 -48
- package/ccw/src/core/routes/system-routes.ts +102 -0
- package/ccw/src/core/server.ts +2 -2
- package/ccw/src/templates/dashboard-css/01-base.css +8 -0
- package/ccw/src/templates/dashboard-css/02-session.css +81 -0
- package/ccw/src/templates/dashboard-css/03-tasks.css +5 -0
- package/ccw/src/templates/dashboard-css/04-lite-tasks.css +3071 -0
- package/ccw/src/templates/dashboard-css/21-cli-toolmgmt.css +157 -0
- package/ccw/src/templates/dashboard-css/32-issue-manager.css +23 -0
- package/ccw/src/templates/dashboard-js/components/cli-stream-viewer.js +38 -4
- package/ccw/src/templates/dashboard-js/components/hook-manager.js +68 -34
- package/ccw/src/templates/dashboard-js/components/navigation.js +24 -4
- package/ccw/src/templates/dashboard-js/i18n.js +278 -4
- package/ccw/src/templates/dashboard-js/views/api-settings.js +32 -0
- package/ccw/src/templates/dashboard-js/views/claude-manager.js +44 -3
- package/ccw/src/templates/dashboard-js/views/cli-manager.js +303 -31
- package/ccw/src/templates/dashboard-js/views/history.js +44 -6
- package/ccw/src/templates/dashboard-js/views/home.js +1 -0
- package/ccw/src/templates/dashboard-js/views/issue-manager.js +54 -7
- package/ccw/src/templates/dashboard-js/views/lite-tasks.js +2621 -4
- package/ccw/src/templates/dashboard.html +5 -0
- package/ccw/src/tools/memory-update-queue.js +5 -11
- package/package.json +2 -1
- package/.claude/skills/ccw/index/command-capabilities.json +0 -127
- package/.claude/skills/ccw/index/intent-rules.json +0 -136
- package/.claude/skills/ccw/index/workflow-chains.json +0 -451
- package/.claude/skills/ccw/phases/actions/bugfix.md +0 -218
- package/.claude/skills/ccw/phases/actions/coupled.md +0 -194
- package/.claude/skills/ccw/phases/actions/docs.md +0 -93
- package/.claude/skills/ccw/phases/actions/full.md +0 -154
- package/.claude/skills/ccw/phases/actions/issue.md +0 -201
- package/.claude/skills/ccw/phases/actions/rapid.md +0 -104
- package/.claude/skills/ccw/phases/actions/review-fix.md +0 -84
- package/.claude/skills/ccw/phases/actions/tdd.md +0 -66
- package/.claude/skills/ccw/phases/actions/ui.md +0 -79
- package/.claude/skills/ccw/phases/orchestrator.md +0 -435
- package/.claude/skills/ccw/specs/intent-classification.md +0 -336
- package/.claude/skills/ccw-help/index/all-agents.json +0 -82
- package/.claude/skills/ccw-help/index/all-commands.json +0 -882
- package/.claude/skills/ccw-help/index/by-category.json +0 -914
- package/.claude/skills/ccw-help/index/by-use-case.json +0 -896
- package/.claude/skills/ccw-help/index/command-relationships.json +0 -160
- 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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
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)">×</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">▶</span>
|
|
849
|
+
<span class="section-label">{ } Raw JSON</span>
|
|
850
|
+
</div>
|
|
851
|
+
<div class="collapsible-content collapsed">
|
|
852
|
+
<pre class="json-content">${escapeHtml(JSON.stringify(plan, null, 2))}</pre>
|
|
853
|
+
</div>
|
|
854
|
+
</div>
|
|
855
|
+
</div>
|
|
856
|
+
`;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
/**
|
|
860
|
+
* Load and render Context tab - displays context-package content
|
|
861
|
+
*/
|
|
862
|
+
async function loadAndRenderMultiCliContextTab(session, contentArea) {
|
|
863
|
+
contentArea.innerHTML = `<div class="tab-loading">${t('common.loading') || 'Loading...'}</div>`;
|
|
864
|
+
|
|
865
|
+
try {
|
|
866
|
+
if (window.SERVER_MODE && session.path) {
|
|
867
|
+
const response = await fetch(`/api/session-detail?path=${encodeURIComponent(session.path)}&type=context`);
|
|
868
|
+
if (response.ok) {
|
|
869
|
+
const data = await response.json();
|
|
870
|
+
contentArea.innerHTML = renderMultiCliContextContent(data.context, session);
|
|
871
|
+
initCollapsibleSections(contentArea);
|
|
872
|
+
if (typeof lucide !== 'undefined') lucide.createIcons();
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
// Fallback: show session context_package if available
|
|
877
|
+
contentArea.innerHTML = renderMultiCliContextContent(session.context_package, session);
|
|
878
|
+
initCollapsibleSections(contentArea);
|
|
879
|
+
if (typeof lucide !== 'undefined') lucide.createIcons();
|
|
880
|
+
} catch (err) {
|
|
881
|
+
contentArea.innerHTML = `<div class="tab-error">Failed to load context: ${err.message}</div>`;
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
/**
|
|
886
|
+
* Render context content for multi-cli
|
|
887
|
+
*/
|
|
888
|
+
function renderMultiCliContextContent(context, session) {
|
|
889
|
+
// Also check for context_package in session
|
|
890
|
+
const ctx = context || session.context_package || {};
|
|
891
|
+
|
|
892
|
+
if (!ctx || Object.keys(ctx).length === 0) {
|
|
893
|
+
return `
|
|
894
|
+
<div class="tab-empty-state">
|
|
895
|
+
<div class="empty-icon"><i data-lucide="package" class="w-12 h-12"></i></div>
|
|
896
|
+
<div class="empty-title">${t('empty.noContext') || 'No Context Data'}</div>
|
|
897
|
+
<div class="empty-text">${t('empty.noContextText') || 'No context package available for this session.'}</div>
|
|
898
|
+
</div>
|
|
899
|
+
`;
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
let sections = [];
|
|
903
|
+
|
|
904
|
+
// Task Description
|
|
905
|
+
if (ctx.task_description) {
|
|
906
|
+
sections.push(`
|
|
907
|
+
<div class="context-section">
|
|
908
|
+
<h4 class="context-section-title"><i data-lucide="file-text" class="w-4 h-4 inline mr-1"></i> ${t('multiCli.context.taskDescription')}</h4>
|
|
909
|
+
<p class="context-description">${escapeHtml(ctx.task_description)}</p>
|
|
910
|
+
</div>
|
|
911
|
+
`);
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
// Constraints
|
|
915
|
+
if (ctx.constraints?.length) {
|
|
916
|
+
sections.push(`
|
|
917
|
+
<div class="context-section">
|
|
918
|
+
<h4 class="context-section-title"><i data-lucide="alert-triangle" class="w-4 h-4 inline mr-1"></i> ${t('multiCli.context.constraints')}</h4>
|
|
919
|
+
<ul class="constraints-list">
|
|
920
|
+
${ctx.constraints.map(c => `<li>${escapeHtml(c)}</li>`).join('')}
|
|
921
|
+
</ul>
|
|
922
|
+
</div>
|
|
923
|
+
`);
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
// Focus Paths
|
|
927
|
+
if (ctx.focus_paths?.length) {
|
|
928
|
+
sections.push(`
|
|
929
|
+
<div class="context-section">
|
|
930
|
+
<h4 class="context-section-title"><i data-lucide="folder" class="w-4 h-4 inline mr-1"></i> ${t('multiCli.context.focusPaths')}</h4>
|
|
931
|
+
<div class="path-tags">
|
|
932
|
+
${ctx.focus_paths.map(p => `<span class="path-tag">${escapeHtml(p)}</span>`).join('')}
|
|
933
|
+
</div>
|
|
934
|
+
</div>
|
|
935
|
+
`);
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
// Relevant Files
|
|
939
|
+
if (ctx.relevant_files?.length) {
|
|
940
|
+
sections.push(`
|
|
941
|
+
<div class="context-section collapsible-section">
|
|
942
|
+
<div class="collapsible-header">
|
|
943
|
+
<span class="collapse-icon">▶</span>
|
|
944
|
+
<span class="section-label"><i data-lucide="files" class="w-4 h-4 inline mr-1"></i> ${t('multiCli.context.relevantFiles')} (${ctx.relevant_files.length})</span>
|
|
945
|
+
</div>
|
|
946
|
+
<div class="collapsible-content collapsed">
|
|
947
|
+
<ul class="files-list">
|
|
948
|
+
${ctx.relevant_files.map(f => `
|
|
949
|
+
<li class="file-item">
|
|
950
|
+
<span class="file-icon">📄</span>
|
|
951
|
+
<code>${escapeHtml(typeof f === 'string' ? f : f.path || f.file || '')}</code>
|
|
952
|
+
${f.reason ? `<span class="file-reason">${escapeHtml(f.reason)}</span>` : ''}
|
|
953
|
+
</li>
|
|
954
|
+
`).join('')}
|
|
955
|
+
</ul>
|
|
956
|
+
</div>
|
|
957
|
+
</div>
|
|
958
|
+
`);
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
// Dependencies
|
|
962
|
+
if (ctx.dependencies?.length) {
|
|
963
|
+
sections.push(`
|
|
964
|
+
<div class="context-section collapsible-section">
|
|
965
|
+
<div class="collapsible-header">
|
|
966
|
+
<span class="collapse-icon">▶</span>
|
|
967
|
+
<span class="section-label"><i data-lucide="git-branch" class="w-4 h-4 inline mr-1"></i> ${t('multiCli.context.dependencies')} (${ctx.dependencies.length})</span>
|
|
968
|
+
</div>
|
|
969
|
+
<div class="collapsible-content collapsed">
|
|
970
|
+
<ul class="deps-list">
|
|
971
|
+
${ctx.dependencies.map(d => `<li>${escapeHtml(typeof d === 'string' ? d : d.name || JSON.stringify(d))}</li>`).join('')}
|
|
972
|
+
</ul>
|
|
973
|
+
</div>
|
|
974
|
+
</div>
|
|
975
|
+
`);
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
// Conflict Risks
|
|
979
|
+
if (ctx.conflict_risks?.length) {
|
|
980
|
+
sections.push(`
|
|
981
|
+
<div class="context-section">
|
|
982
|
+
<h4 class="context-section-title"><i data-lucide="alert-circle" class="w-4 h-4 inline mr-1"></i> ${t('multiCli.context.conflictRisks')}</h4>
|
|
983
|
+
<ul class="risks-list">
|
|
984
|
+
${ctx.conflict_risks.map(r => `<li class="risk-item">${escapeHtml(typeof r === 'string' ? r : r.description || JSON.stringify(r))}</li>`).join('')}
|
|
985
|
+
</ul>
|
|
986
|
+
</div>
|
|
987
|
+
`);
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
// Session ID
|
|
991
|
+
if (ctx.session_id) {
|
|
992
|
+
sections.push(`
|
|
993
|
+
<div class="context-section">
|
|
994
|
+
<h4 class="context-section-title"><i data-lucide="hash" class="w-4 h-4 inline mr-1"></i> ${t('multiCli.context.sessionId')}</h4>
|
|
995
|
+
<code class="session-id-code">${escapeHtml(ctx.session_id)}</code>
|
|
996
|
+
</div>
|
|
997
|
+
`);
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
// Raw JSON
|
|
1001
|
+
sections.push(`
|
|
1002
|
+
<div class="context-section collapsible-section">
|
|
1003
|
+
<div class="collapsible-header">
|
|
1004
|
+
<span class="collapse-icon">▶</span>
|
|
1005
|
+
<span class="section-label">{ } ${t('multiCli.context.rawJson')}</span>
|
|
1006
|
+
</div>
|
|
1007
|
+
<div class="collapsible-content collapsed">
|
|
1008
|
+
<pre class="json-content">${escapeHtml(JSON.stringify(ctx, null, 2))}</pre>
|
|
1009
|
+
</div>
|
|
1010
|
+
</div>
|
|
1011
|
+
`);
|
|
1012
|
+
|
|
1013
|
+
return `<div class="context-tab-content">${sections.join('')}</div>`;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
/**
|
|
1017
|
+
* Load and render Summary tab
|
|
1018
|
+
*/
|
|
1019
|
+
async function loadAndRenderMultiCliSummaryTab(session, contentArea) {
|
|
1020
|
+
contentArea.innerHTML = `<div class="tab-loading">${t('common.loading') || 'Loading...'}</div>`;
|
|
1021
|
+
|
|
1022
|
+
try {
|
|
1023
|
+
if (window.SERVER_MODE && session.path) {
|
|
1024
|
+
const response = await fetch(`/api/session-detail?path=${encodeURIComponent(session.path)}&type=summary`);
|
|
1025
|
+
if (response.ok) {
|
|
1026
|
+
const data = await response.json();
|
|
1027
|
+
contentArea.innerHTML = renderMultiCliSummaryContent(data.summary, session);
|
|
1028
|
+
initCollapsibleSections(contentArea);
|
|
1029
|
+
if (typeof lucide !== 'undefined') lucide.createIcons();
|
|
1030
|
+
return;
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
// Fallback: show synthesis summary
|
|
1034
|
+
contentArea.innerHTML = renderMultiCliSummaryContent(null, session);
|
|
1035
|
+
initCollapsibleSections(contentArea);
|
|
1036
|
+
if (typeof lucide !== 'undefined') lucide.createIcons();
|
|
1037
|
+
} catch (err) {
|
|
1038
|
+
contentArea.innerHTML = `<div class="tab-error">Failed to load summary: ${err.message}</div>`;
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
/**
|
|
1043
|
+
* Render summary content for multi-cli
|
|
1044
|
+
*/
|
|
1045
|
+
function renderMultiCliSummaryContent(summary, session) {
|
|
1046
|
+
const synthesis = session.latestSynthesis || session.discussionTopic || {};
|
|
1047
|
+
const plan = session.plan || {};
|
|
1048
|
+
|
|
1049
|
+
// Use summary from file or build from synthesis
|
|
1050
|
+
const summaryText = summary || synthesis.convergence?.summary || plan.summary;
|
|
1051
|
+
|
|
1052
|
+
if (!summaryText && !synthesis.solutions?.length) {
|
|
1053
|
+
return `
|
|
1054
|
+
<div class="tab-empty-state">
|
|
1055
|
+
<div class="empty-icon"><i data-lucide="file-text" class="w-12 h-12"></i></div>
|
|
1056
|
+
<div class="empty-title">${t('empty.noSummary') || 'No Summary'}</div>
|
|
1057
|
+
<div class="empty-text">${t('empty.noSummaryText') || 'No summary available for this session.'}</div>
|
|
1058
|
+
</div>
|
|
1059
|
+
`;
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
let sections = [];
|
|
1063
|
+
|
|
1064
|
+
// Main Summary
|
|
1065
|
+
if (summaryText) {
|
|
1066
|
+
const summaryContent = typeof summaryText === 'string' ? summaryText : getI18nText(summaryText);
|
|
1067
|
+
sections.push(`
|
|
1068
|
+
<div class="summary-section">
|
|
1069
|
+
<h4 class="summary-section-title"><i data-lucide="file-text" class="w-4 h-4 inline mr-1"></i> ${t('multiCli.summary.title')}</h4>
|
|
1070
|
+
<div class="summary-content">${escapeHtml(summaryContent)}</div>
|
|
1071
|
+
</div>
|
|
1072
|
+
`);
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
// Convergence Status
|
|
1076
|
+
if (synthesis.convergence) {
|
|
1077
|
+
const conv = synthesis.convergence;
|
|
1078
|
+
sections.push(`
|
|
1079
|
+
<div class="summary-section">
|
|
1080
|
+
<h4 class="summary-section-title"><i data-lucide="git-merge" class="w-4 h-4 inline mr-1"></i> ${t('multiCli.summary.convergence')}</h4>
|
|
1081
|
+
<div class="convergence-info">
|
|
1082
|
+
<span class="convergence-level ${conv.level || ''}">${escapeHtml(conv.level || 'unknown')}</span>
|
|
1083
|
+
<span class="convergence-rec ${conv.recommendation || ''}">${escapeHtml(conv.recommendation || '')}</span>
|
|
1084
|
+
</div>
|
|
1085
|
+
</div>
|
|
1086
|
+
`);
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
// Solutions Summary
|
|
1090
|
+
if (synthesis.solutions?.length) {
|
|
1091
|
+
sections.push(`
|
|
1092
|
+
<div class="summary-section collapsible-section">
|
|
1093
|
+
<div class="collapsible-header">
|
|
1094
|
+
<span class="collapse-icon">▶</span>
|
|
1095
|
+
<span class="section-label"><i data-lucide="lightbulb" class="w-4 h-4 inline mr-1"></i> ${t('multiCli.summary.solutions')} (${synthesis.solutions.length})</span>
|
|
1096
|
+
</div>
|
|
1097
|
+
<div class="collapsible-content collapsed">
|
|
1098
|
+
${synthesis.solutions.map((sol, idx) => `
|
|
1099
|
+
<div class="solution-summary-item">
|
|
1100
|
+
<span class="solution-num">#${idx + 1}</span>
|
|
1101
|
+
<span class="solution-name">${escapeHtml(getI18nText(sol.title) || sol.id || `${t('multiCli.summary.solution')} ${idx + 1}`)}</span>
|
|
1102
|
+
${sol.feasibility?.score ? `<span class="feasibility-badge">${Math.round(sol.feasibility.score * 100)}%</span>` : ''}
|
|
1103
|
+
</div>
|
|
1104
|
+
`).join('')}
|
|
1105
|
+
</div>
|
|
1106
|
+
</div>
|
|
1107
|
+
`);
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
return `<div class="summary-tab-content">${sections.join('')}</div>`;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
/**
|
|
1114
|
+
* Render Discussion Topic tab
|
|
1115
|
+
* Shows: title, description, scope, keyQuestions, status, tags
|
|
1116
|
+
*/
|
|
1117
|
+
function renderMultiCliTopicTab(session) {
|
|
1118
|
+
const topic = session.discussionTopic || session.latestSynthesis?.discussionTopic || {};
|
|
1119
|
+
|
|
1120
|
+
if (!topic || Object.keys(topic).length === 0) {
|
|
1121
|
+
return `
|
|
1122
|
+
<div class="tab-empty-state">
|
|
1123
|
+
<div class="empty-icon"><i data-lucide="message-circle" class="w-12 h-12"></i></div>
|
|
1124
|
+
<div class="empty-title">${t('multiCli.empty.topic') || 'No Discussion Topic'}</div>
|
|
1125
|
+
<div class="empty-text">${t('multiCli.empty.topicText') || 'No discussion topic data available for this session.'}</div>
|
|
1126
|
+
</div>
|
|
1127
|
+
`;
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
const title = getI18nText(topic.title) || 'Untitled';
|
|
1131
|
+
const description = getI18nText(topic.description) || '';
|
|
1132
|
+
const scope = topic.scope || {};
|
|
1133
|
+
const keyQuestions = topic.keyQuestions || [];
|
|
1134
|
+
const status = topic.status || 'unknown';
|
|
1135
|
+
const tags = topic.tags || [];
|
|
1136
|
+
|
|
1137
|
+
let sections = [];
|
|
1138
|
+
|
|
1139
|
+
// Title and Description
|
|
1140
|
+
sections.push(`
|
|
1141
|
+
<div class="multi-cli-topic-section">
|
|
1142
|
+
<h3 class="multi-cli-topic-title">${escapeHtml(title)}</h3>
|
|
1143
|
+
${description ? `<p class="multi-cli-topic-description">${escapeHtml(description)}</p>` : ''}
|
|
1144
|
+
<div class="topic-meta">
|
|
1145
|
+
<span class="multi-cli-status ${status}">${escapeHtml(status)}</span>
|
|
1146
|
+
${tags.length ? tags.map(tag => `<span class="tag-badge">${escapeHtml(tag)}</span>`).join('') : ''}
|
|
1147
|
+
</div>
|
|
1148
|
+
</div>
|
|
1149
|
+
`);
|
|
1150
|
+
|
|
1151
|
+
// Scope (included/excluded)
|
|
1152
|
+
if (scope.included?.length || scope.excluded?.length) {
|
|
1153
|
+
sections.push(`
|
|
1154
|
+
<div class="multi-cli-section scope-section">
|
|
1155
|
+
<h4 class="section-title"><i data-lucide="target" class="w-4 h-4 inline mr-1"></i> ${t('multiCli.scope') || 'Scope'}</h4>
|
|
1156
|
+
${scope.included?.length ? `
|
|
1157
|
+
<div class="scope-included">
|
|
1158
|
+
<strong>${t('multiCli.scope.included') || 'Included'}:</strong>
|
|
1159
|
+
<ul class="scope-list">
|
|
1160
|
+
${scope.included.map(item => `<li>${escapeHtml(getI18nText(item))}</li>`).join('')}
|
|
1161
|
+
</ul>
|
|
1162
|
+
</div>
|
|
1163
|
+
` : ''}
|
|
1164
|
+
${scope.excluded?.length ? `
|
|
1165
|
+
<div class="scope-excluded">
|
|
1166
|
+
<strong>${t('multiCli.scope.excluded') || 'Excluded'}:</strong>
|
|
1167
|
+
<ul class="scope-list excluded">
|
|
1168
|
+
${scope.excluded.map(item => `<li>${escapeHtml(getI18nText(item))}</li>`).join('')}
|
|
1169
|
+
</ul>
|
|
1170
|
+
</div>
|
|
1171
|
+
` : ''}
|
|
1172
|
+
</div>
|
|
1173
|
+
`);
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
// Key Questions
|
|
1177
|
+
if (keyQuestions.length) {
|
|
1178
|
+
sections.push(`
|
|
1179
|
+
<div class="multi-cli-section questions-section">
|
|
1180
|
+
<h4 class="section-title"><i data-lucide="help-circle" class="w-4 h-4 inline mr-1"></i> ${t('multiCli.keyQuestions') || 'Key Questions'}</h4>
|
|
1181
|
+
<ol class="key-questions-list">
|
|
1182
|
+
${keyQuestions.map(q => `<li>${escapeHtml(getI18nText(q))}</li>`).join('')}
|
|
1183
|
+
</ol>
|
|
1184
|
+
</div>
|
|
1185
|
+
`);
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
return `<div class="multi-cli-topic-tab">${sections.join('')}</div>`;
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
/**
|
|
1192
|
+
* Render Related Files tab
|
|
1193
|
+
* Shows: fileTree, impactSummary
|
|
1194
|
+
*/
|
|
1195
|
+
function renderMultiCliFilesTab(session) {
|
|
1196
|
+
// Use helper to extract files from synthesis data structure
|
|
1197
|
+
const relatedFiles = extractFilesFromSynthesis(session.latestSynthesis);
|
|
1198
|
+
|
|
1199
|
+
if (!relatedFiles || (!relatedFiles.fileTree?.length && !relatedFiles.impactSummary?.length)) {
|
|
1200
|
+
return `
|
|
1201
|
+
<div class="tab-empty-state">
|
|
1202
|
+
<div class="empty-icon"><i data-lucide="folder-tree" class="w-12 h-12"></i></div>
|
|
1203
|
+
<div class="empty-title">${t('multiCli.empty.files') || 'No Related Files'}</div>
|
|
1204
|
+
<div class="empty-text">${t('multiCli.empty.filesText') || 'No file analysis data available for this session.'}</div>
|
|
1205
|
+
</div>
|
|
1206
|
+
`;
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
const fileTree = relatedFiles.fileTree || [];
|
|
1210
|
+
const impactSummary = relatedFiles.impactSummary || [];
|
|
1211
|
+
const dependencyGraph = relatedFiles.dependencyGraph || [];
|
|
1212
|
+
|
|
1213
|
+
let sections = [];
|
|
1214
|
+
|
|
1215
|
+
// File Tree
|
|
1216
|
+
if (fileTree.length) {
|
|
1217
|
+
sections.push(`
|
|
1218
|
+
<div class="multi-cli-section file-tree-section collapsible-section">
|
|
1219
|
+
<div class="collapsible-header">
|
|
1220
|
+
<span class="collapse-icon">►</span>
|
|
1221
|
+
<span class="section-label"><i data-lucide="folder-tree" class="w-4 h-4 inline mr-1"></i> ${t('multiCli.fileTree') || 'File Tree'} (${fileTree.length})</span>
|
|
1222
|
+
</div>
|
|
1223
|
+
<div class="collapsible-content collapsed">
|
|
1224
|
+
<div class="file-tree-list">
|
|
1225
|
+
${renderFileTreeNodes(fileTree)}
|
|
1226
|
+
</div>
|
|
1227
|
+
</div>
|
|
1228
|
+
</div>
|
|
1229
|
+
`);
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
// Impact Summary
|
|
1233
|
+
if (impactSummary.length) {
|
|
1234
|
+
sections.push(`
|
|
1235
|
+
<div class="multi-cli-section impact-section collapsible-section">
|
|
1236
|
+
<div class="collapsible-header">
|
|
1237
|
+
<span class="collapse-icon">►</span>
|
|
1238
|
+
<span class="section-label"><i data-lucide="alert-triangle" class="w-4 h-4 inline mr-1"></i> ${t('multiCli.impactSummary') || 'Impact Summary'} (${impactSummary.length})</span>
|
|
1239
|
+
</div>
|
|
1240
|
+
<div class="collapsible-content collapsed">
|
|
1241
|
+
<div class="impact-list">
|
|
1242
|
+
${impactSummary.map(impact => `
|
|
1243
|
+
<div class="impact-item impact-${impact.score || 'medium'}">
|
|
1244
|
+
<div class="impact-header">
|
|
1245
|
+
<code class="impact-file">${escapeHtml(impact.filePath || '')}</code>
|
|
1246
|
+
${impact.line ? `<span class="impact-line">:${impact.line}</span>` : ''}
|
|
1247
|
+
<span class="impact-score ${impact.score || 'medium'}">${escapeHtml(impact.score || 'medium')}</span>
|
|
1248
|
+
</div>
|
|
1249
|
+
${impact.reasoning ? `<div class="impact-reason">${escapeHtml(getI18nText(impact.reasoning))}</div>` : ''}
|
|
1250
|
+
</div>
|
|
1251
|
+
`).join('')}
|
|
1252
|
+
</div>
|
|
1253
|
+
</div>
|
|
1254
|
+
</div>
|
|
1255
|
+
`);
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
// Dependency Graph
|
|
1259
|
+
if (dependencyGraph.length) {
|
|
1260
|
+
sections.push(`
|
|
1261
|
+
<div class="multi-cli-section deps-section collapsible-section">
|
|
1262
|
+
<div class="collapsible-header">
|
|
1263
|
+
<span class="collapse-icon">►</span>
|
|
1264
|
+
<span class="section-label"><i data-lucide="git-branch" class="w-4 h-4 inline mr-1"></i> ${t('multiCli.dependencies') || 'Dependencies'} (${dependencyGraph.length})</span>
|
|
1265
|
+
</div>
|
|
1266
|
+
<div class="collapsible-content collapsed">
|
|
1267
|
+
<div class="deps-list">
|
|
1268
|
+
${dependencyGraph.map(edge => `
|
|
1269
|
+
<div class="dep-edge">
|
|
1270
|
+
<code>${escapeHtml(edge.source || '')}</code>
|
|
1271
|
+
<span class="dep-arrow">→</span>
|
|
1272
|
+
<code>${escapeHtml(edge.target || '')}</code>
|
|
1273
|
+
<span class="dep-relationship">(${escapeHtml(edge.relationship || 'depends')})</span>
|
|
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">▶</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">►</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">►</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">►</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">►</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">►</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 ? '▼' : '►'}</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];
|