diff --git a/.automaker/feature_list.json b/.automaker/feature_list.json index c418e2f2..32395bba 100644 --- a/.automaker/feature_list.json +++ b/.automaker/feature_list.json @@ -30,7 +30,7 @@ "category": "Kanban", "description": "when clicking a value in the typeahead, there is a bug where it does not close automatically, fix this", "steps": [], - "status": "in_progress" + "status": "verified" }, { "id": "feature-1765260671085-7dgotl21h", @@ -86,7 +86,7 @@ "category": "Kanban", "description": "Ability to delete in progress cards which will auto stop their agents on delete", "steps": [], - "status": "in_progress" + "status": "verified" }, { "id": "feature-1765262348401-hivjg6vuq", diff --git a/app/src/components/views/board-view.tsx b/app/src/components/views/board-view.tsx index 9694b6be..2087d78b 100644 --- a/app/src/components/views/board-view.tsx +++ b/app/src/components/views/board-view.tsx @@ -152,6 +152,7 @@ export function BoardView() { ...f, id: f.id || `feature-${index}-${Date.now()}`, status: f.status || "backlog", + startedAt: f.startedAt, // Preserve startedAt timestamp }) ); setFeatures(featuresWithIds); @@ -256,6 +257,7 @@ export function BoardView() { description: f.description, steps: f.steps, status: f.status, + startedAt: f.startedAt, })); await api.writeFile( `${currentProject.path}/.automaker/feature_list.json`, @@ -325,13 +327,14 @@ export function BoardView() { return; } - // Move the feature - moveFeature(featureId, targetStatus); - - // If moved to in_progress, trigger the agent + // Move the feature and set startedAt if moving to in_progress if (targetStatus === "in_progress") { + // Update with startedAt timestamp + updateFeature(featureId, { status: targetStatus, startedAt: new Date().toISOString() }); console.log("[Board] Feature moved to in_progress, starting agent..."); await handleRunFeature(draggedFeature); + } else { + moveFeature(featureId, targetStatus); } }; @@ -358,8 +361,34 @@ export function BoardView() { setEditingFeature(null); }; - const handleDeleteFeature = (featureId: string) => { - if (window.confirm("Are you sure you want to delete this feature?")) { + const handleDeleteFeature = async (featureId: string) => { + const feature = features.find((f) => f.id === featureId); + if (!feature) return; + + // Check if the feature is currently running + const isRunning = runningAutoTasks.includes(featureId); + + const confirmMessage = isRunning + ? "This feature has an agent running. Deleting it will stop the agent. Are you sure you want to delete this feature?" + : "Are you sure you want to delete this feature?"; + + if (window.confirm(confirmMessage)) { + // If the feature is running, stop the agent first + if (isRunning) { + try { + await autoMode.stopFeature(featureId); + toast.success("Agent stopped", { + description: `Stopped and deleted: ${feature.description.slice(0, 50)}${feature.description.length > 50 ? "..." : ""}`, + }); + } catch (error) { + console.error("[Board] Error stopping feature before delete:", error); + toast.error("Failed to stop agent", { + description: "The feature will still be deleted.", + }); + } + } + + // Remove the feature removeFeature(featureId); } }; diff --git a/app/src/components/views/kanban-card.tsx b/app/src/components/views/kanban-card.tsx index 179fbc76..dd261053 100644 --- a/app/src/components/views/kanban-card.tsx +++ b/app/src/components/views/kanban-card.tsx @@ -13,6 +13,7 @@ import { import { Button } from "@/components/ui/button"; import { Feature } from "@/store/app-store"; import { GripVertical, Edit, CheckCircle2, Circle, Loader2, Trash2, Eye, PlayCircle, RotateCcw, StopCircle } from "lucide-react"; +import { CountUpTimer } from "@/components/ui/count-up-timer"; interface KanbanCardProps { feature: Feature; @@ -60,9 +61,18 @@ export function KanbanCard({ feature, onEdit, onDelete, onViewOutput, onVerify, > {isCurrentAutoTask && ( -
+
Running... + {feature.startedAt && ( + + )} +
+ )} + {/* Show timer for in_progress cards that aren't currently running */} + {!isCurrentAutoTask && feature.status === "in_progress" && feature.startedAt && ( +
+
)}
@@ -193,6 +203,18 @@ export function KanbanCard({ feature, onEdit, onDelete, onViewOutput, onVerify, Output )} + )} {!isCurrentAutoTask && feature.status !== "in_progress" && ( diff --git a/app/tests/utils.ts b/app/tests/utils.ts index 65e3920f..6f216be7 100644 --- a/app/tests/utils.ts +++ b/app/tests/utils.ts @@ -806,3 +806,91 @@ export async function isMessageListVisible(page: Page): Promise { const messageList = page.locator('[data-testid="message-list"]'); return await messageList.isVisible(); } + +/** + * Get the count up timer element for a specific feature card + */ +export async function getTimerForFeature( + page: Page, + featureId: string +): Promise { + const card = page.locator(`[data-testid="kanban-card-${featureId}"]`); + return card.locator('[data-testid="count-up-timer"]'); +} + +/** + * Get the timer display text for a specific feature card + */ +export async function getTimerDisplayForFeature( + page: Page, + featureId: string +): Promise { + const card = page.locator(`[data-testid="kanban-card-${featureId}"]`); + const timerDisplay = card.locator('[data-testid="timer-display"]'); + return await timerDisplay.textContent(); +} + +/** + * Check if a timer is visible for a specific feature + */ +export async function isTimerVisibleForFeature( + page: Page, + featureId: string +): Promise { + const card = page.locator(`[data-testid="kanban-card-${featureId}"]`); + const timer = card.locator('[data-testid="count-up-timer"]'); + return await timer.isVisible().catch(() => false); +} + +/** + * Set up a mock project with features that have startedAt timestamps + */ +export async function setupMockProjectWithInProgressFeatures( + page: Page, + options?: { + maxConcurrency?: number; + runningTasks?: string[]; + features?: Array<{ + id: string; + category: string; + description: string; + status: "backlog" | "in_progress" | "verified"; + steps?: string[]; + startedAt?: string; + }>; + } +): Promise { + await page.addInitScript( + (opts: typeof options) => { + const mockProject = { + id: "test-project-1", + name: "Test Project", + path: "/mock/test-project", + lastOpened: new Date().toISOString(), + }; + + const mockFeatures = opts?.features || []; + + const mockState = { + state: { + projects: [mockProject], + currentProject: mockProject, + theme: "dark", + sidebarOpen: true, + apiKeys: { anthropic: "", google: "" }, + chatSessions: [], + chatHistoryOpen: false, + maxConcurrency: opts?.maxConcurrency ?? 3, + isAutoModeRunning: false, + runningAutoTasks: opts?.runningTasks ?? [], + autoModeActivityLog: [], + features: mockFeatures, + }, + version: 0, + }; + + localStorage.setItem("automaker-storage", JSON.stringify(mockState)); + }, + options + ); +}