diff --git a/apps/app/src/components/ui/task-progress-panel.tsx b/apps/app/src/components/ui/task-progress-panel.tsx index 4430e5e3..1753f309 100644 --- a/apps/app/src/components/ui/task-progress-panel.tsx +++ b/apps/app/src/components/ui/task-progress-panel.tsx @@ -2,9 +2,10 @@ import { useState, useEffect, useCallback } from "react"; import { cn } from "@/lib/utils"; -import { CheckCircle2, Circle, Loader2, ChevronDown, ChevronRight } from "lucide-react"; +import { Check, Loader2, Circle, ChevronDown, ChevronRight, FileCode } from "lucide-react"; import { getElectronAPI } from "@/lib/electron"; import type { AutoModeEvent } from "@/types/electron"; +import { Badge } from "@/components/ui/badge"; interface TaskInfo { id: string; @@ -23,8 +24,8 @@ interface TaskProgressPanelProps { export function TaskProgressPanel({ featureId, projectPath, className }: TaskProgressPanelProps) { const [tasks, setTasks] = useState([]); const [isExpanded, setIsExpanded] = useState(true); - const [currentTaskId, setCurrentTaskId] = useState(null); const [isLoading, setIsLoading] = useState(true); + const [currentTaskId, setCurrentTaskId] = useState(null); // Load initial tasks from feature's planSpec const loadInitialTasks = useCallback(async () => { @@ -91,14 +92,24 @@ export function TaskProgressPanel({ featureId, projectPath, className }: TaskPro setTasks((prev) => { // Check if task already exists - const existing = prev.find((t) => t.id === taskEvent.taskId); - if (existing) { - // Update status to in_progress - return prev.map((t) => - t.id === taskEvent.taskId ? { ...t, status: "in_progress" as const } : t - ); + const existingIndex = prev.findIndex((t) => t.id === taskEvent.taskId); + + if (existingIndex !== -1) { + // Update status to in_progress and mark previous as completed + return prev.map((t, idx) => { + if (t.id === taskEvent.taskId) { + return { ...t, status: "in_progress" as const }; + } + // If we are moving to a task that is further down the list, assume previous ones are completed + // This is a heuristic, but usually correct for sequential execution + if (idx < existingIndex && t.status !== "completed") { + return { ...t, status: "completed" as const }; + } + return t; + }); } - // Add new task + + // Add new task if it doesn't exist (fallback) return [ ...prev, { @@ -128,106 +139,136 @@ export function TaskProgressPanel({ featureId, projectPath, className }: TaskPro return unsubscribe; }, [featureId]); - // Calculate progress const completedCount = tasks.filter((t) => t.status === "completed").length; const totalCount = tasks.length; const progressPercent = totalCount > 0 ? Math.round((completedCount / totalCount) * 100) : 0; - // Don't render if loading or no tasks - if (isLoading) { - return null; - } - - if (tasks.length === 0) { + if (isLoading || tasks.length === 0) { return null; } return ( -
- {/* Header with progress */} +
- {/* Task list */} - {isExpanded && ( -
- {tasks.map((task) => ( -
- {/* Status icon */} - {task.status === "completed" ? ( - - ) : task.status === "in_progress" ? ( - - ) : ( - - )} +
+
+
+ {/* Vertical Connector Line */} +
+ +
+ {tasks.map((task, index) => { + const isActive = task.status === "in_progress"; + const isCompleted = task.status === "completed"; + const isPending = task.status === "pending"; - {/* Task info */} -
-
- - {task.id} - - - {task.description} - -
- {task.filePath && ( - - {task.filePath} - - )} -
+ {/* Icon Status */} +
+ {isCompleted && } + {isActive && } + {isPending && } +
+ + {/* Task Content */} +
+
+
+

+ {task.description} +

+ {isActive && ( + + Active + + )} +
+ + {(task.filePath || isActive) && ( +
+ {task.filePath ? ( + <> + + + {task.filePath} + + + ) : ( + /* Spacer */ + )} +
+ )} +
+
+
+ ); + })}
- ))} +
- )} +
); -} +} \ No newline at end of file diff --git a/apps/app/src/hooks/use-auto-mode.ts b/apps/app/src/hooks/use-auto-mode.ts index ef838bca..cb5112c3 100644 --- a/apps/app/src/hooks/use-auto-mode.ts +++ b/apps/app/src/hooks/use-auto-mode.ts @@ -155,10 +155,10 @@ export function useAutoMode() { case "auto_mode_error": if (event.featureId && event.error) { - // Check if this is a user-initiated cancellation (not a real error) - if (event.errorType === "cancellation") { - // User cancelled the feature - just log as info, not an error - console.log("[AutoMode] Feature cancelled:", event.error); + // Check if this is a user-initiated cancellation or abort (not a real error) + if (event.errorType === "cancellation" || event.errorType === "abort") { + // User cancelled/aborted the feature - just log as info, not an error + console.log("[AutoMode] Feature cancelled/aborted:", event.error); // Remove from running tasks if (eventProjectId) { removeRunningTask(eventProjectId, event.featureId); diff --git a/apps/app/src/types/electron.d.ts b/apps/app/src/types/electron.d.ts index 99905bf2..a0e3f31e 100644 --- a/apps/app/src/types/electron.d.ts +++ b/apps/app/src/types/electron.d.ts @@ -193,7 +193,7 @@ export type AutoModeEvent = | { type: "auto_mode_error"; error: string; - errorType?: "authentication" | "cancellation" | "execution"; + errorType?: "authentication" | "cancellation" | "abort" | "execution"; featureId?: string; projectId?: string; projectPath?: string; diff --git a/apps/server/src/lib/error-handler.ts b/apps/server/src/lib/error-handler.ts index 8f6dbc00..1ddc83a2 100644 --- a/apps/server/src/lib/error-handler.ts +++ b/apps/server/src/lib/error-handler.ts @@ -84,7 +84,9 @@ export function classifyError(error: unknown): ErrorInfo { let type: ErrorType; if (isAuth) { type = "authentication"; - } else if (isCancellation || isAbort) { + } else if (isAbort) { + type = "abort"; + } else if (isCancellation) { type = "cancellation"; } else if (error instanceof Error) { type = "execution";