diff --git a/packages/app/src/components/explorer-sidebar.tsx b/packages/app/src/components/explorer-sidebar.tsx
index aa9217ef1..0e73d89f3 100644
--- a/packages/app/src/components/explorer-sidebar.tsx
+++ b/packages/app/src/components/explorer-sidebar.tsx
@@ -15,9 +15,13 @@ import { useTranslation } from "react-i18next";
import {
formatPrTabLabel,
PullRequestPane,
+ PullRequestPaneError,
+ PullRequestPaneSkeleton,
PullRequestTabIcon,
usePrPaneData,
} from "@/git/pull-request-panel";
+import { useCheckoutGitActionsStore } from "@/git/actions-store";
+import type { UsePrPaneDataResult } from "@/git/pull-request-panel/use-data";
import {
usePanelStore,
selectIsFileExplorerOpen,
@@ -27,6 +31,7 @@ import {
} from "@/stores/panel-store";
import { useExplorerSidebarAnimation } from "@/contexts/explorer-sidebar-animation-context";
import { useSidebarAnimation } from "@/contexts/sidebar-animation-context";
+import { useToast } from "@/contexts/toast-context";
import { canCloseRightSidebarGesture } from "@/utils/sidebar-animation-state";
import { HEADER_INNER_HEIGHT } from "@/constants/layout";
import { GitDiffPane } from "@/git/diff-pane";
@@ -452,6 +457,7 @@ function ExplorerSidebarContent({
}: SidebarContentProps) {
const { theme } = useUnistyles();
const { t } = useTranslation();
+ const toast = useToast();
const padding = useWindowControlsPadding("explorerSidebar");
const canQueryPullRequest = isGit && Boolean(workspaceRoot);
const prPane = usePrPaneData({
@@ -461,11 +467,17 @@ function ExplorerSidebarContent({
timelineEnabled: activeTab === "pr" && canQueryPullRequest && isOpen,
});
const hasPullRequest = prPane.prNumber !== null;
+ const showPrTab = hasPullRequest || (activeTab === "pr" && prPane.isLoading);
const requestedTab: ExplorerTab =
!isGit && (activeTab === "changes" || activeTab === "pr") ? "files" : activeTab;
- const resolvedTab: ExplorerTab =
- requestedTab === "pr" && !hasPullRequest ? "changes" : requestedTab;
+ const resolvedTab: ExplorerTab = requestedTab === "pr" && !showPrTab ? "changes" : requestedTab;
const prTabLabel = formatPrTabLabel(prPane.prNumber);
+ const refreshGitActions = useCheckoutGitActionsStore((s) => s.refresh);
+ const handlePrRetry = useCallback(() => {
+ refreshGitActions({ serverId, cwd: workspaceRoot }).catch((error) => {
+ toast.error(error instanceof Error ? error.message : t("workspace.git.diff.failedRefresh"));
+ });
+ }, [refreshGitActions, serverId, t, toast, workspaceRoot]);
const workspaceAttachmentScopeKey = useMemo(
() => buildWorkspaceAttachmentScopeKey({ serverId, workspaceId, cwd: workspaceRoot }),
[serverId, workspaceId, workspaceRoot],
@@ -498,7 +510,7 @@ function ExplorerSidebarContent({
onTabPress={onTabPress}
testID="explorer-tab-files"
/>
- {isGit && hasPullRequest && (
+ {isGit && showPrTab && (
)}
- {resolvedTab === "pr" && prPane.data && (
-
)}
@@ -555,6 +568,38 @@ function ExplorerSidebarContent({
);
}
+interface PrTabContentProps {
+ serverId: string;
+ cwd: string;
+ prPane: UsePrPaneDataResult;
+ workspaceAttachmentScopeKey: string;
+ onRetry: () => void;
+}
+
+function PrTabContent({
+ serverId,
+ cwd,
+ prPane,
+ workspaceAttachmentScopeKey,
+ onRetry,
+}: PrTabContentProps) {
+ if (prPane.data) {
+ return (
+
+ );
+ }
+ if (prPane.error) {
+ return ;
+ }
+ return ;
+}
+
// Static styles for Animated.Views — must NOT use Unistyles dynamic theme to
// avoid the "Unable to find node on an unmounted component" crash when Unistyles
// tries to patch the native node that Reanimated also manages.
diff --git a/packages/app/src/git/pull-request-panel/activity-skeleton.tsx b/packages/app/src/git/pull-request-panel/activity-skeleton.tsx
new file mode 100644
index 000000000..72c315f3b
--- /dev/null
+++ b/packages/app/src/git/pull-request-panel/activity-skeleton.tsx
@@ -0,0 +1,87 @@
+import { useEffect, useMemo, useRef } from "react";
+import { Animated, View, type StyleProp, type ViewStyle } from "react-native";
+import { StyleSheet } from "react-native-unistyles";
+
+const ROW_KEYS = [0, 1, 2].map((i) => `pr-activity-skeleton-row-${i}`);
+
+export function useSkeletonPulse(): Animated.Value {
+ const pulse = useRef(new Animated.Value(0)).current;
+ useEffect(() => {
+ const animation = Animated.loop(
+ Animated.sequence([
+ Animated.timing(pulse, { toValue: 1, duration: 1000, useNativeDriver: true }),
+ Animated.timing(pulse, { toValue: 0, duration: 1000, useNativeDriver: true }),
+ ]),
+ );
+ animation.start();
+ return () => animation.stop();
+ }, [pulse]);
+ return pulse;
+}
+
+export function SkeletonPulse({
+ pulse,
+ style,
+}: {
+ pulse: Animated.Value;
+ style: StyleProp;
+}) {
+ const opacity = pulse.interpolate({
+ inputRange: [0, 1],
+ outputRange: [0.4, 0.8],
+ });
+ const pulseStyle = useMemo(() => [style, { opacity }], [style, opacity]);
+ return ;
+}
+
+export function PrActivitySkeleton() {
+ const pulse = useSkeletonPulse();
+
+ return (
+
+ {ROW_KEYS.map((key) => (
+
+
+
+
+
+
+
+ ))}
+
+ );
+}
+
+const styles = StyleSheet.create((theme) => ({
+ container: {
+ paddingHorizontal: theme.spacing[3],
+ gap: theme.spacing[3],
+ },
+ row: {
+ flexDirection: "row",
+ alignItems: "center",
+ gap: theme.spacing[2],
+ },
+ avatar: {
+ width: 20,
+ height: 20,
+ borderRadius: theme.borderRadius.full,
+ backgroundColor: theme.colors.surface2,
+ },
+ lines: {
+ flex: 1,
+ gap: theme.spacing[1],
+ },
+ lineWide: {
+ width: "70%",
+ height: 12,
+ borderRadius: theme.borderRadius.sm,
+ backgroundColor: theme.colors.surface2,
+ },
+ lineNarrow: {
+ width: "45%",
+ height: 10,
+ borderRadius: theme.borderRadius.sm,
+ backgroundColor: theme.colors.surface2,
+ },
+}));
diff --git a/packages/app/src/git/pull-request-panel/index.ts b/packages/app/src/git/pull-request-panel/index.ts
index 0f7e4a820..065625c31 100644
--- a/packages/app/src/git/pull-request-panel/index.ts
+++ b/packages/app/src/git/pull-request-panel/index.ts
@@ -1,4 +1,6 @@
export { PullRequestPane } from "./pane";
+export { PullRequestPaneError } from "./pane-error";
+export { PullRequestPaneSkeleton } from "./pane-skeleton";
export { PullRequestTabIcon } from "./tab-icon";
export { formatPrTabLabel } from "./tab-label";
export { prPaneTimelineQueryKind } from "./query-keys";
diff --git a/packages/app/src/git/pull-request-panel/pane-error.tsx b/packages/app/src/git/pull-request-panel/pane-error.tsx
new file mode 100644
index 000000000..1e3cef516
--- /dev/null
+++ b/packages/app/src/git/pull-request-panel/pane-error.tsx
@@ -0,0 +1,39 @@
+import { Text, View } from "react-native";
+import { StyleSheet } from "react-native-unistyles";
+import { RotateCw } from "lucide-react-native";
+import { useTranslation } from "react-i18next";
+import { Button } from "@/components/ui/button";
+
+export function PullRequestPaneError({
+ onRetry,
+ message,
+}: {
+ onRetry: () => void;
+ message?: string;
+}) {
+ const { t } = useTranslation();
+ return (
+
+ {message ?? t("workspace.git.diff.failedRefresh")}
+
+
+ );
+}
+
+const styles = StyleSheet.create((theme) => ({
+ root: {
+ flex: 1,
+ alignItems: "center",
+ justifyContent: "center",
+ gap: theme.spacing[3],
+ paddingHorizontal: theme.spacing[4],
+ backgroundColor: theme.colors.surfaceSidebar,
+ },
+ message: {
+ fontSize: theme.fontSize.xs,
+ color: theme.colors.foregroundMuted,
+ textAlign: "center",
+ },
+}));
diff --git a/packages/app/src/git/pull-request-panel/pane-skeleton.tsx b/packages/app/src/git/pull-request-panel/pane-skeleton.tsx
new file mode 100644
index 000000000..12e78dc16
--- /dev/null
+++ b/packages/app/src/git/pull-request-panel/pane-skeleton.tsx
@@ -0,0 +1,113 @@
+import { Text, View } from "react-native";
+import { StyleSheet } from "react-native-unistyles";
+import { useTranslation } from "react-i18next";
+import { PrActivitySkeleton, SkeletonPulse, useSkeletonPulse } from "./activity-skeleton";
+
+const CHECK_ROW_KEYS = [0, 1, 2].map((i) => `pr-pane-skeleton-check-${i}`);
+
+export function PullRequestPaneSkeleton() {
+ const { t } = useTranslation();
+ const pulse = useSkeletonPulse();
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ {t("workspace.git.pr.sections.checks")}
+
+ {CHECK_ROW_KEYS.map((key) => (
+
+
+
+
+ ))}
+
+
+
+
+
+
+
+ );
+}
+
+const styles = StyleSheet.create((theme) => ({
+ root: {
+ flex: 1,
+ minHeight: 0,
+ backgroundColor: theme.colors.surfaceSidebar,
+ },
+ header: {
+ gap: theme.spacing[2],
+ paddingHorizontal: theme.spacing[4],
+ paddingVertical: theme.spacing[4],
+ },
+ title: {
+ width: "75%",
+ height: 16,
+ borderRadius: theme.borderRadius.sm,
+ backgroundColor: theme.colors.surface2,
+ },
+ subtitle: {
+ width: "40%",
+ height: 12,
+ borderRadius: theme.borderRadius.sm,
+ backgroundColor: theme.colors.surface2,
+ },
+ toolbar: {
+ flexDirection: "row",
+ alignItems: "center",
+ gap: theme.spacing[2],
+ paddingHorizontal: theme.spacing[3],
+ paddingVertical: theme.spacing[2],
+ borderBottomWidth: 1,
+ borderBottomColor: theme.colors.border,
+ },
+ toolbarButton: {
+ width: 96,
+ height: 24,
+ borderRadius: theme.borderRadius.lg,
+ backgroundColor: theme.colors.surface2,
+ },
+ section: {
+ paddingVertical: theme.spacing[2],
+ },
+ sectionTitle: {
+ fontSize: theme.fontSize.xs,
+ fontWeight: theme.fontWeight.medium,
+ color: theme.colors.foregroundMuted,
+ paddingHorizontal: theme.spacing[3],
+ paddingVertical: theme.spacing[2],
+ },
+ checks: {
+ gap: theme.spacing[1],
+ },
+ checkRow: {
+ flexDirection: "row",
+ alignItems: "center",
+ gap: theme.spacing[2],
+ paddingHorizontal: theme.spacing[3],
+ minHeight: 32,
+ },
+ checkDot: {
+ width: 14,
+ height: 14,
+ borderRadius: theme.borderRadius.full,
+ backgroundColor: theme.colors.surface2,
+ },
+ checkName: {
+ width: "60%",
+ height: 12,
+ borderRadius: theme.borderRadius.sm,
+ backgroundColor: theme.colors.surface2,
+ },
+}));
diff --git a/packages/app/src/git/pull-request-panel/pane.tsx b/packages/app/src/git/pull-request-panel/pane.tsx
index 4e1e10872..1f487af6d 100644
--- a/packages/app/src/git/pull-request-panel/pane.tsx
+++ b/packages/app/src/git/pull-request-panel/pane.tsx
@@ -25,10 +25,13 @@ import {
MessageSquare,
MessageSquarePlus,
MoreHorizontal,
+ RotateCw,
} from "lucide-react-native";
import type { PressableStateCallbackType } from "react-native";
+import { useTranslation } from "react-i18next";
import { openExternalUrl } from "@/utils/open-external-url";
import { Button } from "@/components/ui/button";
+import { LoadingSpinner } from "@/components/ui/loading-spinner";
import { StatusBadge } from "@/components/ui/status-badge";
import {
DropdownMenu,
@@ -42,9 +45,12 @@ import { writeMarkdownToRichClipboard } from "@/utils/rich-clipboard";
import { useHostRuntimeClient } from "@/runtime/host-runtime";
import { useSessionStore } from "@/stores/session-store";
import { useWorkspaceAttachmentsStore } from "@/attachments/workspace-attachments-store";
+import { useToast } from "@/contexts/toast-context";
+import { useCheckoutGitActionsStore } from "@/git/actions-store";
import { isNative } from "@/constants/platform";
-import { useIsCompactFormFactor } from "@/constants/layout";
-import type { Theme } from "@/styles/theme";
+import { useIsCompactFormFactor, WORKSPACE_SECONDARY_HEADER_HEIGHT } from "@/constants/layout";
+import { ICON_SIZE, type Theme } from "@/styles/theme";
+import { PrActivitySkeleton } from "./activity-skeleton";
import {
collapseActivity,
expandActivity,
@@ -85,6 +91,8 @@ const ThemedGitPullRequestDraft = withUnistyles(GitPullRequestDraft);
const ThemedMessageSquare = withUnistyles(MessageSquare);
const ThemedMessageSquarePlus = withUnistyles(MessageSquarePlus);
const ThemedMoreHorizontal = withUnistyles(MoreHorizontal);
+const ThemedRotateCw = withUnistyles(RotateCw);
+const ThemedLoadingSpinner = withUnistyles(LoadingSpinner);
const foregroundColorMapping = (theme: Theme) => ({ color: theme.colors.foreground });
const foregroundMutedColorMapping = (theme: Theme) => ({ color: theme.colors.foregroundMuted });
@@ -138,6 +146,13 @@ function kebabTriggerStyle({
return [styles.kebabButton, hovered && styles.kebabButtonHovered];
}
+function refreshButtonStyle({
+ hovered = false,
+ pressed = false,
+}: PressableStateCallbackType & { hovered?: boolean }) {
+ return [styles.refreshButton, (hovered || pressed) && styles.refreshButtonHovered];
+}
+
function renderKebabTriggerIcon({ hovered }: { hovered?: boolean }) {
return (
state.sessions[serverId]?.serverInfo?.features?.githubCheckDetails === true,
@@ -199,6 +218,24 @@ export function PullRequestPane({
void openExternalUrl(data.url);
}, [data.url]);
+ const refreshSupported = useSessionStore(
+ (state) => state.sessions[serverId]?.serverInfo?.features?.checkoutRefresh === true,
+ );
+ const runRefresh = useCheckoutGitActionsStore((state) => state.refresh);
+ const isRefreshing =
+ useCheckoutGitActionsStore((state) =>
+ state.getStatus({ serverId, cwd, actionId: "refresh" }),
+ ) === "pending";
+
+ const handleRefresh = useCallback(() => {
+ if (isRefreshing) {
+ return;
+ }
+ void runRefresh({ serverId, cwd }).catch((error) => {
+ toast.error(error instanceof Error ? error.message : t("workspace.git.diff.failedRefresh"));
+ });
+ }, [cwd, isRefreshing, runRefresh, serverId, t, toast]);
+
const handleToggleChecks = useCallback(() => {
setChecksOpen((open) => !open);
}, []);
@@ -395,6 +432,47 @@ export function PullRequestPane({
return (
+
+
+
+
+ {refreshSupported ? (
+
+
+ {isRefreshing ? (
+
+ ) : (
+
+ )}
+
+
+ ) : null}
+
+
{({ hovered }) => (
<>
@@ -420,8 +498,6 @@ export function PullRequestPane({
)}
-
-
) : null}
- {visibleEntries.length === 0 ? (
+ {activityLoading ? : null}
+ {!activityLoading && visibleEntries.length === 0 ? (
No activity yet
- ) : (
- visibleEntries.map(({ entry, collapsed }) => (
-
- ))
- )}
+ ) : null}
+ {!activityLoading
+ ? visibleEntries.map(({ entry, collapsed }) => (
+
+ ))
+ : null}
@@ -1236,6 +1315,48 @@ const styles = StyleSheet.create((theme) => ({
height: 1,
backgroundColor: theme.colors.border,
},
+ toolbar: {
+ height: WORKSPACE_SECONDARY_HEADER_HEIGHT,
+ flexDirection: "row",
+ alignItems: "center",
+ justifyContent: "space-between",
+ borderBottomWidth: 1,
+ borderBottomColor: theme.colors.border,
+ paddingTop: theme.spacing[2],
+ paddingRight: theme.spacing[3],
+ paddingBottom: theme.spacing[2],
+ paddingLeft: theme.spacing[3],
+ },
+ toolbarActions: {
+ flexDirection: "row",
+ alignItems: "center",
+ gap: theme.spacing[1],
+ },
+ viewButton: {
+ gap: theme.spacing[1],
+ minHeight: 24,
+ height: 24,
+ paddingVertical: 0,
+ paddingHorizontal: theme.spacing[1],
+ borderRadius: theme.borderRadius.base,
+ },
+ refreshButton: {
+ marginLeft: "auto",
+ width: 22,
+ height: 22,
+ borderRadius: theme.borderRadius.md,
+ alignItems: "center",
+ justifyContent: "center",
+ },
+ refreshButtonHovered: {
+ backgroundColor: theme.colors.surface2,
+ },
+ refreshIcon: {
+ width: ICON_SIZE.md,
+ height: ICON_SIZE.md,
+ alignItems: "center",
+ justifyContent: "center",
+ },
sectionHeader: {
flexDirection: "row",
alignItems: "center",
diff --git a/packages/app/src/git/pull-request-panel/use-data.test.ts b/packages/app/src/git/pull-request-panel/use-data.test.ts
index 11c1f1227..c3c5109dc 100644
--- a/packages/app/src/git/pull-request-panel/use-data.test.ts
+++ b/packages/app/src/git/pull-request-panel/use-data.test.ts
@@ -435,6 +435,37 @@ describe("selectPrPaneState", () => {
expect(state.isLoading).toBe(false);
});
+ it("reports activityLoading while the timeline is pending its first response", () => {
+ const state = selectPrPaneState({
+ ...baseSelectInput,
+ status: prStatus(),
+ shouldFetchTimeline: true,
+ timelineIsLoading: true,
+ });
+ expect(state.activityLoading).toBe(true);
+ });
+
+ it("does not report activityLoading when no timeline fetch was scheduled", () => {
+ const state = selectPrPaneState({
+ ...baseSelectInput,
+ status: prStatus(),
+ shouldFetchTimeline: false,
+ timelineIsLoading: true,
+ });
+ expect(state.activityLoading).toBe(false);
+ });
+
+ it("does not report activityLoading once the timeline payload resolves", () => {
+ const state = selectPrPaneState({
+ ...baseSelectInput,
+ status: prStatus(),
+ shouldFetchTimeline: true,
+ timelineIsLoading: false,
+ timelinePayload: timelinePayload(),
+ });
+ expect(state.activityLoading).toBe(false);
+ });
+
it("reports refreshing during background revalidation of either query", () => {
expect(
selectPrPaneState({
diff --git a/packages/app/src/git/pull-request-panel/use-data.ts b/packages/app/src/git/pull-request-panel/use-data.ts
index e3cd9557e..b99cdfdf6 100644
--- a/packages/app/src/git/pull-request-panel/use-data.ts
+++ b/packages/app/src/git/pull-request-panel/use-data.ts
@@ -27,6 +27,7 @@ export interface UsePrPaneDataResult {
data: PrPaneData | null;
prNumber: number | null;
isLoading: boolean;
+ activityLoading: boolean;
isRefreshing: boolean;
error: Error | null;
githubFeaturesEnabled: boolean;
@@ -167,13 +168,14 @@ export function selectPrPaneState(input: SelectPrPaneStateInput): UsePrPaneDataR
: mapPrPaneData(input.status, input.timelinePayload);
const statusRefreshing = input.statusIsFetching && !input.statusIsLoading;
const timelineRefreshing = input.timelineIsFetching && !input.timelineIsLoading;
+ const timelinePending =
+ input.shouldFetchTimeline && input.timelineIsLoading && input.timelinePayload === undefined;
return {
data,
prNumber: identity.prNumber,
- isLoading:
- input.statusIsLoading ||
- (input.shouldFetchTimeline && input.timelineIsLoading && input.timelinePayload === undefined),
+ isLoading: input.statusIsLoading || timelinePending,
+ activityLoading: timelinePending,
isRefreshing: statusRefreshing || timelineRefreshing,
error: firstNonSuppressedError({
statusPayloadError: input.statusPayloadError,
diff --git a/packages/app/src/git/use-actions.tsx b/packages/app/src/git/use-actions.tsx
index bdf74b323..22491d60f 100644
--- a/packages/app/src/git/use-actions.tsx
+++ b/packages/app/src/git/use-actions.tsx
@@ -7,6 +7,7 @@ import { type CheckoutPrStatusPayload, useCheckoutPrStatusQuery } from "@/git/us
import {
buildGitActions,
narrowPullRequestState,
+ type BuildGitActionsInput,
type GitAction,
type GitActions,
} from "@/git/policy";
@@ -568,8 +569,8 @@ export function useGitActions({ serverId, cwd, icons }: UseGitActionsInput): Use
}, [prStatus?.url, handleCreatePr]);
// Build actions
- const gitActions: GitActions = useMemo(() => {
- const actions = buildGitActions({
+ const gitActionsInput = useMemo(
+ () => ({
isGit,
githubFeaturesEnabled,
githubAutoMergeActionsEnabled,
@@ -684,61 +685,66 @@ export function useGitActions({ serverId, cwd, icons }: UseGitActionsInput): Use
handler: handleArchiveWorktree,
},
},
- });
- return translateGitActions(actions, { baseRefLabel, hasPullRequest, t });
- }, [
- t,
- isGit,
- hasRemote,
- hasPullRequest,
- prStatus?.url,
- prStatus?.state,
- prStatus?.isDraft,
- prStatus?.isMerged,
- prStatus?.mergeable,
- prStatus?.github,
- aheadCount,
- behindBaseCount,
- isPaseoOwnedWorktree,
- isOnBaseBranch,
- githubFeaturesEnabled,
- githubAutoMergeActionsEnabled,
- hasUncommittedChanges,
- aheadOfOrigin,
- behindOfOrigin,
- shipDefault,
- baseRefLabel,
- shouldPromoteArchive,
- actionsDisabled,
- commitStatus,
- pullStatus,
- pushStatus,
- pullAndPushStatus,
- prCreateStatus,
- mergePrStatuses.squash,
- mergePrStatuses.merge,
- mergePrStatuses.rebase,
- enablePrAutoMergeStatuses.squash,
- enablePrAutoMergeStatuses.merge,
- enablePrAutoMergeStatuses.rebase,
- disablePrAutoMergeStatus,
- mergeStatus,
- mergeFromBaseStatus,
- archiveStatus,
- handleCommit,
- handlePull,
- handlePush,
- handlePullAndPush,
- handlePrAction,
- handleMergePr,
- handleEnablePrAutoMerge,
- handleDisablePrAutoMerge,
- handleMergeBranch,
- handleMergeFromBase,
- handleArchiveWorktree,
- icons,
- baseRef,
- ]);
+ }),
+ [
+ isGit,
+ hasRemote,
+ hasPullRequest,
+ prStatus?.url,
+ prStatus?.state,
+ prStatus?.isDraft,
+ prStatus?.isMerged,
+ prStatus?.mergeable,
+ prStatus?.github,
+ aheadCount,
+ behindBaseCount,
+ isPaseoOwnedWorktree,
+ isOnBaseBranch,
+ githubFeaturesEnabled,
+ githubAutoMergeActionsEnabled,
+ hasUncommittedChanges,
+ aheadOfOrigin,
+ behindOfOrigin,
+ shipDefault,
+ baseRefLabel,
+ shouldPromoteArchive,
+ actionsDisabled,
+ commitStatus,
+ pullStatus,
+ pushStatus,
+ pullAndPushStatus,
+ prCreateStatus,
+ mergePrStatuses.squash,
+ mergePrStatuses.merge,
+ mergePrStatuses.rebase,
+ enablePrAutoMergeStatuses.squash,
+ enablePrAutoMergeStatuses.merge,
+ enablePrAutoMergeStatuses.rebase,
+ disablePrAutoMergeStatus,
+ mergeStatus,
+ mergeFromBaseStatus,
+ archiveStatus,
+ handleCommit,
+ handlePull,
+ handlePush,
+ handlePullAndPush,
+ handlePrAction,
+ handleMergePr,
+ handleEnablePrAutoMerge,
+ handleDisablePrAutoMerge,
+ handleMergeBranch,
+ handleMergeFromBase,
+ handleArchiveWorktree,
+ icons,
+ baseRef,
+ ],
+ );
+
+ const gitActions: GitActions = useMemo(
+ () =>
+ translateGitActions(buildGitActions(gitActionsInput), { baseRefLabel, hasPullRequest, t }),
+ [gitActionsInput, baseRefLabel, hasPullRequest, t],
+ );
return { gitActions, branchLabel, isGit };
}
diff --git a/packages/app/src/i18n/resources.test.ts b/packages/app/src/i18n/resources.test.ts
index 42a56a4dc..aa935da05 100644
--- a/packages/app/src/i18n/resources.test.ts
+++ b/packages/app/src/i18n/resources.test.ts
@@ -243,6 +243,7 @@ describe("translation resources", () => {
expect(en.workspace.git.actions.commit.label).toBe("Commit");
expect(en.workspace.git.diff.binaryFile).toBe("Binary file");
expect(en.workspace.git.pr.sections.checks).toBe("Checks");
+ expect(en.workspace.git.pr.actions.viewPullRequest).toBe("View");
expect(en.review.comment.placeholder).toBe("Leave a comment");
});
diff --git a/packages/app/src/i18n/resources/ar.ts b/packages/app/src/i18n/resources/ar.ts
index db1638913..9339ed33e 100644
--- a/packages/app/src/i18n/resources/ar.ts
+++ b/packages/app/src/i18n/resources/ar.ts
@@ -717,6 +717,9 @@ export const ar: TranslationResources = {
failedOpen: "فشل في فتح مساحة العمل",
},
pr: {
+ actions: {
+ viewPullRequest: "عرض",
+ },
sections: {
checks: "الشيكات",
reviews: "التعليقات",
diff --git a/packages/app/src/i18n/resources/en.ts b/packages/app/src/i18n/resources/en.ts
index 9a56ab44b..efca11b2b 100644
--- a/packages/app/src/i18n/resources/en.ts
+++ b/packages/app/src/i18n/resources/en.ts
@@ -724,6 +724,9 @@ export const en = {
failedOpen: "Failed to open workspace",
},
pr: {
+ actions: {
+ viewPullRequest: "View",
+ },
sections: {
checks: "Checks",
reviews: "Reviews",
diff --git a/packages/app/src/i18n/resources/es.ts b/packages/app/src/i18n/resources/es.ts
index 13cd887f1..95d572554 100644
--- a/packages/app/src/i18n/resources/es.ts
+++ b/packages/app/src/i18n/resources/es.ts
@@ -744,6 +744,9 @@ export const es: TranslationResources = {
failedOpen: "No se pudo abrir el espacio de trabajo",
},
pr: {
+ actions: {
+ viewPullRequest: "Ver",
+ },
sections: {
checks: "cheques",
reviews: "Reseñas",
diff --git a/packages/app/src/i18n/resources/fr.ts b/packages/app/src/i18n/resources/fr.ts
index 7cea39611..520b7c18b 100644
--- a/packages/app/src/i18n/resources/fr.ts
+++ b/packages/app/src/i18n/resources/fr.ts
@@ -743,6 +743,9 @@ export const fr: TranslationResources = {
failedOpen: "Échec de l'ouverture de l'espace de travail",
},
pr: {
+ actions: {
+ viewPullRequest: "Voir",
+ },
sections: {
checks: "Chèques",
reviews: "Avis",
diff --git a/packages/app/src/i18n/resources/ru.ts b/packages/app/src/i18n/resources/ru.ts
index c07851afb..872e7de65 100644
--- a/packages/app/src/i18n/resources/ru.ts
+++ b/packages/app/src/i18n/resources/ru.ts
@@ -736,6 +736,9 @@ export const ru: TranslationResources = {
failedOpen: "Не удалось открыть рабочую область",
},
pr: {
+ actions: {
+ viewPullRequest: "Открыть",
+ },
sections: {
checks: "Чеки",
reviews: "Отзывы",
diff --git a/packages/app/src/i18n/resources/zh-CN.ts b/packages/app/src/i18n/resources/zh-CN.ts
index 4c9b9ecf3..1958a2515 100644
--- a/packages/app/src/i18n/resources/zh-CN.ts
+++ b/packages/app/src/i18n/resources/zh-CN.ts
@@ -711,6 +711,9 @@ export const zhCN: TranslationResources = {
failedOpen: "打开 workspace 失败",
},
pr: {
+ actions: {
+ viewPullRequest: "查看",
+ },
sections: {
checks: "Checks",
reviews: "Reviews",