Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion scripts/check_react_compiler_coverage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ const HOT_COMPONENTS: Record<string, readonly string[]> = {
*/
const KNOWN_SKIPPED: ReadonlySet<string> = new Set([
"src/browser/components/ProjectSidebar/ProjectSidebar.tsx#ProjectSidebarInner",
"src/browser/features/RightSidebar/CodeReview/ImmersiveReviewView.tsx#ImmersiveReviewView",
]);

// Subset of babel-plugin-react-compiler's logger events that this guard reads.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ import {
} from "@/common/types/review";
import type { FileStats, FileTreeNode } from "@/common/utils/git/numstatParser";
import type { ReviewActionCallbacks } from "../../Shared/InlineReviewNote";
import { runWithCatchFinally } from "@/browser/utils/compilerSafeControlFlow";

interface ImmersiveReviewViewProps {
workspaceId: string;
Expand Down Expand Up @@ -584,7 +585,7 @@ export const ImmersiveReviewView: React.FC<ImmersiveReviewViewProps> = (props) =
lineMap = newLineMap;
range = parsed.new;
} else if (parsed.old) {
oldLineMap ??= buildOldLineNumberToIndexMap(overlayData.content);
oldLineMap = oldLineMap ?? buildOldLineNumberToIndexMap(overlayData.content);
lineMap = oldLineMap;
range = parsed.old;
} else {
Expand Down Expand Up @@ -721,7 +722,11 @@ export const ImmersiveReviewView: React.FC<ImmersiveReviewViewProps> = (props) =

// Refs keep hot-path callbacks stable so cursor movement doesn't trigger expensive re-renders.
const activeLineIndexRef = useRef<number | null>(null);
// The ref serves the stable hot-path callbacks; the state mirror feeds render
// (the line-selection summary), since React Compiler rejects ref reads during render.
// Every write sets both, next to the cursor/selection updates it batches with.
const hunkJumpLineRangeRef = useRef<SelectedLineRange | null>(null);
const [hunkJumpLineRange, setHunkJumpLineRange] = useState<SelectedLineRange | null>(null);
const selectedLineRangeRef = useRef<SelectedLineRange | null>(null);
const selectedHunkIdRef = useRef<string | null>(selectedHunkId);
const isReadRef = useRef(isRead);
Expand Down Expand Up @@ -838,6 +843,7 @@ export const ImmersiveReviewView: React.FC<ImmersiveReviewViewProps> = (props) =
pendingJumpSelectAllHunkIdRef.current = null;
clearHunkJumpRangeHighlight();
hunkJumpLineRangeRef.current = null;
setHunkJumpLineRange(null);
skipScrollUntilCursorSettlesRef.current = false;
setActiveLineIndex(null);
setSelectedLineRange(null);
Expand All @@ -851,11 +857,10 @@ export const ImmersiveReviewView: React.FC<ImmersiveReviewViewProps> = (props) =
const modifiedStart = selectedHunkRange.firstModifiedIndex ?? selectedHunkRange.startIndex;
const modifiedEnd = selectedHunkRange.lastModifiedIndex ?? selectedHunkRange.endIndex;
skipScrollUntilCursorSettlesRef.current = activeLineIndexRef.current !== modifiedEnd;
hunkJumpLineRangeRef.current = {
startIndex: modifiedStart,
endIndex: modifiedEnd,
};
applyHunkJumpRangeHighlight(hunkJumpLineRangeRef.current);
const jumpRange = { startIndex: modifiedStart, endIndex: modifiedEnd };
hunkJumpLineRangeRef.current = jumpRange;
setHunkJumpLineRange(jumpRange);
applyHunkJumpRangeHighlight(jumpRange);
setActiveLineIndex(modifiedEnd);
setSelectedLineRange(null);
return;
Expand All @@ -867,6 +872,7 @@ export const ImmersiveReviewView: React.FC<ImmersiveReviewViewProps> = (props) =
) {
clearHunkJumpRangeHighlight();
hunkJumpLineRangeRef.current = null;
setHunkJumpLineRange(null);
}

const cursorLineIndex = activeLineIndexRef.current;
Expand Down Expand Up @@ -1099,16 +1105,16 @@ export const ImmersiveReviewView: React.FC<ImmersiveReviewViewProps> = (props) =
return selectedLineRange;
}

if (hunkJumpLineRangeRef.current) {
return hunkJumpLineRangeRef.current;
if (hunkJumpLineRange) {
return hunkJumpLineRange;
}

if (activeLineIndex === null) {
return null;
}

return { startIndex: activeLineIndex, endIndex: activeLineIndex };
}, [activeLineIndex, selectedLineRange]);
}, [activeLineIndex, hunkJumpLineRange, selectedLineRange]);

