Skip to content

perf(ui): V2メディア一覧の初期表示を高速化 - #655

Merged
hmjn023 merged 12 commits into
developfrom
perf/v2-media-grid-loading
Aug 2, 2026
Merged

perf(ui): V2メディア一覧の初期表示を高速化#655
hmjn023 merged 12 commits into
developfrom
perf/v2-media-grid-loading

Conversation

@hmjn023

@hmjn023 hmjn023 commented Aug 2, 2026

Copy link
Copy Markdown
Owner

概要

V2のメディア一覧で、画像が遅れてバラバラに表示される問題を改善します。

変更内容

  • 仮想化の計測前に検索結果全件をマウントせず、スケルトンを表示
  • 初回2行を優先ロードし、残りは遅延ロード
  • Server/Tauriの一覧・検索経路で優先ロード指定を伝播
  • V2検索・ソース画面でフィルター情報を初期先読み

検証

  • bun run check
  • Server unit tests / typecheck
  • UI tests / typecheck
  • Server production build

Summary by CodeRabbit

  • 新機能

    • メディアグリッドの初期表示と画像読み込みを最適化し、優先項目を先に読み込めるようになりました。
    • 大量のメディアを高速スクロールしても、表示を安定して維持できるようになりました。
    • 検索結果を1ページ最大200件まで表示できるようになりました。
    • 管理画面から不足している256pxサムネイルを一括生成できるようになりました。
    • 画面サイズに応じたサムネイル表示に対応しました。
  • バグ修正

    • グリッド幅の確定前にスケルトンを表示し、レイアウトの安定性を改善しました。
    • サムネイルの読み込み、キャッシュ、フォールバック処理を改善しました。

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.
@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

サムネイルを256pxと512pxで扱えるようにし、生成ジョブ、API契約、ウォームアップ画面を追加します。仮想メディアグリッドは画像ロード優先度、srcset、スケルトン表示を制御します。検索ルート、キャッシュ、E2Eテストも更新します。

Changes

サムネイル生成基盤

