Merge branch 'main' of github.com:webdevcody/automaker

This commit is contained in:
Cody Seibert
2025-12-09 19:00:21 -05:00
24 changed files with 4783 additions and 155 deletions

View File

@@ -65,6 +65,8 @@ import {
FastForward,
FlaskConical,
CheckCircle2,
MessageSquare,
GitCommit,
} from "lucide-react";
import { toast } from "sonner";
import { Slider } from "@/components/ui/slider";
@@ -81,6 +83,7 @@ type ColumnId = Feature["status"];
const COLUMNS: { id: ColumnId; title: string; color: string }[] = [
{ id: "backlog", title: "Backlog", color: "bg-zinc-500" },
{ id: "in_progress", title: "In Progress", color: "bg-yellow-500" },
{ id: "waiting_approval", title: "Waiting Approval", color: "bg-orange-500" },
{ id: "verified", title: "Verified", color: "bg-green-500" },
];
@@ -119,6 +122,12 @@ export function BoardView() {
const [showDeleteAllVerifiedDialog, setShowDeleteAllVerifiedDialog] =
useState(false);
const [persistedCategories, setPersistedCategories] = useState<string[]>([]);
const [showFollowUpDialog, setShowFollowUpDialog] = useState(false);
const [followUpFeature, setFollowUpFeature] = useState<Feature | null>(null);
const [followUpPrompt, setFollowUpPrompt] = useState("");
const [followUpImagePaths, setFollowUpImagePaths] = useState<
DescriptionImagePath[]
>([]);
// Make current project available globally for modal
useEffect(() => {
@@ -428,6 +437,7 @@ export function BoardView() {
startedAt: f.startedAt,
imagePaths: f.imagePaths,
skipTests: f.skipTests,
summary: f.summary,
}));
await api.writeFile(
`${currentProject.path}/.automaker/feature_list.json`,
@@ -776,6 +786,142 @@ export function BoardView() {
});
};
// Open follow-up dialog for waiting_approval features
const handleOpenFollowUp = (feature: Feature) => {
console.log("[Board] Opening follow-up dialog for feature:", {
id: feature.id,
description: feature.description,
});
setFollowUpFeature(feature);
setFollowUpPrompt("");
setFollowUpImagePaths([]);
setShowFollowUpDialog(true);
};
// Handle sending follow-up prompt
const handleSendFollowUp = async () => {
if (!currentProject || !followUpFeature || !followUpPrompt.trim()) return;
// Save values before clearing state
const featureId = followUpFeature.id;
const featureDescription = followUpFeature.description;
const prompt = followUpPrompt;
const imagePaths = followUpImagePaths.map((img) => img.path);
console.log("[Board] Sending follow-up prompt for feature:", {
id: featureId,
prompt: prompt,
imagePaths: imagePaths,
});
const api = getElectronAPI();
if (!api?.autoMode?.followUpFeature) {
console.error("Follow-up feature API not available");
toast.error("Follow-up not available", {
description: "This feature is not available in the current version.",
});
return;
}
// Move feature back to in_progress before sending follow-up
updateFeature(featureId, {
status: "in_progress",
startedAt: new Date().toISOString(),
});
// Reset follow-up state immediately (close dialog, clear form)
setShowFollowUpDialog(false);
setFollowUpFeature(null);
setFollowUpPrompt("");
setFollowUpImagePaths([]);
// Show success toast immediately
toast.success("Follow-up started", {
description: `Continuing work on: ${featureDescription.slice(0, 50)}${
featureDescription.length > 50 ? "..." : ""
}`,
});
// Call the API in the background (don't await - let it run async)
api.autoMode
.followUpFeature(currentProject.path, featureId, prompt, imagePaths)
.catch((error) => {
console.error("[Board] Error sending follow-up:", error);
toast.error("Failed to send follow-up", {
description:
error instanceof Error ? error.message : "An error occurred",
});
// Reload features to revert status if there was an error
loadFeatures();
});
};
// Handle commit-only for waiting_approval features (marks as verified and commits)
const handleCommitFeature = async (feature: Feature) => {
if (!currentProject) return;
console.log("[Board] Committing feature:", {
id: feature.id,
description: feature.description,
});
try {
const api = getElectronAPI();
if (!api?.autoMode?.commitFeature) {
console.error("Commit feature API not available");
toast.error("Commit not available", {
description: "This feature is not available in the current version.",
});
return;
}
// Call the API to commit this feature
const result = await api.autoMode.commitFeature(
currentProject.path,
feature.id
);
if (result.success) {
console.log("[Board] Feature committed successfully");
// Move to verified status
moveFeature(feature.id, "verified");
toast.success("Feature committed", {
description: `Committed and verified: ${feature.description.slice(
0,
50
)}${feature.description.length > 50 ? "..." : ""}`,
});
} else {
console.error("[Board] Failed to commit feature:", result.error);
toast.error("Failed to commit feature", {
description: result.error || "An error occurred",
});
await loadFeatures();
}
} catch (error) {
console.error("[Board] Error committing feature:", error);
toast.error("Failed to commit feature", {
description:
error instanceof Error ? error.message : "An error occurred",
});
await loadFeatures();
}
};
// Move feature to waiting_approval (for skipTests features when agent completes)
const handleMoveToWaitingApproval = (feature: Feature) => {
console.log("[Board] Moving feature to waiting_approval:", {
id: feature.id,
description: feature.description,
});
updateFeature(feature.id, { status: "waiting_approval" });
toast.info("Feature ready for review", {
description: `Ready for approval: ${feature.description.slice(0, 50)}${
feature.description.length > 50 ? "..." : ""
}`,
});
};
const checkContextExists = async (featureId: string): Promise<boolean> => {
if (!currentProject) return false;
@@ -844,12 +990,30 @@ export function BoardView() {
const handleForceStopFeature = async (feature: Feature) => {
try {
await autoMode.stopFeature(feature.id);
// Move the feature back to backlog status after stopping
moveFeature(feature.id, "backlog");
// Determine where to move the feature after stopping:
// - If it's a skipTests feature that was in waiting_approval (i.e., during commit operation),
// move it back to waiting_approval so user can try commit again or do follow-up
// - Otherwise, move to backlog
const targetStatus =
feature.skipTests && feature.status === "waiting_approval"
? "waiting_approval"
: "backlog";
if (targetStatus !== feature.status) {
moveFeature(feature.id, targetStatus);
}
toast.success("Agent stopped", {
description: `Stopped working on: ${feature.description.slice(0, 50)}${
feature.description.length > 50 ? "..." : ""
}`,
description:
targetStatus === "waiting_approval"
? `Stopped commit - returned to waiting approval: ${feature.description.slice(
0,
50
)}${feature.description.length > 50 ? "..." : ""}`
: `Stopped working on: ${feature.description.slice(0, 50)}${
feature.description.length > 50 ? "..." : ""
}`,
});
} catch (error) {
console.error("[Board] Error stopping feature:", error);
@@ -1114,6 +1278,8 @@ export function BoardView() {
onMoveBackToInProgress={() =>
handleMoveBackToInProgress(feature)
}
onFollowUp={() => handleOpenFollowUp(feature)}
onCommit={() => handleCommitFeature(feature)}
hasContext={featuresWithContext.has(feature.id)}
isCurrentAutoTask={runningAutoTasks.includes(
feature.id
@@ -1457,6 +1623,86 @@ export function BoardView() {
</DialogFooter>
</DialogContent>
</Dialog>
{/* Follow-Up Prompt Dialog */}
<Dialog
open={showFollowUpDialog}
onOpenChange={(open) => {
if (!open) {
setShowFollowUpDialog(false);
setFollowUpFeature(null);
setFollowUpPrompt("");
setFollowUpImagePaths([]);
}
}}
>
<DialogContent
data-testid="follow-up-dialog"
onKeyDown={(e) => {
if (
(e.metaKey || e.ctrlKey) &&
e.key === "Enter" &&
followUpPrompt.trim()
) {
e.preventDefault();
handleSendFollowUp();
}
}}
>
<DialogHeader>
<DialogTitle>Follow-Up Prompt</DialogTitle>
<DialogDescription>
Send additional instructions to continue working on this feature.
{followUpFeature && (
<span className="block mt-2 text-primary">
Feature: {followUpFeature.description.slice(0, 100)}
{followUpFeature.description.length > 100 ? "..." : ""}
</span>
)}
</DialogDescription>
</DialogHeader>
<div className="space-y-4 py-4">
<div className="space-y-2">
<Label htmlFor="follow-up-prompt">Instructions</Label>
<DescriptionImageDropZone
value={followUpPrompt}
onChange={setFollowUpPrompt}
images={followUpImagePaths}
onImagesChange={setFollowUpImagePaths}
placeholder="Describe what needs to be fixed or changed..."
/>
</div>
<p className="text-xs text-muted-foreground">
The agent will continue from where it left off, using the existing
context. You can attach screenshots to help explain the issue.
</p>
</div>
<DialogFooter>
<Button
variant="ghost"
onClick={() => {
setShowFollowUpDialog(false);
setFollowUpFeature(null);
setFollowUpPrompt("");
setFollowUpImagePaths([]);
}}
>
Cancel
</Button>
<Button
onClick={handleSendFollowUp}
disabled={!followUpPrompt.trim()}
data-testid="confirm-follow-up"
>
<MessageSquare className="w-4 h-4 mr-2" />
Send Follow-Up
<span className="ml-2 px-1.5 py-0.5 text-[10px] font-mono rounded bg-white/10 border border-white/20">
</span>
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
);
}