import { useState, useEffect } from "react"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"; import { Button } from "@/components/ui/button"; import { HotkeyButton } from "@/components/ui/hotkey-button"; import { Label } from "@/components/ui/label"; import { CategoryAutocomplete } from "@/components/ui/category-autocomplete"; import { DescriptionImageDropZone, FeatureImagePath as DescriptionImagePath, ImagePreviewMap, } from "@/components/ui/description-image-dropzone"; import { MessageSquare, Settings2, SlidersHorizontal, FlaskConical, Sparkles, ChevronDown, } from "lucide-react"; import { toast } from "sonner"; import { getElectronAPI } from "@/lib/electron"; import { modelSupportsThinking } from "@/lib/utils"; import { useAppStore, AgentModel, ThinkingLevel, FeatureImage, AIProfile, PlanningMode, } from "@/store/app-store"; import { ModelSelector, ThinkingLevelSelector, ProfileQuickSelect, TestingTabContent, PrioritySelector, BranchSelector, PlanningModeSelector, } from "../shared"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { useNavigate } from "@tanstack/react-router"; interface AddFeatureDialogProps { open: boolean; onOpenChange: (open: boolean) => void; onAdd: (feature: { category: string; description: string; steps: string[]; images: FeatureImage[]; imagePaths: DescriptionImagePath[]; skipTests: boolean; model: AgentModel; thinkingLevel: ThinkingLevel; branchName: string; // Can be empty string to use current branch priority: number; planningMode: PlanningMode; requirePlanApproval: boolean; }) => void; categorySuggestions: string[]; branchSuggestions: string[]; branchCardCounts?: Record; // Map of branch name to unarchived card count defaultSkipTests: boolean; defaultBranch?: string; currentBranch?: string; isMaximized: boolean; showProfilesOnly: boolean; aiProfiles: AIProfile[]; } export function AddFeatureDialog({ open, onOpenChange, onAdd, categorySuggestions, branchSuggestions, branchCardCounts, defaultSkipTests, defaultBranch = "main", currentBranch, isMaximized, showProfilesOnly, aiProfiles, }: AddFeatureDialogProps) { const navigate = useNavigate(); const [useCurrentBranch, setUseCurrentBranch] = useState(true); const [newFeature, setNewFeature] = useState({ category: "", description: "", steps: [""], images: [] as FeatureImage[], imagePaths: [] as DescriptionImagePath[], skipTests: false, model: "opus" as AgentModel, thinkingLevel: "none" as ThinkingLevel, branchName: "", priority: 2 as number, // Default to medium priority }); const [newFeaturePreviewMap, setNewFeaturePreviewMap] = useState(() => new Map()); const [showAdvancedOptions, setShowAdvancedOptions] = useState(false); const [descriptionError, setDescriptionError] = useState(false); const [isEnhancing, setIsEnhancing] = useState(false); const [enhancementMode, setEnhancementMode] = useState< "improve" | "technical" | "simplify" | "acceptance" >("improve"); const [planningMode, setPlanningMode] = useState("skip"); const [requirePlanApproval, setRequirePlanApproval] = useState(false); // Get enhancement model, planning mode defaults, and worktrees setting from store const { enhancementModel, defaultPlanningMode, defaultRequirePlanApproval, useWorktrees, } = useAppStore(); // Sync defaults when dialog opens useEffect(() => { if (open) { setNewFeature((prev) => ({ ...prev, skipTests: defaultSkipTests, branchName: defaultBranch || "", })); setUseCurrentBranch(true); setPlanningMode(defaultPlanningMode); setRequirePlanApproval(defaultRequirePlanApproval); } }, [ open, defaultSkipTests, defaultBranch, defaultPlanningMode, defaultRequirePlanApproval, ]); const handleAdd = () => { if (!newFeature.description.trim()) { setDescriptionError(true); return; } // Validate branch selection when "other branch" is selected if (useWorktrees && !useCurrentBranch && !newFeature.branchName.trim()) { toast.error("Please select a branch name"); return; } const category = newFeature.category || "Uncategorized"; const selectedModel = newFeature.model; const normalizedThinking = modelSupportsThinking(selectedModel) ? newFeature.thinkingLevel : "none"; // Use current branch if toggle is on // If currentBranch is provided (non-primary worktree), use it // Otherwise (primary worktree), use empty string which means "unassigned" (show only on primary) const finalBranchName = useCurrentBranch ? currentBranch || "" : newFeature.branchName || ""; onAdd({ category, description: newFeature.description, steps: newFeature.steps.filter((s) => s.trim()), images: newFeature.images, imagePaths: newFeature.imagePaths, skipTests: newFeature.skipTests, model: selectedModel, thinkingLevel: normalizedThinking, branchName: finalBranchName, priority: newFeature.priority, planningMode, requirePlanApproval, }); // Reset form setNewFeature({ category: "", description: "", steps: [""], images: [], imagePaths: [], skipTests: defaultSkipTests, model: "opus", priority: 2, thinkingLevel: "none", branchName: "", }); setUseCurrentBranch(true); setPlanningMode(defaultPlanningMode); setRequirePlanApproval(defaultRequirePlanApproval); setNewFeaturePreviewMap(new Map()); setShowAdvancedOptions(false); setDescriptionError(false); onOpenChange(false); }; const handleDialogClose = (open: boolean) => { onOpenChange(open); if (!open) { setNewFeaturePreviewMap(new Map()); setShowAdvancedOptions(false); setDescriptionError(false); } }; const handleEnhanceDescription = async () => { if (!newFeature.description.trim() || isEnhancing) return; setIsEnhancing(true); try { const api = getElectronAPI(); const result = await api.enhancePrompt?.enhance( newFeature.description, enhancementMode, enhancementModel ); if (result?.success && result.enhancedText) { const enhancedText = result.enhancedText; setNewFeature((prev) => ({ ...prev, description: enhancedText })); toast.success("Description enhanced!"); } else { toast.error(result?.error || "Failed to enhance description"); } } catch (error) { console.error("Enhancement failed:", error); toast.error("Failed to enhance description"); } finally { setIsEnhancing(false); } }; const handleModelSelect = (model: AgentModel) => { setNewFeature({ ...newFeature, model, thinkingLevel: modelSupportsThinking(model) ? newFeature.thinkingLevel : "none", }); }; const handleProfileSelect = ( model: AgentModel, thinkingLevel: ThinkingLevel ) => { setNewFeature({ ...newFeature, model, thinkingLevel, }); }; const newModelAllowsThinking = modelSupportsThinking(newFeature.model); return ( { const target = e.target as HTMLElement; if (target.closest('[data-testid="category-autocomplete-list"]')) { e.preventDefault(); } }} onInteractOutside={(e: CustomEvent) => { const target = e.target as HTMLElement; if (target.closest('[data-testid="category-autocomplete-list"]')) { e.preventDefault(); } }} > Add New Feature Create a new feature card for the Kanban board. Prompt Model Options {/* Prompt Tab */}
{ setNewFeature({ ...newFeature, description: value }); if (value.trim()) { setDescriptionError(false); } }} images={newFeature.imagePaths} onImagesChange={(images) => setNewFeature({ ...newFeature, imagePaths: images }) } placeholder="Describe the feature..." previewMap={newFeaturePreviewMap} onPreviewMapChange={setNewFeaturePreviewMap} autoFocus error={descriptionError} />
setEnhancementMode("improve")} > Improve Clarity setEnhancementMode("technical")} > Add Technical Details setEnhancementMode("simplify")} > Simplify setEnhancementMode("acceptance")} > Add Acceptance Criteria
setNewFeature({ ...newFeature, category: value }) } suggestions={categorySuggestions} placeholder="e.g., Core, UI, API" data-testid="feature-category-input" />
{useWorktrees && ( setNewFeature({ ...newFeature, branchName: value }) } branchSuggestions={branchSuggestions} branchCardCounts={branchCardCounts} currentBranch={currentBranch} testIdPrefix="feature" /> )} {/* Priority Selector */} setNewFeature({ ...newFeature, priority }) } testIdPrefix="priority" />
{/* Model Tab */} {/* Show Advanced Options Toggle */} {showProfilesOnly && (

Simple Mode Active

Only showing AI profiles. Advanced model tweaking is hidden.

)} {/* Quick Select Profile Section */} { onOpenChange(false); navigate({ to: "/profiles" }); }} /> {/* Separator */} {aiProfiles.length > 0 && (!showProfilesOnly || showAdvancedOptions) && (
)} {/* Claude Models Section */} {(!showProfilesOnly || showAdvancedOptions) && ( <> {newModelAllowsThinking && ( setNewFeature({ ...newFeature, thinkingLevel: level }) } /> )} )} {/* Options Tab */} {/* Planning Mode Section */}
{/* Testing Section */} setNewFeature({ ...newFeature, skipTests }) } steps={newFeature.steps} onStepsChange={(steps) => setNewFeature({ ...newFeature, steps })} /> Add Feature
); }