Skip to content
This repository was archived by the owner on Aug 6, 2026. It is now read-only.
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
46 changes: 46 additions & 0 deletions packages/ui/src/features/code-review/commentFileFilter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,52 @@ export function filterReviewItemsByFilePaths(
return filteredItems;
}

export function filterReviewItemsByViewedState(
items: ReviewListItem[],
currentSignatures: ReadonlyMap<string, string>,
viewedRecord: Readonly<Record<string, string>>,
): ReviewListItem[] {
const filteredItems: ReviewListItem[] = [];
let pendingSectionItems: ReviewListItem[] = [];

for (const item of items) {
if (!item.filePaths) {
pendingSectionItems = [item];
continue;
}

const signature = item.scrollKey
? currentSignatures.get(item.scrollKey)
: undefined;
if (
signature !== undefined &&
item.scrollKey !== undefined &&
viewedRecord[item.scrollKey] === signature
) {
continue;
}

filteredItems.push(...pendingSectionItems, item);
pendingSectionItems = [];
}

return filteredItems;
}

export function resolveVisibleActiveFilePath(
items: ReviewListItem[],
activeFilePath: string | null,
): string | null {
if (
activeFilePath &&
items.some((item) => item.filePaths?.includes(activeFilePath))
) {
return activeFilePath;
}

return items.find((item) => item.scrollKey)?.scrollKey ?? null;
}