const selectedLineSummary = useMemo(() => {
const selection = getCurrentLineSelection();
Expand Down Expand Up @@ -1195,6 +1201,7 @@ export const ImmersiveReviewView: React.FC<ImmersiveReviewViewProps> = (props) =
// cursor (activeLineIndex) rather than the stale range from this comment.
clearHunkJumpRangeHighlight();
hunkJumpLineRangeRef.current = null;
setHunkJumpLineRange(null);
setSelectedLineRange(null);
containerRef.current?.focus();
},
Expand All @@ -1207,6 +1214,7 @@ export const ImmersiveReviewView: React.FC<ImmersiveReviewViewProps> = (props) =
setInlineComposerRequest(null);
clearHunkJumpRangeHighlight();
hunkJumpLineRangeRef.current = null;
setHunkJumpLineRange(null);
setSelectedLineRange(null);
containerRef.current?.focus();
}, [clearHunkJumpRangeHighlight]);
Expand All @@ -1223,6 +1231,7 @@ export const ImmersiveReviewView: React.FC<ImmersiveReviewViewProps> = (props) =

clearHunkJumpRangeHighlight();
hunkJumpLineRangeRef.current = null;
setHunkJumpLineRange(null);
setActiveLineIndex(nextIndex);

if (extendRange) {
Expand Down Expand Up @@ -1396,13 +1405,16 @@ export const ImmersiveReviewView: React.FC<ImmersiveReviewViewProps> = (props) =
} | null>(null);
const copyFileRequestIdRef = useRef(0);
const pendingCopyFilePathRef = useRef<string | null>(null);
// Both refs update during render so isStale() sees path AND content-version
// changes before passive effects run; a resolved read's microtask can otherwise
// beat the invalidation effect after a same-path refresh commits.
// Both refs update in a layout effect (same task as the commit, before passive effects)
// so isStale() sees path AND content-version changes before passive effects run; a
// resolved read's microtask can otherwise beat the invalidation effect after a same-path
// refresh commits. (React Compiler rejects ref writes during render.)
const activeFilePathRef = useRef(activeFilePath);
activeFilePathRef.current = activeFilePath;
const activeFileContentVersionRef = useRef(activeFileContentVersion);
activeFileContentVersionRef.current = activeFileContentVersion;
useLayoutEffect(() => {
activeFilePathRef.current = activeFilePath;
activeFileContentVersionRef.current = activeFileContentVersion;
});

useEffect(() => {
return () => {
Expand Down Expand Up @@ -1485,7 +1497,7 @@ export const ImmersiveReviewView: React.FC<ImmersiveReviewViewProps> = (props) =
requestId !== copyFileRequestIdRef.current ||
activeFilePathRef.current !== filePath ||
activeFileContentVersionRef.current !== contentVersion;
try {
const copyFileContents = async () => {
const result = await api.workspace.executeBash({
workspaceId: props.workspaceId,
script: buildReadFileScript(filePath, {
Expand Down Expand Up @@ -1534,26 +1546,30 @@ export const ImmersiveReviewView: React.FC<ImmersiveReviewViewProps> = (props) =
if (!isStale()) {
showCopyFileFeedback("copied", filePath, contentVersion);
}
} catch (error) {
};
const reportCopyFailure = (error: unknown) => {
console.error("Failed to copy file contents:", error);
if (!isStale()) {
showCopyFileFeedback("failed", filePath, contentVersion);
}
} finally {
};
await runWithCatchFinally(copyFileContents, reportCopyFailure, () => {
// A superseding request owns the pending slot; only the current one releases it.
if (
pendingCopyFilePathRef.current === filePath &&
copyFileRequestIdRef.current === requestId
) {
pendingCopyFilePathRef.current = null;
}
}
});
};

// Keyboard handling reads the handler through a ref (matching onToggleReadRef) so the
// effect does not depend on a per-render function identity.
const handleCopyFileRef = useRef(handleCopyFile);
handleCopyFileRef.current = handleCopyFile;
useLayoutEffect(() => {
handleCopyFileRef.current = handleCopyFile;
});

const activeCopyFileFeedback = copyFileFeedback?.kind ?? null;

Expand All @@ -1567,6 +1583,7 @@ export const ImmersiveReviewView: React.FC<ImmersiveReviewViewProps> = (props) =

clearHunkJumpRangeHighlight();
hunkJumpLineRangeRef.current = null;
setHunkJumpLineRange(null);
const anchorIndex = shiftKey
? (selectedLineRangeRef.current?.startIndex ?? activeLineIndexRef.current ?? lineIndex)
: lineIndex;
Expand Down
Loading