Skip to content

Develop - #559

Merged
hmjn023 merged 28 commits into
mainfrom
develop
Jul 3, 2026
Merged

hmjn023 merged 28 commits into
mainfrom
develop

Conversation

@hmjn023

@hmjn023 hmjn023 commented Jun 29, 2026 •

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features

    • ベクター検索で「類似元を解除」ボタンを追加し、解除時に検索表示状態をリセット。
    • 抽出拡張で pixivFANBOX 対応を拡充(Fanboxページからのメディア処理)。
  • Bug Fixes

    • AI処理・ジョブ・自動タグ付けのログを強化し、開始/完了と所要時間を追跡しやすく改善。
    • 未対応RPCはメソッド/URLを含めて記録しつつ 404 を返すように変更。併せて著者の platform 対応をバックアップ/復元・同期・検索/表示/ファイル名生成へ反映。

hmjn and others added 2 commits June 29, 2026 23:25

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces comprehensive logging for AI tagging, vector similarity search, and job execution, capturing duration and execution contexts. It also adds a feature to clear the vector search anchor in the UI. The review feedback highlights three main areas for improvement: synchronizing the session storage when clearing the vector search anchor to prevent state inconsistency, avoiding optional chaining on required API schema fields, and handling invalid data records gracefully by logging and returning null instead of using fallback values.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +64 to +70
export const clearVectorSearchAnchor = () => {
setSearchState({
similarityAnchorMediaId: null,
offset: 0,
scrollY: 0,
});
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

activateVectorSearch では sessionStorage に similarityAnchorMediaId などの状態を保存していますが、clearVectorSearchAnchor では sessionStorage の更新や削除が行われていません。これにより、ページをリロードした際などにセッションストレージから古いデータが復元され、状態の不整合が発生する可能性があります。clearVectorSearchAnchor 内でも sessionStorage の同期(または削除)を行うことを検討してください。

Suggested change
export const clearVectorSearchAnchor = () => {
setSearchState({
similarityAnchorMediaId: null,
offset: 0,
scrollY: 0,
});
};
export const clearVectorSearchAnchor = () => {
setSearchState({
similarityAnchorMediaId: null,
offset: 0,
scrollY: 0,
});
if (typeof sessionStorage !== "undefined") {
const stored = sessionStorage.getItem("current-all");
if (stored) {
try {
const parsed = JSON.parse(stored);
parsed.similarityAnchorMediaId = null;
sessionStorage.setItem("current-all", JSON.stringify(parsed));
} catch {
sessionStorage.removeItem("current-all");
}
}
}
};

{
...logContext,
execution: "remote",
detectionCount: result.detections.length,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

リモートサーバーからのレスポンス result の構造を検証する際、APIスキーマで必須とされているフィールドに対してオプショナルチェーニング(?.)やNull合体演算子(??)を使用することは避けてください。これらはスキーマ契約違反を隠蔽し、予期しない挙動の原因となります。代わりに、Zodなどのスキーマ定義を用いて受信時に厳密なバリデーションを行うか、型安全なガードを実装してください。

const parsedResult = aiResponseSchema.parse(result);
const detectionCount = parsedResult.detections.length;
References
  1. Do not use optional chaining (?.) or nullish coalescing (??) on fields that are defined as required/mandatory in the API contract or schema.

Comment on lines +54 to +56
tagCount: result ? Object.keys(result.general).length : 0,
characterCount: result ? Object.keys(result.character).length : 0,
ipCount: result?.ips.length ?? 0,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

データレコードのパースにおいて、result 内の必須フィールドが欠落している場合にオプショナルチェーニングやデフォルト値(0)を使用すると、スキーマ違反やデータ破損が隠蔽されてしまいます。破損したレコードは、例外をスローしてバッチ全体を失敗させるのではなく、エラーをログに記録した上で null を返して安全にスキップ(または後続処理で flatMap 等で除外)するように実装してください。

try {
	if (!result || !result.general || !result.character) {
		throw new Error("Invalid record structure");
	}
	const tagCount = Object.keys(result.general).length;
	const characterCount = Object.keys(result.character).length;
} catch (error) {
	console.error("Failed to parse record:", error);
	return null;
}
References
  1. When parsing data records (e.g., from a database), handle invalid records gracefully by returning null and logging an error, rather than throwing an exception.
  2. Do not use optional chaining (?.) or nullish coalescing (??) on fields that are defined as required/mandatory in the API contract or schema.

@coderabbitai

coderabbitai Bot commented Jun 29, 2026 •

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

サーバーのログ出力を構造化し、著者を platform/account ベースで扱うデータモデルと永続化を追加しました。LanceDBダンプとバックアップ復元は新しい著者構造と画像取得に対応し、検索UIとXtracterも新しい著者表示とFanbox/Danbooru対応へ更新されています。

Changes

サーバー構造化ログ

Layer / File(s) Summary
JobWorkerの開始/完了/失敗ログ
apps/server/src/infrastructure/jobs/job-worker.ts
processJob に startedAt を導入し、Job started/Job completed/Job failed を durationMs・jobId・type・mediaSourceId・parentId・isAiJob を含む構造化ログで出力するよう変更。
各ジョブ処理の完了ログ
apps/server/src/infrastructure/jobs/ccip-jobs.ts, apps/server/src/infrastructure/jobs/tagging-jobs.ts, apps/server/src/tests/unit/infrastructure/jobs/ccip-jobs.test.ts, apps/server/src/tests/unit/infrastructure/jobs/tagging-jobs.test.ts
processCcipExtractionJob が extract の戻り値を受け取り skipped 等を含む完了ログを追加。processAutoTaggingJob がタグ件数を含む完了ログを追加。対応するテストのモック返り値も構造化レスポンスへ更新。
AIルーターの構造化ログ
apps/server/src/infrastructure/api/routers/ai-router.ts
tag と detectAndCropCharacters に startedAt・logContext を導入し、開始・完了・失敗ログを追加。startCcipExtraction にキュー投入ログを追加し、startBatchCcipExtraction の条件記述を整形。
メディアルーターの構造化ログ
apps/server/src/infrastructure/api/routers/media-router.ts
searchSimilar に開始/完了/失敗ログと durationMs を追加し、delete/move/bulkDelete の warn 呼び出し形式を更新。
RPCルートの未マッチ警告ログ
apps/server/src/routes/api/rpc.$.ts
logger を import し、レスポンス未生成時に method と url を含む警告ログを出してから 404 を返すよう変更。

著者プラットフォームとバックアップ

Layer / File(s) Summary
著者スキーマとRepository
packages/core/src/domain/media/schemas.ts, packages/core/src/domain/authors/schemas.ts, packages/core/src/domain/repositories/author-repository.ts, packages/db/src/repositories/author-repository.ts, packages/db/src/schema.ts, apps/server/drizzle/0017_ambitious_gideon.sql, apps/server/drizzle/meta/_journal.json
author_platform 列挙、author_accounts テーブル、著者アカウント型、findOrCreateBulk の NewAuthor[] 対応、単発作成経路の更新、対応マイグレーションと journal エントリが追加。
LanceDBダンプのAPI拡張
packages/application/src/ports/lancedb-dump-service.ts, packages/application/src/services/lancedb-dump-service.ts, apps/server/src/application/services/lancedb-dump-service.ts
readMediaFilePaths の契約と公開APIが追加され、LanceDBサービスは media から id と filePath を返すようになった。manifestバージョン更新、media_authors の platform 列、同期時スキーマ移行、作者キー構成、画像パス取得も更新。
BackupServiceの復元とダンプ
apps/server/src/application/services/backup-service.ts
authorRestoreKey と著者復元ロジックが追加され、_restoreRelations と _transformMediaList が platform と accounts を使う形へ更新。createDump の lancedb モードではキャッシュTAR生成と画像同梱が追加され、各取得箇所の authors include も拡張。
バックアップとLanceDBのテスト更新
apps/server/src/tests/integration/backup/backup-service.test.ts, apps/server/src/tests/unit/application/services/lancedb-dump-service.test.ts, apps/server/src/tests/integration/repository/author-dedupe.test.ts, apps/server/src/tests/unit/application/services/media-processing-service.test.ts, apps/server/src/tests/unit/domain/media/schemas.test.ts, apps/server/src/tests/unit/infrastructure/jobs/download-jobs.test.ts
バックアップ統合テスト、LanceDBダンプ単体テスト、著者重複排除テスト、ダウンロードファイル名テストが platform と新しい著者アカウント構造に合わせて更新された。

検索UIの著者表示

Layer / File(s) Summary
ProSearchBuilderの著者ラベル
apps/server/src/components/media/pro-search-builder.tsx, packages/ui/src/pro-search-builder.tsx
getAuthorLabel の表示から (twitter) を外し、accountId を : 区切りで表示するように変更。候補絞り込みと optionValue も更新。
SearchFiltersの選択値対応
apps/server/src/components/media/search-filters.tsx, packages/ui/src/search-filters.tsx
getSelectedItemValue を追加し、選択済みバッジの照合を切り替えたうえで、作者フィルタが author.id と author.name を分けて扱うように変更。

XtracterのFanbox/Danbooru対応

Layer / File(s) Summary
Xtracterのスキーマとmanifest
apps/xtracter/src/schema.ts, apps/xtracter/public/manifest.json, apps/xtracter/README.md
authorSchema に platform を追加し、Fanbox 用の host permissions と content script 対象を manifest に追加。README も対応サイトを更新。
FanboxとDanbooruの収集処理
apps/xtracter/src/content/fanbox.ts, apps/xtracter/src/content/index.ts, apps/xtracter/src/content/twitter.ts, apps/xtracter/src/content/danbooru.ts
processFanboxMedia、getFanboxImageUrl、extractMetadata を追加し、Fanbox ページの画像とメタデータを処理。processMedia に Fanbox 分岐を追加し、Danbooru と Twitter の著者にも platform を付与。
媒体ファイル名生成
packages/core/src/domain/media/utils/filename-utils.ts, packages/core/tests/filename-utils.test.ts
extractIdFromUrl が Fanbox 投稿URLを扱うようになり、generateMediaFilename は accountId・platform・postId・assetId ベースへ変更された。

レビュー自動化設定

Layer / File(s) Summary
.coderabbit.yaml設定
.coderabbit.yaml
.coderabbit.yaml が新規追加され、レビュー設定が構成された。

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

Possibly related PRs

  • hmjn023/solid-imager#556: ccip 系の開始/完了ログや類似検索まわりの処理追加と直接つながる変更が含まれています。
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.70% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive タイトルが「Develop」とだけあり、変更内容を示す情報がなく、何が主な変更点か判断できません。 主要な変更点を1文で具体的に示すタイトルにしてください。例: 「作者アカウント対応と検索/ダンプ処理の改善」
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
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
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch develop

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: 3

🧹 Nitpick comments (1)
apps/server/src/infrastructure/api/routers/media-router.ts (1)

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

logContext を共通化して startedAt もそのまま流してください。

同じキーを 3 箇所で手書きしているので、今後どれか 1 箇所だけ項目がずれるリスクがあります。const logContext = { startedAt, anchorMediaId: input.anchorMediaId, mediaSourceId: input.mediaSourceId, topK: input.topK } を作って各ログで展開しておくと、このコホートで揃えたい構造化ログの形を維持しやすいです。

差分イメージ
-			const startedAt = Date.now();
-			logger.info(
-				{
-					anchorMediaId: input.anchorMediaId,
-					mediaSourceId: input.mediaSourceId,
-					topK: input.topK,
-				},
-				"Vector similarity search started",
-			);
+			const startedAt = Date.now();
+			const logContext = {
+				startedAt,
+				anchorMediaId: input.anchorMediaId,
+				mediaSourceId: input.mediaSourceId,
+				topK: input.topK,
+			};
+			logger.info(logContext, "Vector similarity search started");
 			try {
 				const result = await ccipVectorService.searchSimilar(
 					input.anchorMediaId,
 					input.topK,
 					input.mediaSourceId,
 				);
 				logger.info(
 					{
-						anchorMediaId: input.anchorMediaId,
-						mediaSourceId: input.mediaSourceId,
-						topK: input.topK,
+						...logContext,
 						resultCount: result.media.length,
 						durationMs: Date.now() - startedAt,
 					},
 					"Vector similarity search completed",
 				);
 				return result;
 			} catch (error) {
 				logger.error(
 					{
+						...logContext,
 						err: error,
-						anchorMediaId: input.anchorMediaId,
-						mediaSourceId: input.mediaSourceId,
-						topK: input.topK,
 						durationMs: Date.now() - startedAt,
 					},
 					"Vector similarity search failed",
 				);
🤖 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/routers/media-router.ts` around lines 46 -
84, The Vector similarity search logging in media-router repeats the same fields
across the start, success, and error paths, which can drift over time. In the
search handler around ccipVectorService.searchSimilar, introduce a shared
logContext that includes startedAt plus anchorMediaId, mediaSourceId, and topK,
and reuse it in logger.info/logger.error so every log entry stays structurally
consistent. Keep the timing data flowing through the same shared context and
only add resultCount or err where needed.
🤖 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/infrastructure/api/routers/ai-router.ts`:
- Around line 191-197: The upload logging in ai-router still records
user-provided file names, which can leak PII into start/complete/failure logs.
Update the logContext built in the file-input branch of the AI upload flow so it
no longer includes input.file.name; use a non-identifying alternative such as a
hashed identifier or keep only fileSize. Make the same change anywhere the
upload lifecycle logs reference the file name, including the later log block
noted in the review, so all related logs stay consistent and privacy-safe.
- Around line 246-251: callRemoteTagging() と callRemoteCrop()
の戻り値をそのまま型アサーションせず、ai-router.ts の該当処理で taggingResponseSchema と
detectAndCropResponseSchema を使って parse してから result を扱うように修正してください。ORPC 境界から来る
unknown をスキーマで検証し、result.detections.length などの参照やログ/返却は parse 後の値に限定してください。関連箇所は
callRemoteTagging、callRemoteCrop、result.detections を扱う処理です。

In `@packages/ui/src/stores/search-store.ts`:
- Around line 64-70: `clearVectorSearchAnchor` only resets the in-memory search
state, so the cleared anchor is restored after reload from the persisted
`"current-all"` value. Update this function to also clear or null out the saved
`similarityAnchorMediaId` in sessionStorage, matching the persistence behavior
used by `activateVectorSearch` and the restore logic in
`use-current-search-persistence`, so the解除 state remains after refresh.

---

Nitpick comments:
In `@apps/server/src/infrastructure/api/routers/media-router.ts`:
- Around line 46-84: The Vector similarity search logging in media-router
repeats the same fields across the start, success, and error paths, which can
drift over time. In the search handler around ccipVectorService.searchSimilar,
introduce a shared logContext that includes startedAt plus anchorMediaId,
mediaSourceId, and topK, and reuse it in logger.info/logger.error so every log
entry stays structurally consistent. Keep the timing data flowing through the
same shared context and only add resultCount or err where needed.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e4805d8f-9c2e-4591-b41a-ff7310b0cfd9

📥 Commits

Reviewing files that changed from the base of the PR and between 1e65fe4 and 032f3e5.

📒 Files selected for processing (10)
  • apps/server/src/infrastructure/api/routers/ai-router.ts
  • apps/server/src/infrastructure/api/routers/media-router.ts
  • apps/server/src/infrastructure/jobs/ccip-jobs.ts
  • apps/server/src/infrastructure/jobs/job-worker.ts
  • apps/server/src/infrastructure/jobs/tagging-jobs.ts
  • apps/server/src/routes/api/rpc.$.ts
  • apps/server/src/tests/unit/infrastructure/jobs/ccip-jobs.test.ts
  • apps/server/src/tests/unit/infrastructure/jobs/tagging-jobs.test.ts
  • packages/ui/src/search-control-panel.tsx
  • packages/ui/src/stores/search-store.ts

Comment thread apps/server/src/infrastructure/api/routers/ai-router.ts
Comment thread apps/server/src/infrastructure/api/routers/ai-router.ts Outdated
Comment thread packages/ui/src/stores/search-store.ts
hmjn023 and others added 3 commits June 30, 2026 01:05
…ng from DB

createDump lancedb mode now checks for an existing cache directory
(.cache/lancedb-cache/source-{id}/). When found, the cache is
tarred directly without DB queries or writeToLanceDB rebuild.
Falls back to the original rebuild path when cache is unavailable.
- Check manifest.json instead of just directory existence
- Wrap cache path in try-catch for safe fallback to DB rebuild
- Parallelize image downloads with Promise.all
- Only readMediaFilePaths when includeImages is true
perf(backup): LanceDB dump で既存キャッシュを直接使用

@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/application/services/backup-service.ts`:
- Around line 1548-1597: The cache-read fallback in the TAR dump path is
ineffective because the outer try/catch around the cache branch only covers
synchronous setup in backup-service’s cacheValid block, while the real work
happens later inside the async IIFE after nodeStreamToWebReadable(passThrough)
has already been returned. Move the cache validation and archive population work
(including readMediaFilePaths, archive.directory, and image reads) to complete
before returning the stream, or otherwise surface async failures back to the
cacheValid decision point so the code can actually log the warning and fall back
to DB rebuild instead of returning a broken TAR. Use the cacheValid branch,
archive.finalize, and nodeStreamToWebReadable as the key locations to refactor.
- Around line 1557-1578: In backup-service’s archive creation flow, the
combination of archive.directory(cacheDir, false) and the includeImages block
can add the same images/ entries twice. Update the logic around the
archive.append path to skip files already included via archive.directory, or
exclude images/ from the directory walk, using the readMediaFilePaths and
driver.get handling in backup-service to avoid duplicate tar entries.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 438c6dbe-49e2-4f8b-ae17-e4ee85dfbe75

📥 Commits

Reviewing files that changed from the base of the PR and between 032f3e5 and 1ba477f.

📒 Files selected for processing (4)
  • apps/server/src/application/services/backup-service.ts
  • apps/server/src/application/services/lancedb-dump-service.ts
  • packages/application/src/ports/lancedb-dump-service.ts
  • packages/application/src/services/lancedb-dump-service.ts

Comment thread apps/server/src/application/services/backup-service.ts
Comment on lines +1557 to +1578
archive.directory(cacheDir, false);

if (includeImages) {
const { readMediaFilePaths } = await import(
"~/application/services/lancedb-dump-service"
);
const mediaFilePaths = await readMediaFilePaths(cacheDir);

await Promise.all(
mediaFilePaths.map(async (item) => {
if (!item.filePath) return;
try {
const buffer = await driver.get(item.filePath);
archive.append(buffer, {
name: `images/${item.filePath}`,
});
} catch {
// ignore missing files
}
}),
);
}

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# キャッシュ/ダンプ dir に images が書き込まれる経路があるか確認
rg -nP '"images"|images/|join\([^)]*images' --type=ts -C2 packages/application/src/services/lancedb-dump-service.ts | head -40

Repository: hmjn023/solid-imager

Length of output: 332


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 関連箇所の前後を確認
sed -n '1538,1590p' apps/server/src/application/services/backup-service.ts
printf '\n---\n'
sed -n '790,850p' packages/application/src/services/lancedb-dump-service.ts

Repository: hmjn023/solid-imager

Length of output: 3501


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 画像パスの読み出し元も確認
rg -n "readMediaFilePaths|archive\.directory\(|archive\.append\(" apps/server/src/application/services/backup-service.ts packages/application/src/services/lancedb-dump-service.ts -C 3

Repository: hmjn023/solid-imager

Length of output: 5622


images/ の重複格納を避けてください
cacheDir では images/<filePath> を別経路で扱っているため、archive.directory(cacheDir, false) の後に同じ画像を archive.append() すると tar に重複エントリが入り得ます。images/ を除外するか、追加側をスキップしてください。

🤖 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/application/services/backup-service.ts` around lines 1557 -
1578, In backup-service’s archive creation flow, the combination of
archive.directory(cacheDir, false) and the includeImages block can add the same
images/ entries twice. Update the logic around the archive.append path to skip
files already included via archive.directory, or exclude images/ from the
directory walk, using the readMediaFilePaths and driver.get handling in
backup-service to avoid duplicate tar entries.

@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: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/xtracter/src/content/danbooru.ts (1)

269-281: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

単一の twitterAccountId を全 author に無条件で platform: "twitter" として適用してしまう。

複数の artist タグが存在する投稿で、ページ全体から検出された単一の twitterAccountId が、無関係な author にも platform: "twitter" として一律で付与されます。accountId の代入は !author.accountId の条件付きですが、platform の代入はガードされていないため、実際には twitter に紐づかない author にも誤ったプラットフォームラベルが付きます。

🐛 提案(accountId 代入と platform 代入を同一条件に揃える)
 	if (twitterAccountId) {
 		for (const author of authors) {
 			if (!author.accountId) {
 				author.accountId = twitterAccountId;
+				author.platform = "twitter";
 			}
-			author.platform = "twitter";
 		}
 	} else {
🤖 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/xtracter/src/content/danbooru.ts` around lines 269 - 281, The author
mapping in danbooru.ts applies twitterAccountId to every author and sets
author.platform to "twitter" even when the author already has its own accountId,
so align the platform assignment with the same conditional used in the author
loop. Update the logic around the authors processing block so only authors that
actually receive the fallback twitterAccountId are labeled with the twitter
platform, while authors with existing accountId values keep their own platform
handling; use the existing authors iteration and twitterAccountId check as the
main locator.
🧹 Nitpick comments (2)
apps/server/src/components/media/pro-search-builder.tsx (1)

331-332: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

著者ラベルからプラットフォーム表示が削除された。

(twitter) のようなプラットフォーム表示がなくなり、name: accountId のみの表示になりました。複数プラットフォーム(twitter/pixiv-fanbox/danbooru)対応が追加された今、同一 accountId 表記でもプラットフォームが区別できずユーザーが混同する可能性があります。

♻️ 提案(platform を再表示)
 	const getAuthorLabel = (author: Author) =>
-		author.accountId ? `${author.name}: ${author.accountId}` : author.name;
+		author.accountId
+			? `${author.name}: ${author.accountId}${author.platform ? ` (${author.platform})` : ""}`
+			: author.name;
🤖 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/components/media/pro-search-builder.tsx` around lines 331 -
332, The getAuthorLabel helper in pro-search-builder.tsx no longer shows the
author platform, so update it to include the platform identifier again alongside
name and accountId. Use the existing Author fields in getAuthorLabel to append
the platform label in the displayed author text, keeping the format clear enough
to distinguish twitter, pixiv-fanbox, danbooru, and similar sources. Ensure any
places rendering author labels through this helper pick up the revised format
without changing unrelated search builder behavior.
packages/core/src/domain/authors/schemas.ts (1)

14-24: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

z.string().uuid() / z.string().url() を新しい API に寄せる
Zod 4 ではまだ動きますが非推奨なので、この新規スキーマだけでも z.uuid() / z.url() に統一すると移行が楽です。

🤖 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/core/src/domain/authors/schemas.ts` around lines 14 - 24, The new
authorAccountSchema still uses deprecated z.string().uuid() and z.string().url()
calls, so update the schema in authorAccountSchema to use Zod 4’s top-level
z.uuid() and z.url() helpers instead. Keep the same field behavior for id,
authorId, and profileUrl, and leave the rest of the schema and AuthorAccount
type inference unchanged.
🤖 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/backup-service.ts`:
- Around line 414-506: The author restore loop in backup-service’s author
recovery path is causing N+1 queries by awaiting multiple DB lookups per author
inside the `for (const input of authorInputs)` block. Refactor this section to
batch work like `_ensureMasterData`: prefetch all relevant `authors` and
`authorAccounts` rows for the collected `authorData`, resolve reusable/linked
IDs in memory, and perform inserts in bulk before building `authorMap` and
`authorRestoreKey` mappings. Keep the existing dedupe/legacy matching behavior,
but move DB round-trips out of the per-author loop.

In `@packages/db/src/repositories/author-repository.ts`:
- Around line 36-41: `findOrCreateAuthorsBulk` in `author-repository.ts` is only
checking `input.name.length > 0`, so whitespace-only author names can bypass the
same validation enforced by `create()`. Update the bulk filter to apply the same
trimmed-name rule used by `create` (reject names whose trimmed length is zero)
before deduping with `authorInputKey`, so bulk paths like `registerAuthors`
behave consistently with single-author creation.
- Around line 32-172: findOrCreateAuthorsBulk currently does a non-atomic
read/insert flow that can create duplicate authors under concurrent requests and
also lets whitespace-only names through because it only checks
input.name.length. Update the bulk path in author-repository to match the
stricter create() validation by trimming names before accepting them, and change
the author/author_accounts persistence to an atomic strategy (for example,
author_accounts-driven upsert or locking plus UPSERT) so concurrent `(platform,
accountId)` / `name` lookups cannot create orphaned duplicate rows. Use the
existing findOrCreateAuthorsBulk, authors, and authorAccounts logic as the main
place to apply the fix.

In `@packages/ui/src/pro-search-builder.tsx`:
- Line 494: `ProSearchBuilder` の選択値の契約がずれており、`optionValue` が `item.id` なのに保存・復元は
`value.name` / `item.name` 基準になっています。`ProSearchBuilder` 内の該当ロジックを揃えて、検索条件を name
ベースで維持するなら `optionValue` も name に統一し、ID ベースにするなら保存・復元・検索判定をまとめて `id`
基準へ変更してください。特に選択肢の識別、保存処理、復元処理が同じキーを使うように修正してください。

---

Outside diff comments:
In `@apps/xtracter/src/content/danbooru.ts`:
- Around line 269-281: The author mapping in danbooru.ts applies
twitterAccountId to every author and sets author.platform to "twitter" even when
the author already has its own accountId, so align the platform assignment with
the same conditional used in the author loop. Update the logic around the
authors processing block so only authors that actually receive the fallback
twitterAccountId are labeled with the twitter platform, while authors with
existing accountId values keep their own platform handling; use the existing
authors iteration and twitterAccountId check as the main locator.

---

Nitpick comments:
In `@apps/server/src/components/media/pro-search-builder.tsx`:
- Around line 331-332: The getAuthorLabel helper in pro-search-builder.tsx no
longer shows the author platform, so update it to include the platform
identifier again alongside name and accountId. Use the existing Author fields in
getAuthorLabel to append the platform label in the displayed author text,
keeping the format clear enough to distinguish twitter, pixiv-fanbox, danbooru,
and similar sources. Ensure any places rendering author labels through this
helper pick up the revised format without changing unrelated search builder
behavior.

In `@packages/core/src/domain/authors/schemas.ts`:
- Around line 14-24: The new authorAccountSchema still uses deprecated
z.string().uuid() and z.string().url() calls, so update the schema in
authorAccountSchema to use Zod 4’s top-level z.uuid() and z.url() helpers
instead. Keep the same field behavior for id, authorId, and profileUrl, and
leave the rest of the schema and AuthorAccount type inference unchanged.
🪄 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: 2d69cdfa-3921-4c32-ae02-119126ed361c

📥 Commits

Reviewing files that changed from the base of the PR and between 8024594 and 25097f4.

📒 Files selected for processing (31)
  • apps/server/drizzle/0017_ambitious_gideon.sql
  • apps/server/drizzle/meta/0017_snapshot.json
  • apps/server/drizzle/meta/_journal.json
  • apps/server/src/application/services/backup-service.ts
  • apps/server/src/components/media/pro-search-builder.tsx
  • apps/server/src/components/media/search-filters.tsx
  • apps/server/src/infrastructure/jobs/download-jobs.ts
  • apps/server/src/tests/integration/backup/backup-service.test.ts
  • apps/server/src/tests/integration/repository/author-dedupe.test.ts
  • apps/server/src/tests/unit/application/services/lancedb-dump-service.test.ts
  • apps/server/src/tests/unit/application/services/media-processing-service.test.ts
  • apps/server/src/tests/unit/domain/media/schemas.test.ts
  • apps/server/src/tests/unit/infrastructure/jobs/download-jobs.test.ts
  • apps/xtracter/README.md
  • apps/xtracter/public/manifest.json
  • apps/xtracter/src/content/danbooru.ts
  • apps/xtracter/src/content/fanbox.ts
  • apps/xtracter/src/content/index.ts
  • apps/xtracter/src/content/twitter.ts
  • apps/xtracter/src/schema.ts
  • packages/application/src/services/lancedb-dump-service.ts
  • packages/application/src/services/media-processing-service.ts
  • packages/core/src/domain/authors/schemas.ts
  • packages/core/src/domain/media/schemas.ts
  • packages/core/src/domain/media/utils/filename-utils.ts
  • packages/core/src/domain/repositories/author-repository.ts
  • packages/core/tests/filename-utils.test.ts
  • packages/db/src/repositories/author-repository.ts
  • packages/db/src/schema.ts
  • packages/ui/src/pro-search-builder.tsx
  • packages/ui/src/search-filters.tsx
✅ Files skipped from review due to trivial changes (2)
  • apps/server/src/infrastructure/jobs/download-jobs.ts
  • apps/xtracter/README.md

Comment thread apps/server/src/application/services/backup-service.ts
Comment thread packages/db/src/repositories/author-repository.ts
Comment thread packages/db/src/repositories/author-repository.ts
: ""
}
optionValue={(item) => item.name}
optionValue={(item) => item.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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

optionValue と保存される検索値を揃えてください。

Line 494 は選択肢を item.id で識別しますが、Line 475 は value.name を保存し、Line 497-499 も item.name で復元しています。同名の作者・関連項目があると、選んだ ID ではなく最初に一致した名前へ戻る可能性があります。検索条件が name ベースのままなら optionValue も name に戻し、ID ベースにするなら保存・復元・検索契約をまとめて更新してください。

name ベース契約を維持する場合の修正案
-						optionValue={(item) => item.id}
+						optionValue={(item) => item.name}
📝 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
optionValue={(item) => item.id}
optionValue={(item) => item.name}
🤖 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/pro-search-builder.tsx` at line 494, `ProSearchBuilder`
の選択値の契約がずれており、`optionValue` が `item.id` なのに保存・復元は `value.name` / `item.name`
基準になっています。`ProSearchBuilder` 内の該当ロジックを揃えて、検索条件を name ベースで維持するなら `optionValue` も
name に統一し、ID ベースにするなら保存・復元・検索判定をまとめて `id`
基準へ変更してください。特に選択肢の識別、保存処理、復元処理が同じキーを使うように修正してください。

hmjn023 added 2 commits July 2, 2026 00:25
fix(lancedb): use SQL transform for schema migration
fix(search): 詳細検索の対象選択を更新できるよう修正

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/server/src/components/media/pro-search-builder.tsx (1)

499-499: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

著者検索の値契約と optionValue を揃えてください。

optionValue を item.id にすると、著者条件が ID ベースになります。一方、下流の packages/db/src/repositories/media-repository.ts は author 条件を authors.name と比較しているため、選択した著者で検索が一致しなくなります。name 契約を維持するなら item.name に戻し、ID 契約にするなら DB 側も authors.id 比較へ更新してください。

name 契約を維持する場合の修正案
-						optionValue={(item) => item.id}
+						optionValue={(item) => item.name}
🤖 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/components/media/pro-search-builder.tsx` at line 499, The
author search contract is inconsistent because ProSearchBuilder is using
optionValue={(item) => item.id} while the downstream media repository compares
author filters against authors.name. Update the author selection flow in
pro-search-builder.tsx to keep the same contract end-to-end: either restore
optionValue to item.name, or if ID-based selection is intended, change the
author filtering logic in media-repository.ts to compare against authors.id
instead of names. Use the existing author-related fields and filter handling in
ProSearchBuilder and MediaRepository to keep the chosen contract consistent.
🧹 Nitpick comments (1)
apps/server/src/components/media/pro-search-builder.tsx (1)

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

不要な型キャストを型分岐に置き換えてください。

child.type と "accountId" in item で絞り込めるため、as SearchCriterion / as SearchGroup / as Author は不要です。

修正案
-							<Show
-								fallback={
-									<CriterionBuilder
+							{child.type === "group" ? (
+								<GroupBuilder
+									authors={props.authors}
+									characters={props.characters}
+									depth={props.depth + 1}
+									group={child}
+									ips={props.ips}
+									onChange={(g) => updateChild(index(), g)}
+									onRemove={() => removeChild(index())}
+									projects={props.projects}
+									tags={props.tags}
+								/>
+							) : (
+								<CriterionBuilder
 										authors={props.authors}
 										characters={props.characters}
-										criterion={child as SearchCriterion}
+										criterion={child}
 										ips={props.ips}
 										onChange={(c) => updateChild(index(), c)}
 										onRemove={() => removeChild(index())}
 										projects={props.projects}
 										tags={props.tags}
 									/>
-								}
-								when={isSearchGroup(child)}
-							>
-								<GroupBuilder
-									authors={props.authors}
-									characters={props.characters}
-									depth={props.depth + 1}
-									group={child as SearchGroup}
-									ips={props.ips}
-									onChange={(g) => updateChild(index(), g)}
-									onRemove={() => removeChild(index())}
-									projects={props.projects}
-									tags={props.tags}
-								/>
-							</Show>
+							)}
-				const label =
-					props.criterion.target === "author"
-						? getAuthorLabel(item as Author)
-						: (item as { name: string }).name;
+				const label = "accountId" in item ? getAuthorLabel(item) : item.name;

As per coding guidelines, **/*.{ts,tsx}: “In application code, do not use any, unnecessary unknown, as unknown as ..., or as any; prefer public types, type guards, Zod schemas, and explicit mappers, and only use unavoidable casts in the smallest possible scope with a comment.”

Also applies to: 360-363

🤖 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/components/media/pro-search-builder.tsx` around lines 273 -
287, Remove the unnecessary type assertions in the SearchBuilder rendering logic
by relying on the existing type guards instead of casting. In the branch that
renders the criterion and group children, use the narrowed result from
`isSearchCriterion(child)` and `isSearchGroup(child)` directly in the
`SearchCriterionBuilder` and `GroupBuilder` paths, and for the author-related
branch use the `"accountId" in item` check to narrow to `Author` without `as
Author`. Keep the fix localized to the child-dispatch code in
`pro-search-builder.tsx` so `child.type`-based narrowing and the helper guards
do the typing work.

Source: Coding guidelines

🤖 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.

Outside diff comments:
In `@apps/server/src/components/media/pro-search-builder.tsx`:
- Line 499: The author search contract is inconsistent because ProSearchBuilder
is using optionValue={(item) => item.id} while the downstream media repository
compares author filters against authors.name. Update the author selection flow
in pro-search-builder.tsx to keep the same contract end-to-end: either restore
optionValue to item.name, or if ID-based selection is intended, change the
author filtering logic in media-repository.ts to compare against authors.id
instead of names. Use the existing author-related fields and filter handling in
ProSearchBuilder and MediaRepository to keep the chosen contract consistent.

---

Nitpick comments:
In `@apps/server/src/components/media/pro-search-builder.tsx`:
- Around line 273-287: Remove the unnecessary type assertions in the
SearchBuilder rendering logic by relying on the existing type guards instead of
casting. In the branch that renders the criterion and group children, use the
narrowed result from `isSearchCriterion(child)` and `isSearchGroup(child)`
directly in the `SearchCriterionBuilder` and `GroupBuilder` paths, and for the
author-related branch use the `"accountId" in item` check to narrow to `Author`
without `as Author`. Keep the fix localized to the child-dispatch code in
`pro-search-builder.tsx` so `child.type`-based narrowing and the helper guards
do the typing work.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 82097b48-ff09-441d-be7e-ae9c926fd254

📥 Commits

Reviewing files that changed from the base of the PR and between 25097f4 and 9a83060.

📒 Files selected for processing (4)
  • apps/server/src/components/media/pro-search-builder.tsx
  • apps/server/src/tests/unit/application/services/lancedb-dump-service.test.ts
  • packages/application/src/services/lancedb-dump-service.ts
  • packages/ui/src/pro-search-builder.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/server/src/tests/unit/application/services/lancedb-dump-service.test.ts
  • packages/application/src/services/lancedb-dump-service.ts

hmjn and others added 4 commits July 4, 2026 01:25
- Refactor extractMetadataFromUrl to remove duplicate URL parsing (reuse extractTwitterAuthorIdFromStatusUrl)
- Batch author display-name updates into single CASE UPDATE instead of per-author loop
- Normalize Twitter accountId at save time (strip @ + lowercase) for index-friendly storage
- Merge upstream conflict resolution for duplicate author account inserts
- Normalize legacy authors.accountId on lookup: build legacyByAccount map
  with normalized keys (strip @ + lowercase) for Twitter account matching
- Search DB for both raw and @-prefixed accountId variants
- Fix CASE WHEN SQL to compare authors.id instead of bare UUID cast
fix(authors): refresh display names by account identity
@hmjn023
hmjn023 merged commit 1ec2906 into main Jul 3, 2026
1 check passed
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