From a1777689e7036b4556089294916819496bd0ca22 Mon Sep 17 00:00:00 2001 From: Bohdan Triapitsyn Date: Thu, 14 May 2026 23:33:04 +0300 Subject: [PATCH] feat: support multi-run for non-Git projects Creates non-Git multi-runs in the same project directory Shows concise footer info when worktree isolation is unavailable Displays default model variants with a friendly label --- .../components/multirun/BranchSelector.tsx | 23 ++++++++---- .../components/multirun/ModelMultiSelect.tsx | 6 +++- .../components/multirun/MultiRunLauncher.tsx | 13 ++++++- packages/ui/src/lib/i18n/messages/en.ts | 1 + packages/ui/src/lib/i18n/messages/es.ts | 1 + packages/ui/src/lib/i18n/messages/ko.ts | 1 + packages/ui/src/lib/i18n/messages/pl.ts | 1 + packages/ui/src/lib/i18n/messages/pt-BR.ts | 1 + packages/ui/src/lib/i18n/messages/uk.ts | 1 + packages/ui/src/lib/i18n/messages/zh-CN.ts | 1 + packages/ui/src/stores/useMultiRunStore.ts | 36 ++++++++++++------- 11 files changed, 65 insertions(+), 20 deletions(-) diff --git a/packages/ui/src/components/multirun/BranchSelector.tsx b/packages/ui/src/components/multirun/BranchSelector.tsx index 9e1a0348..ca74c65f 100644 --- a/packages/ui/src/components/multirun/BranchSelector.tsx +++ b/packages/ui/src/components/multirun/BranchSelector.tsx @@ -9,7 +9,7 @@ import { SelectTrigger, SelectValue, } from '@/components/ui/select'; -import { useGitStore, useGitBranches, useGitLoadingBranches } from '@/stores/useGitStore'; +import { useGitStore, useGitBranches, useGitLoadingBranches, useGitLoadingStatus, useIsGitRepo } from '@/stores/useGitStore'; import { useRuntimeAPIs } from '@/hooks/useRuntimeAPIs'; import { getRootBranch } from '@/lib/worktrees/worktreeStatus'; import { useI18n } from '@/lib/i18n'; @@ -53,15 +53,26 @@ export interface BranchSelectorState { export function useBranchOptions(directory: string | null): BranchSelectorState { const { git } = useRuntimeAPIs(); const branches = useGitBranches(directory); - const isLoading = useGitLoadingBranches(directory); + const isGitRepo = useIsGitRepo(directory); + const isLoadingStatus = useGitLoadingStatus(directory); + const isLoadingBranches = useGitLoadingBranches(directory); const fetchBranches = useGitStore((state) => state.fetchBranches); + const fetchStatus = useGitStore((state) => state.fetchStatus); + + React.useEffect(() => { + if (!directory || !git || isGitRepo !== null || isLoadingStatus) return; + void fetchStatus(directory, git, { silent: true }); + }, [directory, git, fetchStatus, isGitRepo, isLoadingStatus]); // Fetch branches if not cached React.useEffect(() => { if (!directory || !git) return; + if (isGitRepo !== true) return; if (branches?.all) return; // Already cached void fetchBranches(directory, git); - }, [directory, git, branches?.all, fetchBranches]); + }, [directory, git, isGitRepo, branches?.all, fetchBranches]); + + const isLoading = isLoadingStatus || (isGitRepo === true && isLoadingBranches); // Compute local and remote branch lists (same as NewWorktreeDialog) const localBranches = React.useMemo(() => { @@ -82,10 +93,10 @@ export function useBranchOptions(directory: string | null): BranchSelectorState // isGitRepository: true if we got branches, false if fetch returned empty, null if not yet loaded const isGitRepository = React.useMemo(() => { if (!directory) return null; + if (isGitRepo !== null) return isGitRepo; if (isLoading) return null; - if (!branches) return null; - return Boolean(branches.all); - }, [directory, isLoading, branches]); + return branches?.all ? true : null; + }, [directory, isLoading, branches, isGitRepo]); return { localBranches, remoteBranches, isLoading, isGitRepository }; } diff --git a/packages/ui/src/components/multirun/ModelMultiSelect.tsx b/packages/ui/src/components/multirun/ModelMultiSelect.tsx index c0b0a4e1..0978c65d 100644 --- a/packages/ui/src/components/multirun/ModelMultiSelect.tsx +++ b/packages/ui/src/components/multirun/ModelMultiSelect.tsx @@ -560,7 +560,11 @@ export const ModelMultiSelect: React.FC = ({ variantValue === DEFAULT_VARIANT_VALUE ? 'text-muted-foreground' : 'text-[color:var(--status-info)]' )} /> - + + {(value) => value === DEFAULT_VARIANT_VALUE + ? t('multirun.modelMultiSelect.variant.default') + : value} + diff --git a/packages/ui/src/components/multirun/MultiRunLauncher.tsx b/packages/ui/src/components/multirun/MultiRunLauncher.tsx index 2b7150c0..aea4441a 100644 --- a/packages/ui/src/components/multirun/MultiRunLauncher.tsx +++ b/packages/ui/src/components/multirun/MultiRunLauncher.tsx @@ -587,7 +587,12 @@ export const MultiRunLauncher: React.FC = ({ }; const isValid = Boolean( - name.trim() && prompt.trim() && selectedModels.length >= 2 && worktreeBaseBranch && isGitRepository && !isLoadingWorktreeBaseBranches + name.trim() && + prompt.trim() && + selectedModels.length >= 2 && + selectedProjectDirectory && + !isLoadingWorktreeBaseBranches && + (isGitRepository === false || (isGitRepository === true && worktreeBaseBranch)) ); const configuredSetupCount = setupCommands.filter(cmd => cmd.trim()).length; @@ -929,6 +934,12 @@ export const MultiRunLauncher: React.FC = ({ {/* ── Fixed footer ── */}
+ {isGitRepository === false ? ( +
+ + {t('multirun.launcher.project.gitRequired')} +
+ ) : null}