"use client"; 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 { Input } from "@/components/ui/input"; 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 { Checkbox } from "@/components/ui/checkbox"; import { MessageSquare, Settings2, FlaskConical, Plus, Brain, UserCircle, Zap, Scale, Cpu, Rocket, Sparkles, } from "lucide-react"; import { cn, modelSupportsThinking } from "@/lib/utils"; import { useAppStore, AgentModel, ThinkingLevel, FeatureImage, AIProfile, } from "@/store/app-store"; type ModelOption = { id: AgentModel; label: string; description: string; badge?: string; provider: "claude"; }; const CLAUDE_MODELS: ModelOption[] = [ { id: "haiku", label: "Claude Haiku", description: "Fast and efficient for simple tasks.", badge: "Speed", provider: "claude", }, { id: "sonnet", label: "Claude Sonnet", description: "Balanced performance with strong reasoning.", badge: "Balanced", provider: "claude", }, { id: "opus", label: "Claude Opus", description: "Most capable model for complex work.", badge: "Premium", provider: "claude", }, ]; // Profile icon mapping const PROFILE_ICONS: Record< string, React.ComponentType<{ className?: string }> > = { Brain, Zap, Scale, Cpu, Rocket, Sparkles, }; 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; }) => void; categorySuggestions: string[]; defaultSkipTests: boolean; isMaximized: boolean; showProfilesOnly: boolean; aiProfiles: AIProfile[]; } export function AddFeatureDialog({ open, onOpenChange, onAdd, categorySuggestions, defaultSkipTests, isMaximized, showProfilesOnly, aiProfiles, }: AddFeatureDialogProps) { const [newFeature, setNewFeature] = useState({ category: "", description: "", steps: [""], images: [] as FeatureImage[], imagePaths: [] as DescriptionImagePath[], skipTests: false, model: "opus" as AgentModel, thinkingLevel: "none" as ThinkingLevel, }); const [newFeaturePreviewMap, setNewFeaturePreviewMap] = useState(() => new Map()); const [showAdvancedOptions, setShowAdvancedOptions] = useState(false); const [descriptionError, setDescriptionError] = useState(false); // Sync skipTests default when dialog opens useEffect(() => { if (open) { setNewFeature((prev) => ({ ...prev, skipTests: defaultSkipTests, })); } }, [open, defaultSkipTests]); const handleAdd = () => { // Validate description is required if (!newFeature.description.trim()) { setDescriptionError(true); return; } const category = newFeature.category || "Uncategorized"; const selectedModel = newFeature.model; const normalizedThinking = modelSupportsThinking(selectedModel) ? newFeature.thinkingLevel : "none"; 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, }); // Reset form setNewFeature({ category: "", description: "", steps: [""], images: [], imagePaths: [], skipTests: defaultSkipTests, model: "opus", thinkingLevel: "none", }); setNewFeaturePreviewMap(new Map()); setShowAdvancedOptions(false); setDescriptionError(false); onOpenChange(false); }; const handleDialogClose = (open: boolean) => { onOpenChange(open); // Clear preview map, validation error, and reset advanced options when dialog closes if (!open) { setNewFeaturePreviewMap(new Map()); setShowAdvancedOptions(false); setDescriptionError(false); } }; const renderModelOptions = ( options: ModelOption[], selectedModel: AgentModel, onSelect: (model: AgentModel) => void, testIdPrefix = "model-select" ) => (
{options.map((option) => { const isSelected = selectedModel === option.id; // Shorter display names for compact view const shortName = option.label.replace("Claude ", ""); return ( ); })}
); const newModelAllowsThinking = modelSupportsThinking(newFeature.model); return ( { // Prevent dialog from closing when clicking on category autocomplete dropdown const target = e.target as HTMLElement; if (target.closest('[data-testid="category-autocomplete-list"]')) { e.preventDefault(); } }} onInteractOutside={(e) => { // Prevent dialog from closing when clicking on category autocomplete dropdown 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 Testing {/* 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} />
setNewFeature({ ...newFeature, category: value }) } suggestions={categorySuggestions} placeholder="e.g., Core, UI, API" data-testid="feature-category-input" />
{/* Model Tab */} {/* Show Advanced Options Toggle - only when profiles-only mode is enabled */} {showProfilesOnly && (

Simple Mode Active

Only showing AI profiles. Advanced model tweaking is hidden.

)} {/* Quick Select Profile Section */} {aiProfiles.length > 0 && (
Presets
{aiProfiles.slice(0, 6).map((profile) => { const IconComponent = profile.icon ? PROFILE_ICONS[profile.icon] : Brain; const isSelected = newFeature.model === profile.model && newFeature.thinkingLevel === profile.thinkingLevel; return ( ); })}

Or customize below. Manage profiles in{" "}

)} {/* Separator */} {aiProfiles.length > 0 && (!showProfilesOnly || showAdvancedOptions) && (
)} {/* Claude Models Section - Hidden when showProfilesOnly is true and showAdvancedOptions is false */} {(!showProfilesOnly || showAdvancedOptions) && (
Native
{renderModelOptions( CLAUDE_MODELS, newFeature.model, (model) => setNewFeature({ ...newFeature, model, thinkingLevel: modelSupportsThinking(model) ? newFeature.thinkingLevel : "none", }) )} {/* Thinking Level - Only shown when Claude model is selected */} {newModelAllowsThinking && (
{( [ "none", "low", "medium", "high", "ultrathink", ] as ThinkingLevel[] ).map((level) => ( ))}

Higher levels give more time to reason through complex problems.

)}
)} {/* Testing Tab */}
setNewFeature({ ...newFeature, skipTests: checked !== true, }) } data-testid="skip-tests-checkbox" />

When enabled, this feature will use automated TDD. When disabled, it will require manual verification.

{/* Verification Steps - Only shown when skipTests is enabled */} {newFeature.skipTests && (

Add manual steps to verify this feature works correctly.

{newFeature.steps.map((step, index) => ( { const steps = [...newFeature.steps]; steps[index] = e.target.value; setNewFeature({ ...newFeature, steps }); }} data-testid={`feature-step-${index}-input`} /> ))}
)}
Add Feature
); }