@@ -10,7 +10,7 @@ import {
1010 gitPruneWorktreesMutationOptions ,
1111 gitRemoveWorktreeMutationOptions ,
1212} from "~/lib/gitReactQuery" ;
13- import { formatWorktreePathForDisplay } from "~/worktreeCleanup" ;
13+ import { formatBranchAge , formatWorktreePathForDisplay } from "~/worktreeCleanup" ;
1414import { useWorktreeCleanupStore } from "~/worktreeCleanupStore" ;
1515import { toastManager } from "./ui/toast" ;
1616import { Badge } from "./ui/badge" ;
@@ -64,10 +64,37 @@ export function WorktreeCleanupDialog() {
6464 const hasCandidates = candidates . length > 0 ;
6565 const isBusy = removeWorktreeMutation . isPending || pruneWorktreesMutation . isPending ;
6666
67+ const staleCandidates = useMemo (
68+ ( ) =>
69+ candidates . filter (
70+ ( c ) => ! c . pathExists && resolveWorktreeUsageCount ( c , threadWorktreePaths ) === 0 ,
71+ ) ,
72+ [ candidates , threadWorktreePaths ] ,
73+ ) ;
74+ const hasStaleCandidates = staleCandidates . length > 0 ;
75+
6776 const handleClose = ( ) => {
6877 closeDialog ( ) ;
6978 } ;
7079
80+ const handlePruneAllStale = async ( ) => {
81+ if ( ! cwd || ! hasStaleCandidates ) return ;
82+ try {
83+ await pruneWorktreesMutation . mutateAsync ( { cwd } ) ;
84+ toastManager . add ( {
85+ type : "success" ,
86+ title : "Stale records pruned" ,
87+ description : `Pruned ${ staleCandidates . length } stale worktree record${ staleCandidates . length === 1 ? "" : "s" } .` ,
88+ } ) ;
89+ } catch ( error ) {
90+ toastManager . add ( {
91+ type : "error" ,
92+ title : "Could not prune stale records" ,
93+ description : error instanceof Error ? error . message : "Unknown error." ,
94+ } ) ;
95+ }
96+ } ;
97+
7198 const handleRemoveCandidate = async ( candidate : GitWorktreeCleanupCandidate ) => {
7299 if ( ! cwd ) return ;
73100 const usageCount = resolveWorktreeUsageCount ( candidate , threadWorktreePaths ) ;
@@ -151,10 +178,21 @@ export function WorktreeCleanupDialog() {
151178 < div className = "flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between" >
152179 < div className = "min-w-0 space-y-2" >
153180 < div className = "flex flex-wrap items-center gap-2" >
154- < Badge variant = "outline" size = "sm" >
155- < GitMergeIcon className = "size-3.5" />
156- Merged PR #{ candidate . prNumber }
157- </ Badge >
181+ < a
182+ href = { candidate . prUrl }
183+ target = "_blank"
184+ rel = "noopener noreferrer"
185+ className = "no-underline"
186+ >
187+ < Badge
188+ variant = "outline"
189+ size = "sm"
190+ className = "cursor-pointer hover:bg-accent"
191+ >
192+ < GitMergeIcon className = "size-3.5" />
193+ Merged PR #{ candidate . prNumber }
194+ </ Badge >
195+ </ a >
158196 { candidate . pathExists ? (
159197 < Badge variant = "success" size = "sm" >
160198 On disk
@@ -182,6 +220,11 @@ export function WorktreeCleanupDialog() {
182220 { " · " }
183221 Path{ " " }
184222 < span className = "font-mono text-foreground" > { displayPath } </ span >
223+ { " · " }
224+ Merged{ " " }
225+ < span className = "text-foreground" >
226+ { formatBranchAge ( candidate . mergedAt ) }
227+ </ span >
185228 </ div >
186229 </ div >
187230 </ div >
@@ -215,9 +258,26 @@ export function WorktreeCleanupDialog() {
215258 < div className = "text-xs text-muted-foreground" >
216259 { candidates . length } candidate{ candidates . length === 1 ? "" : "s" } found
217260 </ div >
218- < Button variant = "outline" size = "sm" onClick = { handleClose } >
219- Close
220- </ Button >
261+ < div className = "flex items-center gap-2" >
262+ { hasStaleCandidates ? (
263+ < Button
264+ variant = "destructive-outline"
265+ size = "sm"
266+ disabled = { isBusy }
267+ onClick = { ( ) => void handlePruneAllStale ( ) }
268+ >
269+ { pruneWorktreesMutation . isPending ? (
270+ < LoaderCircleIcon className = "size-3.5 animate-spin" />
271+ ) : (
272+ < Trash2Icon className = "size-3.5" />
273+ ) }
274+ Prune all stale ({ staleCandidates . length } )
275+ </ Button >
276+ ) : null }
277+ < Button variant = "outline" size = "sm" onClick = { handleClose } >
278+ Close
279+ </ Button >
280+ </ div >
221281 </ div >
222282 </ DialogFooter >
223283 </ DialogPopup >
0 commit comments