Layer / File(s) Summary
サイズ付き生成契約とジョブ処理
packages/core/src/domain/thumbnails/schemas.ts, apps/server/src/infrastructure/jobs/*, packages/db/src/repositories/*
256pxまたは512pxの生成指定、欠落分のみの処理、子ジョブの重複排除、親ジョブの進捗通知を追加します。
APIと画像レスポンス
apps/server/src/infrastructure/api/routers/thumbnails-router.ts, apps/server/src/routes/api/*thumbnail*, apps/server/public/openapi.json
生成APIの入力・出力契約を更新します。画像取得時のサイズ検証、フォールバック、キャッシュヘッダーを追加します。

メディアグリッドと画像ロード

Layer / File(s) Summary
レスポンシブ画像とロード状態
packages/ui/src/thumbnail-*.tsx, packages/ui/src/media-grid-item.tsx, apps/server/src/components/media/*, apps/tauri/src/components/media/*
srcsetsizesfetchpriorityenabled、要求サイズを画像コンポーネントへ伝播します。
仮想化と描画オプション
packages/ui/src/source-media-grid.tsx, packages/ui/src/screens/*, apps/server/src/routes/*, apps/tauri/src/routes/*
可視範囲、先読み範囲、初期優先範囲、遅延読み込みを仮想化方式ごとに設定します。描画オプションを各MediaGridItemへ渡します。
検索ルートとクエリ状態
apps/server/src/routes/v2/*, apps/server/src/hooks/use-media-source-events.ts, packages/ui/src/query-options/search-query.ts
V2ルートのSSRを無効化します。V2メディアソースパスをイベント対象に追加します。検索クエリに5分のstaleTimeを設定します。

ウォームアップと検証

Layer / File(s) Summary
サムネイルウォームアップ
packages/ui/src/hooks/use-manager-page.ts, packages/ui/src/screens/*manager-screen.tsx, apps/*/routes/*manager.tsx
選択したメディアソースの不足する256pxサムネイルを生成する操作、状態表示、進捗表示を追加します。
仮想グリッド検証
apps/server/src/tests/e2e/ui-gallery/*, apps/server/src/tests/e2e/ui-components.gallery.spec.ts, packages/ui/src/thumbnail-source.test.ts
1,000件の固定メディアと仮想サムネイル配信を追加します。高速スクロール、マウント数、可視画像、256px・512px候補の選択を検証します。

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ManagerScreen
  participant ThumbnailRouter
  participant ThumbnailService
  participant JobWorker
  participant ThumbnailImage
  ManagerScreen->>ThumbnailRouter: request warmup for size 256
  ThumbnailRouter->>ThumbnailService: start generation with missingOnly
  ThumbnailService->>JobWorker: enqueue thumbnail jobs
  JobWorker->>ThumbnailImage: generate and publish thumbnail
  ThumbnailImage-->>ManagerScreen: update job progress
Loading

Possibly related PRs

  • hmjn023/solid-imager#565: 同じサムネイル取得ルートで欠落サムネイルの処理を変更しています。
  • hmjn023/solid-imager#599: search-screen.tsxsource-media-grid.tsx の画像ロードおよび仮想化変更に関連します。
  • hmjn023/solid-imager#650: V2検索、ソースメディア画面、MediaGridItem の変更領域が重なります。
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed タイトルは、V2メディア一覧の初期表示を高速化するという変更の主目的を明確に示しています。
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch perf/v2-media-grid-loading

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

hmjn023 added 5 commits August 2, 2026 14:16
Keep skeleton rows aligned to the responsive column count and eagerly load the mounted virtual range with a larger overscan buffer.
Match the source collection mount guard so the virtual grid initializes only after the client mount and persisted search state restoration begins.
Limit virtual image prefetching to the initial rows and keep search result queries fresh while navigating back from media details.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/server/src/hooks/use-media-source-events.ts`:
- Around line 83-89: Update useMediaSourceEvents so both /search and /v2/search
are treated as active routes when id is "*" or a specific source ID; retain the
existing source-specific route checks. Add a test covering the V2 search screen
with searchState.selectedSource set, verifying media add, removal, or change
events trigger updates.

In `@packages/ui/src/thumbnail-image.tsx`:
- Around line 29-47: Introduce a shared load-generation counter in the thumbnail
loading flow so only the newest request may update state. Update load,
applyResolvedUrl, the promise rejection path, handleError, and the
source.subscribe callback to capture and validate the current generation
alongside cancelled before calling setUrl, setError, or source.onError;
invalidate prior generations when props.source changes or a retry starts.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: c84e2579-e48b-4df5-965f-5a2826d510b3

📥 Commits

Reviewing files that changed from the base of the PR and between f7d9cb0 and a56cffe.

📒 Files selected for processing (10)
  • apps/server/src/hooks/use-media-source-events.ts
  • apps/server/src/routes/api/sources.$mediaSourceId.thumbnail.$mediaId.ts
  • apps/server/src/routes/v2/search.tsx
  • apps/server/src/routes/v2/sources/$mediaSourceId/index.tsx
  • apps/server/src/tests/e2e/ui-components.gallery.spec.ts
  • apps/server/src/tests/e2e/ui-gallery/src.tsx
  • packages/ui/src/query-options/search-query.test.ts
  • packages/ui/src/query-options/search-query.ts
  • packages/ui/src/source-media-grid.tsx
  • packages/ui/src/thumbnail-image.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/ui/src/source-media-grid.tsx

Comment thread apps/server/src/hooks/use-media-source-events.ts Outdated
Comment thread packages/ui/src/thumbnail-image.tsx Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

♻️ Duplicate comments (1)
packages/ui/src/thumbnail-image.tsx (1)

24-98: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

非同期ロードの競合を解消してください。世代管理がありません。

load()loadSrcSet()は同じcancelledフラグを共有します。source.subscribeload()loadSrcSet()を再実行すると、先行する非同期呼び出しと後続する呼び出しが同時に存在します。先行するPromiseが後から解決すると、applyResolvedUrlまたはsetSrcSetが最新の値を上書きします。

仮想グリッドでセルが再利用される場合、または再試行(onErrorによるcacheKey更新)が短時間に複数回発生する場合、この競合が発生します。古いレスポンスが新しいURLやsrcsetを上書きする可能性があります。

世代番号(requestIdなど)を追加してください。呼び出し時点の世代番号を記録し、状態更新の直前にその世代が最新であることを確認してください。

🔒️ 世代管理を追加する修正案
 	createEffect(() => {
 		const source = props.source;
 		if (source !== previousSource) {
 			previousSource = source;
 			setUrl(null);
 			setSrcSet(undefined);
 			setError(false);
 		}
 		if (props.enabled === false) {
 			return;
 		}
 		setError(false);
-		let cancelled = false;
+		let cancelled = false;
+		let requestId = 0;
 
-		const applyResolvedUrl = (resolved: string) => {
-			if (!cancelled) {
+		const applyResolvedUrl = (version: number, resolved: string) => {
+			if (!cancelled && version === requestId) {
 				setUrl(resolved);
 			}
 		};
 
 		const load = () => {
+			const version = ++requestId;
 			try {
 				const resolved = source.getUrl();
 				if (typeof resolved === "string") {
-					applyResolvedUrl(resolved);
+					applyResolvedUrl(version, resolved);
 					return;
 				}
-				void resolved.then(applyResolvedUrl).catch(() => {
-					if (!cancelled) {
+				void resolved
+					.then((value) => applyResolvedUrl(version, value))
+					.catch(() => {
+					if (!cancelled && version === requestId) {
 						setError(true);
 						source.onError?.();
 					}
 				});
 			} catch {
-				if (!cancelled) {
+				if (!cancelled && version === requestId) {
 					setError(true);
 					source.onError?.();
 				}
 			}
 		};
 		const loadSrcSet = () => {
+			const version = requestId;
 			try {
 				const resolved = source.getSrcSet?.();
 				if (typeof resolved === "string" || resolved === undefined) {
-					if (!cancelled) setSrcSet(resolved);
+					if (!cancelled && version === requestId) setSrcSet(resolved);
 					return;
 				}
 				void resolved.then((value) => {
-					if (!cancelled) setSrcSet(value);
+					if (!cancelled && version === requestId) setSrcSet(value);
 				});
 			} catch {
-				if (!cancelled) setSrcSet(undefined);
+				if (!cancelled && version === requestId) setSrcSet(undefined);
 			}
 		};
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ui/src/thumbnail-image.tsx` around lines 24 - 98,
ThumbnailImageのcreateEffect内で、source.subscribeによる再実行を含む各load/loadSrcSet呼び出しを識別できる世代番号を追加してください。各リクエスト開始時のrequestIdをPromiseの完了処理で確認し、最新世代の場合のみsetUrl、setSrcSet、setError、source.onErrorを実行するように更新します。既存のcancelledチェックは維持し、後続リクエスト開始時に世代を進めて古いレスポンスを無視してください。
🧹 Nitpick comments (3)
apps/server/src/infrastructure/api-clients/thumbnails.ts (1)

13-20: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

共有された ThumbnailSize を使用してください。

Line 15 の 256 | 512 はドメイン契約を重複定義します。
@solid-imager/core/domain/thumbnails/schemasThumbnailSize を使用してください。
これにより、API クライアントと共有スキーマのサイズ定義が分離しません。

As per coding guidelines, "Use Zod schemas for domain contracts and schema-driven development."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/server/src/infrastructure/api-clients/thumbnails.ts` around lines 13 -
20, Update startThumbnailGeneration to import and use the shared ThumbnailSize
type from `@solid-imager/core/domain/thumbnails/schemas` instead of redeclaring
256 | 512, while preserving the existing options defaults and API call.

Source: Coding guidelines

packages/ui/src/screens/v2-manager-screen.tsx (1)

824-859: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

「Current run」セクションを共通コンポーネントへ抽出してください。

このブロックは BatchToolPanel の行732-767と完全に同一です。taggingStatusjobProgressactiveJobId の表示ロジックが2箇所に複製されます。将来どちらか一方だけを変更すると、表示が分岐します。CurrentRunSection として1つに集約してください。

♻️ 抽出例
+function CurrentRunSection(props: { manager: UseManagerPageResult }) {
+	return (
+		<Show when={props.manager.taggingStatus() || props.manager.jobProgress()}>
+			<section
+				aria-live="polite"
+				class="space-y-3 rounded-md border border-[var(--v2-border)] bg-[var(--v2-surface)] p-4"
+			>
+				{/* 既存の Current run マークアップをそのまま移動 */}
+			</section>
+		</Show>
+	);
+}

