fix(app): open Changes on the populated comparison (#3636)
* fix(app): restore smart Changes comparison selection The tab migration persisted the selected comparison, pinning Changes to an empty view after checkout state changed. Keep comparison overrides ephemeral per surface and expire them when dirtiness changes, while preserving only presentation state in tabs. * chore(app): date Changes compatibility cleanup
This commit is contained in:
@@ -69,6 +69,7 @@ import { useToast } from "@/contexts/toast-context";
|
||||
import { useSessionStore } from "@/stores/session-store";
|
||||
import { confirmDialog } from "@/utils/confirm-dialog";
|
||||
import { LoadingSpinner } from "@/components/ui/loading-spinner";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
PaneContentToolbar,
|
||||
paneContentToolbarIconSize,
|
||||
@@ -80,6 +81,7 @@ import type { WorkspaceTabTarget } from "@/workspace-tabs/model";
|
||||
import { buildWorkspaceTabPersistenceKey } from "@/workspace-tabs/model";
|
||||
import { isWeb } from "@/constants/platform";
|
||||
import { usePublishWorkingDiffAttachment, useWorkingDiff } from "@/git/use-working-diff";
|
||||
import type { CheckoutStatusPayload } from "@/git/use-status-query";
|
||||
import { DiffTooLargeState } from "@/git/diff-too-large-state";
|
||||
import { openDesktopTarget, useDesktopOpenTargets } from "@/workspace/desktop-open-targets";
|
||||
|
||||
@@ -165,6 +167,7 @@ interface ChangesSurfaceProps {
|
||||
cwd: string;
|
||||
enabled?: boolean;
|
||||
host: "explorer" | "panel";
|
||||
modeScope: string;
|
||||
focusPath?: string;
|
||||
focusRequestId?: number;
|
||||
onOpenFile?: (path: string) => void;
|
||||
@@ -607,6 +610,7 @@ interface DiffBodyContentProps {
|
||||
diffTooLarge: boolean;
|
||||
hasChanges: boolean;
|
||||
emptyMessage: string;
|
||||
emptyAction: ChangesEmptyAction | null;
|
||||
children: ReactElement;
|
||||
checkingRepositoryLabel: string;
|
||||
notRepositoryLabel: string;
|
||||
@@ -621,6 +625,7 @@ function DiffBodyContent({
|
||||
diffTooLarge,
|
||||
hasChanges,
|
||||
emptyMessage,
|
||||
emptyAction,
|
||||
children,
|
||||
checkingRepositoryLabel,
|
||||
notRepositoryLabel,
|
||||
@@ -668,6 +673,16 @@ function DiffBodyContent({
|
||||
return (
|
||||
<View style={styles.emptyContainer}>
|
||||
<Text style={styles.emptyText}>{emptyMessage}</Text>
|
||||
{emptyAction ? (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="xs"
|
||||
testID="changes-empty-switch-mode"
|
||||
onPress={emptyAction.onPress}
|
||||
>
|
||||
{emptyAction.label}
|
||||
</Button>
|
||||
) : null}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -690,6 +705,32 @@ function computeCommittedDiffDescription(
|
||||
return branchLabel === baseRefLabel ? undefined : `${branchLabel} -> ${baseRefLabel}`;
|
||||
}
|
||||
|
||||
interface ChangesEmptyAction {
|
||||
label: string;
|
||||
onPress: () => void;
|
||||
}
|
||||
|
||||
function computeChangesEmptyAction(input: {
|
||||
hideWhitespace: boolean;
|
||||
diffMode: "uncommitted" | "base";
|
||||
status: CheckoutStatusPayload | null;
|
||||
seeUncommittedLabel: string;
|
||||
seeCommittedLabel: string;
|
||||
selectUncommitted: () => void;
|
||||
selectBase: () => void;
|
||||
}): ChangesEmptyAction | null {
|
||||
if (input.hideWhitespace || !input.status?.isGit) {
|
||||
return null;
|
||||
}
|
||||
if (input.diffMode === "base" && input.status.isDirty) {
|
||||
return { label: input.seeUncommittedLabel, onPress: input.selectUncommitted };
|
||||
}
|
||||
if (input.diffMode === "uncommitted" && (input.status.aheadBehind?.ahead ?? 0) > 0) {
|
||||
return { label: input.seeCommittedLabel, onPress: input.selectBase };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function computePrErrorMessage(
|
||||
githubFeaturesEnabled: boolean,
|
||||
prPayloadError: { message?: string } | null | undefined,
|
||||
@@ -1012,6 +1053,7 @@ export function ChangesSurface({
|
||||
cwd,
|
||||
enabled,
|
||||
host,
|
||||
modeScope,
|
||||
focusPath,
|
||||
focusRequestId,
|
||||
onOpenFile,
|
||||
@@ -1059,11 +1101,6 @@ export function ChangesSurface({
|
||||
layout: instanceState.layout === "unified" ? "split" : "unified",
|
||||
});
|
||||
}, [instanceState, updateState]);
|
||||
const handleDiffModeChange = useCallback(
|
||||
(mode: "uncommitted" | "base") => updateState({ ...instanceState, mode }),
|
||||
[instanceState, updateState],
|
||||
);
|
||||
|
||||
const codeFontSize = appSettings.codeFontSize;
|
||||
|
||||
const overflowToggleStyle = useMemo(() => buildOverflowButtonStyle(isMobile), [isMobile]);
|
||||
@@ -1127,9 +1164,7 @@ export function ChangesSurface({
|
||||
cwd,
|
||||
ignoreWhitespace: instanceState.hideWhitespace,
|
||||
enabled: enabled !== false,
|
||||
mode: instanceState.mode,
|
||||
baseRef: instanceState.baseRef,
|
||||
onModeChange: handleDiffModeChange,
|
||||
modeScope,
|
||||
});
|
||||
usePublishWorkingDiffAttachment({
|
||||
serverId,
|
||||
@@ -1332,6 +1367,15 @@ export function ChangesSurface({
|
||||
uncommitted: t("workspace.git.diff.emptyUncommitted"),
|
||||
againstBase: (label) => t("workspace.git.diff.emptyAgainstBase", { baseRef: label }),
|
||||
});
|
||||
const emptyAction = computeChangesEmptyAction({
|
||||
hideWhitespace: instanceState.hideWhitespace,
|
||||
diffMode,
|
||||
status,
|
||||
seeUncommittedLabel: t("workspace.git.diff.seeUncommittedChanges"),
|
||||
seeCommittedLabel: t("workspace.git.diff.seeCommittedChanges"),
|
||||
selectUncommitted: handleSelectUncommitted,
|
||||
selectBase: handleSelectBase,
|
||||
});
|
||||
|
||||
const diffContent: ReactElement = (
|
||||
<DiffBodyContent
|
||||
@@ -1343,6 +1387,7 @@ export function ChangesSurface({
|
||||
diffTooLarge={diffTooLarge}
|
||||
hasChanges={hasChanges}
|
||||
emptyMessage={emptyMessage}
|
||||
emptyAction={emptyAction}
|
||||
checkingRepositoryLabel={t("workspace.git.diff.checkingRepository")}
|
||||
notRepositoryLabel={t("workspace.git.diff.notRepository")}
|
||||
>
|
||||
@@ -1549,6 +1594,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
paddingTop: theme.spacing[16],
|
||||
gap: theme.spacing[2],
|
||||
},
|
||||
emptyText: {
|
||||
fontSize: theme.fontSize.base,
|
||||
|
||||
@@ -5,8 +5,11 @@ import {
|
||||
} from "@/attachments/workspace-attachments-store";
|
||||
import {
|
||||
buildReviewDraftKey,
|
||||
buildReviewDraftScopeKey,
|
||||
useInlineReviewController,
|
||||
useResolvedDiffMode,
|
||||
useReviewAttachmentSnapshot,
|
||||
useSetDiffModeOverride,
|
||||
} from "@/review";
|
||||
import { useCheckoutDiffQuery } from "@/git/use-diff-query";
|
||||
import { useCheckoutStatusQuery } from "@/git/use-status-query";
|
||||
@@ -18,9 +21,7 @@ interface UseWorkingDiffOptions {
|
||||
ignoreWhitespace: boolean;
|
||||
enabled: boolean;
|
||||
queryScope?: string;
|
||||
mode?: "uncommitted" | "base";
|
||||
baseRef?: string;
|
||||
onModeChange?: (mode: "uncommitted" | "base") => void;
|
||||
modeScope: string;
|
||||
}
|
||||
|
||||
export function useWorkingDiff({
|
||||
@@ -30,9 +31,7 @@ export function useWorkingDiff({
|
||||
ignoreWhitespace,
|
||||
enabled,
|
||||
queryScope,
|
||||
mode,
|
||||
baseRef: selectedBaseRef,
|
||||
onModeChange,
|
||||
modeScope,
|
||||
}: UseWorkingDiffOptions) {
|
||||
const {
|
||||
status,
|
||||
@@ -46,26 +45,44 @@ export function useWorkingDiff({
|
||||
const statusErrorMessage =
|
||||
status?.error?.message ??
|
||||
(isStatusError && statusError instanceof Error ? statusError.message : null);
|
||||
const baseRef = selectedBaseRef ?? gitStatus?.baseRef ?? undefined;
|
||||
const baseRef = gitStatus?.baseRef ?? undefined;
|
||||
const hasUncommittedChanges = Boolean(gitStatus?.isDirty);
|
||||
const currentBranchName =
|
||||
gitStatus?.currentBranch && gitStatus.currentBranch !== "HEAD" ? gitStatus.currentBranch : null;
|
||||
|
||||
const reviewDraftScopeKey = useMemo(
|
||||
() =>
|
||||
buildReviewDraftScopeKey({
|
||||
serverId,
|
||||
workspaceId,
|
||||
cwd,
|
||||
baseRef,
|
||||
ignoreWhitespace,
|
||||
}),
|
||||
[baseRef, cwd, ignoreWhitespace, serverId, workspaceId],
|
||||
);
|
||||
const modeScopeKey = `${reviewDraftScopeKey}:surface=${encodeURIComponent(modeScope)}`;
|
||||
const diffMode = useResolvedDiffMode({
|
||||
scopeKey: modeScopeKey,
|
||||
hasUncommittedChanges,
|
||||
});
|
||||
const setDiffModeOverride = useSetDiffModeOverride();
|
||||
const selectDiffMode = useCallback(
|
||||
(nextMode: "uncommitted" | "base") => {
|
||||
if (onModeChange) {
|
||||
onModeChange(nextMode);
|
||||
return;
|
||||
}
|
||||
// A Changes surface always supplies its tab-owned presentation. Keep this
|
||||
// fallback deterministic for legacy non-tab consumers without mutating
|
||||
// workspace-wide review preferences.
|
||||
setDiffModeOverride({
|
||||
scopeKey: modeScopeKey,
|
||||
override: {
|
||||
serverId,
|
||||
cwd,
|
||||
mode: nextMode,
|
||||
isDirtyAtSelection: hasUncommittedChanges,
|
||||
},
|
||||
});
|
||||
},
|
||||
[onModeChange],
|
||||
[cwd, hasUncommittedChanges, modeScopeKey, serverId, setDiffModeOverride],
|
||||
);
|
||||
const selectUncommitted = useCallback(() => selectDiffMode("uncommitted"), [selectDiffMode]);
|
||||
const selectBase = useCallback(() => selectDiffMode("base"), [selectDiffMode]);
|
||||
const diffMode = mode ?? (hasUncommittedChanges ? "uncommitted" : "base");
|
||||
|
||||
const {
|
||||
files,
|
||||
|
||||
Reference in New Issue
Block a user