Skip to content

Feature/game commentary mode#514

Merged
tegnike merged 43 commits into
developfrom
feature/game-commentary-mode
Apr 27, 2026
Merged

Feature/game commentary mode#514
tegnike merged 43 commits into
developfrom
feature/game-commentary-mode

Conversation

@tegnike
Copy link
Copy Markdown
Owner

@tegnike tegnike commented Apr 26, 2026

Summary by CodeRabbit

リリースノート

  • 新機能

    • ゲーム実況機能:ゲーム画面をリアルタイムにAIが解説する機能を追加
    • ビデオ遅延機能:ビデオ表示に遅延を設定可能に
    • 環境変数クイックコピー:キャラクター位置を環境変数形式でコピー可能に
  • 改善

    • マルチモーダル設定をシンプル化
    • 音声処理の同時実行性を向上
    • メモリ検索にタイムアウト機能を追加
  • 多言語対応

    • 全言語でゲーム実況、ビデオ表示制御の翻訳を追加

tegnike and others added 30 commits March 23, 2026 16:16
画面キャプチャを一定間隔で自動取得し、マルチモーダルAIに送信して
リアルタイム実況コメントを生成・発話する新機能を実装。

- CaptureServiceシングルトンによる画面キャプチャAPI
- 専用ring bufferによる実況履歴管理(chatLog汚染防止)
- 完了ベースのsetTimeoutループで発話重複を回避
- アイドルモード等との排他制御ルール追加
- 設定画面タブ・環境変数・i18n対応

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
実況コメントをchatLogに保存し、YouTube/Mastraと文脈を自動共有する。
- デフォルトでchatLogに保存するよう変更
- [実況]prefixで実況コメントを区別可能に
- 実況生成時にchatLogの直近メッセージも参照して視聴者コメントの文脈を把握

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
YouTubeモードとゲーム実況モードの同時利用時にキャプチャボタンが表示されない問題を修正。
元の制限は技術的制約ではなく、全モードで常にボタンを表示するように変更。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
YouTubeモードと同じ操作体系に統一。設定ONでボタン表示、ボタン押下で実況開始/停止。
開始時にキャプチャが未開始なら自動で画面共有を開始する。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- ハードコードのSUFFIXを廃止し、実況プロンプトをpublic/presets/game-commentary-prompt.txtで管理
- usePresetLoaderに登録し他のプロンプトと同じ仕組みに統一
- キャラクタープロンプトの感情タグ指示と重複しないようプロンプト内容を整理
- gameCommentarySaveToChatを実行時にgetState()で取得しクロージャの古い値参照を修正
- ボタンOFF時にSpeakQueue.stopAll()を呼び進行中の発話を停止
- isRunningRefで最新状態を参照し停止後のタイマー再開を防止

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
GameCommentarySettingsはSettingsStateに含まれているため、
Record<string, unknown>へのキャストは不要。Vercelビルドエラーを修正。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Message型は[text, image]の順序で定義されているが、
[image, text]の順になっていたため型エラーが発生していた。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
直列のPromiseチェーンを廃止し、複数文のTTS合成を並列実行するよう変更。
順序管理にpendingResults Mapを導入し、合成完了順に関わらず正しい順序で
SpeakQueueへエンキューする。レート制限を1秒→250msに短縮。
セッション変更時のonCompleteコールバック漏れも修正。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- AIが実況セリフとは別に画面の情景描写を生成し、裏でリングバッファに保存
- 次回キャプチャ時に過去の情景描写をコンテキストとして渡し、画面変化の比較や
  ゲーム進行の流れの把握を可能にする
- [scene]タグによる出力フォーマットの解析を追加(後方互換性あり)
- キャプチャ間隔を10-60秒から0-20秒に変更(デフォルト5秒)
- 0秒設定は発話完了後に即座に次のキャプチャを開始(完了ベースsetTimeoutのため安全)
- captureIntervalRefを導入し、実況中の設定変更が即座に反映されるよう修正
- ドキュメント3言語(ja/en/zh)に情景描写の仕様とキャプチャ間隔の変更を反映

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- 実況テキストが分割されず一括でTTSに渡されていた問題を修正
- splitSentenceで句点・感嘆符・改行ごとに分割し、文単位で発話キューに投入
- 最後の文の完了時のみ次回キャプチャをスケジュールする動作を維持

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- 実況履歴の参照数(contextCount)の最小値を1→0に変更し、履歴参照を無効化可能に
- contextCount取得時の || を ?? に修正(0がfalsyで5に置換される問題)
- contextCount=0時はリングバッファへの追加をスキップ
- 実況プロンプトの説明に[scene]タグの解説を追記
- 実況プロンプトのテキストエリアを縦方向にリサイズ可能に変更

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- serviceReasoningDefaultsにollama用のeffort設定を追加(none/low/medium/high)
- buildReasoningProviderOptionsにollamaのcase分岐を追加

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
マルチモーダル機能の利用モード(always/never/ai-decide)を廃止し、
enableMultiModal(boolean)のみで制御するように簡素化。
既存のmultiModalMode設定はマイグレーションでenableMultiModalに変換される。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
VRMモーションタグ(think, cheer, wave, shy等10種)の説明を追加し、
既存・新規の会話例にモーションタグを組み込んだ。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Apr 26, 2026

Claude finished @tegnike's task —— View job


PR Review: Game Commentary Mode Feature

This PR introduces a comprehensive AI-powered game commentary system that captures screen content and generates real-time commentary. Here's my detailed review:

📋 Review Summary

✅ Strengths

Architecture & Design:

  • Clean separation of concerns with dedicated useGameCommentaryMode hook, CaptureService singleton, and proper store integration
  • Background scene analysis system provides rich context during long commentary sessions (analyzeGameCommentaryScene.ts:250)
  • Exclusion rules integration prevents conflicts with other modes (realtime API, audio mode, etc.) via exclusionRules.ts:330-377
  • Proper React patterns with ref-based state management and cleanup in hooks

Implementation Quality:

  • Comprehensive error handling including abort signal support and graceful degradation
  • Memory-efficient ring buffers for commentary history (useGameCommentaryMode.ts:164-175)
  • Canvas-based screen capture with configurable compression and resize options
  • Test coverage includes unit tests for core functions and integration scenarios

⚠️ Performance Considerations

Screen Capture Overhead:

  • Canvas operations in CaptureService.captureFrame() run synchronously on main thread (captureService.ts:40-57)
  • Image processing during resize creates new Image objects without disposal
  • Memory leaks potential - background analysis accumulates ImageBitmap objects without explicit cleanup

Recommendations:

// Consider moving canvas operations to OffscreenCanvas or Web Workers
captureFrame(maxWidth?: number, quality?: number): string | null {
  // Add try-finally to ensure cleanup
  try {
    const img = new Image()
    // ... existing logic
  } finally {
    // Explicit cleanup if needed
  }
}

🔒 Security Assessment

✅ Secure Practices:

  • No user input injection - AI prompts are from preset files, not user-provided
  • Screen capture permission properly requested via getDisplayMedia() API
  • AbortController usage prevents request racing and memory leaks
  • Data URI handling is safe - base64 image data only

Minor Concern:

  • AI prompt injection - while preset files are controlled, ensure user-provided context (chat messages) are sanitized before inclusion in AI requests

🧪 Test Coverage Analysis

Strong Coverage:

  • ✅ Core functions: generateGameCommentary, parseCommentaryResponse, buildGameCommentaryMessages
  • ✅ Hook lifecycle: timer management, state transitions, background analysis
  • ✅ Error scenarios: abort handling, network failures, invalid responses
  • ✅ Concurrency: speaker queue integration and session management

Coverage Gaps:

  • ⚠️ CaptureService canvas operations not directly tested
  • ⚠️ Image resize quality/compression edge cases
  • ⚠️ Background analysis memory cleanup

🐛 Potential Issues

1. Race Conditions (useGameCommentaryMode.ts:369-380):

// Token validation may not prevent all races
if (requestToken !== commentaryRequestTokenRef.current) {
  return // Good: prevents stale requests
}

✅ Well handled with request tokens and abort controllers.

2. Memory Management (useGameCommentaryMode.ts:214-220):

// Background analyses accumulate without size limits initially
if (backgroundSceneAnalysesRef.current.length > GAME_COMMENTARY_BACKGROUND_ANALYSIS.MAX_BUFFERED_ITEMS) {
  // Cleanup occurs, but consider explicit disposal
}

3. Canvas Performance (capture.tsx:56-63):

// Synchronous canvas operations on main thread
ctx.drawImage(video, 0, 0)
return canvas.toDataURL('image/jpeg', 0.9)

📊 Code Quality

✅ Best Practices:

  • TypeScript types are comprehensive and well-defined
  • Error boundaries and fallback states implemented
  • Internationalization support via i18next
  • Consistent naming conventions and JSDoc documentation

⚠️ Improvements:

  • Consider extracting magic numbers to constants (MIN_SCHEDULED_CAPTURE_INTERVAL_SECONDS = 3)
  • Some functions exceed optimal length (e.g., triggerCommentary at 160+ lines)
  • Canvas operations could benefit from performance optimization

💡 Recommendations

High Priority:

  1. Optimize canvas operations - consider OffscreenCanvas or requestAnimationFrame scheduling
  2. Add explicit cleanup for ImageBitmap objects in background analysis
  3. Monitor memory usage in long-running commentary sessions

Medium Priority:

  1. Add performance metrics logging for capture/generation times
  2. Consider compression alternatives - WebP support where available
  3. Enhance error recovery for network failures during AI generation

Low Priority:

  1. Extract large functions into smaller, testable units
  2. Add JSDoc examples for complex interfaces like CommentaryHistoryEntry
  3. Consider adding TypeScript strict mode compliance

🎯 Final Assessment

This is a well-architected feature with strong separation of concerns, comprehensive error handling, and good test coverage. The implementation demonstrates solid understanding of React patterns and performance considerations.

Recommendation: ✅ Approve with minor suggestions

The identified issues are primarily optimizations rather than blocking problems. The feature integrates cleanly with the existing codebase and follows established patterns.


@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 26, 2026

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

ゲーム実況機能を新規追加し、multiModalMode設定を廃止、ビデオ表示/キャプチャシステムを強化、複数言語の翻訳を更新、エージェントスキルドキュメントを追加、テストを更新します。環境変数とコンポーネントの多くのリファクタリングが含まれます。

Changes

Cohort / File(s) Summary
ゲーム実況機能(コア機能)
src/hooks/useGameCommentaryMode.ts, src/features/gameCommentary/generateGameCommentary.ts, src/features/gameCommentary/analyzeGameCommentaryScene.ts, src/features/gameCommentary/captureService.ts, src/features/gameCommentary/gameCommentaryTypes.ts, src/components/gameCommentaryManager.tsx, src/components/settings/gameCommentarySettings.tsx
ゲーム実況の自動実行、キャプチャ、AI生成、シーン分析機能を実装。設定UI、状態管理、キャプチャサービス、生成ロジック、型定義を含みます。
ビデオ表示・キャプチャシステム
src/components/capture.tsx, src/components/common/VideoDisplay.tsx, src/utils/mediaDisplay.ts
ビデオ遅延レンダリング、フレームバッファリング、DPR対応キャンバス処理、アスペクト比保持のサイズ調整ロジックを追加。
マルチモーダルモード廃止
src/features/constants/aiModels.ts, src/features/chat/handlers.ts, src/components/form.tsx, src/components/menu.tsx, src/components/messageInput.tsx, src/components/settings/modelProvider.tsx, src/components/settings/modelProvider/OpenAIConfig.tsx, src/components/settings/modelProvider/hooks/useAIServiceHandlers.ts, src/components/settings/modelProvider/hooks/useModelProviderState.ts
文字列ベースのmultiModalModeを廃止し、ブール値のenableMultiModalに統一。マルチモーダル判定ロジックを簡略化。
チャット・ストリーミング処理
src/features/chat/aiChatFactory.ts, src/features/chat/openAIAudioChat.ts, src/features/chat/vercelAIChat.ts, src/features/chat/difyChat.ts
AbortSignal対応、キャンセル処理、エラーハンドリング改善を追加。
スピーチキューイング
src/features/messages/speakCharacter.ts, src/features/messages/speakQueue.ts
セッション対応の停止操作、並行合成のバッファリング、順序保持のロジックを追加。
設定ストア
src/features/stores/settings.ts, src/features/stores/exclusionEngine.ts, src/features/stores/exclusionRules.ts, src/features/stores/menu.ts
ゲーム実況設定、hideVideoDisplay、ゲーム実況の相互排他ルールを追加。
メモリ機能
src/features/memory/memoryStoreSync.ts
メモリコンテキスト検索のタイムアウト機能を追加。
環境設定
.env.example, .gitignore, .github/workflows/deploy-cloudflare.yml
ゲーム実況関連の環境変数を追加、Remotion/.team/ディレクトリを除外、Electron バイナリダウンロードスキップを追加。
UI・設定コンポーネント
src/components/settings/idleSettings.tsx, src/components/settings/presenceSettings.tsx, src/components/settings/slide.tsx, src/components/settings/slideConvert.tsx, src/components/settings/index.tsx, src/components/iconButton.tsx, src/hooks/useIdleMode.ts, src/hooks/useResizable.ts, src/components/useYoutube.tsx
ゲーム実況との相互排他ルール、ゲームコントローラーアイコン、サイズ変更ロジック改善を追加。
多言語翻訳(11言語)
locales/*/translation.json (ja, en, ar, de, es, fr, hi, it, ko, pl, pt, ru, th, vi, zh-CN, zh-TW)
環境変数コピー、ビデオ表示トグル、ゲーム実況設定、デモモード通知の文字列を追加。マルチモーダルモード文字列を再配置。
エージェントスキル
.agents/skills/sync-translations/SKILL.md, .agents/skills/update-docs/SKILL.md, .agents/skills/verify-endpoints/SKILL.md
翻訳同期、ドキュメント更新、エンドポイント検証のAIスキル定義を追加。
プリセット
public/presets/game-commentary-prompt.txt, public/presets/game-commentary-background-analysis-prompt.txt, public/presets/preset2.txt
ゲーム実況用プロンプト、背景分析プロンプト、モーションタグ例を追加。
テスト
src/__tests__/components/*, src/__tests__/features/*, src/__tests__/hooks/*, src/__tests__/utils/*
ゲーム実況、メモリ、AbortSignal、キューイング、UI表示、マルチモーダル廃止に対応するテストを追加・更新。
ドキュメント
AGENTS.md
AITuberKit プロジェクトの概要、開発ガイドラインを記載した新規ドキュメント。

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant GameCommentaryManager
    participant useGameCommentaryMode
    participant CaptureService
    participant generateGameCommentary
    participant SpeakQueue
    participant HomeStore

    User->>GameCommentaryManager: (enable game commentary)
    activate GameCommentaryManager
    GameCommentaryManager->>useGameCommentaryMode: get state & callbacks
    activate useGameCommentaryMode
    
    loop capture cycle
        useGameCommentaryMode->>CaptureService: captureFrame()
        activate CaptureService
        CaptureService-->>useGameCommentaryMode: imageData
        deactivate CaptureService
        
        useGameCommentaryMode->>generateGameCommentary: generate commentary with image
        activate generateGameCommentary
        generateGameCommentary->>generateGameCommentary: build AI messages + prompt
        generateGameCommentary-->>useGameCommentaryMode: {text, emotion, sceneDescription}
        deactivate generateGameCommentary
        
        useGameCommentaryMode->>SpeakQueue: enqueue sentences
        activate SpeakQueue
        SpeakQueue->>HomeStore: set isSpeaking = true
        SpeakQueue-->>useGameCommentaryMode: (speaking)
        deactivate SpeakQueue
        
        Note over useGameCommentaryMode: transition to "speaking" state
        
        useGameCommentaryMode->>useGameCommentaryMode: wait for speech completion
        Note over useGameCommentaryMode: after speech ends, transition to "waiting"
        
        useGameCommentaryMode->>useGameCommentaryMode: schedule next capture
    end
    
    User->>GameCommentaryManager: (user chat arrives)
    GameCommentaryManager->>useGameCommentaryMode: interrupt signal
    useGameCommentaryMode->>SpeakQueue: stopSession()
    useGameCommentaryMode-->>GameCommentaryManager: state = "waiting"
    deactivate useGameCommentaryMode
    deactivate GameCommentaryManager
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • 本番リリース #459: マルチモーダルモード廃止と関連する同じファイル群(settings store、VideoDisplay、menu、messageInput、aiModels)の修正が重複しており、code-level で直接関連。
  • 話終わったあとに表情をneutralに戻す #207: speakCharacter・SpeakQueue のリファクタリング部分で重複した変更があり、スピーチキューイングロジックの修正が関連。
  • Claude/issue 405 20250530 094000 #406: ビデオキャプチャコンポーネント(src/components/capture.tsx、VideoDisplay.tsx)の背景ビデオ・キャプチャ処理ロジックが重複しており、code-level で関連。
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 32.26% 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 PRのタイトル「Feature/game commentary mode」は、変更セットの主要な機能を明確に表しており、ゲーム実況モード機能の追加というコアな変更を正確に要約しています。
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.

✏️ 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 feature/game-commentary-mode

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

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Apr 26, 2026

Claude finished @tegnike's task —— View job


PR Review: Game Commentary Mode Feature

Todo List:

  • Read core game commentary implementation files
  • Analyze code architecture and patterns
  • Review security considerations
  • Evaluate performance implications
  • Check test coverage quality
  • Provide comprehensive feedback

📋 Review Summary

This PR introduces a comprehensive AI-powered game commentary system that captures screen content and generates real-time commentary. The implementation demonstrates solid architectural patterns and thorough consideration of edge cases.

Strengths

Excellent Architecture & Design

  • Clean separation of concerns with dedicated hook (useGameCommentaryMode), singleton service (CaptureService), and proper store integration
  • Robust state management using refs to avoid stale closures and proper React patterns
  • Background scene analysis system provides contextual awareness during long commentary sessions
  • Exclusion rules integration prevents conflicts with other modes (realtime API, audio mode, etc.)
  • Ring buffer implementation for commentary history with configurable context size

Comprehensive Error Handling

  • AbortController support prevents race conditions and memory leaks
  • Graceful degradation when capture or AI generation fails
  • Proper cleanup of timers, streams, and background processes
  • Request token validation prevents stale responses

Strong Test Coverage

  • Well-structured tests for core functions: generateGameCommentary, parseCommentaryResponse, buildGameCommentaryMessages
  • Hook lifecycle testing covers timer management, state transitions, and interruption scenarios
  • Integration testing for speaker queue and concurrency handling
  • Mock implementations properly isolate units under test

⚠️ Areas for Improvement

1. Performance Optimizations

Canvas Operations - HIGH PRIORITY (captureService.ts:40-57):

// Current implementation does synchronous canvas operations on main thread
const img = new Image()
img.src = raw
// No explicit cleanup of image objects

// Consider using OffscreenCanvas for heavy operations
if (img.width <= maxWidth) return raw
ctx.drawImage(img, 0, 0, canvas.width, canvas.height) // Main thread blocking

Memory Management (useGameCommentaryMode.ts:214-220):

  • Background analyses accumulate without explicit cleanup of ImageBitmap objects
  • Ring buffer cleanup could be more aggressive for memory conservation

2. Security Considerations

✅ Generally Secure:

  • Screen capture uses proper getDisplayMedia() permissions
  • AI prompts are from controlled preset files, not user input
  • Data URI handling is safe (base64 image data only)

Minor Concern:

  • Chat message integration (useGameCommentaryMode.ts:352-358) should sanitize user content before AI prompt inclusion

3. Code Quality Improvements

Magic Numbers (useGameCommentaryMode.ts:16):

const MIN_SCHEDULED_CAPTURE_INTERVAL_SECONDS = 3 // Should be in constants file

Function Length: The triggerCommentary function is 160+ lines and could benefit from extraction:

// Consider extracting:
// - Image capture logic
// - AI generation with context building  
// - Speech synthesis and queuing

Image Object Lifecycle (captureService.ts:40-44):

// Potential memory leak - Image objects not explicitly disposed
const img = new Image()
img.src = raw
// Consider: img.onload = () => { /* process then img.src = '' */ }

🔧 Specific Technical Issues

Race Condition Handling

The request token system effectively prevents stale responses:

if (requestToken !== commentaryRequestTokenRef.current) {
  return // Properly exits on stale requests
}

Background Analysis Throttling

Good implementation prevents analysis spam during speech:

if (isBackgroundAnalysisInFlightRef.current) return
// Ensures only one analysis at a time

Stream Cleanup ✅ (capture.tsx:66-70)

Proper event listener setup for detecting browser-initiated stream termination.

📊 Performance Analysis

Capture Frequency: 3-300 second intervals with minimum 3s constraint is reasonable
Image Compression: Configurable quality (0.7 default) with resize options helps manage API costs
Context Management: Ring buffer limits prevent unbounded memory growth
Background Processing: Properly queued to avoid interference with main commentary flow

🎯 Recommendations

High Priority

  1. Optimize canvas operations - consider OffscreenCanvas or requestAnimationFrame scheduling
  2. Add explicit ImageBitmap disposal in background analysis cleanup
  3. Extract large functions into smaller, testable units

Medium Priority

  1. Enhance error recovery for network failures during AI generation
  2. Add performance monitoring for capture/generation times
  3. Consider WebP compression where supported for better efficiency

Low Priority

  1. Move magic numbers to constants file
  2. Add JSDoc examples for complex interfaces
  3. Consider strict TypeScript compliance improvements

🔍 Code Quality Score