ThumbnailWarmupPanelBatchToolPanel の両方で <CurrentRunSection manager={props.manager} /> を使います。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ui/src/screens/v2-manager-screen.tsx` around lines 824 - 859,
Extract the duplicated “Current run” markup and its taggingStatus, jobProgress,
and activeJobId display logic into a shared CurrentRunSection component. Update
both ThumbnailWarmupPanel and BatchToolPanel to render it via CurrentRunSection
with the existing manager prop, preserving the current UI and behavior.
packages/ui/src/screens/manager-screen.tsx (1)

376-418: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

ソース選択のSelectブロックを共通コンポーネントへ抽出してください。

このファイル内で Select + SelectValue<unknown> + find(...) のブロックが3箇所に重複します(行260-295のtagging、この行376-418のthumbnails、行470-509のduplicates)。packages/ui/src/screens/v2-manager-screen.tsx は同じ処理を SourceSelect として抽出済みです。legacy画面でも同等のヘルパーを1つ定義すると、プレースホルダー文言や name 抽出ロジックの分岐を1箇所に集約できます。

♻️ 抽出例
+function LegacySourceSelect(props: {
+	manager: UseManagerPageResult;
+	onChange: (id: string | undefined) => void;
+	options: { id: string; name: string }[];
+	placeholder: string;
+	value: { id: string; name: string } | null;
+}) {
+	return (
+		<Select
+			itemComponent={(selectProps) => (
+				<SelectItem item={selectProps.item}>
+					{selectProps.item.rawValue.name}
+				</SelectItem>
+			)}
+			onChange={(value) => props.onChange(value?.id)}
+			options={props.options}
+			optionTextValue="name"
+			optionValue="id"
+			placeholder={props.placeholder}
+			value={props.value}
+		>
+			<SelectTrigger>
+				<SelectValue<unknown>>
+					{(state) => {
+						const option = state.selectedOption();
+						return option && typeof option === "object" && "name" in option
+							? (option as { name: string }).name
+							: props.placeholder;
+					}}
+				</SelectValue>
+			</SelectTrigger>
+			<SelectContent />
+		</Select>
+	);
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ui/src/screens/manager-screen.tsx` around lines 376 - 418, Extract
the duplicated source-selection Select markup into a shared SourceSelect helper
within manager-screen.tsx, following the existing v2-manager-screen.tsx
implementation. Replace the three tagging, thumbnails, and duplicates Select
blocks while preserving their manager source options, selected-source updates,
and surrounding labels or descriptions; centralize the placeholder and selected
name logic in SourceSelect.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/server/src/application/services/job-dispatch-service.ts`:
- Around line 9-12: job-dispatch-service.ts の infrastructure/jobs/thumbnails
直接参照を削除し、サムネイル処理を application 層のポート(インターフェース)として定義してください。Job dispatch service
はそのポートにのみ依存して実装を注入できる形にし、infrastructure 側で deleteThumbnail と
processThumbnailGenerationJob をポートへ適合させて composition root から注入してください。

In `@apps/server/src/routes/api/sources`.$mediaSourceId.thumbnail.$mediaId.ts:
- Around line 36-57: Update the missing-thumbnail branch in the route handler to
call queueThumbnailGeneration(mediaSourceId, mediaId, size) for every requested
size before the size === THUMBNAIL_SIZE_SMALL fallback logic. Preserve the
existing 256px-specific large-thumbnail fallback, and avoid queueing the same
request twice.

In `@packages/db/src/repositories/job-repository.ts`:
- Around line 72-90: Make active thumbnail job deduplication atomic: add a
partial unique index in the jobs schema covering type, source_id,
payload.mediaId, and payload.size for generate_thumbnail jobs with pending or
in_progress status, then update the job creation flow to use one INSERT ... ON
CONFLICT against that constraint instead of a separate lookup. Add an
integration test proving concurrent creation requests register only one job.

In `@packages/ui/src/screens/v2-manager-screen.tsx`:
- Around line 796-822: Update SourceSelect to accept a placeholder prop and use
it for both the unselected display and select placeholder instead of hardcoding
“All sources.” Pass “Select source” from ThumbnailWarmupPanel, preserving the
existing selection behavior and required-source button disabling.

In `@packages/ui/src/source-media-grid.tsx`:
- Around line 391-404: In the non-virtualized element-scroll branch of the
imageLoadPolicy in source-media-grid, change only the loading threshold from
INITIAL_HIGH_PRIORITY_MEDIA to initialPriorityMediaCount(). Keep fetchpriority’s
threshold as INITIAL_HIGH_PRIORITY_MEDIA and preserve the existing eager/lazy
behavior otherwise.

---

Duplicate comments:
In `@packages/ui/src/thumbnail-image.tsx`:
- Around line 24-98:
ThumbnailImageのcreateEffect内で、source.subscribeによる再実行を含む各load/loadSrcSet呼び出しを識別できる世代番号を追加してください。各リクエスト開始時のrequestIdをPromiseの完了処理で確認し、最新世代の場合のみsetUrl、setSrcSet、setError、source.onErrorを実行するように更新します。既存のcancelledチェックは維持し、後続リクエスト開始時に世代を進めて古いレスポンスを無視してください。

---

Nitpick comments:
In `@apps/server/src/infrastructure/api-clients/thumbnails.ts`:
- Around line 13-20: Update startThumbnailGeneration to import and use the
shared ThumbnailSize type from `@solid-imager/core/domain/thumbnails/schemas`
instead of redeclaring 256 | 512, while preserving the existing options defaults
and API call.

In `@packages/ui/src/screens/manager-screen.tsx`:
- Around line 376-418: Extract the duplicated source-selection Select markup
into a shared SourceSelect helper within manager-screen.tsx, following the
existing v2-manager-screen.tsx implementation. Replace the three tagging,
thumbnails, and duplicates Select blocks while preserving their manager source
options, selected-source updates, and surrounding labels or descriptions;
centralize the placeholder and selected name logic in SourceSelect.

In `@packages/ui/src/screens/v2-manager-screen.tsx`:
- Around line 824-859: Extract the duplicated “Current run” markup and its
taggingStatus, jobProgress, and activeJobId display logic into a shared
CurrentRunSection component. Update both ThumbnailWarmupPanel and BatchToolPanel
to render it via CurrentRunSection with the existing manager prop, preserving
the current UI and behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: b4f703b1-a7c0-4e69-8690-f20fb472f1af

📥 Commits

Reviewing files that changed from the base of the PR and between a56cffe and 3f6acac.

📒 Files selected for processing (44)
  • apps/server/public/openapi.json
  • apps/server/src/application/services/job-dispatch-service.ts
  • apps/server/src/application/services/thumbnail-service.ts
  • apps/server/src/components/media/media-grid-item.tsx
  • apps/server/src/components/media/thumbnail-image.tsx
  • apps/server/src/infrastructure/api-clients/thumbnails.ts
  • apps/server/src/infrastructure/api/routers/thumbnails-router.ts
  • apps/server/src/infrastructure/jobs/job-worker.ts
  • apps/server/src/infrastructure/jobs/thumbnails.ts
  • apps/server/src/routes/api/sources.$mediaSourceId.thumbnail.$mediaId.ts
  • apps/server/src/routes/manager.tsx
  • apps/server/src/routes/search.tsx
  • apps/server/src/routes/sources/$mediaSourceId/components/source-media-page.tsx
  • apps/server/src/routes/v2/manager.tsx
  • apps/server/src/routes/v2/search.tsx
  • apps/server/src/tests/e2e/ui-components.gallery.spec.ts
  • apps/server/src/tests/e2e/ui-gallery/src.tsx
  • apps/server/src/tests/e2e/ui-gallery/vite.config.ts
  • apps/server/src/tests/unit/application/services/job-dispatch-service.test.ts
  • apps/server/src/tests/unit/infrastructure/jobs/job-worker.test.ts
  • apps/tauri/src/components/media/media-grid-item.tsx
  • apps/tauri/src/components/media/thumbnail-image.tsx
  • apps/tauri/src/infrastructure/api-clients/thumbnails-api.ts
  • apps/tauri/src/infrastructure/media/thumbnail-runtime.ts
  • apps/tauri/src/routes/manager.tsx
  • apps/tauri/src/routes/search.tsx
  • apps/tauri/src/routes/sources/$mediaSourceId/components/source-media-page.tsx
  • packages/application/src/ports/thumbnail-service.ts
  • packages/application/src/services/thumbnail-service.ts
  • packages/core/src/domain/contract/thumbnails.contract.ts
  • packages/core/src/domain/sources/events.ts
  • packages/core/src/domain/thumbnails/schemas.ts
  • packages/db/src/repositories/job-repository.test.ts
  • packages/db/src/repositories/job-repository.ts
  • packages/ui/src/hooks/use-manager-page.ts
  • packages/ui/src/media-grid-item.tsx
  • packages/ui/src/screens/manager-screen.tsx
  • packages/ui/src/screens/search-screen.tsx
  • packages/ui/src/screens/source-media-screen.tsx
  • packages/ui/src/screens/v2-manager-screen.tsx
  • packages/ui/src/source-media-grid.tsx
  • packages/ui/src/thumbnail-image.tsx
  • packages/ui/src/thumbnail-source.test.ts
  • packages/ui/src/thumbnail-source.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • apps/server/src/routes/sources/$mediaSourceId/components/source-media-page.tsx
  • apps/tauri/src/routes/sources/$mediaSourceId/components/source-media-page.tsx
  • apps/server/src/routes/search.tsx
  • apps/tauri/src/routes/search.tsx
  • apps/server/src/routes/v2/search.tsx
  • apps/server/src/tests/e2e/ui-gallery/src.tsx

Comment thread apps/server/src/application/services/job-dispatch-service.ts Outdated
Comment thread packages/db/src/repositories/job-repository.ts
Comment thread packages/ui/src/screens/v2-manager-screen.tsx
Comment thread packages/ui/src/source-media-grid.tsx

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/server/drizzle/0022_sleepy_turbo.sql`:
- Around line 3-12: UPDATE the DELETE condition in the duplicate jobs cleanup so
only jobs with status `pending` can be deleted; do not include `in_progress`
rows in either the deleted duplicate set or the retained-row matching logic
unless required for safe deduplication. Preserve the existing duplicate matching
criteria for type, source_id, payload fields, and creation ordering.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 7cb87692-c6b2-4cd4-ba57-784c7cbe0da2

