From 3db95a44899cdff73496b691f8bea27d03ce6ae3 Mon Sep 17 00:00:00 2001 From: LyAhn Date: Sun, 21 Jun 2026 13:45:11 +0100 Subject: [PATCH] perf: virtualize the Duplicate Finder group list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The duplicate view rendered every group and every thumbnail at once — a 5,000-pair result mounted ~10K elements, making scroll lag heavily (same class of bug as the old per-month Timeline). Virtualize the group list so only on-screen cards mount; heights are measured dynamically since each group wraps a variable number of copies. --- src/components/DuplicateFinder.tsx | 42 +++++++++++++++++++++++++----- 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/src/components/DuplicateFinder.tsx b/src/components/DuplicateFinder.tsx index 1ab85fe..7824a49 100644 --- a/src/components/DuplicateFinder.tsx +++ b/src/components/DuplicateFinder.tsx @@ -1,4 +1,5 @@ -import { useState } from "react"; +import { useRef, useState } from "react"; +import { useVirtualizer } from "@tanstack/react-virtual"; import { convertFileSrc } from "@tauri-apps/api/core"; import { DuplicateGroup, useGalleryStore } from "../store"; import { FolderScopeDropdown } from "./FolderScopeDropdown"; @@ -130,6 +131,17 @@ export function DuplicateFinder() { const [deleting, setDeleting] = useState(false); const [deleteResult, setDeleteResult] = useState(null); + // Virtualize the group list so a large result set (e.g. thousands of pairs) + // only mounts the on-screen cards. Group cards vary in height (number of + // copies wraps across rows), so heights are measured dynamically. + const scrollRef = useRef(null); + const virtualizer = useVirtualizer({ + count: duplicateGroups.length, + getScrollElement: () => scrollRef.current, + estimateSize: () => 220, + overscan: 4, + }); + const selectedCount = duplicateSelectedIds.size; const hasResults = duplicateGroups.length > 0; const hasScanned = hasResults || duplicateLastScanned !== null || (!duplicateScanning && duplicateScanProgress !== null); @@ -277,11 +289,29 @@ export function DuplicateFinder() {

No duplicate files found.

) : ( -
-
- {duplicateGroups.map((group) => ( - - ))} +
+
+ {virtualizer.getVirtualItems().map((virtualItem) => { + const group = duplicateGroups[virtualItem.index]; + if (!group) return null; + return ( +
+ +
+ ); + })}
)}