feat: add remove button to each worktree in sidebar list

Show an × button on hover for non-main worktrees. Replace the
boolean showConfirmRemove with a removeBranch string so the
confirm dialog works from both sidebar and top bar.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
centdix
2026-02-21 02:03:06 +00:00
parent b2fac069df
commit bcbfe4659d
2 changed files with 22 additions and 13 deletions
+10 -10
View File
@@ -16,7 +16,7 @@
let worktrees = $state<WorktreeInfo[]>([]);
let selectedBranch = $state<string | null>(null);
let showConfirmRemove = $state(false);
let removeBranch = $state<string | null>(null);
let showCreateDialog = $state(false);
let creating = $state(false);
let removing = $state(false);
@@ -63,12 +63,12 @@
}
async function handleRemove() {
if (!selectedBranch) return;
if (!removeBranch) return;
removing = true;
try {
await api.removeWorktree(selectedBranch);
showConfirmRemove = false;
selectedBranch = null;
await api.removeWorktree(removeBranch);
if (selectedBranch === removeBranch) selectedBranch = null;
removeBranch = null;
await refresh();
} catch (err) {
alert(`Failed to remove: ${err instanceof Error ? err.message : err}`);
@@ -95,14 +95,14 @@
title="New Worktree"
><span class="text-lg leading-none">+</span> New</button>
</div>
<WorktreeList worktrees={visibleWorktrees} selected={selectedBranch} onselect={(b) => (selectedBranch = b)} />
<WorktreeList worktrees={visibleWorktrees} selected={selectedBranch} onselect={(b) => (selectedBranch = b)} onremove={(b) => (removeBranch = b)} />
</aside>
<main class="flex-1 min-w-0 flex flex-col overflow-hidden">
<TopBar
name={selectedBranch}
worktree={selectedWorktree}
onremove={() => (showConfirmRemove = true)}
onremove={() => { if (selectedBranch) removeBranch = selectedBranch; }}
/>
{#if canConnect}
@@ -161,11 +161,11 @@
<div class="fixed inset-0 bg-black/50 z-40" onclick={() => (showCreateDialog = false)}></div>
{/if}
{#if showConfirmRemove}
{#if removeBranch}
<ConfirmDialog
message={`Remove worktree "${selectedBranch}"? This action cannot be undone.`}
message={`Remove worktree "${removeBranch}"? This action cannot be undone.`}
loading={removing}
onconfirm={handleRemove}
oncancel={() => (showConfirmRemove = false)}
oncancel={() => (removeBranch = null)}
/>
{/if}
@@ -1,10 +1,11 @@
<script lang="ts">
import type { WorktreeInfo } from "./types";
let { worktrees, selected, onselect }: {
let { worktrees, selected, onselect, onremove }: {
worktrees: WorktreeInfo[];
selected: string | null;
onselect: (branch: string) => void;
onremove: (branch: string) => void;
} = $props();
function dotColor(agent: string): string {
@@ -19,13 +20,13 @@
{#each worktrees as wt (wt.branch)}
{@const isMain = wt.path === "(here)" || wt.branch === "main"}
{@const isActive = wt.branch === selected}
<li class="mb-0.5">
<li class="mb-0.5 group relative">
<button
type="button"
class="w-full py-2.5 px-3 rounded-md border cursor-pointer flex flex-col gap-1 text-left text-inherit text-sm bg-transparent hover:bg-hover {isActive ? 'bg-active border-accent' : 'border-transparent'}"
onclick={() => onselect(wt.branch)}
>
<span class="font-medium truncate">{wt.branch}</span>
<span class="font-medium truncate pr-5">{wt.branch}</span>
<span class="flex gap-2 text-[11px] text-muted">
<span><span class="inline-block w-2 h-2 rounded-full mr-1 align-middle {dotColor(wt.agent)}"></span>{wt.agent || "none"}</span>
{#if wt.mux && wt.mux !== "-"}
@@ -36,6 +37,14 @@
{/if}
</span>
</button>
{#if !isMain}
<button
type="button"
class="absolute top-2 right-2 w-5 h-5 rounded flex items-center justify-center text-muted hover:text-danger hover:bg-hover opacity-0 group-hover:opacity-100 transition-opacity cursor-pointer"
title="Remove worktree"
onclick={(e) => { e.stopPropagation(); onremove(wt.branch); }}
>&times;</button>
{/if}
</li>
{/each}
</ul>