📥 Commits

Reviewing files that changed from the base of the PR and between 3f6acac and e928f2a.

📒 Files selected for processing (16)
  • apps/server/drizzle/0022_sleepy_turbo.sql
  • apps/server/drizzle/meta/0022_snapshot.json
  • apps/server/drizzle/meta/_journal.json
  • apps/server/src/application/services/job-dispatch-service.ts
  • apps/server/src/hooks/use-media-source-events.ts
  • apps/server/src/infrastructure/bootstrap.ts
  • apps/server/src/routes/api/sources.$mediaSourceId.thumbnail.$mediaId.ts
  • apps/server/src/tests/unit/application/services/job-dispatch-service.test.ts
  • packages/db/src/repositories/job-repository.test.ts
  • packages/db/src/repositories/job-repository.ts
  • packages/db/src/schema.ts
  • packages/ui/src/hooks/use-search-page.ts
  • packages/ui/src/hooks/use-source-media-page.ts
  • packages/ui/src/screens/v2-manager-screen.tsx
  • packages/ui/src/source-media-grid.tsx
  • packages/ui/src/thumbnail-image.tsx
🚧 Files skipped from review as they are similar to previous changes (8)
  • apps/server/src/hooks/use-media-source-events.ts
  • apps/server/src/tests/unit/application/services/job-dispatch-service.test.ts
  • apps/server/src/routes/api/sources.$mediaSourceId.thumbnail.$mediaId.ts
  • packages/db/src/repositories/job-repository.ts
  • packages/db/src/repositories/job-repository.test.ts
  • packages/ui/src/thumbnail-image.tsx
  • packages/ui/src/screens/v2-manager-screen.tsx
  • packages/ui/src/source-media-grid.tsx

