-
- {children}
- {/* Anchor for scroll to bottom */}
-
-
+ {children}
+ {/* Anchor for scroll to bottom */}
+
{/* Show scroll button when user is not at bottom */}
{showScrollButton && isUserScrolling && (
diff --git a/ccw/frontend/src/components/shared/TaskDrawer.tsx b/ccw/frontend/src/components/shared/TaskDrawer.tsx
index a70693b7..c01d0fe1 100644
--- a/ccw/frontend/src/components/shared/TaskDrawer.tsx
+++ b/ccw/frontend/src/components/shared/TaskDrawer.tsx
@@ -82,15 +82,16 @@ export function TaskDrawer({ task, isOpen, onClose }: TaskDrawerProps) {
return () => window.removeEventListener('keydown', handleEsc);
}, [isOpen, onClose]);
- if (!task || !isOpen) {
- return null;
- }
-
// Normalize task to unified flat format (handles old nested, new flat, and raw LiteTask/TaskData)
+ // MUST be called before early return to satisfy React hooks rules
const nt = React.useMemo(
- () => normalizeTask(task as unknown as Record
),
+ () => task ? normalizeTask(task as unknown as Record) : null,
[task],
);
+
+ if (!task || !isOpen || !nt) {
+ return null;
+ }
const taskId = nt.task_id || 'N/A';
const taskTitle = nt.title || 'Untitled Task';
const taskDescription = nt.description;
diff --git a/ccw/frontend/src/lib/api.ts b/ccw/frontend/src/lib/api.ts
index 73d9e914..5d188a1b 100644
--- a/ccw/frontend/src/lib/api.ts
+++ b/ccw/frontend/src/lib/api.ts
@@ -2173,6 +2173,75 @@ export interface LiteTaskSession {
status?: string;
createdAt?: string;
updatedAt?: string;
+ // Multi-cli-plan specific fields
+ rounds?: RoundSynthesis[];
+}
+
+// Multi-cli-plan synthesis types
+export interface SolutionFileAction {
+ file: string;
+ line: number;
+ action: 'modify' | 'create' | 'delete';
+}
+
+export interface SolutionTask {
+ id: string;
+ name: string;
+ depends_on: string[];
+ files: SolutionFileAction[];
+ key_point: string | null;
+}
+
+export interface Solution {
+ name: string;
+ source_cli: string[];
+ feasibility: number;
+ effort: string;
+ risk: string;
+ summary: string;
+ pros: string[];
+ cons: string[];
+ affected_files: SolutionFileAction[];
+ implementation_plan: {
+ approach: string;
+ tasks: SolutionTask[];
+ execution_flow: string;
+ milestones: string[];
+ };
+ dependencies: {
+ internal: string[];
+ external: string[];
+ };
+ technical_concerns: string[];
+}
+
+export interface SynthesisConvergence {
+ score: number;
+ new_insights: boolean;
+ recommendation: 'converged' | 'continue' | 'user_input_needed';
+ rationale: string;
+}
+
+export interface SynthesisCrossVerification {
+ agreements: string[];
+ disagreements: Array<{
+ topic: string;
+ gemini: string;
+ codex: string;
+ resolution: string | null;
+ }>;
+ resolution: string;
+}
+
+export interface RoundSynthesis {
+ round: number;
+ timestamp: string;
+ cli_executions: Record;
+ solutions: Solution[];
+ convergence: SynthesisConvergence;
+ cross_verification: SynthesisCrossVerification;
+ clarification_questions: string[];
+ user_feedback_incorporated?: string;
}
export interface LiteTasksResponse {
diff --git a/ccw/frontend/src/pages/LiteTasksPage.tsx b/ccw/frontend/src/pages/LiteTasksPage.tsx
index b8875b8a..5c45b914 100644
--- a/ccw/frontend/src/pages/LiteTasksPage.tsx
+++ b/ccw/frontend/src/pages/LiteTasksPage.tsx
@@ -30,6 +30,17 @@ import {
Clock,
AlertCircle,
FileCode,
+ ThumbsUp,
+ ThumbsDown,
+ Target,
+ GitCompare,
+ HelpCircle,
+ Cpu,
+ Timer,
+ Sparkles,
+ Layers,
+ CheckCheck,
+ ArrowRight,
} from 'lucide-react';
import { useLiteTasks } from '@/hooks/useLiteTasks';
import { Button } from '@/components/ui/Button';
@@ -37,7 +48,7 @@ import { Badge } from '@/components/ui/Badge';
import { Card, CardContent } from '@/components/ui/Card';
import { TabsNavigation } from '@/components/ui/TabsNavigation';
import { TaskDrawer } from '@/components/shared/TaskDrawer';
-import { fetchLiteSessionContext, type LiteTask, type LiteTaskSession, type LiteSessionContext } from '@/lib/api';
+import { fetchLiteSessionContext, type LiteTask, type LiteTaskSession, type LiteSessionContext, type RoundSynthesis } from '@/lib/api';
import { LiteContextContent } from '@/components/lite-tasks/LiteContextContent';
import { useNavigate } from 'react-router-dom';
@@ -532,25 +543,36 @@ function ExpandedMultiCliPanel({
{/* Discussion Tab */}
{activeTab === 'discussion' && (
-
-
-
-
-
- {formatMessage({ id: 'liteTasks.multiCli.discussionRounds' })}
-
- {roundCount} {formatMessage({ id: 'liteTasks.rounds' })}
-
-
- {formatMessage({ id: 'liteTasks.multiCli.discussionDescription' })}
-
- {goal && (
-
-
{goal}
+ {/* Rounds Detail */}
+ {session.rounds && session.rounds.length > 0 ? (
+ session.rounds.map((round, idx) => (
+
+ ))
+ ) : (
+
+
+
+
+
+ {formatMessage({ id: 'liteTasks.multiCli.discussionRounds' })}
+
+ {roundCount} {formatMessage({ id: 'liteTasks.rounds' })}
- )}
-
-
+
+ {formatMessage({ id: 'liteTasks.multiCli.discussionDescription' })}
+
+ {goal && (
+
+ )}
+
+
+ )}
)}
diff --git a/ccw/src/core/lite-scanner.ts b/ccw/src/core/lite-scanner.ts
index fff457f8..4259240f 100644
--- a/ccw/src/core/lite-scanner.ts
+++ b/ccw/src/core/lite-scanner.ts
@@ -118,6 +118,9 @@ interface LiteTaskDetail {
explorations: unknown[];
clarifications: unknown | null;
diagnoses?: Diagnoses;
+ // Multi-cli-plan specific
+ rounds?: RoundSynthesis[];
+ latestSynthesis?: RoundSynthesis | null;
}
/**
@@ -923,6 +926,9 @@ export async function getLiteTaskDetail(workflowDir: string, type: string, sessi
tasks: extractTasksFromSyntheses(syntheses),
explorations,
clarifications,
+ // Multi-cli-plan specific fields
+ rounds: syntheses,
+ latestSynthesis,
};
return detail;