Files
paseo/packages/app/src/git/use-working-diff.ts
T
9e5accee3c fix: reject oversized checkout diffs (#2488)
* fix: reject oversized checkout diffs

* fix(diff): keep large previews from disconnecting clients

The physical socket limit is an OOM backstop, not a frame-size contract. Bound structured diff construction incrementally and report oversized previews without closing the client's socket.

* fix(app): preserve oversized diff state

* fix(diff): stay within relay frame limit

---------

Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
2026-07-30 00:48:05 +08:00

172 lines
4.5 KiB
TypeScript

import { useCallback, useEffect, useMemo } from "react";
import {
buildWorkspaceAttachmentScopeKey,
useWorkspaceAttachmentsStore,
} 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";
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 reviewDraftScopeKey = useMemo(
() =>
buildReviewDraftScopeKey({
serverId,
workspaceId,
cwd,
baseRef,
ignoreWhitespace,
}),
[baseRef, cwd, ignoreWhitespace, serverId, workspaceId],
);
const diffMode = useResolvedDiffMode({
scopeKey: reviewDraftScopeKey,
hasUncommittedChanges,
});
const setDiffModeOverride = useSetDiffModeOverride();
const selectDiffMode = useCallback(
(mode: "uncommitted" | "base") => {
setDiffModeOverride({
scopeKey: reviewDraftScopeKey,
override: { serverId, cwd, mode, isDirtyAtSelection: hasUncommittedChanges },
});
},
[cwd, hasUncommittedChanges, reviewDraftScopeKey, serverId, setDiffModeOverride],
);
const selectUncommitted = useCallback(() => selectDiffMode("uncommitted"), [selectDiffMode]);
const selectBase = useCallback(() => selectDiffMode("base"), [selectDiffMode]);
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]);
}