Comment on lines +3 to +12
DELETE FROM "jobs" AS duplicate
USING "jobs" AS retained
WHERE duplicate."type" = 'generate_thumbnail'
AND duplicate."status" IN ('pending', 'in_progress')
AND retained."type" = duplicate."type"
AND retained."status" IN ('pending', 'in_progress')
AND retained."source_id" = duplicate."source_id"
AND retained."payload"->>'mediaId' = duplicate."payload"->>'mediaId'
AND retained."payload"->>'size' = duplicate."payload"->>'size'
AND (retained."created_at", retained."id") < (duplicate."created_at", duplicate."id");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

in_progress のジョブを物理削除します。

DELETE の条件は pendingin_progress の両方を対象にします。マイグレーション実行時にワーカーが稼働していると、実行中ジョブの行が消えます。その場合、ワーカーの完了更新は0行更新になり、結果とエラーが記録されません。

対策は2つあります。削除対象を pending のみに限定し、in_progress の重複は自然消滅を待つ方法が安全です。あるいは、マイグレーションをワーカー停止中に実行する運用手順を明記してください。

🛠️ pending のみを削除対象にする例
 	DELETE FROM "jobs" AS duplicate
 	USING "jobs" AS retained
 	WHERE duplicate."type" = 'generate_thumbnail'
