From 01d78be7482eabd57ae1fcd9df40476879879761 Mon Sep 17 00:00:00 2001 From: Test User Date: Sat, 20 Dec 2025 12:28:54 -0500 Subject: [PATCH 1/3] refactor: Restructure KanbanCard component for improved organization and functionality - Moved KanbanCard logic into separate files for better modularity, including card actions, badges, content sections, and agent info panel. - Updated import paths to reflect new file structure. - Enhanced readability and maintainability of the KanbanCard component by breaking it down into smaller, focused components. - Removed the old KanbanCard implementation and replaced it with a new, organized structure that supports better code management. --- .../views/board-view/components/index.ts | 2 +- .../board-view/components/kanban-card.tsx | 1332 ----------------- .../kanban-card/agent-info-panel.tsx | 283 ++++ .../components/kanban-card/card-actions.tsx | 341 +++++ .../components/kanban-card/card-badges.tsx | 238 +++ .../kanban-card/card-content-sections.tsx | 82 + .../components/kanban-card/card-header.tsx | 330 ++++ .../components/kanban-card/kanban-card.tsx | 217 +++ .../components/kanban-card/summary-dialog.tsx | 75 + 9 files changed, 1567 insertions(+), 1333 deletions(-) delete mode 100644 apps/ui/src/components/views/board-view/components/kanban-card.tsx create mode 100644 apps/ui/src/components/views/board-view/components/kanban-card/agent-info-panel.tsx create mode 100644 apps/ui/src/components/views/board-view/components/kanban-card/card-actions.tsx create mode 100644 apps/ui/src/components/views/board-view/components/kanban-card/card-badges.tsx create mode 100644 apps/ui/src/components/views/board-view/components/kanban-card/card-content-sections.tsx create mode 100644 apps/ui/src/components/views/board-view/components/kanban-card/card-header.tsx create mode 100644 apps/ui/src/components/views/board-view/components/kanban-card/kanban-card.tsx create mode 100644 apps/ui/src/components/views/board-view/components/kanban-card/summary-dialog.tsx diff --git a/apps/ui/src/components/views/board-view/components/index.ts b/apps/ui/src/components/views/board-view/components/index.ts index 49cf06ef..ba0b54cc 100644 --- a/apps/ui/src/components/views/board-view/components/index.ts +++ b/apps/ui/src/components/views/board-view/components/index.ts @@ -1,2 +1,2 @@ -export { KanbanCard } from "./kanban-card"; +export { KanbanCard } from "./kanban-card/kanban-card"; export { KanbanColumn } from "./kanban-column"; diff --git a/apps/ui/src/components/views/board-view/components/kanban-card.tsx b/apps/ui/src/components/views/board-view/components/kanban-card.tsx deleted file mode 100644 index 7406201d..00000000 --- a/apps/ui/src/components/views/board-view/components/kanban-card.tsx +++ /dev/null @@ -1,1332 +0,0 @@ -import { useState, useEffect, useMemo, memo } 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 { HotkeyButton } from "@/components/ui/hotkey-button"; -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, -} from "@/components/ui/dialog"; -import { DeleteConfirmDialog } from "@/components/ui/delete-confirm-dialog"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; -import { Feature, useAppStore, ThinkingLevel } from "@/store/app-store"; -import { - GripVertical, - Edit, - CheckCircle2, - Circle, - Loader2, - Trash2, - Eye, - PlayCircle, - RotateCcw, - StopCircle, - Hand, - MessageSquare, - GitCommit, - Cpu, - Wrench, - ListTodo, - Sparkles, - Expand, - FileText, - MoreVertical, - AlertCircle, - GitBranch, - GitPullRequest, - ExternalLink, - ChevronDown, - ChevronUp, - Brain, - Wand2, - Archive, - Lock, -} from "lucide-react"; -import { CountUpTimer } from "@/components/ui/count-up-timer"; -import { getElectronAPI } from "@/lib/electron"; -import { getBlockingDependencies } from "@/lib/dependency-resolver"; -import { - parseAgentContext, - AgentTaskInfo, - formatModelName, - DEFAULT_MODEL, -} from "@/lib/agent-context-parser"; -import { Markdown } from "@/components/ui/markdown"; -import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from "@/components/ui/tooltip"; - -/** - * Formats thinking level for compact display - */ -function formatThinkingLevel(level: ThinkingLevel | undefined): string { - if (!level || level === "none") return ""; - const labels: Record = { - none: "", - low: "Low", - medium: "Med", // - high: "High", // - ultrathink: "Ultra", - }; - return labels[level]; -} - -interface KanbanCardProps { - feature: Feature; - onEdit: () => void; - onDelete: () => void; - onViewOutput?: () => void; - onVerify?: () => void; - onResume?: () => void; - onForceStop?: () => void; - onManualVerify?: () => void; - onMoveBackToInProgress?: () => void; - onFollowUp?: () => void; - onCommit?: () => void; - onImplement?: () => void; - onComplete?: () => void; - onViewPlan?: () => void; - onApprovePlan?: () => void; - hasContext?: boolean; - isCurrentAutoTask?: boolean; - shortcutKey?: string; - contextContent?: string; - summary?: string; - opacity?: number; - glassmorphism?: boolean; - cardBorderEnabled?: boolean; - cardBorderOpacity?: number; -} - -export const KanbanCard = memo(function KanbanCard({ - feature, - onEdit, - onDelete, - onViewOutput, - onVerify, - onResume, - onForceStop, - onManualVerify, - onMoveBackToInProgress, - onFollowUp, - onCommit, - onImplement, - onComplete, - onViewPlan, - onApprovePlan, - hasContext, - isCurrentAutoTask, - shortcutKey, - contextContent, - summary, - opacity = 100, - glassmorphism = true, - cardBorderEnabled = true, - cardBorderOpacity = 100, -}: KanbanCardProps) { - const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); - const [isSummaryDialogOpen, setIsSummaryDialogOpen] = useState(false); - const [agentInfo, setAgentInfo] = useState(null); - const [isDescriptionExpanded, setIsDescriptionExpanded] = useState(false); - const [currentTime, setCurrentTime] = useState(() => Date.now()); - const { - kanbanCardDetailLevel, - enableDependencyBlocking, - features, - useWorktrees, - } = useAppStore(); - - // Calculate blocking dependencies (if feature is in backlog and has incomplete dependencies) - const blockingDependencies = useMemo(() => { - if (!enableDependencyBlocking || feature.status !== "backlog") { - return []; - } - return getBlockingDependencies(feature, features); - }, [enableDependencyBlocking, feature, features]); - - const showSteps = - kanbanCardDetailLevel === "standard" || - kanbanCardDetailLevel === "detailed"; - const showAgentInfo = kanbanCardDetailLevel === "detailed"; - - const isJustFinished = useMemo(() => { - if ( - !feature.justFinishedAt || - feature.status !== "waiting_approval" || - feature.error - ) { - return false; - } - const finishedTime = new Date(feature.justFinishedAt).getTime(); - const twoMinutes = 2 * 60 * 1000; - return currentTime - finishedTime < twoMinutes; - }, [feature.justFinishedAt, feature.status, feature.error, currentTime]); - - useEffect(() => { - if (!feature.justFinishedAt || feature.status !== "waiting_approval") { - return; - } - - const finishedTime = new Date(feature.justFinishedAt).getTime(); - const twoMinutes = 2 * 60 * 1000; - const timeRemaining = twoMinutes - (currentTime - finishedTime); - - if (timeRemaining <= 0) { - return; - } - - const interval = setInterval(() => { - setCurrentTime(Date.now()); - }, 1000); - - return () => clearInterval(interval); - }, [feature.justFinishedAt, feature.status, currentTime]); - - useEffect(() => { - const loadContext = async () => { - if (contextContent) { - const info = parseAgentContext(contextContent); - setAgentInfo(info); - return; - } - - 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; - - if (api.features) { - const result = await api.features.getAgentOutput( - currentProject.path, - feature.id - ); - - if (result.success && result.content) { - const info = parseAgentContext(result.content); - setAgentInfo(info); - } - } else { - const contextPath = `${currentProject.path}/.automaker/features/${feature.id}/agent-output.md`; - const result = await api.readFile(contextPath); - - if (result.success && result.content) { - const info = parseAgentContext(result.content); - setAgentInfo(info); - } - } - } catch { - console.debug("[KanbanCard] No context file for feature:", feature.id); - } - }; - - loadContext(); - - 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 = () => { - onDelete(); - }; - - const isDraggable = - feature.status === "backlog" || - feature.status === "waiting_approval" || - feature.status === "verified" || - (feature.status === "in_progress" && !isCurrentAutoTask); - const { - attributes, - listeners, - setNodeRef, - transform, - transition, - isDragging, - } = useSortable({ - id: feature.id, - disabled: !isDraggable, - }); - - const style = { - transform: CSS.Transform.toString(transform), - transition, - opacity: isDragging ? 0.5 : undefined, - }; - - const borderStyle: React.CSSProperties = { ...style }; - if (!cardBorderEnabled) { - (borderStyle as Record).borderWidth = "0px"; - (borderStyle as Record).borderColor = "transparent"; - } else if (cardBorderOpacity !== 100) { - (borderStyle as Record).borderWidth = "1px"; - (borderStyle as Record).borderColor = - `color-mix(in oklch, var(--border) ${cardBorderOpacity}%, transparent)`; - } - - const cardElement = ( - - {/* Background overlay with opacity */} - {!isDragging && ( -
- )} - - {/* Compact Badge Row */} - {(feature.error || - (blockingDependencies.length > 0 && - !feature.error && - !feature.skipTests && - feature.status === "backlog") || - isJustFinished) && ( -
- {/* Error badge */} - {feature.error && ( - - - -
- -
-
- -

{feature.error}

-
-
-
- )} - - {/* Blocked badge */} - {blockingDependencies.length > 0 && - !feature.error && - !feature.skipTests && - feature.status === "backlog" && ( - - - -
- -
-
- -

- Blocked by {blockingDependencies.length} incomplete{" "} - {blockingDependencies.length === 1 - ? "dependency" - : "dependencies"} -

-

- {blockingDependencies - .map((depId) => { - const dep = features.find((f) => f.id === depId); - return dep?.description || depId; - }) - .join(", ")} -

-
-
-
- )} - - {/* Just Finished badge */} - {isJustFinished && ( -
- -
- )} -
- )} - - {/* Category row */} -
- - {feature.category} - -
- - - {/* Priority and Manual Verification badges - top left, aligned with delete button */} - {(feature.priority || - (feature.skipTests && - !feature.error && - feature.status === "backlog")) && ( -
- {/* Priority badge */} - {feature.priority && ( - - - -
- {feature.priority === 1 - ? "H" - : feature.priority === 2 - ? "M" - : "L"} -
-
- -

- {feature.priority === 1 - ? "High Priority" - : feature.priority === 2 - ? "Medium Priority" - : "Low Priority"} -

-
-
-
- )} - {/* Manual verification badge */} - {feature.skipTests && - !feature.error && - feature.status === "backlog" && ( - - - -
- -
-
- -

Manual verification required

-
-
-
- )} -
- )} - {isCurrentAutoTask && ( -
-
- - {feature.startedAt && ( - - )} -
- - - - - - { - e.stopPropagation(); - onEdit(); - }} - data-testid={`edit-running-${feature.id}`} - className="text-xs" - > - - Edit - - {/* Model info in dropdown */} -
-
- - - {formatModelName(feature.model ?? DEFAULT_MODEL)} - -
-
-
-
-
- )} - {!isCurrentAutoTask && feature.status === "backlog" && ( -
- -
- )} - {!isCurrentAutoTask && - (feature.status === "waiting_approval" || - feature.status === "verified") && ( - <> -
- - {onViewOutput && ( - - )} - -
- - )} - {!isCurrentAutoTask && feature.status === "in_progress" && ( - <> -
- - - - - - - { - e.stopPropagation(); - onEdit(); - }} - data-testid={`edit-feature-${feature.id}`} - className="text-xs" - > - - Edit - - {onViewOutput && ( - { - e.stopPropagation(); - onViewOutput(); - }} - data-testid={`view-logs-${feature.id}`} - className="text-xs" - > - - View Logs - - )} - {/* Model info in dropdown */} -
-
- - - {formatModelName(feature.model ?? DEFAULT_MODEL)} - -
-
-
-
-
- - )} -
- {isDraggable && ( -
- -
- )} -
- {feature.titleGenerating ? ( -
- - - Generating title... - -
- ) : feature.title ? ( - - {feature.title} - - ) : null} - - {feature.description || feature.summary || feature.id} - - {(feature.description || feature.summary || "").length > 100 && ( - - )} -
-
-
- - - {/* Target Branch Display */} - {useWorktrees && feature.branchName && ( -
- - - {feature.branchName} - -
- )} - - {/* PR URL Display */} - {typeof feature.prUrl === "string" && - /^https?:\/\//i.test(feature.prUrl) && - (() => { - const prNumber = feature.prUrl.split("/").pop(); - return ( - - ); - })()} - - {/* Steps Preview */} - {showSteps && feature.steps && 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 -

- )} -
- )} - - {/* Model/Preset Info for Backlog Cards */} - {showAgentInfo && feature.status === "backlog" && ( -
-
-
- - - {formatModelName(feature.model ?? DEFAULT_MODEL)} - -
- {feature.thinkingLevel && feature.thinkingLevel !== "none" && ( -
- - - {formatThinkingLevel(feature.thinkingLevel)} - -
- )} -
-
- )} - - {/* Agent Info Panel */} - {showAgentInfo && feature.status !== "backlog" && agentInfo && ( -
- {/* Model & Phase */} -
-
- - - {formatModelName(feature.model ?? DEFAULT_MODEL)} - -
- {agentInfo.currentPhase && ( -
- {agentInfo.currentPhase} -
- )} -
- - {/* Task List Progress */} - {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 */} - {(feature.status === "waiting_approval" || - feature.status === "verified") && ( - <> - {(feature.summary || summary || agentInfo.summary) && ( -
-
-
- - Summary -
- -
-

- {feature.summary || summary || agentInfo.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 && ( - <> - {/* Approve Plan button - PRIORITY: shows even when agent is "running" (paused for approval) */} - {feature.planSpec?.status === "generated" && onApprovePlan && ( - - )} - {onViewOutput && ( - - )} - {onForceStop && ( - - )} - - )} - {!isCurrentAutoTask && feature.status === "in_progress" && ( - <> - {/* Approve Plan button - shows when plan is generated and waiting for approval */} - {feature.planSpec?.status === "generated" && onApprovePlan && ( - - )} - {feature.skipTests && onManualVerify ? ( - - ) : hasContext && onResume ? ( - - ) : onVerify ? ( - - ) : null} - {onViewOutput && !feature.skipTests && ( - - )} - - )} - {!isCurrentAutoTask && feature.status === "verified" && ( - <> - {/* Logs button */} - {onViewOutput && ( - - )} - {/* Complete button */} - {onComplete && ( - - )} - - )} - {!isCurrentAutoTask && feature.status === "waiting_approval" && ( - <> - {/* Refine prompt button */} - {onFollowUp && ( - - )} - {/* Show Verify button if PR was created (changes are committed), otherwise show Commit button */} - {feature.prUrl && onManualVerify ? ( - - ) : onCommit ? ( - - ) : null} - - )} - {!isCurrentAutoTask && feature.status === "backlog" && ( - <> - - {feature.planSpec?.content && onViewPlan && ( - - )} - {onImplement && ( - - )} - - )} -
-
- - {/* Delete Confirmation Dialog */} - - - {/* Summary Modal */} - - - - - - Implementation Summary - - - {(() => { - const displayText = - feature.description || feature.summary || "No description"; - return displayText.length > 100 - ? `${displayText.slice(0, 100)}...` - : displayText; - })()} - - -
- - {feature.summary || - summary || - agentInfo?.summary || - "No summary available"} - -
- - - -
-
- - ); - - // Wrap with animated border when in progress - if (isCurrentAutoTask) { - return
{cardElement}
; - } - - return cardElement; -}); diff --git a/apps/ui/src/components/views/board-view/components/kanban-card/agent-info-panel.tsx b/apps/ui/src/components/views/board-view/components/kanban-card/agent-info-panel.tsx new file mode 100644 index 00000000..5a8e083f --- /dev/null +++ b/apps/ui/src/components/views/board-view/components/kanban-card/agent-info-panel.tsx @@ -0,0 +1,283 @@ +import { useEffect, useState } from "react"; +import { Feature, ThinkingLevel, useAppStore } from "@/store/app-store"; +import { + AgentTaskInfo, + parseAgentContext, + formatModelName, + DEFAULT_MODEL, +} from "@/lib/agent-context-parser"; +import { cn } from "@/lib/utils"; +import { + Cpu, + Brain, + ListTodo, + Sparkles, + Expand, + CheckCircle2, + Circle, + Loader2, + Wrench, +} from "lucide-react"; +import { getElectronAPI } from "@/lib/electron"; +import { SummaryDialog } from "./summary-dialog"; + +/** + * Formats thinking level for compact display + */ +function formatThinkingLevel(level: ThinkingLevel | undefined): string { + if (!level || level === "none") return ""; + const labels: Record = { + none: "", + low: "Low", + medium: "Med", + high: "High", + ultrathink: "Ultra", + }; + return labels[level]; +} + +interface AgentInfoPanelProps { + feature: Feature; + contextContent?: string; + summary?: string; + isCurrentAutoTask?: boolean; +} + +export function AgentInfoPanel({ + feature, + contextContent, + summary, + isCurrentAutoTask, +}: AgentInfoPanelProps) { + const { kanbanCardDetailLevel } = useAppStore(); + const [agentInfo, setAgentInfo] = useState(null); + const [isSummaryDialogOpen, setIsSummaryDialogOpen] = useState(false); + + const showAgentInfo = kanbanCardDetailLevel === "detailed"; + + useEffect(() => { + const loadContext = async () => { + if (contextContent) { + const info = parseAgentContext(contextContent); + setAgentInfo(info); + return; + } + + if (feature.status === "backlog") { + setAgentInfo(null); + return; + } + + try { + const api = getElectronAPI(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any, no-undef + const currentProject = (window as any).__currentProject; + if (!currentProject?.path) return; + + if (api.features) { + const result = await api.features.getAgentOutput( + currentProject.path, + feature.id + ); + + if (result.success && result.content) { + const info = parseAgentContext(result.content); + setAgentInfo(info); + } + } else { + const contextPath = `${currentProject.path}/.automaker/features/${feature.id}/agent-output.md`; + const result = await api.readFile(contextPath); + + if (result.success && result.content) { + const info = parseAgentContext(result.content); + setAgentInfo(info); + } + } + } catch { + // eslint-disable-next-line no-undef + console.debug("[KanbanCard] No context file for feature:", feature.id); + } + }; + + loadContext(); + + if (isCurrentAutoTask) { + // eslint-disable-next-line no-undef + const interval = setInterval(loadContext, 3000); + return () => { + // eslint-disable-next-line no-undef + clearInterval(interval); + }; + } + }, [feature.id, feature.status, contextContent, isCurrentAutoTask]); + // Model/Preset Info for Backlog Cards + if (showAgentInfo && feature.status === "backlog") { + return ( +
+
+
+ + + {formatModelName(feature.model ?? DEFAULT_MODEL)} + +
+ {feature.thinkingLevel && feature.thinkingLevel !== "none" && ( +
+ + + {formatThinkingLevel(feature.thinkingLevel)} + +
+ )} +
+
+ ); + } + + // Agent Info Panel for non-backlog cards + if (showAgentInfo && feature.status !== "backlog" && agentInfo) { + return ( +
+ {/* Model & Phase */} +
+
+ + + {formatModelName(feature.model ?? DEFAULT_MODEL)} + +
+ {agentInfo.currentPhase && ( +
+ {agentInfo.currentPhase} +
+ )} +
+ + {/* Task List Progress */} + {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 */} + {(feature.status === "waiting_approval" || + feature.status === "verified") && ( + <> + {(feature.summary || summary || agentInfo.summary) && ( +
+
+
+ + Summary +
+ +
+

+ {feature.summary || summary || agentInfo.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 + + )} +
+ )} + + )} +
+ ); + } + + // Always render SummaryDialog if showAgentInfo is true (even if no agentInfo yet) + // This ensures the dialog can be opened from the expand button + return ( + <> + {showAgentInfo && ( + + )} + + ); +} diff --git a/apps/ui/src/components/views/board-view/components/kanban-card/card-actions.tsx b/apps/ui/src/components/views/board-view/components/kanban-card/card-actions.tsx new file mode 100644 index 00000000..68fd7e37 --- /dev/null +++ b/apps/ui/src/components/views/board-view/components/kanban-card/card-actions.tsx @@ -0,0 +1,341 @@ +import { Feature } from "@/store/app-store"; +import { Button } from "@/components/ui/button"; +import { + Edit, + PlayCircle, + RotateCcw, + StopCircle, + CheckCircle2, + GitCommit, + FileText, + Eye, + Wand2, + Archive, +} from "lucide-react"; + +interface CardActionsProps { + feature: Feature; + isCurrentAutoTask: boolean; + hasContext?: boolean; + shortcutKey?: string; + onEdit: () => void; + onViewOutput?: () => void; + onVerify?: () => void; + onResume?: () => void; + onForceStop?: () => void; + onManualVerify?: () => void; + onFollowUp?: () => void; + onCommit?: () => void; + onImplement?: () => void; + onComplete?: () => void; + onViewPlan?: () => void; + onApprovePlan?: () => void; +} + +export function CardActions({ + feature, + isCurrentAutoTask, + hasContext, + shortcutKey, + onEdit, + onViewOutput, + onVerify, + onResume, + onForceStop, + onManualVerify, + onFollowUp, + onCommit, + onImplement, + onComplete, + onViewPlan, + onApprovePlan, +}: CardActionsProps) { + return ( +
+ {isCurrentAutoTask && ( + <> + {/* Approve Plan button - PRIORITY: shows even when agent is "running" (paused for approval) */} + {feature.planSpec?.status === "generated" && onApprovePlan && ( + + )} + {onViewOutput && ( + + )} + {onForceStop && ( + + )} + + )} + {!isCurrentAutoTask && feature.status === "in_progress" && ( + <> + {/* Approve Plan button - shows when plan is generated and waiting for approval */} + {feature.planSpec?.status === "generated" && onApprovePlan && ( + + )} + {feature.skipTests && onManualVerify ? ( + + ) : hasContext && onResume ? ( + + ) : onVerify ? ( + + ) : null} + {onViewOutput && !feature.skipTests && ( + + )} + + )} + {!isCurrentAutoTask && feature.status === "verified" && ( + <> + {/* Logs button */} + {onViewOutput && ( + + )} + {/* Complete button */} + {onComplete && ( + + )} + + )} + {!isCurrentAutoTask && feature.status === "waiting_approval" && ( + <> + {/* Refine prompt button */} + {onFollowUp && ( + + )} + {/* Show Verify button if PR was created (changes are committed), otherwise show Commit button */} + {feature.prUrl && onManualVerify ? ( + + ) : onCommit ? ( + + ) : null} + + )} + {!isCurrentAutoTask && feature.status === "backlog" && ( + <> + + {feature.planSpec?.content && onViewPlan && ( + + )} + {onImplement && ( + + )} + + )} +
+ ); +} + diff --git a/apps/ui/src/components/views/board-view/components/kanban-card/card-badges.tsx b/apps/ui/src/components/views/board-view/components/kanban-card/card-badges.tsx new file mode 100644 index 00000000..4fa5415a --- /dev/null +++ b/apps/ui/src/components/views/board-view/components/kanban-card/card-badges.tsx @@ -0,0 +1,238 @@ +import { useEffect, useMemo, useState } from "react"; +import { Feature, useAppStore } from "@/store/app-store"; +import { cn } from "@/lib/utils"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { AlertCircle, Lock, Sparkles, Hand } from "lucide-react"; +import { getBlockingDependencies } from "@/lib/dependency-resolver"; + +interface CardBadgesProps { + feature: Feature; +} + +export function CardBadges({ feature }: CardBadgesProps) { + const { enableDependencyBlocking, features } = useAppStore(); + const [currentTime, setCurrentTime] = useState(() => Date.now()); + + // Calculate blocking dependencies (if feature is in backlog and has incomplete dependencies) + const blockingDependencies = useMemo(() => { + if (!enableDependencyBlocking || feature.status !== "backlog") { + return []; + } + return getBlockingDependencies(feature, features); + }, [enableDependencyBlocking, feature, features]); + + const isJustFinished = useMemo(() => { + if ( + !feature.justFinishedAt || + feature.status !== "waiting_approval" || + feature.error + ) { + return false; + } + const finishedTime = new Date(feature.justFinishedAt).getTime(); + const twoMinutes = 2 * 60 * 1000; + return currentTime - finishedTime < twoMinutes; + }, [feature.justFinishedAt, feature.status, feature.error, currentTime]); + + useEffect(() => { + if (!feature.justFinishedAt || feature.status !== "waiting_approval") { + return; + } + + const finishedTime = new Date(feature.justFinishedAt).getTime(); + const twoMinutes = 2 * 60 * 1000; + const timeRemaining = twoMinutes - (currentTime - finishedTime); + + if (timeRemaining <= 0) { + return; + } + + // eslint-disable-next-line no-undef + const interval = setInterval(() => { + setCurrentTime(Date.now()); + }, 1000); + + return () => { + // eslint-disable-next-line no-undef + clearInterval(interval); + }; + }, [feature.justFinishedAt, feature.status, currentTime]); + // Status badges row (error, blocked, just finished) + const showStatusBadges = + feature.error || + (blockingDependencies.length > 0 && + !feature.error && + !feature.skipTests && + feature.status === "backlog") || + isJustFinished; + + if (!showStatusBadges) { + return null; + } + + return ( +
+ {/* Error badge */} + {feature.error && ( + + + +
+ +
+
+ +

{feature.error}

+
+
+
+ )} + + {/* Blocked badge */} + {blockingDependencies.length > 0 && + !feature.error && + !feature.skipTests && + feature.status === "backlog" && ( + + + +
+ +
+
+ +

+ Blocked by {blockingDependencies.length} incomplete{" "} + {blockingDependencies.length === 1 ? "dependency" : "dependencies"} +

+

+ {blockingDependencies + .map((depId) => { + const dep = features.find((f) => f.id === depId); + return dep?.description || depId; + }) + .join(", ")} +

+
+
+
+ )} + + {/* Just Finished badge */} + {isJustFinished && ( +
+ +
+ )} +
+ ); +} + +interface PriorityBadgesProps { + feature: Feature; +} + +export function PriorityBadges({ feature }: PriorityBadgesProps) { + const showPriorityBadges = + feature.priority || + (feature.skipTests && + !feature.error && + feature.status === "backlog"); + + if (!showPriorityBadges) { + return null; + } + + return ( +
+ {/* Priority badge */} + {feature.priority && ( + + + +
+ {feature.priority === 1 + ? "H" + : feature.priority === 2 + ? "M" + : "L"} +
+
+ +

+ {feature.priority === 1 + ? "High Priority" + : feature.priority === 2 + ? "Medium Priority" + : "Low Priority"} +

+
+
+
+ )} + {/* Manual verification badge */} + {feature.skipTests && + !feature.error && + feature.status === "backlog" && ( + + + +
+ +
+
+ +

Manual verification required

+
+
+
+ )} +
+ ); +} + diff --git a/apps/ui/src/components/views/board-view/components/kanban-card/card-content-sections.tsx b/apps/ui/src/components/views/board-view/components/kanban-card/card-content-sections.tsx new file mode 100644 index 00000000..07ad0552 --- /dev/null +++ b/apps/ui/src/components/views/board-view/components/kanban-card/card-content-sections.tsx @@ -0,0 +1,82 @@ +import { Feature } from "@/store/app-store"; +import { GitBranch, GitPullRequest, ExternalLink, CheckCircle2, Circle } from "lucide-react"; + +interface CardContentSectionsProps { + feature: Feature; + useWorktrees: boolean; + showSteps: boolean; +} + +export function CardContentSections({ + feature, + useWorktrees, + showSteps, +}: CardContentSectionsProps) { + return ( + <> + {/* Target Branch Display */} + {useWorktrees && feature.branchName && ( +
+ + + {feature.branchName} + +
+ )} + + {/* PR URL Display */} + {typeof feature.prUrl === "string" && + /^https?:\/\//i.test(feature.prUrl) && + (() => { + const prNumber = feature.prUrl.split("/").pop(); + return ( + + ); + })()} + + {/* Steps Preview */} + {showSteps && feature.steps && 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 +

+ )} +
+ )} + + ); +} + diff --git a/apps/ui/src/components/views/board-view/components/kanban-card/card-header.tsx b/apps/ui/src/components/views/board-view/components/kanban-card/card-header.tsx new file mode 100644 index 00000000..1d3bc41d --- /dev/null +++ b/apps/ui/src/components/views/board-view/components/kanban-card/card-header.tsx @@ -0,0 +1,330 @@ +import { useState } from "react"; +import { Feature } from "@/store/app-store"; +import { cn } from "@/lib/utils"; +import { + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { + GripVertical, + Edit, + Loader2, + Trash2, + FileText, + MoreVertical, + ChevronDown, + ChevronUp, + Cpu, +} from "lucide-react"; +import { CountUpTimer } from "@/components/ui/count-up-timer"; +import { formatModelName, DEFAULT_MODEL } from "@/lib/agent-context-parser"; +import { DeleteConfirmDialog } from "@/components/ui/delete-confirm-dialog"; + +interface CardHeaderProps { + feature: Feature; + isDraggable: boolean; + isCurrentAutoTask: boolean; + onEdit: () => void; + onDelete: () => void; + onViewOutput?: () => void; +} + +export function CardHeaderSection({ + feature, + isDraggable, + isCurrentAutoTask, + onEdit, + onDelete, + onViewOutput, +}: CardHeaderProps) { + const [isDescriptionExpanded, setIsDescriptionExpanded] = useState(false); + const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); + + const handleDeleteClick = (e: React.MouseEvent) => { + e.stopPropagation(); + setIsDeleteDialogOpen(true); + }; + + const handleConfirmDelete = () => { + onDelete(); + }; + + return ( + + {/* Running task header */} + {isCurrentAutoTask && ( +
+
+ + {feature.startedAt && ( + + )} +
+ + + + + + { + e.stopPropagation(); + onEdit(); + }} + data-testid={`edit-running-${feature.id}`} + className="text-xs" + > + + Edit + + {/* Model info in dropdown */} +
+
+ + + {formatModelName(feature.model ?? DEFAULT_MODEL)} + +
+
+
+
+
+ )} + + {/* Backlog header */} + {!isCurrentAutoTask && feature.status === "backlog" && ( +
+ +
+ )} + + {/* Waiting approval / Verified header */} + {!isCurrentAutoTask && + (feature.status === "waiting_approval" || + feature.status === "verified") && ( + <> +
+ + {onViewOutput && ( + + )} + +
+ + )} + + {/* In progress header */} + {!isCurrentAutoTask && feature.status === "in_progress" && ( + <> +
+ + + + + + + { + e.stopPropagation(); + onEdit(); + }} + data-testid={`edit-feature-${feature.id}`} + className="text-xs" + > + + Edit + + {onViewOutput && ( + { + e.stopPropagation(); + onViewOutput(); + }} + data-testid={`view-logs-${feature.id}`} + className="text-xs" + > + + View Logs + + )} + {/* Model info in dropdown */} +
+
+ + + {formatModelName(feature.model ?? DEFAULT_MODEL)} + +
+
+
+
+
+ + )} + + {/* Title and description */} +
+ {isDraggable && ( +
+ +
+ )} +
+ {feature.titleGenerating ? ( +
+ + + Generating title... + +
+ ) : feature.title ? ( + + {feature.title} + + ) : null} + + {feature.description || feature.summary || feature.id} + + {(feature.description || feature.summary || "").length > 100 && ( + + )} +
+
+ + {/* Delete Confirmation Dialog */} + +
+ ); +} + diff --git a/apps/ui/src/components/views/board-view/components/kanban-card/kanban-card.tsx b/apps/ui/src/components/views/board-view/components/kanban-card/kanban-card.tsx new file mode 100644 index 00000000..79cb9c4d --- /dev/null +++ b/apps/ui/src/components/views/board-view/components/kanban-card/kanban-card.tsx @@ -0,0 +1,217 @@ +import React, { memo } from "react"; +import { useSortable } from "@dnd-kit/sortable"; +import { CSS } from "@dnd-kit/utilities"; +import { cn } from "@/lib/utils"; +import { Card, CardContent } from "@/components/ui/card"; +import { Feature, useAppStore } from "@/store/app-store"; +import { CardBadges, PriorityBadges } from "./card-badges"; +import { CardHeaderSection } from "./card-header"; +import { CardContentSections } from "./card-content-sections"; +import { AgentInfoPanel } from "./agent-info-panel"; +import { CardActions } from "./card-actions"; + +interface KanbanCardProps { + feature: Feature; + onEdit: () => void; + onDelete: () => void; + onViewOutput?: () => void; + onVerify?: () => void; + onResume?: () => void; + onForceStop?: () => void; + onManualVerify?: () => void; + onMoveBackToInProgress?: () => void; + onFollowUp?: () => void; + onCommit?: () => void; + onImplement?: () => void; + onComplete?: () => void; + onViewPlan?: () => void; + onApprovePlan?: () => void; + hasContext?: boolean; + isCurrentAutoTask?: boolean; + shortcutKey?: string; + contextContent?: string; + summary?: string; + opacity?: number; + glassmorphism?: boolean; + cardBorderEnabled?: boolean; + cardBorderOpacity?: number; +} + +export const KanbanCard = memo(function KanbanCard({ + feature, + onEdit, + onDelete, + onViewOutput, + onVerify, + onResume, + onForceStop, + onManualVerify, + onMoveBackToInProgress: _onMoveBackToInProgress, + onFollowUp, + onCommit, + onImplement, + onComplete, + onViewPlan, + onApprovePlan, + hasContext, + isCurrentAutoTask, + shortcutKey, + contextContent, + summary, + opacity = 100, + glassmorphism = true, + cardBorderEnabled = true, + cardBorderOpacity = 100, +}: KanbanCardProps) { + const { kanbanCardDetailLevel, useWorktrees } = useAppStore(); + + const showSteps = + kanbanCardDetailLevel === "standard" || + kanbanCardDetailLevel === "detailed"; + + const isDraggable = + feature.status === "backlog" || + feature.status === "waiting_approval" || + feature.status === "verified" || + (feature.status === "in_progress" && !isCurrentAutoTask); + const { + attributes, + listeners, + setNodeRef, + transform, + transition, + isDragging, + } = useSortable({ + id: feature.id, + disabled: !isDraggable, + }); + + const style = { + transform: CSS.Transform.toString(transform), + transition, + opacity: isDragging ? 0.5 : undefined, + }; + + const borderStyle: React.CSSProperties = { ...style }; + if (!cardBorderEnabled) { + (borderStyle as Record).borderWidth = "0px"; + (borderStyle as Record).borderColor = "transparent"; + } else if (cardBorderOpacity !== 100) { + (borderStyle as Record).borderWidth = "1px"; + (borderStyle as Record).borderColor = + `color-mix(in oklch, var(--border) ${cardBorderOpacity}%, transparent)`; + } + + const cardElement = ( + + {/* Background overlay with opacity */} + {!isDragging && ( +
+ )} + + {/* Status Badges Row */} + + + {/* Category row */} +
+ + {feature.category} + +
+ + {/* Priority and Manual Verification badges */} + + + {/* Card Header */} + + + + {/* Content Sections */} + + + {/* Agent Info Panel */} + + + {/* Actions */} + + + + ); + + // Wrap with animated border when in progress + if (isCurrentAutoTask) { + return
{cardElement}
; + } + + return cardElement; +}); diff --git a/apps/ui/src/components/views/board-view/components/kanban-card/summary-dialog.tsx b/apps/ui/src/components/views/board-view/components/kanban-card/summary-dialog.tsx new file mode 100644 index 00000000..08a0dfc8 --- /dev/null +++ b/apps/ui/src/components/views/board-view/components/kanban-card/summary-dialog.tsx @@ -0,0 +1,75 @@ +import { Feature } from "@/store/app-store"; +import { AgentTaskInfo } from "@/lib/agent-context-parser"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { Markdown } from "@/components/ui/markdown"; +import { Sparkles } from "lucide-react"; + +interface SummaryDialogProps { + feature: Feature; + agentInfo: AgentTaskInfo | null; + summary?: string; + isOpen: boolean; + onOpenChange: (open: boolean) => void; +} + +export function SummaryDialog({ + feature, + agentInfo, + summary, + isOpen, + onOpenChange, +}: SummaryDialogProps) { + return ( + + + + + + Implementation Summary + + + {(() => { + const displayText = + feature.description || feature.summary || "No description"; + return displayText.length > 100 + ? `${displayText.slice(0, 100)}...` + : displayText; + })()} + + +
+ + {feature.summary || + summary || + agentInfo?.summary || + "No summary available"} + +
+ + + +
+
+ ); +} + From 723274523d42e1e57d25a66c3795a25694900081 Mon Sep 17 00:00:00 2001 From: Test User Date: Sat, 20 Dec 2025 12:45:51 -0500 Subject: [PATCH 2/3] refactor: Remove commit actions and update badge logic in Kanban components - Removed the onCommit action from KanbanBoard and related components to streamline functionality. - Updated CardActions to replace the Commit button with a Mark as Verified button, enhancing clarity in user interactions. - Introduced a new CardBadge component for consistent styling of badges across KanbanCard, improving code reusability and maintainability. - Refactored badge rendering logic to include a Just Finished badge, ensuring accurate representation of feature status. --- .../components/kanban-card/card-actions.tsx | 16 +- .../components/kanban-card/card-badges.tsx | 224 ++++++++++-------- .../components/kanban-card/kanban-card.tsx | 3 - .../views/board-view/kanban-board.tsx | 1 - 4 files changed, 132 insertions(+), 112 deletions(-) diff --git a/apps/ui/src/components/views/board-view/components/kanban-card/card-actions.tsx b/apps/ui/src/components/views/board-view/components/kanban-card/card-actions.tsx index 68fd7e37..24c0eb85 100644 --- a/apps/ui/src/components/views/board-view/components/kanban-card/card-actions.tsx +++ b/apps/ui/src/components/views/board-view/components/kanban-card/card-actions.tsx @@ -6,7 +6,6 @@ import { RotateCcw, StopCircle, CheckCircle2, - GitCommit, FileText, Eye, Wand2, @@ -25,7 +24,6 @@ interface CardActionsProps { onForceStop?: () => void; onManualVerify?: () => void; onFollowUp?: () => void; - onCommit?: () => void; onImplement?: () => void; onComplete?: () => void; onViewPlan?: () => void; @@ -44,7 +42,6 @@ export function CardActions({ onForceStop, onManualVerify, onFollowUp, - onCommit, onImplement, onComplete, onViewPlan, @@ -251,7 +248,7 @@ export function CardActions({ Refine )} - {/* Show Verify button if PR was created (changes are committed), otherwise show Commit button */} + {/* Show Verify button if PR was created (changes are committed), otherwise show Mark as Verified button */} {feature.prUrl && onManualVerify ? ( - ) : onCommit ? ( + ) : onManualVerify ? ( ) : null} @@ -338,4 +335,3 @@ export function CardActions({
); } - diff --git a/apps/ui/src/components/views/board-view/components/kanban-card/card-badges.tsx b/apps/ui/src/components/views/board-view/components/kanban-card/card-badges.tsx index 4fa5415a..fdfa4cf6 100644 --- a/apps/ui/src/components/views/board-view/components/kanban-card/card-badges.tsx +++ b/apps/ui/src/components/views/board-view/components/kanban-card/card-badges.tsx @@ -7,16 +7,46 @@ import { TooltipProvider, TooltipTrigger, } from "@/components/ui/tooltip"; -import { AlertCircle, Lock, Sparkles, Hand } from "lucide-react"; +import { AlertCircle, Lock, Hand, Sparkles } from "lucide-react"; import { getBlockingDependencies } from "@/lib/dependency-resolver"; +interface CardBadgeProps { + children: React.ReactNode; + className?: string; + "data-testid"?: string; + title?: string; +} + +/** + * Shared badge component matching the "Just Finished" badge style + * Used for priority badges and other card badges + */ +function CardBadge({ + children, + className, + "data-testid": dataTestId, + title, +}: CardBadgeProps) { + return ( +
+ {children} +
+ ); +} + interface CardBadgesProps { feature: Feature; } export function CardBadges({ feature }: CardBadgesProps) { const { enableDependencyBlocking, features } = useAppStore(); - const [currentTime, setCurrentTime] = useState(() => Date.now()); // Calculate blocking dependencies (if feature is in backlog and has incomplete dependencies) const blockingDependencies = useMemo(() => { @@ -26,50 +56,13 @@ export function CardBadges({ feature }: CardBadgesProps) { return getBlockingDependencies(feature, features); }, [enableDependencyBlocking, feature, features]); - const isJustFinished = useMemo(() => { - if ( - !feature.justFinishedAt || - feature.status !== "waiting_approval" || - feature.error - ) { - return false; - } - const finishedTime = new Date(feature.justFinishedAt).getTime(); - const twoMinutes = 2 * 60 * 1000; - return currentTime - finishedTime < twoMinutes; - }, [feature.justFinishedAt, feature.status, feature.error, currentTime]); - - useEffect(() => { - if (!feature.justFinishedAt || feature.status !== "waiting_approval") { - return; - } - - const finishedTime = new Date(feature.justFinishedAt).getTime(); - const twoMinutes = 2 * 60 * 1000; - const timeRemaining = twoMinutes - (currentTime - finishedTime); - - if (timeRemaining <= 0) { - return; - } - - // eslint-disable-next-line no-undef - const interval = setInterval(() => { - setCurrentTime(Date.now()); - }, 1000); - - return () => { - // eslint-disable-next-line no-undef - clearInterval(interval); - }; - }, [feature.justFinishedAt, feature.status, currentTime]); - // Status badges row (error, blocked, just finished) + // Status badges row (error, blocked) const showStatusBadges = feature.error || (blockingDependencies.length > 0 && !feature.error && !feature.skipTests && - feature.status === "backlog") || - isJustFinished; + feature.status === "backlog"); if (!showStatusBadges) { return null; @@ -117,13 +110,12 @@ export function CardBadges({ feature }: CardBadgesProps) {
- +

Blocked by {blockingDependencies.length} incomplete{" "} - {blockingDependencies.length === 1 ? "dependency" : "dependencies"} + {blockingDependencies.length === 1 + ? "dependency" + : "dependencies"}

{blockingDependencies @@ -137,21 +129,6 @@ export function CardBadges({ feature }: CardBadgesProps) { )} - - {/* Just Finished badge */} - {isJustFinished && ( -

- -
- )} ); } @@ -161,11 +138,49 @@ interface PriorityBadgesProps { } export function PriorityBadges({ feature }: PriorityBadgesProps) { + const [currentTime, setCurrentTime] = useState(() => Date.now()); + + const isJustFinished = useMemo(() => { + if ( + !feature.justFinishedAt || + feature.status !== "waiting_approval" || + feature.error + ) { + return false; + } + const finishedTime = new Date(feature.justFinishedAt).getTime(); + const twoMinutes = 2 * 60 * 1000; + return currentTime - finishedTime < twoMinutes; + }, [feature.justFinishedAt, feature.status, feature.error, currentTime]); + + useEffect(() => { + if (!feature.justFinishedAt || feature.status !== "waiting_approval") { + return; + } + + const finishedTime = new Date(feature.justFinishedAt).getTime(); + const twoMinutes = 2 * 60 * 1000; + const timeRemaining = twoMinutes - (currentTime - finishedTime); + + if (timeRemaining <= 0) { + return; + } + + // eslint-disable-next-line no-undef + const interval = setInterval(() => { + setCurrentTime(Date.now()); + }, 1000); + + return () => { + // eslint-disable-next-line no-undef + clearInterval(interval); + }; + }, [feature.justFinishedAt, feature.status, currentTime]); + const showPriorityBadges = feature.priority || - (feature.skipTests && - !feature.error && - feature.status === "backlog"); + (feature.skipTests && !feature.error && feature.status === "backlog") || + isJustFinished; if (!showPriorityBadges) { return null; @@ -178,24 +193,32 @@ export function PriorityBadges({ feature }: PriorityBadgesProps) { -
- {feature.priority === 1 - ? "H" - : feature.priority === 2 - ? "M" - : "L"} -
+ {feature.priority === 1 ? ( + + H + + ) : feature.priority === 2 ? ( + + M + + ) : ( + + L + + )} +

@@ -210,29 +233,34 @@ export function PriorityBadges({ feature }: PriorityBadgesProps) { )} {/* Manual verification badge */} - {feature.skipTests && - !feature.error && - feature.status === "backlog" && ( - - - -

- -
- - -

Manual verification required

-
-
-
- )} + {feature.skipTests && !feature.error && feature.status === "backlog" && ( + + + + + + + + +

Manual verification required

+
+
+
+ )} + + {/* Just Finished badge */} + {isJustFinished && ( + + + + )} ); } - diff --git a/apps/ui/src/components/views/board-view/components/kanban-card/kanban-card.tsx b/apps/ui/src/components/views/board-view/components/kanban-card/kanban-card.tsx index 79cb9c4d..84ab2c76 100644 --- a/apps/ui/src/components/views/board-view/components/kanban-card/kanban-card.tsx +++ b/apps/ui/src/components/views/board-view/components/kanban-card/kanban-card.tsx @@ -21,7 +21,6 @@ interface KanbanCardProps { onManualVerify?: () => void; onMoveBackToInProgress?: () => void; onFollowUp?: () => void; - onCommit?: () => void; onImplement?: () => void; onComplete?: () => void; onViewPlan?: () => void; @@ -48,7 +47,6 @@ export const KanbanCard = memo(function KanbanCard({ onManualVerify, onMoveBackToInProgress: _onMoveBackToInProgress, onFollowUp, - onCommit, onImplement, onComplete, onViewPlan, @@ -198,7 +196,6 @@ export const KanbanCard = memo(function KanbanCard({ onForceStop={onForceStop} onManualVerify={onManualVerify} onFollowUp={onFollowUp} - onCommit={onCommit} onImplement={onImplement} onComplete={onComplete} onViewPlan={onViewPlan} diff --git a/apps/ui/src/components/views/board-view/kanban-board.tsx b/apps/ui/src/components/views/board-view/kanban-board.tsx index 1ebbf042..1dddffe3 100644 --- a/apps/ui/src/components/views/board-view/kanban-board.tsx +++ b/apps/ui/src/components/views/board-view/kanban-board.tsx @@ -194,7 +194,6 @@ export function KanbanBoard({ onMoveBackToInProgress(feature) } onFollowUp={() => onFollowUp(feature)} - onCommit={() => onCommit(feature)} onComplete={() => onComplete(feature)} onImplement={() => onImplement(feature)} onViewPlan={() => onViewPlan(feature)} From 92e79453296afb832fce1b04e4716b4cd359de64 Mon Sep 17 00:00:00 2001 From: Test User Date: Sat, 20 Dec 2025 13:12:56 -0500 Subject: [PATCH 3/3] refactor: Update Worktree Integration Tests to reflect button changes - Renamed the Commit button to Mark as Verified in the test cases to align with recent UI changes. - Updated feature descriptions in the tests to match the new functionality. - Adjusted visibility checks for the Mark as Verified button to ensure accurate testing of the updated UI behavior. --- apps/ui/tests/worktree-integration.spec.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/ui/tests/worktree-integration.spec.ts b/apps/ui/tests/worktree-integration.spec.ts index f2a808c2..c3db89c9 100644 --- a/apps/ui/tests/worktree-integration.spec.ts +++ b/apps/ui/tests/worktree-integration.spec.ts @@ -2857,7 +2857,7 @@ test.describe("Worktree Integration Tests", () => { await expect(commitButton).not.toBeVisible({ timeout: 2000 }); }); - test("feature in waiting_approval without prUrl should show Commit button", async ({ + test("feature in waiting_approval without prUrl should show Mark as Verified button", async ({ page, }) => { await setupProjectWithPath(page, testRepo.path); @@ -2867,7 +2867,7 @@ test.describe("Worktree Integration Tests", () => { // Create a feature await clickAddFeature(page); - await fillAddFeatureDialog(page, "Feature without PR for commit test", { + await fillAddFeatureDialog(page, "Feature without PR for mark as verified test", { category: "Testing", }); await confirmAddFeature(page); @@ -2880,7 +2880,7 @@ test.describe("Worktree Integration Tests", () => { const featureFilePath = path.join(featuresDir, dir, "feature.json"); if (fs.existsSync(featureFilePath)) { const data = JSON.parse(fs.readFileSync(featureFilePath, "utf-8")); - return data.description === "Feature without PR for commit test"; + return data.description === "Feature without PR for mark as verified test"; } return false; }); @@ -2908,9 +2908,9 @@ test.describe("Worktree Integration Tests", () => { ); await expect(featureCard).toBeVisible({ timeout: 5000 }); - // Verify the Commit button is visible - const commitButton = page.locator(`[data-testid="commit-${featureData.id}"]`); - await expect(commitButton).toBeVisible({ timeout: 5000 }); + // Verify the Mark as Verified button is visible + const markAsVerifiedButton = page.locator(`[data-testid="mark-as-verified-${featureData.id}"]`); + await expect(markAsVerifiedButton).toBeVisible({ timeout: 5000 }); // Verify the Verify button is NOT visible const verifyButton = page.locator(`[data-testid="verify-${featureData.id}"]`);