Files
paseo/packages/app/src/git/use-working-diff.ts
T
Mohamed Boudra 49784fac15 Keep Changes and working diff comparisons in sync (#4199)
* fix(app): keep Changes comparison and diff in sync

Own the working comparison at the workspace checkout so the Changes tree and adjacent working diff share it. Immutable commit diffs remain scoped to their commit SHA.

* docs(app): align working diff ownership notes
2026-09-02 12:38:16 +02:00

151 lines
3.9 KiB
TypeScript

import { useCallback, useEffect, useMemo } from "react";
import {
buildWorkspaceAttachmentScopeKey,
useWorkspaceAttachmentsStore,
} from "@/attachments/workspace-attachments-store";
import {
buildReviewDraftKey,
useInlineReviewController,
useReviewAttachmentSnapshot,
} from "@/review";
import { useCheckoutDiffQuery } from "@/git/use-diff-query";
import { useCheckoutStatusQuery } from "@/git/use-status-query";
import { useWorkingDiffComparison } from "@/git/working-diff-comparison";
interface UseWorkingDiffOptions {
serverId: string;
workspaceId?: string;
cwd: string;
ignoreWhitespace: boolean;
enabled: boolean;
queryScope?: string;
}
export function useWorkingDiff({
serverId,
workspaceId,
cwd,
ignoreWhitespace,
enabled,
queryScope,
}: UseWorkingDiffOptions) {
const {
status,
isLoading: isStatusLoading,
isError: isStatusError,
error: statusError,
} = useCheckoutStatusQuery({ serverId, cwd });
const gitStatus = status && status.isGit ? status : null;
const isGit = Boolean(gitStatus);
const notGit = status !== null && !status.isGit && !status.error;
const statusErrorMessage =
status?.error?.message ??
(isStatusError && statusError instanceof Error ? statusError.message : null);
const baseRef = gitStatus?.baseRef ?? undefined;
const hasUncommittedChanges = Boolean(gitStatus?.isDirty);
const currentBranchName =
gitStatus?.currentBranch && gitStatus.currentBranch !== "HEAD" ? gitStatus.currentBranch : null;
const { comparison: diffMode, selectComparison } = useWorkingDiffComparison({
serverId,
workspaceId,
cwd,
isDirty: hasUncommittedChanges,
});
const selectUncommitted = useCallback(() => selectComparison("uncommitted"), [selectComparison]);
const selectBase = useCallback(() => selectComparison("base"), [selectComparison]);
const {
files,
payloadError: diffPayloadError,
diffTooLarge,
isLoading: isDiffLoading,
} = useCheckoutDiffQuery({
serverId,
cwd,
mode: diffMode,
baseRef,
ignoreWhitespace,
enabled: enabled && isGit,
queryScope,
});
const reviewDraftKey = useMemo(
() =>
buildReviewDraftKey({
serverId,
workspaceId,
cwd,
mode: diffMode,
baseRef,
ignoreWhitespace,
}),
[baseRef, cwd, diffMode, ignoreWhitespace, serverId, workspaceId],
);
const reviewActions = useInlineReviewController({ reviewDraftKey });
const reviewAttachment = useReviewAttachmentSnapshot({
key: reviewDraftKey,
diffFiles: files,
cwd,
mode: diffMode,
baseRef,
});
return {
status,
isStatusLoading,
isGit,
notGit,
statusErrorMessage,
baseRef,
currentBranchName,
diffMode,
selectUncommitted,
selectBase,
files,
diffPayloadError,
diffTooLarge,
isDiffLoading,
reviewActions,
reviewAttachment,
};
}
export function usePublishWorkingDiffAttachment({
serverId,
workspaceId,
cwd,
attachment,
enabled,
}: {
serverId: string;
workspaceId?: string;
cwd: string;
attachment: ReturnType<typeof useWorkingDiff>["reviewAttachment"];
enabled: boolean;
}) {
const scopeKey = useMemo(
() => buildWorkspaceAttachmentScopeKey({ serverId, workspaceId, cwd }),
[cwd, serverId, workspaceId],
);
const setWorkspaceAttachments = useWorkspaceAttachmentsStore(
(state) => state.setWorkspaceAttachments,
);
const clearWorkspaceAttachments = useWorkspaceAttachmentsStore(
(state) => state.clearWorkspaceAttachments,
);
useEffect(() => {
if (!enabled) {
return;
}
const attachments = attachment ? [attachment] : [];
setWorkspaceAttachments({ scopeKey, attachments });
return () => {
const current = useWorkspaceAttachmentsStore.getState().attachmentsByScope[scopeKey];
if (current === attachments) {
clearWorkspaceAttachments({ scopeKey });
}
};
}, [attachment, clearWorkspaceAttachments, enabled, scopeKey, setWorkspaceAttachments]);
}