Skip to content

Commit 0ed6af8

Browse files
committed
fix(web): extract shared checkout error handler and update thread branch on stash & switch
Addresses bot review: the "Stash & Switch" onClick handler was missing the onSetThreadBranch call, leaving the UI stale after a successful stash-and-checkout. Extracts the duplicated error handling into a shared handleCheckoutError function used by both selectBranch and createBranch.
1 parent 4ad4fcb commit 0ed6af8

1 file changed

Lines changed: 99 additions & 129 deletions

File tree

apps/web/src/components/BranchToolbarBranchSelector.tsx

Lines changed: 99 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import type { GitBranch } from "@t3tools/contracts";
2-
import { useInfiniteQuery, useQueryClient } from "@tanstack/react-query";
1+
import type { GitBranch, NativeApi } from "@t3tools/contracts";
2+
import { type QueryClient, useInfiniteQuery, useQueryClient } from "@tanstack/react-query";
33
import { useVirtualizer } from "@tanstack/react-virtual";
44
import { ChevronDownIcon } from "lucide-react";
55
import {
@@ -92,6 +92,83 @@ function formatDirtyWorktreeDescription(files: string[]): string {
9292
return `${basenames.slice(0, 2).join(", ")} and ${basenames.length - 2} other file${basenames.length - 2 === 1 ? "" : "s"} have uncommitted changes. Commit or stash before switching.`;
9393
}
9494

95+
function handleCheckoutError(
96+
error: unknown,
97+
ctx: {
98+
api: NativeApi;
99+
cwd: string;
100+
branch: string;
101+
queryClient: QueryClient;
102+
onSuccess: () => void;
103+
fallbackTitle: string;
104+
},
105+
): void {
106+
const dirtyWorktree = parseDirtyWorktreeError(error);
107+
if (dirtyWorktree) {
108+
toastManager.add({
109+
type: "warning",
110+
title: "Uncommitted changes block checkout.",
111+
description: formatDirtyWorktreeDescription(dirtyWorktree.files),
112+
actionProps: {
113+
children: "Stash & Switch",
114+
onClick: async () => {
115+
try {
116+
await ctx.api.git.stashAndCheckout({ cwd: ctx.cwd, branch: ctx.branch });
117+
await invalidateGitQueries(ctx.queryClient);
118+
ctx.onSuccess();
119+
} catch (stashError) {
120+
if (isStashConflictError(stashError)) {
121+
toastManager.add({
122+
type: "warning",
123+
title: "Stash could not be applied.",
124+
description: toBranchActionErrorMessage(stashError),
125+
actionProps: {
126+
children: "Discard stash",
127+
onClick: async () => {
128+
const confirmed = await readNativeApi()?.dialogs.confirm(
129+
"Drop the most recent stash entry? This cannot be undone.",
130+
);
131+
if (!confirmed) return;
132+
try {
133+
await ctx.api.git.stashDrop({ cwd: ctx.cwd });
134+
} catch (dropError) {
135+
toastManager.add({
136+
type: "error",
137+
title: "Failed to drop stash.",
138+
description: toBranchActionErrorMessage(dropError),
139+
});
140+
}
141+
},
142+
},
143+
});
144+
} else {
145+
toastManager.add({
146+
type: "error",
147+
title: "Failed to stash and switch.",
148+
description: toBranchActionErrorMessage(stashError),
149+
});
150+
}
151+
}
152+
},
153+
},
154+
});
155+
return;
156+
}
157+
if (isUnresolvedIndexError(error)) {
158+
toastManager.add({
159+
type: "error",
160+
title: "Unresolved conflicts in the repository.",
161+
description: toBranchActionErrorMessage(error),
162+
});
163+
return;
164+
}
165+
toastManager.add({
166+
type: "error",
167+
title: ctx.fallbackTitle,
168+
description: toBranchActionErrorMessage(error),
169+
});
170+
}
171+
95172
function getBranchTriggerLabel(input: {
96173
activeWorktreePath: string | null;
97174
effectiveEnvMode: EnvMode;
@@ -275,71 +352,16 @@ export function BranchToolbarBranchSelector({
275352
onSetThreadBranch(nextBranchName, selectionTarget.nextWorktreePath);
276353
} catch (error) {
277354
setOptimisticBranch(previousBranch);
278-
const dirtyWorktree = parseDirtyWorktreeError(error);
279-
if (dirtyWorktree) {
280-
toastManager.add({
281-
type: "warning",
282-
title: "Uncommitted changes block checkout.",
283-
description: formatDirtyWorktreeDescription(dirtyWorktree.files),
284-
actionProps: {
285-
children: "Stash & Switch",
286-
onClick: async () => {
287-
try {
288-
await api.git.stashAndCheckout({
289-
cwd: selectionTarget.checkoutCwd,
290-
branch: branch.name,
291-
});
292-
await invalidateGitQueries(queryClient);
293-
} catch (stashError) {
294-
if (isStashConflictError(stashError)) {
295-
toastManager.add({
296-
type: "warning",
297-
title: "Stash could not be applied.",
298-
description: toBranchActionErrorMessage(stashError),
299-
actionProps: {
300-
children: "Discard stash",
301-
onClick: async () => {
302-
const confirmed = await readNativeApi()?.dialogs.confirm(
303-
"Drop the most recent stash entry? This cannot be undone.",
304-
);
305-
if (!confirmed) return;
306-
try {
307-
await api.git.stashDrop({ cwd: selectionTarget.checkoutCwd });
308-
} catch (dropError) {
309-
toastManager.add({
310-
type: "error",
311-
title: "Failed to drop stash.",
312-
description: toBranchActionErrorMessage(dropError),
313-
});
314-
}
315-
},
316-
},
317-
});
318-
} else {
319-
toastManager.add({
320-
type: "error",
321-
title: "Failed to stash and switch.",
322-
description: toBranchActionErrorMessage(stashError),
323-
});
324-
}
325-
}
326-
},
327-
},
328-
});
329-
return;
330-
}
331-
if (isUnresolvedIndexError(error)) {
332-
toastManager.add({
333-
type: "error",
334-
title: "Unresolved conflicts in the repository.",
335-
description: toBranchActionErrorMessage(error),
336-
});
337-
return;
338-
}
339-
toastManager.add({
340-
type: "error",
341-
title: "Failed to checkout branch.",
342-
description: toBranchActionErrorMessage(error),
355+
handleCheckoutError(error, {
356+
api,
357+
cwd: selectionTarget.checkoutCwd,
358+
branch: branch.name,
359+
queryClient,
360+
onSuccess: () => {
361+
setOptimisticBranch(selectedBranchName);
362+
onSetThreadBranch(selectedBranchName, selectionTarget.nextWorktreePath);
363+
},
364+
fallbackTitle: "Failed to checkout branch.",
343365
});
344366
}
345367
});
@@ -366,68 +388,16 @@ export function BranchToolbarBranchSelector({
366388
onSetThreadBranch(createBranchResult.branch, activeWorktreePath);
367389
} catch (error) {
368390
setOptimisticBranch(previousBranch);
369-
const dirtyWorktree = parseDirtyWorktreeError(error);
370-
if (dirtyWorktree) {
371-
toastManager.add({
372-
type: "warning",
373-
title: "Uncommitted changes block checkout.",
374-
description: formatDirtyWorktreeDescription(dirtyWorktree.files),
375-
actionProps: {
376-
children: "Stash & Switch",
377-
onClick: async () => {
378-
try {
379-
await api.git.stashAndCheckout({ cwd: branchCwd, branch: name });
380-
await invalidateGitQueries(queryClient);
381-
} catch (stashError) {
382-
if (isStashConflictError(stashError)) {
383-
toastManager.add({
384-
type: "warning",
385-
title: "Stash could not be applied.",
386-
description: toBranchActionErrorMessage(stashError),
387-
actionProps: {
388-
children: "Discard stash",
389-
onClick: async () => {
390-
const confirmed = await readNativeApi()?.dialogs.confirm(
391-
"Drop the most recent stash entry? This cannot be undone.",
392-
);
393-
if (!confirmed) return;
394-
try {
395-
await api.git.stashDrop({ cwd: branchCwd });
396-
} catch (dropError) {
397-
toastManager.add({
398-
type: "error",
399-
title: "Failed to drop stash.",
400-
description: toBranchActionErrorMessage(dropError),
401-
});
402-
}
403-
},
404-
},
405-
});
406-
} else {
407-
toastManager.add({
408-
type: "error",
409-
title: "Failed to stash and switch.",
410-
description: toBranchActionErrorMessage(stashError),
411-
});
412-
}
413-
}
414-
},
415-
},
416-
});
417-
return;
418-
}
419-
if (isUnresolvedIndexError(error)) {
420-
toastManager.add({
421-
type: "error",
422-
title: "Unresolved conflicts in the repository.",
423-
description: toBranchActionErrorMessage(error),
424-
});
425-
return;
426-
}
427-
toastManager.add({
428-
type: "error",
429-
title: "Failed to create and checkout branch.",
430-
description: toBranchActionErrorMessage(error),
391+
handleCheckoutError(error, {
392+
api,
393+
cwd: branchCwd,
394+
branch: name,
395+
queryClient,
396+
onSuccess: () => {
397+
setOptimisticBranch(name);
398+
onSetThreadBranch(name, activeWorktreePath);
399+
},
400+
fallbackTitle: "Failed to create and checkout branch.",
431401
});
432402
}
433403
});

0 commit comments

Comments
 (0)