From 72e803b56d958763a25be94b10b377c3c7a89233 Mon Sep 17 00:00:00 2001
From: Cody Seibert
Date: Sun, 14 Dec 2025 20:06:52 -0500
Subject: [PATCH 1/5] feat: implement completed features management in
BoardView and KanbanCard
- Added functionality to complete and unarchive features, allowing users to manage feature statuses effectively.
- Introduced a modal to display completed features, enhancing user experience by providing a dedicated view for archived items.
- Updated KanbanCard to include buttons for completing features and managing their states, improving interactivity and workflow.
- Modified the Feature interface to include a new "completed" status, ensuring comprehensive state management across the application.
---
apps/app/src/components/views/board-view.tsx | 234 ++++++++++++++++++
apps/app/src/components/views/kanban-card.tsx | 192 +++++++++++++-
apps/app/src/store/app-store.ts | 7 +-
3 files changed, 421 insertions(+), 12 deletions(-)
diff --git a/apps/app/src/components/views/board-view.tsx b/apps/app/src/components/views/board-view.tsx
index 42c326e4..6891c1e4 100644
--- a/apps/app/src/components/views/board-view.tsx
+++ b/apps/app/src/components/views/board-view.tsx
@@ -90,6 +90,8 @@ import {
Maximize2,
Shuffle,
ImageIcon,
+ Archive,
+ ArchiveRestore,
} from "lucide-react";
import { toast } from "sonner";
import { Slider } from "@/components/ui/slider";
@@ -208,6 +210,9 @@ export function BoardView() {
useState(false);
const [showBoardBackgroundModal, setShowBoardBackgroundModal] =
useState(false);
+ const [showCompletedModal, setShowCompletedModal] = useState(false);
+ const [deleteCompletedFeature, setDeleteCompletedFeature] =
+ useState(null);
const [persistedCategories, setPersistedCategories] = useState([]);
const [showFollowUpDialog, setShowFollowUpDialog] = useState(false);
const [followUpFeature, setFollowUpFeature] = useState(null);
@@ -1497,6 +1502,47 @@ export function BoardView() {
}
};
+ // Complete a verified feature (move to completed/archived)
+ const handleCompleteFeature = (feature: Feature) => {
+ console.log("[Board] Completing feature:", {
+ id: feature.id,
+ description: feature.description,
+ });
+
+ const updates = {
+ status: "completed" as const,
+ };
+ updateFeature(feature.id, updates);
+ persistFeatureUpdate(feature.id, updates);
+
+ toast.success("Feature completed", {
+ description: `Archived: ${feature.description.slice(0, 50)}${
+ feature.description.length > 50 ? "..." : ""
+ }`,
+ });
+ };
+
+ // Unarchive a completed feature (move back to verified)
+ const handleUnarchiveFeature = (feature: Feature) => {
+ console.log("[Board] Unarchiving feature:", {
+ id: feature.id,
+ description: feature.description,
+ });
+
+ const updates = {
+ status: "verified" as const,
+ };
+ updateFeature(feature.id, updates);
+ persistFeatureUpdate(feature.id, updates);
+
+ toast.success("Feature restored", {
+ description: `Moved back to verified: ${feature.description.slice(
+ 0,
+ 50
+ )}${feature.description.length > 50 ? "..." : ""}`,
+ });
+ };
+
const checkContextExists = async (featureId: string): Promise => {
if (!currentProject) return false;
@@ -1518,6 +1564,11 @@ export function BoardView() {
}
};
+ // Memoize completed features for the archive modal
+ const completedFeatures = useMemo(() => {
+ return features.filter((f) => f.status === "completed");
+ }, [features]);
+
// Memoize column features to prevent unnecessary re-renders
const columnFeaturesMap = useMemo(() => {
const map: Record = {
@@ -1525,6 +1576,7 @@ export function BoardView() {
in_progress: [],
waiting_approval: [],
verified: [],
+ completed: [], // Completed features are shown in the archive modal, not as a column
};
// Filter features by search query (case-insensitive)
@@ -1903,6 +1955,31 @@ export function BoardView() {
+ {/* Completed/Archived Features Button */}
+
+
+
+
+
+ Completed Features ({completedFeatures.length})
+
+
+
{/* Kanban Card Detail Level Toggle */}
handleCommitFeature(feature)}
onRevert={() => handleRevertFeature(feature)}
onMerge={() => handleMergeFeature(feature)}
+ onComplete={() =>
+ handleCompleteFeature(feature)
+ }
+ onImplement={async () => {
+ // Check concurrency limit
+ if (!autoMode.canStartNewTask) {
+ toast.error("Concurrency limit reached", {
+ description: `You can only have ${
+ autoMode.maxConcurrency
+ } task${
+ autoMode.maxConcurrency > 1 ? "s" : ""
+ } running at a time. Wait for a task to complete or increase the limit.`,
+ });
+ return;
+ }
+ // Update with startedAt timestamp
+ const updates = {
+ status: "in_progress" as const,
+ startedAt: new Date().toISOString(),
+ };
+ updateFeature(feature.id, updates);
+ persistFeatureUpdate(feature.id, updates);
+ console.log(
+ "[Board] Feature moved to in_progress via Implement button, starting agent..."
+ );
+ await handleRunFeature(feature);
+ }}
hasContext={featuresWithContext.has(feature.id)}
isCurrentAutoTask={runningAutoTasks.includes(
feature.id
@@ -2157,6 +2261,136 @@ export function BoardView() {
onOpenChange={setShowBoardBackgroundModal}
/>
+ {/* Completed Features Modal */}
+
+
+ {/* Delete Completed Feature Confirmation Dialog */}
+
+
{/* Add Feature Dialog */}
)}
- {!isCurrentAutoTask && (
+ {!isCurrentAutoTask && feature.status === "backlog" && (
+
+
+
+ )}
+ {!isCurrentAutoTask && feature.status === "waiting_approval" && (
+
+
+ {onViewOutput && (
+
+ )}
+
+
+ )}
+ {!isCurrentAutoTask && feature.status === "verified" && (
+
+
+ {onViewOutput && (
+
+ )}
+
+
+ )}
+ {!isCurrentAutoTask && feature.status === "in_progress" && (
@@ -552,7 +671,7 @@ export const KanbanCard = memo(function KanbanCard({
Edit
- {onViewOutput && feature.status !== "backlog" && (
+ {onViewOutput && (
{
e.stopPropagation();
@@ -926,12 +1045,12 @@ export const KanbanCard = memo(function KanbanCard({
)}
{!isCurrentAutoTask && feature.status === "verified" && (
<>
- {/* Logs button if context exists */}
- {hasContext && onViewOutput && (
+ {/* Logs button - styled like Refine */}
+ {onViewOutput && (
+ )}
+ {/* Complete button */}
+ {onComplete && (
+
)}
>
@@ -972,7 +1108,7 @@ export const KanbanCard = memo(function KanbanCard({
)}
- {/* Follow-up prompt button */}
+ {/* Refine prompt button */}
{onFollowUp && (
)}
{/* Merge button - only show when worktree exists */}
@@ -1026,6 +1162,40 @@ export const KanbanCard = memo(function KanbanCard({
)}
>
)}
+ {!isCurrentAutoTask && feature.status === "backlog" && (
+ <>
+
+ {onImplement && (
+
+ )}
+ >
+ )}
diff --git a/apps/app/src/store/app-store.ts b/apps/app/src/store/app-store.ts
index a4625932..3c2aeaa4 100644
--- a/apps/app/src/store/app-store.ts
+++ b/apps/app/src/store/app-store.ts
@@ -277,7 +277,12 @@ export interface Feature {
category: string;
description: string;
steps: string[];
- status: "backlog" | "in_progress" | "waiting_approval" | "verified";
+ status:
+ | "backlog"
+ | "in_progress"
+ | "waiting_approval"
+ | "verified"
+ | "completed";
images?: FeatureImage[];
imagePaths?: FeatureImagePath[]; // Paths to temp files for agent context
startedAt?: string; // ISO timestamp for when the card moved to in_progress
From 919e08689a79bf612f7613a21199ea0bd2eadd5f Mon Sep 17 00:00:00 2001
From: Cody Seibert
Date: Sun, 14 Dec 2025 20:21:42 -0500
Subject: [PATCH 2/5] refactor: streamline feature implementation handling in
BoardView and KanbanCard
- Introduced a helper function, handleStartImplementation, to manage concurrency checks and feature status updates when moving features from backlog to in_progress.
- Simplified the onImplement callback in KanbanCard to utilize the new helper function, enhancing code readability and maintainability.
- Removed redundant concurrency checks from multiple locations, centralizing the logic for better consistency and reducing code duplication.
---
apps/app/src/components/views/board-view.tsx | 79 +++++-------
apps/app/src/components/views/kanban-card.tsx | 116 ++++++------------
2 files changed, 68 insertions(+), 127 deletions(-)
diff --git a/apps/app/src/components/views/board-view.tsx b/apps/app/src/components/views/board-view.tsx
index 6891c1e4..f7587f59 100644
--- a/apps/app/src/components/views/board-view.tsx
+++ b/apps/app/src/components/views/board-view.tsx
@@ -849,34 +849,12 @@ export function BoardView() {
// Same column, nothing to do
if (targetStatus === draggedFeature.status) return;
- // Check concurrency limit before moving to in_progress (only for backlog -> in_progress and if running agent)
- if (
- targetStatus === "in_progress" &&
- draggedFeature.status === "backlog" &&
- !autoMode.canStartNewTask
- ) {
- console.log("[Board] Cannot start new task - at max concurrency limit");
- toast.error("Concurrency limit reached", {
- description: `You can only have ${autoMode.maxConcurrency} task${
- autoMode.maxConcurrency > 1 ? "s" : ""
- } running at a time. Wait for a task to complete or increase the limit.`,
- });
- return;
- }
-
// Handle different drag scenarios
if (draggedFeature.status === "backlog") {
// From backlog
if (targetStatus === "in_progress") {
- // Update with startedAt timestamp
- const updates = {
- status: targetStatus,
- startedAt: new Date().toISOString(),
- };
- updateFeature(featureId, updates);
- persistFeatureUpdate(featureId, updates);
- console.log("[Board] Feature moved to in_progress, starting agent...");
- await handleRunFeature(draggedFeature);
+ // Use helper function to handle concurrency check and start implementation
+ await handleStartImplementation(draggedFeature);
} else {
moveFeature(featureId, targetStatus);
persistFeatureUpdate(featureId, { status: targetStatus });
@@ -1149,6 +1127,28 @@ export function BoardView() {
}
};
+ // Helper function to start implementing a feature (from backlog to in_progress)
+ const handleStartImplementation = async (feature: Feature) => {
+ if (!autoMode.canStartNewTask) {
+ toast.error("Concurrency limit reached", {
+ description: `You can only have ${autoMode.maxConcurrency} task${
+ autoMode.maxConcurrency > 1 ? "s" : ""
+ } running at a time. Wait for a task to complete or increase the limit.`,
+ });
+ return false;
+ }
+
+ const updates = {
+ status: "in_progress" as const,
+ startedAt: new Date().toISOString(),
+ };
+ updateFeature(feature.id, updates);
+ persistFeatureUpdate(feature.id, updates);
+ console.log("[Board] Feature moved to in_progress, starting agent...");
+ await handleRunFeature(feature);
+ return true;
+ };
+
const handleVerifyFeature = async (feature: Feature) => {
if (!currentProject) return;
@@ -2187,30 +2187,9 @@ export function BoardView() {
onComplete={() =>
handleCompleteFeature(feature)
}
- onImplement={async () => {
- // Check concurrency limit
- if (!autoMode.canStartNewTask) {
- toast.error("Concurrency limit reached", {
- description: `You can only have ${
- autoMode.maxConcurrency
- } task${
- autoMode.maxConcurrency > 1 ? "s" : ""
- } running at a time. Wait for a task to complete or increase the limit.`,
- });
- return;
- }
- // Update with startedAt timestamp
- const updates = {
- status: "in_progress" as const,
- startedAt: new Date().toISOString(),
- };
- updateFeature(feature.id, updates);
- persistFeatureUpdate(feature.id, updates);
- console.log(
- "[Board] Feature moved to in_progress via Implement button, starting agent..."
- );
- await handleRunFeature(feature);
- }}
+ onImplement={() =>
+ handleStartImplementation(feature)
+ }
hasContext={featuresWithContext.has(feature.id)}
isCurrentAutoTask={runningAutoTasks.includes(
feature.id
@@ -2376,9 +2355,9 @@ export function BoardView() {
)}
- {!isCurrentAutoTask && feature.status === "waiting_approval" && (
-
-
- {onViewOutput && (
+ {!isCurrentAutoTask &&
+ (feature.status === "waiting_approval" ||
+ feature.status === "verified") && (
+
- )}
-
-
- )}
- {!isCurrentAutoTask && feature.status === "verified" && (
-
-
- {onViewOutput && (
+ {onViewOutput && (
+
+ )}
- )}
-
-
- )}
+
+ )}
{!isCurrentAutoTask && feature.status === "in_progress" && (
From f25d62fe25f893a2ab628bfd1e84b255f67dadbe Mon Sep 17 00:00:00 2001
From: Cody Seibert
Date: Mon, 15 Dec 2025 01:07:47 -0500
Subject: [PATCH 3/5] feat: implement project setup dialog and refactor sidebar
integration
- Added a new ProjectSetupDialog component to facilitate project specification generation, enhancing user experience by guiding users through project setup.
- Refactored the Sidebar component to integrate the new ProjectSetupDialog, replacing the previous inline dialog implementation for improved code organization and maintainability.
- Updated the sidebar to handle project overview and feature generation options, streamlining the project setup process.
- Removed the old dialog implementation from the Sidebar, reducing code duplication and improving clarity.
---
apps/app/src/app/api/chat/route.ts | 172 -----------
.../layout/project-setup-dialog.tsx | 114 +++++++
apps/app/src/components/layout/sidebar.tsx | 90 +-----
apps/app/src/components/views/board-view.tsx | 26 +-
apps/app/src/config/model-config.ts | 93 ++++++
apps/server/src/lib/model-resolver.ts | 9 +-
apps/server/src/lib/sdk-options.ts | 291 ++++++++++++++++++
.../app-spec/generate-features-from-spec.ts | 33 +-
.../src/routes/app-spec/generate-spec.ts | 147 ++++++---
.../app-spec/parse-and-create-features.ts | 19 +-
.../suggestions/generate-suggestions.ts | 11 +-
apps/server/src/services/agent-service.ts | 30 +-
apps/server/src/services/auto-mode-service.ts | 21 +-
13 files changed, 724 insertions(+), 332 deletions(-)
delete mode 100644 apps/app/src/app/api/chat/route.ts
create mode 100644 apps/app/src/components/layout/project-setup-dialog.tsx
create mode 100644 apps/app/src/config/model-config.ts
create mode 100644 apps/server/src/lib/sdk-options.ts
diff --git a/apps/app/src/app/api/chat/route.ts b/apps/app/src/app/api/chat/route.ts
deleted file mode 100644
index 11f3db6a..00000000
--- a/apps/app/src/app/api/chat/route.ts
+++ /dev/null
@@ -1,172 +0,0 @@
-import {
- query,
- Options,
- SDKAssistantMessage,
-} from "@anthropic-ai/claude-agent-sdk";
-import { NextRequest, NextResponse } from "next/server";
-import path from "path";
-
-const systemPrompt = `You are an AI assistant helping users build software. You are part of the Automaker application,
-which is designed to help developers plan, design, and implement software projects autonomously.
-
-Your role is to:
-- Help users define their project requirements and specifications
-- Ask clarifying questions to better understand their needs
-- Suggest technical approaches and architectures
-- Guide them through the development process
-- Be conversational and helpful
-- Write, edit, and modify code files as requested
-- Execute commands and tests
-- Search and analyze the codebase
-
-When discussing projects, help users think through:
-- Core functionality and features
-- Technical stack choices
-- Data models and architecture
-- User experience considerations
-- Testing strategies
-
-You have full access to the codebase and can:
-- Read files to understand existing code
-- Write new files
-- Edit existing files
-- Run bash commands
-- Search for code patterns
-- Execute tests and builds`;
-
-export async function POST(request: NextRequest) {
- try {
- const { messages, workingDirectory } = await request.json();
-
- console.log(
- "[API] CLAUDE_CODE_OAUTH_TOKEN present:",
- !!process.env.CLAUDE_CODE_OAUTH_TOKEN
- );
-
- if (!process.env.CLAUDE_CODE_OAUTH_TOKEN) {
- return NextResponse.json(
- { error: "CLAUDE_CODE_OAUTH_TOKEN not configured" },
- { status: 500 }
- );
- }
-
- // Get the last user message
- const lastMessage = messages[messages.length - 1];
-
- // Determine working directory - default to parent of app directory
- const cwd = workingDirectory || path.resolve(process.cwd(), "..");
-
- console.log("[API] Working directory:", cwd);
-
- // Create query with options that enable code modification
- const options: Options = {
- // model: "claude-sonnet-4-20250514",
- model: "claude-opus-4-5-20251101",
- systemPrompt,
- maxTurns: 20,
- cwd,
- // Enable all core tools for code modification
- allowedTools: [
- "Read",
- "Write",
- "Edit",
- "Glob",
- "Grep",
- "Bash",
- "WebSearch",
- "WebFetch",
- ],
- // Auto-accept file edits within the working directory
- permissionMode: "acceptEdits",
- // Enable sandbox for safer bash execution
- sandbox: {
- enabled: true,
- autoAllowBashIfSandboxed: true,
- },
- };
-
- // Convert message history to SDK format to preserve conversation context
- // Include both user and assistant messages for full context
- const sessionId = `api-session-${Date.now()}`;
- const conversationMessages = messages.map(
- (msg: { role: string; content: string }) => {
- if (msg.role === "user") {
- return {
- type: "user" as const,
- message: {
- role: "user" as const,
- content: msg.content,
- },
- parent_tool_use_id: null,
- session_id: sessionId,
- };
- } else {
- // Assistant message
- return {
- type: "assistant" as const,
- message: {
- role: "assistant" as const,
- content: [
- {
- type: "text" as const,
- text: msg.content,
- },
- ],
- },
- session_id: sessionId,
- };
- }
- }
- );
-
- // Execute query with full conversation context
- const queryResult = query({
- prompt:
- conversationMessages.length > 0
- ? conversationMessages
- : lastMessage.content,
- options,
- });
-
- let responseText = "";
- const toolUses: Array<{ name: string; input: unknown }> = [];
-
- // Collect the response from the async generator
- for await (const msg of queryResult) {
- if (msg.type === "assistant") {
- const assistantMsg = msg as SDKAssistantMessage;
- if (assistantMsg.message.content) {
- for (const block of assistantMsg.message.content) {
- if (block.type === "text") {
- responseText += block.text;
- } else if (block.type === "tool_use") {
- // Track tool usage for transparency
- toolUses.push({
- name: block.name,
- input: block.input,
- });
- }
- }
- }
- } else if (msg.type === "result") {
- if (msg.subtype === "success") {
- if (msg.result) {
- responseText = msg.result;
- }
- }
- }
- }
-
- return NextResponse.json({
- content: responseText || "Sorry, I couldn't generate a response.",
- toolUses: toolUses.length > 0 ? toolUses : undefined,
- });
- } catch (error: unknown) {
- console.error("Claude API error:", error);
- const errorMessage =
- error instanceof Error
- ? error.message
- : "Failed to get response from Claude";
- return NextResponse.json({ error: errorMessage }, { status: 500 });
- }
-}
diff --git a/apps/app/src/components/layout/project-setup-dialog.tsx b/apps/app/src/components/layout/project-setup-dialog.tsx
new file mode 100644
index 00000000..4b4ce15e
--- /dev/null
+++ b/apps/app/src/components/layout/project-setup-dialog.tsx
@@ -0,0 +1,114 @@
+"use client";
+
+import { Sparkles } from "lucide-react";
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+} from "@/components/ui/dialog";
+import { Button } from "@/components/ui/button";
+import { Checkbox } from "@/components/ui/checkbox";
+
+interface ProjectSetupDialogProps {
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+ projectOverview: string;
+ onProjectOverviewChange: (value: string) => void;
+ generateFeatures: boolean;
+ onGenerateFeaturesChange: (value: boolean) => void;
+ onCreateSpec: () => void;
+ onSkip: () => void;
+ isCreatingSpec: boolean;
+}
+
+export function ProjectSetupDialog({
+ open,
+ onOpenChange,
+ projectOverview,
+ onProjectOverviewChange,
+ generateFeatures,
+ onGenerateFeaturesChange,
+ onCreateSpec,
+ onSkip,
+ isCreatingSpec,
+}: ProjectSetupDialogProps) {
+ return (
+
+ );
+}
+
diff --git a/apps/app/src/components/layout/sidebar.tsx b/apps/app/src/components/layout/sidebar.tsx
index cf163102..ae81b122 100644
--- a/apps/app/src/components/layout/sidebar.tsx
+++ b/apps/app/src/components/layout/sidebar.tsx
@@ -79,10 +79,10 @@ import {
} from "@/lib/project-init";
import { toast } from "sonner";
import { themeOptions } from "@/config/theme-options";
-import { Checkbox } from "@/components/ui/checkbox";
import type { SpecRegenerationEvent } from "@/types/electron";
import { DeleteProjectDialog } from "@/components/views/settings-view/components/delete-project-dialog";
import { NewProjectModal } from "@/components/new-project-modal";
+import { ProjectSetupDialog } from "@/components/layout/project-setup-dialog";
import {
DndContext,
DragEndEvent,
@@ -471,6 +471,12 @@ export function Sidebar() {
toast.error("Failed to create specification", {
description: result.error,
});
+ } else {
+ // Show processing toast to inform user
+ toast.info("Generating app specification...", {
+ description:
+ "This may take a minute. You'll be notified when complete.",
+ });
}
// If successful, we'll wait for the events to update the state
} catch (error) {
@@ -1904,79 +1910,17 @@ export function Sidebar() {
{/* New Project Setup Dialog */}
-
+ onOpenChange={setShowSetupDialog}
+ projectOverview={projectOverview}
+ onProjectOverviewChange={setProjectOverview}
+ generateFeatures={generateFeatures}
+ onGenerateFeaturesChange={setGenerateFeatures}
+ onCreateSpec={handleCreateInitialSpec}
+ onSkip={handleSkipSetup}
+ isCreatingSpec={isCreatingSpec}
+ />
{/* New Project Onboarding Dialog */}
-
+
{activeFeature && (
From f04cac8e2f108ffe3a637675a53f1ddff4d831cd Mon Sep 17 00:00:00 2001
From: Cody Seibert
Date: Mon, 15 Dec 2025 09:59:20 -0500
Subject: [PATCH 5/5] style: refine sidebar and dropdown menu components for
improved UI
- Simplified the sidebar button's class structure by removing unnecessary overflow styling.
- Enhanced the visual representation of the trashed projects count with updated styling for better visibility.
- Wrapped the dropdown menu's subcontent in a portal for improved rendering and performance.
---
.DS_Store | Bin 10244 -> 0 bytes
.../layout/project-setup-dialog.tsx | 64 ++++++++++++++++--
apps/app/src/components/layout/sidebar.tsx | 24 +++++--
apps/app/src/components/ui/dropdown-menu.tsx | 18 ++---
apps/app/src/components/views/board-view.tsx | 2 +-
apps/app/src/lib/electron.ts | 24 ++++---
apps/app/src/lib/http-api-client.ts | 12 ++--
apps/app/src/types/electron.d.ts | 8 ++-
.../app-spec/generate-features-from-spec.ts | 9 ++-
.../src/routes/app-spec/generate-spec.ts | 7 +-
.../src/routes/app-spec/routes/create.ts | 7 +-
.../app-spec/routes/generate-features.ts | 13 +++-
.../src/routes/app-spec/routes/generate.ts | 6 +-
13 files changed, 148 insertions(+), 46 deletions(-)
delete mode 100644 .DS_Store
diff --git a/.DS_Store b/.DS_Store
deleted file mode 100644
index 21a2444b66188de34d1e90ece55499acfbc0ec1e..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 10244
zcmeHMU2GIp6u#%S&>1?=0a_{8fk_u4;1-q^`omAM{Zsx6Y)iMLu*~j^bYMEO?96V#
zQq!3D!jHx$jepT6e~3J&DDg!TMbSq^6O8czqw(iK6Ma#6@Z7nxEwpG%L|&A(3aO%5uy@81gwn85;oTg$5E4DWE=avuVpoBX=&HoiaKU
zgdPYz5PBf=K3F$lw8^F*q&zEM$T6-bpwYC^qRKr47WLXTX%ik1n&?;K}^Zj
zBO?v5RZ+FRan)#49cihLMb*^}jiaNYP!(CZZddA%HDo)-xd9M-1jx)u2A!C&Qeo
z{5GN_kPJ7?
zo_U{XW7xWBALto2b#Jh@V7g}7GP-ST$Pi~kRJ>SGJ%7QSHHyLqOehqGCi7|Y
zaNhKe$`L{E*HD#OX+X!9+vezO6UPlvLDu-d
z6;e#Jj59QZCb}Y*wJPn(
zKDO*u&GovnhRt5-0`I#A`}*BMEoXEMBLIUp+Kf_vOHt00I(5@?{7&@7_F6^hW7DiHE)Lg^9Yj|AKz(=M0@dfZlyeIXK0yzTE!B)**QQlz+9+|iMEED#7pA@!t#
zY$f|ij*O5~`L~5KK4-E*ycQFapQmAvg{v;1oOzkHORM3_J_Z!AtN8yb5RGb$AOdzz6Uld<>W2
zOZW=Dh9BTp_zkYmX>=Z~rt@hnjnX>0f;Q1+x|Xh^+i4d~QjH#@4h^pFXfe?C77gT-
z9z_~_HZahFYd?DC`hiw+@5W7=rLDJ2pk12fhgN0P9rM&B%U7;$-q1P@L5|rXOs@vN
zkN9ii#7Df3h)HO@=*OV6a8WVnCc==1WeQI1Mi&&fkD@@Fu(i@4|U_KS2EFi1+W{`v9v&3~##@!|?M2
zh6{b
zp$9?_{B;jNX-A@?4aXNxN|RODUAu~PKUP-QxG_$B6>P5gBLEq89lsPS8(X{#VbAeE
gHpZ#1j&ffe>A&=6z@J~Vh3Eh9{Le1LZ|n2_?~u-Zo&W#<
diff --git a/apps/app/src/components/layout/project-setup-dialog.tsx b/apps/app/src/components/layout/project-setup-dialog.tsx
index 3683b5cc..82453203 100644
--- a/apps/app/src/components/layout/project-setup-dialog.tsx
+++ b/apps/app/src/components/layout/project-setup-dialog.tsx
@@ -1,6 +1,6 @@
"use client";
-import { Sparkles } from "lucide-react";
+import { Sparkles, Clock } from "lucide-react";
import {
Dialog,
DialogContent,
@@ -11,6 +11,19 @@ import {
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
+import { cn } from "@/lib/utils";
+
+// Feature count options
+export type FeatureCount = 20 | 50 | 100;
+const FEATURE_COUNT_OPTIONS: {
+ value: FeatureCount;
+ label: string;
+ warning?: string;
+}[] = [
+ { value: 20, label: "20" },
+ { value: 50, label: "50", warning: "May take up to 5 minutes" },
+ { value: 100, label: "100", warning: "May take up to 5 minutes" },
+];
interface ProjectSetupDialogProps {
open: boolean;
@@ -19,6 +32,8 @@ interface ProjectSetupDialogProps {
onProjectOverviewChange: (value: string) => void;
generateFeatures: boolean;
onGenerateFeaturesChange: (value: boolean) => void;
+ featureCount: FeatureCount;
+ onFeatureCountChange: (value: FeatureCount) => void;
onCreateSpec: () => void;
onSkip: () => void;
isCreatingSpec: boolean;
@@ -31,6 +46,8 @@ export function ProjectSetupDialog({
onProjectOverviewChange,
generateFeatures,
onGenerateFeaturesChange,
+ featureCount,
+ onFeatureCountChange,
onCreateSpec,
onSkip,
isCreatingSpec,
@@ -94,16 +111,52 @@ export function ProjectSetupDialog({
+
+ {/* Feature Count Selection - only shown when generateFeatures is enabled */}
+ {generateFeatures && (
+
+
+
+ {FEATURE_COUNT_OPTIONS.map((option) => (
+
+ ))}
+
+ {FEATURE_COUNT_OPTIONS.find((o) => o.value === featureCount)
+ ?.warning && (
+
+
+ {
+ FEATURE_COUNT_OPTIONS.find((o) => o.value === featureCount)
+ ?.warning
+ }
+
+ )}
+
+ )}
-