diff --git a/apps/server/src/routes/worktree/common.ts b/apps/server/src/routes/worktree/common.ts index bc6e59ba..6527ab77 100644 --- a/apps/server/src/routes/worktree/common.ts +++ b/apps/server/src/routes/worktree/common.ts @@ -111,6 +111,19 @@ export async function isGitRepo(repoPath: string): Promise { } } +/** + * Check if a git repository has at least one commit (i.e., HEAD exists) + * Returns false for freshly initialized repos with no commits + */ +export async function hasCommits(repoPath: string): Promise { + try { + await execAsync('git rev-parse --verify HEAD', { cwd: repoPath }); + return true; + } catch { + return false; + } +} + /** * Check if an error is ENOENT (file/path not found or spawn failed) * These are expected in test environments with mock paths diff --git a/apps/server/src/routes/worktree/routes/checkout-branch.ts b/apps/server/src/routes/worktree/routes/checkout-branch.ts index ef8ddc47..dc28a19b 100644 --- a/apps/server/src/routes/worktree/routes/checkout-branch.ts +++ b/apps/server/src/routes/worktree/routes/checkout-branch.ts @@ -5,7 +5,7 @@ import type { Request, Response } from 'express'; import { exec } from 'child_process'; import { promisify } from 'util'; -import { getErrorMessage, logError } from '../common.js'; +import { getErrorMessage, logError, isGitRepo, hasCommits } from '../common.js'; const execAsync = promisify(exec); @@ -43,6 +43,26 @@ export function createCheckoutBranchHandler() { return; } + // Check if path is a git repository + if (!(await isGitRepo(worktreePath))) { + res.status(400).json({ + success: false, + error: 'Not a git repository', + code: 'NOT_GIT_REPO', + }); + return; + } + + // Check if repository has at least one commit + if (!(await hasCommits(worktreePath))) { + res.status(400).json({ + success: false, + error: 'Repository has no commits yet', + code: 'NO_COMMITS', + }); + return; + } + // Get current branch for reference const { stdout: currentBranchOutput } = await execAsync('git rev-parse --abbrev-ref HEAD', { cwd: worktreePath, diff --git a/apps/server/src/routes/worktree/routes/commit.ts b/apps/server/src/routes/worktree/routes/commit.ts index 6cdc39c1..5080c905 100644 --- a/apps/server/src/routes/worktree/routes/commit.ts +++ b/apps/server/src/routes/worktree/routes/commit.ts @@ -5,7 +5,7 @@ import type { Request, Response } from 'express'; import { exec } from 'child_process'; import { promisify } from 'util'; -import { getErrorMessage, logError } from '../common.js'; +import { getErrorMessage, logError, isGitRepo } from '../common.js'; const execAsync = promisify(exec); @@ -25,6 +25,16 @@ export function createCommitHandler() { return; } + // Check if path is a git repository + if (!(await isGitRepo(worktreePath))) { + res.status(400).json({ + success: false, + error: 'Not a git repository', + code: 'NOT_GIT_REPO', + }); + return; + } + // Check for uncommitted changes const { stdout: status } = await execAsync('git status --porcelain', { cwd: worktreePath, diff --git a/apps/server/src/routes/worktree/routes/list-branches.ts b/apps/server/src/routes/worktree/routes/list-branches.ts index 5fab4aff..cb0022fe 100644 --- a/apps/server/src/routes/worktree/routes/list-branches.ts +++ b/apps/server/src/routes/worktree/routes/list-branches.ts @@ -5,7 +5,7 @@ import type { Request, Response } from 'express'; import { exec } from 'child_process'; import { promisify } from 'util'; -import { getErrorMessage, logWorktreeError } from '../common.js'; +import { getErrorMessage, logWorktreeError, isGitRepo, hasCommits } from '../common.js'; const execAsync = promisify(exec); @@ -30,6 +30,26 @@ export function createListBranchesHandler() { return; } + // Check if the path is a git repository before running git commands + if (!(await isGitRepo(worktreePath))) { + res.status(400).json({ + success: false, + error: 'Not a git repository', + code: 'NOT_GIT_REPO', + }); + return; + } + + // Check if the repository has any commits (freshly init'd repos have no HEAD) + if (!(await hasCommits(worktreePath))) { + res.status(400).json({ + success: false, + error: 'Repository has no commits yet', + code: 'NO_COMMITS', + }); + return; + } + // Get current branch const { stdout: currentBranchOutput } = await execAsync('git rev-parse --abbrev-ref HEAD', { cwd: worktreePath, diff --git a/apps/server/src/routes/worktree/routes/merge.ts b/apps/server/src/routes/worktree/routes/merge.ts index 40ac8dd4..c7baedd0 100644 --- a/apps/server/src/routes/worktree/routes/merge.ts +++ b/apps/server/src/routes/worktree/routes/merge.ts @@ -6,7 +6,7 @@ import type { Request, Response } from 'express'; import { exec } from 'child_process'; import { promisify } from 'util'; import path from 'path'; -import { getErrorMessage, logError } from '../common.js'; +import { getErrorMessage, logError, isGitRepo, hasCommits } from '../common.js'; const execAsync = promisify(exec); @@ -27,6 +27,26 @@ export function createMergeHandler() { return; } + // Check if path is a git repository + if (!(await isGitRepo(projectPath))) { + res.status(400).json({ + success: false, + error: 'Not a git repository', + code: 'NOT_GIT_REPO', + }); + return; + } + + // Check if repository has at least one commit + if (!(await hasCommits(projectPath))) { + res.status(400).json({ + success: false, + error: 'Repository has no commits yet', + code: 'NO_COMMITS', + }); + return; + } + const branchName = `feature/${featureId}`; // Git worktrees are stored in project directory const worktreePath = path.join(projectPath, '.worktrees', featureId); diff --git a/apps/server/src/routes/worktree/routes/pull.ts b/apps/server/src/routes/worktree/routes/pull.ts index 4384e207..e99787af 100644 --- a/apps/server/src/routes/worktree/routes/pull.ts +++ b/apps/server/src/routes/worktree/routes/pull.ts @@ -5,7 +5,7 @@ import type { Request, Response } from 'express'; import { exec } from 'child_process'; import { promisify } from 'util'; -import { getErrorMessage, logError } from '../common.js'; +import { getErrorMessage, logError, isGitRepo, hasCommits } from '../common.js'; const execAsync = promisify(exec); @@ -24,6 +24,26 @@ export function createPullHandler() { return; } + // Check if path is a git repository + if (!(await isGitRepo(worktreePath))) { + res.status(400).json({ + success: false, + error: 'Not a git repository', + code: 'NOT_GIT_REPO', + }); + return; + } + + // Check if repository has at least one commit + if (!(await hasCommits(worktreePath))) { + res.status(400).json({ + success: false, + error: 'Repository has no commits yet', + code: 'NO_COMMITS', + }); + return; + } + // Get current branch name const { stdout: branchOutput } = await execAsync('git rev-parse --abbrev-ref HEAD', { cwd: worktreePath, diff --git a/apps/server/src/routes/worktree/routes/push.ts b/apps/server/src/routes/worktree/routes/push.ts index c0337f43..64acc526 100644 --- a/apps/server/src/routes/worktree/routes/push.ts +++ b/apps/server/src/routes/worktree/routes/push.ts @@ -5,7 +5,7 @@ import type { Request, Response } from 'express'; import { exec } from 'child_process'; import { promisify } from 'util'; -import { getErrorMessage, logError } from '../common.js'; +import { getErrorMessage, logError, isGitRepo, hasCommits } from '../common.js'; const execAsync = promisify(exec); @@ -25,6 +25,26 @@ export function createPushHandler() { return; } + // Check if path is a git repository + if (!(await isGitRepo(worktreePath))) { + res.status(400).json({ + success: false, + error: 'Not a git repository', + code: 'NOT_GIT_REPO', + }); + return; + } + + // Check if repository has at least one commit + if (!(await hasCommits(worktreePath))) { + res.status(400).json({ + success: false, + error: 'Repository has no commits yet', + code: 'NO_COMMITS', + }); + return; + } + // Get branch name const { stdout: branchOutput } = await execAsync('git rev-parse --abbrev-ref HEAD', { cwd: worktreePath, diff --git a/apps/server/src/routes/worktree/routes/switch-branch.ts b/apps/server/src/routes/worktree/routes/switch-branch.ts index 3df7a3f2..b6683198 100644 --- a/apps/server/src/routes/worktree/routes/switch-branch.ts +++ b/apps/server/src/routes/worktree/routes/switch-branch.ts @@ -9,7 +9,7 @@ import type { Request, Response } from 'express'; import { exec } from 'child_process'; import { promisify } from 'util'; -import { getErrorMessage, logError } from '../common.js'; +import { getErrorMessage, logError, isGitRepo, hasCommits } from '../common.js'; const execAsync = promisify(exec); @@ -83,6 +83,26 @@ export function createSwitchBranchHandler() { return; } + // Check if path is a git repository + if (!(await isGitRepo(worktreePath))) { + res.status(400).json({ + success: false, + error: 'Not a git repository', + code: 'NOT_GIT_REPO', + }); + return; + } + + // Check if repository has at least one commit + if (!(await hasCommits(worktreePath))) { + res.status(400).json({ + success: false, + error: 'Repository has no commits yet', + code: 'NO_COMMITS', + }); + return; + } + // Get current branch const { stdout: currentBranchOutput } = await execAsync('git rev-parse --abbrev-ref HEAD', { cwd: worktreePath, diff --git a/apps/ui/src/components/views/board-view/worktree-panel/components/worktree-actions-dropdown.tsx b/apps/ui/src/components/views/board-view/worktree-panel/components/worktree-actions-dropdown.tsx index d5710f5e..f0b93e33 100644 --- a/apps/ui/src/components/views/board-view/worktree-panel/components/worktree-actions-dropdown.tsx +++ b/apps/ui/src/components/views/board-view/worktree-panel/components/worktree-actions-dropdown.tsx @@ -7,6 +7,7 @@ import { DropdownMenuTrigger, DropdownMenuLabel, } from '@/components/ui/dropdown-menu'; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; import { Trash2, MoreHorizontal, @@ -20,9 +21,10 @@ import { Globe, MessageSquare, GitMerge, + AlertCircle, } from 'lucide-react'; import { cn } from '@/lib/utils'; -import type { WorktreeInfo, DevServerInfo, PRInfo } from '../types'; +import type { WorktreeInfo, DevServerInfo, PRInfo, GitRepoStatus } from '../types'; interface WorktreeActionsDropdownProps { worktree: WorktreeInfo; @@ -35,6 +37,7 @@ interface WorktreeActionsDropdownProps { isStartingDevServer: boolean; isDevServerRunning: boolean; devServerInfo?: DevServerInfo; + gitRepoStatus: GitRepoStatus; onOpenChange: (open: boolean) => void; onPull: (worktree: WorktreeInfo) => void; onPush: (worktree: WorktreeInfo) => void; @@ -60,6 +63,7 @@ export function WorktreeActionsDropdown({ isStartingDevServer, isDevServerRunning, devServerInfo, + gitRepoStatus, onOpenChange, onPull, onPush, @@ -76,6 +80,14 @@ export function WorktreeActionsDropdown({ // Check if there's a PR associated with this worktree from stored metadata const hasPR = !!worktree.pr; + // Check git operations availability + const canPerformGitOps = gitRepoStatus.isGitRepo && gitRepoStatus.hasCommits; + const gitOpsDisabledReason = !gitRepoStatus.isGitRepo + ? 'Not a git repository' + : !gitRepoStatus.hasCommits + ? 'Repository has no commits yet' + : null; + return ( @@ -92,6 +104,16 @@ export function WorktreeActionsDropdown({ + {/* Warning label when git operations are not available */} + {!canPerformGitOps && ( + <> + + + {gitOpsDisabledReason} + + + + )} {isDevServerRunning ? ( <> @@ -124,36 +146,92 @@ export function WorktreeActionsDropdown({ )} - onPull(worktree)} disabled={isPulling} className="text-xs"> - - {isPulling ? 'Pulling...' : 'Pull'} - {behindCount > 0 && ( - - {behindCount} behind - - )} - - onPush(worktree)} - disabled={isPushing || aheadCount === 0} - className="text-xs" - > - - {isPushing ? 'Pushing...' : 'Push'} - {aheadCount > 0 && ( - - {aheadCount} ahead - - )} - + + + +
+ canPerformGitOps && onPull(worktree)} + disabled={isPulling || !canPerformGitOps} + className={cn('text-xs', !canPerformGitOps && 'opacity-50 cursor-not-allowed')} + > + + {isPulling ? 'Pulling...' : 'Pull'} + {!canPerformGitOps && ( + + )} + {canPerformGitOps && behindCount > 0 && ( + + {behindCount} behind + + )} + +
+
+ {gitOpsDisabledReason && ( + +

{gitOpsDisabledReason}

+
+ )} +
+
+ + + +
+ canPerformGitOps && onPush(worktree)} + disabled={isPushing || aheadCount === 0 || !canPerformGitOps} + className={cn('text-xs', !canPerformGitOps && 'opacity-50 cursor-not-allowed')} + > + + {isPushing ? 'Pushing...' : 'Push'} + {!canPerformGitOps && ( + + )} + {canPerformGitOps && aheadCount > 0 && ( + + {aheadCount} ahead + + )} + +
+
+ {gitOpsDisabledReason && ( + +

{gitOpsDisabledReason}

+
+ )} +
+
{!worktree.isMain && ( - onResolveConflicts(worktree)} - className="text-xs text-purple-500 focus:text-purple-600" - > - - Pull & Resolve Conflicts - + + + +
+ canPerformGitOps && onResolveConflicts(worktree)} + disabled={!canPerformGitOps} + className={cn( + 'text-xs text-purple-500 focus:text-purple-600', + !canPerformGitOps && 'opacity-50 cursor-not-allowed' + )} + > + + Pull & Resolve Conflicts + {!canPerformGitOps && ( + + )} + +
+
+ {gitOpsDisabledReason && ( + +

{gitOpsDisabledReason}

+
+ )} +
+
)} onOpenInEditor(worktree)} className="text-xs"> @@ -162,17 +240,60 @@ export function WorktreeActionsDropdown({ {worktree.hasChanges && ( - onCommit(worktree)} className="text-xs"> - - Commit Changes - + + + +
+ gitRepoStatus.isGitRepo && onCommit(worktree)} + disabled={!gitRepoStatus.isGitRepo} + className={cn( + 'text-xs', + !gitRepoStatus.isGitRepo && 'opacity-50 cursor-not-allowed' + )} + > + + Commit Changes + {!gitRepoStatus.isGitRepo && ( + + )} + +
+
+ {!gitRepoStatus.isGitRepo && ( + +

Not a git repository

+
+ )} +
+
)} {/* Show PR option for non-primary worktrees, or primary worktree with changes */} {(!worktree.isMain || worktree.hasChanges) && !hasPR && ( - onCreatePR(worktree)} className="text-xs"> - - Create Pull Request - + + + +
+ canPerformGitOps && onCreatePR(worktree)} + disabled={!canPerformGitOps} + className={cn('text-xs', !canPerformGitOps && 'opacity-50 cursor-not-allowed')} + > + + Create Pull Request + {!canPerformGitOps && ( + + )} + +
+
+ {gitOpsDisabledReason && ( + +

{gitOpsDisabledReason}

+
+ )} +
+
)} {/* Show PR info and Address Comments button if PR exists */} {!worktree.isMain && hasPR && worktree.pr && ( diff --git a/apps/ui/src/components/views/board-view/worktree-panel/components/worktree-tab.tsx b/apps/ui/src/components/views/board-view/worktree-panel/components/worktree-tab.tsx index 76c95db1..9e357231 100644 --- a/apps/ui/src/components/views/board-view/worktree-panel/components/worktree-tab.tsx +++ b/apps/ui/src/components/views/board-view/worktree-panel/components/worktree-tab.tsx @@ -2,7 +2,7 @@ import { Button } from '@/components/ui/button'; import { RefreshCw, Globe, Loader2, CircleDot, GitPullRequest } from 'lucide-react'; import { cn } from '@/lib/utils'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; -import type { WorktreeInfo, BranchInfo, DevServerInfo, PRInfo } from '../types'; +import type { WorktreeInfo, BranchInfo, DevServerInfo, PRInfo, GitRepoStatus } from '../types'; import { BranchSwitchDropdown } from './branch-switch-dropdown'; import { WorktreeActionsDropdown } from './worktree-actions-dropdown'; @@ -27,6 +27,7 @@ interface WorktreeTabProps { isStartingDevServer: boolean; aheadCount: number; behindCount: number; + gitRepoStatus: GitRepoStatus; onSelectWorktree: (worktree: WorktreeInfo) => void; onBranchDropdownOpenChange: (open: boolean) => void; onActionsDropdownOpenChange: (open: boolean) => void; @@ -67,6 +68,7 @@ export function WorktreeTab({ isStartingDevServer, aheadCount, behindCount, + gitRepoStatus, onSelectWorktree, onBranchDropdownOpenChange, onActionsDropdownOpenChange, @@ -320,6 +322,7 @@ export function WorktreeTab({ isStartingDevServer={isStartingDevServer} isDevServerRunning={isDevServerRunning} devServerInfo={devServerInfo} + gitRepoStatus={gitRepoStatus} onOpenChange={onActionsDropdownOpenChange} onPull={onPull} onPush={onPush} diff --git a/apps/ui/src/components/views/board-view/worktree-panel/hooks/use-branches.ts b/apps/ui/src/components/views/board-view/worktree-panel/hooks/use-branches.ts index 69a78ec0..15c266d5 100644 --- a/apps/ui/src/components/views/board-view/worktree-panel/hooks/use-branches.ts +++ b/apps/ui/src/components/views/board-view/worktree-panel/hooks/use-branches.ts @@ -1,6 +1,6 @@ import { useState, useCallback } from 'react'; import { getElectronAPI } from '@/lib/electron'; -import type { BranchInfo } from '../types'; +import type { BranchInfo, GitRepoStatus } from '../types'; export function useBranches() { const [branches, setBranches] = useState([]); @@ -8,6 +8,10 @@ export function useBranches() { const [behindCount, setBehindCount] = useState(0); const [isLoadingBranches, setIsLoadingBranches] = useState(false); const [branchFilter, setBranchFilter] = useState(''); + const [gitRepoStatus, setGitRepoStatus] = useState({ + isGitRepo: true, + hasCommits: true, + }); const fetchBranches = useCallback(async (worktreePath: string) => { setIsLoadingBranches(true); @@ -22,9 +26,31 @@ export function useBranches() { setBranches(result.result.branches); setAheadCount(result.result.aheadCount || 0); setBehindCount(result.result.behindCount || 0); + setGitRepoStatus({ isGitRepo: true, hasCommits: true }); + } else if (result.code === 'NOT_GIT_REPO') { + // Not a git repository - clear branches silently without logging an error + setBranches([]); + setAheadCount(0); + setBehindCount(0); + setGitRepoStatus({ isGitRepo: false, hasCommits: false }); + } else if (result.code === 'NO_COMMITS') { + // Git repo but no commits yet - clear branches silently without logging an error + setBranches([]); + setAheadCount(0); + setBehindCount(0); + setGitRepoStatus({ isGitRepo: true, hasCommits: false }); + } else if (!result.success) { + // Other errors - log them + console.warn('Failed to fetch branches:', result.error); + setBranches([]); + setAheadCount(0); + setBehindCount(0); } } catch (error) { console.error('Failed to fetch branches:', error); + setBranches([]); + setAheadCount(0); + setBehindCount(0); } finally { setIsLoadingBranches(false); } @@ -48,5 +74,6 @@ export function useBranches() { setBranchFilter, resetBranchFilter, fetchBranches, + gitRepoStatus, }; } diff --git a/apps/ui/src/components/views/board-view/worktree-panel/hooks/use-worktree-actions.ts b/apps/ui/src/components/views/board-view/worktree-panel/hooks/use-worktree-actions.ts index 381975b1..09f39068 100644 --- a/apps/ui/src/components/views/board-view/worktree-panel/hooks/use-worktree-actions.ts +++ b/apps/ui/src/components/views/board-view/worktree-panel/hooks/use-worktree-actions.ts @@ -3,6 +3,15 @@ import { getElectronAPI } from '@/lib/electron'; import { toast } from 'sonner'; import type { WorktreeInfo } from '../types'; +// Error codes that need special user-friendly handling +const GIT_STATUS_ERROR_CODES = ['NOT_GIT_REPO', 'NO_COMMITS'] as const; + +// User-friendly messages for git status errors +const GIT_STATUS_ERROR_MESSAGES: Record = { + NOT_GIT_REPO: 'This directory is not a git repository', + NO_COMMITS: 'Repository has no commits yet. Create an initial commit first.', +}; + interface UseWorktreeActionsOptions { fetchWorktrees: () => Promise | undefined>; fetchBranches: (worktreePath: string) => Promise; @@ -29,6 +38,15 @@ export function useWorktreeActions({ fetchWorktrees, fetchBranches }: UseWorktre toast.success(result.result.message); fetchWorktrees(); } else { + // Handle git status errors with informative messages + const errorCode = (result as { code?: string }).code; + if ( + errorCode && + GIT_STATUS_ERROR_CODES.includes(errorCode as (typeof GIT_STATUS_ERROR_CODES)[number]) + ) { + toast.info(GIT_STATUS_ERROR_MESSAGES[errorCode] || result.error); + return; + } toast.error(result.error || 'Failed to switch branch'); } } catch (error) { @@ -56,6 +74,15 @@ export function useWorktreeActions({ fetchWorktrees, fetchBranches }: UseWorktre toast.success(result.result.message); fetchWorktrees(); } else { + // Handle git status errors with informative messages + const errorCode = (result as { code?: string }).code; + if ( + errorCode && + GIT_STATUS_ERROR_CODES.includes(errorCode as (typeof GIT_STATUS_ERROR_CODES)[number]) + ) { + toast.info(GIT_STATUS_ERROR_MESSAGES[errorCode] || result.error); + return; + } toast.error(result.error || 'Failed to pull latest changes'); } } catch (error) { @@ -84,6 +111,15 @@ export function useWorktreeActions({ fetchWorktrees, fetchBranches }: UseWorktre fetchBranches(worktree.path); fetchWorktrees(); } else { + // Handle git status errors with informative messages + const errorCode = (result as { code?: string }).code; + if ( + errorCode && + GIT_STATUS_ERROR_CODES.includes(errorCode as (typeof GIT_STATUS_ERROR_CODES)[number]) + ) { + toast.info(GIT_STATUS_ERROR_MESSAGES[errorCode] || result.error); + return; + } toast.error(result.error || 'Failed to push changes'); } } catch (error) { diff --git a/apps/ui/src/components/views/board-view/worktree-panel/types.ts b/apps/ui/src/components/views/board-view/worktree-panel/types.ts index 901ca357..c6ecefde 100644 --- a/apps/ui/src/components/views/board-view/worktree-panel/types.ts +++ b/apps/ui/src/components/views/board-view/worktree-panel/types.ts @@ -23,6 +23,11 @@ export interface BranchInfo { isRemote: boolean; } +export interface GitRepoStatus { + isGitRepo: boolean; + hasCommits: boolean; +} + export interface DevServerInfo { worktreePath: string; port: number; diff --git a/apps/ui/src/components/views/board-view/worktree-panel/worktree-panel.tsx b/apps/ui/src/components/views/board-view/worktree-panel/worktree-panel.tsx index 665f3434..0f4a1765 100644 --- a/apps/ui/src/components/views/board-view/worktree-panel/worktree-panel.tsx +++ b/apps/ui/src/components/views/board-view/worktree-panel/worktree-panel.tsx @@ -61,6 +61,7 @@ export function WorktreePanel({ setBranchFilter, resetBranchFilter, fetchBranches, + gitRepoStatus, } = useBranches(); const { @@ -210,6 +211,7 @@ export function WorktreePanel({ isStartingDevServer={isStartingDevServer} aheadCount={aheadCount} behindCount={behindCount} + gitRepoStatus={gitRepoStatus} onSelectWorktree={handleSelectWorktree} onBranchDropdownOpenChange={handleBranchDropdownOpenChange(mainWorktree)} onActionsDropdownOpenChange={handleActionsDropdownOpenChange(mainWorktree)} @@ -264,6 +266,7 @@ export function WorktreePanel({ isStartingDevServer={isStartingDevServer} aheadCount={aheadCount} behindCount={behindCount} + gitRepoStatus={gitRepoStatus} onSelectWorktree={handleSelectWorktree} onBranchDropdownOpenChange={handleBranchDropdownOpenChange(worktree)} onActionsDropdownOpenChange={handleActionsDropdownOpenChange(worktree)} diff --git a/apps/ui/src/types/electron.d.ts b/apps/ui/src/types/electron.d.ts index 3c07c2d6..170c81dd 100644 --- a/apps/ui/src/types/electron.d.ts +++ b/apps/ui/src/types/electron.d.ts @@ -813,6 +813,7 @@ export interface WorktreeAPI { behindCount: number; }; error?: string; + code?: 'NOT_GIT_REPO'; // Error code for non-git directories }>; // Switch to an existing branch