Architecture: ⭐⭐⭐⭐⭐ (Excellent)
Error Handling: ⭐⭐⭐⭐⭐ (Comprehensive)
Performance: ⭐⭐⭐⭐☆ (Good, room for optimization)
Security: ⭐⭐⭐⭐⭐ (Well-secured)
Test Coverage: ⭐⭐⭐⭐⭐ (Thorough)
Maintainability: ⭐⭐⭐⭐☆ (Good, some refactoring beneficial)

Final Assessment

This is a well-architected feature with strong separation of concerns, comprehensive error handling, and excellent test coverage. The implementation demonstrates solid understanding of React patterns and performance considerations.

Recommendation: ✅ Approve with Minor Suggestions

The identified issues are primarily optimizations rather than blocking problems. The feature integrates cleanly with the existing codebase and follows established patterns. The thorough test coverage and robust error handling make this a production-ready implementation.


Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

Caution

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

⚠️ Outside diff range comments (3)
.agents/skills/remotion-best-practices/rules/assets/charts-bar-chart.tsx (1)

1-174: ⚠️ Potential issue | 🟡 Minor

整形ルール違反が多く、差分ノイズが大きいです。

Line [1] 〜 Line [174] は Prettier 指摘が大量に出ているので、コミット前に自動整形を一度通してください。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/skills/remotion-best-practices/rules/assets/charts-bar-chart.tsx
around lines 1 - 174, Many lines in this file fail Prettier formatting; run the
auto-formatter before committing to remove noise. Fix by running your project's
Prettier (or `npm run format`/`npx prettier --write`) on this file and re-commit
so components like MyAnimation, Title, YAxis, XAxis, and Bar are consistently
formatted and the diff only contains semantic changes.
src/__tests__/hooks/usePresetLoader.test.ts (1)

193-227: ⚠️ Potential issue | 🟡 Minor

gameCommentaryPromptTemplate の検証が抜けています。

Line 193 以降は game-commentary-prompt.txt を読むことまでは見ていますが、モック値も state assertion も background-analysis 側しかありません。usePresetLoadergameCommentaryPromptTemplate を保存しなくても、このケースは通ってしまいます。

💡 追加すると安全な最小差分
       const presets: Record<string, string> = {
         'idle-ai-prompt-template.txt': 'Idle AI template',
         'youtube-prompt-evaluate.txt': 'Evaluate prompt',
         'youtube-prompt-continuation.txt': 'Continuation prompt',
         'youtube-prompt-sleep.txt': 'Sleep prompt',
         'youtube-prompt-new-topic.txt': 'New topic prompt',
         'youtube-prompt-select-comment.txt': 'Select comment prompt',
         'multimodal-ai-decision-prompt.txt': 'Multimodal decision prompt',
+        'game-commentary-prompt.txt': 'Game commentary prompt',
         'game-commentary-background-analysis-prompt.txt':
           'Background analysis prompt',
       }
@@
+      expect(state.gameCommentaryPromptTemplate).toBe(
+        'Game commentary prompt'
+      )
       expect(state.gameCommentaryBackgroundAnalysisPromptTemplate).toBe(
         'Background analysis prompt'
       )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/__tests__/hooks/usePresetLoader.test.ts` around lines 193 - 227, The test
is missing an assertion for gameCommentaryPromptTemplate: update the test that
renders usePresetLoader to ensure mockLoadPreset is called with
'game-commentary-prompt.txt' (it should already be in PROMPT_PRESET_FILES) and
add an expectation on settingsStore.getState().gameCommentaryPromptTemplate to
equal the mocked value for that file (e.g., 'Game commentary prompt' or whatever
mock mapping you use); locate the assertions block that checks other state
fields (idleAiPromptTemplate, conversationContinuityPrompt*,
multiModalAiDecisionPrompt, gameCommentaryBackgroundAnalysisPromptTemplate) and
add the single line assertion for gameCommentaryPromptTemplate and ensure the
mockLoadPreset mapping returned in the test includes the corresponding filename
key so the loader actually sets the state.
src/features/chat/openAIAudioChat.ts (1)

27-44: ⚠️ Potential issue | 🔴 Critical

OpenAI SDK の型エラーを修正する必要があります。

as const により modalities: ['text', 'audio'] が readonly tuple になり、OpenAI SDK の streaming overload に合致していません。ChatCompletionCreateParamsStreaming 型を明示的に指定し、modalities を可変配列として渡してください。

修正方法
-import { ChatCompletionMessageParam } from 'openai/resources/chat/completions'
+import {
+  ChatCompletionCreateParamsStreaming,
+  ChatCompletionMessageParam,
+} from 'openai/resources/chat/completions'

-    const request = {
+    const request: ChatCompletionCreateParamsStreaming = {
       model: (ss.selectAIModel as AudioModeModel) || defaultModels.openaiAudio,
       messages: messageSelectors.getAudioMessages(
         messages
       ) as ChatCompletionMessageParam[],
       stream: true,
       modalities: ['text', 'audio'],
       audio: {
         voice: ss.audioModeVoice as RealtimeAPIModeVoice,
         format: 'pcm16',
       },
-    } as const
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/chat/openAIAudioChat.ts` around lines 27 - 44, The request
object currently uses `as const` which makes `modalities: ['text','audio']` a
readonly tuple that doesn't match the OpenAI streaming overload; remove the `as
const`, explicitly type the request as the streaming param type (e.g.
`ChatCompletionCreateParamsStreaming`) and ensure `modalities` is a normal
mutable array (['text','audio']) before calling `openai.chat.completions.create`
(refer to `request`, `ss.selectAIModel`, `defaultModels.openaiAudio`,
`messageSelectors.getAudioMessages`, `ChatCompletionMessageParam`,
`options.signal`, and `openai.chat.completions.create` to locate the code).
🟠 Major comments (28)
.agents/skills/remotion-best-practices/rules/extract-frames.md-89-92 (1)

89-92: ⚠️ Potential issue | 🟠 Major

中断時に成功終了してしまうため、キャンセル判定が不正確です

Line 90-92 は break で正常 return になるため、呼び出し側が「完了」と誤認します。中断は throw に統一してください。

修正案(abort時は必ず失敗として返す)
-  for await (using videoSample of sink.samplesAtTimestamps(timestamps)) {
-    if (signal?.aborted) {
-      break;
-    }
+  for await (using videoSample of sink.samplesAtTimestamps(timestamps)) {
+    if (signal?.aborted) {
+      throw new Error("Aborted");
+    }

Also applies to: 170-188, 207-228

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/skills/remotion-best-practices/rules/extract-frames.md around lines
89 - 92, The current for-await loops over sink.samplesAtTimestamps(timestamps)
use "if (signal?.aborted) { break; }", which causes the function to return
normally on cancellation; replace the break with throwing a cancellation error
so callers see a failure. Specifically, in the loops iterating "for await (using
videoSample of sink.samplesAtTimestamps(timestamps))" (and the other occurrences
noted), change the abort check to "if (signal?.aborted) throw new
Error('Operation aborted')" (or throw a DOMException/AbortError if you prefer)
so cancellation is propagated as an exception rather than a normal return.
Ensure all similar abort checks in the file (the ranges referenced) are updated
consistently.
.agents/skills/remotion-best-practices/rules/extract-frames.md-14-16 (1)

14-16: ⚠️ Potential issue | 🟠 Major

using 構文は環境要件が厳しく、「any project」への貼り付けは現実的ではありません

Line 14 で「any project」への貼り付けを示唆していますが、using 構文には以下の要件があります:

  • TypeScript 5.2 以上(必須)、tsconfig で "target": "es2022" 以上、"lib""esnext.disposable" を含める必要があります
  • Safari は 2026年時点で非対応です。実運用ではトランスパイルまたはポリフィルが必須になります
  • Node.js 26+ でのみネイティブサポートが確実です

要件を明記するか、従来の try/finally によるフォールバック実装も併示してください。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/skills/remotion-best-practices/rules/extract-frames.md around lines
14 - 16, 更新する文は、「This function can be copy-pasted into any
project」という主張が誤解を招くため、`using` 構文の環境要件(TypeScript 5.2+、tsconfig `"target":
"es2022"` 以上、`"lib"` に `"esnext.disposable"`、Node.js 26+ が確実なネイティブサポート、Safari
は未対応でトランスパイル/ポリフィルが必要)を明記するか、代替として同じ処理を行う従来の `try/finally`
フォールバック実装を本文に併記してください(参照すべきキーワード:`using` 構文、`try/finally` フォールバック、"This function
can be copy-pasted into any project" 表現を修正)。
src/hooks/useResizable.ts-185-195 (1)

185-195: ⚠️ Potential issue | 🟠 Major

公開した setSize が制約を素通りします

この setter は min/maxaspectRatio の正規化を一切通さないので、外部呼び出しだけでフックの不変条件を崩せます。{ width: 1, height: 1 } のような値もそのまま入り、以後の aspectRatioRef まで汚染されます。handleResizeMove と同じ正規化ロジックを共通化して、setSize / resetSize / ドラッグ更新の全部で再利用した方が安全です。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/useResizable.ts` around lines 185 - 195, 公開されている setSize が min/max
と aspectRatio の正規化を通さずフックの不変条件を壊せるので、handleResizeMove と同じ正規化ロジックを切り出して(例:
normalizeSize 或いは applyConstraints として)setSizeState
に渡す前に常に通すようにしてください;その共通関数は最小/最大値と aspectRatioRef
を適用して新しいサイズを返し、setSize、resetSize、そして handleResizeMove の全てで使い、同一サイズなら早期リターンして
state を更新しないようにしてください。
.agents/skills/remotion-best-practices/rules/animations.md-12-20 (1)

12-20: ⚠️ Potential issue | 🟠 Major

サンプルコードの import が不足しています

Line 16 と Line 18 で使っている useVideoConfiginterpolate が未import のため、このコード例はそのまま動作しません。

修正案
-import { useCurrentFrame } from "remotion";
+import { interpolate, useCurrentFrame, useVideoConfig } from "remotion";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/skills/remotion-best-practices/rules/animations.md around lines 12 -
20, The sample component FadeIn uses useVideoConfig and interpolate but only
imports useCurrentFrame; update the imports to include useVideoConfig and
interpolate from "remotion" so the FadeIn component can access fps and compute
opacity (i.e., add useVideoConfig and interpolate alongside useCurrentFrame in
the import statement referenced by the FadeIn function).
.agents/skills/remotion-best-practices/rules/trimming.md-15-20 (1)

15-20: ⚠️ Potential issue | 🟠 Major

useVideoConfig() の戻り値の扱いが誤っています

Line 17 は const { fps } = useVideoConfig(); で取り出す必要があります。useVideoConfig() はオブジェクトを返すため、現状だと fps がオブジェクト全体になり、Line 19 と Line 33 の算術演算が失敗します。

差分案
-const fps = useVideoConfig();
+const { fps } = useVideoConfig();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/skills/remotion-best-practices/rules/trimming.md around lines 15 -
20, useVideoConfig() returns an object, but the code currently assigns the whole
object to fps; change the assignment to destructure the fps property
(useVideoConfig -> const { fps } = useVideoConfig()) so arithmetic using fps
(e.g., in Sequence from={-0.5 * fps} and any other calculations like the later
usage around Line 33) operate on a number, not an object; update all occurrences
that assume fps is a number to use the destructured fps from useVideoConfig().
.agents/skills/remotion-promo-video-factory/scripts/verify-build.sh-14-19 (1)

14-19: ⚠️ Potential issue | 🟠 Major

build:gif の存在判定が環境依存です

Line 14 の rg 依存と npm run -s 出力フォーマット依存で、ripgrep がインストールされていない環境では set -euo pipefail により即失敗します。npm run ... --if-present に寄せる方が堅牢です。

差分案
-if npm run -s | rg -q "^  build:gif"; then
-  echo "[3/3] GIF render"
-  npm run -s build:gif
-else
-  echo "[3/3] GIF render skipped (build:gif not found)"
-fi
+echo "[3/3] GIF render (optional)"
+npm run -s build:gif --if-present
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/skills/remotion-promo-video-factory/scripts/verify-build.sh around
lines 14 - 19, The script currently checks for the "build:gif" script by running
"npm run -s" and piping to "rg", which is brittle (depends on ripgrep and npm
output) and will fail under "set -euo pipefail"; change the logic to avoid
parsing output and instead invoke npm directly with the built-in guard: run "npm
run --if-present build:gif" (or the equivalent npm CLI flag) and use its exit
behavior to decide logging, replacing the "npm run -s | rg -q" check and the
subsequent "npm run -s build:gif" invocation so the script no longer depends on
rg or npm output formatting.
.agents/skills/remotion-best-practices/rules/transparent-videos.md-91-93 (1)

91-93: ⚠️ Potential issue | 🟠 Major

WebM (VP9) セクションで codec 値が不整合です。

Line [91] の defaultCodec"vp8" になっており、同セクションの意図(VP9)および Line [68] の CLI 例と矛盾しています。

修正案
-    defaultCodec: "vp8",
+    defaultCodec: "vp9",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/skills/remotion-best-practices/rules/transparent-videos.md around
lines 91 - 93, The defaultCodec value in the VP9 section is incorrect: change
the property named defaultCodec (currently "vp8") to "vp9" so the configuration
for defaultCodec, along with defaultVideoImageFormat and defaultPixelFormat,
matches the VP9 section intent and the CLI example; locate and update the
defaultCodec entry in this block to "vp9" to resolve the inconsistency.
.agents/skills/remotion-best-practices/rules/audio-visualization.md-194-197 (1)

194-197: ⚠️ Potential issue | 🟠 Major

dB変換で log10(0) が発生し得ます。

Line [195] は value === 0 のとき -Infinity になるため、後続の正規化結果が壊れます。

