From c8e66a866e86ad39fdce0e06f696bf02f206d67f Mon Sep 17 00:00:00 2001 From: webdevcody Date: Tue, 13 Jan 2026 09:30:15 -0500 Subject: [PATCH] feat: enhance settings view and feature defaults management - Introduced default feature model settings in the settings view, allowing users to specify the default AI model for new feature cards. - Updated navigation to include a direct link to model defaults in the settings menu. - Enhanced the Add Feature dialog to utilize the default feature model from the app store. - Implemented synchronization of the default feature model in settings migration and sync hooks. - Improved UI components to reflect changes in default settings, ensuring a cohesive user experience. --- .../board-view/dialogs/add-feature-dialog.tsx | 42 +++++++++++++++---- .../dialogs/edit-feature-dialog.tsx | 32 ++++++++++++-- .../ui/src/components/views/settings-view.tsx | 11 ++++- .../views/settings-view/config/navigation.ts | 2 +- .../feature-defaults-section.tsx | 34 ++++++++++++++- apps/ui/src/hooks/use-settings-migration.ts | 1 + apps/ui/src/hooks/use-settings-sync.ts | 2 + apps/ui/src/routes/settings.tsx | 10 +++++ apps/ui/src/store/app-store.ts | 4 ++ libs/types/src/settings.ts | 3 ++ 10 files changed, 124 insertions(+), 17 deletions(-) diff --git a/apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx b/apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx index 736f3c40..dfee5c30 100644 --- a/apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx +++ b/apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx @@ -21,7 +21,8 @@ import { FeatureTextFilePath as DescriptionTextFilePath, ImagePreviewMap, } from '@/components/ui/description-image-dropzone'; -import { Play, Cpu, FolderKanban } from 'lucide-react'; +import { Play, Cpu, FolderKanban, Settings2 } from 'lucide-react'; +import { useNavigate } from '@tanstack/react-router'; import { toast } from 'sonner'; import { cn } from '@/lib/utils'; import { modelSupportsThinking } from '@/lib/utils'; @@ -33,7 +34,7 @@ import { PlanningMode, Feature, } from '@/store/app-store'; -import type { ReasoningEffort, PhaseModelEntry } from '@automaker/types'; +import type { ReasoningEffort, PhaseModelEntry, AgentModel } from '@automaker/types'; import { supportsReasoningEffort, isClaudeModel } from '@automaker/types'; import { TestingTabContent, @@ -152,6 +153,7 @@ export function AddFeatureDialog({ forceCurrentBranchMode, }: AddFeatureDialogProps) { const isSpawnMode = !!parentFeature; + const navigate = useNavigate(); const [workMode, setWorkMode] = useState('current'); // Form state @@ -187,7 +189,8 @@ export function AddFeatureDialog({ const [selectedAncestorIds, setSelectedAncestorIds] = useState>(new Set()); // Get defaults from store - const { defaultPlanningMode, defaultRequirePlanApproval, useWorktrees } = useAppStore(); + const { defaultPlanningMode, defaultRequirePlanApproval, useWorktrees, defaultFeatureModel } = + useAppStore(); // Track previous open state to detect when dialog opens const wasOpenRef = useRef(false); @@ -207,7 +210,7 @@ export function AddFeatureDialog({ ); setPlanningMode(defaultPlanningMode); setRequirePlanApproval(defaultRequirePlanApproval); - setModelEntry({ model: 'opus' }); + setModelEntry(defaultFeatureModel); // Initialize description history (empty for new feature) setDescriptionHistory([]); @@ -228,6 +231,7 @@ export function AddFeatureDialog({ defaultBranch, defaultPlanningMode, defaultRequirePlanApproval, + defaultFeatureModel, useWorktrees, selectedNonMainWorktreeBranch, forceCurrentBranchMode, @@ -318,7 +322,7 @@ export function AddFeatureDialog({ // When a non-main worktree is selected, use its branch name for custom mode setBranchName(selectedNonMainWorktreeBranch || ''); setPriority(2); - setModelEntry({ model: 'opus' }); + setModelEntry(defaultFeatureModel); setWorkMode( getDefaultWorkMode(useWorktrees, selectedNonMainWorktreeBranch, forceCurrentBranchMode) ); @@ -473,9 +477,31 @@ export function AddFeatureDialog({ {/* AI & Execution Section */}
-
- - AI & Execution +
+
+ + AI & Execution +
+ + + + + + +

Change default model and planning settings for new features

+
+
+
diff --git a/apps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsx b/apps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsx index 9912201d..ae7d655b 100644 --- a/apps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsx +++ b/apps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsx @@ -21,7 +21,8 @@ import { FeatureTextFilePath as DescriptionTextFilePath, ImagePreviewMap, } from '@/components/ui/description-image-dropzone'; -import { GitBranch, Cpu, FolderKanban } from 'lucide-react'; +import { GitBranch, Cpu, FolderKanban, Settings2 } from 'lucide-react'; +import { useNavigate } from '@tanstack/react-router'; import { toast } from 'sonner'; import { cn, modelSupportsThinking } from '@/lib/utils'; import { Feature, ModelAlias, ThinkingLevel, useAppStore, PlanningMode } from '@/store/app-store'; @@ -86,6 +87,7 @@ export function EditFeatureDialog({ isMaximized, allFeatures, }: EditFeatureDialogProps) { + const navigate = useNavigate(); const [editingFeature, setEditingFeature] = useState(feature); // Derive initial workMode from feature's branchName const [workMode, setWorkMode] = useState(() => { @@ -363,9 +365,31 @@ export function EditFeatureDialog({ {/* AI & Execution Section */}
-
- - AI & Execution +
+
+ + AI & Execution +
+ + + + + + +

Change default model and planning settings for new features

+
+
+
diff --git a/apps/ui/src/components/views/settings-view.tsx b/apps/ui/src/components/views/settings-view.tsx index aa6a8a84..2655e8a5 100644 --- a/apps/ui/src/components/views/settings-view.tsx +++ b/apps/ui/src/components/views/settings-view.tsx @@ -1,6 +1,6 @@ import { useState } from 'react'; +import { useSearch } from '@tanstack/react-router'; import { useAppStore } from '@/store/app-store'; -import { useSetupStore } from '@/store/setup-store'; import { useSettingsView, type SettingsViewId } from './settings-view/hooks'; import { NAV_ITEMS } from './settings-view/config/navigation'; @@ -51,6 +51,8 @@ export function SettingsView() { setDefaultPlanningMode, defaultRequirePlanApproval, setDefaultRequirePlanApproval, + defaultFeatureModel, + setDefaultFeatureModel, autoLoadClaudeMd, setAutoLoadClaudeMd, promptCustomization, @@ -86,8 +88,11 @@ export function SettingsView() { } }; + // Get initial view from URL search params + const { view: initialView } = useSearch({ from: '/settings' }); + // Use settings view navigation hook - const { activeView, navigateTo } = useSettingsView(); + const { activeView, navigateTo } = useSettingsView({ initialView }); // Handle navigation - if navigating to 'providers', default to 'claude-provider' const handleNavigate = (viewId: SettingsViewId) => { @@ -152,11 +157,13 @@ export function SettingsView() { skipVerificationInAutoMode={skipVerificationInAutoMode} defaultPlanningMode={defaultPlanningMode} defaultRequirePlanApproval={defaultRequirePlanApproval} + defaultFeatureModel={defaultFeatureModel} onDefaultSkipTestsChange={setDefaultSkipTests} onEnableDependencyBlockingChange={setEnableDependencyBlocking} onSkipVerificationInAutoModeChange={setSkipVerificationInAutoMode} onDefaultPlanningModeChange={setDefaultPlanningMode} onDefaultRequirePlanApprovalChange={setDefaultRequirePlanApproval} + onDefaultFeatureModelChange={setDefaultFeatureModel} /> ); case 'worktrees': diff --git a/apps/ui/src/components/views/settings-view/config/navigation.ts b/apps/ui/src/components/views/settings-view/config/navigation.ts index f63d0494..6a810973 100644 --- a/apps/ui/src/components/views/settings-view/config/navigation.ts +++ b/apps/ui/src/components/views/settings-view/config/navigation.ts @@ -37,8 +37,8 @@ export const GLOBAL_NAV_GROUPS: NavigationGroup[] = [ { label: 'Model & Prompts', items: [ - { id: 'model-defaults', label: 'Model Defaults', icon: Workflow }, { id: 'defaults', label: 'Feature Defaults', icon: FlaskConical }, + { id: 'model-defaults', label: 'Model Defaults', icon: Workflow }, { id: 'worktrees', label: 'Worktrees', icon: GitBranch }, { id: 'prompts', label: 'Prompt Customization', icon: MessageSquareText }, { id: 'api-keys', label: 'API Keys', icon: Key }, diff --git a/apps/ui/src/components/views/settings-view/feature-defaults/feature-defaults-section.tsx b/apps/ui/src/components/views/settings-view/feature-defaults/feature-defaults-section.tsx index c3b4e9ae..956d28fa 100644 --- a/apps/ui/src/components/views/settings-view/feature-defaults/feature-defaults-section.tsx +++ b/apps/ui/src/components/views/settings-view/feature-defaults/feature-defaults-section.tsx @@ -10,6 +10,7 @@ import { ScrollText, ShieldCheck, FastForward, + Cpu, } from 'lucide-react'; import { cn } from '@/lib/utils'; import { @@ -19,6 +20,8 @@ import { SelectTrigger, SelectValue, } from '@/components/ui/select'; +import type { PhaseModelEntry } from '@automaker/types'; +import { PhaseModelSelector } from '../model-defaults/phase-model-selector'; type PlanningMode = 'skip' | 'lite' | 'spec' | 'full'; @@ -28,11 +31,13 @@ interface FeatureDefaultsSectionProps { skipVerificationInAutoMode: boolean; defaultPlanningMode: PlanningMode; defaultRequirePlanApproval: boolean; + defaultFeatureModel: PhaseModelEntry; onDefaultSkipTestsChange: (value: boolean) => void; onEnableDependencyBlockingChange: (value: boolean) => void; onSkipVerificationInAutoModeChange: (value: boolean) => void; onDefaultPlanningModeChange: (value: PlanningMode) => void; onDefaultRequirePlanApprovalChange: (value: boolean) => void; + onDefaultFeatureModelChange: (value: PhaseModelEntry) => void; } export function FeatureDefaultsSection({ @@ -41,11 +46,13 @@ export function FeatureDefaultsSection({ skipVerificationInAutoMode, defaultPlanningMode, defaultRequirePlanApproval, + defaultFeatureModel, onDefaultSkipTestsChange, onEnableDependencyBlockingChange, onSkipVerificationInAutoModeChange, onDefaultPlanningModeChange, onDefaultRequirePlanApprovalChange, + onDefaultFeatureModelChange, }: FeatureDefaultsSectionProps) { return (
+ {/* Default Feature Model Setting */} +
+
+ +
+
+
+ + +
+

+ The default AI model and thinking level used when creating new feature cards. +

+
+
+ + {/* Separator */} +
+ {/* Planning Mode Default */}
-
)} {/* Separator */} - {defaultPlanningMode === 'skip' &&
} +
{/* Automated Testing Setting */}
diff --git a/apps/ui/src/hooks/use-settings-migration.ts b/apps/ui/src/hooks/use-settings-migration.ts index bb86c10c..e4375cbf 100644 --- a/apps/ui/src/hooks/use-settings-migration.ts +++ b/apps/ui/src/hooks/use-settings-migration.ts @@ -562,6 +562,7 @@ export function hydrateStoreFromSettings(settings: GlobalSettings): void { useWorktrees: settings.useWorktrees ?? true, defaultPlanningMode: settings.defaultPlanningMode ?? 'skip', defaultRequirePlanApproval: settings.defaultRequirePlanApproval ?? false, + defaultFeatureModel: settings.defaultFeatureModel ?? { model: 'opus' }, muteDoneSound: settings.muteDoneSound ?? false, enhancementModel: settings.enhancementModel ?? 'sonnet', validationModel: settings.validationModel ?? 'opus', diff --git a/apps/ui/src/hooks/use-settings-sync.ts b/apps/ui/src/hooks/use-settings-sync.ts index 41ef6693..1fb9dbd0 100644 --- a/apps/ui/src/hooks/use-settings-sync.ts +++ b/apps/ui/src/hooks/use-settings-sync.ts @@ -42,6 +42,7 @@ const SETTINGS_FIELDS_TO_SYNC = [ 'useWorktrees', 'defaultPlanningMode', 'defaultRequirePlanApproval', + 'defaultFeatureModel', 'muteDoneSound', 'enhancementModel', 'validationModel', @@ -466,6 +467,7 @@ export async function refreshSettingsFromServer(): Promise { useWorktrees: serverSettings.useWorktrees, defaultPlanningMode: serverSettings.defaultPlanningMode, defaultRequirePlanApproval: serverSettings.defaultRequirePlanApproval, + defaultFeatureModel: serverSettings.defaultFeatureModel ?? { model: 'opus' }, muteDoneSound: serverSettings.muteDoneSound, enhancementModel: serverSettings.enhancementModel, validationModel: serverSettings.validationModel, diff --git a/apps/ui/src/routes/settings.tsx b/apps/ui/src/routes/settings.tsx index 74170d94..c509e93a 100644 --- a/apps/ui/src/routes/settings.tsx +++ b/apps/ui/src/routes/settings.tsx @@ -1,6 +1,16 @@ import { createFileRoute } from '@tanstack/react-router'; import { SettingsView } from '@/components/views/settings-view'; +import type { SettingsViewId } from '@/components/views/settings-view/hooks'; + +interface SettingsSearchParams { + view?: SettingsViewId; +} export const Route = createFileRoute('/settings')({ component: SettingsView, + validateSearch: (search: Record): SettingsSearchParams => { + return { + view: search.view as SettingsViewId | undefined, + }; + }, }); diff --git a/apps/ui/src/store/app-store.ts b/apps/ui/src/store/app-store.ts index 36aec5ed..280ba7c1 100644 --- a/apps/ui/src/store/app-store.ts +++ b/apps/ui/src/store/app-store.ts @@ -657,6 +657,7 @@ export interface AppState { defaultPlanningMode: PlanningMode; defaultRequirePlanApproval: boolean; + defaultFeatureModel: PhaseModelEntry; // Plan Approval State // When a plan requires user approval, this holds the pending approval details @@ -1104,6 +1105,7 @@ export interface AppActions { setDefaultPlanningMode: (mode: PlanningMode) => void; setDefaultRequirePlanApproval: (require: boolean) => void; + setDefaultFeatureModel: (entry: PhaseModelEntry) => void; // Plan Approval actions setPendingPlanApproval: ( @@ -1277,6 +1279,7 @@ const initialState: AppState = { specCreatingForProject: null, defaultPlanningMode: 'skip' as PlanningMode, defaultRequirePlanApproval: false, + defaultFeatureModel: { model: 'opus' } as PhaseModelEntry, pendingPlanApproval: null, claudeRefreshInterval: 60, claudeUsage: null, @@ -3093,6 +3096,7 @@ export const useAppStore = create()((set, get) => ({ setDefaultPlanningMode: (mode) => set({ defaultPlanningMode: mode }), setDefaultRequirePlanApproval: (require) => set({ defaultRequirePlanApproval: require }), + setDefaultFeatureModel: (entry) => set({ defaultFeatureModel: entry }), // Plan Approval actions setPendingPlanApproval: (approval) => set({ pendingPlanApproval: approval }), diff --git a/libs/types/src/settings.ts b/libs/types/src/settings.ts index 38402c24..a4efa469 100644 --- a/libs/types/src/settings.ts +++ b/libs/types/src/settings.ts @@ -375,6 +375,8 @@ export interface GlobalSettings { defaultPlanningMode: PlanningMode; /** Default: require manual approval before generating */ defaultRequirePlanApproval: boolean; + /** Default model and thinking level for new feature cards */ + defaultFeatureModel: PhaseModelEntry; // Audio Preferences /** Mute completion notification sound */ @@ -698,6 +700,7 @@ export const DEFAULT_GLOBAL_SETTINGS: GlobalSettings = { useWorktrees: true, defaultPlanningMode: 'skip', defaultRequirePlanApproval: false, + defaultFeatureModel: { model: 'opus' }, muteDoneSound: false, phaseModels: DEFAULT_PHASE_MODELS, enhancementModel: 'sonnet',