Files
openchamber/packages/ui/src/components/multirun/BranchSelector.tsx
T
00821700de chore: remove dead code (59 unused files + ~125 unused exports) (#1835)
* chore: remove dead/unreferenced files across ui, vscode

Remove 59 unused source files (components, hooks, lib utils, stores,
barrels, and orphaned vscode github modules) that are not imported by
any entry-reachable code. Also drop a stale test mock for the removed
execCommands module.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>

* refactor: remove unused exported symbols (types, functions, consts, hooks)

Remove exported symbols whose identifier is referenced nowhere in the
repository (verified via repo-wide search), across ui types/contracts,
lib utilities, sync layer, stores, and components. Also drop the few
imports/private helpers orphaned by these removals.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>

* refactor: remove more unused exports (desktop, shortcuts, worktree, vscode)

Continue removing repo-wide unreferenced exported functions, consts and
types across lib/desktop, shortcuts, worktreeSessionCreator, sync, and
vscode gitService, with cascading orphaned helpers/imports cleaned up.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>

* chore: add dead-code cleanup tooling

* refactor: checkpoint dead-code cleanup

* refactor: remove dead-code suppressions

---------

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-06-26 19:27:53 +03:00

208 lines
7.4 KiB
TypeScript

import React from 'react';
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectSeparator,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
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';
/** localStorage key matching NewWorktreeDialog */
const LAST_SOURCE_BRANCH_KEY = 'oc:lastWorktreeSourceBranch';
export interface BranchSelectorProps {
/** Current directory to check for git repository */
directory: string | null;
/** Currently selected branch */
value: string;
/** Called when branch selection changes */
onChange: (branch: string) => void;
/** Optional className for the trigger */
className?: string;
/** Whether the selector is disabled */
disabled?: boolean;
/** ID for accessibility */
id?: string;
}
export interface BranchSelectorState {
localBranches: string[];
remoteBranches: string[];
isLoading: boolean;
isGitRepository: boolean | null;
}
/**
* Hook to load available git branches for a directory.
* Uses the shared useGitStore (same as NewWorktreeDialog).
*/
// eslint-disable-next-line react-refresh/only-export-components -- Hook is tightly coupled with BranchSelector
export function useBranchOptions(directory: string | null): BranchSelectorState {
const { git } = useRuntimeAPIs();
const branches = useGitBranches(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, isGitRepo, branches?.all, fetchBranches]);
const isLoading = isLoadingStatus || (isGitRepo === true && isLoadingBranches);
// Compute local and remote branch lists (same as NewWorktreeDialog)
const localBranches = React.useMemo(() => {
if (!branches?.all) return [];
return branches.all
.filter((branchName: string) => !branchName.startsWith('remotes/'))
.sort();
}, [branches]);
const remoteBranches = React.useMemo(() => {
if (!branches?.all) return [];
return branches.all
.filter((branchName: string) => branchName.startsWith('remotes/'))
.map((branchName: string) => branchName.replace(/^remotes\//, ''))
.sort();
}, [branches]);
// isGitRepository: true if we got branches, false if fetch returned empty, null if not yet loaded
const isGitRepository = React.useMemo<boolean | null>(() => {
if (!directory) return null;
if (isGitRepo !== null) return isGitRepo;
if (isLoading) return null;
return branches?.all ? true : null;
}, [directory, isLoading, branches, isGitRepo]);
return { localBranches, remoteBranches, isLoading, isGitRepository };
}
/**
* Branch selector dropdown for selecting a source branch for worktree creation.
* Matches the NewWorktreeDialog source branch selector exactly.
*/
export const BranchSelector: React.FC<BranchSelectorProps> = ({
directory,
value,
onChange,
className,
disabled,
id,
}) => {
const { t } = useI18n();
const { localBranches, remoteBranches, isLoading, isGitRepository } = useBranchOptions(directory);
const allBranches = React.useMemo(
() => [...localBranches, ...remoteBranches.map(b => `remotes/${b}`)],
[localBranches, remoteBranches],
);
// Resolve default source branch (same priority as NewWorktreeDialog)
React.useEffect(() => {
if (disabled || isLoading || allBranches.length === 0) return;
// If current value is valid, keep it
if (value && allBranches.includes(value)) return;
const resolve = async () => {
try {
const rootBranch = directory ? await getRootBranch(directory).catch(() => null) : null;
const saved = localStorage.getItem(LAST_SOURCE_BRANCH_KEY);
if (saved && allBranches.includes(saved)) {
onChange(saved);
} else if (rootBranch && allBranches.includes(rootBranch)) {
onChange(rootBranch);
} else if (allBranches.includes('main')) {
onChange('main');
} else if (allBranches.includes('master')) {
onChange('master');
} else if (allBranches[0]) {
onChange(allBranches[0]);
}
} catch {
// ignore
}
};
void resolve();
}, [allBranches, directory, disabled, isLoading, onChange, value]);
const isDisabled = disabled || !isGitRepository || isLoading;
return (
<div>
<Select
value={value}
onValueChange={onChange}
disabled={isDisabled}
>
<SelectTrigger
id={id}
size="lg"
className={className ?? 'w-fit typography-meta text-foreground'}
>
<SelectValue placeholder={isLoading ? t('multiRun.branchSelector.status.loadingBranches') : t('multiRun.branchSelector.placeholder.selectSourceBranch')} />
</SelectTrigger>
<SelectContent className="max-h-[280px] max-w-[320px]">
{isLoading ? (
<div className="px-2 py-4 text-center typography-meta text-muted-foreground">
{t('multiRun.branchSelector.status.loadingBranches')}
</div>
) : localBranches.length === 0 && remoteBranches.length === 0 ? (
<div className="px-2 py-4 text-center typography-meta text-muted-foreground">
{t('multiRun.branchSelector.status.noBranchesFound')}
</div>
) : (
<>
{localBranches.length > 0 && (
<SelectGroup>
<SelectLabel className="font-semibold text-foreground">{t('multiRun.branchSelector.groups.localBranches')}</SelectLabel>
{localBranches.map((branch) => (
<SelectItem key={branch} value={branch} className="whitespace-normal break-all">
{branch}
</SelectItem>
))}
</SelectGroup>
)}
{localBranches.length > 0 && remoteBranches.length > 0 && (
<SelectSeparator />
)}
{remoteBranches.length > 0 && (
<SelectGroup>
<SelectLabel className="font-semibold text-foreground">{t('multiRun.branchSelector.groups.remoteBranches')}</SelectLabel>
{remoteBranches.map((branch) => (
<SelectItem key={`remotes/${branch}`} value={`remotes/${branch}`} className="whitespace-normal break-all">
{branch}
</SelectItem>
))}
</SelectGroup>
)}
</>
)}
</SelectContent>
</Select>
{isGitRepository === false && (
<p className="typography-micro text-muted-foreground/70 mt-2">{t('multiRun.branchSelector.status.notInGitRepository')}</p>
)}
</div>
);
};