From f7d9cb0e01e9c1aaa8ca40889f5135a16bb22229 Mon Sep 17 00:00:00 2001 From: hmjn Date: Sun, 2 Aug 2026 14:05:32 +0900 Subject: [PATCH 01/26] perf(ui): improve v2 media grid loading Avoid mounting the full result set while virtual grid metrics are pending, prioritize the first two rows, and prefetch V2 filter metadata like the existing routes. --- apps/server/src/routes/search.tsx | 4 +- .../components/source-media-page.tsx | 1 + apps/server/src/routes/v2/search.tsx | 13 ++++ .../v2/sources/$mediaSourceId/index.tsx | 20 ++++++ apps/tauri/src/routes/search.tsx | 3 +- .../components/source-media-page.tsx | 3 +- packages/ui/src/screens/search-screen.tsx | 10 ++- .../ui/src/screens/source-media-screen.tsx | 1 + packages/ui/src/source-media-grid.tsx | 61 +++++++++++++------ 9 files changed, 92 insertions(+), 24 deletions(-) diff --git a/apps/server/src/routes/search.tsx b/apps/server/src/routes/search.tsx index 4c992f293..9f72ad770 100644 --- a/apps/server/src/routes/search.tsx +++ b/apps/server/src/routes/search.tsx @@ -119,7 +119,9 @@ function SearchRoute() { onSelectSource={(id) => setSearchState("selectedSource", id)} page={page} presetClient={PresetClient} - renderMediaItem={(media) => } + renderMediaItem={(media, options) => ( + + )} renderNavActions={({ openMobileFilters }) => ( + +
+ {manager().taggingStatus()} +
+
+ + {(progress) => ( +
+
+ Batch progress + + {progress().processed} / {progress().total} + +
+ 0 + ? (progress().processed / progress().total) * 100 + : 0 + } + /> +
+ )} +
+ + + + diff --git a/packages/ui/src/screens/search-screen.tsx b/packages/ui/src/screens/search-screen.tsx index 9c71d05d4..7b318256d 100644 --- a/packages/ui/src/screens/search-screen.tsx +++ b/packages/ui/src/screens/search-screen.tsx @@ -10,6 +10,7 @@ import type { UseSearchPageResult, } from "../hooks/use-search-page"; import type { SourceMediaPagePresetClient } from "../hooks/use-source-media-page"; +import type { MediaGridImageLoadPolicy } from "../media-grid-item"; import { MobileSearchFilterDialog } from "../mobile-search-filter-dialog"; import { SearchControlPanel } from "../search-control-panel"; import { LoadingRegion, MediaGridSkeleton } from "../skeleton"; @@ -32,6 +33,7 @@ export type SearchScreenProps = { renderMediaItem: ( media: Media, options?: { + imageLoadPolicy?: MediaGridImageLoadPolicy; onPreviewSelect?: () => void; isPreviewSelected?: boolean; priority?: boolean; diff --git a/packages/ui/src/screens/source-media-screen.tsx b/packages/ui/src/screens/source-media-screen.tsx index 847c6c04f..f0c772ad2 100644 --- a/packages/ui/src/screens/source-media-screen.tsx +++ b/packages/ui/src/screens/source-media-screen.tsx @@ -17,6 +17,7 @@ import type { UploadOptions, UseSourceMediaPageResult, } from "../hooks/use-source-media-page"; +import type { MediaGridImageLoadPolicy } from "../media-grid-item"; import { MobileSearchFilterDialog } from "../mobile-search-filter-dialog"; import { SearchControlPanel } from "../search-control-panel"; import { LoadingRegion, MediaGridSkeleton } from "../skeleton"; @@ -33,6 +34,7 @@ export type SourceMediaScreenProps = { renderItem: ( media: Media, options: { + imageLoadPolicy?: MediaGridImageLoadPolicy; onContextMenu: () => void; priority?: boolean; isBulkSelectMode?: boolean; diff --git a/packages/ui/src/screens/v2-manager-screen.tsx b/packages/ui/src/screens/v2-manager-screen.tsx index 5450337b1..c9c13999b 100644 --- a/packages/ui/src/screens/v2-manager-screen.tsx +++ b/packages/ui/src/screens/v2-manager-screen.tsx @@ -127,6 +127,12 @@ const MANAGER_CATEGORIES: ManagerCategory[] = [ label: "Vector extraction", value: "vectors", }, + { + description: "Warm responsive image cache", + group: "Tools", + label: "Thumbnails", + value: "thumbnails", + }, { description: "Review matching media", group: "Tools", @@ -201,6 +207,8 @@ function ManagerCategoryIcon(props: { value: V2ManagerCategory }) { return