add ability to collapse worktree panel

This commit is contained in:
Cody Seibert
2025-12-20 00:05:48 -05:00
3 changed files with 96 additions and 21 deletions

View File

@@ -91,21 +91,6 @@ export function WorktreeTab({
onStopDevServer,
onOpenDevServerUrl,
}: WorktreeTabProps) {
// Determine border color based on state:
// - Running features: cyan border (high visibility, indicates active work)
// - Uncommitted changes: amber border (warning state, needs attention)
// - Both: cyan takes priority (running is more important to see)
const getBorderClasses = () => {
if (isRunning) {
return "ring-2 ring-cyan-500 ring-offset-1 ring-offset-background";
}
if (hasChanges) {
return "ring-2 ring-amber-500 ring-offset-1 ring-offset-background";
}
return "";
};
const borderClasses = getBorderClasses();
let prBadge: JSX.Element | null = null;
if (worktree.pr) {
@@ -193,7 +178,7 @@ export function WorktreeTab({
}
return (
<div className={cn("flex items-center rounded-md", borderClasses)}>
<div className="flex items-center rounded-md">
{worktree.isMain ? (
<>
<Button

View File

@@ -20,9 +20,12 @@ export function useWorktrees({ projectPath, refreshTrigger = 0, onRemovedWorktre
const setWorktreesInStore = useAppStore((s) => s.setWorktrees);
const useWorktreesEnabled = useAppStore((s) => s.useWorktrees);
const fetchWorktrees = useCallback(async () => {
const fetchWorktrees = useCallback(async (options?: { silent?: boolean }) => {
if (!projectPath) return;
setIsLoading(true);
const silent = options?.silent ?? false;
if (!silent) {
setIsLoading(true);
}
try {
const api = getElectronAPI();
if (!api?.worktree?.listAll) {
@@ -40,7 +43,9 @@ export function useWorktrees({ projectPath, refreshTrigger = 0, onRemovedWorktre
console.error("Failed to fetch worktrees:", error);
return undefined;
} finally {
setIsLoading(false);
if (!silent) {
setIsLoading(false);
}
}
}, [projectPath, setWorktreesInStore]);

View File

@@ -1,5 +1,12 @@
import { useState, useEffect, useRef } from "react";
import { Button } from "@/components/ui/button";
import { GitBranch, Plus, RefreshCw } from "lucide-react";
import {
GitBranch,
Plus,
RefreshCw,
PanelLeftOpen,
PanelLeftClose,
} from "lucide-react";
import { cn, pathsEqual } from "@/lib/utils";
import type { WorktreePanelProps, WorktreeInfo } from "./types";
import {
@@ -12,6 +19,8 @@ import {
} from "./hooks";
import { WorktreeTab } from "./components";
const WORKTREE_PANEL_COLLAPSED_KEY = "worktree-panel-collapsed";
export function WorktreePanel({
projectPath,
onCreateWorktree,
@@ -80,6 +89,45 @@ export function WorktreePanel({
features,
});
// Collapse state with localStorage persistence
const [isCollapsed, setIsCollapsed] = useState(() => {
if (typeof window === "undefined") return false;
const saved = localStorage.getItem(WORKTREE_PANEL_COLLAPSED_KEY);
return saved === "true";
});
useEffect(() => {
localStorage.setItem(WORKTREE_PANEL_COLLAPSED_KEY, String(isCollapsed));
}, [isCollapsed]);
const toggleCollapsed = () => setIsCollapsed((prev) => !prev);
// Periodic interval check (1 second) to detect branch changes on disk
const intervalRef = useRef<NodeJS.Timeout | null>(null);
useEffect(() => {
intervalRef.current = setInterval(() => {
fetchWorktrees({ silent: true });
}, 1000);
return () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
}
};
}, [fetchWorktrees]);
// Get the currently selected worktree for collapsed view
const selectedWorktree = worktrees.find((w) => {
if (
currentWorktree === null ||
currentWorktree === undefined ||
currentWorktree.path === null
) {
return w.isMain;
}
return pathsEqual(w.path, currentWorktreePath);
});
const isWorktreeSelected = (worktree: WorktreeInfo) => {
return worktree.isMain
? currentWorktree === null ||
@@ -106,9 +154,46 @@ export function WorktreePanel({
const mainWorktree = worktrees.find((w) => w.isMain);
const nonMainWorktrees = worktrees.filter((w) => !w.isMain);
// Collapsed view - just show current branch and toggle
if (isCollapsed) {
return (
<div className="flex items-center gap-2 px-4 py-1.5 border-b border-border bg-glass/50 backdrop-blur-sm">
<Button
variant="ghost"
size="sm"
className="h-6 w-6 p-0 text-muted-foreground hover:text-foreground"
onClick={toggleCollapsed}
title="Expand worktree panel"
>
<PanelLeftOpen className="w-4 h-4" />
</Button>
<GitBranch className="w-4 h-4 text-muted-foreground" />
<span className="text-sm text-muted-foreground">Branch:</span>
<span className="text-sm font-mono font-medium">
{selectedWorktree?.branch ?? "main"}
</span>
{selectedWorktree?.hasChanges && (
<span className="inline-flex items-center justify-center h-4 min-w-[1rem] px-1 text-[10px] font-medium rounded border bg-amber-500/20 text-amber-600 dark:text-amber-400 border-amber-500/30">
{selectedWorktree.changedFilesCount ?? "!"}
</span>
)}
</div>
);
}
// Expanded view - full worktree panel
return (
<div className="flex items-center gap-2 px-4 py-2 border-b border-border bg-glass/50 backdrop-blur-sm">
{/* Branch section - always visible */}
<Button
variant="ghost"
size="sm"
className="h-6 w-6 p-0 text-muted-foreground hover:text-foreground"
onClick={toggleCollapsed}
title="Collapse worktree panel"
>
<PanelLeftClose className="w-4 h-4" />
</Button>
<GitBranch className="w-4 h-4 text-muted-foreground" />
<span className="text-sm text-muted-foreground mr-2">Branch:</span>