Skip to content

feat(#57): AI抽出中のタイトル・アーティストを設定可能な配色でデコード表示#290

Merged
GeneralD merged 3 commits into
mainfrom
feat/57-ai-processing-indicator
Jun 15, 2026
Merged

feat(#57): AI抽出中のタイトル・アーティストを設定可能な配色でデコード表示#290
GeneralD merged 3 commits into
mainfrom
feat/57-ai-processing-indicator

Conversation

@GeneralD

@GeneralD GeneralD commented Jun 15, 2026

Copy link
Copy Markdown
Owner

type breaking scope diff files tests review

Closes #57

概要

AI(LLM)抽出がキャッシュミスでライブ API を叩いている間、ヘッダーのタイトル/アーティストを設定可能な配色(既定: 緑 #4ADE80)でスクランブル表示し、ユーザーに「AI が処理中」だと視覚的に伝える。解決後は通常色で settle する。キャッシュヒット時・[ai] 未設定時は従来通り即時表示で挙動は変わらない。

ユーザーが見える流れ

  1. 曲が変わる → 生のタイトルが通常色でデコード表示
  2. 300ms 後、[ai] 設定済み かつ LLM 未キャッシュなら → processing_color でスクランブル開始(= AI 処理中サイン)
  3. API 応答 → AI 解決後のタイトルへ通常色で settle

緑スクランブルが出ている時間 = ライブ API 往復の実レイテンシ(通常 0.5〜数秒)なので、一瞬の点滅ではなくはっきり見えるインジケータになる。

変更点

  • Entity: TrackUpdateaiResolving フラグ追加。DecodeEffect / DecodeEffectConfigprocessingColor 追加(config key text.decode_effect.processing_color、solid / gradient 両対応、既定 #4ADE80FF
  • Domain / 実装: MetadataRepository / MetadataUseCaseisAIMetadataCached(track:) を追加(LLM キャッシュ参照のみ・DataSource は叩かない)
  • TrackInteractor: debounce 後、aiConfigured && !isAIMetadataCached のときだけ aiResolving: trueTrackUpdate を送出
  • HeaderPresenter: aiResolvingDecodeEffectState.startLoading(無限スクランブル)+ processingColor に切替、解決更新で通常色へ復帰。HeaderView は実効 titleColor / artistColor@Published)を参照
  • ConfigRepository: config.text.decodeEffect.processingColorDecodeEffect にマッピング
  • バージョン 2.15.0、README / CLAUDE.md 更新

テスト

swift test 全 964 件グリーン。新規・追加カバレッジ:

  • TrackInteractorAIProcessingTests(新規)— AI 設定済み + キャッシュミスで aiResolving 送出 / キャッシュヒット・AI 未設定では非送出
  • HeaderPresenterTests — 処理中は processingColor で持続スクランブル(自発 settle しない)/ 解決後に通常色へ復帰 / 非 AI 更新は通常色のまま
  • MetadataRepositoryTests / MetadataUseCaseTestsisAIMetadataCached(キャッシュのみ参照を検証)
  • DecodeEffectConfigTests(新規)— processing_color の decode / 既定フォールバック / encode round-trip
  • ConfigRepositoryTestsprocessing_colorDecodeEffect まで流れる
  • ConfigTemplateTests — TOML / JSON テンプレートスナップショットに processing_color を反映

スクリーンショットについて

本機能の視覚状態(緑スクランブル)は 実 API キー + 再生中の曲 + LLM キャッシュミス が揃ったライブ API 往復中にのみ現れるため、バックグラウンドセッションの VM / ホスト環境では狙ったフレームを安定して再現・撮影できない。挙動はユニットテストで網羅済み。通常利用では数秒間はっきり表示される。

Summary by CodeRabbit

  • New Features

    • Added AI processing indicator: displays a loading effect with configurable color on title/artist while AI-based metadata is resolving from cache misses; returns to normal colors once resolution completes.
  • Configuration

    • Added processing_color key under [text.decode_effect] section to customize the title/artist color displayed during AI resolution.

LLMキャッシュミスかつAIエンドポイント設定時、API往復の間ヘッダーを
processing_color(既定: 緑 #4ADE80)でスクランブル表示し、解決後に
通常色へ settle する。キャッシュヒット時は従来通り即時表示で変化なし。

- TrackUpdate に aiResolving フラグを追加
- MetadataRepository/UseCase に isAIMetadataCached を追加(キャッシュ参照のみ)
- TrackInteractor: debounce 後、AI設定済み && 未キャッシュ時に aiResolving 更新を送出
- HeaderPresenter: aiResolving で startLoading(無限スクランブル)+ processingColor、
  解決更新で通常色へ復帰。HeaderView は実効 titleColor/artistColor を参照
- DecodeEffect(Config) に processingColor(config key: text.decode_effect.processing_color、
  solid/gradient 対応)を追加
- バージョンを 2.15.0 に更新、README/CLAUDE.md を更新
Copilot AI review requested due to automatic review settings June 15, 2026 02:10
@GeneralD GeneralD self-assigned this Jun 15, 2026

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@GeneralD, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 49 minutes and 21 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 62616481-eee1-4385-bec4-4b5b0478987a

📥 Commits

Reviewing files that changed from the base of the PR and between 1dc6750 and 85e690a.

📒 Files selected for processing (2)
  • README.md
  • Tests/PresentersTests/HeaderPresenterTests.swift
📝 Walkthrough

Walkthrough

Adds an AI metadata processing indicator for LLM cache-miss scenarios. TrackUpdate gains an aiResolving flag; DecodeEffect/DecodeEffectConfig gain a processingColor property. TrackInteractorImpl emits an intermediate aiResolving: true update on cache miss via a new isAIMetadataCached capability chain. HeaderPresenter scrambles title/artist with processingColor until resolution, then restores normal colors.

Changes

AI Processing Indicator

Layer / File(s) Summary
Entity data contracts: aiResolving and processingColor
Sources/Entity/TrackUpdate.swift, Sources/Entity/Style/DecodeEffect.swift, Sources/Entity/Config/DecodeEffectConfig.swift, Sources/ConfigRepository/ConfigRepositoryImpl.swift
TrackUpdate gains aiResolving: Bool. DecodeEffect and DecodeEffectConfig gain processingColor: ColorStyle (default #4ADE80FF) with a processing_color Codable key. ConfigRepositoryImpl passes processingColor when constructing DecodeEffect.
isAIMetadataCached protocol and implementation chain
Sources/Domain/Repository/MetadataRepository.swift, Sources/Domain/UseCase/MetadataUseCase.swift, Sources/MetadataRepository/MetadataRepositoryImpl.swift, Sources/MetadataUseCase/MetadataUseCaseImpl.swift
MetadataRepository and MetadataUseCase protocols add isAIMetadataCached(track:) -> Bool. MetadataRepositoryImpl checks llmDataStore for a cached entry; MetadataUseCaseImpl delegates to it. Unimplemented stubs return false.
TrackInteractorImpl: aiResolving pre-emit on cache miss
Sources/TrackInteractor/TrackInteractorImpl.swift
resolveTrack checks aiConfigured from appStyle.ai. On cache miss, it emits an intermediate TrackUpdate(lyricsState: .loading, aiResolving: true) before resolveCandidates, with task cancellation guards.
HeaderPresenter processing state and HeaderView color binding
Sources/Presenters/Track/HeaderPresenter.swift, Sources/Views/Header/HeaderView.swift
HeaderPresenter exposes @Published titleColor/artistColor. start() initializes them from style configs and stores processingColor from decode-effect config. receive(_:) branches on aiResolving: true triggers startProcessing (scramble loop with processingColor); false triggers revealTitle/revealArtist (resets to normal style colors). HeaderView reads presenter.titleColor/artistColor via resolver.shapeStyle(from:).
DecodeEffectConfig and config repository tests
Tests/EntityTests/DecodeEffectConfigTests.swift, Tests/ConfigDataSourceTests/ConfigTemplateTests.swift, Tests/ConfigRepositoryTests/ConfigRepositoryTests.swift
New DecodeEffectConfigTests covers processing_color decode (solid/gradient), defaults, encoding key name, and round-trip. Config template snapshots updated for processing_color. ConfigRepositoryTests adds two LoadAppStyle tests for processingColor decoding and default, extending makeAppConfig with a text parameter.
isAIMetadataCached repository and use-case tests
Tests/MetadataRepositoryTests/MetadataRepositoryTests.swift, Tests/MetadataUseCaseTests/MetadataUseCaseTests.swift
MetadataRepositoryTests adds an isAIMetadataCached suite with hit/miss tests and a no-LLM-datasource-call assertion. MetadataUseCaseTests adds two delegation tests with a configurable MockMetadataRepository.aiCached flag.
TrackInteractorAIProcessingTests and stub conformance
Tests/TrackInteractorTests/TrackInteractorAIProcessingTests.swift, Tests/TrackInteractorTests/TrackInteractor*Tests.swift, Tests/TrackHandlerTests/TrackHandlerImplTests.swift, Tests/LyricsUseCaseTests/...
New TrackInteractorAIProcessingTests asserts aiResolving emitted on cache miss, suppressed on cache hit, and suppressed when no AI endpoint is configured. All existing InstantMetadataUseCase / StubMetadataUseCase test doubles updated to satisfy the new isAIMetadataCached protocol requirement.
HeaderPresenter AI tests, docs, and version bump
Tests/PresentersTests/HeaderPresenterTests.swift, README.md, .claude/CLAUDE.md, Sources/VersionHandler/Resources/version.txt
HeaderPresenterTests adds an AIProcessing suite testing scramble during aiResolving, color restoration after resolution, and normal-color behavior for non-AI updates; waitForReveal refactored to a generic waitUntil. README.md and CLAUDE.md document processing_color. Version bumped to 2.15.0.

Sequence Diagram(s)

sequenceDiagram
  rect rgba(100, 149, 237, 0.5)
    Note over TrackInteractorImpl,HeaderView: LLM cache miss path
  end
  participant TrackInteractorImpl
  participant MetadataUseCaseImpl
  participant llmDataStore
  participant HeaderPresenter
  participant HeaderView

  TrackInteractorImpl->>MetadataUseCaseImpl: isAIMetadataCached(track:)
  MetadataUseCaseImpl->>llmDataStore: lookup(title+artist)
  llmDataStore-->>MetadataUseCaseImpl: nil (cache miss)
  MetadataUseCaseImpl-->>TrackInteractorImpl: false
  TrackInteractorImpl->>HeaderPresenter: emit TrackUpdate(aiResolving: true)
  HeaderPresenter->>HeaderView: titleColor/artistColor = processingColor (scramble)

  TrackInteractorImpl->>MetadataUseCaseImpl: resolveCandidates(track:)
  MetadataUseCaseImpl-->>TrackInteractorImpl: resolved metadata
  TrackInteractorImpl->>HeaderPresenter: emit TrackUpdate(aiResolving: false)
  HeaderPresenter->>HeaderView: titleColor/artistColor = normal style colors (reveal)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 Hop hop, the AI thinks away,
Green letters scramble, shimmer, and sway.
Cache miss? No worry — I'll flash you a sign!
Once resolved, the colors return to design.
A rabbit-built glow for the processing time~ ✨

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title references issue #57 and describes the main feature: configurable color decode display for AI-extracting title/artist, which matches the primary changes throughout the codebase.
Linked Issues check ✅ Passed All coding requirements from issue #57 are implemented: AI processing indicator via aiResolving flag, visual feedback with configurable processingColor during cache miss, preservation of instant display on cache hit, and proper integration across interactor/presenter/config layers.
Out of Scope Changes check ✅ Passed All changes are directly aligned with issue #57 objectives: AI processing detection, visual feedback implementation, configuration support, entity updates, and comprehensive test coverage. No unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/57-ai-processing-indicator

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 and usage tips.

@codecov

codecov Bot commented Jun 15, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.66667% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
Sources/TrackInteractor/TrackInteractorImpl.swift 87.50% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

@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

Caution

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

⚠️ Outside diff range comments (1)
Tests/TrackInteractorTests/TrackInteractorAIProcessingTests.swift (1)

195-197: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Replace fixed sleep with deadline polling in test assertion.

Line 196 uses a fixed delay before asserting sustained state. This violates the test timing guideline and can flake under variable CI load; switch to polling until a deadline.

As per coding guidelines, "**/*Test*.swift: Do not wait on async state with fixed Task.sleep delays in tests. Poll until a deadline instead."

Suggested change
-                // Sustained: the scramble never auto-settles to .revealed on its own.
-                try? await Task.sleep(for: .milliseconds(120))
-                `#expect`(presenter.titlePhase == .revealing)
-                `#expect`(presenter.titleColor == Self.processing)
+                // Sustained: assert repeatedly until deadline instead of fixed sleep.
+                let deadline = ContinuousClock.now + .milliseconds(120)
+                while ContinuousClock.now < deadline {
+                    `#expect`(presenter.titlePhase == .revealing)
+                    `#expect`(presenter.titleColor == Self.processing)
+                    try? await Task.sleep(for: .milliseconds(10))
+                }
🤖 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 `@Tests/TrackInteractorTests/TrackInteractorAIProcessingTests.swift` around
lines 195 - 197, The test at line 196 uses a fixed Task.sleep delay before
asserting on sustained state, which can cause flaky behavior under variable CI
load. Replace the fixed sleep with deadline polling: remove the Task.sleep call
and instead implement a polling loop that repeatedly checks the assertion
condition until either it passes or a reasonable deadline is exceeded, allowing
the test to complete quickly when the state is ready rather than always waiting
the full fixed duration.

Source: Coding guidelines

🧹 Nitpick comments (1)
Tests/MetadataUseCaseTests/MetadataUseCaseTests.swift (1)

107-107: ⚡ Quick win

Use let for immutable mock state.

Line 107 defines aiCached as var, but this value is never mutated in the test flow; make it let to match the repository guideline and tighten intent.

As per coding guidelines, "**/*.swift: Prefer let and functional transforms. Every var should have a real reason to exist."

Suggested change
-private struct MockMetadataRepository: MetadataRepository {
-    let candidates: [Track]
-    var aiCached: Bool = false
+private struct MockMetadataRepository: MetadataRepository {
+    let candidates: [Track]
+    let aiCached: Bool
🤖 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 `@Tests/MetadataUseCaseTests/MetadataUseCaseTests.swift` at line 107, Change
the `aiCached` property in the MetadataUseCaseTests.swift file from `var` to
`let` since this value is never mutated throughout the test flow. This aligns
with the coding guideline that prefers `let` and functional transforms, ensuring
that every `var` has a legitimate reason to exist. Simply replace the `var`
keyword with `let` in the variable declaration.

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.

Inline comments:
In `@README.md`:
- Line 151: The README.md documentation for the processing_color configuration
parameter shows an inconsistent default value format. Update the default color
value on line 151 from `#4ADE80` to `#4ADE80FF` to align with the canonical
representation used elsewhere in this PR's documentation and internal contracts,
ensuring the alpha channel is explicitly included in all documented color
values.

In `@Tests/PresentersTests/HeaderPresenterTests.swift`:
- Line 196: The test at line 196 uses a hard-coded Task.sleep delay which can
cause flaky CI behavior. Replace the fixed millisecond delay in the Task.sleep
call with a polling mechanism that repeatedly checks for the desired async state
condition until a deadline is reached. This ensures the test waits only as long
as necessary rather than introducing arbitrary delays.

---

Outside diff comments:
In `@Tests/TrackInteractorTests/TrackInteractorAIProcessingTests.swift`:
- Around line 195-197: The test at line 196 uses a fixed Task.sleep delay before
asserting on sustained state, which can cause flaky behavior under variable CI
load. Replace the fixed sleep with deadline polling: remove the Task.sleep call
and instead implement a polling loop that repeatedly checks the assertion
condition until either it passes or a reasonable deadline is exceeded, allowing
the test to complete quickly when the state is ready rather than always waiting
the full fixed duration.

---

Nitpick comments:
In `@Tests/MetadataUseCaseTests/MetadataUseCaseTests.swift`:
- Line 107: Change the `aiCached` property in the MetadataUseCaseTests.swift
file from `var` to `let` since this value is never mutated throughout the test
flow. This aligns with the coding guideline that prefers `let` and functional
transforms, ensuring that every `var` has a legitimate reason to exist. Simply
replace the `var` keyword with `let` in the variable declaration.
🪄 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

Run ID: e65cb1ba-590e-4bc1-8679-75ae6da44c89

📥 Commits

Reviewing files that changed from the base of the PR and between 5781118 and 1dc6750.

📒 Files selected for processing (28)
  • .claude/CLAUDE.md
  • README.md
  • Sources/ConfigRepository/ConfigRepositoryImpl.swift
  • Sources/Domain/Repository/MetadataRepository.swift
  • Sources/Domain/UseCase/MetadataUseCase.swift
  • Sources/Entity/Config/DecodeEffectConfig.swift
  • Sources/Entity/Style/DecodeEffect.swift
  • Sources/Entity/TrackUpdate.swift
  • Sources/MetadataRepository/MetadataRepositoryImpl.swift
  • Sources/MetadataUseCase/MetadataUseCaseImpl.swift
  • Sources/Presenters/Track/HeaderPresenter.swift
  • Sources/TrackInteractor/TrackInteractorImpl.swift
  • Sources/VersionHandler/Resources/version.txt
  • Sources/Views/Header/HeaderView.swift
  • Tests/ConfigDataSourceTests/ConfigTemplateTests.swift
  • Tests/ConfigRepositoryTests/ConfigRepositoryTests.swift
  • Tests/EntityTests/DecodeEffectConfigTests.swift
  • Tests/LyricsUseCaseTests/LyricsSearchServiceTests.swift
  • Tests/LyricsUseCaseTests/LyricsServiceTests.swift
  • Tests/MetadataRepositoryTests/MetadataRepositoryTests.swift
  • Tests/MetadataUseCaseTests/MetadataUseCaseTests.swift
  • Tests/PresentersTests/HeaderPresenterTests.swift
  • Tests/TrackHandlerTests/TrackHandlerImplTests.swift
  • Tests/TrackInteractorTests/TrackInteractorAIProcessingTests.swift
  • Tests/TrackInteractorTests/TrackInteractorArtworkTests.swift
  • Tests/TrackInteractorTests/TrackInteractorPlaybackPositionTests.swift
  • Tests/TrackInteractorTests/TrackInteractorRaceTests.swift
  • Tests/TrackInteractorTests/TrackInteractorStyleTests.swift

Comment thread README.md Outdated
Comment thread Tests/PresentersTests/HeaderPresenterTests.swift Outdated
GeneralD added 2 commits June 15, 2026 11:21
README の他の色既定値(highlight グラデーション)が 8 桁 RGBA 形式で、
Entity の既定値も .solid("#4ADE80FF") であるため、表記を統一する。
固定の Task.sleep(120ms) による実時間待機を廃止し、注入した TestClock を
advance してスクランブルループを複数フレーム進めても .revealing のまま
settle しないことを決定的に検証する。CI のタイミング非依存になり、
ループの継続描画も網羅する。
@GeneralD GeneralD merged commit 1334b5d into main Jun 15, 2026
4 checks passed
@GeneralD GeneralD deleted the feat/57-ai-processing-indicator branch June 15, 2026 02:27
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.

Show AI processing indicator during title extraction

2 participants