export function deriveCommentFileFilterState({
items,
requestedFilter,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,17 @@ interface DiffSettingsMenuProps {
unresolvedCommentedFileCount: number;
commentFilter: CommentFileFilter;
onCommentFilterChange?: (filter: CommentFileFilter) => void;
hideViewedFiles: boolean;
onHideViewedFilesChange: (hideViewed: boolean) => void;
}

export function DiffSettingsMenu({
commentedFileCount,
unresolvedCommentedFileCount,
commentFilter,
onCommentFilterChange,
hideViewedFiles,
onHideViewedFilesChange,
}: DiffSettingsMenuProps) {
const wordWrap = useDiffViewerStore((s) => s.wordWrap);
const toggleWordWrap = useDiffViewerStore((s) => s.toggleWordWrap);
Expand Down Expand Up @@ -79,6 +83,11 @@ export function DiffSettingsMenu({
{hideWhitespaceChanges ? "Show whitespace" : "Hide whitespace"}
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
onClick={() => onHideViewedFilesChange(!hideViewedFiles)}
>
{hideViewedFiles ? "Show viewed files" : "Hide viewed files"}
</DropdownMenuItem>
<DropdownMenuItem onClick={handleToggleReviewComments}>
{showReviewComments ? "Hide review comments" : "Show review comments"}
</DropdownMenuItem>
Expand Down
57 changes: 52 additions & 5 deletions packages/ui/src/features/code-review/components/ReviewShell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ import {
import { VList, type VListHandle } from "virtua";
import {
deriveCommentFileFilterState,
filterReviewItemsByViewedState,
getEmptyReviewMessage,
type ReviewListItem,
resolveVisibleActiveFilePath,
} from "../commentFileFilter";
import {
REVIEW_LIST_BUFFER_PX,
Expand Down Expand Up @@ -155,6 +157,12 @@ export function ReviewShell({
const setCommentFileFilter = useReviewNavigationStore(
(state) => state.setCommentFileFilter,
);
const hideViewedFiles = useReviewNavigationStore(
(state) => state.hideViewedFiles[taskId] ?? false,
);
const setHideViewedFiles = useReviewNavigationStore(
(state) => state.setHideViewedFiles,
);
const {
activeFilter: activeCommentFilter,
visibleItems,
Expand All @@ -170,9 +178,21 @@ export function ReviewShell({
}),
[commentFilter, commentedFilePaths, items, unresolvedCommentedFilePaths],
);
const filteredItems = useMemo(() => {
if (!hideViewedFiles) return visibleItems;
return filterReviewItemsByViewedState(
visibleItems,
currentSignatures,
viewedRecord,
);
}, [currentSignatures, hideViewedFiles, viewedRecord, visibleItems]);
Comment thread
MattPua marked this conversation as resolved.
const filteredFileCount = useMemo(
() => filteredItems.filter((item) => item.filePaths).length,
[filteredItems],
);
const visibleItemIndexByFilePath = useMemo(
() => buildItemIndex(visibleItems),
[visibleItems],
() => buildItemIndex(filteredItems),
[filteredItems],
);

const workerFactory = useCallback(
Expand Down Expand Up @@ -253,8 +273,28 @@ export function ReviewShell({
const setActiveFilePath = useReviewNavigationStore(
(s) => s.setActiveFilePath,
);
const activeFilePath = useReviewNavigationStore(
(s) => s.activeFilePaths[taskId] ?? null,
);
const clearTask = useReviewNavigationStore((s) => s.clearTask);

useEffect(() => {
if (!hideViewedFiles || !activeFilePath) return;
const nextActiveFilePath = resolveVisibleActiveFilePath(
filteredItems,
activeFilePath,
);
if (nextActiveFilePath === activeFilePath) return;
lastActiveRef.current = nextActiveFilePath;
setActiveFilePath(taskId, nextActiveFilePath);
}, [
activeFilePath,
filteredItems,
hideViewedFiles,
setActiveFilePath,
taskId,
]);

useEffect(() => {
return () => {
if (navigationFrameRef.current !== null) {
Expand Down Expand Up @@ -348,11 +388,13 @@ export function ReviewShell({
<Spinner size="2" />
</Flex>
);
} else if (isEmpty || visibleItems.length === 0) {
} else if (isEmpty || filteredItems.length === 0) {
reviewContent = (
<Flex align="center" justify="center" className="min-h-0 flex-1">
<Text color="gray" className="text-sm">
{getEmptyReviewMessage(activeCommentFilter)}
{hideViewedFiles
? "No unviewed file changes"
: getEmptyReviewMessage(activeCommentFilter)}
</Text>
</Flex>
);
Expand All @@ -366,7 +408,7 @@ export function ReviewShell({
shift={false}
style={{ scrollbarGutter: "stable" }}
onScroll={handleScroll}
data={visibleItems}
data={filteredItems}
>
{renderItem}
</VList>
Expand Down Expand Up @@ -413,6 +455,11 @@ export function ReviewShell({
? (filter) => setCommentFileFilter(taskId, filter)
: undefined
}
hideViewedFiles={hideViewedFiles}
filteredFileCount={filteredFileCount}
onHideViewedFilesChange={(hideViewed) =>
setHideViewedFiles(taskId, hideViewed)
}
linesAdded={linesAdded}
linesRemoved={linesRemoved}
allExpanded={allExpanded}
Expand Down
29 changes: 21 additions & 8 deletions packages/ui/src/features/code-review/components/ReviewToolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ interface ReviewToolbarProps {
unresolvedCommentedFileCount: number;
commentFilter: CommentFileFilter;
onCommentFilterChange?: (filter: CommentFileFilter) => void;
hideViewedFiles: boolean;
filteredFileCount: number;
onHideViewedFilesChange: (hideViewed: boolean) => void;
linesAdded: number;
linesRemoved: number;
allExpanded: boolean;
Expand Down Expand Up @@ -82,6 +85,9 @@ export const ReviewToolbar = memo(function ReviewToolbar({
unresolvedCommentedFileCount,
commentFilter,
onCommentFilterChange,
hideViewedFiles,
filteredFileCount,
onHideViewedFilesChange,
allExpanded,
onExpandAll,
onCollapseAll,
Expand All @@ -108,13 +114,18 @@ export const ReviewToolbar = memo(function ReviewToolbar({
setReviewMode(taskId, "closed");
};

const { count: visibleFileCount, label: fileCountLabel } =
getVisibleFileSummary(
commentFilter,
fileCount,
commentedFileCount,
unresolvedCommentedFileCount,
);
const visibleFileSummary = getVisibleFileSummary(
commentFilter,
fileCount,
commentedFileCount,
unresolvedCommentedFileCount,
);
const visibleFileCount = hideViewedFiles
? filteredFileCount
: visibleFileSummary.count;
const fileCountLabel = hideViewedFiles
? formatFileCount(filteredFileCount, "not viewed")
: visibleFileSummary.label;

return (
<Flex
Expand All @@ -129,7 +140,7 @@ export const ReviewToolbar = memo(function ReviewToolbar({
>
<Flex align="center" gap="2">
<Text className="font-medium text-[13px]">{fileCountLabel}</Text>
{visibleFileCount > 0 && (
{!hideViewedFiles && visibleFileCount > 0 && (
<Text className="text-(--gray-10) text-[13px]">
{viewedCount}/{visibleFileCount} viewed
</Text>
Expand Down Expand Up @@ -216,6 +227,8 @@ export const ReviewToolbar = memo(function ReviewToolbar({
unresolvedCommentedFileCount={unresolvedCommentedFileCount}
commentFilter={commentFilter}
onCommentFilterChange={onCommentFilterChange}
hideViewedFiles={hideViewedFiles}
onHideViewedFilesChange={onHideViewedFilesChange}
/>

<Tooltip content="Close review">
Expand Down
15 changes: 15 additions & 0 deletions packages/ui/src/features/code-review/reviewNavigationStore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ describe("reviewNavigationStore", () => {
reviewModes: {},
selectedPrUrls: {},
commentFileFilters: {},
hideViewedFiles: {},
});
});

Expand All @@ -28,6 +29,20 @@ describe("reviewNavigationStore", () => {
).toBeUndefined();
});

it("stores and clears the viewed-file filter per task", () => {
const store = useReviewNavigationStore.getState();
store.setHideViewedFiles("task-1", true);

expect(useReviewNavigationStore.getState().hideViewedFiles["task-1"]).toBe(
true,
);

store.clearTask("task-1");
expect(useReviewNavigationStore.getState().hideViewedFiles["task-1"]).toBe(
false,
);
});

it("clears the comment filter when navigating to a file", () => {
const store = useReviewNavigationStore.getState();
store.setCommentFileFilter("task-1", "unresolved");
Expand Down
13 changes: 13 additions & 0 deletions packages/ui/src/features/code-review/reviewNavigationStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ interface ReviewNavigationStoreState {
reviewModes: Record<string, ReviewMode>;
selectedPrUrls: Record<string, string | undefined>;
commentFileFilters: Record<string, CommentFileFilter>;
hideViewedFiles: Record<string, boolean>;
}

interface ReviewNavigationStoreActions {
Expand All @@ -19,6 +20,7 @@ interface ReviewNavigationStoreActions {
setReviewMode: (taskId: string, mode: ReviewMode) => void;
setSelectedPrUrl: (taskId: string, url: string) => void;
setCommentFileFilter: (taskId: string, filter: CommentFileFilter) => void;
setHideViewedFiles: (taskId: string, hideViewed: boolean) => void;
getReviewMode: (taskId: string) => ReviewMode;
}

Expand All @@ -32,6 +34,7 @@ export const useReviewNavigationStore = create<ReviewNavigationStore>()(
reviewModes: {},
selectedPrUrls: {},
commentFileFilters: {},
hideViewedFiles: {},

setActiveFilePath: (taskId, path) =>
set((state) => ({
Expand All @@ -45,6 +48,7 @@ export const useReviewNavigationStore = create<ReviewNavigationStore>()(
...state.commentFileFilters,
[taskId]: "none",
},
hideViewedFiles: { ...state.hideViewedFiles, [taskId]: false },
})),

clearScrollRequest: (taskId) =>
Expand All @@ -61,6 +65,7 @@ export const useReviewNavigationStore = create<ReviewNavigationStore>()(
[taskId]: "none",
},
selectedPrUrls: { ...state.selectedPrUrls, [taskId]: undefined },
hideViewedFiles: { ...state.hideViewedFiles, [taskId]: false },
})),

setReviewMode: (taskId, mode) =>
Expand All @@ -87,6 +92,14 @@ export const useReviewNavigationStore = create<ReviewNavigationStore>()(
},
})),

setHideViewedFiles: (taskId, hideViewed) =>
set((state) => ({
hideViewedFiles: {
...state.hideViewedFiles,
[taskId]: hideViewed,
},
})),

getReviewMode: (taskId) => get().reviewModes[taskId] ?? "closed",
}),
);
Loading
Loading