diff --git a/apps/ui/src/components/views/board-view.tsx b/apps/ui/src/components/views/board-view.tsx index 028e55e3..faa3f1d4 100644 --- a/apps/ui/src/components/views/board-view.tsx +++ b/apps/ui/src/components/views/board-view.tsx @@ -101,6 +101,8 @@ export function BoardView() { useWorktrees, enableDependencyBlocking, skipVerificationInAutoMode, + planUseSelectedWorktreeBranch, + addFeatureUseSelectedWorktreeBranch, isPrimaryWorktreeBranch, getPrimaryWorktreeBranch, setPipelineConfig, @@ -1370,6 +1372,14 @@ export function BoardView() { isMaximized={isMaximized} parentFeature={spawnParentFeature} allFeatures={hookFeatures} + // When setting is enabled and a non-main worktree is selected, pass its branch to default to 'custom' work mode + selectedNonMainWorktreeBranch={ + addFeatureUseSelectedWorktreeBranch && currentWorktreePath !== null + ? currentWorktreeBranch || undefined + : undefined + } + // When the worktree setting is disabled, force 'current' branch mode + forceCurrentBranchMode={!addFeatureUseSelectedWorktreeBranch} /> {/* Edit Feature Dialog */} @@ -1449,7 +1459,7 @@ export function BoardView() { setPendingPlanResult={setPendingBacklogPlan} isGeneratingPlan={isGeneratingPlan} setIsGeneratingPlan={setIsGeneratingPlan} - currentBranch={selectedWorktreeBranch} + currentBranch={planUseSelectedWorktreeBranch ? selectedWorktreeBranch : undefined} /> {/* Plan Approval Dialog */} diff --git a/apps/ui/src/components/views/board-view/board-header.tsx b/apps/ui/src/components/views/board-view/board-header.tsx index 5a9b7302..cfaa8a27 100644 --- a/apps/ui/src/components/views/board-view/board-header.tsx +++ b/apps/ui/src/components/views/board-view/board-header.tsx @@ -9,6 +9,8 @@ import { UsagePopover } from '@/components/usage-popover'; import { useAppStore } from '@/store/app-store'; import { useSetupStore } from '@/store/setup-store'; import { AutoModeSettingsDialog } from './dialogs/auto-mode-settings-dialog'; +import { WorktreeSettingsDialog } from './dialogs/worktree-settings-dialog'; +import { PlanSettingsDialog } from './dialogs/plan-settings-dialog'; import { getHttpApiClient } from '@/lib/http-api-client'; import { BoardSearchBar } from './board-search-bar'; import { BoardControls } from './board-controls'; @@ -55,10 +57,22 @@ export function BoardHeader({ completedCount, }: BoardHeaderProps) { const [showAutoModeSettings, setShowAutoModeSettings] = useState(false); + const [showWorktreeSettings, setShowWorktreeSettings] = useState(false); + const [showPlanSettings, setShowPlanSettings] = useState(false); const apiKeys = useAppStore((state) => state.apiKeys); const claudeAuthStatus = useSetupStore((state) => state.claudeAuthStatus); const skipVerificationInAutoMode = useAppStore((state) => state.skipVerificationInAutoMode); const setSkipVerificationInAutoMode = useAppStore((state) => state.setSkipVerificationInAutoMode); + const planUseSelectedWorktreeBranch = useAppStore((state) => state.planUseSelectedWorktreeBranch); + const setPlanUseSelectedWorktreeBranch = useAppStore( + (state) => state.setPlanUseSelectedWorktreeBranch + ); + const addFeatureUseSelectedWorktreeBranch = useAppStore( + (state) => state.addFeatureUseSelectedWorktreeBranch + ); + const setAddFeatureUseSelectedWorktreeBranch = useAppStore( + (state) => state.setAddFeatureUseSelectedWorktreeBranch + ); const codexAuthStatus = useSetupStore((state) => state.codexAuthStatus); // Worktree panel visibility (per-project) @@ -132,9 +146,25 @@ export function BoardHeader({ onCheckedChange={handleWorktreePanelToggle} data-testid="worktrees-toggle" /> + setShowWorktreeSettings(true)} + className="p-1 rounded hover:bg-accent/50 transition-colors" + title="Worktree Settings" + data-testid="worktree-settings-button" + > + + )} + {/* Worktree Settings Dialog */} + + {/* Concurrency Control - only show after mount to prevent hydration issues */} {isMounted && ( @@ -209,15 +239,33 @@ export function BoardHeader({ onSkipVerificationChange={setSkipVerificationInAutoMode} /> - - - Plan - + {/* Plan Button with Settings */} + + + + Plan + + setShowPlanSettings(true)} + className="p-1 rounded hover:bg-accent/50 transition-colors" + title="Plan Settings" + data-testid="plan-settings-button" + > + + + + + {/* Plan Settings Dialog */} + ); 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 797b64b9..736f3c40 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 @@ -56,6 +56,32 @@ import { const logger = createLogger('AddFeatureDialog'); +/** + * Determines the default work mode based on global settings and current worktree selection. + * + * Priority: + * 1. If forceCurrentBranchMode is true, always defaults to 'current' (work on current branch) + * 2. If a non-main worktree is selected in the board header, defaults to 'custom' (use that branch) + * 3. If useWorktrees global setting is enabled, defaults to 'auto' (automatic worktree creation) + * 4. Otherwise, defaults to 'current' (work on current branch without isolation) + */ +const getDefaultWorkMode = ( + useWorktrees: boolean, + selectedNonMainWorktreeBranch?: string, + forceCurrentBranchMode?: boolean +): WorkMode => { + // If force current branch mode is enabled (worktree setting is off), always use 'current' + if (forceCurrentBranchMode) { + return 'current'; + } + // If a non-main worktree is selected, default to 'custom' mode with that branch + if (selectedNonMainWorktreeBranch) { + return 'custom'; + } + // Otherwise, respect the global worktree setting + return useWorktrees ? 'auto' : 'current'; +}; + type FeatureData = { title: string; category: string; @@ -89,6 +115,16 @@ interface AddFeatureDialogProps { isMaximized: boolean; parentFeature?: Feature | null; allFeatures?: Feature[]; + /** + * When a non-main worktree is selected in the board header, this will be set to that worktree's branch. + * When set, the dialog will default to 'custom' work mode with this branch pre-filled. + */ + selectedNonMainWorktreeBranch?: string; + /** + * When true, forces the dialog to default to 'current' work mode (work on current branch). + * This is used when the "Use selected worktree branch" setting is disabled. + */ + forceCurrentBranchMode?: boolean; } /** @@ -112,6 +148,8 @@ export function AddFeatureDialog({ isMaximized, parentFeature = null, allFeatures = [], + selectedNonMainWorktreeBranch, + forceCurrentBranchMode, }: AddFeatureDialogProps) { const isSpawnMode = !!parentFeature; const [workMode, setWorkMode] = useState('current'); @@ -149,7 +187,7 @@ export function AddFeatureDialog({ const [selectedAncestorIds, setSelectedAncestorIds] = useState>(new Set()); // Get defaults from store - const { defaultPlanningMode, defaultRequirePlanApproval } = useAppStore(); + const { defaultPlanningMode, defaultRequirePlanApproval, useWorktrees } = useAppStore(); // Track previous open state to detect when dialog opens const wasOpenRef = useRef(false); @@ -161,8 +199,12 @@ export function AddFeatureDialog({ if (justOpened) { setSkipTests(defaultSkipTests); - setBranchName(defaultBranch || ''); - setWorkMode('current'); + // When a non-main worktree is selected, use its branch name for custom mode + // Otherwise, use the default branch + setBranchName(selectedNonMainWorktreeBranch || defaultBranch || ''); + setWorkMode( + getDefaultWorkMode(useWorktrees, selectedNonMainWorktreeBranch, forceCurrentBranchMode) + ); setPlanningMode(defaultPlanningMode); setRequirePlanApproval(defaultRequirePlanApproval); setModelEntry({ model: 'opus' }); @@ -186,6 +228,9 @@ export function AddFeatureDialog({ defaultBranch, defaultPlanningMode, defaultRequirePlanApproval, + useWorktrees, + selectedNonMainWorktreeBranch, + forceCurrentBranchMode, parentFeature, allFeatures, ]); @@ -270,10 +315,13 @@ export function AddFeatureDialog({ setImagePaths([]); setTextFilePaths([]); setSkipTests(defaultSkipTests); - setBranchName(''); + // When a non-main worktree is selected, use its branch name for custom mode + setBranchName(selectedNonMainWorktreeBranch || ''); setPriority(2); setModelEntry({ model: 'opus' }); - setWorkMode('current'); + setWorkMode( + getDefaultWorkMode(useWorktrees, selectedNonMainWorktreeBranch, forceCurrentBranchMode) + ); setPlanningMode(defaultPlanningMode); setRequirePlanApproval(defaultRequirePlanApproval); setPreviewMap(new Map()); diff --git a/apps/ui/src/components/views/board-view/dialogs/plan-settings-dialog.tsx b/apps/ui/src/components/views/board-view/dialogs/plan-settings-dialog.tsx new file mode 100644 index 00000000..bd42cb1a --- /dev/null +++ b/apps/ui/src/components/views/board-view/dialogs/plan-settings-dialog.tsx @@ -0,0 +1,67 @@ +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog'; +import { Label } from '@/components/ui/label'; +import { Switch } from '@/components/ui/switch'; +import { GitBranch, Settings2 } from 'lucide-react'; + +interface PlanSettingsDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + planUseSelectedWorktreeBranch: boolean; + onPlanUseSelectedWorktreeBranchChange: (value: boolean) => void; +} + +export function PlanSettingsDialog({ + open, + onOpenChange, + planUseSelectedWorktreeBranch, + onPlanUseSelectedWorktreeBranchChange, +}: PlanSettingsDialogProps) { + return ( + + + + + + Plan Settings + + + Configure how the Plan feature creates and organizes new features. + + + + + {/* Use Selected Worktree Branch Setting */} + + + + + + Use selected worktree branch + + + + + When enabled, features created via the Plan dialog will be assigned to the currently + selected worktree branch. When disabled, features will be added to the main branch. + + + + + + + ); +} diff --git a/apps/ui/src/components/views/board-view/dialogs/worktree-settings-dialog.tsx b/apps/ui/src/components/views/board-view/dialogs/worktree-settings-dialog.tsx new file mode 100644 index 00000000..da7cb134 --- /dev/null +++ b/apps/ui/src/components/views/board-view/dialogs/worktree-settings-dialog.tsx @@ -0,0 +1,67 @@ +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog'; +import { Label } from '@/components/ui/label'; +import { Switch } from '@/components/ui/switch'; +import { GitBranch, Settings2 } from 'lucide-react'; + +interface WorktreeSettingsDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + addFeatureUseSelectedWorktreeBranch: boolean; + onAddFeatureUseSelectedWorktreeBranchChange: (value: boolean) => void; +} + +export function WorktreeSettingsDialog({ + open, + onOpenChange, + addFeatureUseSelectedWorktreeBranch, + onAddFeatureUseSelectedWorktreeBranchChange, +}: WorktreeSettingsDialogProps) { + return ( + + + + + + Worktree Settings + + + Configure how worktrees affect feature creation and organization. + + + + + {/* Use Selected Worktree Branch Setting */} + + + + + + Use selected worktree branch + + + + + When enabled, the Add Feature dialog will default to custom branch mode with the + currently selected worktree branch pre-filled. + + + + + + + ); +} diff --git a/apps/ui/src/store/app-store.ts b/apps/ui/src/store/app-store.ts index 884309cc..25c431f3 100644 --- a/apps/ui/src/store/app-store.ts +++ b/apps/ui/src/store/app-store.ts @@ -530,6 +530,8 @@ export interface AppState { defaultSkipTests: boolean; // Default value for skip tests when creating new features enableDependencyBlocking: boolean; // When true, show blocked badges and warnings for features with incomplete dependencies (default: true) skipVerificationInAutoMode: boolean; // When true, auto-mode grabs features even if dependencies are not verified (only checks they're not running) + planUseSelectedWorktreeBranch: boolean; // When true, Plan dialog creates features on the currently selected worktree branch + addFeatureUseSelectedWorktreeBranch: boolean; // When true, Add Feature dialog defaults to custom mode with selected worktree branch // Worktree Settings useWorktrees: boolean; // Whether to use git worktree isolation for features (default: true) @@ -913,6 +915,8 @@ export interface AppActions { setDefaultSkipTests: (skip: boolean) => void; setEnableDependencyBlocking: (enabled: boolean) => void; setSkipVerificationInAutoMode: (enabled: boolean) => Promise; + setPlanUseSelectedWorktreeBranch: (enabled: boolean) => Promise; + setAddFeatureUseSelectedWorktreeBranch: (enabled: boolean) => Promise; // Worktree Settings actions setUseWorktrees: (enabled: boolean) => void; @@ -1191,6 +1195,8 @@ const initialState: AppState = { defaultSkipTests: true, // Default to manual verification (tests disabled) enableDependencyBlocking: true, // Default to enabled (show dependency blocking UI) skipVerificationInAutoMode: false, // Default to disabled (require dependencies to be verified) + planUseSelectedWorktreeBranch: true, // Default to enabled (Plan creates features on selected worktree branch) + addFeatureUseSelectedWorktreeBranch: false, // Default to disabled (Add Feature uses normal defaults) useWorktrees: true, // Default to enabled (git worktree isolation) currentWorktreeByProject: {}, worktreesByProject: {}, @@ -1816,6 +1822,18 @@ export const useAppStore = create()((set, get) => ({ const { syncSettingsToServer } = await import('@/hooks/use-settings-migration'); await syncSettingsToServer(); }, + setPlanUseSelectedWorktreeBranch: async (enabled) => { + set({ planUseSelectedWorktreeBranch: enabled }); + // Sync to server settings file + const { syncSettingsToServer } = await import('@/hooks/use-settings-migration'); + await syncSettingsToServer(); + }, + setAddFeatureUseSelectedWorktreeBranch: async (enabled) => { + set({ addFeatureUseSelectedWorktreeBranch: enabled }); + // Sync to server settings file + const { syncSettingsToServer } = await import('@/hooks/use-settings-migration'); + await syncSettingsToServer(); + }, // Worktree Settings actions setUseWorktrees: (enabled) => set({ useWorktrees: enabled }),
+ When enabled, features created via the Plan dialog will be assigned to the currently + selected worktree branch. When disabled, features will be added to the main branch. +
+ When enabled, the Add Feature dialog will default to custom branch mode with the + currently selected worktree branch pre-filled. +