-		AND duplicate."status" IN ('pending', 'in_progress')
+		AND duplicate."status" = 'pending'
 		AND retained."type" = duplicate."type"
 		AND retained."status" IN ('pending', 'in_progress')

この変更では、in_progress 行が残り、pending 側が削除されます。順序比較の条件は再検討が必要です。

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
DELETE FROM "jobs" AS duplicate
USING "jobs" AS retained
WHERE duplicate."type" = 'generate_thumbnail'
AND duplicate."status" IN ('pending', 'in_progress')
AND retained."type" = duplicate."type"
AND retained."status" IN ('pending', 'in_progress')
AND retained."source_id" = duplicate."source_id"
AND retained."payload"->>'mediaId' = duplicate."payload"->>'mediaId'
AND retained."payload"->>'size' = duplicate."payload"->>'size'
AND (retained."created_at", retained."id") < (duplicate."created_at", duplicate."id");
DELETE FROM "jobs" AS duplicate
USING "jobs" AS retained
WHERE duplicate."type" = 'generate_thumbnail'
AND duplicate."status" = 'pending'
AND retained."type" = duplicate."type"
AND retained."status" IN ('pending', 'in_progress')
AND retained."source_id" = duplicate."source_id"
AND retained."payload"->>'mediaId' = duplicate."payload"->>'mediaId'
AND retained."payload"->>'size' = duplicate."payload"->>'size'
AND (retained."created_at", retained."id") < (duplicate."created_at", duplicate."id");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/server/drizzle/0022_sleepy_turbo.sql` around lines 3 - 12, UPDATE the
DELETE condition in the duplicate jobs cleanup so only jobs with status
`pending` can be deleted; do not include `in_progress` rows in either the
deleted duplicate set or the retained-row matching logic unless required for
safe deduplication. Preserve the existing duplicate matching criteria for type,
source_id, payload fields, and creation ordering.

@hmjn023
hmjn023 merged commit f4505a4 into develop Aug 2, 2026
1 check passed
@coderabbitai coderabbitai Bot mentioned this pull request Aug 6, 2026
@hmjn023
hmjn023 deleted the perf/v2-media-grid-loading branch August 12, 2026 20:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant