mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-02 20:43:36 +00:00
feat(kanban): Add ability to delete in-progress cards with auto agent stop
- Add delete button to in_progress cards that are not currently running - Update handleDeleteFeature to stop running agents before deleting - Show confirmation message mentioning agent will be stopped when card is running - Add test utilities for delete in-progress feature testing Generated with Claude Code Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -30,7 +30,7 @@
|
|||||||
"category": "Kanban",
|
"category": "Kanban",
|
||||||
"description": "when clicking a value in the typeahead, there is a bug where it does not close automatically, fix this",
|
"description": "when clicking a value in the typeahead, there is a bug where it does not close automatically, fix this",
|
||||||
"steps": [],
|
"steps": [],
|
||||||
"status": "in_progress"
|
"status": "verified"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "feature-1765260671085-7dgotl21h",
|
"id": "feature-1765260671085-7dgotl21h",
|
||||||
@@ -86,7 +86,7 @@
|
|||||||
"category": "Kanban",
|
"category": "Kanban",
|
||||||
"description": "Ability to delete in progress cards which will auto stop their agents on delete",
|
"description": "Ability to delete in progress cards which will auto stop their agents on delete",
|
||||||
"steps": [],
|
"steps": [],
|
||||||
"status": "in_progress"
|
"status": "verified"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "feature-1765262348401-hivjg6vuq",
|
"id": "feature-1765262348401-hivjg6vuq",
|
||||||
|
|||||||
@@ -152,6 +152,7 @@ export function BoardView() {
|
|||||||
...f,
|
...f,
|
||||||
id: f.id || `feature-${index}-${Date.now()}`,
|
id: f.id || `feature-${index}-${Date.now()}`,
|
||||||
status: f.status || "backlog",
|
status: f.status || "backlog",
|
||||||
|
startedAt: f.startedAt, // Preserve startedAt timestamp
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
setFeatures(featuresWithIds);
|
setFeatures(featuresWithIds);
|
||||||
@@ -256,6 +257,7 @@ export function BoardView() {
|
|||||||
description: f.description,
|
description: f.description,
|
||||||
steps: f.steps,
|
steps: f.steps,
|
||||||
status: f.status,
|
status: f.status,
|
||||||
|
startedAt: f.startedAt,
|
||||||
}));
|
}));
|
||||||
await api.writeFile(
|
await api.writeFile(
|
||||||
`${currentProject.path}/.automaker/feature_list.json`,
|
`${currentProject.path}/.automaker/feature_list.json`,
|
||||||
@@ -325,13 +327,14 @@ export function BoardView() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move the feature
|
// Move the feature and set startedAt if moving to in_progress
|
||||||
moveFeature(featureId, targetStatus);
|
|
||||||
|
|
||||||
// If moved to in_progress, trigger the agent
|
|
||||||
if (targetStatus === "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...");
|
console.log("[Board] Feature moved to in_progress, starting agent...");
|
||||||
await handleRunFeature(draggedFeature);
|
await handleRunFeature(draggedFeature);
|
||||||
|
} else {
|
||||||
|
moveFeature(featureId, targetStatus);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -358,8 +361,34 @@ export function BoardView() {
|
|||||||
setEditingFeature(null);
|
setEditingFeature(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteFeature = (featureId: string) => {
|
const handleDeleteFeature = async (featureId: string) => {
|
||||||
if (window.confirm("Are you sure you want to delete this feature?")) {
|
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);
|
removeFeature(featureId);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Feature } from "@/store/app-store";
|
import { Feature } from "@/store/app-store";
|
||||||
import { GripVertical, Edit, CheckCircle2, Circle, Loader2, Trash2, Eye, PlayCircle, RotateCcw, StopCircle } from "lucide-react";
|
import { GripVertical, Edit, CheckCircle2, Circle, Loader2, Trash2, Eye, PlayCircle, RotateCcw, StopCircle } from "lucide-react";
|
||||||
|
import { CountUpTimer } from "@/components/ui/count-up-timer";
|
||||||
|
|
||||||
interface KanbanCardProps {
|
interface KanbanCardProps {
|
||||||
feature: Feature;
|
feature: Feature;
|
||||||
@@ -60,9 +61,18 @@ export function KanbanCard({ feature, onEdit, onDelete, onViewOutput, onVerify,
|
|||||||
>
|
>
|
||||||
<CardHeader className="p-3 pb-2">
|
<CardHeader className="p-3 pb-2">
|
||||||
{isCurrentAutoTask && (
|
{isCurrentAutoTask && (
|
||||||
<div className="absolute top-2 right-2 flex items-center gap-1 bg-purple-500/20 border border-purple-500 rounded px-2 py-0.5">
|
<div className="absolute top-2 right-2 flex items-center gap-2 bg-purple-500/20 border border-purple-500 rounded px-2 py-0.5">
|
||||||
<Loader2 className="w-4 h-4 text-purple-400 animate-spin" />
|
<Loader2 className="w-4 h-4 text-purple-400 animate-spin" />
|
||||||
<span className="text-xs text-purple-400 font-medium">Running...</span>
|
<span className="text-xs text-purple-400 font-medium">Running...</span>
|
||||||
|
{feature.startedAt && (
|
||||||
|
<CountUpTimer startedAt={feature.startedAt} className="text-purple-400" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{/* Show timer for in_progress cards that aren't currently running */}
|
||||||
|
{!isCurrentAutoTask && feature.status === "in_progress" && feature.startedAt && (
|
||||||
|
<div className="absolute top-2 right-2">
|
||||||
|
<CountUpTimer startedAt={feature.startedAt} className="text-yellow-500" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex items-start gap-2">
|
<div className="flex items-start gap-2">
|
||||||
@@ -193,6 +203,18 @@ export function KanbanCard({ feature, onEdit, onDelete, onViewOutput, onVerify,
|
|||||||
Output
|
Output
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="h-7 text-xs text-destructive hover:text-destructive hover:bg-destructive/10"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onDelete();
|
||||||
|
}}
|
||||||
|
data-testid={`delete-inprogress-feature-${feature.id}`}
|
||||||
|
>
|
||||||
|
<Trash2 className="w-3 h-3" />
|
||||||
|
</Button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{!isCurrentAutoTask && feature.status !== "in_progress" && (
|
{!isCurrentAutoTask && feature.status !== "in_progress" && (
|
||||||
|
|||||||
@@ -806,3 +806,91 @@ export async function isMessageListVisible(page: Page): Promise<boolean> {
|
|||||||
const messageList = page.locator('[data-testid="message-list"]');
|
const messageList = page.locator('[data-testid="message-list"]');
|
||||||
return await messageList.isVisible();
|
return await messageList.isVisible();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the count up timer element for a specific feature card
|
||||||
|
*/
|
||||||
|
export async function getTimerForFeature(
|
||||||
|
page: Page,
|
||||||
|
featureId: string
|
||||||
|
): Promise<Locator> {
|
||||||
|
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<string | null> {
|
||||||
|
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<boolean> {
|
||||||
|
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<void> {
|
||||||
|
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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user