Files
paseo/packages/app/src/git/use-working-diff.ts
T
Mohamed Boudra b26b9234c0 Improve diff review with mobile comment sheets and direct feedback (#6455)
* feat(review): edit compact diff comments in a sheet

* feat(review): send diff feedback to workspace agents

Send saved reviews from the diff while retaining file navigation and unrelated composer drafts. Clear acknowledged versions only so comments edited or added during submission remain available.

* fix(review): preserve drafts and coordinate feedback across surfaces

Share pending submission ownership by draft while keeping recipient menus local. Retain editable text across presentation changes and acknowledgement, and reserve safe-area clearance for floating actions.

* fix(review): center a small primary feedback button on desktop

* fix(review): restore focus to feedback button after closing menu
2026-10-09 21:31:06 +01:00

162 lines
4.2 KiB
TypeScript

import { useCallback, useEffect, useMemo } from "react";
import {
buildWorkspaceAttachmentScopeKey,
useWorkspaceAttachmentsStore,
} from "@/attachments/workspace-attachments-store";
import {
buildReviewDraftKey,
useInlineReviewController,
useReviewAttachmentSnapshot,
useReviewFeedback,
} 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;
presentation?: string;
}
export function useWorkingDiff({
serverId,
workspaceId,
cwd,
ignoreWhitespace,
enabled,
queryScope,
presentation,
}: 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, presentation });
const reviewAttachment = useReviewAttachmentSnapshot({
key: reviewDraftKey,
diffFiles: files,
cwd,
mode: diffMode,
baseRef,
});
const reviewFeedback = useReviewFeedback({
serverId,
workspaceId,
reviewDraftKey,
attachment: reviewAttachment,
});
return {
status,
isStatusLoading,
isGit,
notGit,
statusErrorMessage,
baseRef,
currentBranchName,
diffMode,
selectUncommitted,
selectBase,
files,
diffPayloadError,
diffTooLarge,
isDiffLoading,
reviewActions,
reviewAttachment,
reviewFeedback,
};
}
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]);
}