Improve git action toast feedback
This commit is contained in:
@@ -80,6 +80,7 @@ import { openExternalUrl } from "@/utils/open-external-url";
|
|||||||
import { GitActionsSplitButton } from "@/components/git-actions-split-button";
|
import { GitActionsSplitButton } from "@/components/git-actions-split-button";
|
||||||
import { usePanelStore } from "@/stores/panel-store";
|
import { usePanelStore } from "@/stores/panel-store";
|
||||||
import { buildWorkspaceExplorerStateKey } from "@/hooks/use-file-explorer-actions";
|
import { buildWorkspaceExplorerStateKey } from "@/hooks/use-file-explorer-actions";
|
||||||
|
import { useToast } from "@/contexts/toast-context";
|
||||||
import {
|
import {
|
||||||
formatDiffContentText,
|
formatDiffContentText,
|
||||||
formatDiffGutterText,
|
formatDiffGutterText,
|
||||||
@@ -607,12 +608,12 @@ type DiffFlatItem =
|
|||||||
|
|
||||||
export function GitDiffPane({ serverId, workspaceId, cwd, hideHeaderRow }: GitDiffPaneProps) {
|
export function GitDiffPane({ serverId, workspaceId, cwd, hideHeaderRow }: GitDiffPaneProps) {
|
||||||
const { theme } = useUnistyles();
|
const { theme } = useUnistyles();
|
||||||
|
const toast = useToast();
|
||||||
const isMobile = useIsCompactFormFactor();
|
const isMobile = useIsCompactFormFactor();
|
||||||
const showDesktopWebScrollbar = Platform.OS === "web" && !isMobile;
|
const showDesktopWebScrollbar = Platform.OS === "web" && !isMobile;
|
||||||
const canUseSplitLayout = Platform.OS === "web" && !isMobile;
|
const canUseSplitLayout = Platform.OS === "web" && !isMobile;
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [diffModeOverride, setDiffModeOverride] = useState<"uncommitted" | "base" | null>(null);
|
const [diffModeOverride, setDiffModeOverride] = useState<"uncommitted" | "base" | null>(null);
|
||||||
const [actionError, setActionError] = useState<string | null>(null);
|
|
||||||
const [postShipArchiveSuggested, setPostShipArchiveSuggested] = useState(false);
|
const [postShipArchiveSuggested, setPostShipArchiveSuggested] = useState(false);
|
||||||
const [shipDefault, setShipDefault] = useState<"merge" | "pr">("merge");
|
const [shipDefault, setShipDefault] = useState<"merge" | "pr">("merge");
|
||||||
const { preferences: changesPreferences, updatePreferences: updateChangesPreferences } =
|
const { preferences: changesPreferences, updatePreferences: updateChangesPreferences } =
|
||||||
@@ -919,77 +920,106 @@ export function GitDiffPane({ serverId, workspaceId, cwd, hideHeaderRow }: GitDi
|
|||||||
const runMergeFromBase = useCheckoutGitActionsStore((state) => state.mergeFromBase);
|
const runMergeFromBase = useCheckoutGitActionsStore((state) => state.mergeFromBase);
|
||||||
const runArchiveWorktree = useCheckoutGitActionsStore((state) => state.archiveWorktree);
|
const runArchiveWorktree = useCheckoutGitActionsStore((state) => state.archiveWorktree);
|
||||||
|
|
||||||
|
const toastActionError = useCallback(
|
||||||
|
(error: unknown, fallback: string) => {
|
||||||
|
const message = error instanceof Error ? error.message : fallback;
|
||||||
|
toast.error(message);
|
||||||
|
},
|
||||||
|
[toast],
|
||||||
|
);
|
||||||
|
|
||||||
|
const toastActionSuccess = useCallback(
|
||||||
|
(message: string) => {
|
||||||
|
toast.show(message, { variant: "success" });
|
||||||
|
},
|
||||||
|
[toast],
|
||||||
|
);
|
||||||
|
|
||||||
const handleCommit = useCallback(() => {
|
const handleCommit = useCallback(() => {
|
||||||
setActionError(null);
|
void runCommit({ serverId, cwd })
|
||||||
void runCommit({ serverId, cwd }).catch((err) => {
|
.then(() => {
|
||||||
const message = err instanceof Error ? err.message : "Failed to commit";
|
toastActionSuccess("Committed");
|
||||||
setActionError(message);
|
})
|
||||||
});
|
.catch((err) => {
|
||||||
}, [runCommit, serverId, cwd]);
|
toastActionError(err, "Failed to commit");
|
||||||
|
});
|
||||||
|
}, [cwd, runCommit, serverId, toastActionError, toastActionSuccess]);
|
||||||
|
|
||||||
const handlePush = useCallback(() => {
|
const handlePush = useCallback(() => {
|
||||||
setActionError(null);
|
void runPush({ serverId, cwd })
|
||||||
void runPush({ serverId, cwd }).catch((err) => {
|
.then(() => {
|
||||||
const message = err instanceof Error ? err.message : "Failed to push";
|
toastActionSuccess("Pushed");
|
||||||
setActionError(message);
|
})
|
||||||
});
|
.catch((err) => {
|
||||||
}, [runPush, serverId, cwd]);
|
toastActionError(err, "Failed to push");
|
||||||
|
});
|
||||||
|
}, [cwd, runPush, serverId, toastActionError, toastActionSuccess]);
|
||||||
|
|
||||||
const handleCreatePr = useCallback(() => {
|
const handleCreatePr = useCallback(() => {
|
||||||
void persistShipDefault("pr");
|
void persistShipDefault("pr");
|
||||||
setActionError(null);
|
void runCreatePr({ serverId, cwd })
|
||||||
void runCreatePr({ serverId, cwd }).catch((err) => {
|
.then(() => {
|
||||||
const message = err instanceof Error ? err.message : "Failed to create PR";
|
toastActionSuccess("PR created");
|
||||||
setActionError(message);
|
})
|
||||||
});
|
.catch((err) => {
|
||||||
}, [persistShipDefault, runCreatePr, serverId, cwd]);
|
toastActionError(err, "Failed to create PR");
|
||||||
|
});
|
||||||
|
}, [cwd, persistShipDefault, runCreatePr, serverId, toastActionError, toastActionSuccess]);
|
||||||
|
|
||||||
const handleMergeBranch = useCallback(() => {
|
const handleMergeBranch = useCallback(() => {
|
||||||
if (!baseRef) {
|
if (!baseRef) {
|
||||||
setActionError("Base ref unavailable");
|
toast.error("Base ref unavailable");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
void persistShipDefault("merge");
|
void persistShipDefault("merge");
|
||||||
setActionError(null);
|
|
||||||
void runMergeBranch({ serverId, cwd, baseRef })
|
void runMergeBranch({ serverId, cwd, baseRef })
|
||||||
.then(() => {
|
.then(() => {
|
||||||
setPostShipArchiveSuggested(true);
|
setPostShipArchiveSuggested(true);
|
||||||
|
toastActionSuccess("Merged");
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
const message = err instanceof Error ? err.message : "Failed to merge";
|
toastActionError(err, "Failed to merge");
|
||||||
setActionError(message);
|
|
||||||
});
|
});
|
||||||
}, [baseRef, persistShipDefault, runMergeBranch, serverId, cwd]);
|
}, [
|
||||||
|
baseRef,
|
||||||
|
cwd,
|
||||||
|
persistShipDefault,
|
||||||
|
runMergeBranch,
|
||||||
|
serverId,
|
||||||
|
toast,
|
||||||
|
toastActionError,
|
||||||
|
toastActionSuccess,
|
||||||
|
]);
|
||||||
|
|
||||||
const handleMergeFromBase = useCallback(() => {
|
const handleMergeFromBase = useCallback(() => {
|
||||||
if (!baseRef) {
|
if (!baseRef) {
|
||||||
setActionError("Base ref unavailable");
|
toast.error("Base ref unavailable");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setActionError(null);
|
void runMergeFromBase({ serverId, cwd, baseRef })
|
||||||
void runMergeFromBase({ serverId, cwd, baseRef }).catch((err) => {
|
.then(() => {
|
||||||
const message = err instanceof Error ? err.message : "Failed to merge from base";
|
toastActionSuccess("Updated");
|
||||||
setActionError(message);
|
})
|
||||||
});
|
.catch((err) => {
|
||||||
}, [baseRef, runMergeFromBase, serverId, cwd]);
|
toastActionError(err, "Failed to merge from base");
|
||||||
|
});
|
||||||
|
}, [baseRef, cwd, runMergeFromBase, serverId, toast, toastActionError, toastActionSuccess]);
|
||||||
|
|
||||||
const handleArchiveWorktree = useCallback(() => {
|
const handleArchiveWorktree = useCallback(() => {
|
||||||
const worktreePath = status?.cwd;
|
const worktreePath = status?.cwd;
|
||||||
if (!worktreePath) {
|
if (!worktreePath) {
|
||||||
setActionError("Worktree path unavailable");
|
toast.error("Worktree path unavailable");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setActionError(null);
|
|
||||||
const targetWorkingDir = resolveNewAgentWorkingDir(cwd, status ?? null);
|
const targetWorkingDir = resolveNewAgentWorkingDir(cwd, status ?? null);
|
||||||
void runArchiveWorktree({ serverId, cwd, worktreePath })
|
void runArchiveWorktree({ serverId, cwd, worktreePath })
|
||||||
.then(() => {
|
.then(() => {
|
||||||
router.replace(buildNewAgentRoute(serverId, targetWorkingDir));
|
router.replace(buildNewAgentRoute(serverId, targetWorkingDir));
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
const message = err instanceof Error ? err.message : "Failed to archive worktree";
|
toastActionError(err, "Failed to archive worktree");
|
||||||
setActionError(message);
|
|
||||||
});
|
});
|
||||||
}, [runArchiveWorktree, router, serverId, cwd, status]);
|
}, [cwd, router, runArchiveWorktree, serverId, status, toast, toastActionError]);
|
||||||
|
|
||||||
const renderFlatItem = useCallback(
|
const renderFlatItem = useCallback(
|
||||||
({ item }: { item: DiffFlatItem }) => {
|
({ item }: { item: DiffFlatItem }) => {
|
||||||
@@ -1458,7 +1488,6 @@ export function GitDiffPane({ serverId, workspaceId, cwd, hideHeaderRow }: GitDi
|
|||||||
</View>
|
</View>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{actionError ? <Text style={styles.actionErrorText}>{actionError}</Text> : null}
|
|
||||||
{prErrorMessage ? <Text style={styles.actionErrorText}>{prErrorMessage}</Text> : null}
|
{prErrorMessage ? <Text style={styles.actionErrorText}>{prErrorMessage}</Text> : null}
|
||||||
|
|
||||||
<View style={styles.diffContainer}>
|
<View style={styles.diffContainer}>
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ export function useToastHost(): {
|
|||||||
toast: ToastState | null;
|
toast: ToastState | null;
|
||||||
dismiss: () => void;
|
dismiss: () => void;
|
||||||
} {
|
} {
|
||||||
|
const { theme } = useUnistyles();
|
||||||
const [toast, setToast] = useState<ToastState | null>(null);
|
const [toast, setToast] = useState<ToastState | null>(null);
|
||||||
const idRef = useRef(0);
|
const idRef = useRef(0);
|
||||||
|
|
||||||
@@ -84,11 +85,11 @@ export function useToastHost(): {
|
|||||||
copied: (label?: string) =>
|
copied: (label?: string) =>
|
||||||
show(label ? `Copied ${label}` : "Copied", {
|
show(label ? `Copied ${label}` : "Copied", {
|
||||||
variant: "success",
|
variant: "success",
|
||||||
icon: <CheckCircle2 size={18} />,
|
icon: <CheckCircle2 size={18} color={theme.colors.foreground} />,
|
||||||
}),
|
}),
|
||||||
error: (message: string) => show(message, { variant: "error", durationMs: 3200 }),
|
error: (message: string) => show(message, { variant: "error", durationMs: 3200 }),
|
||||||
}),
|
}),
|
||||||
[show],
|
[show, theme.colors.foreground],
|
||||||
);
|
);
|
||||||
|
|
||||||
const dismiss = useCallback(() => {
|
const dismiss = useCallback(() => {
|
||||||
@@ -113,6 +114,8 @@ export function ToastViewport({
|
|||||||
const opacity = useRef(new Animated.Value(0)).current;
|
const opacity = useRef(new Animated.Value(0)).current;
|
||||||
const translateY = useRef(new Animated.Value(-8)).current;
|
const translateY = useRef(new Animated.Value(-8)).current;
|
||||||
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||||
|
const dismissDeadlineRef = useRef<number | null>(null);
|
||||||
|
const remainingDurationRef = useRef(0);
|
||||||
|
|
||||||
const clearTimer = useCallback(() => {
|
const clearTimer = useCallback(() => {
|
||||||
if (timeoutRef.current) {
|
if (timeoutRef.current) {
|
||||||
@@ -143,9 +146,39 @@ export function ToastViewport({
|
|||||||
});
|
});
|
||||||
}, [clearTimer, onDismiss, opacity, translateY]);
|
}, [clearTimer, onDismiss, opacity, translateY]);
|
||||||
|
|
||||||
|
const scheduleDismiss = useCallback(
|
||||||
|
(durationMs: number) => {
|
||||||
|
clearTimer();
|
||||||
|
const nextDurationMs = Math.max(0, durationMs);
|
||||||
|
remainingDurationRef.current = nextDurationMs;
|
||||||
|
dismissDeadlineRef.current = Date.now() + nextDurationMs;
|
||||||
|
timeoutRef.current = setTimeout(() => {
|
||||||
|
animateOut();
|
||||||
|
}, nextDurationMs);
|
||||||
|
},
|
||||||
|
[animateOut, clearTimer],
|
||||||
|
);
|
||||||
|
|
||||||
|
const pauseDismiss = useCallback(() => {
|
||||||
|
if (dismissDeadlineRef.current !== null) {
|
||||||
|
remainingDurationRef.current = Math.max(0, dismissDeadlineRef.current - Date.now());
|
||||||
|
}
|
||||||
|
dismissDeadlineRef.current = null;
|
||||||
|
clearTimer();
|
||||||
|
}, [clearTimer]);
|
||||||
|
|
||||||
|
const resumeDismiss = useCallback(() => {
|
||||||
|
if (!toast) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
scheduleDismiss(remainingDurationRef.current || toast.durationMs);
|
||||||
|
}, [scheduleDismiss, toast]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!toast) {
|
if (!toast) {
|
||||||
clearTimer();
|
clearTimer();
|
||||||
|
dismissDeadlineRef.current = null;
|
||||||
|
remainingDurationRef.current = 0;
|
||||||
opacity.setValue(0);
|
opacity.setValue(0);
|
||||||
translateY.setValue(-8);
|
translateY.setValue(-8);
|
||||||
return;
|
return;
|
||||||
@@ -170,14 +203,13 @@ export function ToastViewport({
|
|||||||
}),
|
}),
|
||||||
]).start();
|
]).start();
|
||||||
|
|
||||||
timeoutRef.current = setTimeout(() => {
|
remainingDurationRef.current = toast.durationMs;
|
||||||
animateOut();
|
scheduleDismiss(toast.durationMs);
|
||||||
}, toast.durationMs);
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
clearTimer();
|
clearTimer();
|
||||||
};
|
};
|
||||||
}, [animateOut, clearTimer, opacity, toast, translateY]);
|
}, [clearTimer, opacity, scheduleDismiss, toast, translateY]);
|
||||||
|
|
||||||
if (!toast) {
|
if (!toast) {
|
||||||
return null;
|
return null;
|
||||||
@@ -202,6 +234,8 @@ export function ToastViewport({
|
|||||||
<View style={styles.container} pointerEvents="box-none">
|
<View style={styles.container} pointerEvents="box-none">
|
||||||
<Animated.View
|
<Animated.View
|
||||||
testID={toast.testID ?? "app-toast"}
|
testID={toast.testID ?? "app-toast"}
|
||||||
|
onPointerEnter={pauseDismiss}
|
||||||
|
onPointerLeave={resumeDismiss}
|
||||||
style={[
|
style={[
|
||||||
styles.toast,
|
styles.toast,
|
||||||
toast.variant === "success" ? styles.toastSuccess : null,
|
toast.variant === "success" ? styles.toastSuccess : null,
|
||||||
@@ -219,7 +253,6 @@ export function ToastViewport({
|
|||||||
<Text
|
<Text
|
||||||
testID="app-toast-message"
|
testID="app-toast-message"
|
||||||
style={[styles.message, toast.variant === "error" ? styles.messageError : null]}
|
style={[styles.message, toast.variant === "error" ? styles.messageError : null]}
|
||||||
numberOfLines={2}
|
|
||||||
>
|
>
|
||||||
{toast.content}
|
{toast.content}
|
||||||
</Text>
|
</Text>
|
||||||
@@ -255,7 +288,7 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
gap: theme.spacing[2],
|
gap: theme.spacing[2],
|
||||||
backgroundColor: theme.colors.surface0,
|
backgroundColor: theme.colors.surface0,
|
||||||
borderRadius: theme.borderRadius.full,
|
borderRadius: theme.borderRadius["2xl"],
|
||||||
borderWidth: theme.borderWidth[1],
|
borderWidth: theme.borderWidth[1],
|
||||||
borderColor: theme.colors.border,
|
borderColor: theme.colors.border,
|
||||||
paddingVertical: theme.spacing[2],
|
paddingVertical: theme.spacing[2],
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { useCheckoutPrStatusQuery } from "@/hooks/use-checkout-pr-status-query";
|
|||||||
import { buildGitActions, type GitActions } from "@/components/git-actions-policy";
|
import { buildGitActions, type GitActions } from "@/components/git-actions-policy";
|
||||||
import { buildNewAgentRoute, resolveNewAgentWorkingDir } from "@/utils/new-agent-routing";
|
import { buildNewAgentRoute, resolveNewAgentWorkingDir } from "@/utils/new-agent-routing";
|
||||||
import { openExternalUrl } from "@/utils/open-external-url";
|
import { openExternalUrl } from "@/utils/open-external-url";
|
||||||
|
import { useToast } from "@/contexts/toast-context";
|
||||||
|
|
||||||
export type { GitActionId, GitAction, GitActions } from "@/components/git-actions-policy";
|
export type { GitActionId, GitAction, GitActions } from "@/components/git-actions-policy";
|
||||||
|
|
||||||
@@ -31,13 +32,12 @@ interface UseGitActionsInput {
|
|||||||
interface UseGitActionsResult {
|
interface UseGitActionsResult {
|
||||||
gitActions: GitActions;
|
gitActions: GitActions;
|
||||||
branchLabel: string;
|
branchLabel: string;
|
||||||
actionError: string | null;
|
|
||||||
isGit: boolean;
|
isGit: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useGitActions({ serverId, cwd, icons }: UseGitActionsInput): UseGitActionsResult {
|
export function useGitActions({ serverId, cwd, icons }: UseGitActionsInput): UseGitActionsResult {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [actionError, setActionError] = useState<string | null>(null);
|
const toast = useToast();
|
||||||
const [postShipArchiveSuggested, setPostShipArchiveSuggested] = useState(false);
|
const [postShipArchiveSuggested, setPostShipArchiveSuggested] = useState(false);
|
||||||
const [shipDefault, setShipDefault] = useState<"merge" | "pr">("merge");
|
const [shipDefault, setShipDefault] = useState<"merge" | "pr">("merge");
|
||||||
|
|
||||||
@@ -125,78 +125,107 @@ export function useGitActions({ serverId, cwd, icons }: UseGitActionsInput): Use
|
|||||||
const runMergeFromBase = useCheckoutGitActionsStore((state) => state.mergeFromBase);
|
const runMergeFromBase = useCheckoutGitActionsStore((state) => state.mergeFromBase);
|
||||||
const runArchiveWorktree = useCheckoutGitActionsStore((state) => state.archiveWorktree);
|
const runArchiveWorktree = useCheckoutGitActionsStore((state) => state.archiveWorktree);
|
||||||
|
|
||||||
|
const toastActionError = useCallback(
|
||||||
|
(error: unknown, fallback: string) => {
|
||||||
|
const message = error instanceof Error ? error.message : fallback;
|
||||||
|
toast.error(message);
|
||||||
|
},
|
||||||
|
[toast],
|
||||||
|
);
|
||||||
|
|
||||||
|
const toastActionSuccess = useCallback(
|
||||||
|
(message: string) => {
|
||||||
|
toast.show(message, { variant: "success" });
|
||||||
|
},
|
||||||
|
[toast],
|
||||||
|
);
|
||||||
|
|
||||||
// Handlers
|
// Handlers
|
||||||
const handleCommit = useCallback(() => {
|
const handleCommit = useCallback(() => {
|
||||||
setActionError(null);
|
void runCommit({ serverId, cwd })
|
||||||
void runCommit({ serverId, cwd }).catch((err) => {
|
.then(() => {
|
||||||
const message = err instanceof Error ? err.message : "Failed to commit";
|
toastActionSuccess("Committed");
|
||||||
setActionError(message);
|
})
|
||||||
});
|
.catch((err) => {
|
||||||
}, [runCommit, serverId, cwd]);
|
toastActionError(err, "Failed to commit");
|
||||||
|
});
|
||||||
|
}, [cwd, runCommit, serverId, toastActionError, toastActionSuccess]);
|
||||||
|
|
||||||
const handlePush = useCallback(() => {
|
const handlePush = useCallback(() => {
|
||||||
setActionError(null);
|
void runPush({ serverId, cwd })
|
||||||
void runPush({ serverId, cwd }).catch((err) => {
|
.then(() => {
|
||||||
const message = err instanceof Error ? err.message : "Failed to push";
|
toastActionSuccess("Pushed");
|
||||||
setActionError(message);
|
})
|
||||||
});
|
.catch((err) => {
|
||||||
}, [runPush, serverId, cwd]);
|
toastActionError(err, "Failed to push");
|
||||||
|
});
|
||||||
|
}, [cwd, runPush, serverId, toastActionError, toastActionSuccess]);
|
||||||
|
|
||||||
const handleCreatePr = useCallback(() => {
|
const handleCreatePr = useCallback(() => {
|
||||||
void persistShipDefault("pr");
|
void persistShipDefault("pr");
|
||||||
setActionError(null);
|
void runCreatePr({ serverId, cwd })
|
||||||
void runCreatePr({ serverId, cwd }).catch((err) => {
|
.then(() => {
|
||||||
const message = err instanceof Error ? err.message : "Failed to create PR";
|
toastActionSuccess("PR created");
|
||||||
setActionError(message);
|
})
|
||||||
});
|
.catch((err) => {
|
||||||
}, [persistShipDefault, runCreatePr, serverId, cwd]);
|
toastActionError(err, "Failed to create PR");
|
||||||
|
});
|
||||||
|
}, [cwd, persistShipDefault, runCreatePr, serverId, toastActionError, toastActionSuccess]);
|
||||||
|
|
||||||
const handleMergeBranch = useCallback(() => {
|
const handleMergeBranch = useCallback(() => {
|
||||||
if (!baseRef) {
|
if (!baseRef) {
|
||||||
setActionError("Base ref unavailable");
|
toast.error("Base ref unavailable");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
void persistShipDefault("merge");
|
void persistShipDefault("merge");
|
||||||
setActionError(null);
|
|
||||||
void runMergeBranch({ serverId, cwd, baseRef })
|
void runMergeBranch({ serverId, cwd, baseRef })
|
||||||
.then(() => {
|
.then(() => {
|
||||||
setPostShipArchiveSuggested(true);
|
setPostShipArchiveSuggested(true);
|
||||||
|
toastActionSuccess("Merged");
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
const message = err instanceof Error ? err.message : "Failed to merge";
|
toastActionError(err, "Failed to merge");
|
||||||
setActionError(message);
|
|
||||||
});
|
});
|
||||||
}, [baseRef, persistShipDefault, runMergeBranch, serverId, cwd]);
|
}, [
|
||||||
|
baseRef,
|
||||||
|
cwd,
|
||||||
|
persistShipDefault,
|
||||||
|
runMergeBranch,
|
||||||
|
serverId,
|
||||||
|
toast,
|
||||||
|
toastActionError,
|
||||||
|
toastActionSuccess,
|
||||||
|
]);
|
||||||
|
|
||||||
const handleMergeFromBase = useCallback(() => {
|
const handleMergeFromBase = useCallback(() => {
|
||||||
if (!baseRef) {
|
if (!baseRef) {
|
||||||
setActionError("Base ref unavailable");
|
toast.error("Base ref unavailable");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setActionError(null);
|
void runMergeFromBase({ serverId, cwd, baseRef })
|
||||||
void runMergeFromBase({ serverId, cwd, baseRef }).catch((err) => {
|
.then(() => {
|
||||||
const message = err instanceof Error ? err.message : "Failed to merge from base";
|
toastActionSuccess("Updated");
|
||||||
setActionError(message);
|
})
|
||||||
});
|
.catch((err) => {
|
||||||
}, [baseRef, runMergeFromBase, serverId, cwd]);
|
toastActionError(err, "Failed to merge from base");
|
||||||
|
});
|
||||||
|
}, [baseRef, cwd, runMergeFromBase, serverId, toast, toastActionError, toastActionSuccess]);
|
||||||
|
|
||||||
const handleArchiveWorktree = useCallback(() => {
|
const handleArchiveWorktree = useCallback(() => {
|
||||||
const worktreePath = status?.cwd;
|
const worktreePath = status?.cwd;
|
||||||
if (!worktreePath) {
|
if (!worktreePath) {
|
||||||
setActionError("Worktree path unavailable");
|
toast.error("Worktree path unavailable");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setActionError(null);
|
|
||||||
const targetWorkingDir = resolveNewAgentWorkingDir(cwd, status ?? null);
|
const targetWorkingDir = resolveNewAgentWorkingDir(cwd, status ?? null);
|
||||||
void runArchiveWorktree({ serverId, cwd, worktreePath })
|
void runArchiveWorktree({ serverId, cwd, worktreePath })
|
||||||
.then(() => {
|
.then(() => {
|
||||||
router.replace(buildNewAgentRoute(serverId, targetWorkingDir));
|
router.replace(buildNewAgentRoute(serverId, targetWorkingDir));
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
const message = err instanceof Error ? err.message : "Failed to archive worktree";
|
toastActionError(err, "Failed to archive worktree");
|
||||||
setActionError(message);
|
|
||||||
});
|
});
|
||||||
}, [runArchiveWorktree, router, serverId, cwd, status]);
|
}, [cwd, router, runArchiveWorktree, serverId, status, toast, toastActionError]);
|
||||||
|
|
||||||
// Derived state
|
// Derived state
|
||||||
const actionsDisabled = !isGit || Boolean(status?.error) || isStatusLoading;
|
const actionsDisabled = !isGit || Boolean(status?.error) || isStatusLoading;
|
||||||
@@ -333,5 +362,5 @@ export function useGitActions({ serverId, cwd, icons }: UseGitActionsInput): Use
|
|||||||
baseRef,
|
baseRef,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return { gitActions, branchLabel, actionError, isGit };
|
return { gitActions, branchLabel, isGit };
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user