Skip to content

fix: Queryとloaderの非同期状態を統一 (#578) - #591

Closed
hmjn023 wants to merge 4 commits into
developfrom
fix/issue-578-query-loader-state
Closed

fix: Queryとloaderの非同期状態を統一 (#578)#591
hmjn023 wants to merge 4 commits into
developfrom
fix/issue-578-query-loader-state

Conversation

@hmjn023

@hmjn023 hmjn023 commented Jul 5, 2026

Copy link
Copy Markdown
Owner

概要

Queryとroute loaderの非同期状態をServer/Tauriで統一し、補助データ取得中もApp Shellとページ本体を先に表示できるようにします。

変更内容

  • pending/data/empty/error/offlineとfetch状態の共通UIモデルを追加
  • QueryClient既定値、query key、transient error判定をServer/Tauriで共通化
  • 補助loaderをclient-side non-blocking prefetchへ変更し、media detail本体のみ必須取得を維持
  • Solid Router SSR Query統合を有効化し、二重Providerを解消
  • 検索、類似検索、source media、Tauri collectionへAbortSignalを伝播
  • Sources、Search、Config、Manager、Source Media、Media Detailでcached dataを維持
  • 状態変換、Query設定、query key、timeout/cancel、SSR重複fetchのテストを追加

検証

  • 全workspace typecheck
  • 変更対象Biome check
  • Server unit 161 tests
  • Server integration 64 tests(共有setup timeout対象は単独再実行で確認)
  • CLI 11 tests、Core 12 tests、UI 22 tests、Client 4 tests
  • Server/Tauri web production build
  • Playwright SSR E2E 5 tests

fixes #578

Summary by CodeRabbit

  • New Features
    • 検索(類似・メディア)でキャンセル可能になり、画面切り替え時の無駄な通信を抑制しました。
    • 読み込み中/更新中/オフライン/エラーの状態表示を統一し、各画面の案内を改善しました。
  • Bug Fixes
    • ルート遷移のプリフェッチ制御とキャッシュ更新の見直しにより、設定ページの重複発行やちらつきを軽減しました。
    • オフライン時は保存済みデータを優先して表示しやすくしました。
  • Tests
    • 設定画面の重複発行防止をE2Eで確認するテストを追加しました。

@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: fced6a52-0750-4c98-b693-b705889f9a6a

📥 Commits

Reviewing files that changed from the base of the PR and between 15f5b62 and 1dee9a3.

📒 Files selected for processing (5)
  • apps/server/src/routes/__root.tsx
  • apps/server/src/routes/sources/$mediaSourceId/components/source-media-page.tsx
  • packages/ui/src/hooks/use-source-media-page.ts
  • packages/ui/src/screens/source-media-screen.tsx
  • packages/ui/src/source-media-page.tsx

📝 Walkthrough

Walkthrough

QueryClient設定、query key生成、QueryUiState変換を共通化し、Server/Tauriのloaderをprefetch中心へ変更しました。検索・一覧APIへAbortSignalを伝播し、各画面の状態表示とクエリ無効化キーを共通定義へ統一しています。

Changes

共通Query基盤

Layer / File(s) Summary
クライアントAPIとQueryClient設定
packages/client/src/*, packages/ui/src/query-options/*
一時的エラー判定、TimedFetch公開、QueryClient設定、client-only prefetchを追加し、関連テストを整備した。
Query keyとUI状態モデル
packages/ui/src/query-options/search-query.ts, packages/ui/src/query-options/sources-query.ts, packages/ui/src/query-state.ts, packages/ui/src/query-state.test.ts
検索・ソース用query key factoryと、pending/data/empty/error/offlineおよびfetch状態を表すQueryUiState変換を追加した。

Server側Query統合

Layer / File(s) Summary
QueryOptionsとRouter
apps/server/src/infrastructure/api-clients/queries/index.ts, apps/server/src/infrastructure/api-clients/search-api.ts, apps/server/src/router.tsx
query optionsへquery key・default configを付与し、検索APIへAbortSignalを渡し、QueryClientとSSR Query統合を更新した。
Routeのprefetch化
apps/server/src/routes/config.tsx, apps/server/src/routes/manager.tsx, apps/server/src/routes/search.tsx, apps/server/src/routes/sources/*
loaderの待機型取得をprefetch方式へ変更し、config・sourcesの表示状態をQueryUiStateへ移行した。
検証とマウント後描画
apps/server/src/tests/e2e/pages.spec.ts, apps/server/src/routes/sources/$mediaSourceId/components/source-media-page.tsx
configの重複RPC検証を追加し、メディア一覧をマウント後に描画する制御を追加した。

Tauri側Query統合

Layer / File(s) Summary
QueryOptionsとAPI
apps/tauri/src/queries/index.ts, apps/tauri/src/infrastructure/api-clients/*
query key・default configのラッパー化、検索APIのAbortSignal対応、projects query option削除を行った。
Collections
apps/tauri/src/collections/*
各collectionのquery keyをfactory化し、queryFnからAbortSignalをAPIへ伝播した。
RouterとRoutes
apps/tauri/src/router.tsx, apps/tauri/src/routes/*
QueryClient設定を共通化し、config・search・manager・sources関連loaderをprefetch方式へ変更した。

UIフックと画面

Layer / File(s) Summary
ManagerとSearchフロー
packages/ui/src/hooks/use-manager-page.ts, packages/ui/src/hooks/use-search-page.ts, packages/ui/src/screens/manager-screen.tsx, packages/ui/src/screens/search-screen.tsx
queryStates、contentState、filterStates、AbortSignal、prefetchを統合し、状態別表示を追加した。
Source mediaフロー
packages/ui/src/hooks/use-source-media-page.ts, packages/ui/src/source-media-page.ts, packages/ui/src/screens/source-media-screen.tsx
source media queryのkey・Signal・UI状態・client-only実行・invalidate処理を統合した。
共有画面とsidebar
packages/ui/src/screens/sources-screen.tsx, packages/ui/src/screens/media-detail-screen.tsx, packages/ui/src/media-sidebar-content.tsx, apps/server/src/routes/__root.tsx
画面状態をQueryUiStateへ移行し、invalidate keyをfactory化し、rootのSuspenseラップを削除した。

Estimated code review effort: 4 (Complex) | ~75 minutes

Possibly related issues

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 変更内容の中心であるQuery/loaderの非同期状態統一を簡潔かつ具体的に表しています。
Linked Issues check ✅ Passed 共通のQuery UI状態、AbortSignal伝播、Query設定/キー集約、loaderの非同期化とSSR統合が揃っており、要件を概ね満たしています。
Out of Scope Changes check ✅ Passed 変更は状態統一と関連するQuery/loader基盤の整理に集中しており、明確なスコープ外変更は見当たりません。
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/issue-578-query-loader-state

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


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

@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

🧹 Nitpick comments (5)
packages/client/src/api-error.test.ts (1)

1-18: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

CORS_ERROR / UNKNOWN コードのテストが未カバー。

APIErrorcode は5種類あるが、テストは NETWORK_ERROR/TIMEOUT(true)と SERVER_ERROR(false)のみを検証している。CORS_ERRORUNKNOWN の判定結果もアサートしておくと、意図しない仕様変更を早期に検知できる。

🤖 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/client/src/api-error.test.ts` around lines 1 - 18, The
isTransientApiError test coverage is incomplete: APIError codes CORS_ERROR and
UNKNOWN are not asserted. Update the api-error.test.ts cases around
isTransientApiError to include explicit expectations for APIError with
CORS_ERROR and UNKNOWN, using the existing APIError and isTransientApiError
symbols, so all five code paths are covered alongside the current NETWORK_ERROR,
TIMEOUT, and SERVER_ERROR checks.
packages/client/src/api-error.ts (1)

26-40: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

メッセージ文字列マッチによる誤分類のリスク

isTransientApiErrorAPIError 以外の Error に対して message の部分文字列一致("network", "timeout", "failed to fetch")で一時的エラーかどうかを判定している。この関数は createAppQueryClientConfig(isTransientApiError) に渡され、QueryClient全体のリトライ可否を左右する(apps/tauri/src/router.tsx, apps/server/src/router.tsx 参照)。バリデーションエラーやビジネスロジック上のエラーメッセージにたまたま "network" や "timeout" という単語が含まれる場合、本来リトライすべきでないエラーが誤って一時的エラーとして扱われ、不要なリトライが発生する可能性がある。

error.name === "TypeError" (fetchの典型的な失敗) など、より堅牢な判定基準の併用を検討してほしい。

🤖 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/client/src/api-error.ts` around lines 26 - 40, isTransientApiError
currently classifies non-APIError cases by loose message substring matches,
which can mislabel business/validation errors as transient. Update
isTransientApiError to use stronger checks in addition to APIError.code, such as
inspecting error.name (for example TypeError from fetch failures) or other
explicit failure signals, and reduce reliance on message.includes in
packages/client/src/api-error.ts. Keep the behavior aligned with
createAppQueryClientConfig(isTransientApiError) usage in the router setup so
only genuinely retryable errors are treated as transient.
apps/server/src/infrastructure/api-clients/queries/index.ts (1)

24-58: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

list 系 queryOptions の重複パターンを共通化できます。

tagsQueryOptionsconfigQueryOptions の6関数はいずれも ...utils.X.list.queryOptions(), queryKey: XQueryKeys.all(), ...defaultXQueryConfig という同一構造の繰り返しです。小さなヘルパーで抽出するとDRY性が向上します(apps/tauri 側にも同様のパターンがあれば併せて統一を検討してください)。

♻️ 提案リファクタ例
+function withListQueryConfig<T extends { queryKey: unknown }>(
+	options: T,
+	queryKey: readonly unknown[],
+	config: { staleTime: number },
+) {
+	return { ...options, queryKey, ...config };
+}
+
-export const tagsQueryOptions = () => ({
-	...utils.tags.list.queryOptions(),
-	queryKey: tagsQueryKeys.all(),
-	...defaultTagsQueryConfig,
-});
+export const tagsQueryOptions = () =>
+	withListQueryConfig(
+		utils.tags.list.queryOptions(),
+		tagsQueryKeys.all(),
+		defaultTagsQueryConfig,
+	);
🤖 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/queries/index.ts` around lines 24
- 58, The six query option builders in the query options module all repeat the
same `list.queryOptions()` plus `queryKey` plus default config pattern; extract
that shared shape into a small helper and have `tagsQueryOptions`,
`mediaSourcesQueryOptions`, `allProjectsQueryOptions`,
`allCharactersQueryOptions`, `allIpsQueryOptions`, `allAuthorsQueryOptions`, and
`configQueryOptions` delegate to it. Keep the existing behavior and type
inference intact by wiring the helper around the relevant `utils.*` and
`*QueryKeys` / `default*QueryConfig` values, and apply the same consolidation
pattern anywhere similar in the `apps/tauri` side if present.
apps/server/src/tests/e2e/pages.spec.ts (1)

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

固定のsleepに依存したテストはCIでflakyになりやすい

waitForTimeout(500) の代わりに、page.waitForLoadState("networkidle") や設定フォームの表示を待つ(例: await page.getByRole(...).waitFor())ようにすると、環境負荷によるタイミングのブレに強くなります。

♻️ 提案例
 		await page.goto("/config");
-		await page.waitForTimeout(CLIENT_QUERY_SETTLE_MS);
+		await page.waitForLoadState("networkidle");
 
 		expect(rpcRequestCount).toBeLessThanOrEqual(1);

Also applies to: 26-40

🤖 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/tests/e2e/pages.spec.ts` at line 5, The e2e test in
pages.spec.ts relies on a fixed CLIENT_QUERY_SETTLE_MS sleep and
waitForTimeout(500), which makes it flaky under CI timing variance. Update the
affected test flow to wait on a deterministic condition instead, such as
page.waitForLoadState("networkidle") or waiting for the specific settings form
element via page.getByRole(...).waitFor(), and remove the hardcoded timeout from
the test logic.
apps/server/src/routes/config.tsx (1)

29-39: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

pending/error表示にroleが付与されていない

fetchState === "background-fetching" / "paused" のブロックには role="status" が付いていますが、pending(Line 29-31)と error/offline(Line 33-39)のブロックには付与されていません。エラー・オフライン表示はユーザーへの通知として重要なため、スクリーンリーダーに伝わるよう role="status"(またはエラーはrole="alert")を統一的に付けることを推奨します。

♻️ 提案例
 			<Show when={state().phase === "pending"}>
-				<div class="py-10 text-center">Loading settings...</div>
+				<div class="py-10 text-center" role="status">Loading settings...</div>
 			</Show>

 			<Show when={state().phase === "error" || state().phase === "offline"}>
-				<div class="py-10 text-red-500">
+				<div class="py-10 text-red-500" role="alert">
 					{state().phase === "offline"
 						? "Offline. Settings will load after reconnecting."
 						: "Error loading settings."}
 				</div>
 			</Show>
🤖 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/routes/config.tsx` around lines 29 - 39, The pending and
error/offline state messages in the config route are missing accessible
live-region roles, unlike the existing background-fetching/paused status blocks.
Update the conditional UI in config.tsx so the pending notice and the
error/offline notice use an appropriate role consistently, using role="status"
for general loading notifications and role="alert" if you want the error/offline
message to be announced more urgently. Reference the existing Show blocks around
state().phase and keep the change localized to those message containers.
🤖 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/tauri/src/queries/index.ts`:
- Around line 24-58: The query option helpers in queryOptions are reusing the
same queryKey across different queryFn implementations, which can cause cache
collisions and startup-order-dependent behavior. Review tagsQueryOptions,
mediaSourcesQueryOptions, allProjectsQueryOptions, allCharactersQueryOptions,
allIpsQueryOptions, allAuthorsQueryOptions, and configQueryOptions, plus any
related collection queries such as projectsForMediaQueryOptions. Make the key or
the fetcher consistent for each data source, so a given key always maps to one
implementation and shared keys like tagsQueryKeys.all() or
projectsQueryKeys.forMedia(...) are not bound to multiple backends.

---

Nitpick comments:
In `@apps/server/src/infrastructure/api-clients/queries/index.ts`:
- Around line 24-58: The six query option builders in the query options module
all repeat the same `list.queryOptions()` plus `queryKey` plus default config
pattern; extract that shared shape into a small helper and have
`tagsQueryOptions`, `mediaSourcesQueryOptions`, `allProjectsQueryOptions`,
`allCharactersQueryOptions`, `allIpsQueryOptions`, `allAuthorsQueryOptions`, and
`configQueryOptions` delegate to it. Keep the existing behavior and type
inference intact by wiring the helper around the relevant `utils.*` and
`*QueryKeys` / `default*QueryConfig` values, and apply the same consolidation
pattern anywhere similar in the `apps/tauri` side if present.

In `@apps/server/src/routes/config.tsx`:
- Around line 29-39: The pending and error/offline state messages in the config
route are missing accessible live-region roles, unlike the existing
background-fetching/paused status blocks. Update the conditional UI in
config.tsx so the pending notice and the error/offline notice use an appropriate
role consistently, using role="status" for general loading notifications and
role="alert" if you want the error/offline message to be announced more
urgently. Reference the existing Show blocks around state().phase and keep the
change localized to those message containers.

In `@apps/server/src/tests/e2e/pages.spec.ts`:
- Line 5: The e2e test in pages.spec.ts relies on a fixed CLIENT_QUERY_SETTLE_MS
sleep and waitForTimeout(500), which makes it flaky under CI timing variance.
Update the affected test flow to wait on a deterministic condition instead, such
as page.waitForLoadState("networkidle") or waiting for the specific settings
form element via page.getByRole(...).waitFor(), and remove the hardcoded timeout
from the test logic.

In `@packages/client/src/api-error.test.ts`:
- Around line 1-18: The isTransientApiError test coverage is incomplete:
APIError codes CORS_ERROR and UNKNOWN are not asserted. Update the
api-error.test.ts cases around isTransientApiError to include explicit
expectations for APIError with CORS_ERROR and UNKNOWN, using the existing
APIError and isTransientApiError symbols, so all five code paths are covered
alongside the current NETWORK_ERROR, TIMEOUT, and SERVER_ERROR checks.

In `@packages/client/src/api-error.ts`:
- Around line 26-40: isTransientApiError currently classifies non-APIError cases
by loose message substring matches, which can mislabel business/validation
errors as transient. Update isTransientApiError to use stronger checks in
addition to APIError.code, such as inspecting error.name (for example TypeError
from fetch failures) or other explicit failure signals, and reduce reliance on
message.includes in packages/client/src/api-error.ts. Keep the behavior aligned
with createAppQueryClientConfig(isTransientApiError) usage in the router setup
so only genuinely retryable errors are treated as transient.
🪄 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: a0767ecf-0f99-4f69-aeed-9ce469774783

📥 Commits

Reviewing files that changed from the base of the PR and between e3fbfac and 47dc0c6.

📒 Files selected for processing (51)
  • apps/server/src/infrastructure/api-clients/error-policy.ts
  • apps/server/src/infrastructure/api-clients/queries/index.ts
  • apps/server/src/infrastructure/api-clients/search-api.ts
  • apps/server/src/router.tsx
  • apps/server/src/routes/config.tsx
  • 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/sources/$mediaSourceId/index.tsx
  • apps/server/src/routes/sources/index.tsx
  • apps/server/src/tests/e2e/pages.spec.ts
  • apps/tauri/src/collections/authors-collection.ts
  • apps/tauri/src/collections/characters-collection.ts
  • apps/tauri/src/collections/ips-collection.ts
  • apps/tauri/src/collections/projects-collection.ts
  • apps/tauri/src/collections/sources-collection.ts
  • apps/tauri/src/collections/tags-collection.ts
  • apps/tauri/src/infrastructure/api-clients/projects-api.ts
  • apps/tauri/src/infrastructure/api-clients/search-api.ts
  • apps/tauri/src/queries/index.ts
  • apps/tauri/src/router.tsx
  • apps/tauri/src/routes/config.tsx
  • apps/tauri/src/routes/manager.tsx
  • apps/tauri/src/routes/search.tsx
  • apps/tauri/src/routes/sources/$mediaSourceId/$mediaId/index.tsx
  • apps/tauri/src/routes/sources/$mediaSourceId/index.tsx
  • apps/tauri/src/routes/sources/index.tsx
  • packages/client/src/api-error.test.ts
  • packages/client/src/api-error.ts
  • packages/client/src/create-client.test.ts
  • packages/client/src/create-client.ts
  • packages/client/src/index.ts
  • packages/ui/src/hooks/use-manager-page.ts
  • packages/ui/src/hooks/use-search-page.ts
  • packages/ui/src/hooks/use-source-media-page.ts
  • packages/ui/src/media-sidebar-content.tsx
  • packages/ui/src/query-options/index.ts
  • packages/ui/src/query-options/prefetch.ts
  • packages/ui/src/query-options/query-client.test.ts
  • packages/ui/src/query-options/query-client.ts
  • packages/ui/src/query-options/search-query.test.ts
  • packages/ui/src/query-options/search-query.ts
  • packages/ui/src/query-options/sources-query.ts
  • packages/ui/src/query-state.test.ts
  • packages/ui/src/query-state.ts
  • packages/ui/src/screens/manager-screen.tsx
  • packages/ui/src/screens/media-detail-screen.tsx
  • packages/ui/src/screens/search-screen.tsx
  • packages/ui/src/screens/source-media-screen.tsx
  • packages/ui/src/screens/sources-screen.tsx
  • packages/ui/src/source-media-page.tsx
💤 Files with no reviewable changes (1)
  • apps/server/src/infrastructure/api-clients/error-policy.ts

Comment thread apps/tauri/src/queries/index.ts
@hmjn023

hmjn023 commented Jul 9, 2026

Copy link
Copy Markdown
Owner Author

CodeRabbit 指摘に対応しました。

  • Tauri の TanStack DB collection 用 queryKey を collections/* namespace に分離し、通常の Query cache key と衝突しないようにしました
  • projectsForMediaQueryOptions の重複 helper を削除し、~/queries 側へ一本化しました
  • origin/develop を merge して App Shellを常時表示しRouterのpending/error境界を整備する #577 反映後の状態へ追従しました

検証:

  • bun run typecheck
  • bun run --cwd apps/tauri lint
  • bun run --cwd packages/ui lint
  • bun run --cwd packages/ui test
  • bun run --cwd apps/tauri build:web
  • git diff --check

@hmjn023 hmjn023 closed this Jul 10, 2026
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