修正案
-const scaled = frequencies.map((value) => {
-  const db = 20 * Math.log10(value);
+const scaled = frequencies.map((value) => {
+  const safe = Math.max(value, 1e-6);
+  const db = 20 * Math.log10(safe);
   return (db - minDb) / (maxDb - minDb);
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/skills/remotion-best-practices/rules/audio-visualization.md around
lines 194 - 197, The dB conversion in the frequencies.map callback can produce
-Infinity when value === 0; update the scaled calculation inside frequencies.map
(the callback using parameters value and local variable db) to guard against
zero by substituting a small positive epsilon (e.g., Math.max(value, 1e-10) or
Number.EPSILON) before calling Math.log10, then compute db and normalize as
before; also ensure the normalization (using minDb and maxDb) won't divide by
zero by handling the case maxDb === minDb (fallback to 0 or clamp) so the
normalized result remains finite.
.agents/skills/remotion-best-practices/rules/parameters.md-47-53 (1)

47-53: ⚠️ Potential issue | 🟠 Major

コンポーネント関数の引数が不足しており、サンプルコードが動作しません。

Line 47 でコンポーネント関数が引数を受け取らない () => { と定義されているのに対し、Line 50 で props.title を参照しています。これにより型エラーおよび実行時エラーが発生します。

関数シグネチャに props パラメータを追加してください。

修正案
-const MyComponent: React.FC<z.infer<typeof MyCompositionSchema>> = () => {
+const MyComponent: React.FC<z.infer<typeof MyCompositionSchema>> = (props) => {
   return (
     <div>
       <h1>{props.title}</h1>
     </div>
   );
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/skills/remotion-best-practices/rules/parameters.md around lines 47 -
53, The component MyComponent currently declares no parameters but accesses
props.title, causing type/runtime errors; update the function signature to
accept a props parameter typed as z.infer<typeof MyCompositionSchema> (e.g., add
a single parameter named props of that inferred type) so references like
props.title are valid and type-checked.
.agents/skills/remotion-best-practices/rules/parameters.md-60-63 (1)

60-63: ⚠️ Potential issue | 🟠 Major

import 名と利用名が不一致です

Line 60 で MycComponent を import していますが、Line 63 では MyComponent を使用しています。このコード例をそのままコピーすると未定義参照エラーになります。

修正
-import {MycComponent, MyCompositionSchema} from './MyComposition';
+import {MyComponent, MyCompositionSchema} from './MyComposition';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/skills/remotion-best-practices/rules/parameters.md around lines 60 -
63, インポート名と利用名が一致していないため未定義参照が発生します:import側の MycComponent と JSX 内で使っている
MyComponent を揃えてください(例えば RemotionRoot 内の Composition の component プロパティが参照する名前を
import の MycComponent に合わせるか、逆に import を MyComponent に変更して整合性を取る)。該当シンボルは
MycComponent / MyComponent とコンポーネント定義 RemotionRoot(および Composition の component
プロパティ)です。
.agents/skills/remotion-best-practices/rules/timing.md-88-92 (1)

88-92: ⚠️ Potential issue | 🟠 Major

spring の自己参照初期化によるドキュメントサンプルの実行エラー

Line 88 の const spring = spring({...}) は実行不可能です。関数名と変数名が同一のため、右辺の spring は未定義状態となり ReferenceError が発生します。同ファイル内の他のサンプル (Line 75 の entrance、Line 100 の springProgress) では正しく異なる変数名が使用されています。

修正例:

修正案
-const spring = spring({
+const springProgress = spring({
   frame,
   fps,
   durationInFrames: 40,
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/skills/remotion-best-practices/rules/timing.md around lines 88 - 92,
const spring = spring({...}) は関数名と変数名が衝突して右辺の spring が未定義になるため ReferenceError
を起こします。解決策は変数名を関数名と異なるものに変更することです(例: rename the variable to springValue or
springAnim) — locate the problematic declaration `const spring = spring({...})`
and rename the left-hand identifier to something like `springValue` (or match
naming pattern used in `entrance` / `springProgress` samples), then update any
subsequent references to use the new variable name so the call to the
`spring(...)` function invokes the actual function rather than referencing the
variable.
.agents/skills/remotion-best-practices/rules/maps.md-85-85 (1)

85-85: ⚠️ Potential issue | 🟠 Major

Mapbox style URL に不可視文字が混入しています

Line 85 の '⁠mapbox://styles/mapbox/standard' は先頭に不可視文字が入っており、コピペ時に URL 解析失敗の原因になります。ASCII のみの 'mapbox://styles/mapbox/standard' に置き換えてください。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/skills/remotion-best-practices/rules/maps.md at line 85, The Mapbox
style string contains an invisible character before
"mapbox://styles/mapbox/standard"; locate the style property where it is written
as '⁠mapbox://styles/mapbox/standard' and replace it with the ASCII-only literal
'mapbox://styles/mapbox/standard' so the URL parses correctly when
copied/pasted.
.agents/skills/remotion-best-practices/rules/maps.md-46-46 (1)

46-46: ⚠️ Potential issue | 🟠 Major

環境変数サンプルの代入記号が誤っています

Line 46 は == になっており、.env として無効です。= に修正してください。

修正例
-REMOTION_MAPBOX_TOKEN==pk.your-mapbox-access-token
+REMOTION_MAPBOX_TOKEN=pk.your-mapbox-access-token
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/skills/remotion-best-practices/rules/maps.md at line 46, The
environment variable sample uses a double-equals operator; update the sample so
REMOTION_MAPBOX_TOKEN is assigned with a single equals sign (change
REMOTION_MAPBOX_TOKEN==pk.your-mapbox-access-token to
REMOTION_MAPBOX_TOKEN=pk.your-mapbox-access-token) in the maps.md rule so it
becomes a valid .env assignment.
.agents/skills/remotion-best-practices/rules/light-leaks.md-10-10 (1)

10-10: ⚠️ Potential issue | 🟠 Major

バージョンアップを即時実行する文言は避けてください

Line 10 の npx remotion upgrade は、明示許可なしの依存更新を誘導します。upgrade の直接案内ではなく、「現在バージョン確認のみ」または「明示承認後に実施」に変更するのが安全です。

修正例
-This only works from Remotion 4.0.415 and up. Use `npx remotion versions` to check your Remotion version and `npx remotion upgrade` to upgrade your Remotion version.
+This only works from Remotion 4.0.415 and up. Use `npx remotion versions` to check your current version.
+If an upgrade is needed, perform it only after explicit approval in your project workflow.

Based on learnings: Do not upgrade package versions without explicit permission.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/skills/remotion-best-practices/rules/light-leaks.md at line 10,
Update the guidance that currently instructs users to run "npx remotion upgrade"
(line containing `npx remotion upgrade`) to avoid prompting unapproved
dependency updates; change the text to only show how to check the current
Remotion version (keep `npx remotion versions`) and state that upgrades should
be performed only after explicit approval or follow your project's upgrade
process (e.g., "confirm with your team/maintainer before running an upgrade" or
"if approved, run `npx remotion upgrade`"); ensure the revised sentence replaces
the direct upgrade imperative with a conditional/approval-first phrasing.
.agents/skills/remotion-best-practices/rules/assets/text-animations-word-highlight.tsx-1-108 (1)

1-108: ⚠️ Potential issue | 🟠 Major

Prettier違反が大量にあり、lint失敗の原因になります

Line [1] から Line [108] までフォーマット不一致が連続しているため、このままだとCIで失敗します。プロジェクト設定の Prettier でこのファイルを整形してください。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
@.agents/skills/remotion-best-practices/rules/assets/text-animations-word-highlight.tsx
around lines 1 - 108, ファイル全体がプロジェクトの Prettier 設定に合っておらず CI の lint
を通らないため、.agents/skills/remotion-best-practices/rules/assets/text-animations-word-highlight.tsx
をプロジェクトの Prettier 設定で整形してください(例: 実行コマンド: npx prettier --write <file>
またはエディタの「Format Document」)。対象コードには Highlight コンポーネント、MyAnimation コンポーネント、および定数
(COLOR_BG, COLOR_TEXT, COLOR_HIGHLIGHT, FULL_TEXT, HIGHLIGHT_WORD, etc.)
が含まれているので、整形後にインポート行や JSX のインデント、カンマ・セミコロン等の一貫性を確認してコミットしてください。
.agents/skills/remotion-promo-video-factory/scripts/capture-frames.sh-15-24 (1)

15-24: ⚠️ Potential issue | 🟠 Major

--project-dir 指定時に出力先が意図せず変わる不具合があります

Line [15] で相対 OUTPUT_DIR を作成したあと、Line [22] で cd しているため、Line [23] の書き込み先が別ディレクトリ基準になります。project-dir を使うとフレーム保存先がずれる/失敗する可能性があります。

💡 提案修正
 mkdir -p "${OUTPUT_DIR}"
+OUTPUT_DIR="$(cd "${OUTPUT_DIR}" && pwd)"
 
 IFS=',' read -r -a FRAMES <<< "${FRAME_LIST}"
 for frame in "${FRAMES[@]}"; do
   frame_trimmed="$(echo "$frame" | xargs)"
   out_file="${OUTPUT_DIR}/frame-$(printf "%03d" "${frame_trimmed}").png"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/skills/remotion-promo-video-factory/scripts/capture-frames.sh around
lines 15 - 24, The script creates OUTPUT_DIR before changing directory into
PROJECT_DIR, so when you cd into PROJECT_DIR the subsequent npx remotion still
writes relative to PROJECT_DIR and can misplace outputs; fix by resolving
OUTPUT_DIR to an absolute path (or otherwise prefix out_file with an absolute
path) before the for loop and before the cd so that out_file uses the absolute
OUTPUT_DIR when calling npx remotion still; update the capture-frames.sh logic
around OUTPUT_DIR, FRAMES parsing, and the npx remotion still invocation to
reference that absolute path (alternatively, create OUTPUT_DIR after cd but
ensure it's the intended location).
.agents/skills/remotion-best-practices/rules/videos.md-41-41 (1)

41-41: ⚠️ Potential issue | 🟠 Major

trimBefore / trimAfter の単位説明を修正してください

Line 41 の「Values are in seconds」は誤りです。Remotion公式ドキュメントによると、これらのプロパティは フレーム単位 です。秒単位と記載されたままでは、利用者が秒数を直接渡して意図しないトリミングが発生します。

修正案
-Use `trimBefore` and `trimAfter` to remove portions of the video. Values are in seconds.
+Use `trimBefore` and `trimAfter` to remove portions of the video. Values are in frames.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/skills/remotion-best-practices/rules/videos.md at line 41, The
documentation line incorrectly states that trimBefore and trimAfter are in
seconds; update the description for the properties trimBefore and trimAfter to
state that their values are in frames (frame units) instead of seconds, and
adjust any explanatory text or examples on that line to reflect frame-based
values so users do not pass seconds by mistake.
src/features/gameCommentary/gameCommentaryTypes.ts-65-96 (1)

65-96: ⚠️ Potential issue | 🟠 Major

クランプ関数が NaN を素通りします。

Line 65-96 のクランプ関数は NaN 入力を処理していません。NaN の比較結果はすべて false であるため、そのまま返されます。

実装上の懸念:

  • src/components/settings/gameCommentarySettings.tsx Line 417-419 で、parseInt() の結果を isNaN チェックなしに clampBackgroundAnalysisInterval() に渡しています
  • Line 306 では clampContextCount() に対して if (!isNaN(value)) ガード があり、対照的です

Number.isFinite() ガードで防いでください。DEFAULT_GAME_COMMENTARY_CONFIG の値をフォールバックとして使用する提案は妥当です。

🛠 提案差分
+function clampNumber(value: number, min: number, max: number, fallback: number) {
+  if (!Number.isFinite(value)) return fallback
+  if (value < min) return min
+  if (value > max) return max
+  return value
+}
+
 // Validate and clamp capture interval value
 export function clampCaptureInterval(value: number): number {
-  if (value < GAME_COMMENTARY_INTERVAL.MIN) return GAME_COMMENTARY_INTERVAL.MIN
-  if (value > GAME_COMMENTARY_INTERVAL.MAX) return GAME_COMMENTARY_INTERVAL.MAX
-  return value
+  return clampNumber(
+    value,
+    GAME_COMMENTARY_INTERVAL.MIN,
+    GAME_COMMENTARY_INTERVAL.MAX,
+    DEFAULT_GAME_COMMENTARY_CONFIG.gameCommentaryCaptureInterval
+  )
 }
 
 // Validate and clamp context count value
 export function clampContextCount(value: number): number {
-  if (value < GAME_COMMENTARY_CONTEXT_COUNT.MIN)
-    return GAME_COMMENTARY_CONTEXT_COUNT.MIN
-  if (value > GAME_COMMENTARY_CONTEXT_COUNT.MAX)
-    return GAME_COMMENTARY_CONTEXT_COUNT.MAX
-  return value
+  return clampNumber(
+    value,
+    GAME_COMMENTARY_CONTEXT_COUNT.MIN,
+    GAME_COMMENTARY_CONTEXT_COUNT.MAX,
+    DEFAULT_GAME_COMMENTARY_CONFIG.gameCommentaryContextCount
+  )
 }
 
 // Validate and clamp video delay value
 export function clampVideoDelay(value: number): number {
-  if (value < GAME_COMMENTARY_VIDEO_DELAY.MIN)
-    return GAME_COMMENTARY_VIDEO_DELAY.MIN
-  if (value > GAME_COMMENTARY_VIDEO_DELAY.MAX)
-    return GAME_COMMENTARY_VIDEO_DELAY.MAX
-  return value
+  return clampNumber(
+    value,
+    GAME_COMMENTARY_VIDEO_DELAY.MIN,
+    GAME_COMMENTARY_VIDEO_DELAY.MAX,
+    DEFAULT_GAME_COMMENTARY_CONFIG.gameCommentaryVideoDelay
+  )
 }
 
 // Validate and clamp background analysis interval value
 export function clampBackgroundAnalysisInterval(value: number): number {
-  if (value < GAME_COMMENTARY_BACKGROUND_ANALYSIS_INTERVAL.MIN)
-    return GAME_COMMENTARY_BACKGROUND_ANALYSIS_INTERVAL.MIN
-  if (value > GAME_COMMENTARY_BACKGROUND_ANALYSIS_INTERVAL.MAX)
-    return GAME_COMMENTARY_BACKGROUND_ANALYSIS_INTERVAL.MAX
-  return value
+  return clampNumber(
+    value,
+    GAME_COMMENTARY_BACKGROUND_ANALYSIS_INTERVAL.MIN,
+    GAME_COMMENTARY_BACKGROUND_ANALYSIS_INTERVAL.MAX,
+    DEFAULT_GAME_COMMENTARY_CONFIG.gameCommentaryBackgroundAnalysisInterval
+  )
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/gameCommentary/gameCommentaryTypes.ts` around lines 65 - 96, All
clamp* functions (clampCaptureInterval, clampContextCount, clampVideoDelay,
clampBackgroundAnalysisInterval) currently pass NaN through because comparisons
with NaN are false; update each to first check Number.isFinite(value) and if
false return the corresponding default from DEFAULT_GAME_COMMENTARY_CONFIG
(e.g., DEFAULT_GAME_COMMENTARY_CONFIG.captureInterval, contextCount, videoDelay,
backgroundAnalysisInterval) before performing MIN/MAX clamping so callers like
clampBackgroundAnalysisInterval(parseInt(...)) are safe without their own isNaN
guards.
src/components/menu.tsx-280-324 (1)

280-324: ⚠️ Potential issue | 🟠 Major

YouTube モード時の既存 UI 変更は切り分けたほうが安全です。

Line 280 以降で !youtubeMode ガードを外したため、ゲーム実況と無関係でも capture / webcam / image ボタンが常時表示されます。今回の目的がゲーム実況用操作の追加なら、少なくとも gameCommentaryEnabled 時だけ露出するなど、既存 UX への影響は限定したほうがよいです。

Based on learnings, Do not change the existing UI/UX design without explicit permission

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/menu.tsx` around lines 280 - 324, The new capture/webcam/image
buttons are being rendered unconditionally; restrict their visibility to the
game-commentary/YouTube mode by wrapping the IconButton + input block with a
guard (e.g., check gameCommentaryEnabled or youtubeMode) so they only render
when that feature is enabled; update the conditional around the block that uses
toggleCapture, toggleWebcam, imageFileInputRef and the
isMultiModalAvailable(...) check (which uses selectAIService, selectAIModel,
enableMultiModal, customModel) and ensure readers still set homeStore.modalImage
only when the image input is used.
src/components/capture.tsx-52-63 (1)

52-63: ⚠️ Potential issue | 🟠 Major

遅延実況が有効でも、実況用キャプチャ元がライブ映像のままです。

Line 53 で登録しているコールバックは常に videoRef.current を JPEG 化しています。これだと gameCommentaryVideoDelay を有効にしても、ゲーム実況の解析対象だけは遅延前の現在フレームになり、表示中の映像と実況対象がずれます。CaptureService 側は遅延キャンバス/遅延フレームを優先して読むようにしないと、今回の delay 機能が実況には効きません。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/capture.tsx` around lines 52 - 63, The registered capture
callback always reads from videoRef.current, so when gameCommentaryVideoDelay is
enabled the commentary capture bypasses the delay and captures the live frame;
update the callback registered via
CaptureService.getInstance().registerCaptureFunction to first check for and
prefer the delay-provided frame/canvas from CaptureService (e.g., a
delayedCanvas/delayedFrame API or a getDelayedFrame/getDelayedCanvas method)
when gameCommentaryVideoDelay is enabled, falling back to videoRef.current only
if no delayed frame is available; ensure you still validate readiness
(readyState and canvas context) and return the JPEG dataURL as before.
src/components/capture.tsx-137-145 (1)

137-145: ⚠️ Potential issue | 🟠 Major

遅延フレームループが停止後も残り、cleanup 後の ImageBitmap もリークします。

Line 137 の effect は isVideoDelayed が変わらない限り生き続けるので、画面共有を止めても requestAnimationFrame が回り続けます。加えて Line 170 の createImageBitmap() は cleanup 後にも resolve し得るため、破棄済みバッファへ再度 bitmap を積んでリークします。キャプチャ状態も終了条件に含め、dispose 済みなら解放して捨ててください。

Also applies to: 170-179, 221-228

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/capture.tsx` around lines 137 - 145, The effect that drives
the delayed-frame capture (inside useEffect) can continue running after capture
stops and createImageBitmap() can resolve after cleanup, leaking ImageBitmap
objects; fix this by tracking an active flag and cancelling the RAF loop on
cleanup and when capture stops, and by checking that flag after
createImageBitmap resolves before pushing to frameBuffer — if inactive,
immediately call bitmap.close() instead of storing it. Specifically, in the
useEffect surrounding
CAPTURE_FPS/CAPTURE_INTERVAL_MS/frameBuffer/offscreen/offCtx, store the
requestAnimationFrame id and clear it on cleanup, add a local boolean like let
active = true and set active = false in cleanup, include capture-running state
(e.g., isVideoDelayed && videoDelaySeconds > 0 && captureActive) in the effect
conditions, and after each createImageBitmap(...) resolution, check active (or
capture state) before pushing to frameBuffer; if not active call bitmap.close()
to release resources.
src/features/messages/speakQueue.ts-83-95 (1)

83-95: ⚠️ Potential issue | 🟠 Major

stopSession が対象外セッションの待機タスクまで消しています。

Line 84-86 で停止対象セッションだけを除外したあと、Line 95 の clearQueue() で残っている別セッションのタスクまで全消去されています。新しいセッションの音声がすでに積まれているケースだと、stopSession が実質的にグローバル stop になってしまいます。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/messages/speakQueue.ts` around lines 83 - 95, The stopSession
implementation currently removes tasks for the given sessionId but then calls
instance.clearQueue(), which deletes all remaining tasks across sessions; change
it to only clear tasks for the session being stopped (e.g., filter
instance.queue to remove tasks with sessionId === sessionId or add a new method
like clearSessionQueue(sessionId)) and avoid calling the global clearQueue()
that knocks out other sessions; also ensure the flags manipulated
(instance.stopped, instance.isProcessing, SpeakQueue.stopTokenCounter) are
applied only in the context of the stopped session (or tied to a per-session
token) so other sessions continue processing normally.
src/features/gameCommentary/captureService.ts-34-55 (1)

34-55: ⚠️ Potential issue | 🟠 Major

captureFrame メソッドの実装では画像リサイズと品質圧縮がほぼ機能しません。

img.src = raw の直後に img.width / img.height を読み込んでいますが、HTML仕様に基づきHTMLImageElementの読み込みは非同期であり、この時点では幅と高さは 0 です。そのため Line 43 の条件 if (img.width <= maxWidth) は常に真(0 ≤ maxWidth)となり、元の未圧縮データ URL が返されます。結果として maxWidth 引数と quality 引数がまったく適用されず、高解像度のまま送信されます。

このメソッドは非同期化し、img.onload またはこれに相当する方法で画像の読み込み完了後に drawImage を呼び出す必要があります。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/gameCommentary/captureService.ts` around lines 34 - 55, The
captureFrame implementation currently reads img.width/img.height synchronously
so resizing never happens; change captureFrame (the function named captureFrame)
to be asynchronous (return Promise<string | null>), create the Image, set
img.src, and await its load (using img.onload wrapped in a Promise or
equivalent) before checking img.width/img.height and performing canvas resizing
and canvas.toDataURL with the provided quality; also handle img.onerror to
reject/return null and keep existing fallbacks (return raw when no resizing
needed) and ensure getContext('2d') null-check remains.
src/features/gameCommentary/analyzeGameCommentaryScene.ts-18-20 (1)

18-20: ⚠️ Potential issue | 🟠 Major

背景解析リクエストも中断できるようにしてください。

この関数は AbortSignal を受け取れないため、実況停止や割り込み後も背景解析の AI リクエストが最後まで走り続けます。結果は捨ててもトークン消費と接続保持は残るので、AIChatResponseStreamOptions を受け取って getAIChatResponseStream(messages, options) に流した方が安全です。

🔧 修正例
+import type { AIChatResponseStreamOptions } from '@/features/chat/aiChatFactory'
 import { getAIChatResponseStream } from '@/features/chat/aiChatFactory'
 import { THINKING_MARKER } from '@/features/chat/vercelAIChat'
 import { Message } from '@/features/messages/messages'
 import settingsStore from '@/features/stores/settings'
@@
 export async function analyzeGameCommentaryScene(
-  imageData: string
+  imageData: string,
+  options: AIChatResponseStreamOptions = {}
 ): Promise<string | null> {
@@
-    const stream = await getAIChatResponseStream(messages)
+    const stream = await getAIChatResponseStream(messages, options)

Also applies to: 48-49

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/gameCommentary/analyzeGameCommentaryScene.ts` around lines 18 -
20, analyzeGameCommentaryScene should accept AIChatResponseStreamOptions (or at
least an AbortSignal) and forward it to getAIChatResponseStream so background AI
requests can be aborted; update the function signature export async function
analyzeGameCommentaryScene(imageData: string, options?:
AIChatResponseStreamOptions | { signal?: AbortSignal }) : Promise<string | null>
and pass options (or options.signal) into getAIChatResponseStream(messages,
options), and apply the same change to the other similar call site(s) in this
file so all AI chat stream calls support cancellation.
src/components/common/VideoDisplay.tsx-75-76 (1)

75-76: ⚠️ Potential issue | 🟠 Major

hideVideoDisplay が背景表示まで消しています。

showBackgroundVideo!hideVideoDisplay で絞っているので、背景モードで「非表示」を押すとプレビュー非表示ではなく背景そのものが消えます。HideVideoDisplay という挙動より一段強く、既存の背景表示 UX を変えてしまうので、非表示はフローティングプレビュー/UI だけに限定した方がよいです。Based on learnings: Do not change the existing UI/UX design without explicit permission.

Also applies to: 486-522

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/common/VideoDisplay.tsx` around lines 75 - 76, The current
logic makes hideVideoDisplay also hide the background video because
showBackgroundVideo is conditioned with !hideVideoDisplay; change the condition
so background mode only depends on useVideoAsBackground (e.g.,
showBackgroundVideo = useVideoAsBackground) and keep hideVideoDisplay affecting
only the floating preview (showFloatingPreview = !useVideoAsBackground &&
!hideVideoDisplay); update the same pattern wherever similar logic appears (the
other block referenced around the same diff) to preserve existing background-UX
while allowing hideVideoDisplay to only hide the floating preview/UI.
src/features/gameCommentary/generateGameCommentary.ts-100-106 (1)

100-106: ⚠️ Potential issue | 🟠 Major

THINKING_MARKER の除外がチャンク境界に依存しています。

reader.read() の分割単位は任意なので、マーカー自体や思考本文が複数チャンクに分かれると startsWith() では取り切れません。結果として思考テキストが実況本文に混ざる可能性があります。ストリーム全体で状態を持つデコーダにするか、同じ処理を analyzeGameCommentaryScene.ts と共通化した方が安全です。Based on learnings: Verify that no duplicate implementations exist before adding new features.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/gameCommentary/generateGameCommentary.ts` around lines 100 -
106, The code relies on value.startsWith(THINKING_MARKER) inside the
reader.read() loop (in generateGameCommentary.ts), which fails when the marker
or thought content is split across stream chunks; change to a stateful stream
decoder that keeps a running buffer across reads and searches for
THINKING_MARKER anywhere in the buffered text (or reuse the same decoder/utility
used by analyzeGameCommentaryScene.ts if present) so you correctly detect and
strip the marker and the entire thought block even when split across chunks;
update logic around reader.read(), fullText accumulation, and the
marker-handling path to operate on the buffer and only emit non-thinking text to
fullText.
src/hooks/useGameCommentaryMode.ts-350-358 (1)

350-358: ⚠️ Potential issue | 🟠 Major

maxPastMessages = 0 で履歴が無制限になります。

slice(0) は 0 件ではなく全件を返すため、履歴を送らない設定でも毎回すべてのチャットをプロンプトに載せてしまいます。コスト増とコンテキスト肥大化につながるので、0 以下のときは空配列を返す分岐にしてください。

🔧 修正例
-      const recentMessages = chatLog
-        .filter((m) => m.role === 'user' || m.role === 'assistant')
-        .slice(maxPastMessages > 0 ? -maxPastMessages : 0)
-        .map((m) => ({
-          role: m.role,
-          content: typeof m.content === 'string' ? m.content : '',
-        }))
+      const recentMessages =
+        maxPastMessages > 0
+          ? chatLog
+              .filter((m) => m.role === 'user' || m.role === 'assistant')
+              .slice(-maxPastMessages)
+              .map((m) => ({
+                role: m.role,
+                content: typeof m.content === 'string' ? m.content : '',
+              }))
+          : []
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/useGameCommentaryMode.ts` around lines 350 - 358, The
recentMessages calculation in useGameCommentaryMode uses slice(maxPastMessages >
0 ? -maxPastMessages : 0) which causes slice(0) to return the full chatLog when
maxPastMessages is 0; change the logic so that when maxPastMessages is <= 0 you
return an empty array instead of slicing, e.g. branch before mapping: if
maxPastMessages <= 0 set recentMessages = [] else take the last maxPastMessages
from chatLog (using slice(-maxPastMessages)) and then map; update the code that
builds recentMessages (references: maxPastMessages, chatLog, recentMessages)
accordingly.
src/hooks/useGameCommentaryMode.ts-291-306 (1)

291-306: ⚠️ Potential issue | 🟠 Major

scheduleNext()の依存配列にtriggerCommentaryを追加してください。

scheduleNext()内のsetTimeouttriggerCommentary()を呼び出していますが、scheduleNext()の依存配列[clearTimers, getEffectiveCaptureInterval]triggerCommentaryが含まれていません。そのため、triggerCommentaryが依存するgameCommentaryResizeWidthgameCommentaryImageQualityなどの設定が変更されて新しいバージョンが作成されても、スケジュール済みのタイマーは古いバージョンの関数を使い続けます。依存配列にtriggerCommentaryを追加するか、ref経由で最新の関数を参照する仕組みに変更してください。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/useGameCommentaryMode.ts` around lines 291 - 306, scheduleNext
currently sets a timeout that calls triggerCommentary but its dependency array
only lists clearTimers and getEffectiveCaptureInterval, so update scheduleNext
to include triggerCommentary in its dependency array (i.e. [clearTimers,
getEffectiveCaptureInterval, triggerCommentary]) so the timer uses the latest
triggerCommentary; alternatively, replace direct triggerCommentary usage inside
setTimeout with a mutable ref (e.g., triggerCommentaryRef.current) and keep the
existing dependencies, ensuring countdownRef and timerRef still clear/reset with
clearTimers.
🧹 Nitpick comments (14)
.agents/skills/sync-translations/SKILL.md (2)

39-41: コードブロックに言語指定子を追加してください

静的解析ツールが指摘しているように、コードブロックには言語指定子を追加することが推奨されます。

📝 修正案
-```
+```text
 locales/ja/translation.json
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @.agents/skills/sync-translations/SKILL.md around lines 39 - 41, Update the
fenced code block that currently contains "locales/ja/translation.json" in
.agents/skills/sync-translations/SKILL.md to include a language specifier (e.g.,
change the opening totext) so the code block is explicitly marked;
locate the block with the exact content locales/ja/translation.json and replace
its fence accordingly.


</details>

---

`84-86`: **コードブロックに言語指定子を追加してください**

コマンド実行例のコードブロックには言語指定子(`bash`または`shell`)を追加してください。

<details>
<summary>📝 修正案</summary>

```diff
-```
+```bash
 /sync-translations
 ```
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @.agents/skills/sync-translations/SKILL.md around lines 84 - 86, The fenced
code block showing the command "/sync-translations" lacks a language tag; update
that triple-backtick block to include a shell language specifier (e.g., bash or shell) so the command renders with proper syntax highlighting—locate the
fenced block containing "/sync-translations" in SKILL.md and change the opening
fence to bash (or shell).


</details>

</blockquote></details>
<details>
<summary>.agents/skills/remotion-best-practices/rules/charts.md (1)</summary><blockquote>

`17-31`: **変数名の衝突に注意してください。**

Line 29 の `height: height * item.value` において、`height` という変数名が2回使用されています(CSSプロパティ名と `useVideoConfig()` から取得した変数)。これは技術的には動作しますが、可読性のため、異なる変数名を使用することを検討してください(例:`barHeight` や `computedHeight`)。


<details>
<summary>💡 可読性向上の提案</summary>

```diff
 const STAGGER_DELAY = 5;
 const frame = useCurrentFrame();
-const { fps } = useVideoConfig();
+const { fps, height: videoHeight } = useVideoConfig();
 
 const bars = data.map((item, i) => {
   const height = spring({
     frame,
     fps,
     delay: i * STAGGER_DELAY,
     config: { damping: 200 },
   });
-  return <div style={{ height: height * item.value }} />;
+  return <div style={{ height: `${height * item.value * videoHeight}px` }} />;
 });
```

または、棒の高さ計算を別の変数名で行う:

```diff
 const bars = data.map((item, i) => {
-  const height = spring({
+  const progress = spring({
     frame,
     fps,
     delay: i * STAGGER_DELAY,
     config: { damping: 200 },
   });
-  return <div style={{ height: height * item.value }} />;
+  const barHeight = progress * item.value;
+  return <div style={{ height: barHeight }} />;
 });
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In @.agents/skills/remotion-best-practices/rules/charts.md around lines 17 - 31,
The code reuses the identifier height for both the spring result and the CSS
property which hurts readability; in the block that creates bars (variables
STAGGER_DELAY, frame from useCurrentFrame, fps from useVideoConfig, and the bars
map), rename the spring result (currently height) to a distinct name (e.g.,
barHeight or computedHeight) and use that new name when computing the style
(height: barHeight * item.value) so the spring value and the CSS property are
not conflated.
```

</details>

</blockquote></details>
<details>
<summary>.agents/skills/remotion-best-practices/rules/transcribe-captions.md (1)</summary><blockquote>

`55-65`: **プレースホルダーファイル名をより明確にすることを検討してください。**

`audio123.wav` や `captions123.json` のようなファイル名は、実際の使用例として混乱を招く可能性があります。より明確なプレースホルダー(例:`audio.wav` や `captions.json`、または `input-audio.wav` と `output-captions.json`)を使用することで、ドキュメントの理解しやすさが向上します。


<details>
<summary>💡 より明確な例への改善案</summary>

```diff
-  inputPath: "/path/to/audio123.wav",
+  inputPath: "/path/to/input-audio.wav",
   tokenLevelTimestamps: true,
 });
 
 // Optional: Apply our recommended postprocessing
 const { captions } = toCaptions({
   whisperCppOutput,
 });
 
 // Write it to the public/ folder so it can be fetched from Remotion
-fs.writeFileSync("captions123.json", JSON.stringify(captions, null, 2));
+fs.writeFileSync("output-captions.json", JSON.stringify(captions, null, 2));
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In @.agents/skills/remotion-best-practices/rules/transcribe-captions.md around
lines 55 - 65, Change confusing placeholder filenames to clearer, consistent
examples: update the inputPath value (referenced in the diff and the variable
inputPath) from "audio123.wav" to something like "input-audio.wav" or
"audio.wav", and update the fs.writeFileSync target (where captions from
toCaptions(whisperCppOutput) are written) from "captions123.json" to
"output-captions.json" or "captions.json"; keep the variable names
whisperCppOutput, toCaptions, and captions unchanged so the example still shows
how the transcription output flows into fs.writeFileSync.
```

</details>

</blockquote></details>
<details>
<summary>.agents/skills/remotion-best-practices/rules/gifs.md (1)</summary><blockquote>

`2-3`: **frontmatter の `name` をファイル名に揃えると管理しやすいです。**

`gifs.md` に対して `name: gif` だと参照時に揺れやすいので、`gifs` に統一するのをおすすめします。

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In @.agents/skills/remotion-best-practices/rules/gifs.md around lines 2 - 3,
frontmatter の name がファイル名と一致しておらず参照時に混乱するので、gifs.md の frontmatter 内の name: gif を
name: gifs に変更してファイル名と一致させてください(検索や参照で使われる識別子である frontmatter の name
を統一することで管理が楽になります)。
```

</details>

</blockquote></details>
<details>
<summary>.agents/skills/remotion-promo-video-factory/SKILL.md (1)</summary><blockquote>

`123-126`: **最終検証コマンドを package manager 非依存の表現にすると再利用しやすいです。**

現状は npm 固定なので、`<pm> run ...` 形式や npm/bun/yarn/pnpm 併記にするとチーム適用範囲が広がります。  


<details>
<summary>例(記述改善)</summary>

```diff
-1. `npx tsc --noEmit`
-2. `npm run -s build`
-3. `npm run -s build:gif` if available
+1. `npx tsc --noEmit`
+2. `<pm> run -s build` (例: npm / bun / yarn / pnpm)
+3. `<pm> run -s build:gif` if available
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In @.agents/skills/remotion-promo-video-factory/SKILL.md around lines 123 - 126,
現状の npm 固定コマンド(例: "npx tsc --noEmit", "npm run -s build", "npm run -s
build:gif")はパッケージマネージャ依存なので、再利用性を高めるためにパッケージマネージャ非依存の表現へ変更してください。具体的には SKILL.md
の該当コマンドリストを "<pm> run -s build" のようなプレースホルダ形式に置き換えるか、"npm / yarn / pnpm / bun
それぞれのコマンドを併記" のいずれかで記述を更新し、最後の検証手順("Render final mp4...")も同様に pm 非依存または各 PM
対応例を追記してチームで使える汎用的な記述にしてください。
```

</details>

</blockquote></details>
<details>
<summary>src/utils/mediaDisplay.ts (1)</summary><blockquote>

`7-14`: **無効入力時のフォールバックは最小サイズ保証を入れると安全です。**

Line 13 で 0/負値が返る可能性があるため、最低1pxに寄せる防御を推奨します。  


<details>
<summary>💡 提案差分</summary>

```diff
   if (
     sourceWidth <= 0 ||
     sourceHeight <= 0 ||
     maxWidth <= 0 ||
     maxHeight <= 0
   ) {
-    return { width: maxWidth, height: maxHeight }
+    return {
+      width: Math.max(1, maxWidth),
+      height: Math.max(1, maxHeight),
+    }
   }
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@src/utils/mediaDisplay.ts` around lines 7 - 14, The fallback path in
src/utils/mediaDisplay.ts currently returns maxWidth/maxHeight directly when
inputs are non-positive; change that to ensure a minimum of 1px by clamping both
width and height with Math.max(1, maxWidth) and Math.max(1, maxHeight) before
returning, so the function never yields 0 or negative dimensions (update the
return in the conditional that currently returns the raw maxWidth/maxHeight).
```

</details>

</blockquote></details>
<details>
<summary>src/components/settings/index.tsx (1)</summary><blockquote>

`21-21`: **import は `@/*` エイリアスへ統一してください。**

この追加行だけ相対パスになっており、リポジトリの import ルールとずれています。  


<details>
<summary>差分案</summary>

```diff
-import GameCommentarySettings from './gameCommentarySettings'
+import GameCommentarySettings from '@/components/settings/gameCommentarySettings'
```
</details>

As per coding guidelines, `src/**/*.{ts,tsx,js,jsx}`: Use path alias `@/*` for imports instead of relative paths to reference `./src/*`.

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@src/components/settings/index.tsx` at line 21, The import of
GameCommentarySettings uses a relative path ('./gameCommentarySettings'); update
that import to use the project alias syntax starting with '@/...' instead so it
follows the repository import rule (replace the './gameCommentarySettings'
import with an equivalent '@/...' alias import for GameCommentarySettings).
```

</details>

</blockquote></details>
<details>
<summary>src/features/chat/vercelAIChat.ts (1)</summary><blockquote>

`10-10`: **新規 import は `@/` エイリアスで統一してください。**

Line 10 だけ相対 import を追加していて、この PR で規約違反を増やしています。

As per coding guidelines, `src/**/*.{ts,tsx,js,jsx}`: Use path aliases `@/*` → `./src/*` in all TypeScript and JavaScript imports

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@src/features/chat/vercelAIChat.ts` at line 10, The import in vercelAIChat.ts
uses a relative path; change it to the project alias style by replacing the
relative import of AIChatResponseStreamOptions with the '@/...' alias (e.g.,
import type { AIChatResponseStreamOptions } from
'@/features/chat/aiChatFactory') so the symbol AIChatResponseStreamOptions is
imported via the alias and matches the repo's src import convention.
```

</details>

</blockquote></details>
<details>
<summary>src/features/chat/openAIAudioChat.ts (1)</summary><blockquote>

`14-14`: **新しい import は `@/` エイリアスへ寄せてください。**

`./aiChatFactory` を増やすより、`@/features/chat/aiChatFactory` に揃えたほうがこの配下の import ルールと一貫します。

As per coding guidelines, "Use path alias `@/*` for imports instead of relative paths to reference `./src/*`".

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@src/features/chat/openAIAudioChat.ts` at line 14, The import in
openAIAudioChat.ts uses a relative path; replace it with the project path alias
by importing AIChatResponseStreamOptions from "@/features/chat/aiChatFactory"
instead of "./aiChatFactory" so it follows the repository's alias convention and
keeps imports consistent under this feature (update the import statement that
references AIChatResponseStreamOptions accordingly).
```

</details>

</blockquote></details>
<details>
<summary>src/__tests__/features/messages/speakCharacterConcurrency.test.ts (1)</summary><blockquote>

`2-66`: **テスト側の import / mock パスも `@/` に揃えてください。**

深い相対パスが多く、ファイル移動で壊れやすいですし、`speakCharacter.ts` 側の import 表記ともずれます。alias に揃えると mock 対象も追いやすくなります。

As per coding guidelines, "Use path alias `@/*` for imports instead of relative paths to reference `./src/*`".

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@src/__tests__/features/messages/speakCharacterConcurrency.test.ts` around
lines 2 - 66, Update all jest.mock and import statements in this test to use the
project path alias (`@/`...) instead of deep relative paths: replace occurrences
like '../../../features/stores/settings',
'../../../features/messages/speakQueue', '../../../utils/wait',
'../../../features/messages/synthesizeVoiceVoicevox', and the import of
speakCharacter from '../../../features/messages/speakCharacter' with their
corresponding '@/...' alias modules so mocks align with speakCharacter's imports
and are resilient to file moves; ensure each mocked symbol (e.g., getState,
SpeakQueue class, wait, synthesizeVoiceVoicevoxApi, etc.) keeps the same mock
shape and names when switching to the alias paths.
```

</details>

</blockquote></details>
<details>
<summary>src/features/chat/difyChat.ts (1)</summary><blockquote>

`5-5`: **新しい import は `@/` エイリアスに揃えてください。**

ここだけ `./aiChatFactory` の相対パスが増えると、移動時に壊れやすくなります。`@/features/chat/aiChatFactory` に合わせたほうが、このディレクトリの import ルールと揃います。

As per coding guidelines, "Use path alias `@/*` for imports instead of relative paths to reference `./src/*`".

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@src/features/chat/difyChat.ts` at line 5, Update the import for
AIChatResponseStreamOptions to use the project path alias instead of a relative
path: replace the relative import of AIChatResponseStreamOptions from
"./aiChatFactory" with the aliased import from "@/features/chat/aiChatFactory"
so the symbol AIChatResponseStreamOptions is imported via the `@/` alias and
matches the directory's import convention.
```

</details>

</blockquote></details>
<details>
<summary>src/components/settings/gameCommentarySettings.tsx (1)</summary><blockquote>

`7-10`: **新規 import は `@/` エイリアスに揃えてください。**

Line 10 だけ相対 import になっていて、このPRで追加したコードが既存ルールから外れています。`@/components/toggleSwitch` のようなエイリアス参照に合わせたいです。  


As per coding guidelines "Use path alias `@/*` for imports instead of relative paths to reference `./src/*`".

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@src/components/settings/gameCommentarySettings.tsx` around lines 7 - 10, The
import for ToggleSwitch uses a relative path ('../toggleSwitch') which violates
the project's alias rule; update the import to use the path alias (e.g., import
ToggleSwitch from '@/components/toggleSwitch') so the symbol ToggleSwitch is
imported via the '@/...' alias consistent with other imports like settingsStore
and homeStore.
```

</details>

</blockquote></details>
<details>
<summary>src/__tests__/features/gameCommentary/generateGameCommentary.test.ts (1)</summary><blockquote>

`103-118`: **`console.error` の差し替えは `spyOn` / `mockRestore` に寄せたほうが安全です。**

この書き方だと途中の `await` やアサーションで落ちた場合に復元まで到達せず、後続テストまでグローバルな `console.error` が汚染されます。`jest.spyOn(console, 'error')` を使って `mockRestore()` で戻す形にしてください。

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@src/__tests__/features/gameCommentary/generateGameCommentary.test.ts` around
lines 103 - 118, Replace the direct reassignment of console.error in the test
for generateGameCommentary with jest.spyOn to avoid global contamination: use
jest.spyOn(console, 'error') before mocking getAIChatResponseStream to reject
with abortError, assert expectations, and finally call mockRestore() on the spy
to ensure console.error is restored even if the test fails; reference the
generateGameCommentary test block and the getAIChatResponseStream mock when
making the change.
```

</details>

</blockquote></details>

</blockquote></details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

Comment thread src/components/settings/modelProvider/hooks/useAIServiceHandlers.ts
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/hooks/useGameCommentaryMode.ts`:
- Line 66: gameCommentaryResizeWidth is using a falsy check
(ss.gameCommentaryResizeWidth || 1024) which treats 0 as unset and overrides the
intended "no resize" value; change the assignment in useGameCommentaryMode (the
gameCommentaryResizeWidth binding that reads from ss) to only default when the
setting is undefined or null (e.g., use nullish coalescing or an explicit
undefined check) so that a configured 0 is preserved while still defaulting to
1024 when truly not provided.
- Around line 69-70: Clamp the runtime background analysis interval to a safe
minimum (e.g., 1) to avoid an immediate setTimeout re-run loop; replace direct
uses of gameCommentaryBackgroundAnalysisInterval with a small accessor like
getEffectiveBackgroundAnalysisInterval (created via useCallback) that returns
Math.max(gameCommentaryBackgroundAnalysisInterval, 1), and use that accessor
wherever the interval drives the setTimeout loop (the code around the existing
setTimeout logic that currently reads gameCommentaryBackgroundAnalysisInterval).
- Around line 291-306: scheduleNext closes over an outdated triggerCommentary
because the timer callback was created without including triggerCommentary in
deps; update scheduleNext to call a ref that always points to the latest
triggerCommentary: create triggerCommentaryRef (e.g.
triggerCommentaryRef.current) and add a useEffect that sets
triggerCommentaryRef.current = () => { void triggerCommentary() } whenever
triggerCommentary changes, then change the setTimeout and any interval callbacks
inside scheduleNext to invoke triggerCommentaryRef.current() instead of
triggerCommentary; keep clearTimers, getEffectiveCaptureInterval in deps and
remove the stale eslint-disable comment.
🪄 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

Run ID: dd3fbc1a-90fb-4406-a7ed-6103b2c40ecb

📥 Commits

Reviewing files that changed from the base of the PR and between 6e7736d and 833f234.

📒 Files selected for processing (1)
  • src/hooks/useGameCommentaryMode.ts

Comment thread src/hooks/useGameCommentaryMode.ts Outdated
Comment thread src/hooks/useGameCommentaryMode.ts
Comment thread src/hooks/useGameCommentaryMode.ts
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Apr 26, 2026

Claude finished @tegnike's task —— View job


PR Review: Game Commentary Mode Feature

This PR introduces a comprehensive AI-powered game commentary system that captures screen content and generates real-time commentary. Here's my detailed review:

📋 Review Summary

✅ Strengths

Architecture & Design:

  • Clean separation of concerns with dedicated useGameCommentaryMode hook, CaptureService singleton, and proper store integration
  • Background scene analysis system provides rich context during long commentary sessions (analyzeGameCommentaryScene.ts:250)
  • Exclusion rules integration prevents conflicts with other modes (realtime API, audio mode, etc.) via exclusionRules.ts:330-377
  • Proper React patterns with ref-based state management and cleanup in hooks

Implementation Quality:

  • Comprehensive error handling including abort signal support and graceful degradation
  • Memory-efficient ring buffers for commentary history (useGameCommentaryMode.ts:164-175)
  • Canvas-based screen capture with configurable compression and resize options
  • Test coverage includes unit tests for core functions and integration scenarios

⚠️ Performance Considerations

Screen Capture Overhead:

  • Canvas operations in CaptureService.captureFrame() run synchronously on main thread (captureService.ts:40-57)
  • Image processing during resize creates new Image objects without disposal
  • Memory leaks potential - background analysis accumulates ImageBitmap objects without explicit cleanup

Recommendations:

// Consider moving canvas operations to OffscreenCanvas or Web Workers
captureFrame(maxWidth?: number, quality?: number): string | null {
  // Add try-finally to ensure cleanup
  try {
    const img = new Image()
    // ... existing logic
  } finally {
    // Explicit cleanup if needed
  }
}

🔒 Security Assessment

✅ Secure Practices:

  • No user input injection - AI prompts are from preset files, not user-provided
  • Screen capture permission properly requested via getDisplayMedia() API
  • AbortController usage prevents request racing and memory leaks
  • Data URI handling is safe - base64 image data only

Minor Concern:

  • AI prompt injection - while preset files are controlled, ensure user-provided context (chat messages) are sanitized before inclusion in AI requests

🧪 Test Coverage Analysis

Strong Coverage:

  • ✅ Core functions: generateGameCommentary, parseCommentaryResponse, buildGameCommentaryMessages
  • ✅ Hook lifecycle: timer management, state transitions, background analysis
  • ✅ Error scenarios: abort handling, network failures, invalid responses
  • ✅ Concurrency: speaker queue integration and session management

Coverage Gaps:

  • ⚠️ CaptureService canvas operations not directly tested
  • ⚠️ Image resize quality/compression edge cases
  • ⚠️ Background analysis memory cleanup

🐛 Potential Issues

1. Race Conditions (useGameCommentaryMode.ts:369-380):

// Token validation may not prevent all races
if (requestToken !== commentaryRequestTokenRef.current) {
  return // Good: prevents stale requests
}

✅ Well handled with request tokens and abort controllers.

2. Memory Management (useGameCommentaryMode.ts:214-220):

// Background analyses accumulate without size limits initially
if (backgroundSceneAnalysesRef.current.length > GAME_COMMENTARY_BACKGROUND_ANALYSIS.MAX_BUFFERED_ITEMS) {
  // Cleanup occurs, but consider explicit disposal
}

3. Canvas Performance (capture.tsx:56-63):

// Synchronous canvas operations on main thread
ctx.drawImage(video, 0, 0)
return canvas.toDataURL('image/jpeg', 0.9)

📊 Code Quality

✅ Best Practices:

  • TypeScript types are comprehensive and well-defined
  • Error boundaries and fallback states implemented
  • Internationalization support via i18next
  • Consistent naming conventions and JSDoc documentation

⚠️ Improvements:

  • Consider extracting magic numbers to constants (MIN_SCHEDULED_CAPTURE_INTERVAL_SECONDS = 3)
  • Some functions exceed optimal length (e.g., triggerCommentary at 160+ lines)
  • Canvas operations could benefit from performance optimization

💡 Recommendations

High Priority:

  1. Optimize canvas operations - consider OffscreenCanvas or requestAnimationFrame scheduling
  2. Add explicit cleanup for ImageBitmap objects in background analysis
  3. Monitor memory usage in long-running commentary sessions

Medium Priority:

  1. Add performance metrics logging for capture/generation times
  2. Consider compression alternatives - WebP support where available
  3. Enhance error recovery for network failures during AI generation

Low Priority:

  1. Extract large functions into smaller, testable units
  2. Add JSDoc examples for complex interfaces like CommentaryHistoryEntry
  3. Consider adding TypeScript strict mode compliance

🎯 Final Assessment

This is a well-architected feature with strong separation of concerns, comprehensive error handling, and good test coverage. The implementation demonstrates solid understanding of React patterns and performance considerations.

Recommendation: ✅ Approve with minor suggestions

The identified issues are primarily optimizations rather than blocking problems. The feature integrates cleanly with the existing codebase and follows established patterns.


Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
src/__tests__/components/settings/modelProvider/useAIServiceHandlers.test.ts (1)

54-56: アサーションを強化して誤検知を減らしてください。

Line [54]-[56] の toHaveBeenCalledWith だけだと、余分な setState 呼び出しがあっても通る可能性があります。toHaveBeenCalledTimes(1) の併用を推奨します。

差分案
     expect(mockedSettingsStore.setState).toHaveBeenCalledWith({
       enableMultiModal: false,
     })
+    expect(mockedSettingsStore.setState).toHaveBeenCalledTimes(1)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/__tests__/components/settings/modelProvider/useAIServiceHandlers.test.ts`
around lines 54 - 56, 強化されたアサーションを追加して余分な呼び出しを検出できるようにしてください: 現在の期待は
mockedSettingsStore.setState
の引数だけを検証しているので、テスト内の該当箇所(expect(mockedSettingsStore.setState).toHaveBeenCalledWith({...}))に対して呼び出し回数のチェックを併用し、expect(mockedSettingsStore.setState).toHaveBeenCalledTimes(1)
を追加して余計な setState 呼び出しがないことを保証してください。
src/__tests__/hooks/useGameCommentaryMode.test.ts (1)

391-404: gameCommentaryImageQuality: 0 の回帰テストも追加しておきたいです。

resizeWidth=0 はカバーできていますが、同じ falsy default 系の不具合は画質設定でも起きやすいです。captureFrame(1024, 0) が呼ばれるケースも押さえておくと、この種の回帰を防ぎやすいです。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/__tests__/hooks/useGameCommentaryMode.test.ts` around lines 391 - 404,
Add a regression test to cover the falsy image-quality case: in the existing
test suite for useGameCommentaryMode add a test similar to the "preserves zero
resize width" one but set setupSettingsState({ gameCommentaryImageQuality: 0 })
and assert that mockCaptureFrame is called with the default width (e.g. 1024)
and quality 0 (expect(mockCaptureFrame).toHaveBeenCalledWith(1024, 0)); keep the
same renderHook(...useGameCommentaryMode...) and timer/flush flow so the hook
triggers the capture path.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/hooks/useGameCommentaryMode.ts`:
- Around line 73-80: The settingsStore subscription in useGameCommentaryMode
only forces a re-render and doesn't restart any pending timers, so changes to
gameCommentaryCaptureInterval don't affect the currently waiting timeout; update
the subscription callback to clear the existing timer (use the same timeoutRef /
any existing timer id used by scheduleNext / clearTimeout) and then call
scheduleNext() so the new captureIntervalRef.current is applied immediately;
ensure you still call settingsStore.subscribe/unsubscribe as before and preserve
cleanup via the returned unsubscribe.
- Line 66: The defaulting logic for gameCommentaryImageQuality in
useGameCommentaryMode is wrong: replace the falsy OR fallback so that a
legitimate 0 value from ss.gameCommentaryImageQuality is preserved; change the
assignment that currently uses "ss.gameCommentaryImageQuality || 0.7" to use a
nullish check (or explicit undefined check) so only undefined/null falls back to
0.7, ensuring captureFrame can receive quality=0; reference variables:
gameCommentaryImageQuality, ss.gameCommentaryImageQuality, and the
useGameCommentaryMode hook, and ensure captureFrame continues to accept 0 as
valid.

---

Nitpick comments:
In
`@src/__tests__/components/settings/modelProvider/useAIServiceHandlers.test.ts`:
- Around line 54-56: 強化されたアサーションを追加して余分な呼び出しを検出できるようにしてください: 現在の期待は
mockedSettingsStore.setState
の引数だけを検証しているので、テスト内の該当箇所(expect(mockedSettingsStore.setState).toHaveBeenCalledWith({...}))に対して呼び出し回数のチェックを併用し、expect(mockedSettingsStore.setState).toHaveBeenCalledTimes(1)
を追加して余計な setState 呼び出しがないことを保証してください。

In `@src/__tests__/hooks/useGameCommentaryMode.test.ts`:
- Around line 391-404: Add a regression test to cover the falsy image-quality
case: in the existing test suite for useGameCommentaryMode add a test similar to
the "preserves zero resize width" one but set setupSettingsState({
gameCommentaryImageQuality: 0 }) and assert that mockCaptureFrame is called with
the default width (e.g. 1024) and quality 0
(expect(mockCaptureFrame).toHaveBeenCalledWith(1024, 0)); keep the same
renderHook(...useGameCommentaryMode...) and timer/flush flow so the hook
triggers the capture path.
🪄 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

Run ID: 89537612-d4bd-4084-a0da-ad4affc52ab0

📥 Commits

Reviewing files that changed from the base of the PR and between 833f234 and edefee1.

📒 Files selected for processing (5)
  • .github/workflows/deploy-cloudflare.yml
  • src/__tests__/components/settings/modelProvider/useAIServiceHandlers.test.ts
  • src/__tests__/hooks/useGameCommentaryMode.test.ts
  • src/components/settings/modelProvider/hooks/useAIServiceHandlers.ts
  • src/hooks/useGameCommentaryMode.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/settings/modelProvider/hooks/useAIServiceHandlers.ts

Comment thread src/hooks/useGameCommentaryMode.ts Outdated
Comment thread src/hooks/useGameCommentaryMode.ts Outdated
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Apr 26, 2026

Claude finished @tegnike's task —— View job


PR Review: Game Commentary Mode Feature

Todo List:

  • Read core game commentary implementation files
  • Analyze code architecture and patterns
  • Review security considerations
  • Evaluate performance implications
  • Check test coverage quality
  • Provide comprehensive feedback

📋 Review Summary

This PR introduces a comprehensive AI-powered game commentary system that captures screen content and generates real-time commentary. The implementation demonstrates solid architectural patterns and thorough consideration of edge cases.

Strengths

Excellent Architecture & Design

  • Clean separation of concerns with dedicated hook (useGameCommentaryMode), singleton service (CaptureService), and proper store integration
  • Robust state management using refs to avoid stale closures and proper React patterns
  • Background scene analysis system provides contextual awareness during long commentary sessions
  • Exclusion rules integration prevents conflicts with other modes (realtime API, audio mode, etc.)
  • Ring buffer implementation for commentary history with configurable context size

Comprehensive Error Handling

  • AbortController support prevents race conditions and memory leaks
  • Graceful degradation when capture or AI generation fails
  • Proper cleanup of timers, streams, and background processes
  • Request token validation prevents stale responses

Strong Test Coverage

  • Well-structured tests for core functions: generateGameCommentary, parseCommentaryResponse, buildGameCommentaryMessages
  • Hook lifecycle testing covers timer management, state transitions, and interruption scenarios
  • Integration testing for speaker queue and concurrency handling
  • Mock implementations properly isolate units under test

⚠️ Areas for Improvement

1. Performance Optimizations

Canvas Operations - HIGH PRIORITY (captureService.ts:40-57):

// Current implementation does synchronous canvas operations on main thread
const img = new Image()
img.src = raw
// No explicit cleanup of image objects

// Consider using OffscreenCanvas for heavy operations
if (img.width <= maxWidth) return raw
ctx.drawImage(img, 0, 0, canvas.width, canvas.height) // Main thread blocking

Memory Management (useGameCommentaryMode.ts:214-220):

  • Background analyses accumulate without explicit cleanup of ImageBitmap objects
  • Ring buffer cleanup could be more aggressive for memory conservation

2. Security Considerations

✅ Generally Secure:

  • Screen capture uses proper getDisplayMedia() permissions
  • AI prompts are from controlled preset files, not user input
  • Data URI handling is safe (base64 image data only)

Minor Concern:

  • Chat message integration (useGameCommentaryMode.ts:352-358) should sanitize user content before AI prompt inclusion

3. Code Quality Improvements

Magic Numbers (useGameCommentaryMode.ts:16):

const MIN_SCHEDULED_CAPTURE_INTERVAL_SECONDS = 3 // Should be in constants file

Function Length: The triggerCommentary function is 160+ lines and could benefit from extraction:

// Consider extracting:
// - Image capture logic
// - AI generation with context building  
// - Speech synthesis and queuing

Image Object Lifecycle (captureService.ts:40-44):

// Potential memory leak - Image objects not explicitly disposed
const img = new Image()
img.src = raw
// Consider: img.onload = () => { /* process then img.src = '' */ }

🔧 Specific Technical Issues

Race Condition Handling

The request token system effectively prevents stale responses:

if (requestToken !== commentaryRequestTokenRef.current) {
  return // Properly exits on stale requests
}

Background Analysis Throttling

Good implementation prevents analysis spam during speech:

if (isBackgroundAnalysisInFlightRef.current) return
// Ensures only one analysis at a time

Stream Cleanup ✅ (capture.tsx:66-70)

Proper event listener setup for detecting browser-initiated stream termination.

📊 Performance Analysis

Capture Frequency: 3-300 second intervals with minimum 3s constraint is reasonable
Image Compression: Configurable quality (0.7 default) with resize options helps manage API costs
Context Management: Ring buffer limits prevent unbounded memory growth
Background Processing: Properly queued to avoid interference with main commentary flow

🎯 Recommendations

High Priority

  1. Optimize canvas operations - consider OffscreenCanvas or requestAnimationFrame scheduling
  2. Add explicit ImageBitmap disposal in background analysis cleanup
  3. Extract large functions into smaller, testable units

Medium Priority

  1. Enhance error recovery for network failures during AI generation
  2. Add performance monitoring for capture/generation times
  3. Consider WebP compression where supported for better efficiency

Low Priority

  1. Move magic numbers to constants file
  2. Add JSDoc examples for complex interfaces
  3. Consider strict TypeScript compliance improvements

🔍 Code Quality Score

Architecture: ⭐⭐⭐⭐⭐ (Excellent)
Error Handling: ⭐⭐⭐⭐⭐ (Comprehensive)
Performance: ⭐⭐⭐⭐☆ (Good, room for optimization)
Security: ⭐⭐⭐⭐⭐ (Well-secured)
Test Coverage: ⭐⭐⭐⭐⭐ (Thorough)
Maintainability: ⭐⭐⭐⭐☆ (Good, some refactoring beneficial)

Final Assessment

This is a well-architected feature with strong separation of concerns, comprehensive error handling, and excellent test coverage. The implementation demonstrates solid understanding of React patterns and performance considerations.

Recommendation: ✅ Approve with Minor Suggestions

The identified issues are primarily optimizations rather than blocking problems. The feature integrates cleanly with the existing codebase and follows established patterns. The thorough test coverage and robust error handling make this a production-ready implementation.


Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/features/chat/openAIAudioChat.ts (1)

17-17: import はパスエイリアスに統一してください

ここで追加された相対 import は、リポジトリの import ルールとズレています。エイリアス形式に揃えた方が一貫性を保てます。

差分案
-import type { AIChatResponseStreamOptions } from './aiChatFactory'
+import type { AIChatResponseStreamOptions } from '@/features/chat/aiChatFactory'

As per coding guidelines "**/*.{ts,tsx}: Use path aliases: @/*./src/*".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/chat/openAIAudioChat.ts` at line 17, The import in
openAIAudioChat.ts uses a relative path for AIChatResponseStreamOptions; change
it to the project path-alias form so imports follow the repository rule (use `@/`
to map to ./src/). Update the import of AIChatResponseStreamOptions from
'./aiChatFactory' to the corresponding alias path (e.g.,
'@/features/chat/aiChatFactory') so the symbol AIChatResponseStreamOptions is
imported via the path alias.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/features/chat/openAIAudioChat.ts`:
- Line 17: The import in openAIAudioChat.ts uses a relative path for
AIChatResponseStreamOptions; change it to the project path-alias form so imports
follow the repository rule (use `@/` to map to ./src/). Update the import of
AIChatResponseStreamOptions from './aiChatFactory' to the corresponding alias
path (e.g., '@/features/chat/aiChatFactory') so the symbol
AIChatResponseStreamOptions is imported via the path alias.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 8ef77c60-de68-4be5-a94d-776948ba2bd0

📥 Commits

Reviewing files that changed from the base of the PR and between edefee1 and a8852a8.

📒 Files selected for processing (4)
  • src/__tests__/components/settings/modelProvider/useAIServiceHandlers.test.ts
  • src/__tests__/hooks/useGameCommentaryMode.test.ts
  • src/features/chat/openAIAudioChat.ts
  • src/hooks/useGameCommentaryMode.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/tests/hooks/useGameCommentaryMode.test.ts
  • src/tests/components/settings/modelProvider/useAIServiceHandlers.test.ts

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Apr 26, 2026

Claude finished @tegnike's task —— View job


PR Review: Game Commentary Mode Feature

Todo List:

  • Read repository guidelines (CLAUDE.md)
  • Read core game commentary implementation files
  • Analyze code architecture and patterns
  • Review security considerations
  • Evaluate performance implications
  • Check test coverage quality
  • Provide comprehensive feedback

📋 Review Summary

This PR introduces a comprehensive AI-powered game commentary system that captures screen content and generates real-time commentary. The implementation demonstrates solid architectural patterns and thorough consideration of edge cases.

Strengths

Excellent Architecture & Design

  • Clean separation of concerns with dedicated hook (useGameCommentaryMode), singleton service (CaptureService), and proper store integration
  • Robust state management using refs to avoid stale closures and proper React patterns
  • Background scene analysis system provides contextual awareness during long commentary sessions
  • Exclusion rules integration prevents conflicts with other modes (realtime API, audio mode, etc.)
  • Ring buffer implementation for commentary history with configurable context size

Comprehensive Error Handling

  • AbortController support prevents race conditions and memory leaks
  • Graceful degradation when capture or AI generation fails
  • Proper cleanup of timers, streams, and background processes
  • Request token validation prevents stale responses

Strong Test Coverage

  • Well-structured tests for core functions: generateGameCommentary, parseCommentaryResponse, buildGameCommentaryMessages
  • Hook lifecycle testing covers timer management, state transitions, and interruption scenarios
  • Integration testing for speaker queue and concurrency handling
  • Mock implementations properly isolate units under test

⚠️ Areas for Improvement

1. Performance Optimizations

Canvas Operations - HIGH PRIORITY (captureService.ts:40-57):

// Current implementation does synchronous canvas operations on main thread
const img = new Image()
img.src = raw
// No explicit cleanup of image objects

// Consider using OffscreenCanvas for heavy operations
if (img.width <= maxWidth) return raw
ctx.drawImage(img, 0, 0, canvas.width, canvas.height) // Main thread blocking

Memory Management (useGameCommentaryMode.ts:214-220):

  • Background analyses accumulate without explicit cleanup of ImageBitmap objects
  • Ring buffer cleanup could be more aggressive for memory conservation

2. Security Considerations

✅ Generally Secure:

  • Screen capture uses proper getDisplayMedia() permissions
  • AI prompts are from controlled preset files, not user input
  • Data URI handling is safe (base64 image data only)

Minor Concern:

  • Chat message integration (useGameCommentaryMode.ts:352-358) should sanitize user content before AI prompt inclusion

3. Code Quality Improvements

Magic Numbers (useGameCommentaryMode.ts:16):

const MIN_SCHEDULED_CAPTURE_INTERVAL_SECONDS = 3 // Should be in constants file

Function Length: The triggerCommentary function is 160+ lines and could benefit from extraction:

// Consider extracting:
// - Image capture logic
// - AI generation with context building  
// - Speech synthesis and queuing

Image Object Lifecycle (captureService.ts:40-44):

// Potential memory leak - Image objects not explicitly disposed
const img = new Image()
img.src = raw
// Consider: img.onload = () => { /* process then img.src = '' */ }

🔧 Specific Technical Issues

Race Condition Handling

The request token system effectively prevents stale responses:

if (requestToken !== commentaryRequestTokenRef.current) {
  return // Properly exits on stale requests
}

Background Analysis Throttling

Good implementation prevents analysis spam during speech:

if (isBackgroundAnalysisInFlightRef.current) return
// Ensures only one analysis at a time

Stream Cleanup ✅ (capture.tsx:66-70)

Proper event listener setup for detecting browser-initiated stream termination.

📊 Performance Analysis

Capture Frequency: 3-300 second intervals with minimum 3s constraint is reasonable
Image Compression: Configurable quality (0.7 default) with resize options helps manage API costs
Context Management: Ring buffer limits prevent unbounded memory growth
Background Processing: Properly queued to avoid interference with main commentary flow

🎯 Recommendations

High Priority

  1. Optimize canvas operations - consider OffscreenCanvas or requestAnimationFrame scheduling
  2. Add explicit ImageBitmap disposal in background analysis cleanup
  3. Extract large functions into smaller, testable units

Medium Priority

  1. Enhance error recovery for network failures during AI generation
  2. Add performance monitoring for capture/generation times
  3. Consider WebP compression where supported for better efficiency

Low Priority

  1. Move magic numbers to constants file
  2. Add JSDoc examples for complex interfaces
  3. Consider strict TypeScript compliance improvements

🔍 Code Quality Score

Architecture: ⭐⭐⭐⭐⭐ (Excellent)
Error Handling: ⭐⭐⭐⭐⭐ (Comprehensive)
Performance: ⭐⭐⭐⭐☆ (Good, room for optimization)
Security: ⭐⭐⭐⭐⭐ (Well-secured)
Test Coverage: ⭐⭐⭐⭐⭐ (Thorough)
Maintainability: ⭐⭐⭐⭐☆ (Good, some refactoring beneficial)

Final Assessment

This is a well-architected feature with strong separation of concerns, comprehensive error handling, and excellent test coverage. The implementation demonstrates solid understanding of React patterns and performance considerations.

Recommendation: ✅ Approve with Minor Suggestions

The identified issues are primarily optimizations rather than blocking problems. The feature integrates cleanly with the existing codebase and follows established patterns. The thorough test coverage and robust error handling make this a production-ready implementation.


@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Apr 26, 2026

Claude finished @tegnike's task —— View job


PR Review: Game Commentary Mode Feature

Todo List:

  • Read repository guidelines (CLAUDE.md)
  • Read core game commentary implementation files
  • Analyze code architecture and patterns
  • Review security considerations
  • Evaluate performance implications
  • Check test coverage quality
  • Provide comprehensive feedback

📋 Review Summary

This PR introduces a comprehensive AI-powered game commentary system that captures screen content and generates real-time commentary. The implementation demonstrates solid architectural patterns and thorough consideration of edge cases.

Strengths

Excellent Architecture & Design

  • Clean separation of concerns with dedicated hook (useGameCommentaryMode), singleton service (CaptureService), and proper store integration
  • Robust state management using refs to avoid stale closures and proper React patterns
  • Background scene analysis system provides contextual awareness during long commentary sessions
  • Exclusion rules integration prevents conflicts with other modes (realtime API, audio mode, etc.)
  • Ring buffer implementation for commentary history with configurable context size

Comprehensive Error Handling

  • AbortController support prevents race conditions and memory leaks
  • Graceful degradation when capture or AI generation fails
  • Proper cleanup of timers, streams, and background processes
  • Request token validation prevents stale responses

Strong Test Coverage

  • Well-structured tests for core functions: generateGameCommentary, parseCommentaryResponse, buildGameCommentaryMessages
  • Hook lifecycle testing covers timer management, state transitions, and interruption scenarios
  • Integration testing for speaker queue and concurrency handling
  • Mock implementations properly isolate units under test

⚠️ Areas for Improvement

1. Performance Optimizations

Canvas Operations - HIGH PRIORITY (captureService.ts:40-57):

// Current implementation does synchronous canvas operations on main thread
const img = new Image()
img.src = raw
// No explicit cleanup of image objects

// Consider using OffscreenCanvas for heavy operations
if (img.width <= maxWidth) return raw
ctx.drawImage(img, 0, 0, canvas.width, canvas.height) // Main thread blocking

Memory Management (useGameCommentaryMode.ts:214-220):

  • Background analyses accumulate without explicit cleanup of ImageBitmap objects
  • Ring buffer cleanup could be more aggressive for memory conservation

2. Security Considerations

✅ Generally Secure:

  • Screen capture uses proper getDisplayMedia() permissions
  • AI prompts are from controlled preset files, not user input
  • Data URI handling is safe (base64 image data only)

Minor Concern:

  • Chat message integration (useGameCommentaryMode.ts:352-358) should sanitize user content before AI prompt inclusion

3. Code Quality Improvements

Magic Numbers (useGameCommentaryMode.ts:16):

const MIN_SCHEDULED_CAPTURE_INTERVAL_SECONDS = 3 // Should be in constants file

Function Length: The triggerCommentary function is 160+ lines and could benefit from extraction:

// Consider extracting:
// - Image capture logic
// - AI generation with context building  
// - Speech synthesis and queuing

Image Object Lifecycle (captureService.ts:40-44):

// Potential memory leak - Image objects not explicitly disposed
const img = new Image()
img.src = raw
// Consider: img.onload = () => { /* process then img.src = '' */ }

🔧 Specific Technical Issues

Race Condition Handling

The request token system effectively prevents stale responses:

if (requestToken !== commentaryRequestTokenRef.current) {
  return // Properly exits on stale requests
}

Background Analysis Throttling

Good implementation prevents analysis spam during speech:

if (isBackgroundAnalysisInFlightRef.current) return
// Ensures only one analysis at a time

Stream Cleanup ✅ (capture.tsx:66-70)

Proper event listener setup for detecting browser-initiated stream termination.

📊 Performance Analysis

Capture Frequency: 3-300 second intervals with minimum 3s constraint is reasonable
Image Compression: Configurable quality (0.7 default) with resize options helps manage API costs
Context Management: Ring buffer limits prevent unbounded memory growth
Background Processing: Properly queued to avoid interference with main commentary flow

🎯 Recommendations

High Priority

  1. Optimize canvas operations - consider OffscreenCanvas or requestAnimationFrame scheduling
  2. Add explicit ImageBitmap disposal in background analysis cleanup
  3. Extract large functions into smaller, testable units

Medium Priority

  1. Enhance error recovery for network failures during AI generation
  2. Add performance monitoring for capture/generation times
  3. Consider WebP compression where supported for better efficiency

Low Priority

  1. Move magic numbers to constants file
  2. Add JSDoc examples for complex interfaces
  3. Consider strict TypeScript compliance improvements

🔍 Code Quality Score

Architecture: ⭐⭐⭐⭐⭐ (Excellent)
Error Handling: ⭐⭐⭐⭐⭐ (Comprehensive)
Performance: ⭐⭐⭐⭐☆ (Good, room for optimization)
Security: ⭐⭐⭐⭐⭐ (Well-secured)
Test Coverage: ⭐⭐⭐⭐⭐ (Thorough)
Maintainability: ⭐⭐⭐⭐☆ (Good, some refactoring beneficial)

Final Assessment

This is a well-architected feature with strong separation of concerns, comprehensive error handling, and excellent test coverage. The implementation demonstrates solid understanding of React patterns and performance considerations.

Recommendation: ✅ Approve with Minor Suggestions

The identified issues are primarily optimizations rather than blocking problems. The feature integrates cleanly with the existing codebase and follows established patterns. The thorough test coverage and robust error handling make this a production-ready implementation.


Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 11

🧹 Nitpick comments (3)
locales/ar/translation.json (1)

654-654: UI文言に英語固定ラベルが残っています。

Line 654 の Capture はアラビア語UIの中で英語固定です。ボタン名称がローカライズ対象なら、翻訳側で統一したほうが一貫性が上がります。

✏️ 修正例
-    "NoCaptureWarning": "لم تبدأ مشاركة الشاشة. ابدأها بزر “Capture” في القائمة.",
+    "NoCaptureWarning": "لم تبدأ مشاركة الشاشة. ابدأها بزر «التقاط» في القائمة.",

Based on learnings: Support internationalization with Japanese as the source language and delegate other language translations to a separate managed process

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@locales/ar/translation.json` at line 654, The Arabic translation for the
"NoCaptureWarning" message embeds the English fixed label "Capture"; update this
entry to use the localized Arabic button label (or an i18n label reference)
instead of the hard-coded English word so the UI is consistent—locate the
"NoCaptureWarning" key in locales/ar/translation.json and replace "Capture" with
the Arabic translation of the button text or with the shared label key used
elsewhere (e.g. the Capture button's translation key) so all languages render
the same button name.
locales/es/translation.json (1)

435-436: Line 436 のスペイン語は自然な表現に寄せる余地があります。

"Mostrar vídeo de nuevo" は意味は通りますが、UI文言としては "Volver a mostrar el vídeo" の方が自然です。

✍️ 提案差分
-  "ShowVideoDisplay": "Mostrar vídeo de nuevo",
+  "ShowVideoDisplay": "Volver a mostrar el vídeo",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@locales/es/translation.json` around lines 435 - 436, Update the Spanish
translation for the key "ShowVideoDisplay" to a more natural UI phrase: replace
"Mostrar vídeo de nuevo" with "Volver a mostrar el vídeo", leaving
"HideVideoDisplay" unchanged.
locales/ru/translation.json (1)

687-691: 削除対象として整理できます。MultiModalMode* キーは完全に未参照です。

これらの翻訳キーは、廃止予定の multiModalMode 設定に対応するものですが、コード内での参照が一切ありません。src/features/stores/settings.tsmultiModalMode から enableMultiModal への移行処理が行われており、古い設定プロパティは削除済みです。翻訳ファイル内のこれらのキーは保守対象を増やすだけなので、削除を推奨します。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@locales/ru/translation.json` around lines 687 - 691, Remove the dead
translation keys "MultiModalMode", "MultiModalModeDescription",
"MultiModalModeAIDecide", "MultiModalModeAlways", and "MultiModalModeNever" from
the Russian translation file because they correspond to the deprecated
multiModalMode setting (now migrated to enableMultiModal in
src/features/stores/settings.ts) and are no longer referenced in code; update
the locales/ru/translation.json by deleting those keys to avoid maintaining
unused strings.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@locales/de/translation.json`:
- Line 656: The translation for key "DisabledByExclusion" uses the inconsistent
term "Sprachmodus"; update that value so the term matches the rest of the file
by replacing "Sprachmodus" with "Audiomodus" (i.e., edit the string for
"DisabledByExclusion" to use "Audiomodus" so AudioMode terminology is consistent
across the locale).

In `@locales/es/translation.json`:
- Around line 631-665: Update the Spanish translation for the
DisabledByExclusion message to use the consistent term "Modo de audio" instead
of "Modo de voz": locate the JSON key "DisabledByExclusion" and replace the
phrase "Modo de voz" with the same "Modo de audio" string used elsewhere in the
file so the exclusion warning matches the settings names exactly.

In `@locales/fr/translation.json`:
- Around line 633-663: The French locale uses the Japanese range marker "〜"
inconsistently; replace it with the natural French "à" across the affected keys
(e.g., "CaptureIntervalInfo", "ContextCountInfo", "ImageQualityInfo",
"BackgroundAnalysisIntervalInfo", "VideoDelayInfo") and adjust the wording of
"CaptureIntervalInfo" to a more natural French word order (e.g., "Définissez
l’intervalle de capture de l’écran : {{min}} à {{max}} secondes."). Ensure all
other instances of "({{min}}〜{{max}})" follow the same pattern and keep the
existing keys (EnableInfo, CaptureInterval, CaptureIntervalInfo, ContextCount,
ContextCountInfo, PromptTemplateInfo, ImageQualityInfo, ResizeWidthInfo,
BackgroundAnalysisIntervalInfo, VideoDelayInfo) unchanged except for the textual
fixes.

In `@locales/hi/translation.json`:
- Around line 96-97: The hi locale file contains manual additions ("CopyEnvVars"
and "CopyEnvVarsInfo") that violate the policy of only updating Japanese locale
files; revert these manual changes from the hi translation (remove the
"CopyEnvVars" and "CopyEnvVarsInfo" entries) and instead add the new keys and
strings to the ja locale source so translations can be propagated by the proper
workflow; repeat this revert for any other non-ja files where the same new keys
were added.

In `@locales/it/translation.json`:
- Around line 687-691: Remove the unused translation keys MultiModalMode,
MultiModalModeDescription, MultiModalModeAIDecide, MultiModalModeAlways, and
MultiModalModeNever from this Italian translation file (and the other language
files) because the codebase only uses EnableMultiModal and
EnableMultiModalDescription; search for those specific keys (MultiModalMode*,
EnableMultiModal, EnableMultiModalDescription) to confirm usage, then delete the
unused MultiModalMode* entries across all locales to reduce maintenance overhead
and keep translations in sync.

In `@locales/ko/translation.json`:
- Around line 96-97: Revert the manual Korean translations you added (e.g. the
keys "CopyEnvVars" and "CopyEnvVarsInfo" and the other newly added keys in the
same commit) so that only the Japanese locale receives the new strings; undo the
edits in the Korean translation JSON and leave the new keys present only under
the Japanese locale, allowing the translation synchronization workflow to
propagate translations for ko instead of committing manual ko changes.

In `@locales/pl/translation.json`:
- Line 654: The string value for the translation key "NoCaptureWarning" contains
the English button label "Capture"; update this value to use the Polish UI
wording instead (e.g., replace "Capture" with the Polish equivalent used in the
app like "Przechwyć" or the actual Polish button text) so the message no longer
depends on an English label and matches the rest of the Polish UI.

In `@locales/pt/translation.json`:
- Around line 684-691: Add the missing translation keys MultiModalMode,
MultiModalModeDescription, MultiModalModeAIDecide (and also MultiModalModeAlways
and MultiModalModeNever) to the Japanese translation file with appropriate
Japanese strings, ensuring the keys match exactly the ones added in the
Portuguese file; after committing those additions to the Japanese file only, run
the project's translation update tool/process to propagate the new keys to other
language files automatically.

In `@locales/ru/translation.json`:
- Around line 654-656: Update the Russian strings to use the localized UI label
instead of hard-coded English and to use consistent terminology: in the
"NoCaptureWarning" value replace the literal “Capture” with the localized label
placeholder (e.g., the same label used in the UI for the screen-share/capture
action) so it reads with the localized button name rather than the English word,
and in "DisabledByExclusion" replace "голосовой режим" with the existing project
term "Аудиорежим" to keep naming consistent with other keys.

In `@locales/th/translation.json`:
- Around line 684-691: Revert the manual edits in locales/th/translation.json
and restore it to match the canonical translations managed by the translation
pipeline; specifically remove the unintended placeholder {{count}} from the Thai
"ConversationHistoryInfo" entry so it matches the Japanese source, and delete
the five added keys ("MultiModalMode", "MultiModalModeDescription",
"MultiModalModeAIDecide", "MultiModalModeAlways", "MultiModalModeNever") that
are not present in the Japanese source; instead, make any required updates in
the Japanese files under locales/ja/ and trigger the established localization
process so other languages are updated consistently.

In `@locales/zh-TW/translation.json`:
- Around line 631-665: The translation uses inconsistent terminology: the
GameCommentary.DisabledByExclusion value contains "外部連動" but the file already
uses "外部連接模式" elsewhere; update the DisabledByExclusion string to use the
existing term "外部連接模式" so wording is consistent across the locale (search for
the "GameCommentary.DisabledByExclusion" key and replace "外部連動" with "外部連接模式").

---

Nitpick comments:
In `@locales/ar/translation.json`:
- Line 654: The Arabic translation for the "NoCaptureWarning" message embeds the
English fixed label "Capture"; update this entry to use the localized Arabic
button label (or an i18n label reference) instead of the hard-coded English word
so the UI is consistent—locate the "NoCaptureWarning" key in
locales/ar/translation.json and replace "Capture" with the Arabic translation of
the button text or with the shared label key used elsewhere (e.g. the Capture
button's translation key) so all languages render the same button name.

In `@locales/es/translation.json`:
- Around line 435-436: Update the Spanish translation for the key
"ShowVideoDisplay" to a more natural UI phrase: replace "Mostrar vídeo de nuevo"
with "Volver a mostrar el vídeo", leaving "HideVideoDisplay" unchanged.

In `@locales/ru/translation.json`:
- Around line 687-691: Remove the dead translation keys "MultiModalMode",
"MultiModalModeDescription", "MultiModalModeAIDecide", "MultiModalModeAlways",
and "MultiModalModeNever" from the Russian translation file because they
correspond to the deprecated multiModalMode setting (now migrated to
enableMultiModal in src/features/stores/settings.ts) and are no longer
referenced in code; update the locales/ru/translation.json by deleting those
keys to avoid maintaining unused strings.
🪄 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

Run ID: 6cba2886-ebd4-48cb-8304-5a95a349b386

📥 Commits

Reviewing files that changed from the base of the PR and between a8852a8 and d0bf414.

📒 Files selected for processing (16)
  • .gitignore
  • locales/ar/translation.json
  • locales/de/translation.json
  • locales/en/translation.json
  • locales/es/translation.json
  • locales/fr/translation.json
  • locales/hi/translation.json
  • locales/it/translation.json
  • locales/ko/translation.json
  • locales/pl/translation.json
  • locales/pt/translation.json
  • locales/ru/translation.json
  • locales/th/translation.json
  • locales/vi/translation.json
  • locales/zh-CN/translation.json
  • locales/zh-TW/translation.json
✅ Files skipped from review due to trivial changes (1)
  • .gitignore
🚧 Files skipped from review as they are similar to previous changes (1)
  • locales/en/translation.json

"BackgroundAnalysisIntervalInfo": "Legt fest, wie oft die zusätzliche Bildanalyse während der Sprachausgabe ausgeführt wird ({{min}}〜{{max}} Sekunden). Kürzere Intervalle folgen der Szene besser, erhöhen aber die API-Kosten.",
"NoCaptureWarning": "Die Bildschirmfreigabe wurde nicht gestartet. Starten Sie sie über die Schaltfläche „Capture“ im Menü.",
"NonMultiModalWarning": "Das aktuell ausgewählte KI-Modell unterstützt keine multimodale Eingabe (Bilder). Der Spielkommentar-Modus benötigt ein multimodales Modell wie Gemini oder GPT-4o.",
"DisabledByExclusion": "Der Spielkommentar-Modus kann nicht verwendet werden, da ein anderer Modus aktiv ist (Realtime API / Sprachmodus / Externe Verknüpfung / Idle-Modus).",
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

用語の一貫性: SprachmodusAudiomodus に揃えるのを推奨します。

同一ファイル内で AudioMode 系は「Audiomodus」を使っており、ここだけ表記が異なるため、設定の排他理由がやや分かりにくくなります。

修正案
-    "DisabledByExclusion": "Der Spielkommentar-Modus kann nicht verwendet werden, da ein anderer Modus aktiv ist (Realtime API / Sprachmodus / Externe Verknüpfung / Idle-Modus).",
+    "DisabledByExclusion": "Der Spielkommentar-Modus kann nicht verwendet werden, da ein anderer Modus aktiv ist (Realtime API / Audiomodus / Externe Verknüpfung / Idle-Modus).",
📝 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
"DisabledByExclusion": "Der Spielkommentar-Modus kann nicht verwendet werden, da ein anderer Modus aktiv ist (Realtime API / Sprachmodus / Externe Verknüpfung / Idle-Modus).",
"DisabledByExclusion": "Der Spielkommentar-Modus kann nicht verwendet werden, da ein anderer Modus aktiv ist (Realtime API / Audiomodus / Externe Verknüpfung / Idle-Modus).",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@locales/de/translation.json` at line 656, The translation for key
"DisabledByExclusion" uses the inconsistent term "Sprachmodus"; update that
value so the term matches the rest of the file by replacing "Sprachmodus" with
"Audiomodus" (i.e., edit the string for "DisabledByExclusion" to use
"Audiomodus" so AudioMode terminology is consistent across the locale).

Comment on lines +631 to +665
"GameCommentarySettings": "Configuración de comentarios de juego",
"GameCommentary": {
"Enable": "Modo de comentarios de juego",
"EnableInfo": "Captura la pantalla a intervalos regulares y la IA genera y pronuncia comentarios en tiempo real. Se requiere un modelo de IA multimodal.",
"CaptureInterval": "Intervalo de captura (segundos)",
"CaptureIntervalInfo": "Define el intervalo de captura de pantalla ({{min}}〜{{max}} segundos).",
"ContextCount": "Número de comentarios previos de referencia",
"ContextCountInfo": "Define cuántos comentarios anteriores se envían a la IA ({{min}}〜{{max}}). Cuantos más haya, más contexto se considera, pero aumenta el coste de API.",
"PromptTemplate": "Prompt de comentarios",
"PromptTemplateInfo": "Prompt del sistema usado cuando la IA comenta el juego. Se combina con el prompt del sistema de la configuración del personaje.\nSi incluyes la etiqueta [scene] en el formato de salida, la IA generará una descripción de la escena y la usará como contexto de pantalla anterior en comentarios posteriores.",
"ImageQuality": "Calidad de imagen",
"ImageQualityInfo": "Calidad JPEG de la imagen capturada ({{min}}〜{{max}}). Cuanto menor sea, menor será el tamaño de datos y el coste de API.",
"ResizeWidth": "Ancho de redimensionado de imagen",
"ResizeWidthInfo": "Ancho de redimensionado de la captura en píxeles. Valores menores reducen el tamaño de datos.",
"ResizeWidthNone": "Sin redimensionar",
"SaveToChat": "Guardar comentarios en el historial de chat",
"SaveToChatInfo": "Si se activa, los comentarios también aparecerán en el registro de chat. Si se desactiva, solo se conservarán en el búfer dedicado.",
"BackgroundAnalysis": "Análisis auxiliar de imagen durante el habla",
"BackgroundAnalysisInfo": "Si se activa, la pantalla se analiza con baja frecuencia mientras se reproduce el comentario y se pasa como información auxiliar para el siguiente comentario. Refuerza el flujo de la escena, pero genera costes adicionales de API multimodal. Se recomienda solo con modelos rápidos de análisis de imagen.",
"BackgroundAnalysisPromptTemplate": "Prompt de análisis auxiliar de imagen",
"BackgroundAnalysisPromptTemplateInfo": "Prompt del sistema para el análisis auxiliar de imagen durante el habla. Aquí puedes especificar el género del juego y los elementos a observar. Incluir información para identificar el juego mejora la estabilidad.",
"BackgroundAnalysisInterval": "Intervalo de análisis auxiliar de imagen (segundos)",
"BackgroundAnalysisIntervalInfo": "Define cada cuántos segundos se ejecuta el análisis auxiliar durante el habla ({{min}}〜{{max}} segundos). Intervalos más cortos siguen mejor la escena, pero aumentan el coste de API.",
"NoCaptureWarning": "La pantalla compartida no se ha iniciado. Iníciala con el botón “Capture” del menú.",
"NonMultiModalWarning": "El modelo de IA seleccionado no admite multimodalidad (entrada de imágenes). El modo de comentarios de juego requiere un modelo multimodal como Gemini o GPT-4o.",
"DisabledByExclusion": "No se puede usar el modo de comentarios de juego porque otro modo está activo (Realtime API / Modo de voz / Enlace externo / Modo inactivo).",
"ApiCostWarning": "⚠️ El modo de comentarios de juego llama a una API multimodal en cada intervalo de captura, por lo que se generarán costes de API. Ten cuidado.",
"Speaking": "Comentando",
"Capturing": "Capturando",
"WaitingPrefix": "Espera",
"CaptureUnavailable": "Captura detenida",
"VideoDelay": "Retraso de vídeo (segundos)",
"VideoDelayInfo": "Retrasa la visualización del vídeo compartido el número de segundos indicado ({{min}}〜{{max}} segundos). Compensa el tiempo de generación de comentarios por IA y alinea la voz con el vídeo. 0 lo desactiva. Las capturas enviadas a la IA siguen siendo en tiempo real.",
"VideoDelayOff": "OFF"
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

排他モード警告の用語を統一してください。

Line 656 の "Modo de voz" は、同ファイル内で使われている "Modo de audio" と不一致です。設定名とのズレでユーザーが混乱しやすいため、用語統一を推奨します。

🛠️ 提案差分
-    "DisabledByExclusion": "No se puede usar el modo de comentarios de juego porque otro modo está activo (Realtime API / Modo de voz / Enlace externo / Modo inactivo).",
+    "DisabledByExclusion": "No se puede usar el modo de comentarios de juego porque otro modo está activo (Realtime API / Modo de audio / Enlace externo / Modo inactivo).",

Based on learnings: Maintain mutual exclusivity of incompatible settings through the exclusion control middleware system to prevent configuration conflicts.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@locales/es/translation.json` around lines 631 - 665, Update the Spanish
translation for the DisabledByExclusion message to use the consistent term "Modo
de audio" instead of "Modo de voz": locate the JSON key "DisabledByExclusion"
and replace the phrase "Modo de voz" with the same "Modo de audio" string used
elsewhere in the file so the exclusion warning matches the settings names
exactly.

Comment on lines +633 to +663
"Enable": "Mode commentaire de jeu",
"EnableInfo": "Capture l’écran à intervalles réguliers, puis l’IA génère et prononce un commentaire en temps réel. Un modèle d’IA multimodal est requis.",
"CaptureInterval": "Intervalle de capture (secondes)",
"CaptureIntervalInfo": "Définissez l’intervalle de capture de l’écran ({{min}}〜{{max}} secondes).",
"ContextCount": "Nombre d’historiques de commentaire à référencer",
"ContextCountInfo": "Définissez le nombre de commentaires précédents envoyés à l’IA ({{min}}〜{{max}}). Plus ce nombre est élevé, plus le contexte est pris en compte, mais les coûts d’API augmentent.",
"PromptTemplate": "Prompt de commentaire",
"PromptTemplateInfo": "Prompt système utilisé lorsque l’IA commente le jeu. Il est combiné avec le prompt système des paramètres du personnage.\nSi vous incluez la balise [scene] dans le format de sortie, l’IA génère une description de la scène et l’utilise comme contexte d’écran précédent pour les commentaires suivants.",
"ImageQuality": "Qualité d’image",
"ImageQualityInfo": "Qualité JPEG de l’image capturée ({{min}}〜{{max}}). Une valeur plus basse réduit la taille des données et les coûts d’API.",
"ResizeWidth": "Largeur de redimensionnement de l’image",
"ResizeWidthInfo": "Largeur de redimensionnement des captures en pixels. Une valeur plus petite réduit la taille des données.",
"ResizeWidthNone": "Pas de redimensionnement",
"SaveToChat": "Enregistrer les commentaires dans l’historique du chat",
"SaveToChatInfo": "Si activé, les commentaires apparaissent aussi dans le journal de chat. Sinon, ils sont conservés uniquement dans le tampon dédié.",
"BackgroundAnalysis": "Analyse d’image auxiliaire pendant la parole",
"BackgroundAnalysisInfo": "Si activé, l’écran est aussi analysé à faible fréquence pendant la lecture du commentaire et transmis comme information auxiliaire pour le commentaire suivant. Cela améliore la continuité de la scène, mais entraîne des coûts supplémentaires d’API multimodale. Recommandé uniquement avec un modèle d’analyse d’image rapide.",
"BackgroundAnalysisPromptTemplate": "Prompt d’analyse d’image auxiliaire",
"BackgroundAnalysisPromptTemplateInfo": "Prompt système utilisé pour l’analyse d’image auxiliaire pendant la parole. Vous pouvez y préciser le genre du jeu et les éléments à observer. Ajouter des informations permettant d’identifier le jeu améliore la stabilité.",
"BackgroundAnalysisInterval": "Intervalle d’analyse d’image auxiliaire (secondes)",
"BackgroundAnalysisIntervalInfo": "Définissez la fréquence de l’analyse d’image auxiliaire pendant la parole ({{min}}〜{{max}} secondes). Un intervalle plus court suit mieux la scène, mais augmente les coûts d’API.",
"NoCaptureWarning": "Le partage d’écran n’a pas commencé. Lancez-le avec le bouton « Capture » du menu.",
"NonMultiModalWarning": "Le modèle d’IA sélectionné ne prend pas en charge le multimodal (entrée image). Le mode commentaire de jeu nécessite un modèle multimodal comme Gemini ou GPT-4o.",
"DisabledByExclusion": "Le mode commentaire de jeu ne peut pas être utilisé car un autre mode est activé (Realtime API / Mode vocal / Liaison externe / Mode inactif).",
"ApiCostWarning": "⚠️ Le mode commentaire de jeu appelle une API multimodale à chaque intervalle de capture, ce qui entraîne des coûts d’API. Soyez prudent.",
"Speaking": "Commentaire en cours",
"Capturing": "Capture en cours",
"WaitingPrefix": "Attente",
"CaptureUnavailable": "Capture arrêtée",
"VideoDelay": "Délai vidéo (secondes)",
"VideoDelayInfo": "Retarde l’affichage vidéo du partage d’écran du nombre de secondes indiqué ({{min}}〜{{max}} secondes). Cela compense le temps de génération du commentaire par l’IA et synchronise la voix avec la vidéo. 0 désactive l’option. Les captures envoyées à l’IA restent en temps réel.",
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

フランス語UI文言で表記ゆれ(「〜」)があります。

フランス語ロケールでは範囲表記を à に統一した方が自然です。あわせて Line 633 の語順も自然化できます。

✏️ 文言修正案
-    "Enable": "Mode commentaire de jeu",
+    "Enable": "Mode de commentaire de jeu",
-    "CaptureIntervalInfo": "Définissez l’intervalle de capture de l’écran ({{min}}〜{{max}} secondes).",
+    "CaptureIntervalInfo": "Définissez l’intervalle de capture de l’écran ({{min}} à {{max}} secondes).",
-    "ContextCountInfo": "Définissez le nombre de commentaires précédents envoyés à l’IA ({{min}}〜{{max}}). Plus ce nombre est élevé, plus le contexte est pris en compte, mais les coûts d’API augmentent.",
+    "ContextCountInfo": "Définissez le nombre de commentaires précédents envoyés à l’IA ({{min}} à {{max}}). Plus ce nombre est élevé, plus le contexte est pris en compte, mais les coûts d’API augmentent.",
-    "ImageQualityInfo": "Qualité JPEG de l’image capturée ({{min}}〜{{max}}). Une valeur plus basse réduit la taille des données et les coûts d’API.",
+    "ImageQualityInfo": "Qualité JPEG de l’image capturée ({{min}} à {{max}}). Une valeur plus basse réduit la taille des données et les coûts d’API.",
-    "BackgroundAnalysisIntervalInfo": "Définissez la fréquence de l’analyse d’image auxiliaire pendant la parole ({{min}}〜{{max}} secondes). Un intervalle plus court suit mieux la scène, mais augmente les coûts d’API.",
+    "BackgroundAnalysisIntervalInfo": "Définissez la fréquence de l’analyse d’image auxiliaire pendant la parole ({{min}} à {{max}} secondes). Un intervalle plus court suit mieux la scène, mais augmente les coûts d’API.",
-    "VideoDelayInfo": "Retarde l’affichage vidéo du partage d’écran du nombre de secondes indiqué ({{min}}〜{{max}} secondes). Cela compense le temps de génération du commentaire par l’IA et synchronise la voix avec la vidéo. 0 désactive l’option. Les captures envoyées à l’IA restent en temps réel.",
+    "VideoDelayInfo": "Retarde l’affichage vidéo du partage d’écran du nombre de secondes indiqué ({{min}} à {{max}} secondes). Cela compense le temps de génération du commentaire par l’IA et synchronise la voix avec la vidéo. 0 désactive l’option. Les captures envoyées à l’IA restent en temps réel.",
📝 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
"Enable": "Mode commentaire de jeu",
"EnableInfo": "Capture lécran à intervalles réguliers, puis lIA génère et prononce un commentaire en temps réel. Un modèle dIA multimodal est requis.",
"CaptureInterval": "Intervalle de capture (secondes)",
"CaptureIntervalInfo": "Définissez lintervalle de capture de lécran ({{min}}{{max}} secondes).",
"ContextCount": "Nombre dhistoriques de commentaire à référencer",
"ContextCountInfo": "Définissez le nombre de commentaires précédents envoyés à lIA ({{min}}{{max}}). Plus ce nombre est élevé, plus le contexte est pris en compte, mais les coûts dAPI augmentent.",
"PromptTemplate": "Prompt de commentaire",
"PromptTemplateInfo": "Prompt système utilisé lorsque lIA commente le jeu. Il est combiné avec le prompt système des paramètres du personnage.\nSi vous incluez la balise [scene] dans le format de sortie, lIA génère une description de la scène et lutilise comme contexte décran précédent pour les commentaires suivants.",
"ImageQuality": "Qualité dimage",
"ImageQualityInfo": "Qualité JPEG de limage capturée ({{min}}{{max}}). Une valeur plus basse réduit la taille des données et les coûts dAPI.",
"ResizeWidth": "Largeur de redimensionnement de limage",
"ResizeWidthInfo": "Largeur de redimensionnement des captures en pixels. Une valeur plus petite réduit la taille des données.",
"ResizeWidthNone": "Pas de redimensionnement",
"SaveToChat": "Enregistrer les commentaires dans lhistorique du chat",
"SaveToChatInfo": "Si activé, les commentaires apparaissent aussi dans le journal de chat. Sinon, ils sont conservés uniquement dans le tampon dédié.",
"BackgroundAnalysis": "Analyse dimage auxiliaire pendant la parole",
"BackgroundAnalysisInfo": "Si activé, lécran est aussi analysé à faible fréquence pendant la lecture du commentaire et transmis comme information auxiliaire pour le commentaire suivant. Cela améliore la continuité de la scène, mais entraîne des coûts supplémentaires dAPI multimodale. Recommandé uniquement avec un modèle danalyse dimage rapide.",
"BackgroundAnalysisPromptTemplate": "Prompt danalyse dimage auxiliaire",
"BackgroundAnalysisPromptTemplateInfo": "Prompt système utilisé pour lanalyse dimage auxiliaire pendant la parole. Vous pouvez y préciser le genre du jeu et les éléments à observer. Ajouter des informations permettant didentifier le jeu améliore la stabilité.",
"BackgroundAnalysisInterval": "Intervalle danalyse dimage auxiliaire (secondes)",
"BackgroundAnalysisIntervalInfo": "Définissez la fréquence de lanalyse dimage auxiliaire pendant la parole ({{min}}{{max}} secondes). Un intervalle plus court suit mieux la scène, mais augmente les coûts dAPI.",
"NoCaptureWarning": "Le partage décran na pas commencé. Lancez-le avec le bouton « Capture » du menu.",
"NonMultiModalWarning": "Le modèle dIA sélectionné ne prend pas en charge le multimodal (entrée image). Le mode commentaire de jeu nécessite un modèle multimodal comme Gemini ou GPT-4o.",
"DisabledByExclusion": "Le mode commentaire de jeu ne peut pas être utilisé car un autre mode est activé (Realtime API / Mode vocal / Liaison externe / Mode inactif).",
"ApiCostWarning": "⚠️ Le mode commentaire de jeu appelle une API multimodale à chaque intervalle de capture, ce qui entraîne des coûts dAPI. Soyez prudent.",
"Speaking": "Commentaire en cours",
"Capturing": "Capture en cours",
"WaitingPrefix": "Attente",
"CaptureUnavailable": "Capture arrêtée",
"VideoDelay": "Délai vidéo (secondes)",
"VideoDelayInfo": "Retarde laffichage vidéo du partage décran du nombre de secondes indiqué ({{min}}{{max}} secondes). Cela compense le temps de génération du commentaire par lIA et synchronise la voix avec la vidéo. 0 désactive loption. Les captures envoyées à lIA restent en temps réel.",
"Enable": "Mode de commentaire de jeu",
"EnableInfo": "Capture l'écran à intervalles réguliers, puis l'IA génère et prononce un commentaire en temps réel. Un modèle d'IA multimodal est requis.",
"CaptureInterval": "Intervalle de capture (secondes)",
"CaptureIntervalInfo": "Définissez l'intervalle de capture de l'écran ({{min}} à {{max}} secondes).",
"ContextCount": "Nombre d'historiques de commentaire à référencer",
"ContextCountInfo": "Définissez le nombre de commentaires précédents envoyés à l'IA ({{min}} à {{max}}). Plus ce nombre est élevé, plus le contexte est pris en compte, mais les coûts d'API augmentent.",
"PromptTemplate": "Prompt de commentaire",
"PromptTemplateInfo": "Prompt système utilisé lorsque l'IA commente le jeu. Il est combiné avec le prompt système des paramètres du personnage.\nSi vous incluez la balise [scene] dans le format de sortie, l'IA génère une description de la scène et l'utilise comme contexte d'écran précédent pour les commentaires suivants.",
"ImageQuality": "Qualité d'image",
"ImageQualityInfo": "Qualité JPEG de l'image capturée ({{min}} à {{max}}). Une valeur plus basse réduit la taille des données et les coûts d'API.",
"ResizeWidth": "Largeur de redimensionnement de l'image",
"ResizeWidthInfo": "Largeur de redimensionnement des captures en pixels. Une valeur plus petite réduit la taille des données.",
"ResizeWidthNone": "Pas de redimensionnement",
"SaveToChat": "Enregistrer les commentaires dans l'historique du chat",
"SaveToChatInfo": "Si activé, les commentaires apparaissent aussi dans le journal de chat. Sinon, ils sont conservés uniquement dans le tampon dédié.",
"BackgroundAnalysis": "Analyse d'image auxiliaire pendant la parole",
"BackgroundAnalysisInfo": "Si activé, l'écran est aussi analysé à faible fréquence pendant la lecture du commentaire et transmis comme information auxiliaire pour le commentaire suivant. Cela améliore la continuité de la scène, mais entraîne des coûts supplémentaires d'API multimodale. Recommandé uniquement avec un modèle d'analyse d'image rapide.",
"BackgroundAnalysisPromptTemplate": "Prompt d'analyse d'image auxiliaire",
"BackgroundAnalysisPromptTemplateInfo": "Prompt système utilisé pour l'analyse d'image auxiliaire pendant la parole. Vous pouvez y préciser le genre du jeu et les éléments à observer. Ajouter des informations permettant d'identifier le jeu améliore la stabilité.",
"BackgroundAnalysisInterval": "Intervalle d'analyse d'image auxiliaire (secondes)",
"BackgroundAnalysisIntervalInfo": "Définissez la fréquence de l'analyse d'image auxiliaire pendant la parole ({{min}} à {{max}} secondes). Un intervalle plus court suit mieux la scène, mais augmente les coûts d'API.",
"NoCaptureWarning": "Le partage d'écran n'a pas commencé. Lancez-le avec le bouton « Capture » du menu.",
"NonMultiModalWarning": "Le modèle d'IA sélectionné ne prend pas en charge le multimodal (entrée image). Le mode commentaire de jeu nécessite un modèle multimodal comme Gemini ou GPT-4o.",
"DisabledByExclusion": "Le mode commentaire de jeu ne peut pas être utilisé car un autre mode est activé (Realtime API / Mode vocal / Liaison externe / Mode inactif).",
"ApiCostWarning": "⚠️ Le mode commentaire de jeu appelle une API multimodale à chaque intervalle de capture, ce qui entraîne des coûts d'API. Soyez prudent.",
"Speaking": "Commentaire en cours",
"Capturing": "Capture en cours",
"WaitingPrefix": "Attente",
"CaptureUnavailable": "Capture arrêtée",
"VideoDelay": "Délai vidéo (secondes)",
"VideoDelayInfo": "Retarde l'affichage vidéo du partage d'écran du nombre de secondes indiqué ({{min}} à {{max}} secondes). Cela compense le temps de génération du commentaire par l'IA et synchronise la voix avec la vidéo. 0 désactive l'option. Les captures envoyées à l'IA restent en temps réel.",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@locales/fr/translation.json` around lines 633 - 663, The French locale uses
the Japanese range marker "〜" inconsistently; replace it with the natural French
"à" across the affected keys (e.g., "CaptureIntervalInfo", "ContextCountInfo",
"ImageQualityInfo", "BackgroundAnalysisIntervalInfo", "VideoDelayInfo") and
adjust the wording of "CaptureIntervalInfo" to a more natural French word order
(e.g., "Définissez l’intervalle de capture de l’écran : {{min}} à {{max}}
secondes."). Ensure all other instances of "({{min}}〜{{max}})" follow the same
pattern and keep the existing keys (EnableInfo, CaptureInterval,
CaptureIntervalInfo, ContextCount, ContextCountInfo, PromptTemplateInfo,
ImageQualityInfo, ResizeWidthInfo, BackgroundAnalysisIntervalInfo,
VideoDelayInfo) unchanged except for the textual fixes.

Comment on lines +96 to +97
"CopyEnvVars": "पर्यावरण चर कॉपी करें",
"CopyEnvVarsInfo": "वर्तमान कैरेक्टर स्थिति को पर्यावरण चर प्रारूप में क्लिपबोर्ड पर कॉपी करता है। आप इसे .env फ़ाइल में पेस्ट कर सकते हैं।",
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

非日本語ロケールの手動更新は避けてください

このリポジトリの運用方針では、新規翻訳キー追加時は /locales/ja/ のみを更新し、他言語は別の管理プロセスで反映する前提です。locales/hi/translation.json への今回の手動追加はその方針と競合するため、将来の同期ズレや上書きロストの原因になります。ja 側への反映に寄せ、このファイルの手動差分は戻すのが安全です。
Based on learnings: Applies to locales/ja/**/*.json : Update only Japanese language files (/locales/ja/) when adding new translation strings; do not manually update other language files

Also applies to: 308-308, 435-436, 524-524, 631-665, 684-691

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@locales/hi/translation.json` around lines 96 - 97, The hi locale file
contains manual additions ("CopyEnvVars" and "CopyEnvVarsInfo") that violate the
policy of only updating Japanese locale files; revert these manual changes from
the hi translation (remove the "CopyEnvVars" and "CopyEnvVarsInfo" entries) and
instead add the new keys and strings to the ja locale source so translations can
be propagated by the proper workflow; repeat this revert for any other non-ja
files where the same new keys were added.

Comment thread locales/it/translation.json
"BackgroundAnalysisPromptTemplateInfo": "Prompt systemowy używany do pomocniczej analizy obrazu podczas mówienia. Możesz tu wskazać gatunek gry i elementy do obserwowania. Informacje identyfikujące grę poprawiają stabilność.",
"BackgroundAnalysisInterval": "Interwał pomocniczej analizy obrazu (sekundy)",
"BackgroundAnalysisIntervalInfo": "Ustaw, co ile sekund wykonywać pomocniczą analizę obrazu podczas mówienia ({{min}}〜{{max}} s). Krótszy interwał lepiej śledzi scenę, ale zwiększa koszt API.",
"NoCaptureWarning": "Udostępnianie ekranu nie zostało rozpoczęte. Uruchom je przyciskiem „Capture” w menu.",
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

NoCaptureWarning に英語ラベルが残っています

Line 654 の Capture はポーランド語UI内で浮きやすいため、英語ボタン名依存を避けた表現に統一してください。

✏️ 修正案
-    "NoCaptureWarning": "Udostępnianie ekranu nie zostało rozpoczęte. Uruchom je przyciskiem „Capture” w menu.",
+    "NoCaptureWarning": "Udostępnianie ekranu nie zostało rozpoczęte. Uruchom je przyciskiem przechwytywania w menu.",
📝 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
"NoCaptureWarning": "Udostępnianie ekranu nie zostało rozpoczęte. Uruchom je przyciskiem „Capture” w menu.",
"NoCaptureWarning": "Udostępnianie ekranu nie zostało rozpoczęte. Uruchom je przyciskiem przechwytywania w menu.",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@locales/pl/translation.json` at line 654, The string value for the
translation key "NoCaptureWarning" contains the English button label "Capture";
update this value to use the Polish UI wording instead (e.g., replace "Capture"
with the Polish equivalent used in the app like "Przechwyć" or the actual Polish
button text) so the message no longer depends on an English label and matches
the rest of the Polish UI.

Comment thread locales/pt/translation.json
Comment on lines +654 to +656
"NoCaptureWarning": "Демонстрация экрана не начата. Запустите ее кнопкой “Capture” в меню.",
"NonMultiModalWarning": "Выбранная модель ИИ не поддерживает мультимодальность (ввод изображений). Для режима игрового комментирования нужна мультимодальная модель, например Gemini или GPT-4o.",
"DisabledByExclusion": "Режим игрового комментирования недоступен, потому что включен другой режим (Realtime API / голосовой режим / внешняя интеграция / режим ожидания).",
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

操作ラベルの英語固定と用語ゆれを修正してください。

Line 654 の Capture が英語固定で、ローカライズ済み UI ラベルと不一致になる可能性があります。
Line 656 の голосовой режим は既存キーの Аудиорежим と用語がずれており、設定名の一貫性が落ちます。

✏️ 修正例
-    "NoCaptureWarning": "Демонстрация экрана не начата. Запустите ее кнопкой “Capture” в меню.",
+    "NoCaptureWarning": "Демонстрация экрана не начата. Запустите её кнопкой захвата в меню.",
-    "DisabledByExclusion": "Режим игрового комментирования недоступен, потому что включен другой режим (Realtime API / голосовой режим / внешняя интеграция / режим ожидания).",
+    "DisabledByExclusion": "Режим игрового комментирования недоступен, потому что включён другой режим (Realtime API / аудиорежим / внешняя интеграция / режим ожидания).",
📝 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
"NoCaptureWarning": "Демонстрация экрана не начата. Запустите ее кнопкой “Capture” в меню.",
"NonMultiModalWarning": "Выбранная модель ИИ не поддерживает мультимодальность (ввод изображений). Для режима игрового комментирования нужна мультимодальная модель, например Gemini или GPT-4o.",
"DisabledByExclusion": "Режим игрового комментирования недоступен, потому что включен другой режим (Realtime API / голосовой режим / внешняя интеграция / режим ожидания).",
"NoCaptureWarning": "Демонстрация экрана не начата. Запустите её кнопкой захвата в меню.",
"NonMultiModalWarning": "Выбранная модель ИИ не поддерживает мультимодальность (ввод изображений). Для режима игрового комментирования нужна мультимодальная модель, например Gemini или GPT-4o.",
"DisabledByExclusion": "Режим игрового комментирования недоступен, потому что включён другой режим (Realtime API / аудиорежим / внешняя интеграция / режим ожидания).",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@locales/ru/translation.json` around lines 654 - 656, Update the Russian
strings to use the localized UI label instead of hard-coded English and to use
consistent terminology: in the "NoCaptureWarning" value replace the literal
“Capture” with the localized label placeholder (e.g., the same label used in the
UI for the screen-share/capture action) so it reads with the localized button
name rather than the English word, and in "DisabledByExclusion" replace
"голосовой режим" with the existing project term "Аудиорежим" to keep naming
consistent with other keys.

Comment on lines +684 to +691
"DemoModeAppNotice": "นี่คือแอปเดโมของ AITuberKit",
"DemoModeLimitedFeaturesNotice": "ฟีเจอร์บางส่วนถูกจำกัดในเวอร์ชันเดโม",
"DemoModeLogNotice": "แอปนี้ถูกตั้งค่าให้บันทึกบันทึกการสนทนาไว้บนเซิร์ฟเวอร์ บันทึกอาจถูกประมวลผลบางส่วนแล้วเผยแพร่ได้",
"MultiModalMode": "โหมดการใช้งานมัลติโหมด",
"MultiModalModeDescription": "โปรดเลือกเมื่อใดที่จะใช้ฟังก์ชันมัลติโมดอล",
"MultiModalModeAIDecide": "ตัดสินใจด้วย AI",
"MultiModalModeAlways": "ใช้เสมอ",
"MultiModalModeNever": "ไม่ใช้"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

python - <<'PY'
import json
import re
from pathlib import Path

root = Path(".")
th_candidates = list(root.rglob("locales/th/translation.json"))
ja_candidates = list(root.rglob("locales/ja/translation.json"))

if not th_candidates or not ja_candidates:
    raise SystemExit("required locale files not found: locales/th/translation.json and/or locales/ja/translation.json")

th_path = th_candidates[0]
ja_path = ja_candidates[0]

token_re = re.compile(r"\{\{[^{}]+\}\}")

def flatten(obj, prefix=""):
    out = {}
    if isinstance(obj, dict):
        for k, v in obj.items():
            p = f"{prefix}.{k}" if prefix else k
            out.update(flatten(v, p))
    else:
        out[prefix] = obj
    return out

ja = flatten(json.loads(ja_path.read_text(encoding="utf-8")))
th = flatten(json.loads(th_path.read_text(encoding="utf-8")))

missing_in_th = sorted(set(ja) - set(th))
extra_in_th = sorted(set(th) - set(ja))

placeholder_mismatch = []
for k in sorted(set(ja) & set(th)):
    if isinstance(ja[k], str) and isinstance(th[k], str):
        ja_tokens = sorted(token_re.findall(ja[k]))
        th_tokens = sorted(token_re.findall(th[k]))
        if ja_tokens != th_tokens:
            placeholder_mismatch.append((k, ja_tokens, th_tokens))

print("== missing_in_th ==")
print("\n".join(missing_in_th[:200]) or "(none)")
print("\n== extra_in_th ==")
print("\n".join(extra_in_th[:200]) or "(none)")
print("\n== placeholder_mismatch ==")
if placeholder_mismatch:
    for k, jt, tt in placeholder_mismatch[:200]:
        print(f"{k}\n  ja={jt}\n  th={tt}")
else:
    print("(none)")
PY

Repository: tegnike/aituber-kit

Length of output: 293


タイ語ファイルの手動編集を避け、確立されたプロセスに従ってください。

タイ語翻訳ファイル (locales/th/translation.json) の直接編集は避ける必要があります。学習ルールに基づき、翻訳は日本語ファイル (locales/ja/) のみを更新し、他言語は別の管理プロセスで処理します。

検証結果から以下の問題が検出されました:

  1. プレースホルダー不一致: ConversationHistoryInfo で日本語に {{count}} が無いのに、タイ語には存在しています
  2. 余分なキー: MultiModalModeMultiModalModeDescriptionMultiModalModeAIDecideMultiModalModeAlwaysMultiModalModeNever の5つのキーがタイ語に追加されていますが、日本語には未実装です

このような問題は、多言語キー整合性をチェックするCI検証で事前に検出可能です。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@locales/th/translation.json` around lines 684 - 691, Revert the manual edits
in locales/th/translation.json and restore it to match the canonical
translations managed by the translation pipeline; specifically remove the
unintended placeholder {{count}} from the Thai "ConversationHistoryInfo" entry
so it matches the Japanese source, and delete the five added keys
("MultiModalMode", "MultiModalModeDescription", "MultiModalModeAIDecide",
"MultiModalModeAlways", "MultiModalModeNever") that are not present in the
Japanese source; instead, make any required updates in the Japanese files under
locales/ja/ and trigger the established localization process so other languages
are updated consistently.

Comment on lines +631 to +665
"GameCommentarySettings": "遊戲實況設定",
"GameCommentary": {
"Enable": "遊戲實況模式",
"EnableInfo": "以固定間隔擷取螢幕,並由 AI 即時產生與朗讀實況解說。需要支援多模態的 AI 模型。",
"CaptureInterval": "擷取間隔(秒)",
"CaptureIntervalInfo": "設定螢幕擷取的間隔({{min}}〜{{max}} 秒)。",
"ContextCount": "參考的實況歷史數",
"ContextCountInfo": "設定送給 AI 的過去實況留言數({{min}}〜{{max}} 則)。越多越能考量脈絡,但 API 費用也會增加。",
"PromptTemplate": "實況提示詞",
"PromptTemplateInfo": "AI 進行遊戲實況時使用的系統提示詞。會與角色設定的系統提示詞組合使用。\n若在輸出格式中包含 [scene] 標籤,AI 會產生畫面情景描述,並在之後的實況中作為上一次畫面狀況參考。",
"ImageQuality": "影像品質",
"ImageQualityInfo": "擷取影像的 JPEG 品質({{min}}〜{{max}})。越低資料量越小,可降低 API 費用。",
"ResizeWidth": "影像縮放寬度",
"ResizeWidthInfo": "擷取影像的縮放寬度(像素)。越小資料量越小。",
"ResizeWidthNone": "不縮放",
"SaveToChat": "將實況留言儲存到聊天紀錄",
"SaveToChatInfo": "啟用後,實況留言也會顯示在聊天紀錄中。停用時,實況留言只會保留在專用緩衝區。",
"BackgroundAnalysis": "發話中的輔助影像分析",
"BackgroundAnalysisInfo": "啟用後,在實況發話中也會低頻率分析畫面,並將結果作為輔助資訊傳給下一次實況生成。可強化場景連續性,但會產生額外的多模態 API 費用。僅建議在使用高速影像分析模型時啟用。",
"BackgroundAnalysisPromptTemplate": "輔助影像分析提示詞",
"BackgroundAnalysisPromptTemplateInfo": "發話中的輔助影像分析所使用的系統提示詞。可在此指定遊戲類型與應關注的元素。包含可辨識遊戲的資訊會更穩定。",
"BackgroundAnalysisInterval": "輔助影像分析間隔(秒)",
"BackgroundAnalysisIntervalInfo": "設定發話中每隔幾秒執行一次輔助影像分析({{min}}〜{{max}} 秒)。越短越能跟上場景,但 API 費用會增加。",
"NoCaptureWarning": "尚未開始螢幕分享。請使用選單中的「Capture」按鈕開始螢幕分享。",
"NonMultiModalWarning": "目前選擇的 AI 模型不支援多模態(影像輸入)。遊戲實況模式需要 Gemini、GPT-4o 等多模態模型。",
"DisabledByExclusion": "由於其他模式(Realtime API / 語音模式 / 外部連動 / 閒置模式)已啟用,無法使用遊戲實況模式。",
"ApiCostWarning": "⚠️ 遊戲實況模式會在每次擷取間隔呼叫多模態 API,因此會產生 API 費用。請注意。",
"Speaking": "實況中",
"Capturing": "擷取中",
"WaitingPrefix": "等待",
"CaptureUnavailable": "擷取已停止",
"VideoDelay": "影像延遲(秒)",
"VideoDelayInfo": "將螢幕分享的影像顯示延遲指定秒數({{min}}〜{{max}} 秒)。用於補償 AI 生成實況所需時間,使語音與影像時機一致。0 表示停用。送給 AI 的擷取仍會即時進行。",
"VideoDelayOff": "關閉"
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

用語を既存訳に統一してください(Line 656)。

Line 656 の「外部連動」は、同ファイル内の既存用語「外部連接模式」(Line 13)と表記が揺れています。設定名の認知負荷を下げるため統一を推奨します。

✏️ 提案差分
-    "DisabledByExclusion": "由於其他模式(Realtime API / 語音模式 / 外部連動 / 閒置模式)已啟用,無法使用遊戲實況模式。",
+    "DisabledByExclusion": "由於其他模式(Realtime API / 語音模式 / 外部連接 / 閒置模式)已啟用,無法使用遊戲實況模式。",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@locales/zh-TW/translation.json` around lines 631 - 665, The translation uses
inconsistent terminology: the GameCommentary.DisabledByExclusion value contains
"外部連動" but the file already uses "外部連接模式" elsewhere; update the
DisabledByExclusion string to use the existing term "外部連接模式" so wording is
consistent across the locale (search for the
"GameCommentary.DisabledByExclusion" key and replace "外部連動" with "外部連接模式").

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Apr 26, 2026

Claude finished @tegnike's task —— View job


PR Review: Game Commentary Mode Feature

Todo List:

  • Read repository guidelines (CLAUDE.md)
  • Read core game commentary implementation files
  • Analyze code architecture and patterns
  • Review security considerations
  • Evaluate performance implications
  • Check test coverage quality
  • Provide comprehensive feedback

📋 Review Summary

This PR introduces a comprehensive AI-powered game commentary system that captures screen content and generates real-time commentary. The implementation demonstrates solid architectural patterns and thorough consideration of edge cases.

Strengths

Excellent Architecture & Design

  • Clean separation of concerns with dedicated hook (useGameCommentaryMode), singleton service (CaptureService), and proper store integration
  • Robust state management using refs to avoid stale closures and proper React patterns
  • Background scene analysis system provides contextual awareness during long commentary sessions
  • Exclusion rules integration prevents conflicts with other modes (realtime API, audio mode, etc.)
  • Ring buffer implementation for commentary history with configurable context size

Comprehensive Error Handling

  • AbortController support prevents race conditions and memory leaks
  • Graceful degradation when capture or AI generation fails
  • Proper cleanup of timers, streams, and background processes
  • Request token validation prevents stale responses

Strong Test Coverage

  • Well-structured tests for core functions: generateGameCommentary, parseCommentaryResponse, buildGameCommentaryMessages
  • Hook lifecycle testing covers timer management, state transitions, and interruption scenarios
  • Integration testing for speaker queue and concurrency handling
  • Mock implementations properly isolate units under test

⚠️ Areas for Improvement

1. Performance Optimizations

Canvas Operations - HIGH PRIORITY (captureService.ts:40-57):

// Current implementation does synchronous canvas operations on main thread
const img = new Image()
img.src = raw
// No explicit cleanup of image objects

// Consider using OffscreenCanvas for heavy operations
if (img.width <= maxWidth) return raw
ctx.drawImage(img, 0, 0, canvas.width, canvas.height) // Main thread blocking

Memory Management (useGameCommentaryMode.ts:214-220):

  • Background analyses accumulate without explicit cleanup of ImageBitmap objects
  • Ring buffer cleanup could be more aggressive for memory conservation

2. Security Considerations

✅ Generally Secure:

  • Screen capture uses proper getDisplayMedia() permissions
  • AI prompts are from controlled preset files, not user input
  • Data URI handling is safe (base64 image data only)

Minor Concern:

  • Chat message integration (useGameCommentaryMode.ts:352-358) should sanitize user content before AI prompt inclusion

3. Code Quality Improvements

Magic Numbers (useGameCommentaryMode.ts:16):

const MIN_SCHEDULED_CAPTURE_INTERVAL_SECONDS = 3 // Should be in constants file

Function Length: The triggerCommentary function is 160+ lines and could benefit from extraction:

// Consider extracting:
// - Image capture logic
// - AI generation with context building  
// - Speech synthesis and queuing

Image Object Lifecycle (captureService.ts:40-44):

// Potential memory leak - Image objects not explicitly disposed
const img = new Image()
img.src = raw
// Consider: img.onload = () => { /* process then img.src = '' */ }

🔧 Specific Technical Issues

Race Condition Handling

The request token system effectively prevents stale responses:

if (requestToken !== commentaryRequestTokenRef.current) {
  return // Properly exits on stale requests
}

Background Analysis Throttling

Good implementation prevents analysis spam during speech:

if (isBackgroundAnalysisInFlightRef.current) return
// Ensures only one analysis at a time

Stream Cleanup ✅ (capture.tsx:66-70)

Proper event listener setup for detecting browser-initiated stream termination.

📊 Performance Analysis

Capture Frequency: 3-300 second intervals with minimum 3s constraint is reasonable
Image Compression: Configurable quality (0.7 default) with resize options helps manage API costs
Context Management: Ring buffer limits prevent unbounded memory growth
Background Processing: Properly queued to avoid interference with main commentary flow

🎯 Recommendations

High Priority

  1. Optimize canvas operations - consider OffscreenCanvas or requestAnimationFrame scheduling
  2. Add explicit ImageBitmap disposal in background analysis cleanup
  3. Extract large functions into smaller, testable units

Medium Priority

  1. Enhance error recovery for network failures during AI generation
  2. Add performance monitoring for capture/generation times
  3. Consider WebP compression where supported for better efficiency

Low Priority

  1. Move magic numbers to constants file
  2. Add JSDoc examples for complex interfaces
  3. Consider strict TypeScript compliance improvements

🔍 Code Quality Score

Architecture: ⭐⭐⭐⭐⭐ (Excellent)
Error Handling: ⭐⭐⭐⭐⭐ (Comprehensive)
Performance: ⭐⭐⭐⭐☆ (Good, room for optimization)
Security: ⭐⭐⭐⭐⭐ (Well-secured)
Test Coverage: ⭐⭐⭐⭐⭐ (Thorough)
Maintainability: ⭐⭐⭐⭐☆ (Good, some refactoring beneficial)

Final Assessment

This is a well-architected feature with strong separation of concerns, comprehensive error handling, and excellent test coverage. The implementation demonstrates solid understanding of React patterns and performance considerations.

Recommendation: ✅ Approve with Minor Suggestions

The identified issues are primarily optimizations rather than blocking problems. The feature integrates cleanly with the existing codebase and follows established patterns. The thorough test coverage and robust error handling make this a production-ready implementation.


@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Apr 26, 2026

Claude finished @tegnike's task —— View job


PR Review: Game Commentary Mode Feature

Todo List:

  • Read repository guidelines (CLAUDE.md)
  • Read core game commentary implementation files
  • Analyze code architecture and patterns
  • Review security considerations
  • Evaluate performance implications
  • Check test coverage quality
  • Provide comprehensive feedback

📋 Review Summary

This PR introduces a comprehensive AI-powered game commentary system that captures screen content and generates real-time commentary. The implementation demonstrates solid architectural patterns and thorough consideration of edge cases.

Strengths

Excellent Architecture & Design

  • Clean separation of concerns with dedicated hook (useGameCommentaryMode), singleton service (CaptureService), and proper store integration
  • Robust state management using refs to avoid stale closures and proper React patterns
  • Background scene analysis system provides contextual awareness during long commentary sessions
  • Exclusion rules integration prevents conflicts with other modes (realtime API, audio mode, etc.)
  • Ring buffer implementation for commentary history with configurable context size

Comprehensive Error Handling

  • AbortController support prevents race conditions and memory leaks
  • Graceful degradation when capture or AI generation fails
  • Proper cleanup of timers, streams, and background processes
  • Request token validation prevents stale responses

Strong Test Coverage

  • Well-structured tests for core functions: generateGameCommentary, parseCommentaryResponse, buildGameCommentaryMessages
  • Hook lifecycle testing covers timer management, state transitions, and interruption scenarios
  • Integration testing for speaker queue and concurrency handling
  • Mock implementations properly isolate units under test

⚠️ Areas for Improvement

1. Performance Optimizations

Canvas Operations - HIGH PRIORITY (captureService.ts:40-57):

// Current implementation does synchronous canvas operations on main thread
const img = new Image()
img.src = raw
// No explicit cleanup of image objects

// Consider using OffscreenCanvas for heavy operations
if (img.width <= maxWidth) return raw
ctx.drawImage(img, 0, 0, canvas.width, canvas.height) // Main thread blocking

Memory Management (useGameCommentaryMode.ts:214-220):

  • Background analyses accumulate without explicit cleanup of ImageBitmap objects
  • Ring buffer cleanup could be more aggressive for memory conservation

2. Security Considerations

✅ Generally Secure:

  • Screen capture uses proper getDisplayMedia() permissions
  • AI prompts are from controlled preset files, not user input
  • Data URI handling is safe (base64 image data only)

Minor Concern:

  • Chat message integration (useGameCommentaryMode.ts:352-358) should sanitize user content before AI prompt inclusion

3. Code Quality Improvements

Magic Numbers (useGameCommentaryMode.ts:16):

const MIN_SCHEDULED_CAPTURE_INTERVAL_SECONDS = 3 // Should be in constants file

Function Length: The triggerCommentary function is 160+ lines and could benefit from extraction:

// Consider extracting:
// - Image capture logic
// - AI generation with context building  
// - Speech synthesis and queuing

Image Object Lifecycle (captureService.ts:40-44):

// Potential memory leak - Image objects not explicitly disposed
const img = new Image()
img.src = raw
// Consider: img.onload = () => { /* process then img.src = '' */ }

🔧 Specific Technical Issues

Race Condition Handling

The request token system effectively prevents stale responses:

if (requestToken !== commentaryRequestTokenRef.current) {
  return // Properly exits on stale requests
}

Background Analysis Throttling

Good implementation prevents analysis spam during speech:

if (isBackgroundAnalysisInFlightRef.current) return
// Ensures only one analysis at a time

Stream Cleanup ✅ (capture.tsx:66-70)

Proper event listener setup for detecting browser-initiated stream termination.

📊 Performance Analysis

Capture Frequency: 3-300 second intervals with minimum 3s constraint is reasonable
Image Compression: Configurable quality (0.7 default) with resize options helps manage API costs
Context Management: Ring buffer limits prevent unbounded memory growth
Background Processing: Properly queued to avoid interference with main commentary flow

🎯 Recommendations

High Priority

  1. Optimize canvas operations - consider OffscreenCanvas or requestAnimationFrame scheduling
  2. Add explicit ImageBitmap disposal in background analysis cleanup
  3. Extract large functions into smaller, testable units

Medium Priority

  1. Enhance error recovery for network failures during AI generation
  2. Add performance monitoring for capture/generation times
  3. Consider WebP compression where supported for better efficiency

Low Priority

  1. Move magic numbers to constants file
  2. Add JSDoc examples for complex interfaces
  3. Consider strict TypeScript compliance improvements

🔍 Code Quality Score

Architecture: ⭐⭐⭐⭐⭐ (Excellent)
Error Handling: ⭐⭐⭐⭐⭐ (Comprehensive)
Performance: ⭐⭐⭐⭐☆ (Good, room for optimization)
Security: ⭐⭐⭐⭐⭐ (Well-secured)
Test Coverage: ⭐⭐⭐⭐⭐ (Thorough)
Maintainability: ⭐⭐⭐⭐☆ (Good, some refactoring beneficial)

Final Assessment

This is a well-architected feature with strong separation of concerns, comprehensive error handling, and excellent test coverage. The implementation demonstrates solid understanding of React patterns and performance considerations.

Recommendation: ✅ Approve with Minor Suggestions

The identified issues are primarily optimizations rather than blocking problems. The feature integrates cleanly with the existing codebase and follows established patterns. The thorough test coverage and robust error handling make this a production-ready implementation.


@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Apr 26, 2026

Claude finished @tegnike's task —— View job


PR Review: Game Commentary Mode Feature

Todo List:

  • Read repository guidelines (CLAUDE.md)
  • Read core game commentary implementation files
  • Analyze code architecture and patterns
  • Review security considerations
  • Evaluate performance implications
  • Check test coverage quality
  • Provide comprehensive feedback

📋 Review Summary

This PR introduces a comprehensive AI-powered game commentary system that captures screen content and generates real-time commentary. The implementation demonstrates solid architectural patterns and thorough consideration of edge cases.

Strengths

Excellent Architecture & Design

  • Clean separation of concerns with dedicated hook (useGameCommentaryMode), singleton service (CaptureService), and proper store integration
  • Robust state management using refs to avoid stale closures and proper React patterns
  • Background scene analysis system provides contextual awareness during long commentary sessions
  • Exclusion rules integration prevents conflicts with other modes (realtime API, audio mode, etc.)
  • Ring buffer implementation for commentary history with configurable context size

Comprehensive Error Handling

  • AbortController support prevents race conditions and memory leaks
  • Graceful degradation when capture or AI generation fails
  • Proper cleanup of timers, streams, and background processes
  • Request token validation prevents stale responses

Strong Test Coverage

  • Well-structured tests for core functions: generateGameCommentary, parseCommentaryResponse, buildGameCommentaryMessages
  • Hook lifecycle testing covers timer management, state transitions, and interruption scenarios
  • Integration testing for speaker queue and concurrency handling
  • Mock implementations properly isolate units under test

⚠️ Areas for Improvement

1. Performance Optimizations

Canvas Operations - HIGH PRIORITY (captureService.ts:40-57):

// Current implementation does synchronous canvas operations on main thread
const img = new Image()
img.src = raw
// No explicit cleanup of image objects

// Consider using OffscreenCanvas for heavy operations
if (img.width <= maxWidth) return raw
ctx.drawImage(img, 0, 0, canvas.width, canvas.height) // Main thread blocking

Memory Management (useGameCommentaryMode.ts:214-220):

  • Background analyses accumulate without explicit cleanup of ImageBitmap objects
  • Ring buffer cleanup could be more aggressive for memory conservation

2. Security Considerations

✅ Generally Secure:

  • Screen capture uses proper getDisplayMedia() permissions
  • AI prompts are from controlled preset files, not user input
  • Data URI handling is safe (base64 image data only)

Minor Concern:

  • Chat message integration (useGameCommentaryMode.ts:352-358) should sanitize user content before AI prompt inclusion

3. Code Quality Improvements

Magic Numbers (useGameCommentaryMode.ts:16):

const MIN_SCHEDULED_CAPTURE_INTERVAL_SECONDS = 3 // Should be in constants file

Function Length: The triggerCommentary function is 160+ lines and could benefit from extraction:

// Consider extracting:
// - Image capture logic
// - AI generation with context building  
// - Speech synthesis and queuing

Image Object Lifecycle (captureService.ts:40-44):

// Potential memory leak - Image objects not explicitly disposed
const img = new Image()
img.src = raw
// Consider: img.onload = () => { /* process then img.src = '' */ }

🔧 Specific Technical Issues

Race Condition Handling

The request token system effectively prevents stale responses:

if (requestToken !== commentaryRequestTokenRef.current) {
  return // Properly exits on stale requests
}

Background Analysis Throttling

Good implementation prevents analysis spam during speech:

if (isBackgroundAnalysisInFlightRef.current) return
// Ensures only one analysis at a time

Stream Cleanup ✅ (capture.tsx:66-70)

Proper event listener setup for detecting browser-initiated stream termination.

📊 Performance Analysis

Capture Frequency: 3-300 second intervals with minimum 3s constraint is reasonable
Image Compression: Configurable quality (0.7 default) with resize options helps manage API costs
Context Management: Ring buffer limits prevent unbounded memory growth
Background Processing: Properly queued to avoid interference with main commentary flow

🎯 Recommendations

High Priority

  1. Optimize canvas operations - consider OffscreenCanvas or requestAnimationFrame scheduling
  2. Add explicit ImageBitmap disposal in background analysis cleanup
  3. Extract large functions into smaller, testable units

Medium Priority

  1. Enhance error recovery for network failures during AI generation
  2. Add performance monitoring for capture/generation times
  3. Consider WebP compression where supported for better efficiency

Low Priority

  1. Move magic numbers to constants file
  2. Add JSDoc examples for complex interfaces
  3. Consider strict TypeScript compliance improvements

🔍 Code Quality Score

Architecture: ⭐⭐⭐⭐⭐ (Excellent)
Error Handling: ⭐⭐⭐⭐⭐ (Comprehensive)
Performance: ⭐⭐⭐⭐☆ (Good, room for optimization)
Security: ⭐⭐⭐⭐⭐ (Well-secured)
Test Coverage: ⭐⭐⭐⭐⭐ (Thorough)
Maintainability: ⭐⭐⭐⭐☆ (Good, some refactoring beneficial)

Final Assessment

This is a well-architected feature with strong separation of concerns, comprehensive error handling, and excellent test coverage. The implementation demonstrates solid understanding of React patterns and performance considerations.

Recommendation: ✅ Approve with Minor Suggestions

The identified issues are primarily optimizations rather than blocking problems. The feature integrates cleanly with the existing codebase and follows established patterns. The thorough test coverage and robust error handling make this a production-ready implementation.


@tegnike tegnike merged commit ca52c17 into develop Apr 27, 2026
8 checks passed
@tegnike tegnike deleted the feature/game-commentary-mode branch April 27, 2026 07:43
@coderabbitai coderabbitai Bot mentioned this pull request Apr 27, 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