From 01027190672ddd03b8453b97b2792c2beecd2643 Mon Sep 17 00:00:00 2001 From: Kacper Date: Mon, 15 Dec 2025 23:34:38 +0100 Subject: [PATCH] refactor(board-view): extract AddFeatureDialog component --- .claude/settings.local.json | 3 +- REFACTORING_CANDIDATES.md | 310 ++++++++++ apps/app/src/components/views/board-view.tsx | 436 +------------ .../views/board-view/AddFeatureDialog.tsx | 578 ++++++++++++++++++ 4 files changed, 912 insertions(+), 415 deletions(-) create mode 100644 REFACTORING_CANDIDATES.md create mode 100644 apps/app/src/components/views/board-view/AddFeatureDialog.tsx diff --git a/.claude/settings.local.json b/.claude/settings.local.json index b7f93610..937fb43a 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -1,7 +1,8 @@ { "permissions": { "allow": [ - "Bash(dir \"C:\\Users\\Ben\\Desktop\\appdev\\git\\automaker\\apps\\app\\public\")" + "Bash(dir \"C:\\Users\\Ben\\Desktop\\appdev\\git\\automaker\\apps\\app\\public\")", + "Bash(find:*)" ] } } diff --git a/REFACTORING_CANDIDATES.md b/REFACTORING_CANDIDATES.md new file mode 100644 index 00000000..fcff3070 --- /dev/null +++ b/REFACTORING_CANDIDATES.md @@ -0,0 +1,310 @@ +# Large Files - Refactoring Candidates + +This document tracks files in the AutoMaker codebase that exceed 3000 lines or are significantly large (1000+ lines) and should be considered for refactoring into smaller, more maintainable components. + +**Last Updated:** 2025-12-15 +**Total Large Files:** 8 +**Combined Size:** 15,027 lines + +--- + +## 🔴 CRITICAL - Over 3000 Lines + +### 1. board-view.tsx - 3,325 lines +**Path:** `apps/app/src/components/views/board-view.tsx` +**Type:** React Component (TSX) +**Priority:** VERY HIGH + +**Description:** +Main Kanban board view component that serves as the centerpiece of the application. + +**Current Responsibilities:** +- Feature/task card management and drag-and-drop operations using @dnd-kit +- Adding, editing, and deleting features +- Running autonomous agents to implement features +- Displaying feature status across multiple columns (Backlog, In Progress, Waiting Approval, Verified) +- Model/AI profile selection for feature implementation +- Advanced options configuration (thinking level, model selection, skip tests) +- Search/filtering functionality for cards +- Output modal for viewing agent results +- Feature suggestions dialog +- Board background customization +- Integration with Electron APIs for IPC communication +- Keyboard shortcuts support +- 40+ state variables for managing UI state + +**Refactoring Recommendations:** +Extract into smaller components: +- `AddFeatureDialog.tsx` - Feature creation dialog with image upload +- `EditFeatureDialog.tsx` - Feature editing dialog +- `AgentOutputModal.tsx` - Already exists, verify separation +- `FeatureSuggestionsDialog.tsx` - Already exists, verify separation +- `BoardHeader.tsx` - Header with controls and search +- `BoardSearchBar.tsx` - Search and filter functionality +- `ConcurrencyControl.tsx` - Concurrency slider component +- `BoardActions.tsx` - Action buttons (add feature, auto mode, etc.) +- `DragDropContext.tsx` - Wrap drag-and-drop logic +- Custom hooks: + - `useBoardFeatures.ts` - Feature loading and management + - `useBoardDragDrop.ts` - Drag and drop handlers + - `useBoardActions.ts` - Feature action handlers (run, verify, delete, etc.) + - `useBoardKeyboardShortcuts.ts` - Keyboard shortcut logic + +--- + +## 🟡 HIGH PRIORITY - 2000+ Lines + +### 2. sidebar.tsx - 2,396 lines +**Path:** `apps/app/src/components/layout/sidebar.tsx` +**Type:** React Component (TSX) +**Priority:** HIGH + +**Description:** +Main navigation sidebar with comprehensive project management. + +**Current Responsibilities:** +- Project folder navigation and selection +- View mode switching (Board, Agent, Settings, etc.) +- Project operations (create, delete, rename) +- Theme and appearance controls +- Terminal, Wiki, and other view launchers +- Drag-and-drop project reordering +- Settings and configuration access + +**Refactoring Recommendations:** +Split into focused components: +- `ProjectSelector.tsx` - Project list and selection +- `NavigationTabs.tsx` - View mode tabs +- `ProjectActions.tsx` - Create, delete, rename operations +- `SettingsMenu.tsx` - Settings dropdown +- `ThemeSelector.tsx` - Theme controls +- `ViewLaunchers.tsx` - Terminal, Wiki launchers +- Custom hooks: + - `useProjectManagement.ts` - Project CRUD operations + - `useSidebarState.ts` - Sidebar state management + +--- + +### 3. electron.ts - 2,356 lines +**Path:** `apps/app/src/lib/electron.ts` +**Type:** TypeScript Utility/API Bridge +**Priority:** HIGH + +**Description:** +Electron IPC bridge and type definitions for frontend-backend communication. + +**Current Responsibilities:** +- File system operations (read, write, directory listing) +- Project management APIs +- Feature management APIs +- Terminal/shell execution +- Auto mode and agent execution APIs +- Worktree management +- Provider status APIs +- Event handling and subscriptions + +**Refactoring Recommendations:** +Modularize into domain-specific API modules: +- `api/file-system-api.ts` - File operations +- `api/project-api.ts` - Project CRUD +- `api/feature-api.ts` - Feature management +- `api/execution-api.ts` - Auto mode and agent execution +- `api/provider-api.ts` - Provider status and management +- `api/worktree-api.ts` - Git worktree operations +- `api/terminal-api.ts` - Terminal/shell APIs +- `types/electron-types.ts` - Shared type definitions +- `electron.ts` - Main export aggregator + +--- + +### 4. app-store.ts - 2,174 lines +**Path:** `apps/app/src/store/app-store.ts` +**Type:** TypeScript State Management (Zustand Store) +**Priority:** HIGH + +**Description:** +Centralized application state store using Zustand. + +**Current Responsibilities:** +- Global app state types and interfaces +- Project and feature management state +- Theme and appearance settings +- API keys configuration +- Keyboard shortcuts configuration +- Terminal themes configuration +- Auto mode settings +- All store mutations and selectors + +**Refactoring Recommendations:** +Split into domain-specific stores: +- `stores/projects-store.ts` - Project state and actions +- `stores/features-store.ts` - Feature state and actions +- `stores/ui-store.ts` - UI state (theme, sidebar, modals) +- `stores/settings-store.ts` - User settings and preferences +- `stores/execution-store.ts` - Auto mode and running tasks +- `stores/provider-store.ts` - Provider configuration +- `types/store-types.ts` - Shared type definitions +- `app-store.ts` - Main store aggregator with combined selectors + +--- + +## 🟢 MEDIUM PRIORITY - 1000-2000 Lines + +### 5. auto-mode-service.ts - 1,232 lines +**Path:** `apps/server/src/services/auto-mode-service.ts` +**Type:** TypeScript Service (Backend) +**Priority:** MEDIUM-HIGH + +**Description:** +Core autonomous feature implementation service. + +**Current Responsibilities:** +- Worktree creation and management +- Feature execution with Claude Agent SDK +- Concurrent execution with concurrency limits +- Progress streaming via events +- Verification and merge workflows +- Provider management +- Error handling and classification + +**Refactoring Recommendations:** +Extract into service modules: +- `services/worktree-manager.ts` - Worktree operations +- `services/feature-executor.ts` - Feature execution logic +- `services/concurrency-manager.ts` - Concurrency control +- `services/verification-service.ts` - Verification workflows +- `utils/error-classifier.ts` - Error handling utilities + +--- + +### 6. spec-view.tsx - 1,230 lines +**Path:** `apps/app/src/components/views/spec-view.tsx` +**Type:** React Component (TSX) +**Priority:** MEDIUM + +**Description:** +Specification editor view component for feature specification management. + +**Refactoring Recommendations:** +Extract editor components and hooks: +- `SpecEditor.tsx` - Main editor component +- `SpecToolbar.tsx` - Editor toolbar +- `SpecSidebar.tsx` - Spec navigation sidebar +- `useSpecEditor.ts` - Editor state management + +--- + +### 7. kanban-card.tsx - 1,180 lines +**Path:** `apps/app/src/components/views/kanban-card.tsx` +**Type:** React Component (TSX) +**Priority:** MEDIUM + +**Description:** +Individual Kanban card component with rich feature display and interaction. + +**Refactoring Recommendations:** +Split into smaller card components: +- `KanbanCardHeader.tsx` - Card title and metadata +- `KanbanCardBody.tsx` - Card content +- `KanbanCardActions.tsx` - Action buttons +- `KanbanCardStatus.tsx` - Status indicators +- `useKanbanCard.ts` - Card interaction logic + +--- + +### 8. analysis-view.tsx - 1,134 lines +**Path:** `apps/app/src/components/views/analysis-view.tsx` +**Type:** React Component (TSX) +**Priority:** MEDIUM + +**Description:** +Analysis view component for displaying and managing feature analysis data. + +**Refactoring Recommendations:** +Extract visualization and data components: +- `AnalysisChart.tsx` - Chart/graph components +- `AnalysisTable.tsx` - Data table +- `AnalysisFilters.tsx` - Filter controls +- `useAnalysisData.ts` - Data fetching and processing + +--- + +## Refactoring Strategy + +### Phase 1: Critical (Immediate) +1. **board-view.tsx** - Break into dialogs, header, and custom hooks + - Extract all dialogs first (AddFeature, EditFeature) + - Move to custom hooks for business logic + - Split remaining UI into smaller components + +### Phase 2: High Priority (Next Sprint) +2. **sidebar.tsx** - Componentize navigation and project management +3. **electron.ts** - Modularize into API domains +4. **app-store.ts** - Split into domain stores + +### Phase 3: Medium Priority (Future) +5. **auto-mode-service.ts** - Extract service modules +6. **spec-view.tsx** - Break into editor components +7. **kanban-card.tsx** - Split card into sub-components +8. **analysis-view.tsx** - Extract visualization components + +--- + +## General Refactoring Guidelines + +### When Refactoring Large Components: + +1. **Extract Dialogs/Modals First** + - Move dialog components to separate files + - Keep dialog state management in parent initially + - Later extract to custom hooks if complex + +2. **Create Custom Hooks for Business Logic** + - Move data fetching to `useFetch*` hooks + - Move complex state logic to `use*State` hooks + - Move side effects to `use*Effect` hooks + +3. **Split UI into Presentational Components** + - Header/toolbar components + - Content area components + - Footer/action components + +4. **Move Utils and Helpers** + - Extract pure functions to utility files + - Move constants to separate constant files + - Create type files for shared interfaces + +### When Refactoring Large Files: + +1. **Identify Domains/Concerns** + - Group related functionality + - Find natural boundaries + +2. **Extract Gradually** + - Start with least coupled code + - Work towards core functionality + - Test after each extraction + +3. **Maintain Type Safety** + - Export types from extracted modules + - Use shared type files for common interfaces + - Ensure no type errors after refactoring + +--- + +## Progress Tracking + +- [ ] board-view.tsx (3,325 lines) +- [ ] sidebar.tsx (2,396 lines) +- [ ] electron.ts (2,356 lines) +- [ ] app-store.ts (2,174 lines) +- [ ] auto-mode-service.ts (1,232 lines) +- [ ] spec-view.tsx (1,230 lines) +- [ ] kanban-card.tsx (1,180 lines) +- [ ] analysis-view.tsx (1,134 lines) + +**Target:** All files under 500 lines, most under 300 lines + +--- + +*Generated: 2025-12-15* diff --git a/apps/app/src/components/views/board-view.tsx b/apps/app/src/components/views/board-view.tsx index 3554276f..4588cfe7 100644 --- a/apps/app/src/components/views/board-view.tsx +++ b/apps/app/src/components/views/board-view.tsx @@ -61,6 +61,7 @@ import { KanbanCard } from "./kanban-card"; import { AgentOutputModal } from "./agent-output-modal"; import { FeatureSuggestionsDialog } from "./feature-suggestions-dialog"; import { BoardBackgroundModal } from "@/components/dialogs/board-background-modal"; +import { AddFeatureDialog } from "./board-view/AddFeatureDialog"; import { Plus, RefreshCw, @@ -201,16 +202,6 @@ export function BoardView() { const [activeFeature, setActiveFeature] = useState(null); const [editingFeature, setEditingFeature] = useState(null); const [showAddDialog, setShowAddDialog] = useState(false); - const [newFeature, setNewFeature] = useState({ - category: "", - description: "", - steps: [""], - images: [] as FeatureImage[], - imagePaths: [] as DescriptionImagePath[], - skipTests: false, - model: "opus" as AgentModel, - thinkingLevel: "none" as ThinkingLevel, - }); const [isLoading, setIsLoading] = useState(true); const [isMounted, setIsMounted] = useState(false); const [showOutputModal, setShowOutputModal] = useState(false); @@ -233,15 +224,12 @@ export function BoardView() { DescriptionImagePath[] >([]); // Preview maps to persist image previews across tab switches - const [newFeaturePreviewMap, setNewFeaturePreviewMap] = - useState(() => new Map()); const [followUpPreviewMap, setFollowUpPreviewMap] = useState( () => new Map() ); const [editFeaturePreviewMap, setEditFeaturePreviewMap] = useState(() => new Map()); // Local state to temporarily show advanced options when profiles-only mode is enabled - const [showAdvancedOptions, setShowAdvancedOptions] = useState(false); const [showEditAdvancedOptions, setShowEditAdvancedOptions] = useState(false); const [showSuggestionsDialog, setShowSuggestionsDialog] = useState(false); const [suggestionsCount, setSuggestionsCount] = useState(0); @@ -251,8 +239,6 @@ export function BoardView() { const [isGeneratingSuggestions, setIsGeneratingSuggestions] = useState(false); // Search filter for Kanban cards const [searchQuery, setSearchQuery] = useState(""); - // Validation state for add feature form - const [descriptionError, setDescriptionError] = 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; @@ -589,15 +575,6 @@ export function BoardView() { [currentProject, persistedCategories] ); - // Sync skipTests default when dialog opens - useEffect(() => { - if (showAddDialog) { - setNewFeature((prev) => ({ - ...prev, - skipTests: defaultSkipTests, - })); - } - }, [showAddDialog, defaultSkipTests]); // Listen for auto mode feature completion and errors to reload features useEffect(() => { @@ -992,45 +969,24 @@ export function BoardView() { } }; - const handleAddFeature = () => { - // 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"; + const handleAddFeature = (featureData: { + category: string; + description: string; + steps: string[]; + images: FeatureImage[]; + imagePaths: DescriptionImagePath[]; + skipTests: boolean; + model: AgentModel; + thinkingLevel: ThinkingLevel; + }) => { const newFeatureData = { - category, - description: newFeature.description, - steps: newFeature.steps.filter((s) => s.trim()), + ...featureData, status: "backlog" as const, - images: newFeature.images, - imagePaths: newFeature.imagePaths, - skipTests: newFeature.skipTests, - model: selectedModel, - thinkingLevel: normalizedThinking, }; const createdFeature = addFeature(newFeatureData); persistFeatureCreate(createdFeature); // Persist the category - saveCategory(category); - setNewFeature({ - category: "", - description: "", - steps: [""], - images: [], - imagePaths: [], - skipTests: defaultSkipTests, - model: "opus", - thinkingLevel: "none", - }); - // Clear the preview map when the feature is added - setNewFeaturePreviewMap(new Map()); - setShowAddDialog(false); + saveCategory(featureData.category); }; const handleUpdateFeature = () => { @@ -1817,7 +1773,6 @@ export function BoardView() { ); - const newModelAllowsThinking = modelSupportsThinking(newFeature.model); const editModelAllowsThinking = modelSupportsThinking(editingFeature?.model); if (!currentProject) { @@ -2412,363 +2367,16 @@ export function BoardView() { {/* Add Feature Dialog */} - { - setShowAddDialog(open); - // Clear preview map, validation error, and reset advanced options when dialog closes - if (!open) { - setNewFeaturePreviewMap(new Map()); - setShowAdvancedOptions(false); - setDescriptionError(false); - } - }} - > - { - // 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 - - - -
+ onOpenChange={setShowAddDialog} + onAdd={handleAddFeature} + categorySuggestions={categorySuggestions} + defaultSkipTests={defaultSkipTests} + isMaximized={isMaximized} + showProfilesOnly={showProfilesOnly} + aiProfiles={aiProfiles} + /> {/* Edit Feature Dialog */} +> = { + 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 + + + +
+ ); +}