From 01098545cf9ddb098a519597737a48efa96c1b29 Mon Sep 17 00:00:00 2001 From: SuperComboGamer Date: Tue, 16 Dec 2025 23:13:06 -0500 Subject: [PATCH 1/8] feat: integrate planning mode functionality across components - Added a new PlanningMode feature to manage default planning strategies for features. - Updated the FeatureDefaultsSection to include a dropdown for selecting the default planning mode. - Enhanced AddFeatureDialog and EditFeatureDialog to support planning mode selection and state management. - Introduced PlanningModeSelector component for better user interaction with planning modes. - Updated app state management to include default planning mode and related specifications. - Refactored various UI components to ensure compatibility with new planning mode features. --- apps/app/package.json | 1 + apps/app/src/components/ui/checkbox.tsx | 24 +- apps/app/src/components/ui/dialog.tsx | 77 +- apps/app/src/components/ui/dropdown-menu.tsx | 153 +- apps/app/src/components/ui/popover.tsx | 33 +- apps/app/src/components/ui/select.tsx | 160 +++ apps/app/src/components/ui/slider.tsx | 39 +- apps/app/src/components/ui/tabs.tsx | 77 +- apps/app/src/components/ui/tooltip.tsx | 35 +- .../board-view/dialogs/add-feature-dialog.tsx | 43 +- .../dialogs/edit-feature-dialog.tsx | 37 +- .../board-view/dialogs/follow-up-dialog.tsx | 2 +- .../views/board-view/shared/index.ts | 1 + .../shared/planning-mode-selector.tsx | 327 +++++ .../src/components/views/settings-view.tsx | 4 + .../feature-defaults-section.tsx | 88 +- apps/app/src/store/app-store.ts | 25 + apps/server/src/services/auto-mode-service.ts | 102 +- package-lock.json | 1246 +++++++++-------- 19 files changed, 1810 insertions(+), 664 deletions(-) create mode 100644 apps/app/src/components/ui/select.tsx create mode 100644 apps/app/src/components/views/board-view/shared/planning-mode-selector.tsx diff --git a/apps/app/package.json b/apps/app/package.json index ad9100db..528d6613 100644 --- a/apps/app/package.json +++ b/apps/app/package.json @@ -45,6 +45,7 @@ "@radix-ui/react-dropdown-menu": "^2.1.16", "@radix-ui/react-label": "^2.1.8", "@radix-ui/react-popover": "^1.1.15", + "@radix-ui/react-select": "^2.2.6", "@radix-ui/react-slider": "^1.3.6", "@radix-ui/react-slot": "^1.2.4", "@radix-ui/react-tabs": "^1.1.13", diff --git a/apps/app/src/components/ui/checkbox.tsx b/apps/app/src/components/ui/checkbox.tsx index 5b00e0cc..bc464c55 100644 --- a/apps/app/src/components/ui/checkbox.tsx +++ b/apps/app/src/components/ui/checkbox.tsx @@ -13,9 +13,23 @@ interface CheckboxProps extends Omit & { + children?: React.ReactNode; + className?: string; + } & React.RefAttributes +>; + +const CheckboxIndicator = CheckboxPrimitive.Indicator as React.ForwardRefExoticComponent< + React.ComponentPropsWithoutRef & { + children?: React.ReactNode; + className?: string; + } & React.RefAttributes +>; + const Checkbox = React.forwardRef( - ({ className, onCheckedChange, ...props }, ref) => ( - ( + ( }} {...props} > - - - + + ) ); Checkbox.displayName = CheckboxPrimitive.Root.displayName; diff --git a/apps/app/src/components/ui/dialog.tsx b/apps/app/src/components/ui/dialog.tsx index ea00207a..ca028e21 100644 --- a/apps/app/src/components/ui/dialog.tsx +++ b/apps/app/src/components/ui/dialog.tsx @@ -6,6 +6,36 @@ import { XIcon } from "lucide-react"; import { cn } from "@/lib/utils"; +// Type-safe wrappers for Radix UI primitives (React 19 compatibility) +const DialogContentPrimitive = DialogPrimitive.Content as React.ForwardRefExoticComponent< + React.ComponentPropsWithoutRef & { + children?: React.ReactNode; + className?: string; + } & React.RefAttributes +>; + +const DialogClosePrimitive = DialogPrimitive.Close as React.ForwardRefExoticComponent< + React.ComponentPropsWithoutRef & { + children?: React.ReactNode; + className?: string; + } & React.RefAttributes +>; + +const DialogTitlePrimitive = DialogPrimitive.Title as React.ForwardRefExoticComponent< + React.ComponentPropsWithoutRef & { + children?: React.ReactNode; + className?: string; + } & React.RefAttributes +>; + +const DialogDescriptionPrimitive = DialogPrimitive.Description as React.ForwardRefExoticComponent< + React.ComponentPropsWithoutRef & { + children?: React.ReactNode; + className?: string; + title?: string; + } & React.RefAttributes +>; + function Dialog({ ...props }: React.ComponentProps) { @@ -30,12 +60,20 @@ function DialogClose({ return ; } +const DialogOverlayPrimitive = DialogPrimitive.Overlay as React.ForwardRefExoticComponent< + React.ComponentPropsWithoutRef & { + className?: string; + } & React.RefAttributes +>; + function DialogOverlay({ className, ...props -}: React.ComponentProps) { +}: React.ComponentProps & { + className?: string; +}) { return ( - - {children} {showCloseButton && ( - Close - + )} - + ); } @@ -137,27 +175,42 @@ function DialogFooter({ className, ...props }: React.ComponentProps<"div">) { function DialogTitle({ className, + children, ...props -}: React.ComponentProps) { +}: React.ComponentProps & { + children?: React.ReactNode; + className?: string; +}) { return ( - + > + {children} + ); } function DialogDescription({ className, + children, + title, ...props -}: React.ComponentProps) { +}: React.ComponentProps & { + children?: React.ReactNode; + className?: string; + title?: string; +}) { return ( - + > + {children} + ); } diff --git a/apps/app/src/components/ui/dropdown-menu.tsx b/apps/app/src/components/ui/dropdown-menu.tsx index cdefc5c7..45664093 100644 --- a/apps/app/src/components/ui/dropdown-menu.tsx +++ b/apps/app/src/components/ui/dropdown-menu.tsx @@ -6,9 +6,83 @@ import { Check, ChevronRight, Circle } from "lucide-react" import { cn } from "@/lib/utils" +// Type-safe wrappers for Radix UI primitives (React 19 compatibility) +const DropdownMenuTriggerPrimitive = DropdownMenuPrimitive.Trigger as React.ForwardRefExoticComponent< + React.ComponentPropsWithoutRef & { + children?: React.ReactNode; + asChild?: boolean; + } & React.RefAttributes +>; + +const DropdownMenuSubTriggerPrimitive = DropdownMenuPrimitive.SubTrigger as React.ForwardRefExoticComponent< + React.ComponentPropsWithoutRef & { + children?: React.ReactNode; + className?: string; + } & React.RefAttributes +>; + +const DropdownMenuRadioGroupPrimitive = DropdownMenuPrimitive.RadioGroup as React.ForwardRefExoticComponent< + React.ComponentPropsWithoutRef & { + children?: React.ReactNode; + } & React.RefAttributes +>; + +const DropdownMenuItemPrimitive = DropdownMenuPrimitive.Item as React.ForwardRefExoticComponent< + React.ComponentPropsWithoutRef & { + children?: React.ReactNode; + className?: string; + } & React.HTMLAttributes & React.RefAttributes +>; + +const DropdownMenuRadioItemPrimitive = DropdownMenuPrimitive.RadioItem as React.ForwardRefExoticComponent< + React.ComponentPropsWithoutRef & { + children?: React.ReactNode; + className?: string; + } & React.HTMLAttributes & React.RefAttributes +>; + +const DropdownMenuLabelPrimitive = DropdownMenuPrimitive.Label as React.ForwardRefExoticComponent< + React.ComponentPropsWithoutRef & { + children?: React.ReactNode; + className?: string; + } & React.RefAttributes +>; + +const DropdownMenuCheckboxItemPrimitive = DropdownMenuPrimitive.CheckboxItem as React.ForwardRefExoticComponent< + React.ComponentPropsWithoutRef & { + children?: React.ReactNode; + className?: string; + } & React.RefAttributes +>; + +const DropdownMenuItemIndicatorPrimitive = DropdownMenuPrimitive.ItemIndicator as React.ForwardRefExoticComponent< + React.ComponentPropsWithoutRef & { + children?: React.ReactNode; + } & React.RefAttributes +>; + +const DropdownMenuSeparatorPrimitive = DropdownMenuPrimitive.Separator as React.ForwardRefExoticComponent< + React.ComponentPropsWithoutRef & { + className?: string; + } & React.RefAttributes +>; + const DropdownMenu = DropdownMenuPrimitive.Root -const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger +function DropdownMenuTrigger({ + children, + asChild, + ...props +}: React.ComponentProps & { + children?: React.ReactNode; + asChild?: boolean; +}) { + return ( + + {children} + + ) +} const DropdownMenuGroup = DropdownMenuPrimitive.Group @@ -16,15 +90,26 @@ const DropdownMenuPortal = DropdownMenuPrimitive.Portal const DropdownMenuSub = DropdownMenuPrimitive.Sub -const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup +function DropdownMenuRadioGroup({ + children, + ...props +}: React.ComponentProps & { children?: React.ReactNode }) { + return ( + + {children} + + ) +} const DropdownMenuSubTrigger = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef & { inset?: boolean + children?: React.ReactNode + className?: string } >(({ className, inset, children, ...props }, ref) => ( - {children} - + )) DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName const DropdownMenuSubContent = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef + React.ComponentPropsWithoutRef & { + className?: string; + } >(({ className, ...props }, ref) => ( , - React.ComponentPropsWithoutRef + React.ComponentPropsWithoutRef & { + className?: string; + } >(({ className, sideOffset = 4, ...props }, ref) => ( , React.ComponentPropsWithoutRef & { inset?: boolean - } ->(({ className, inset, ...props }, ref) => ( - +>(({ className, inset, children, ...props }, ref) => ( + + > + {children} + )) DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName const DropdownMenuCheckboxItem = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef + React.ComponentPropsWithoutRef & { + className?: string; + children?: React.ReactNode; + } >(({ className, children, checked, ...props }, ref) => ( - - + - + {children} - + )) DropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName const DropdownMenuRadioItem = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef + React.ComponentPropsWithoutRef & { + children?: React.ReactNode + } & React.HTMLAttributes >(({ className, children, ...props }, ref) => ( - - + - + {children} - + )) DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName @@ -142,9 +239,11 @@ const DropdownMenuLabel = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef & { inset?: boolean + children?: React.ReactNode + className?: string } ->(({ className, inset, ...props }, ref) => ( - (({ className, inset, children, ...props }, ref) => ( + + > + {children} + )) DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName const DropdownMenuSeparator = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef + React.ComponentPropsWithoutRef & { + className?: string; + } >(({ className, ...props }, ref) => ( - & { + children?: React.ReactNode; + asChild?: boolean; + } & React.RefAttributes +>; + +const PopoverContentPrimitive = PopoverPrimitive.Content as React.ForwardRefExoticComponent< + React.ComponentPropsWithoutRef & { + className?: string; + } & React.RefAttributes +>; + function Popover({ ...props }: React.ComponentProps) { @@ -12,9 +26,18 @@ function Popover({ } function PopoverTrigger({ + children, + asChild, ...props -}: React.ComponentProps) { - return +}: React.ComponentProps & { + children?: React.ReactNode; + asChild?: boolean; +}) { + return ( + + {children} + + ) } function PopoverContent({ @@ -22,10 +45,12 @@ function PopoverContent({ align = "center", sideOffset = 4, ...props -}: React.ComponentProps) { +}: React.ComponentProps & { + className?: string; +}) { return ( - , + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + span]:line-clamp-1", + className + )} + {...props} + > + {children} + + + + +)); +SelectTrigger.displayName = SelectPrimitive.Trigger.displayName; + +const SelectScrollUpButton = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)); +SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName; + +const SelectScrollDownButton = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)); +SelectScrollDownButton.displayName = + SelectPrimitive.ScrollDownButton.displayName; + +const SelectContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, position = "popper", ...props }, ref) => ( + + + + + {children} + + + + +)); +SelectContent.displayName = SelectPrimitive.Content.displayName; + +const SelectLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +SelectLabel.displayName = SelectPrimitive.Label.displayName; + +const SelectItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + + {children} + +)); +SelectItem.displayName = SelectPrimitive.Item.displayName; + +const SelectSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +SelectSeparator.displayName = SelectPrimitive.Separator.displayName; + +export { + Select, + SelectGroup, + SelectValue, + SelectTrigger, + SelectContent, + SelectLabel, + SelectItem, + SelectSeparator, + SelectScrollUpButton, + SelectScrollDownButton, +}; diff --git a/apps/app/src/components/ui/slider.tsx b/apps/app/src/components/ui/slider.tsx index 09253417..9a15927b 100644 --- a/apps/app/src/components/ui/slider.tsx +++ b/apps/app/src/components/ui/slider.tsx @@ -4,6 +4,33 @@ import * as React from "react"; import * as SliderPrimitive from "@radix-ui/react-slider"; import { cn } from "@/lib/utils"; +// Type-safe wrappers for Radix UI primitives (React 19 compatibility) +const SliderRootPrimitive = SliderPrimitive.Root as React.ForwardRefExoticComponent< + React.ComponentPropsWithoutRef & { + children?: React.ReactNode; + className?: string; + } & React.RefAttributes +>; + +const SliderTrackPrimitive = SliderPrimitive.Track as React.ForwardRefExoticComponent< + React.ComponentPropsWithoutRef & { + children?: React.ReactNode; + className?: string; + } & React.RefAttributes +>; + +const SliderRangePrimitive = SliderPrimitive.Range as React.ForwardRefExoticComponent< + React.ComponentPropsWithoutRef & { + className?: string; + } & React.RefAttributes +>; + +const SliderThumbPrimitive = SliderPrimitive.Thumb as React.ForwardRefExoticComponent< + React.ComponentPropsWithoutRef & { + className?: string; + } & React.RefAttributes +>; + interface SliderProps extends Omit, "defaultValue" | "dir"> { value?: number[]; defaultValue?: number[]; @@ -21,7 +48,7 @@ interface SliderProps extends Omit, "defau const Slider = React.forwardRef( ({ className, ...props }, ref) => ( - ( )} {...props} > - - - - - + + + + + ) ); Slider.displayName = SliderPrimitive.Root.displayName; diff --git a/apps/app/src/components/ui/tabs.tsx b/apps/app/src/components/ui/tabs.tsx index d849f038..11898606 100644 --- a/apps/app/src/components/ui/tabs.tsx +++ b/apps/app/src/components/ui/tabs.tsx @@ -5,41 +5,86 @@ import * as TabsPrimitive from "@radix-ui/react-tabs" import { cn } from "@/lib/utils" +// Type-safe wrappers for Radix UI primitives (React 19 compatibility) +const TabsRootPrimitive = TabsPrimitive.Root as React.ForwardRefExoticComponent< + React.ComponentPropsWithoutRef & { + children?: React.ReactNode; + className?: string; + } & React.RefAttributes +>; + +const TabsListPrimitive = TabsPrimitive.List as React.ForwardRefExoticComponent< + React.ComponentPropsWithoutRef & { + children?: React.ReactNode; + className?: string; + } & React.RefAttributes +>; + +const TabsTriggerPrimitive = TabsPrimitive.Trigger as React.ForwardRefExoticComponent< + React.ComponentPropsWithoutRef & { + children?: React.ReactNode; + className?: string; + } & React.RefAttributes +>; + +const TabsContentPrimitive = TabsPrimitive.Content as React.ForwardRefExoticComponent< + React.ComponentPropsWithoutRef & { + children?: React.ReactNode; + className?: string; + } & React.RefAttributes +>; + function Tabs({ className, + children, ...props -}: React.ComponentProps) { +}: React.ComponentProps & { + children?: React.ReactNode; + className?: string; +}) { return ( - + > + {children} + ) } function TabsList({ className, + children, ...props -}: React.ComponentProps) { +}: React.ComponentProps & { + children?: React.ReactNode; + className?: string; +}) { return ( - + > + {children} + ) } function TabsTrigger({ className, + children, ...props -}: React.ComponentProps) { +}: React.ComponentProps & { + children?: React.ReactNode; + className?: string; +}) { return ( - + > + {children} + ) } function TabsContent({ className, + children, ...props -}: React.ComponentProps) { +}: React.ComponentProps & { + children?: React.ReactNode; + className?: string; +}) { return ( - + > + {children} + ) } diff --git a/apps/app/src/components/ui/tooltip.tsx b/apps/app/src/components/ui/tooltip.tsx index bc443206..30d3e44b 100644 --- a/apps/app/src/components/ui/tooltip.tsx +++ b/apps/app/src/components/ui/tooltip.tsx @@ -5,18 +5,47 @@ import * as TooltipPrimitive from "@radix-ui/react-tooltip" import { cn } from "@/lib/utils" +// Type-safe wrappers for Radix UI primitives (React 19 compatibility) +const TooltipTriggerPrimitive = TooltipPrimitive.Trigger as React.ForwardRefExoticComponent< + React.ComponentPropsWithoutRef & { + children?: React.ReactNode; + asChild?: boolean; + } & React.RefAttributes +>; + +const TooltipContentPrimitive = TooltipPrimitive.Content as React.ForwardRefExoticComponent< + React.ComponentPropsWithoutRef & { + className?: string; + } & React.RefAttributes +>; + const TooltipProvider = TooltipPrimitive.Provider const Tooltip = TooltipPrimitive.Root -const TooltipTrigger = TooltipPrimitive.Trigger +function TooltipTrigger({ + children, + asChild, + ...props +}: React.ComponentProps & { + children?: React.ReactNode; + asChild?: boolean; +}) { + return ( + + {children} + + ) +} const TooltipContent = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef + React.ComponentPropsWithoutRef & { + className?: string; + } >(({ className, sideOffset = 6, ...props }, ref) => ( - void; categorySuggestions: string[]; defaultSkipTests: boolean; @@ -92,19 +95,21 @@ export function AddFeatureDialog({ const [descriptionError, setDescriptionError] = useState(false); const [isEnhancing, setIsEnhancing] = useState(false); const [enhancementMode, setEnhancementMode] = useState<'improve' | 'technical' | 'simplify' | 'acceptance'>('improve'); + const [planningMode, setPlanningMode] = useState('skip'); - // Get enhancement model from store - const { enhancementModel } = useAppStore(); + // Get enhancement model and default planning mode from store + const { enhancementModel, defaultPlanningMode } = useAppStore(); - // Sync skipTests default when dialog opens + // Sync defaults when dialog opens useEffect(() => { if (open) { setNewFeature((prev) => ({ ...prev, skipTests: defaultSkipTests, })); + setPlanningMode(defaultPlanningMode); } - }, [open, defaultSkipTests]); + }, [open, defaultSkipTests, defaultPlanningMode]); const handleAdd = () => { if (!newFeature.description.trim()) { @@ -128,6 +133,7 @@ export function AddFeatureDialog({ model: selectedModel, thinkingLevel: normalizedThinking, priority: newFeature.priority, + planningMode, }); // Reset form @@ -142,6 +148,7 @@ export function AddFeatureDialog({ priority: 2, thinkingLevel: "none", }); + setPlanningMode(defaultPlanningMode); setNewFeaturePreviewMap(new Map()); setShowAdvancedOptions(false); setDescriptionError(false); @@ -209,13 +216,13 @@ export function AddFeatureDialog({ { + onPointerDownOutside={(e: CustomEvent) => { const target = e.target as HTMLElement; if (target.closest('[data-testid="category-autocomplete-list"]')) { e.preventDefault(); } }} - onInteractOutside={(e) => { + onInteractOutside={(e: CustomEvent) => { const target = e.target as HTMLElement; if (target.closest('[data-testid="category-autocomplete-list"]')) { e.preventDefault(); @@ -241,9 +248,9 @@ export function AddFeatureDialog({ Model - - - Testing + + + Options @@ -395,8 +402,20 @@ export function AddFeatureDialog({ )} - {/* Testing Tab */} - + {/* Options Tab */} + + {/* Planning Mode Section */} + + +
+ + {/* Testing Section */} diff --git a/apps/app/src/components/views/board-view/dialogs/edit-feature-dialog.tsx b/apps/app/src/components/views/board-view/dialogs/edit-feature-dialog.tsx index d1331046..77840ce3 100644 --- a/apps/app/src/components/views/board-view/dialogs/edit-feature-dialog.tsx +++ b/apps/app/src/components/views/board-view/dialogs/edit-feature-dialog.tsx @@ -19,7 +19,7 @@ import { FeatureImagePath as DescriptionImagePath, ImagePreviewMap, } from "@/components/ui/description-image-dropzone"; -import { MessageSquare, Settings2, FlaskConical, Sparkles, ChevronDown, GitBranch } from "lucide-react"; +import { MessageSquare, Settings2, SlidersHorizontal, Sparkles, ChevronDown, GitBranch } from "lucide-react"; import { toast } from "sonner"; import { getElectronAPI } from "@/lib/electron"; import { modelSupportsThinking } from "@/lib/utils"; @@ -29,6 +29,7 @@ import { ThinkingLevel, AIProfile, useAppStore, + PlanningMode, } from "@/store/app-store"; import { ModelSelector, @@ -36,6 +37,7 @@ import { ProfileQuickSelect, TestingTabContent, PrioritySelector, + PlanningModeSelector, } from "../shared"; import { DropdownMenu, @@ -59,6 +61,7 @@ interface EditFeatureDialogProps { thinkingLevel: ThinkingLevel; imagePaths: DescriptionImagePath[]; priority: number; + planningMode: PlanningMode; } ) => void; categorySuggestions: string[]; @@ -85,13 +88,16 @@ export function EditFeatureDialog({ const [isEnhancing, setIsEnhancing] = useState(false); const [enhancementMode, setEnhancementMode] = useState<'improve' | 'technical' | 'simplify' | 'acceptance'>('improve'); const [showDependencyTree, setShowDependencyTree] = useState(false); + const [planningMode, setPlanningMode] = useState(feature?.planningMode ?? 'skip'); // Get enhancement model from store const { enhancementModel } = useAppStore(); useEffect(() => { setEditingFeature(feature); - if (!feature) { + if (feature) { + setPlanningMode(feature.planningMode ?? 'skip'); + } else { setEditFeaturePreviewMap(new Map()); setShowEditAdvancedOptions(false); } @@ -114,6 +120,7 @@ export function EditFeatureDialog({ thinkingLevel: normalizedThinking, imagePaths: editingFeature.imagePaths ?? [], priority: editingFeature.priority ?? 2, + planningMode, }; onUpdate(editingFeature.id, updates); @@ -186,13 +193,13 @@ export function EditFeatureDialog({ { + onPointerDownOutside={(e: CustomEvent) => { const target = e.target as HTMLElement; if (target.closest('[data-testid="category-autocomplete-list"]')) { e.preventDefault(); } }} - onInteractOutside={(e) => { + onInteractOutside={(e: CustomEvent) => { const target = e.target as HTMLElement; if (target.closest('[data-testid="category-autocomplete-list"]')) { e.preventDefault(); @@ -216,9 +223,9 @@ export function EditFeatureDialog({ Model - - - Testing + + + Options @@ -381,8 +388,20 @@ export function EditFeatureDialog({ )} - {/* Testing Tab */} - + {/* Options Tab */} + + {/* Planning Mode Section */} + + +
+ + {/* Testing Section */} diff --git a/apps/app/src/components/views/board-view/dialogs/follow-up-dialog.tsx b/apps/app/src/components/views/board-view/dialogs/follow-up-dialog.tsx index fa822766..c7e5a87a 100644 --- a/apps/app/src/components/views/board-view/dialogs/follow-up-dialog.tsx +++ b/apps/app/src/components/views/board-view/dialogs/follow-up-dialog.tsx @@ -58,7 +58,7 @@ export function FollowUpDialog({ { + onKeyDown={(e: React.KeyboardEvent) => { if ((e.metaKey || e.ctrlKey) && e.key === "Enter" && prompt.trim()) { e.preventDefault(); onSend(); diff --git a/apps/app/src/components/views/board-view/shared/index.ts b/apps/app/src/components/views/board-view/shared/index.ts index 913aa3e5..d24cb2ef 100644 --- a/apps/app/src/components/views/board-view/shared/index.ts +++ b/apps/app/src/components/views/board-view/shared/index.ts @@ -4,3 +4,4 @@ export * from "./thinking-level-selector"; export * from "./profile-quick-select"; export * from "./testing-tab-content"; export * from "./priority-selector"; +export * from "./planning-mode-selector"; diff --git a/apps/app/src/components/views/board-view/shared/planning-mode-selector.tsx b/apps/app/src/components/views/board-view/shared/planning-mode-selector.tsx new file mode 100644 index 00000000..63ac6c50 --- /dev/null +++ b/apps/app/src/components/views/board-view/shared/planning-mode-selector.tsx @@ -0,0 +1,327 @@ +"use client"; + +import { useState } from "react"; +import { + Zap, ClipboardList, FileText, ScrollText, + Loader2, Check, Eye, RefreshCw, Sparkles +} from "lucide-react"; +import { Label } from "@/components/ui/label"; +import { Button } from "@/components/ui/button"; +import { cn } from "@/lib/utils"; + +export type PlanningMode = 'skip' | 'lite' | 'spec' | 'full'; + +export interface PlanSpec { + status: 'pending' | 'generating' | 'generated' | 'approved' | 'rejected'; + content?: string; + version: number; + generatedAt?: string; + approvedAt?: string; + reviewedByUser: boolean; + tasksCompleted?: number; + tasksTotal?: number; +} + +interface PlanningModeSelectorProps { + mode: PlanningMode; + onModeChange: (mode: PlanningMode) => void; + planSpec?: PlanSpec; + onGenerateSpec?: () => void; + onApproveSpec?: () => void; + onRejectSpec?: () => void; + onViewSpec?: () => void; + isGenerating?: boolean; + featureDescription?: string; // For auto-generation context + testIdPrefix?: string; + compact?: boolean; // For use in dialogs vs settings +} + +const modes = [ + { + value: 'skip' as const, + label: 'Skip', + description: 'Direct implementation, no upfront planning', + icon: Zap, + color: 'text-emerald-500', + bgColor: 'bg-emerald-500/10', + borderColor: 'border-emerald-500/30', + badge: 'Default', + }, + { + value: 'lite' as const, + label: 'Lite', + description: 'Think through approach, create task list', + icon: ClipboardList, + color: 'text-blue-500', + bgColor: 'bg-blue-500/10', + borderColor: 'border-blue-500/30', + }, + { + value: 'spec' as const, + label: 'Spec', + description: 'Generate spec with acceptance criteria', + icon: FileText, + color: 'text-purple-500', + bgColor: 'bg-purple-500/10', + borderColor: 'border-purple-500/30', + badge: 'Approval Required', + }, + { + value: 'full' as const, + label: 'Full', + description: 'Comprehensive spec with phased plan', + icon: ScrollText, + color: 'text-amber-500', + bgColor: 'bg-amber-500/10', + borderColor: 'border-amber-500/30', + badge: 'Approval Required', + }, +]; + +export function PlanningModeSelector({ + mode, + onModeChange, + planSpec, + onGenerateSpec, + onApproveSpec, + onRejectSpec, + onViewSpec, + isGenerating = false, + featureDescription, + testIdPrefix = 'planning', + compact = false, +}: PlanningModeSelectorProps) { + const [showPreview, setShowPreview] = useState(false); + const selectedMode = modes.find(m => m.value === mode); + const requiresApproval = mode === 'spec' || mode === 'full'; + const canGenerate = requiresApproval && featureDescription?.trim() && !isGenerating; + const hasSpec = planSpec && planSpec.content; + + return ( +
+ {/* Header with icon */} +
+
+
+ {selectedMode && } +
+
+ +

+ Choose how much upfront planning before implementation +

+
+
+ + {/* Quick action buttons when spec/full mode */} + {requiresApproval && hasSpec && ( +
+ +
+ )} +
+ + {/* Mode Selection Cards */} +
+ {modes.map((m) => { + const isSelected = mode === m.value; + const Icon = m.icon; + return ( + + ); + })} +
+ + {/* Spec Preview/Actions Panel - Only for spec/full modes */} + {requiresApproval && ( +
+
+ {/* Status indicator */} +
+
+ {isGenerating ? ( + <> + + Generating {mode === 'full' ? 'comprehensive spec' : 'spec'}... + + ) : planSpec?.status === 'approved' ? ( + <> + + Spec Approved + + ) : planSpec?.status === 'generated' ? ( + <> + + Spec Ready for Review + + ) : ( + <> + + + Spec will be generated when feature starts + + + )} +
+ + {/* Auto-generate toggle area */} + {!planSpec?.status && canGenerate && onGenerateSpec && ( + + )} +
+ + {/* Spec content preview */} + {hasSpec && ( +
+ + + {showPreview && ( +
+
+                      {planSpec.content}
+                    
+
+ )} +
+ )} + + {/* Action buttons when spec is generated */} + {planSpec?.status === 'generated' && ( +
+ + +
+ )} + + {/* Regenerate option when approved */} + {planSpec?.status === 'approved' && onGenerateSpec && ( +
+ +
+ )} +
+
+ )} + + {/* Info text for non-approval modes */} + {!requiresApproval && ( +

+ {mode === 'skip' + ? "The agent will start implementing immediately without creating a plan or spec." + : "The agent will create a planning outline before implementing, but won't wait for approval."} +

+ )} +
+ ); +} diff --git a/apps/app/src/components/views/settings-view.tsx b/apps/app/src/components/views/settings-view.tsx index bb56262d..b26c4aba 100644 --- a/apps/app/src/components/views/settings-view.tsx +++ b/apps/app/src/components/views/settings-view.tsx @@ -38,6 +38,8 @@ export function SettingsView() { setMuteDoneSound, currentProject, moveProjectToTrash, + defaultPlanningMode, + setDefaultPlanningMode, } = useAppStore(); // Convert electron Project to settings-view Project type @@ -119,9 +121,11 @@ export function SettingsView() { showProfilesOnly={showProfilesOnly} defaultSkipTests={defaultSkipTests} useWorktrees={useWorktrees} + defaultPlanningMode={defaultPlanningMode} onShowProfilesOnlyChange={setShowProfilesOnly} onDefaultSkipTestsChange={setDefaultSkipTests} onUseWorktreesChange={setUseWorktrees} + onDefaultPlanningModeChange={setDefaultPlanningMode} /> ); case "danger": diff --git a/apps/app/src/components/views/settings-view/feature-defaults/feature-defaults-section.tsx b/apps/app/src/components/views/settings-view/feature-defaults/feature-defaults-section.tsx index cd060466..c1155545 100644 --- a/apps/app/src/components/views/settings-view/feature-defaults/feature-defaults-section.tsx +++ b/apps/app/src/components/views/settings-view/feature-defaults/feature-defaults-section.tsx @@ -1,24 +1,40 @@ import { Label } from "@/components/ui/label"; import { Checkbox } from "@/components/ui/checkbox"; -import { FlaskConical, Settings2, TestTube, GitBranch } from "lucide-react"; +import { + FlaskConical, Settings2, TestTube, GitBranch, + Zap, ClipboardList, FileText, ScrollText +} from "lucide-react"; import { cn } from "@/lib/utils"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; + +type PlanningMode = 'skip' | 'lite' | 'spec' | 'full'; interface FeatureDefaultsSectionProps { showProfilesOnly: boolean; defaultSkipTests: boolean; useWorktrees: boolean; + defaultPlanningMode: PlanningMode; onShowProfilesOnlyChange: (value: boolean) => void; onDefaultSkipTestsChange: (value: boolean) => void; onUseWorktreesChange: (value: boolean) => void; + onDefaultPlanningModeChange: (value: PlanningMode) => void; } export function FeatureDefaultsSection({ showProfilesOnly, defaultSkipTests, useWorktrees, + defaultPlanningMode, onShowProfilesOnlyChange, onDefaultSkipTestsChange, onUseWorktreesChange, + onDefaultPlanningModeChange, }: FeatureDefaultsSectionProps) { return (
+ {/* Planning Mode Default */} +
+
+ {defaultPlanningMode === 'skip' && } + {defaultPlanningMode === 'lite' && } + {defaultPlanningMode === 'spec' && } + {defaultPlanningMode === 'full' && } +
+
+
+ + +
+

+ {defaultPlanningMode === 'skip' && "Jump straight to implementation without upfront planning."} + {defaultPlanningMode === 'lite' && "Create a quick planning outline with tasks before building."} + {defaultPlanningMode === 'spec' && "Generate a specification with acceptance criteria for approval."} + {defaultPlanningMode === 'full' && "Create comprehensive spec with phased implementation plan."} +

+
+
+ + {/* Separator */} +
+ {/* Profiles Only Setting */}
void; isSpecCreatingForProject: (projectPath: string) => boolean; + setDefaultPlanningMode: (mode: PlanningMode) => void; + // Reset reset: () => void; } @@ -736,6 +757,7 @@ const initialState: AppState = { defaultFontSize: 14, }, specCreatingForProject: null, + defaultPlanningMode: 'skip' as PlanningMode, }; export const useAppStore = create()( @@ -2115,6 +2137,8 @@ export const useAppStore = create()( return get().specCreatingForProject === projectPath; }, + setDefaultPlanningMode: (mode) => set({ defaultPlanningMode: mode }), + // Reset reset: () => set(initialState), }), @@ -2180,6 +2204,7 @@ export const useAppStore = create()( lastSelectedSessionByProject: state.lastSelectedSessionByProject, // Board background settings boardBackgroundByProject: state.boardBackgroundByProject, + defaultPlanningMode: state.defaultPlanningMode, }), } ) diff --git a/apps/server/src/services/auto-mode-service.ts b/apps/server/src/services/auto-mode-service.ts index 00bfd0f7..ab61f535 100644 --- a/apps/server/src/services/auto-mode-service.ts +++ b/apps/server/src/services/auto-mode-service.ts @@ -23,6 +23,73 @@ import { isAbortError, classifyError } from "../lib/error-handler.js"; const execAsync = promisify(exec); +type PlanningMode = 'skip' | 'lite' | 'spec' | 'full'; + +interface PlanSpec { + status: 'pending' | 'generating' | 'generated' | 'approved' | 'rejected'; + content?: string; + version: number; + generatedAt?: string; + approvedAt?: string; + reviewedByUser: boolean; + tasksCompleted?: number; + tasksTotal?: number; +} + +const PLANNING_PROMPTS = { + lite: `## Planning Phase (Lite Mode) + +Before implementing, create a brief planning outline: + +1. **Goal**: What are we accomplishing? (1 sentence) +2. **Approach**: How will we do it? (2-3 sentences) +3. **Files to Touch**: List files and what changes +4. **Tasks**: Numbered task list (3-7 items) +5. **Risks**: Any gotchas to watch for + +Present this outline, then proceed with implementation.`, + + spec: `## Specification Phase (Spec Mode) + +Before implementing, generate a specification and WAIT for approval: + +1. **Problem**: What problem are we solving? (user perspective) +2. **Solution**: Brief approach (1 sentence) +3. **Acceptance Criteria**: 3-5 items in GIVEN-WHEN-THEN format +4. **Files to Modify**: Table with File, Purpose, Action +5. **Tasks**: Numbered implementation tasks + +After generating the spec, output: +"[SPEC_GENERATED] Please review the specification above. Reply with 'approved' to proceed or provide feedback for revisions." + +DO NOT proceed with implementation until you receive explicit approval.`, + + full: `## Full Specification Phase (Full SDD Mode) + +Before implementing, generate a comprehensive specification and WAIT for approval: + +1. **Problem Statement**: 2-3 sentences, user perspective +2. **User Story**: As a [user], I want [goal], so that [benefit] +3. **Acceptance Criteria**: Multiple scenarios with GIVEN-WHEN-THEN + - Happy path scenario + - Edge case scenarios + - Error handling scenarios +4. **Technical Context**: + - Files to modify (table format) + - Dependencies + - Constraints + - Existing patterns to follow +5. **Non-Goals**: What this feature explicitly does NOT include +6. **Implementation Plan**: Phased tasks (Phase 1: Foundation, Phase 2: Core, etc.) +7. **Success Metrics**: How we know it's done +8. **Risks & Mitigations**: Table of risks + +After generating, output: +"[SPEC_GENERATED] Please review the comprehensive specification above. Reply with 'approved' to proceed or provide feedback for revisions." + +DO NOT proceed with implementation until you receive explicit approval.` +}; + interface Feature { id: string; category: string; @@ -41,6 +108,8 @@ interface Feature { [key: string]: unknown; } >; + planningMode?: PlanningMode; + planSpec?: PlanSpec; } interface RunningFeature { @@ -235,8 +304,19 @@ export class AutoModeService { // Update feature status to in_progress await this.updateFeatureStatus(projectPath, featureId, "in_progress"); - // Build the prompt - const prompt = this.buildFeaturePrompt(feature); + // Build the prompt with planning phase if needed + const featurePrompt = this.buildFeaturePrompt(feature); + const planningPrefix = this.getPlanningPromptPrefix(feature); + const prompt = planningPrefix + featurePrompt; + + // Emit planning mode info + if (feature.planningMode && feature.planningMode !== 'skip') { + this.emitAutoModeEvent('planning_started', { + featureId: feature.id, + mode: feature.planningMode, + message: `Starting ${feature.planningMode} planning phase` + }); + } // Extract image paths from feature const imagePaths = feature.imagePaths?.map((img) => @@ -993,6 +1073,24 @@ Format your response as a structured markdown document.`; return firstLine.substring(0, 57) + "..."; } + /** + * Get the planning prompt prefix based on feature's planning mode + */ + private getPlanningPromptPrefix(feature: Feature): string { + const mode = feature.planningMode || 'skip'; + + if (mode === 'skip') { + return ''; // No planning phase + } + + const planningPrompt = PLANNING_PROMPTS[mode]; + if (!planningPrompt) { + return ''; + } + + return planningPrompt + '\n\n---\n\n## Feature Request\n\n'; + } + /** * Extract image paths from feature's imagePaths array * Handles both string paths and objects with path property diff --git a/package-lock.json b/package-lock.json index ed4a9293..00e5a36e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,6 +33,7 @@ "@radix-ui/react-dropdown-menu": "^2.1.16", "@radix-ui/react-label": "^2.1.8", "@radix-ui/react-popover": "^1.1.15", + "@radix-ui/react-select": "^2.2.6", "@radix-ui/react-slider": "^1.3.6", "@radix-ui/react-slot": "^1.2.4", "@radix-ui/react-tabs": "^1.1.13", @@ -1088,36 +1089,6 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "apps/app/node_modules/@floating-ui/core": { - "version": "1.7.3", - "license": "MIT", - "dependencies": { - "@floating-ui/utils": "^0.2.10" - } - }, - "apps/app/node_modules/@floating-ui/dom": { - "version": "1.7.4", - "license": "MIT", - "dependencies": { - "@floating-ui/core": "^1.7.3", - "@floating-ui/utils": "^0.2.10" - } - }, - "apps/app/node_modules/@floating-ui/react-dom": { - "version": "2.1.6", - "license": "MIT", - "dependencies": { - "@floating-ui/dom": "^1.7.4" - }, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" - } - }, - "apps/app/node_modules/@floating-ui/utils": { - "version": "0.2.10", - "license": "MIT" - }, "apps/app/node_modules/@gar/promisify": { "version": "1.1.3", "dev": true, @@ -1542,35 +1513,6 @@ "node": ">=18" } }, - "apps/app/node_modules/@radix-ui/number": { - "version": "1.1.1", - "license": "MIT" - }, - "apps/app/node_modules/@radix-ui/primitive": { - "version": "1.1.3", - "license": "MIT" - }, - "apps/app/node_modules/@radix-ui/react-arrow": { - "version": "1.1.7", - "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.1.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, "apps/app/node_modules/@radix-ui/react-checkbox": { "version": "1.3.3", "license": "MIT", @@ -1599,72 +1541,6 @@ } } }, - "apps/app/node_modules/@radix-ui/react-collection": { - "version": "1.1.7", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-slot": "1.2.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "apps/app/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": { - "version": "1.2.3", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "apps/app/node_modules/@radix-ui/react-compose-refs": { - "version": "1.1.2", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "apps/app/node_modules/@radix-ui/react-context": { - "version": "1.1.2", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "apps/app/node_modules/@radix-ui/react-dialog": { "version": "1.1.15", "license": "MIT", @@ -1715,44 +1591,6 @@ } } }, - "apps/app/node_modules/@radix-ui/react-direction": { - "version": "1.1.1", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "apps/app/node_modules/@radix-ui/react-dismissable-layer": { - "version": "1.1.11", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-escape-keydown": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, "apps/app/node_modules/@radix-ui/react-dropdown-menu": { "version": "2.1.16", "license": "MIT", @@ -1780,58 +1618,6 @@ } } }, - "apps/app/node_modules/@radix-ui/react-focus-guards": { - "version": "1.1.3", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "apps/app/node_modules/@radix-ui/react-focus-scope": { - "version": "1.1.7", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "apps/app/node_modules/@radix-ui/react-id": { - "version": "1.1.1", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "apps/app/node_modules/@radix-ui/react-label": { "version": "2.1.8", "license": "MIT", @@ -1979,58 +1765,6 @@ } } }, - "apps/app/node_modules/@radix-ui/react-popper": { - "version": "1.2.8", - "license": "MIT", - "dependencies": { - "@floating-ui/react-dom": "^2.0.0", - "@radix-ui/react-arrow": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-layout-effect": "1.1.1", - "@radix-ui/react-use-rect": "1.1.1", - "@radix-ui/react-use-size": "1.1.1", - "@radix-ui/rect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "apps/app/node_modules/@radix-ui/react-portal": { - "version": "1.1.9", - "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, "apps/app/node_modules/@radix-ui/react-presence": { "version": "1.1.5", "license": "MIT", @@ -2053,43 +1787,6 @@ } } }, - "apps/app/node_modules/@radix-ui/react-primitive": { - "version": "2.1.3", - "license": "MIT", - "dependencies": { - "@radix-ui/react-slot": "1.2.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "apps/app/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": { - "version": "1.2.3", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "apps/app/node_modules/@radix-ui/react-roving-focus": { "version": "1.1.11", "license": "MIT", @@ -2242,151 +1939,6 @@ } } }, - "apps/app/node_modules/@radix-ui/react-use-callback-ref": { - "version": "1.1.1", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "apps/app/node_modules/@radix-ui/react-use-controllable-state": { - "version": "1.2.2", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-effect-event": "0.0.2", - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "apps/app/node_modules/@radix-ui/react-use-effect-event": { - "version": "0.0.2", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "apps/app/node_modules/@radix-ui/react-use-escape-keydown": { - "version": "1.1.1", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-callback-ref": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "apps/app/node_modules/@radix-ui/react-use-layout-effect": { - "version": "1.1.1", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "apps/app/node_modules/@radix-ui/react-use-previous": { - "version": "1.1.1", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "apps/app/node_modules/@radix-ui/react-use-rect": { - "version": "1.1.1", - "license": "MIT", - "dependencies": { - "@radix-ui/rect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "apps/app/node_modules/@radix-ui/react-use-size": { - "version": "1.1.1", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "apps/app/node_modules/@radix-ui/react-visually-hidden": { - "version": "1.2.3", - "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.1.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "apps/app/node_modules/@radix-ui/rect": { - "version": "1.1.1", - "license": "MIT" - }, "apps/app/node_modules/@rtsao/scc": { "version": "1.1.0", "dev": true, @@ -3124,16 +2676,6 @@ "dev": true, "license": "Python-2.0" }, - "apps/app/node_modules/aria-hidden": { - "version": "1.2.6", - "license": "MIT", - "dependencies": { - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, "apps/app/node_modules/aria-query": { "version": "5.3.2", "dev": true, @@ -4172,10 +3714,6 @@ "node": ">=6" } }, - "apps/app/node_modules/detect-node-es": { - "version": "1.1.0", - "license": "MIT" - }, "apps/app/node_modules/devlop": { "version": "1.1.0", "license": "MIT", @@ -5321,13 +4859,6 @@ "node": "6.* || 8.* || >= 10.*" } }, - "apps/app/node_modules/get-nonce": { - "version": "1.0.1", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "apps/app/node_modules/get-symbol-description": { "version": "1.1.0", "dev": true, @@ -7852,69 +7383,6 @@ "react": ">=18" } }, - "apps/app/node_modules/react-remove-scroll": { - "version": "2.7.2", - "license": "MIT", - "dependencies": { - "react-remove-scroll-bar": "^2.3.7", - "react-style-singleton": "^2.2.3", - "tslib": "^2.1.0", - "use-callback-ref": "^1.3.3", - "use-sidecar": "^1.1.3" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "apps/app/node_modules/react-remove-scroll-bar": { - "version": "2.3.8", - "license": "MIT", - "dependencies": { - "react-style-singleton": "^2.2.2", - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "apps/app/node_modules/react-style-singleton": { - "version": "2.2.3", - "license": "MIT", - "dependencies": { - "get-nonce": "^1.0.0", - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "apps/app/node_modules/read-binary-file-arch": { "version": "1.0.6", "dev": true, @@ -9156,45 +8624,6 @@ "browserslist": ">= 4.21.0" } }, - "apps/app/node_modules/use-callback-ref": { - "version": "1.3.3", - "license": "MIT", - "dependencies": { - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "apps/app/node_modules/use-sidecar": { - "version": "1.1.3", - "license": "MIT", - "dependencies": { - "detect-node-es": "^1.1.0", - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "apps/app/node_modules/utf8-byte-length": { "version": "1.0.5", "dev": true, @@ -10519,6 +9948,44 @@ "node": ">=18" } }, + "node_modules/@floating-ui/core": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", + "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", + "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.3", + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.6.tgz", + "integrity": "sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.4" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "license": "MIT" + }, "node_modules/@img/colour": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", @@ -11195,6 +10662,502 @@ "dev": true, "license": "MIT" }, + "node_modules/@radix-ui/number": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", + "integrity": "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==", + "license": "MIT" + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-arrow": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", + "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", + "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-direction": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", + "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", + "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz", + "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", + "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", + "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", + "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-rect": "1.1.1", + "@radix-ui/react-use-size": "1.1.1", + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", + "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz", + "integrity": "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", + "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", + "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", + "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", + "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", + "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==", + "license": "MIT", + "dependencies": { + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", + "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz", + "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", + "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", + "license": "MIT" + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.53.3", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz", @@ -12211,6 +12174,18 @@ "node": ">= 0.6" } }, + "node_modules/aria-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", @@ -12680,6 +12655,12 @@ "license": "MIT", "optional": true }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "license": "MIT" + }, "node_modules/dotenv": { "version": "17.2.3", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", @@ -13268,6 +13249,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/get-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", @@ -14369,6 +14359,53 @@ "react": "^19.2.0" } }, + "node_modules/react-remove-scroll": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", + "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.3", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.3" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.2", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/react-resizable-panels": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-3.0.6.tgz", @@ -14379,6 +14416,28 @@ "react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, + "node_modules/react-style-singleton": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/resolve-alpn": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", @@ -15151,6 +15210,49 @@ "punycode": "^2.1.0" } }, + "node_modules/use-callback-ref": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", From b112747073602291c9e5a426018021a2a89ef6ec Mon Sep 17 00:00:00 2001 From: SuperComboGamer Date: Wed, 17 Dec 2025 19:39:09 -0500 Subject: [PATCH 2/8] feat: implement plan approval functionality in board view - Introduced PlanApprovalDialog for reviewing and approving feature plans. - Added state management for pending plan approvals and loading states. - Enhanced BoardView to handle plan approval actions, including approve and reject functionalities. - Updated KanbanCard and KanbanBoard components to include buttons for viewing and approving plans. - Integrated plan approval logic into the auto mode service, allowing for user feedback and plan edits. - Updated app state to manage default plan approval settings and integrate with existing feature workflows. --- apps/app/src/components/views/board-view.tsx | 162 ++++++ .../board-view/components/kanban-card.tsx | 66 ++- .../board-view/dialogs/add-feature-dialog.tsx | 11 +- .../board-view/dialogs/agent-output-modal.tsx | 30 ++ .../dialogs/edit-feature-dialog.tsx | 6 + .../views/board-view/dialogs/index.ts | 1 + .../dialogs/plan-approval-dialog.tsx | 217 ++++++++ .../board-view/hooks/use-board-actions.ts | 6 +- .../board-view/hooks/use-board-features.ts | 5 + .../views/board-view/kanban-board.tsx | 6 + .../shared/planning-mode-selector.tsx | 23 + .../src/components/views/settings-view.tsx | 4 + .../feature-defaults-section.tsx | 40 +- apps/app/src/hooks/use-auto-mode.ts | 87 +++- apps/app/src/lib/electron.ts | 24 + apps/app/src/lib/http-api-client.ts | 14 + apps/app/src/store/app-store.ts | 27 + apps/app/src/types/electron.d.ts | 43 ++ apps/server/src/routes/auto-mode/index.ts | 2 + .../routes/auto-mode/routes/approve-plan.ts | 78 +++ apps/server/src/services/auto-mode-service.ts | 474 ++++++++++++++++-- apps/server/src/services/feature-loader.ts | 21 +- 22 files changed, 1290 insertions(+), 57 deletions(-) create mode 100644 apps/app/src/components/views/board-view/dialogs/plan-approval-dialog.tsx create mode 100644 apps/server/src/routes/auto-mode/routes/approve-plan.ts diff --git a/apps/app/src/components/views/board-view.tsx b/apps/app/src/components/views/board-view.tsx index 2836c917..7bf3fe37 100644 --- a/apps/app/src/components/views/board-view.tsx +++ b/apps/app/src/components/views/board-view.tsx @@ -29,6 +29,7 @@ import { EditFeatureDialog, FeatureSuggestionsDialog, FollowUpDialog, + PlanApprovalDialog, } from "./board-view/dialogs"; import { COLUMNS } from "./board-view/constants"; import { @@ -56,6 +57,9 @@ export function BoardView() { setKanbanCardDetailLevel, specCreatingForProject, setSpecCreatingForProject, + pendingPlanApproval, + setPendingPlanApproval, + updateFeature, } = useAppStore(); const shortcuts = useKeyboardShortcutsConfig(); const { @@ -80,6 +84,8 @@ export function BoardView() { const [showCompletedModal, setShowCompletedModal] = useState(false); const [deleteCompletedFeature, setDeleteCompletedFeature] = useState(null); + // State for viewing plan in read-only mode + const [viewPlanFeature, setViewPlanFeature] = useState(null); // Follow-up state hook const { @@ -111,6 +117,8 @@ export function BoardView() { } = useSuggestionsState(); // Search filter for Kanban cards const [searchQuery, setSearchQuery] = useState(""); + // Plan approval loading state + const [isPlanApprovalLoading, setIsPlanApprovalLoading] = useState(false); // Derive spec creation state from store - check if current project is the one being created const isCreatingSpec = specCreatingForProject === currentProject?.path; const creatingSpecProjectPath = specCreatingForProject ?? undefined; @@ -297,6 +305,130 @@ export function BoardView() { currentProject, }); + // Find feature for pending plan approval + const pendingApprovalFeature = useMemo(() => { + if (!pendingPlanApproval) return null; + return hookFeatures.find((f) => f.id === pendingPlanApproval.featureId) || null; + }, [pendingPlanApproval, hookFeatures]); + + // Handle plan approval + const handlePlanApprove = useCallback( + async (editedPlan?: string) => { + if (!pendingPlanApproval || !currentProject) return; + + const featureId = pendingPlanApproval.featureId; + setIsPlanApprovalLoading(true); + try { + const api = getElectronAPI(); + if (!api?.autoMode?.approvePlan) { + throw new Error("Plan approval API not available"); + } + + const result = await api.autoMode.approvePlan( + pendingPlanApproval.projectPath, + pendingPlanApproval.featureId, + true, + editedPlan + ); + + if (result.success) { + // Immediately update local feature state to hide "Approve Plan" button + // Get current feature to preserve version + const currentFeature = hookFeatures.find(f => f.id === featureId); + updateFeature(featureId, { + planSpec: { + status: 'approved', + content: editedPlan || pendingPlanApproval.planContent, + version: currentFeature?.planSpec?.version || 1, + approvedAt: new Date().toISOString(), + reviewedByUser: true, + }, + }); + // Reload features from server to ensure sync + loadFeatures(); + } else { + console.error("[Board] Failed to approve plan:", result.error); + } + } catch (error) { + console.error("[Board] Error approving plan:", error); + } finally { + setIsPlanApprovalLoading(false); + setPendingPlanApproval(null); + } + }, + [pendingPlanApproval, currentProject, setPendingPlanApproval, updateFeature, loadFeatures, hookFeatures] + ); + + // Handle plan rejection + const handlePlanReject = useCallback( + async (feedback?: string) => { + if (!pendingPlanApproval || !currentProject) return; + + const featureId = pendingPlanApproval.featureId; + setIsPlanApprovalLoading(true); + try { + const api = getElectronAPI(); + if (!api?.autoMode?.approvePlan) { + throw new Error("Plan approval API not available"); + } + + const result = await api.autoMode.approvePlan( + pendingPlanApproval.projectPath, + pendingPlanApproval.featureId, + false, + undefined, + feedback + ); + + if (result.success) { + // Immediately update local feature state + // Get current feature to preserve version + const currentFeature = hookFeatures.find(f => f.id === featureId); + updateFeature(featureId, { + status: 'backlog', + planSpec: { + status: 'rejected', + content: pendingPlanApproval.planContent, + version: currentFeature?.planSpec?.version || 1, + reviewedByUser: true, + }, + }); + // Reload features from server to ensure sync + loadFeatures(); + } else { + console.error("[Board] Failed to reject plan:", result.error); + } + } catch (error) { + console.error("[Board] Error rejecting plan:", error); + } finally { + setIsPlanApprovalLoading(false); + setPendingPlanApproval(null); + } + }, + [pendingPlanApproval, currentProject, setPendingPlanApproval, updateFeature, loadFeatures, hookFeatures] + ); + + // Handle opening approval dialog from feature card button + const handleOpenApprovalDialog = useCallback( + (feature: Feature) => { + if (!feature.planSpec?.content || !currentProject) return; + + // Determine the planning mode for approval (skip should never have a plan requiring approval) + const mode = feature.planningMode; + const approvalMode: "lite" | "spec" | "full" = + mode === 'lite' || mode === 'spec' || mode === 'full' ? mode : 'spec'; + + // Re-open the approval dialog with the feature's plan data + setPendingPlanApproval({ + featureId: feature.id, + projectPath: currentProject.path, + planContent: feature.planSpec.content, + planningMode: approvalMode, + }); + }, + [currentProject, setPendingPlanApproval] + ); + if (!currentProject) { return (
setViewPlanFeature(feature)} + onApprovePlan={handleOpenApprovalDialog} featuresWithContext={featuresWithContext} runningAutoTasks={runningAutoTasks} shortcuts={shortcuts} @@ -494,6 +628,34 @@ export function BoardView() { isGenerating={isGeneratingSuggestions} setIsGenerating={setIsGeneratingSuggestions} /> + + {/* Plan Approval Dialog */} + { + if (!open) { + setPendingPlanApproval(null); + } + }} + feature={pendingApprovalFeature} + planContent={pendingPlanApproval?.planContent || ""} + onApprove={handlePlanApprove} + onReject={handlePlanReject} + isLoading={isPlanApprovalLoading} + /> + + {/* View Plan Dialog (read-only) */} + {viewPlanFeature && viewPlanFeature.planSpec?.content && ( + !open && setViewPlanFeature(null)} + feature={viewPlanFeature} + planContent={viewPlanFeature.planSpec.content} + onApprove={() => setViewPlanFeature(null)} + onReject={() => setViewPlanFeature(null)} + viewOnly={true} + /> + )}
); } diff --git a/apps/app/src/components/views/board-view/components/kanban-card.tsx b/apps/app/src/components/views/board-view/components/kanban-card.tsx index 4d23a31f..c4b80e78 100644 --- a/apps/app/src/components/views/board-view/components/kanban-card.tsx +++ b/apps/app/src/components/views/board-view/components/kanban-card.tsx @@ -107,6 +107,8 @@ interface KanbanCardProps { onMerge?: () => void; onImplement?: () => void; onComplete?: () => void; + onViewPlan?: () => void; + onApprovePlan?: () => void; hasContext?: boolean; isCurrentAutoTask?: boolean; shortcutKey?: string; @@ -134,6 +136,8 @@ export const KanbanCard = memo(function KanbanCard({ onMerge, onImplement, onComplete, + onViewPlan, + onApprovePlan, hasContext, isCurrentAutoTask, shortcutKey, @@ -858,14 +862,31 @@ export const KanbanCard = memo(function KanbanCard({ )} {/* Actions */} -
+
{isCurrentAutoTask && ( <> + {/* Approve Plan button - PRIORITY: shows even when agent is "running" (paused for approval) */} + {feature.planSpec?.status === 'generated' && onApprovePlan && ( + + )} {onViewOutput && ( + )} {feature.skipTests && onManualVerify ? ( + {feature.planSpec?.content && onViewPlan && ( + + )} {onImplement && ( +
+ )} + + {/* Plan Content */} +
+ {isEditMode && !viewOnly ? ( +