From 064a395c4c0f906b655affcfd5f492bd7592c309 Mon Sep 17 00:00:00 2001 From: Cody Seibert Date: Tue, 16 Dec 2025 12:54:53 -0500 Subject: [PATCH] fixing some bugs --- apps/app/src/components/views/board-view.tsx | 42 ++++++++++++++----- .../components/worktree-selector.tsx | 21 ++++++---- .../board-view/dialogs/add-feature-dialog.tsx | 9 ++-- .../dialogs/create-worktree-dialog.tsx | 9 +++- .../hooks/use-board-column-features.ts | 20 +++++++-- apps/app/src/store/app-store.ts | 11 ++--- 6 files changed, 79 insertions(+), 33 deletions(-) diff --git a/apps/app/src/components/views/board-view.tsx b/apps/app/src/components/views/board-view.tsx index ed72b3e6..1cf1949d 100644 --- a/apps/app/src/components/views/board-view.tsx +++ b/apps/app/src/components/views/board-view.tsx @@ -67,6 +67,8 @@ export function BoardView() { setSpecCreatingForProject, getCurrentWorktree, setCurrentWorktree, + getWorktrees, + setWorktrees, } = useAppStore(); const shortcuts = useKeyboardShortcutsConfig(); const { @@ -215,7 +217,7 @@ export function BoardView() { // Branch suggestions for the branch autocomplete const [branchSuggestions, setBranchSuggestions] = useState([]); - // Fetch branches when project changes + // Fetch branches when project changes or worktrees are created/modified useEffect(() => { const fetchBranches = async () => { if (!currentProject) { @@ -244,7 +246,7 @@ export function BoardView() { }; fetchBranches(); - }, [currentProject]); + }, [currentProject, worktreeRefreshKey]); // Custom collision detection that prioritizes columns over cards const collisionDetectionStrategy = useCallback( @@ -339,16 +341,21 @@ export function BoardView() { }); // Use drag and drop hook - // Get current worktree path and branch for filtering features - const currentWorktreePath = currentProject ? getCurrentWorktree(currentProject.path) : null; + // Get current worktree info (path and branch) for filtering features + const currentWorktreeInfo = currentProject ? getCurrentWorktree(currentProject.path) : null; + const currentWorktreePath = currentWorktreeInfo?.path ?? null; + const currentWorktreeBranch = currentWorktreeInfo?.branch ?? null; const worktreesByProject = useAppStore((s) => s.worktreesByProject); const worktrees = useMemo( () => (currentProject ? (worktreesByProject[currentProject.path] ?? EMPTY_WORKTREES) : EMPTY_WORKTREES), [currentProject, worktreesByProject] ); - const currentWorktreeBranch = currentWorktreePath - ? worktrees.find(w => w.path === currentWorktreePath)?.branch || null - : null; + + // Get the branch for the currently selected worktree (for defaulting new features) + // Use the branch from currentWorktreeInfo, or fall back to main worktree's branch + const selectedWorktreeBranch = currentWorktreeBranch + || worktrees.find(w => w.isMain)?.branch + || "main"; const { activeFeature, handleDragStart, handleDragEnd } = useBoardDragDrop({ features: hookFeatures, @@ -533,6 +540,7 @@ export function BoardView() { categorySuggestions={categorySuggestions} branchSuggestions={branchSuggestions} defaultSkipTests={defaultSkipTests} + defaultBranch={selectedWorktreeBranch} isMaximized={isMaximized} showProfilesOnly={showProfilesOnly} aiProfiles={aiProfiles} @@ -603,10 +611,24 @@ export function BoardView() { open={showCreateWorktreeDialog} onOpenChange={setShowCreateWorktreeDialog} projectPath={currentProject.path} - onCreated={(worktreePath) => { + onCreated={(newWorktree) => { + // Add the new worktree to the store immediately to avoid race condition + // when deriving currentWorktreeBranch for filtering + const currentWorktrees = getWorktrees(currentProject.path); + const newWorktreeInfo = { + path: newWorktree.path, + branch: newWorktree.branch, + isMain: false, + isCurrent: false, + hasWorktree: true, + }; + setWorktrees(currentProject.path, [...currentWorktrees, newWorktreeInfo]); + + // Now set the current worktree with both path and branch + setCurrentWorktree(currentProject.path, newWorktree.path, newWorktree.branch); + + // Trigger refresh to get full worktree details (hasChanges, etc.) setWorktreeRefreshKey((k) => k + 1); - // Auto-select the newly created worktree - setCurrentWorktree(currentProject.path, worktreePath); }} /> diff --git a/apps/app/src/components/views/board-view/components/worktree-selector.tsx b/apps/app/src/components/views/board-view/components/worktree-selector.tsx index d0b0765e..dd72d2b3 100644 --- a/apps/app/src/components/views/board-view/components/worktree-selector.tsx +++ b/apps/app/src/components/views/board-view/components/worktree-selector.tsx @@ -198,13 +198,15 @@ export function WorktreeSelector({ // Initialize selection to main if not set useEffect(() => { if (worktrees.length > 0 && currentWorktree === undefined) { - setCurrentWorktree(projectPath, null); // null = main worktree + const mainWorktree = worktrees.find(w => w.isMain); + const mainBranch = mainWorktree?.branch || "main"; + setCurrentWorktree(projectPath, null, mainBranch); // null = main worktree } }, [worktrees, currentWorktree, projectPath, setCurrentWorktree]); const handleSelectWorktree = async (worktree: WorktreeInfo) => { - // Simply select the worktree in the UI - setCurrentWorktree(projectPath, worktree.isMain ? null : worktree.path); + // Simply select the worktree in the UI with both path and branch + setCurrentWorktree(projectPath, worktree.isMain ? null : worktree.path, worktree.branch); }; const handleStartDevServer = async (worktree: WorktreeInfo) => { @@ -454,19 +456,20 @@ export function WorktreeSelector({ }; // The "selected" worktree is based on UI state, not git's current branch - // currentWorktree is null for main, or the worktree path for others - const selectedWorktree = currentWorktree - ? worktrees.find((w) => w.path === currentWorktree) + // currentWorktree.path is null for main, or the worktree path for others + const currentWorktreePath = currentWorktree?.path ?? null; + const selectedWorktree = currentWorktreePath + ? worktrees.find((w) => w.path === currentWorktreePath) : worktrees.find((w) => w.isMain); // Render a worktree tab with branch selector (for main) and actions dropdown const renderWorktreeTab = (worktree: WorktreeInfo) => { // Selection is based on UI state, not git's current branch - // Default to main selected if currentWorktree is null or undefined + // Default to main selected if currentWorktree is null/undefined or path is null const isSelected = worktree.isMain - ? currentWorktree === null || currentWorktree === undefined - : worktree.path === currentWorktree; + ? currentWorktree === null || currentWorktree === undefined || currentWorktree.path === null + : worktree.path === currentWorktreePath; const isRunning = hasRunningFeatures(worktree); diff --git a/apps/app/src/components/views/board-view/dialogs/add-feature-dialog.tsx b/apps/app/src/components/views/board-view/dialogs/add-feature-dialog.tsx index 1e7f1e40..2b1916da 100644 --- a/apps/app/src/components/views/board-view/dialogs/add-feature-dialog.tsx +++ b/apps/app/src/components/views/board-view/dialogs/add-feature-dialog.tsx @@ -69,6 +69,7 @@ interface AddFeatureDialogProps { categorySuggestions: string[]; branchSuggestions: string[]; defaultSkipTests: boolean; + defaultBranch?: string; isMaximized: boolean; showProfilesOnly: boolean; aiProfiles: AIProfile[]; @@ -81,6 +82,7 @@ export function AddFeatureDialog({ categorySuggestions, branchSuggestions, defaultSkipTests, + defaultBranch = "main", isMaximized, showProfilesOnly, aiProfiles, @@ -109,15 +111,16 @@ export function AddFeatureDialog({ // Get enhancement model from store const { enhancementModel } = useAppStore(); - // Sync skipTests default when dialog opens + // Sync defaults when dialog opens useEffect(() => { if (open) { setNewFeature((prev) => ({ ...prev, skipTests: defaultSkipTests, + branchName: defaultBranch, })); } - }, [open, defaultSkipTests]); + }, [open, defaultSkipTests, defaultBranch]); const handleAdd = () => { if (!newFeature.description.trim()) { @@ -155,7 +158,7 @@ export function AddFeatureDialog({ model: "opus", priority: 2, thinkingLevel: "none", - branchName: "main", + branchName: defaultBranch, }); setNewFeaturePreviewMap(new Map()); setShowAdvancedOptions(false); diff --git a/apps/app/src/components/views/board-view/dialogs/create-worktree-dialog.tsx b/apps/app/src/components/views/board-view/dialogs/create-worktree-dialog.tsx index f264b9b9..28eac236 100644 --- a/apps/app/src/components/views/board-view/dialogs/create-worktree-dialog.tsx +++ b/apps/app/src/components/views/board-view/dialogs/create-worktree-dialog.tsx @@ -16,11 +16,16 @@ import { GitBranch, Loader2 } from "lucide-react"; import { getElectronAPI } from "@/lib/electron"; import { toast } from "sonner"; +interface CreatedWorktreeInfo { + path: string; + branch: string; +} + interface CreateWorktreeDialogProps { open: boolean; onOpenChange: (open: boolean) => void; projectPath: string; - onCreated: (worktreePath: string) => void; + onCreated: (worktree: CreatedWorktreeInfo) => void; } export function CreateWorktreeDialog({ @@ -68,7 +73,7 @@ export function CreateWorktreeDialog({ : "Using existing branch", } ); - onCreated(result.worktree.path); + onCreated({ path: result.worktree.path, branch: result.worktree.branch }); onOpenChange(false); setBranchName(""); } else { diff --git a/apps/app/src/components/views/board-view/hooks/use-board-column-features.ts b/apps/app/src/components/views/board-view/hooks/use-board-column-features.ts index 000c9b1e..89015a29 100644 --- a/apps/app/src/components/views/board-view/hooks/use-board-column-features.ts +++ b/apps/app/src/components/views/board-view/hooks/use-board-column-features.ts @@ -43,7 +43,12 @@ export function useBoardColumnFeatures({ // Determine the effective worktree path and branch for filtering // If currentWorktreePath is null, we're on the main worktree const effectiveWorktreePath = currentWorktreePath || projectPath; - const effectiveBranch = currentWorktreeBranch || "main"; + // Use the branch name from the selected worktree + // If we're selecting main (currentWorktreePath is null), currentWorktreeBranch + // should contain the main branch's actual name, defaulting to "main" + // If we're selecting a non-main worktree but can't find it, currentWorktreeBranch is null + // In that case, we can't do branch-based filtering, so we'll handle it specially below + const effectiveBranch = currentWorktreeBranch; filteredFeatures.forEach((f) => { // If feature has a running agent, always show it in "in_progress" @@ -62,6 +67,11 @@ export function useBoardColumnFeatures({ } else if (f.worktreePath) { // Has worktreePath - match by path matchesWorktree = f.worktreePath === effectiveWorktreePath; + } else if (effectiveBranch === null) { + // We're selecting a non-main worktree but can't determine its branch yet + // (worktrees haven't loaded). Don't show branch-only features until we know. + // This prevents showing wrong features during loading. + matchesWorktree = false; } else { // Has branchName but no worktreePath - match by branch name matchesWorktree = featureBranch === effectiveBranch; @@ -76,10 +86,12 @@ export function useBoardColumnFeatures({ // Otherwise, use the feature's status (fallback to backlog for unknown statuses) const status = f.status as ColumnId; - // Backlog items are always visible (they have no worktree assigned) - // For other statuses, filter by worktree + // Filter all items by worktree, including backlog + // This ensures backlog items with a branch assigned only show in that branch if (status === "backlog") { - map.backlog.push(f); + if (matchesWorktree) { + map.backlog.push(f); + } } else if (map[status]) { // Only show if matches current worktree or has no worktree assigned if (matchesWorktree) { diff --git a/apps/app/src/store/app-store.ts b/apps/app/src/store/app-store.ts index 3ae93d16..faf95ad1 100644 --- a/apps/app/src/store/app-store.ts +++ b/apps/app/src/store/app-store.ts @@ -399,7 +399,8 @@ export interface AppState { useWorktrees: boolean; // Whether to use git worktree isolation for features (default: false) // User-managed Worktrees (per-project) - currentWorktreeByProject: Record; // projectPath -> worktreePath or null for main + // projectPath -> { path: worktreePath or null for main, branch: branch name } + currentWorktreeByProject: Record; worktreesByProject: Record< string, Array<{ @@ -582,7 +583,7 @@ export interface AppActions { // Worktree Settings actions setUseWorktrees: (enabled: boolean) => void; - setCurrentWorktree: (projectPath: string, worktreePath: string | null) => void; + setCurrentWorktree: (projectPath: string, worktreePath: string | null, branch: string) => void; setWorktrees: ( projectPath: string, worktrees: Array<{ @@ -593,7 +594,7 @@ export interface AppActions { changedFilesCount?: number; }> ) => void; - getCurrentWorktree: (projectPath: string) => string | null; + getCurrentWorktree: (projectPath: string) => { path: string | null; branch: string } | null; getWorktrees: (projectPath: string) => Array<{ path: string; branch: string; @@ -1344,12 +1345,12 @@ export const useAppStore = create()( // Worktree Settings actions setUseWorktrees: (enabled) => set({ useWorktrees: enabled }), - setCurrentWorktree: (projectPath, worktreePath) => { + setCurrentWorktree: (projectPath, worktreePath, branch) => { const current = get().currentWorktreeByProject; set({ currentWorktreeByProject: { ...current, - [projectPath]: worktreePath, + [projectPath]: { path: worktreePath, branch }, }, }); },