"use client"; import { useState, useEffect } from "react"; import { useSortable } from "@dnd-kit/sortable"; import { CSS } from "@dnd-kit/utilities"; import { cn } from "@/lib/utils"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Feature, useAppStore } from "@/store/app-store"; import { GripVertical, Edit, CheckCircle2, Circle, Loader2, Trash2, Eye, PlayCircle, RotateCcw, StopCircle, FlaskConical, ArrowLeft, MessageSquare, GitCommit, Cpu, Wrench, ListTodo, Sparkles, Expand, } from "lucide-react"; import { CountUpTimer } from "@/components/ui/count-up-timer"; import { getElectronAPI } from "@/lib/electron"; import { parseAgentContext, AgentTaskInfo, formatModelName, DEFAULT_MODEL, } from "@/lib/agent-context-parser"; import { Markdown } from "@/components/ui/markdown"; interface KanbanCardProps { feature: Feature; onEdit: () => void; onDelete: () => void; onViewOutput?: () => void; onVerify?: () => void; onResume?: () => void; onForceStop?: () => void; onManualVerify?: () => void; onMoveBackToInProgress?: () => void; onFollowUp?: () => void; onCommit?: () => void; hasContext?: boolean; isCurrentAutoTask?: boolean; shortcutKey?: string; /** Context content for extracting progress info */ contextContent?: string; /** Feature summary from agent completion */ summary?: string; } export function KanbanCard({ feature, onEdit, onDelete, onViewOutput, onVerify, onResume, onForceStop, onManualVerify, onMoveBackToInProgress, onFollowUp, onCommit, hasContext, isCurrentAutoTask, shortcutKey, contextContent, summary, }: KanbanCardProps) { const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); const [isSummaryDialogOpen, setIsSummaryDialogOpen] = useState(false); const [agentInfo, setAgentInfo] = useState(null); const { kanbanCardDetailLevel } = useAppStore(); // Helper functions to check what should be shown based on detail level const showSteps = kanbanCardDetailLevel === "standard" || kanbanCardDetailLevel === "detailed"; const showAgentInfo = kanbanCardDetailLevel === "detailed"; const showProgressBar = kanbanCardDetailLevel === "standard" || kanbanCardDetailLevel === "detailed"; // Load context file for in_progress, waiting_approval, and verified features useEffect(() => { const loadContext = async () => { // Use provided context or load from file if (contextContent) { const info = parseAgentContext(contextContent); setAgentInfo(info); return; } // Only load for non-backlog features if (feature.status === "backlog") { setAgentInfo(null); return; } try { const api = getElectronAPI(); // eslint-disable-next-line @typescript-eslint/no-explicit-any const currentProject = (window as any).__currentProject; if (!currentProject?.path) return; const contextPath = `${currentProject.path}/.automaker/agents-context/${feature.id}.md`; const result = await api.readFile(contextPath); if (result.success && result.content) { const info = parseAgentContext(result.content); setAgentInfo(info); } } catch { // Context file might not exist console.debug("[KanbanCard] No context file for feature:", feature.id); } }; loadContext(); // Reload context periodically while feature is running if (isCurrentAutoTask) { const interval = setInterval(loadContext, 3000); return () => clearInterval(interval); } }, [feature.id, feature.status, contextContent, isCurrentAutoTask]); const handleDeleteClick = (e: React.MouseEvent) => { e.stopPropagation(); setIsDeleteDialogOpen(true); }; const handleConfirmDelete = () => { setIsDeleteDialogOpen(false); onDelete(); }; const handleCancelDelete = () => { setIsDeleteDialogOpen(false); }; // Dragging logic: // - Backlog items can always be dragged // - skipTests items can be dragged even when in_progress or verified (unless currently running) // - Non-skipTests (TDD) items in progress or verified cannot be dragged const isDraggable = feature.status === "backlog" || (feature.skipTests && !isCurrentAutoTask); const { attributes, listeners, setNodeRef, transform, transition, isDragging, } = useSortable({ id: feature.id, disabled: !isDraggable, }); const style = { transform: CSS.Transform.toString(transform), transition, }; return ( {/* Shortcut key badge for in-progress cards */} {shortcutKey && (
{shortcutKey}
)} {/* Skip Tests indicator badge */} {feature.skipTests && (
Manual
)} {isCurrentAutoTask && (
Running... {feature.startedAt && ( )}
)} {/* Show timer for in_progress cards that aren't currently running */} {!isCurrentAutoTask && feature.status === "in_progress" && feature.startedAt && (
)}
{isDraggable && (
)}
{feature.description} {feature.category}
{/* Steps Preview - Show in Standard and Detailed modes */} {showSteps && feature.steps.length > 0 && (
{feature.steps.slice(0, 3).map((step, index) => (
{feature.status === "verified" ? ( ) : ( )} {step}
))} {feature.steps.length > 3 && (

+{feature.steps.length - 3} more steps

)}
)} {/* Agent Info Panel - shows for in_progress, waiting_approval, verified */} {/* Standard mode: Only show progress bar */} {showProgressBar && !showAgentInfo && feature.status !== "backlog" && agentInfo && (isCurrentAutoTask || feature.status === "in_progress") && (
{Math.round(agentInfo.progressPercentage)}%
)} {/* Detailed mode: Show all agent info */} {showAgentInfo && feature.status !== "backlog" && agentInfo && (
{/* Model & Phase */}
{formatModelName(feature.model ?? DEFAULT_MODEL)}
{agentInfo.currentPhase && (
{agentInfo.currentPhase}
)}
{/* Progress Indicator */} {(isCurrentAutoTask || feature.status === "in_progress") && (
{agentInfo.toolCallCount} tools {agentInfo.lastToolUsed && ( {agentInfo.lastToolUsed} )}
{Math.round(agentInfo.progressPercentage)}%
)} {/* Task List Progress (if todos found) */} {agentInfo.todos.length > 0 && (
{ agentInfo.todos.filter((t) => t.status === "completed") .length } /{agentInfo.todos.length} tasks
{agentInfo.todos.slice(0, 3).map((todo, idx) => (
{todo.status === "completed" ? ( ) : todo.status === "in_progress" ? ( ) : ( )} {todo.content}
))} {agentInfo.todos.length > 3 && (

+{agentInfo.todos.length - 3} more

)}
)} {/* Summary for waiting_approval and verified - prioritize feature.summary from UpdateFeatureStatus */} {(feature.status === "waiting_approval" || feature.status === "verified") && ( <> {(feature.summary || summary || agentInfo.summary) && (
Summary

{feature.summary || summary || agentInfo.summary}

)} {/* Show tool count even without summary */} {!feature.summary && !summary && !agentInfo.summary && agentInfo.toolCallCount > 0 && (
{agentInfo.toolCallCount} tool calls {agentInfo.todos.length > 0 && ( { agentInfo.todos.filter( (t) => t.status === "completed" ).length }{" "} tasks done )}
)} )}
)} {/* Actions */}
{isCurrentAutoTask && ( <> {onViewOutput && ( )} {onForceStop && ( )} )} {!isCurrentAutoTask && feature.status === "in_progress" && ( <> {/* skipTests features show manual verify button */} {feature.skipTests && onManualVerify ? ( ) : hasContext && onResume ? ( ) : onVerify ? ( ) : null} {onViewOutput && !feature.skipTests && ( )} )} {!isCurrentAutoTask && feature.status === "verified" && ( <> {/* Move back button for skipTests verified features */} {feature.skipTests && onMoveBackToInProgress && ( )} )} {!isCurrentAutoTask && feature.status === "waiting_approval" && ( <> {/* Follow-up prompt button */} {onFollowUp && ( )} {/* Commit and verify button */} {onCommit && ( )} )} {!isCurrentAutoTask && feature.status === "backlog" && ( <> )}
{/* Delete Confirmation Dialog */} Delete Feature Are you sure you want to delete this feature? This action cannot be undone. {/* Summary Modal */} Implementation Summary {feature.description.length > 100 ? `${feature.description.slice(0, 100)}...` : feature.description}
{feature.summary || summary || agentInfo?.summary || "No summary available"}
); }