fix(terminal): add scroll area to worktree dropdown to prevent overflow (#1146)

* fix(terminal): add scroll area to worktree dropdown to prevent overflow

Wrap worktree list in ScrollArea with max-height of 300px to handle
cases with many worktrees without overflowing the screen.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(terminal): keep separator fixed above scrollable worktree list

Move DropdownMenuSeparator outside ScrollArea so it remains visible
when scrolling through many worktrees, maintaining visual distinction
from the "Create New" item.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Andy
2026-01-15 22:26:18 +01:00
committed by GitHub
parent 658f26cb47
commit 200bb3bcae
@@ -20,6 +20,7 @@ import {
AlertDialogTitle, AlertDialogTitle,
} from '../ui/alert-dialog'; } from '../ui/alert-dialog';
import { cn } from '../../lib/utils'; import { cn } from '../../lib/utils';
import { ScrollArea } from '../ui/scroll-area';
import { useProjectStore } from '../../stores/project-store'; import { useProjectStore } from '../../stores/project-store';
interface WorktreeSelectorProps { interface WorktreeSelectorProps {
@@ -169,90 +170,91 @@ export function WorktreeSelector({
{t('terminal:worktree.createNew')} {t('terminal:worktree.createNew')}
</DropdownMenuItem> </DropdownMenuItem>
{/* Separator and existing worktrees */} {/* Fixed separator between "Create New" and scrollable content */}
{isLoading ? ( <DropdownMenuSeparator />
<>
<DropdownMenuSeparator /> {/* Scrollable content */}
<ScrollArea className="max-h-[300px]">
{isLoading ? (
<div className="flex items-center justify-center py-2"> <div className="flex items-center justify-center py-2">
<Loader2 className="h-4 w-4 animate-spin text-muted-foreground" /> <Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />
</div> </div>
</> ) : (
) : ( <>
<> {/* Terminal Worktrees Section */}
{/* Terminal Worktrees Section */} {worktrees.length > 0 && (
{worktrees.length > 0 && ( <>
<> <div className="px-2 py-1.5 text-xs text-muted-foreground">
<DropdownMenuSeparator /> {t('terminal:worktree.existing')}
<div className="px-2 py-1.5 text-xs text-muted-foreground"> </div>
{t('terminal:worktree.existing')} {worktrees.map((wt) => (
</div> <DropdownMenuItem
{worktrees.map((wt) => ( key={wt.name}
<DropdownMenuItem
key={wt.name}
onClick={(e) => {
e.stopPropagation();
setIsOpen(false);
onSelectWorktree(wt);
}}
className="text-xs group"
>
<FolderGit className="h-3 w-3 mr-2 text-amber-500/70 shrink-0" />
<div className="flex flex-col min-w-0 flex-1">
<span className="truncate font-medium">{wt.name}</span>
{wt.branchName && (
<span className="text-[10px] text-muted-foreground truncate">
{wt.branchName}
</span>
)}
</div>
<button
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); setIsOpen(false);
setDeleteWorktree(wt); onSelectWorktree(wt);
}} }}
className="ml-2 p-1 rounded hover:bg-destructive/10 text-muted-foreground hover:text-destructive opacity-0 group-hover:opacity-100 transition-opacity shrink-0" className="text-xs group"
title={t('common:delete')}
> >
<Trash2 className="h-3 w-3" /> <FolderGit className="h-3 w-3 mr-2 text-amber-500/70 shrink-0" />
</button> <div className="flex flex-col min-w-0 flex-1">
</DropdownMenuItem> <span className="truncate font-medium">{wt.name}</span>
))} {wt.branchName && (
</> <span className="text-[10px] text-muted-foreground truncate">
)} {wt.branchName}
</span>
)}
</div>
<button
onClick={(e) => {
e.stopPropagation();
e.preventDefault();
setDeleteWorktree(wt);
}}
className="ml-2 p-1 rounded hover:bg-destructive/10 text-muted-foreground hover:text-destructive opacity-0 group-hover:opacity-100 transition-opacity shrink-0"
title={t('common:delete')}
>
<Trash2 className="h-3 w-3" />
</button>
</DropdownMenuItem>
))}
</>
)}
{/* Task Worktrees Section */} {/* Task Worktrees Section */}
{taskWorktrees.length > 0 && ( {taskWorktrees.length > 0 && (
<> <>
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<div className="px-2 py-1.5 text-xs text-muted-foreground"> <div className="px-2 py-1.5 text-xs text-muted-foreground">
{t('terminal:worktree.taskWorktrees')} {t('terminal:worktree.taskWorktrees')}
</div> </div>
{taskWorktrees.map((wt) => ( {taskWorktrees.map((wt) => (
<DropdownMenuItem <DropdownMenuItem
key={wt.specName} key={wt.specName}
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
setIsOpen(false); setIsOpen(false);
selectTaskWorktree(wt); selectTaskWorktree(wt);
}} }}
className="text-xs group" className="text-xs group"
> >
<ListTodo className="h-3 w-3 mr-2 text-cyan-500/70 shrink-0" /> <ListTodo className="h-3 w-3 mr-2 text-cyan-500/70 shrink-0" />
<div className="flex flex-col min-w-0 flex-1"> <div className="flex flex-col min-w-0 flex-1">
<span className="truncate font-medium">{wt.specName}</span> <span className="truncate font-medium">{wt.specName}</span>
{wt.branch && ( {wt.branch && (
<span className="text-[10px] text-muted-foreground truncate"> <span className="text-[10px] text-muted-foreground truncate">
{wt.branch} {wt.branch}
</span> </span>
)} )}
</div> </div>
</DropdownMenuItem> </DropdownMenuItem>
))} ))}
</> </>
)} )}
</> </>
)} )}
</ScrollArea>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>