Conversation
画面キャプチャを一定間隔で自動取得し、マルチモーダル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>
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>
fix: ローカルLLM利用時のチャット履歴永続化負荷を軽減する
Feature/game commentary mode
|
Claude finished @tegnike's task —— View job 🔍 Comprehensive PR ReviewThis is a substantial and well-structured PR that introduces significant new functionality while maintaining code quality. Here's my detailed analysis: 🎯 Major Features Added✅ Game Commentary ModeExcellent implementation with proper architecture:
✅ Playwright E2E Testing SetupProfessional testing infrastructure:
✅ Agent Skills DocumentationStructured automation documentation:
🔍 Code Quality AnalysisStrengths
Security Assessment ✅
Performance Considerations
🐛 Issues FoundMinor Issues
Best Practice Suggestions
📊 Test Coverage AssessmentExcellent Coverage
Coverage Gaps (Minor)
🚀 Performance & SecurityPerformance: ⭐⭐⭐⭐⭐
Security: ⭐⭐⭐⭐⭐
Best Practices: ⭐⭐⭐⭐⭐
🎉 Overall AssessmentThis is a high-quality PR that significantly enhances the application while maintaining architectural integrity. The game commentary feature is well-implemented with proper testing, and the E2E testing infrastructure will benefit future development. Recommendations
Code Quality: ⭐⭐⭐⭐⭐ |
ウォークスルーゲーム実況機能の追加、環境変数管理UI、CI/CDの改善(Node 24.x + npm 11.6.2へのピン留め)、新しいPlaywright E2Eテストスイート、14言語にわたる翻訳更新、ドキュメント同期・更新・エンドポイント検証用のエージェントスキル、およびマルチモーダルモードの文字列ベース('never'/'always'/'ai-decide')からブール値(enableMultiModal)への大規模なリファクタリングを実装します。 変更内容
推定コードレビュー努力🎯 4 (複雑) | ⏱️ ~75分 根拠: このPRは複数の領域にまたがる実質的な新機能(ゲーム実況)、破壊的変更(マルチモーダルモードのリファクタリング)、14言語の同一パターン更新、包括的なE2Eテストスイート(400+行)、状態管理の複雑な排他ルール更新を含みます。チェンジセットは異なる関心領域を横断し、密度の高いロジック(speakCharacter、useGameCommentaryMode、generateGameCommentary)を含みます。 関連の可能性がある問題
関連の可能性がある PR
推奨ラベル
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
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 (4)
src/features/stores/home.ts (1)
326-351: 🛠️ Refactor suggestion | 🟠 MajorPromise処理を
async/awaitへ統一してください。この保存処理だけ
.then/.catchチェーンが残っており、同じ関数内の他の非同期処理(11行目のawait addEmbeddingsToMessages)とスタイルが混在しています。コーディングガイドラインに従い、try/catch + await fetchに統一することで一貫性と可読性が向上します。♻️ 提案差分
- void fetch('/api/save-chat-log', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - messages: messagesWithEmbedding, - isNewFile: shouldCreateNewFile, - }), - }) - .then((response) => { - if (response.ok) { - lastSavedLogLength = state.chatLog.length - // 新規ファイルが作成された場合はフラグをリセット - shouldCreateNewFile = false - console.log( - 'Messages saved successfully. New saved length:', - lastSavedLogLength - ) - } else { - console.error('Failed to save chat log:', response.statusText) - } - }) - .catch((error) => { - console.error('チャットログの保存中にエラーが発生しました:', error) - }) + try { + const response = await fetch('/api/save-chat-log', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + messages: messagesWithEmbedding, + isNewFile: shouldCreateNewFile, + }), + }) + + if (response.ok) { + lastSavedLogLength = state.chatLog.length + // 新規ファイルが作成された場合はフラグをリセット + shouldCreateNewFile = false + console.log( + 'Messages saved successfully. New saved length:', + lastSavedLogLength + ) + } else { + console.error('Failed to save chat log:', response.statusText) + } + } catch (error) { + console.error('チャットログの保存中にエラーが発生しました:', error) + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/stores/home.ts` around lines 326 - 351, Replace the promise .then/.catch chain for the fetch call that posts messagesWithEmbedding to '/api/save-chat-log' with an async/await + try/catch block: await the fetch call, check response.ok and on success set lastSavedLogLength = state.chatLog.length and shouldCreateNewFile = false (and log success), otherwise log the response.statusText as an error; catch and log any thrown error (preserving the existing Japanese error message). This keeps the style consistent with the earlier await addEmbeddingsToMessages call and uses the same variables (messagesWithEmbedding, shouldCreateNewFile, lastSavedLogLength, state.chatLog).src/__tests__/features/stores/home.test.ts (1)
35-50:⚠️ Potential issue | 🟡 Minor
global.fetchはdeleteではなく元の値へ復元してください。現在の後片付けだと、元々存在していた
fetchを復元できません。元の参照を保持してafterAllで戻す形にしてください。💡 提案差分
+const originalFetch = global.fetch + describe('homeStore', () => { beforeEach(() => { jest.spyOn(console, 'log').mockImplementation(() => {}) jest.spyOn(console, 'error').mockImplementation(() => {}) ;(global as typeof globalThis & { fetch: jest.Mock }).fetch = jest .fn() .mockResolvedValue({ ok: true, }) @@ afterEach(() => { jest.restoreAllMocks() - delete (global as typeof globalThis & { fetch?: jest.Mock }).fetch }) + + afterAll(() => { + if (originalFetch) { + global.fetch = originalFetch + } else { + delete (global as typeof globalThis & { fetch?: typeof fetch }).fetch + } + })Based on learnings テストファイルでグローバルオブジェクト(例:
global.fetch)を上書きする場合は、afterAll()フック内で元の値に戻して、テスト間の副作用を防ぐべきである。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/__tests__/features/stores/home.test.ts` around lines 35 - 50, Save the original global.fetch reference before you mock it and restore that reference in an afterAll hook instead of deleting it: capture originalFetch (e.g. const originalFetch = global.fetch) before the mock setup, keep the existing mock in beforeEach that sets (global as any).fetch = jest.fn(), remove the delete line in afterEach, call jest.restoreAllMocks() in afterEach as you already do, and add an afterAll that assigns global.fetch = originalFetch to fully revert the global state; refer to the existing global.fetch mock, the afterEach block, and homeStore.setState to locate where to adjust setup/teardown.src/features/chat/openAIAudioChat.ts (1)
57-80:⚠️ Potential issue | 🟠 Majorabort を正常終了に変換しないでください。
Line 58 の
breakだと、その後のflush()/close()に進むため、キャンセルが EOF 扱いになります。1 チャンクでも受信済みだと上位は成功として扱えてしまうので、ここはAbortErrorを投げて stream を error 終了にしたほうが安全です。修正例
for await (const chunk of response) { - if (options.signal?.aborted) break + if (options.signal?.aborted) { + throw new DOMException('The operation was aborted', 'AbortError') + } const audio = (chunk.choices[0]?.delta as any)?.audio if (audio) { if (audio.transcript) { controller.enqueue(audio.transcript) @@ } } + if (options.signal?.aborted) { + throw new DOMException('The operation was aborted', 'AbortError') + } await bufferManager.flush() controller.close()🤖 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 57 - 80, The current for-await loop in openAIAudioChat.ts checks options.signal?.aborted and does a plain break, which lets execution continue to await bufferManager.flush() and controller.close() and thus treats cancel as EOF; instead detect the abort (options.signal?.aborted) inside the loop and throw an AbortError (or rethrow a DOMException with name "AbortError") immediately so the stream ends with an error and flush()/close() are skipped; update the loop surrounding logic that references response, bufferManager.flush(), and controller.close() accordingly to rely on the thrown error path.src/features/stores/exclusionRules.ts (1)
150-177:⚠️ Potential issue | 🟠 Major非マルチモーダル切り替えで
gameCommentaryEnabledが残ります。ここは
enableMultiModalだけ OFF にしているため、ゲーム実況を有効のまま非対応モデルへ切り替えられます。以降の UI と実行条件がずれるので、この分岐でもgameCommentaryEnabledを落としておいたほうが整合します。
Based on learnings, When adding new mode settings with mutual exclusivity requirements, add corresponding exclusion control rules to/src/features/stores/exclusionRules.tsto maintain consistency修正例
apply: () => ({ conversationContinuityMode: false, slideMode: false, enableMultiModal: false, + gameCommentaryEnabled: false, }),🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/stores/exclusionRules.ts` around lines 150 - 177, The aiService-nonMultiModal exclusion rule (id: 'aiService-nonMultiModal') currently turns off conversationContinuityMode, slideMode and enableMultiModal but leaves gameCommentaryEnabled unchanged; update the rule's apply function to also set gameCommentaryEnabled: false so switching to a non-multimodal default model cannot leave game commentary enabled and keeps UI/behavior consistent (modify the apply return object in the aiService-nonMultiModal rule).
🟠 Major comments (23)
src/hooks/useResizable.ts-185-195 (1)
185-195:⚠️ Potential issue | 🟠 Major公開
setSizeが制約をバイパスして不正なサイズを保存できます
setSizeはmin/maxとaspectRatioを考慮せずに状態を更新するため、ドラッグ経路では防いでいる無効サイズを外部から注入できます。ここも同じ正規化ロジックを通してください。修正案(少なくとも clamp を適用)
const setSize = useCallback((nextSize: { width: number; height: number }) => { + const clamp = (value: number, min: number, max: number) => + Math.max(min, Math.min(max, value)) + + const clampedWidth = clamp(nextSize.width, minWidth, maxWidth) + const clampedHeight = clamp(nextSize.height, minHeight, maxHeight) + const normalized = { width: clampedWidth, height: clampedHeight } + setSizeState((currentSize) => { if ( - currentSize.width === nextSize.width && - currentSize.height === nextSize.height + currentSize.width === normalized.width && + currentSize.height === normalized.height ) { return currentSize } - return nextSize + return normalized }) - }, []) + }, [minWidth, minHeight, maxWidth, maxHeight])🤖 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, Public setSize bypasses the component's min/max/aspectRatio constraints by directly calling setSizeState with nextSize; update setSize in useResizable.ts to run nextSize through the same normalization/clamping logic used by the internal resize handlers (e.g., call normalizeSize or clampSize before comparing/applying), so it enforces minWidth/minHeight/maxWidth/maxHeight and aspectRatio constraints before calling setSizeState; if no helper exists, implement a small clamp+aspect-ratio normalization and use that inside setSize.src/hooks/useResizable.ts-87-134 (1)
87-134:⚠️ Potential issue | 🟠 Majorアスペクト比維持時の境界補正が最終的に制約違反を残す可能性があります
Line 125以降の再補正でmax側に合わせた後、min側を再検証していないため、最終サイズがminWidth/minHeightを下回るケースが出ます。制約を満たす幅レンジを先に確定して 1 回で決める形にしたほうが安全です。修正案(制約レンジを先に確定)
- const ratio = aspectRatioRef.current || 1 - const applyFromWidth = (candidateWidth: number) => { - const width = clamp(candidateWidth, minWidth, maxWidth) - return { width, height: width / ratio } - } - const applyFromHeight = (candidateHeight: number) => { - const height = clamp(candidateHeight, minHeight, maxHeight) - return { width: height * ratio, height } - } - - const preferHeight = direction === 'top' || direction === 'bottom' - const nextSize = preferHeight - ? applyFromHeight(newHeight) - : applyFromWidth(newWidth) - - newWidth = nextSize.width - newHeight = nextSize.height - - if (newWidth > maxWidth) { - const adjusted = applyFromWidth(maxWidth) - newWidth = adjusted.width - newHeight = adjusted.height - } - if (newHeight > maxHeight) { - const adjusted = applyFromHeight(maxHeight) - newWidth = adjusted.width - newHeight = adjusted.height - } - if (newWidth < minWidth) { - const adjusted = applyFromWidth(minWidth) - newWidth = adjusted.width - newHeight = adjusted.height - } - if (newHeight < minHeight) { - const adjusted = applyFromHeight(minHeight) - newWidth = adjusted.width - newHeight = adjusted.height - } - if (newWidth > maxWidth) { - const adjusted = applyFromWidth(maxWidth) - newWidth = adjusted.width - newHeight = adjusted.height - } - if (newHeight > maxHeight) { - const adjusted = applyFromHeight(maxHeight) - newWidth = adjusted.width - newHeight = adjusted.height - } + const ratio = aspectRatioRef.current || 1 + const preferHeight = direction === 'top' || direction === 'bottom' + const candidateWidth = preferHeight ? newHeight * ratio : newWidth + + const lowerWidth = Math.max(minWidth, minHeight * ratio) + const upperWidth = Math.min(maxWidth, maxHeight * ratio) + + if (lowerWidth <= upperWidth) { + newWidth = clamp(candidateWidth, lowerWidth, upperWidth) + newHeight = newWidth / ratio + } else { + // 制約が矛盾する場合のフォールバック + newWidth = clamp(candidateWidth, minWidth, maxWidth) + newHeight = clamp(newWidth / ratio, minHeight, maxHeight) + newWidth = clamp(newHeight * ratio, minWidth, maxWidth) + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/hooks/useResizable.ts` around lines 87 - 134, The boundary-correction loop in useResizable (the block using applyFromWidth/applyFromHeight and variables newWidth/newHeight, minWidth/maxWidth,minHeight/maxHeight and preferHeight) can leave the final size violating min constraints because you reapply max then min then max again; instead compute the valid width range once and clamp within it in a single step: determine the allowable width interval by converting minHeight/maxHeight into equivalent width bounds when preferHeight is true (using applyFromHeight) or converting minWidth/maxWidth into height bounds when preferHeight is false, then clamp the candidate width to that single [minAllowedWidth, maxAllowedWidth] range and derive height via applyFromWidth/applyFromHeight exactly once to set newWidth/newHeight. Ensure you replace the repeated if-blocks with this single-range clamping logic so final sizes always respect both min and max constraints.src/features/memory/memoryStoreSync.ts-79-92 (1)
79-92:⚠️ Potential issue | 🟠 Major
Promise.race()のタイムアウト後、基盤のsearchMemories()と埋め込み取得は中断されません。
Promise.race()で呼び出し元は先に抜けていますが、searchMemories()の処理は継続し、getEmbedding()による/api/embeddingへのfetchも完了するまで続きます。会話ごとに通る経路なので、遅いバックエンド時にタイムアウトした検索が積み上がると、無駄な API 呼び出しと IndexedDB 処理が蓄積します。
searchMemories()とgetEmbedding()にAbortSignalサポートを追加し、タイムアウト時に検索本体へ中断を伝搬できるようにしてください。例:SearchOptionsにsignal?: AbortSignalを追加し、fetch呼び出しにsignalを渡すパターン。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/memory/memoryStoreSync.ts` around lines 79 - 92, Add AbortSignal propagation so timed-out searches actually cancel underlying work: update the SearchOptions type to include signal?: AbortSignal, modify searchMemories(...) to accept that signal and wire it through any internal calls (including getEmbedding(...)) so those functions also accept a signal and pass it into their fetch calls; in memoryStoreSync.ts create an AbortController before starting searchPromise, use its signal in the search invocation, and on the Promise.race timeout path call controller.abort() (and still clearTimeout) so slow backends and getEmbedding fetches are aborted when the timeout resolves.src/features/messages/speakQueue.ts-84-96 (1)
84-96:⚠️ Potential issue | 🟠 Majorセッション停止で破棄されたタスクの
onCompleteが呼ばれていません
stopSessionで該当タスクをfilterで落とすだけだと、完了待ち側が解放されず詰まる可能性があります。clearQueue(true)と同様に、破棄時にonCompleteを呼ぶべきです。修正案
const instance = SpeakQueue.getInstance() - instance.queue = instance.queue.filter( - (task) => task.sessionId !== sessionId - ) + const remainingTasks: SpeakTask[] = [] + for (const task of instance.queue) { + if (task.sessionId === sessionId) { + task.onComplete?.() + continue + } + remainingTasks.push(task) + } + instance.queue = remainingTasks if (instance.currentSessionId !== sessionId) { return } @@ - instance.clearQueue() + instance.clearQueue(true)🤖 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 84 - 96, In stopSession, you currently drop tasks by filtering instance.queue without invoking their completion handlers, which leaks waiters; before filtering capture the removed tasks (e.g., const removed = instance.queue.filter(t => t.sessionId === sessionId)), then set instance.queue = instance.queue.filter(t => t.sessionId !== sessionId) and for each removed task call its onComplete (or equivalent completion callback) the same way clearQueue(true) does; alternatively call instance.clearQueue(true) for the removed tasks path—ensure you reference the existing symbols instance.queue, instance.clearQueue, and the stopSession/currentSessionId branch so removed tasks are completed before returning..agents/skills/verify-endpoints/SKILL.md-85-96 (1)
85-96:⚠️ Potential issue | 🟠 MajorAnthropic モデル名を修正が必要です。
テーブル内のモデル名が実際のAnthropicの APIで利用可能なものと異なっています:
- Line 86:
Codex-sonnet-4-5→ 実際のモデルはclaude-sonnet-4-6またはclaude-opus-4-7です
Codex-は存在しないプレフィックスです。Anthropicの現在のAPIモデル(2026年4月)では以下を使用してください:
claude-opus-4-7:最も高性能claude-sonnet-4-6:速度と知能のバランス(推奨)claude-haiku-4-5-20251001:最速スキル実行時にモデル名エラーが発生します。
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.agents/skills/verify-endpoints/SKILL.md around lines 85 - 96, Replace the incorrect Anthropic model name in the SKILL.md table: find the row containing the provider key `anthropic` and the model string `Codex-sonnet-4-5` and change it to a valid Anthropic model (e.g., `claude-sonnet-4-6` or `claude-opus-4-7`); prefer `claude-sonnet-4-6` as the balanced default and update any documentation or example usage that references `Codex-sonnet-4-5` to the chosen valid model name.src/components/settings/index.tsx-234-234 (1)
234-234:⚠️ Potential issue | 🟠 Majorデスクトップ/モバイルで同一
data-testidが重複しています。
settings-tab-${tab.key}が2箇所で生成されるため、テストロケータが複数一致し不安定になります。識別子を分離してください。💡 提案差分
- data-testid={`settings-tab-${tab.key}`} + data-testid={`settings-tab-${tab.key}-desktop`} @@ - data-testid={`settings-tab-${tab.key}`} + data-testid={`settings-tab-${tab.key}-mobile`}Also applies to: 296-296
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/settings/index.tsx` at line 234, The data-testid template string settings-tab-${tab.key} is generated in two places (causing duplicate test locators); change each occurrence to a distinct identifier so tests are stable—e.g., rename one to settings-tab-desktop-${tab.key} and the other to settings-tab-mobile-${tab.key} or append a mode/index suffix; update both JSX attributes that currently render data-testid={`settings-tab-${tab.key}`} to use the unique variants and ensure any tests referencing the old key are updated accordingly.tests/e2e/mobile-smoke.mobile.spec.ts-39-43 (1)
39-43:⚠️ Potential issue | 🟠 Major
evaluate(...click())ではなくlocator.click()に統一して、E2Eテストの安定性を改善してください。
evaluateでのクリックは Playwright の待機・再試行メカニズムをバイパスするため、モバイル環境でのフレークが増加します。♻️ 置き換え案
- await page - .getByRole('button', { name: /Conversation Log/ }) - .evaluate((element) => { - ;(element as HTMLElement).click() - }) + await page.getByRole('button', { name: /Conversation Log/ }).click()- await page.getByTestId('kiosk-mode-toggle').evaluate((element) => { - ;(element as HTMLElement).click() - }) + await page.getByTestId('kiosk-mode-toggle').click()🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/e2e/mobile-smoke.mobile.spec.ts` around lines 39 - 43, Replace the direct DOM click inside evaluate with Playwright's locator click to restore built-in waiting/retry: locate the call using page.getByRole('button', { name: /Conversation Log/ }) (the expression currently followed by .evaluate((element) => {(element as HTMLElement).click()})) and change it to await the locator.click() invocation so the test uses Playwright's stable click behavior on mobile.src/components/form.tsx-30-44 (1)
30-44:⚠️ Potential issue | 🟠 Major画像取得失敗時に
delayedTextが残留します。この変更だと
modalImageが来ないケースで pending text がクリアされず、次の別イベントで画像が入ったときに古い送信が発火します。キャプチャ失敗/キャンセル時の clear か、一定時間後のフォールバック送信が必要です。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/form.tsx` around lines 30 - 44, The effect leaves delayedText uncleared when an image never arrives; update the useEffect (which watches modalImage, delayedText, handleSendChat) to ensure pending text is cleared on image-failure/cancel: add logic to detect when modalImage is explicitly cleared or an image-fetch error occurs and call setDelayedText('') (or invoke a cancellation path), and/or start a fallback timeout (e.g., clear delayedText after N ms if modalImage stays falsy) so that stale delayedText cannot trigger handleSendChat later; reference the existing identifiers useEffect, modalImage, delayedText, handleSendChat, and setDelayedText when implementing the fix..agents/skills/sync-translations/SKILL.md-9-88 (1)
9-88:⚠️ Potential issue | 🟠 Majorこのスキル定義は既存の翻訳運用と衝突しています。
ja/translation.jsonを更新したら他言語も直接書き換える手順になっていて、今回のような非jaロケール差分を常態化させます。source of truth を崩すので、ここはlocales/ja/translation.jsonのみを編集対象にする運用へ寄せたほうがよいです。
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🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.agents/skills/sync-translations/SKILL.md around lines 9 - 88, The skill currently instructs synchronizing missing keys into 15 non-ja locale files (the "対象言語" table and steps 2–3), which conflicts with the desired source-of-truth policy; update .agents/skills/sync-translations/SKILL.md to restrict all operations to Japanese files only (references to locales/ja/translation.json and the rule "Applies to locales/ja/**/*.json"), remove or rewrite the "対象言語" list and any steps that add/overwrite keys in other locales, and replace the merge/translation steps with a single workflow that detects new keys in ja, adds them only under locales/ja/**/*.json (preserving order/structure/indentation) and explicitly forbids editing other locale files.src/features/gameCommentary/analyzeGameCommentaryScene.ts-18-50 (1)
18-50:⚠️ Potential issue | 🟠 Major非マルチモーダルモデルだと背景解析が毎回
nullになりますここは毎回
{ type: 'image' }を送るのに、送信前の capability check がありません。テキスト専用モデル選択時はgetAIChatResponseStream()側で失敗して、この機能が静かに無効化されます。isMultiModalModel()で事前に弾くか、ゲーム実況の背景解析を自動で無効化した方が安全です。As per coding guidelines, "
src/features/**/*.{ts,tsx}: Implement model capability checking using helper functions likeisMultiModalModel(),isReasoningModel(), andisSearchGroundingModel()from the model management system`."🤖 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 - 50, analyzeGameCommentaryScene is sending an image to getAIChatResponseStream without checking model capabilities; before building/sending the image message, obtain the current model (e.g. from your model selection getter) and use isMultiModalModel(selectedModel) to guard the multimodal path—if the model is not multimodal, short-circuit (return null or disable background analysis) or send a text-only fallback; update the code around analyzeGameCommentaryScene and the call site to perform this check so you never pass a { type: 'image' } to getAIChatResponseStream when isMultiModalModel() is false.src/features/stores/settings.ts-886-910 (1)
886-910:⚠️ Potential issue | 🟠 Major移行処理が
NEXT_PUBLIC_GAME_COMMENTARY_*の初期値を潰しています
mergePersistedSettings()はcurrentStateを先に展開しているため、旧 localStorage に存在しない新フィールドは本来そこで env/default が入ります。ここでDEFAULT_GAME_COMMENTARY_CONFIGを書き戻すと、既存ユーザーでは新しい env 初期値が無視されます。移行では legacy key の変換と deprecated key の削除だけに留める方が安全です。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/stores/settings.ts` around lines 886 - 910, The migration code is overwriting environment/default values for the new NEXT_PUBLIC_GAME_COMMENTARY_* fields by force-writing DEFAULT_GAME_COMMENTARY_CONFIG into the migrated object; instead, in mergePersistedSettings() only perform legacy key conversion and removal of deprecated keys and do not assign DEFAULT_GAME_COMMENTARY_CONFIG values back into migrated. Remove the blocks that set migrated.gameCommentaryEnabled, migrated.gameCommentaryPromptTemplate, migrated.gameCommentaryBackgroundAnalysisPromptTemplate, migrated.gameCommentaryVideoDelay, migrated.gameCommentaryBackgroundAnalysisEnabled, and migrated.gameCommentaryBackgroundAnalysisInterval from the migration logic; if you must set anything, only map legacy keys to their new names or set values when an explicit legacy value exists, but do not overwrite values that currentState (or env NEXT_PUBLIC_GAME_COMMENTARY_*) already provides.src/features/gameCommentary/captureService.ts-34-55 (1)
34-55:⚠️ Potential issue | 🟠 Major
img.src設定直後の寸法参照により、リサイズが機能していません画像の
width/heightはonloadイベント発火またはdecode()完了後のみ利用可能です。Line 43 の判定時点でimg.widthは 0 のため、常にrawが返され、maxWidthとqualityによる縮小が実質無効です。
img.decode().then(() => { /* 寸法参照と描画 */ })へ変更するか、呼び出し側で事前に canvas へのリサイズを完了させてください。🤖 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 function reads img.width/img.height immediately after setting img.src so the dimensions are 0 and resizing never runs; make the logic wait for the image to be decoded (e.g. call await img.decode() or attach img.onload and proceed in its callback) before checking img.width and drawing to canvas, update captureFrame (and its callers) to return a Promise<string|null> if you use async/await, and ensure error handling still returns raw on decode/draw failures while using the same variables (captureFrame, img, canvas, ctx) so the resize/path and quality (quality ?? 0.7) are applied correctly.src/components/capture.tsx-127-229 (1)
127-229:⚠️ Potential issue | 🟠 Major遅延描画 effect のメモリリークと async/await 違反を修正してください
現在のコードには 2 つの問題があります:
Coding guideline 違反:
createImageBitmap(offscreen).then(...)は.then()を使用していますが、**/*.{ts,tsx,js,jsx}では async/await 構文を使う必要があります。Race condition: effect の cleanup 後も
createImageBitmap()の Promise が解決し、既にクリアされたframeBufferへ bitmap を追加しようとします。このため ImageBitmap が適切に close されず、メモリリークが発生します。修正として以下を実装してください:
captureStatusを selector で読み込んで effect の起動条件に追加し、キャプチャが非アクティブな状況での不要な RAF ループを防止disposedフラグを使用して cleanup 後に解決した Promise 内で bitmap を即座に close.then()をasync/awaitで書き換え修正イメージ
+ const captureStatus = homeStore((s) => s.captureStatus) + useEffect(() => { - if (!isVideoDelayed || videoDelaySeconds <= 0) return + if (!captureStatus || !isVideoDelayed || videoDelaySeconds <= 0) return const CAPTURE_FPS = 15 const CAPTURE_INTERVAL_MS = 1000 / CAPTURE_FPS const frameBuffer: { bitmap: ImageBitmap; time: number }[] = [] const offscreen = document.createElement('canvas') const offCtx = offscreen.getContext('2d') let animId = 0 let lastCapture = 0 + let disposed = false const loop = () => { const video = videoRef.current const canvas = delayCanvasRef.current if (video && canvas && video.readyState >= 2 && offCtx) { const now = performance.now() if (now - lastCapture >= CAPTURE_INTERVAL_MS) { lastCapture = now const captureTime = now const aspect = video.videoWidth / video.videoHeight const bufW = bufferWidth const bufH = Math.round(bufferWidth / (aspect || 1)) if (offscreen.width !== bufW) offscreen.width = bufW if (offscreen.height !== bufH) offscreen.height = bufH offCtx.drawImage(video, 0, 0, bufW, bufH) - createImageBitmap(offscreen).then((bitmap) => { - frameBuffer.push({ bitmap, time: captureTime }) - - const cutoff = performance.now() - (videoDelaySeconds + 2) * 1000 - while (frameBuffer.length > 0 && frameBuffer[0].time < cutoff) { - frameBuffer[0].bitmap.close() - frameBuffer.shift() - } - }) + void (async () => { + const bitmap = await createImageBitmap(offscreen) + if (disposed) { + bitmap.close() + return + } + + frameBuffer.push({ bitmap, time: captureTime }) + + const cutoff = performance.now() - (videoDelaySeconds + 2) * 1000 + while (frameBuffer.length > 0 && frameBuffer[0].time < cutoff) { + frameBuffer[0].bitmap.close() + frameBuffer.shift() + } + })() } const targetTime = performance.now() - videoDelaySeconds * 1000 let frameToShow: ImageBitmap | null = null ... } animId = requestAnimationFrame(loop) } animId = requestAnimationFrame(loop) return () => { + disposed = true cancelAnimationFrame(animId) for (const frame of frameBuffer) { frame.bitmap.close() } frameBuffer.length = 0 currentDelayedFrameRef.current = null } - }, [isVideoDelayed, videoDelaySeconds, bufferWidth]) + }, [captureStatus, isVideoDelayed, videoDelaySeconds, bufferWidth])🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/capture.tsx` around lines 127 - 229, Effect may leak ImageBitmaps and uses .then(); update the effect to also read captureStatus from the settingsStore selector and bail out if capture is inactive, replace createImageBitmap(...).then(...) with an async/await flow inside the loop (use an async helper or make loop async) and introduce a disposed boolean that is set in the cleanup to detect when the Promise resolves after unmount; when disposed is true, immediately call bitmap.close() instead of pushing into frameBuffer. Ensure you still close all bitmaps from frameBuffer in cleanup and update references: videoRef, delayCanvasRef, frameBuffer, currentDelayedFrameRef, createImageBitmap, and the loop/animId handling.locales/ru/translation.json-96-97 (1)
96-97:⚠️ Potential issue | 🟠 Majorこのリポジトリ運用では非
jaロケールの手動追加は避けるべきです。新規翻訳キー追加時は
/locales/ja/のみ更新し、他言語は別フローで同期する前提なので、このファイルの追加分は運用ルールから外れています。Based on learnings, "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-691
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@locales/ru/translation.json` around lines 96 - 97, This change manually adds new translation keys ("CopyEnvVars" and "CopyEnvVarsInfo") to a non-Japanese locale (ru/translation.json) which violates the repo rule to only update /locales/ja/ when adding new strings; revert the additions in locales/ru/translation.json (remove the "CopyEnvVars" and "CopyEnvVarsInfo" entries) and ensure the new keys are only added to the /locales/ja/ files so that other locales are synchronized via the designated localization flow; apply the same rollback to the other ranges referenced (lines around the additional ranges) where non-ja locales were updated.locales/de/translation.json-96-97 (1)
96-97:⚠️ Potential issue | 🟠 Majorこのリポジトリ運用では非
jaロケールの手動追加は避けるべきです。新規翻訳キー追加時は
/locales/ja/のみ更新し、他言語は別フローで同期する前提なので、このファイルの追加分は運用ルールから外れています。Based on learnings, "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-691
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@locales/de/translation.json` around lines 96 - 97, The change added new German translation keys ("CopyEnvVars" and "CopyEnvVarsInfo") which violates the rule to only update Japanese locale files; remove the newly added keys from locales/de/translation.json so non-ja locales are not manually edited, and ensure the new strings exist only under /locales/ja/ (e.g., in the corresponding ja translation file); repeat this correction for the other mentioned additions (the entries referenced around lines 308, 435-436, 524, and 631-691) to keep only /locales/ja/ updated and leave other locale files untouched.locales/es/translation.json-96-97 (1)
96-97:⚠️ Potential issue | 🟠 Majorこのリポジトリ運用では非
jaロケールの手動追加は避けるべきです。新規翻訳キー追加時は
/locales/ja/のみ更新し、他言語は別フローで同期する前提なので、このファイルの追加分は運用ルールから外れています。Based on learnings, "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-691
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@locales/es/translation.json` around lines 96 - 97, Remove the manually added non-Japanese translation entries for the keys "CopyEnvVars" and "CopyEnvVarsInfo" (and the other similar additions flagged at the review) from all non-ja locale files and instead add the new keys and their translations only in the Japanese locale file (locale key definitions should be placed under the ja translations); revert the edits in locales like es/translation.json so they do not contain these new keys and ensure the canonical additions live only in the /locales/ja/ translation file where new strings are introduced.src/components/common/VideoDisplay.tsx-121-162 (1)
121-162:⚠️ Potential issue | 🟠 Major遅延背景の再描画が常時 60fps で回っています。
Line 128 以降は
delayedFrameRef.currentが更新されていなくても、全画面 canvas を毎フレーム描き直します。実況モードや kiosk の常時表示では CPU/GPU を無駄に使い続けるので、少なくとも「フレーム参照が変わった時」か「viewport サイズが変わった時」だけ描画する形に絞った方がよいです。locales/zh-TW/translation.json-96-97 (1)
96-97:⚠️ Potential issue | 🟠 Majorこのリポジトリ運用では非
jaロケールの手動追加は避けるべきです。新規翻訳キー追加時は
/locales/ja/のみ更新し、他言語は別フローで同期する前提なので、このファイルの追加分は運用ルールから外れています。Based on learnings, "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-691
🤖 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 96 - 97, The PR added new translation keys ("CopyEnvVars" and "CopyEnvVarsInfo") directly into non-Japanese locale files which violates the repo rule to update only /locales/ja/ when adding strings; revert the manual additions in locales/zh-TW/translation.json (and any other non-ja files referenced: ranges around 308, 435-436, 524, 631-691) so those files no longer contain "CopyEnvVars" / "CopyEnvVarsInfo" (or other newly added keys), and ensure the new keys are added only to /locales/ja/translation.json so the downstream translation sync flow can propagate them to other locales.src/features/gameCommentary/gameCommentaryTypes.ts-64-95 (1)
64-95:⚠️ Potential issue | 🟠 Major
NaNが clamp をすり抜けます。Line 65 以降は
NaNの比較結果がすべて false なので、入力欄を空にしたり不正値を渡したときにそのままNaNを返します。これが保存やタイマー計算に入ると、実況スケジュールや UI 表示が壊れます。Number.isFiniteを先に弾く必要があります。修正案
+function clampNumber( + value: number, + min: number, + max: number, + fallback: number +): number { + if (!Number.isFinite(value)) return fallback + return Math.min(max, Math.max(min, 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 + ) } 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 + ) } 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 + ) } 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 64 - 95, Each clamp function (clampCaptureInterval, clampContextCount, clampVideoDelay, clampBackgroundAnalysisInterval) currently lets NaN pass because comparisons with NaN are false; at the top of each function add a guard using Number.isFinite(value) and if it returns false return the corresponding GAME_COMMENTARY_* .MIN value so invalid/empty inputs produce a safe minimum rather than NaN.locales/ar/translation.json-96-97 (1)
96-97:⚠️ Potential issue | 🟠 Majorこの翻訳追加はリポジトリの運用ルールとずれています。
新規キー追加は
/locales/ja/だけを手で更新し、他言語は翻訳フローに任せる前提でした。このファイルを直接更新すると、以後の自動同期や再生成と競合しやすいです。日本語ソース側だけを更新して、このファイルの差分は外した方がよいです。Based on learnings: "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-691
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@locales/ar/translation.json` around lines 96 - 97, This change adds new translation keys (e.g., "CopyEnvVars" and "CopyEnvVarsInfo") directly into non-Japanese locale files which violates the repo rule to only edit /locales/ja/ for new strings; revert/remove these added keys from locales/ar/translation.json (and the other listed non-ja files/lines: 308, 435-436, 524, 631-691) so only the Japanese source (/locales/ja/) contains the new keys, then leave other language files to be updated by the translation sync/flow.locales/hi/translation.json-96-97 (1)
96-97:⚠️ Potential issue | 🟠 Majorこの翻訳追加はリポジトリの運用ルールとずれています。
新規キー追加は
/locales/ja/だけを手で更新し、他言語は翻訳フローに任せる前提でした。このファイルを直接更新すると、以後の自動同期や再生成と競合しやすいです。日本語ソース側だけを更新して、このファイルの差分は外した方がよいです。Based on learnings: "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-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 translation file was manually updated with new keys ("CopyEnvVars", "CopyEnvVarsInfo" and the other added keys referenced) which conflicts with the repo rule to only add new strings in Japanese; revert/remove these newly added keys from locales/hi/translation.json so the file matches the automated translation source, ensure only the Japanese source under locales/ja/ contains the new keys/strings, and leave other language files to the translation flow; update the PR to include only the ja changes and exclude the hi modifications.locales/ko/translation.json-96-97 (1)
96-97:⚠️ Potential issue | 🟠 Majorこの翻訳追加はリポジトリの運用ルールとずれています。
新規キー追加は
/locales/ja/だけを手で更新し、他言語は翻訳フローに任せる前提でした。このファイルを直接更新すると、以後の自動同期や再生成と競合しやすいです。日本語ソース側だけを更新して、このファイルの差分は外した方がよいです。Based on learnings: "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-691
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@locales/ko/translation.json` around lines 96 - 97, You added new translation keys ("CopyEnvVars" and "CopyEnvVarsInfo") directly into a non-Japanese locale, which breaks the repo rule that only /locales/ja/ should be edited for new strings; revert/remove these keys from the Korean translation file (and any other non-ja files you edited) so only the Japanese source contains the new entries, and leave other languages to the automated translation/sync flow; locate the keys by name ("CopyEnvVars", "CopyEnvVarsInfo") and remove their additions (or revert the file changes) while keeping the new keys only in the Japanese locale.src/hooks/useGameCommentaryMode.ts-373-382 (1)
373-382:⚠️ Potential issue | 🟠 Major
maxPastMessages = 0で履歴が 0 件になっていません。
slice(maxPastMessages > 0 ? -maxPastMessages : 0)は、0 のときslice(0)になって chatLog 全件を送ってしまいます。履歴を送らない設定で逆にプロンプトが最大化されるので、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 373 - 382, The current logic for recentMessages uses slice(maxPastMessages > 0 ? -maxPastMessages : 0) which causes maxPastMessages === 0 to return the entire chatLog; change it so that when settingsStore.getState().maxPastMessages is less than or equal to 0 you set recentMessages to an empty array, otherwise filter homeStore.getState().chatLog for roles 'user'/'assistant' and slice(-maxPastMessages) then map to {role, content} as before (update the recentMessages assignment to branch on maxPastMessages <= 0).
🟡 Minor comments (11)
src/utils/mediaDisplay.ts-7-13 (1)
7-13:⚠️ Potential issue | 🟡 Minor不正な上限値で 0/負のサイズを返す可能性があります
maxWidthまたはmaxHeightが 0 以下でもその値を返してしまうため、下流に無効な寸法が伝播します。ここは最小値に正規化するか、明示的にエラーに寄せるのが安全です。💡 修正案(例: 上限値不正時は最小サイズへフォールバック)
if ( - sourceWidth <= 0 || - sourceHeight <= 0 || maxWidth <= 0 || maxHeight <= 0 ) { + return { width: 1, height: 1 } + } + + if (sourceWidth <= 0 || sourceHeight <= 0) { return { width: maxWidth, height: maxHeight } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/utils/mediaDisplay.ts` around lines 7 - 13, The current guard returns raw maxWidth/maxHeight when any input is <= 0, which can propagate invalid dimensions; update the early-return in src/utils/mediaDisplay.ts (the block checking sourceWidth, sourceHeight, maxWidth, maxHeight) to normalize bad max values by falling back to a guaranteed minimum dimension (e.g., a MIN_DIMENSION constant or 1) before returning, i.e. compute normalizedWidth = max(maxWidth, MIN_DIMENSION) and normalizedHeight = max(maxHeight, MIN_DIMENSION) and return those normalized values instead of the raw maxWidth/maxHeight; ensure the chosen MIN_DIMENSION is clearly named and documented and used wherever this function can return fallback sizes.AGENTS.md-57-57 (1)
57-57:⚠️ Potential issue | 🟡 Minorコードフェンスに言語指定がありません
markdownlintの MD040 警告対象です。コードブロックに言語(textなど)を指定してください。修正案
-``` +```text src/ ├── __mocks__/ # テスト用モック(canvas, Three.js等) ... -``` +```🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@AGENTS.md` at line 57, The code block in AGENTS.md that shows the project tree is missing a language tag and triggers markdownlint MD040; update the opening fence from ``` to ```text (i.e., change the code fence that precedes "src/ ├── __mocks__/" to ```text) so the block is language-specified and linting passes, leaving the closing fence as-is.locales/ja/translation.json-448-450 (1)
448-450:⚠️ Potential issue | 🟡 Minor画像送信の説明文が実装挙動とズレています。
Line 449 の「無視される可能性があります」は、実装上は送信をブロックしてエラー表示する動作と一致していません。挙動に合わせた文言にしてください。
差分案
- "EnableMultiModalDescription": "画像送信機能を有効にします。非対応モデルの場合、画像は無視される可能性があります。", + "EnableMultiModalDescription": "画像送信機能を有効にします。非対応モデルまたは無効な設定では、画像付きメッセージは送信できません。",🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@locales/ja/translation.json` around lines 448 - 450, EnableMultiModalDescription currently says images "may be ignored", but the implementation blocks the send and shows an error; update the translation for the key "EnableMultiModalDescription" to reflect that image sending will be prevented and an error will be shown for unsupported models (and ensure the wording aligns with the existing "MultiModalNotSupported" string and the "EnableMultiModal" label).src/components/settings/presenceSettings.tsx-117-121 (1)
117-121:⚠️ Potential issue | 🟡 Minor無効化条件の追加に対して説明文言が追随していません。
Line 117 で
gameCommentaryEnabledを無効化条件に含めたため、PresenceDetectionDisabledInfoの文言もゲーム実況モードを理由として明記してください。現状だと「なぜ無効なのか」が画面表示と一致しません。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/settings/presenceSettings.tsx` around lines 117 - 121, Update the displayed explanation in PresenceDetectionDisabledInfo to include game commentary as a disabling reason: locate the PresenceDetectionDisabledInfo component or the place where its message is composed and add "ゲーム実況モード" (or the appropriate localized wording) to the list of reasons when gameCommentaryEnabled is part of the disable condition, ensuring the text matches the boolean condition that now includes gameCommentaryEnabled so the UI and logic remain consistent..github/workflows/test.yml-130-133 (1)
130-133:⚠️ Potential issue | 🟡 MinorRun E2E tests ジョブに E2E_PORT を設定してください。
通常の E2E テストジョブ (行 76-77) には
E2E_PORTが設定されていない一方で、本番 E2E テストジョブ (行 130-133) にはE2E_PORT: '3201'が設定されています。ドキュメント (tests/e2e/README.md) では通常の E2E テストでE2E_PORT=3200を使用するよう記載されているため、一貫性のために「Run E2E tests」ステップにもE2E_PORT: '3200'を追加してください。現在は Playwright の設定ファイルのデフォルト値 (3100) が使用されています。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/test.yml around lines 130 - 133, The "Run E2E tests" GitHub Actions step is missing the E2E_PORT environment variable and is falling back to the Playwright default (3100); add E2E_PORT: '3200' to the "Run E2E tests" step so it matches the documented test port (tests/e2e/README.md) and the production step uses 3201; locate the step with the name "Run E2E tests" in the workflow and add an env block containing E2E_PORT: '3200'..agents/skills/sync-translations/SKILL.md-39-41 (1)
39-41:⚠️ Potential issue | 🟡 Minorフェンス付きコードブロックに言語指定を付けてください。
MD040 が出ているので、少なくとも
textかbashを付けておくと lint を消せます。修正例
-``` +```text locales/ja/translation.json@@
-
+text
/sync-translationsAlso applies to: 84-86
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.agents/skills/sync-translations/SKILL.md around lines 39 - 41, The markdown fenced code blocks in .agents/skills/sync-translations/SKILL.md (the blocks showing "locales/ja/translation.json" and "/sync-translations") are missing a language specifier which triggers MD040; update those backtick-fenced blocks to include a language tag (e.g., use ```text or ```bash) so the linter passes, and make the same change for the other affected block referenced around lines 84-86.locales/pt/translation.json-656-656 (1)
656-656:⚠️ Potential issue | 🟡 Minor排他理由の説明文が実際の条件と一致していません
文言には
Presence DetectionとSlide Modeが含まれておらず、UI挙動と説明がズレます。実際に無効化トリガーになるモードを列挙してください。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@locales/pt/translation.json` at line 656, The translation string for the key "DisabledByExclusion" is missing two modes (Presence Detection and Slide Mode) and thus doesn't match the UI behavior; update the Portuguese value for "DisabledByExclusion" to enumerate all modes that disable game comment mode (Realtime API, Voice Mode, External Integration, Idle Mode, Presence Detection, Slide Mode), preserving the existing punctuation/style and Portuguese phrasing so the message clearly lists every disabling trigger.tests/e2e/README.md-35-35 (1)
35-35:⚠️ Potential issue | 🟡 Minorテスト件数の固定値は早期に陳腐化します
13 tests / 12 tests / 1 testのような固定数値は運用中にすぐズレるため、READMEの信頼性を落とします。--listで確認する方針だけ残し、具体件数は削るのが安全です。Also applies to: 68-68
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/e2e/README.md` at line 35, The README currently hardcodes test counts ("13 tests / 12 tests / 1 test"), which will become stale; update the README text around the Playwright guidance (references to "npx playwright test --list", "chromium project", "mobile-chromium project", and "*.mobile.spec.ts") to remove the specific numeric counts and instead instruct readers to run `npx playwright test --list` to see current totals and describe which project runs desktop vs mobile specs (i.e., "chromium runs desktop specs" and "mobile-chromium runs mobile-*.spec.ts") so the doc remains accurate without fixed numbers..agents/skills/update-docs/SKILL.md-150-157 (1)
150-157:⚠️ Potential issue | 🟡 Minorコードフェンスに言語指定がありません(MD040)
Markdown lint 警告どおり、2箇所でフェンス言語が未指定です。
差分案
-``` +```text 英語翻訳エージェントと中国語翻訳エージェントを同時に起動する。 各エージェントには以下を伝える: - 変更されたファイルの一覧 - 各ファイルの変更内容の要約 - 日本語ファイルとen/zh対応ファイルの両方を読んでから編集すること - 完了後にnpm run docs:buildでビルド検証すること-
+text
docs: v{バージョン}対応 - {主要変更の要約}(日英中){詳細な変更内容をリスト形式で}
Co-Authored-By: Codex Opus 4.6 noreply@anthropic.com
Also applies to: 198-204
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.agents/skills/update-docs/SKILL.md around lines 150 - 157, Two fenced code blocks in SKILL.md are missing language identifiers causing MD040; update both blocks (the Japanese instruction block and the docs/version block that contains "docs: v{バージョン}... Co-Authored-By...") to include a language tag (use "text") on the opening fences (e.g. change ``` to ```text) and ensure the closing fences remain, so the markdown linter no longer flags the missing fence language.src/__tests__/features/gameCommentary/generateGameCommentary.test.ts-101-119 (1)
101-119:⚠️ Potential issue | 🟡 Minor
console.errorのモック復元漏れリスクがあります途中で assertion が落ちると復元されず、後続テストに影響します。
jest.spyOn(...).mockRestore()に寄せるのが安全です。差分案
it('returns null without logging an API error when commentary generation is aborted', async () => { const abortError = new DOMException('Aborted', 'AbortError') - const originalConsoleError = console.error - console.error = jest.fn() + const consoleErrorSpy = jest + .spyOn(console, 'error') + .mockImplementation(() => {}) ;(getAIChatResponseStream as jest.Mock).mockRejectedValue(abortError) - await expect( - generateGameCommentary([], 'data:image/jpeg;base64,current', [], [], { - signal: new AbortController().signal, - }) - ).resolves.toBeNull() + try { + await expect( + generateGameCommentary([], 'data:image/jpeg;base64,current', [], [], { + signal: new AbortController().signal, + }) + ).resolves.toBeNull() - expect(console.error).not.toHaveBeenCalledWith( - 'ゲーム実況コメント生成エラー:', - abortError - ) - - console.error = originalConsoleError + expect(console.error).not.toHaveBeenCalledWith( + 'ゲーム実況コメント生成エラー:', + abortError + ) + } finally { + consoleErrorSpy.mockRestore() + } })🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/__tests__/features/gameCommentary/generateGameCommentary.test.ts` around lines 101 - 119, Replace the manual console.error save/restore with a jest.spyOn so the mock is always cleaned up even if assertions fail: in the test that mocks getAIChatResponseStream and asserts generateGameCommentary returns null on AbortError, use jest.spyOn(console, 'error').mockImplementation(...) and ensure you call mockRestore() (or restore in a finally/afterEach) to revert the spy; keep references to the test's existing symbols generateGameCommentary and getAIChatResponseStream so the mock targets the same behavior.tests/e2e/kiosk-mode.spec.ts-100-101 (1)
100-101:⚠️ Potential issue | 🟡 Minor固定 300ms 待ちだと送信抑止の検証が甘くなります。
ここは「300ms 以内に飛ばなかった」だけを見ているので、送信処理が少し遅れただけでもテストが通ります。
/api/ai/*を route で捕捉して失敗させるか、送信完了側の UI を待ったうえでリクエスト数を確認しないと偽陽性になります。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/e2e/kiosk-mode.spec.ts` around lines 100 - 101, The test currently uses a fixed 300ms sleep and then checks aiRequests.length, which allows slow sends to pass; replace the brittle await page.waitForTimeout(300) with a deterministic interception or UI-driven check: either register a route handler for "/api/ai/*" via page.route to fail/spy on requests (so you can assert no requests were made), or use page.waitForRequest / page.waitForResponse with a short timeout to assert that no matching request occurs, or wait for the specific "send-complete" UI state before asserting aiRequests.length is 0; remove the fixed timeout and update assertions around aiRequests (and any test helpers that populate aiRequests) to rely on these deterministic signals instead of sleep.
|
Claude finished @tegnike's task —— View job 🔍 Comprehensive PR ReviewI've completed a thorough analysis of this substantial PR. Here's my detailed review focusing on code quality, security, performance, and test coverage. Review Checklist:
🎯 Major Features Assessment✅ Game Commentary Mode ImplementationExcellent overall implementation with proper architecture:
✅ Playwright E2E Testing InfrastructureProfessional testing setup:
✅ Node.js 24.x + npm 11.6.2+ Toolchain UpdateAppropriate modernization following repository standards 🚨 Critical Issues Found1. Locale Translation Policy Violation 🔴High Priority: The PR manually updates 15 non-Japanese locale files, violating the repository's translation policy:
Files affected: All locale files except Impact: This conflicts with the automated translation workflow and creates maintenance burden. Recommendation: Revert all non-Japanese locale changes and keep only the Japanese source updates. 2. Game Commentary NaN Validation Issue 🟠Location: Issue: Clamp functions don't handle 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 // NaN passes through because NaN comparisons are false
}Impact: Invalid form inputs can propagate Fix: Add 3. Memory Leak in Video Delay Feature 🟠Location: Issues:
Impact: Accumulating ImageBitmaps can cause significant memory usage in game commentary mode.
|
There was a problem hiding this comment.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
locales/ko/translation.json (1)
96-691:⚠️ Potential issue | 🟠 Major非日本語ロケールの手動追加は運用方針と不整合です。
このPRでは
locales/ko/translation.jsonに新規キーを直接追加しています。運用が「新規翻訳キー追加時は/locales/ja/のみ更新」の前提なら、翻訳ソースの一貫性が崩れるため、追加元を/locales/ja/に寄せて既存の同期フローで反映する形にしてください。Based on learnings: Update only Japanese language files (
/locales/ja/) when adding new translation strings; do not manually update other language files🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@locales/ko/translation.json` around lines 96 - 691, You added many new translation keys (e.g., "CopyEnvVars", "CopyEnvVarsInfo", "ChangeBackgroundImage", etc.) directly into the Korean locale file, which breaks the project's translation workflow; remove these new keys from the Korean file and instead add them to the Japanese source locale (the ja translation file) so they enter the canonical sync pipeline, then run the project's localization sync/export process to propagate translations to other locales rather than manually editing the ko translation file.
🧹 Nitpick comments (4)
.github/workflows/test.yml (2)
25-33: toolchain セットアップの重複を composite action または reusable workflow で共通化することを検討してください。
Setup npmとVerify toolchainステップがtest、e2e、e2e-productionの 3 つの job で同一のコードとして重複しています。特にVerify toolchainのインライン Node.js スクリプトは複雑で、変更時に 3 箇所を同期する必要があります。将来的に composite action または reusable workflow に抽出することで、メンテナンス性が向上します。
📝 Composite action の例
.github/actions/setup-toolchain/action.yml:name: 'Setup Toolchain' description: 'Setup Node.js 24.x and npm 11.6.2' runs: using: 'composite' steps: - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '24.x' cache: 'npm' - name: Setup npm shell: bash run: npm install -g npm@^11.6.2 - name: Verify toolchain shell: bash run: | node --version npm --version node -e "const { execSync } = require('child_process'); const nodeMajor = Number(process.versions.node.split('.')[0]); const npmVersion = execSync('npm --version', { encoding: 'utf8' }).trim().split('.').map(Number); if (nodeMajor !== 24 || npmVersion[0] !== 11 || npmVersion[1] < 6 || (npmVersion[1] === 6 && npmVersion[2] < 2)) process.exit(1);"各 job での使用:
- uses: ./.github/actions/setup-toolchainAlso applies to: 58-66, 114-122
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/test.yml around lines 25 - 33, Extract the duplicated "Setup npm" and "Verify toolchain" steps (the inline Node.js verification logic) used in the test, e2e, and e2e-production jobs into a single reusable unit (either a composite action or a reusable workflow) and replace each in .github/workflows/test.yml with a single call; specifically move the npm install -g npm@^11.6.2 step and the node -e verification script into the new composite action/reusable workflow, expose any inputs if needed, and update the three jobs to use the new unit so the complex verification logic in the "Verify toolchain" step is maintained in one place.
43-98: E2E job にtimeout-minutesの設定を推奨します。E2E テストは実行時間が長くなりがちで、何らかの問題でハングした場合に CI リソースを長時間占有する可能性があります。明示的なタイムアウトを設定することで、予期しない長時間実行を防げます。
📝 修正案
e2e: runs-on: ubuntu-latest + timeout-minutes: 30 steps:e2e-production: runs-on: ubuntu-latest + timeout-minutes: 30 steps:Also applies to: 99-153
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/test.yml around lines 43 - 98, Add an explicit job timeout for the e2e workflow: in the job named "e2e" (the block starting with "e2e:" that contains steps like "Install dependencies", "Run E2E tests", etc.) add the GitHub Actions key timeout-minutes: <minutes> (e.g., 60) at the same indentation as "runs-on" to limit how long the job can run; also apply the same timeout-minutes setting to the other E2E job(s) referenced elsewhere in this workflow (the other block that runs Playwright tests/listing) so both places use a bounded timeout.tests/e2e/README.md (1)
132-144: CI 運用セクションの実行順序が実際の workflow と異なります。このセクションでは
npm run test:e2eからnpm run test:e2e:productionまでを一連の流れとして記載していますが、実際の.github/workflows/test.ymlではe2ejob とe2e-productionjob は別の job として並列実行されます。ドキュメントの読者が CI の実際の動作を誤解しないよう、job が分離されていることを明記することを検討してください。
📝 修正案
CI では次の順で実行します。 + +**`e2e` job:** ```bash npm install -g npm@^11.6.2 node --version npm --version npm ci npm run test:e2e:install:with-deps npm run test:e2e -- --list npm run test:e2e +``` + +**`e2e-production` job (並列実行):** + +```bash +npm install -g npm@^11.6.2 +node --version +npm --version +npm ci +npm run test:e2e:install:with-deps npm run test:e2e:production -- --list E2E_PORT=3201 npm run test:e2e:production</details> <details> <summary>🤖 Prompt for AI Agents</summary>Verify each finding against the current code and only fix it if needed.
In
@tests/e2e/README.mdaround lines 132 - 144, Update the CI section to reflect
that the "e2e" and "e2e-production" jobs run as separate, parallel jobs in the
workflow: split the single command sequence into two labeled subsections ("e2e"
and "e2e-production (parallel)") and move the production-specific commands (npm
run test:e2e:production -- --list and E2E_PORT=3201 npm run test:e2e:production)
into the production subsection; reference the job names e2e and e2e-production
and the workflow .github/workflows/test.yml so readers understand this mirrors
the actual workflow job separation.</details> </blockquote></details> <details> <summary>tests/e2e/kiosk-mode.spec.ts (1)</summary><blockquote> `22-25`: **`evaluate(...click())` ではなく `locator.click()` を使ってください** Line 23-25 は DOM の `click()` を直接叩くため、Playwright の待機・操作性チェックを迂回し、E2E の偽陽性につながりやすいです。 <details> <summary>修正案</summary> ```diff async function clickElement(page: Page, testId: string) { - await page.getByTestId(testId).evaluate((element) => { - ;(element as HTMLElement).click() - }) + await page.getByTestId(testId).click({ force: true }) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/e2e/kiosk-mode.spec.ts` around lines 22 - 25, The helper clickElement uses page.getByTestId(...).evaluate(...) to call DOM click(), which bypasses Playwright's built-in waits and can cause flaky E2E results; change it to use Playwright's locator click API instead—replace the evaluate call with page.getByTestId(testId).click() (or assign to a locator and call locator.click()) inside the clickElement function so Playwright's waiting and actionability checks run correctly.
🤖 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`:
- Around line 96-97: Don't edit non-Japanese locale files directly; revert the
changes to the German file (the keys "CopyEnvVars" and "CopyEnvVarsInfo") and
instead add the new translation keys and Japanese source strings to the Japanese
locale under the same key names in /locales/ja/ (e.g., add "CopyEnvVars" and
"CopyEnvVarsInfo" entries to the ja file), then run the repository's
localization sync process to propagate translations to other locales; apply the
same fix for the other affected ranges (around the keys referenced for lines
308, 435-436, 524, 533, and 631-691) so only ja is manually updated and non-ja
files are kept in sync automatically.
In `@locales/en/translation.json`:
- Around line 96-97: Revert the manual edits to locales/en/translation.json for
the keys "CopyEnvVars" and "CopyEnvVarsInfo" (and any other changes in the
ranges referenced: 308, 435-436, 524, 533, 631-691), then add the new
translation keys only to locales/ja/translation.json (or submit them to the
sync-translations flow) so the canonical Japanese file contains the new entries
and the automated sync will propagate them to other locales; locate the keys by
name ("CopyEnvVars", "CopyEnvVarsInfo") and ensure no direct edits remain in
non-ja locale files.
In `@locales/hi/translation.json`:
- Around line 96-97: The new localization keys "CopyEnvVars" and
"CopyEnvVarsInfo" were added directly into the Hindi locale which breaks the
master-Japanese sync flow; remove these keys from locales/hi/translation.json
(and any other non-ja files listed: the ranges noted) and instead add/update
them only in the Japanese master file (locales/ja/translation.json), then run
the project's locale sync/update script or workflow so translations propagate
automatically to other languages; ensure you reference the exact keys
"CopyEnvVars" and "CopyEnvVarsInfo" when updating the ja file so the sync picks
them up.
In `@locales/zh-TW/translation.json`:
- Around line 96-97: The zh-TW locale file contains manual additions for keys
"CopyEnvVars" and "CopyEnvVarsInfo" (and other ranges noted) which violates the
rule to only update Japanese source files; revert these manual edits in
locales/zh-TW/translation.json and instead add/update the corresponding keys in
the source Japanese locale under /locales/ja/ (using the exact key names
"CopyEnvVars" and "CopyEnvVarsInfo"), then run the project's i18n
synchronization tool/process to propagate translations to other language files
so the zh-TW file is generated automatically rather than edited by hand.
In `@src/hooks/useGameCommentaryMode.ts`:
- Around line 350-361: The await call to captureService.captureFrame is outside
any try/catch so if it rejects the loop stays stuck with isProcessingRef.current
true and state left as 'capturing'; wrap the captureFrame call (both at the
shown block and the similar block around lines 472-483) in a try/catch, and in
the catch ensure you reset isProcessingRef.current = false, call
setState('waiting') and scheduleNext() before returning (or handling the error),
so failures during capture always clean up and reschedule the loop; keep
references to captureService.captureFrame, isProcessingRef.current, setState,
and scheduleNext when making the change.
- Around line 345-389: After captureFrame returns, validate that the request is
still current and running before calling generateGameCommentary: compare
commentaryRequestTokenRef.current to the local requestToken and ensure
isRunningRef.current is true and stateRef.current is still 'capturing' (or not
'stopped'); if any check fails, set isProcessingRef.current = false, clear
commentaryAbortControllerRef.current, call scheduleNext() (or
setState('waiting')) and return early to avoid calling generateGameCommentary
and wasting API cost; apply this check immediately after imageData is obtained
and before creating the AbortController / calling generateGameCommentary so
stopCommentary() advancement of commentaryRequestTokenRef prevents stale
processing.
- Around line 548-551: アンマウント時の cleanup がタイマーのみを止めており進行中の実況生成や SpeakQueue
セッションが残るため、現在の戻り値関数(現在は clearTimers() と clearBackgroundAnalysisTimer() を呼ぶ箇所)に
invalidateActiveCommentary() を呼んで実況の状態を無効化し、さらに
SpeakQueue.stopSession(sessionIdRef.current)
を呼び出して現在のセッションを明示的に停止する処理を追加してください。これにより後続の完了コールバック経由での
setState()/scheduleNext() 実行経路を遮断できます(参照シンボル: invalidateActiveCommentary,
SpeakQueue.stopSession, sessionIdRef.current, clearTimers,
clearBackgroundAnalysisTimer)。
In `@tests/e2e/kiosk-mode.spec.ts`:
- Around line 89-102: The test currently checks aiRequests immediately with
expect(aiRequests).toHaveLength(0), which can miss delayed network calls;
replace this synchronous check with an explicit wait-for-no-request using
Playwright's waitForRequest on the page (targeting paths starting with
'/api/ai/') with a short timeout and assert that it times out (or catch the
timeout and treat it as success), or alternatively use page.waitForRequest in a
try/catch to fail if any matching request arrives within the timeout; update the
block around page.on('request', ...) and the final assertion to use
page.waitForRequest (or equivalent timeout-based logic) to deterministically
confirm no AI request was sent.
---
Outside diff comments:
In `@locales/ko/translation.json`:
- Around line 96-691: You added many new translation keys (e.g., "CopyEnvVars",
"CopyEnvVarsInfo", "ChangeBackgroundImage", etc.) directly into the Korean
locale file, which breaks the project's translation workflow; remove these new
keys from the Korean file and instead add them to the Japanese source locale
(the ja translation file) so they enter the canonical sync pipeline, then run
the project's localization sync/export process to propagate translations to
other locales rather than manually editing the ko translation file.
---
Nitpick comments:
In @.github/workflows/test.yml:
- Around line 25-33: Extract the duplicated "Setup npm" and "Verify toolchain"
steps (the inline Node.js verification logic) used in the test, e2e, and
e2e-production jobs into a single reusable unit (either a composite action or a
reusable workflow) and replace each in .github/workflows/test.yml with a single
call; specifically move the npm install -g npm@^11.6.2 step and the node -e
verification script into the new composite action/reusable workflow, expose any
inputs if needed, and update the three jobs to use the new unit so the complex
verification logic in the "Verify toolchain" step is maintained in one place.
- Around line 43-98: Add an explicit job timeout for the e2e workflow: in the
job named "e2e" (the block starting with "e2e:" that contains steps like
"Install dependencies", "Run E2E tests", etc.) add the GitHub Actions key
timeout-minutes: <minutes> (e.g., 60) at the same indentation as "runs-on" to
limit how long the job can run; also apply the same timeout-minutes setting to
the other E2E job(s) referenced elsewhere in this workflow (the other block that
runs Playwright tests/listing) so both places use a bounded timeout.
In `@tests/e2e/kiosk-mode.spec.ts`:
- Around line 22-25: The helper clickElement uses
page.getByTestId(...).evaluate(...) to call DOM click(), which bypasses
Playwright's built-in waits and can cause flaky E2E results; change it to use
Playwright's locator click API instead—replace the evaluate call with
page.getByTestId(testId).click() (or assign to a locator and call
locator.click()) inside the clickElement function so Playwright's waiting and
actionability checks run correctly.
In `@tests/e2e/README.md`:
- Around line 132-144: Update the CI section to reflect that the "e2e" and
"e2e-production" jobs run as separate, parallel jobs in the workflow: split the
single command sequence into two labeled subsections ("e2e" and "e2e-production
(parallel)") and move the production-specific commands (npm run
test:e2e:production -- --list and E2E_PORT=3201 npm run test:e2e:production)
into the production subsection; reference the job names e2e and e2e-production
and the workflow .github/workflows/test.yml so readers understand this mirrors
the actual workflow job separation.
🪄 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: d2bc4609-7258-4050-9871-d041961df6aa
📒 Files selected for processing (25)
.agents/skills/sync-translations/SKILL.md.agents/skills/update-docs/SKILL.md.github/workflows/test.ymllocales/ar/translation.jsonlocales/de/translation.jsonlocales/en/translation.jsonlocales/es/translation.jsonlocales/fr/translation.jsonlocales/hi/translation.jsonlocales/it/translation.jsonlocales/ja/translation.jsonlocales/ko/translation.jsonlocales/pl/translation.jsonlocales/pt/translation.jsonlocales/ru/translation.jsonlocales/th/translation.jsonlocales/vi/translation.jsonlocales/zh-CN/translation.jsonlocales/zh-TW/translation.jsonsrc/__tests__/features/gameCommentary/generateGameCommentary.test.tssrc/features/gameCommentary/captureService.tssrc/hooks/useGameCommentaryMode.tstests/e2e/README.mdtests/e2e/helpers/app.tstests/e2e/kiosk-mode.spec.ts
✅ Files skipped from review due to trivial changes (1)
- .agents/skills/update-docs/SKILL.md
🚧 Files skipped from review as they are similar to previous changes (11)
- locales/fr/translation.json
- locales/zh-CN/translation.json
- tests/e2e/helpers/app.ts
- locales/it/translation.json
- locales/th/translation.json
- locales/vi/translation.json
- src/features/gameCommentary/captureService.ts
- locales/pt/translation.json
- locales/ar/translation.json
- locales/ru/translation.json
- locales/es/translation.json
| "CopyEnvVars": "Umgebungsvariablen kopieren", | ||
| "CopyEnvVarsInfo": "Kopiert die aktuelle Charakterposition im Format von Umgebungsvariablen in die Zwischenablage. Sie können sie in Ihre .env-Datei einfügen.", |
There was a problem hiding this comment.
他言語翻訳の直接編集は避けたいです。
このリポジトリの運用だと、新規翻訳キーは日本語ファイルを起点に同期する前提です。de をここで直接更新すると、以後の同期と差分管理が壊れやすいので、追加分は ja 側だけを手動更新して同期処理に任せるほうが安全です。
As per coding guidelines "Update language files only in Japanese (/locales/ja/); do not manually update other language files".
Also applies to: 308-308, 435-436, 524-524, 533-533, 631-691
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@locales/de/translation.json` around lines 96 - 97, Don't edit non-Japanese
locale files directly; revert the changes to the German file (the keys
"CopyEnvVars" and "CopyEnvVarsInfo") and instead add the new translation keys
and Japanese source strings to the Japanese locale under the same key names in
/locales/ja/ (e.g., add "CopyEnvVars" and "CopyEnvVarsInfo" entries to the ja
file), then run the repository's localization sync process to propagate
translations to other locales; apply the same fix for the other affected ranges
(around the keys referenced for lines 308, 435-436, 524, 533, and 631-691) so
only ja is manually updated and non-ja files are kept in sync automatically.
| "CopyEnvVars": "Copy environment variables", | ||
| "CopyEnvVarsInfo": "Copy the current character position to the clipboard in environment variable format. You can paste it into your .env file.", |
There was a problem hiding this comment.
他言語ロケールは手動更新しないでください。
このリポジトリでは新しい翻訳キーは locales/ja/translation.json を更新し、他言語は同期フローで反映する前提です。このファイルを直接編集すると、次回同期時に差分競合や訳語ずれの温床になります。今回の追加分は sync-translations 側へ寄せる形にしたいです。
As per coding guidelines "Update language files only in Japanese (/locales/ja/); do not manually update other language files".
Also applies to: 308-308, 435-436, 524-524, 533-533, 631-691
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@locales/en/translation.json` around lines 96 - 97, Revert the manual edits to
locales/en/translation.json for the keys "CopyEnvVars" and "CopyEnvVarsInfo"
(and any other changes in the ranges referenced: 308, 435-436, 524, 533,
631-691), then add the new translation keys only to locales/ja/translation.json
(or submit them to the sync-translations flow) so the canonical Japanese file
contains the new entries and the automated sync will propagate them to other
locales; locate the keys by name ("CopyEnvVars", "CopyEnvVarsInfo") and ensure
no direct edits remain in non-ja locale files.
| "CopyEnvVars": "पर्यावरण चर कॉपी करें", | ||
| "CopyEnvVarsInfo": "वर्तमान कैरेक्टर स्थिति को पर्यावरण चर प्रारूप में क्लिपबोर्ड पर कॉपी करता है। आप इसे .env फ़ाइल में पेस्ट कर सकते हैं।", |
There was a problem hiding this comment.
このロケール追加は手動ではなく同期フローに寄せてください。
新規キーを locales/hi/translation.json に直接追加すると、ja をマスターにした同期運用とずれてメンテが難しくなります。ここは locales/ja/translation.json のみを手動更新し、他言語は同期スキルで反映する形に揃えたいです。
As per coding guidelines "Update language files only in Japanese (/locales/ja/); do not manually update other language files".
Also applies to: 308-308, 435-436, 524-524, 533-533, 631-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 new localization keys
"CopyEnvVars" and "CopyEnvVarsInfo" were added directly into the Hindi locale
which breaks the master-Japanese sync flow; remove these keys from
locales/hi/translation.json (and any other non-ja files listed: the ranges
noted) and instead add/update them only in the Japanese master file
(locales/ja/translation.json), then run the project's locale sync/update script
or workflow so translations propagate automatically to other languages; ensure
you reference the exact keys "CopyEnvVars" and "CopyEnvVarsInfo" when updating
the ja file so the sync picks them up.
| "CopyEnvVars": "複製環境變數", | ||
| "CopyEnvVarsInfo": "將目前角色位置以環境變數格式複製到剪貼簿。可貼到 .env 檔案中使用。", |
There was a problem hiding this comment.
このファイルも同期生成に寄せてください。
zh-TW への直接追加は、ja をソース・オブ・トゥルースにする翻訳運用と噛み合っていません。手動更新は日本語だけに留めて、他言語は同期ツール経由に統一したいです。
As per coding guidelines "Update language files only in Japanese (/locales/ja/); do not manually update other language files".
Also applies to: 308-308, 435-436, 524-524, 533-533, 631-691
🤖 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 96 - 97, The zh-TW locale file
contains manual additions for keys "CopyEnvVars" and "CopyEnvVarsInfo" (and
other ranges noted) which violates the rule to only update Japanese source
files; revert these manual edits in locales/zh-TW/translation.json and instead
add/update the corresponding keys in the source Japanese locale under
/locales/ja/ (using the exact key names "CopyEnvVars" and "CopyEnvVarsInfo"),
then run the project's i18n synchronization tool/process to propagate
translations to other language files so the zh-TW file is generated
automatically rather than edited by hand.
| isProcessingRef.current = true | ||
| setState('capturing') | ||
| const requestToken = commentaryRequestTokenRef.current + 1 | ||
| commentaryRequestTokenRef.current = requestToken | ||
|
|
||
| // キャプチャ取得 | ||
| const imageData = await captureService.captureFrame( | ||
| gameCommentaryResizeWidth, | ||
| gameCommentaryImageQuality | ||
| ) | ||
|
|
||
| if (!imageData) { | ||
| console.warn('ゲーム実況: キャプチャ取得失敗') | ||
| isProcessingRef.current = false | ||
| setState('waiting') | ||
| scheduleNext() | ||
| return | ||
| } | ||
|
|
||
| // AI実況コメント生成 | ||
| const abortController = new AbortController() | ||
| commentaryAbortControllerRef.current = abortController | ||
|
|
||
| try { | ||
| const backgroundSceneAnalyses = backgroundSceneAnalysesRef.current | ||
| resetBackgroundSceneAnalyses() | ||
|
|
||
| // chatLogから直近メッセージを取得(視聴者コメントとの文脈共有) | ||
| const maxPastMessages = settingsStore.getState().maxPastMessages | ||
| const chatLog = homeStore.getState().chatLog | ||
| 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 result = await generateGameCommentary( | ||
| commentaryHistoryRef.current, | ||
| imageData, | ||
| recentMessages, | ||
| backgroundSceneAnalyses, | ||
| { signal: abortController.signal } | ||
| ) |
There was a problem hiding this comment.
停止後の古いキャプチャ結果で AI 呼び出しが続行されます。
stopCommentary() はトークンを進めますが、captureFrame() 完了直後にそのトークンを再確認していないため、キャプチャ中に停止しても generateGameCommentary() までは進んでしまいます。発話は抑止できても API コストは発生するので、imageData 取得直後に requestToken / isRunningRef / stateRef を再チェックして早期 return したいです。
💡 修正イメージ
const imageData = await captureService.captureFrame(
gameCommentaryResizeWidth,
gameCommentaryImageQuality
)
+
+ if (
+ requestToken !== commentaryRequestTokenRef.current ||
+ !isRunningRef.current ||
+ stateRef.current !== 'capturing'
+ ) {
+ isProcessingRef.current = false
+ return
+ }
if (!imageData) {
console.warn('ゲーム実況: キャプチャ取得失敗')
isProcessingRef.current = false🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/hooks/useGameCommentaryMode.ts` around lines 345 - 389, After
captureFrame returns, validate that the request is still current and running
before calling generateGameCommentary: compare commentaryRequestTokenRef.current
to the local requestToken and ensure isRunningRef.current is true and
stateRef.current is still 'capturing' (or not 'stopped'); if any check fails,
set isProcessingRef.current = false, clear commentaryAbortControllerRef.current,
call scheduleNext() (or setState('waiting')) and return early to avoid calling
generateGameCommentary and wasting API cost; apply this check immediately after
imageData is obtained and before creating the AbortController / calling
generateGameCommentary so stopCommentary() advancement of
commentaryRequestTokenRef prevents stale processing.
| // キャプチャ取得 | ||
| const imageData = await captureService.captureFrame( | ||
| gameCommentaryResizeWidth, | ||
| gameCommentaryImageQuality | ||
| ) | ||
|
|
||
| if (!imageData) { | ||
| console.warn('ゲーム実況: キャプチャ取得失敗') | ||
| isProcessingRef.current = false | ||
| setState('waiting') | ||
| scheduleNext() | ||
| return |
There was a problem hiding this comment.
captureFrame() の例外で実況ループが固まります。
captureFrame() は try の外で await されているので、ここが reject すると isProcessingRef.current が true のまま残り、state も capturing から戻りません。以後の再スケジュールも止まるので、キャプチャ取得も含めて例外処理の内側に入れる必要があります。
Also applies to: 472-483
🤖 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 - 361, The await call to
captureService.captureFrame is outside any try/catch so if it rejects the loop
stays stuck with isProcessingRef.current true and state left as 'capturing';
wrap the captureFrame call (both at the shown block and the similar block around
lines 472-483) in a try/catch, and in the catch ensure you reset
isProcessingRef.current = false, call setState('waiting') and scheduleNext()
before returning (or handling the error), so failures during capture always
clean up and reschedule the loop; keep references to
captureService.captureFrame, isProcessingRef.current, setState, and scheduleNext
when making the change.
| return () => { | ||
| clearTimers() | ||
| clearBackgroundAnalysisTimer() | ||
| } |
There was a problem hiding this comment.
アンマウント cleanup で進行中の実況を止めきれていません。
cleanup ではタイマーしか止めていないので、アンマウント時に進行中の生成や SpeakQueue セッションが残ります。完了コールバックから後で setState / scheduleNext() が走る経路も残るため、ここでも invalidateActiveCommentary() と SpeakQueue.stopSession(sessionIdRef.current) まで実行したほうが安全です。
💡 修正イメージ
return () => {
clearTimers()
clearBackgroundAnalysisTimer()
+ invalidateActiveCommentary()
+ SpeakQueue.stopSession(sessionIdRef.current)
+ sessionIdRef.current = null
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/hooks/useGameCommentaryMode.ts` around lines 548 - 551, アンマウント時の cleanup
がタイマーのみを止めており進行中の実況生成や SpeakQueue セッションが残るため、現在の戻り値関数(現在は clearTimers() と
clearBackgroundAnalysisTimer() を呼ぶ箇所)に invalidateActiveCommentary()
を呼んで実況の状態を無効化し、さらに SpeakQueue.stopSession(sessionIdRef.current)
を呼び出して現在のセッションを明示的に停止する処理を追加してください。これにより後続の完了コールバック経由での
setState()/scheduleNext() 実行経路を遮断できます(参照シンボル: invalidateActiveCommentary,
SpeakQueue.stopSession, sessionIdRef.current, clearTimers,
clearBackgroundAnalysisTimer)。
| const aiRequests: string[] = [] | ||
| page.on('request', (request) => { | ||
| const url = new URL(request.url()) | ||
| if (url.pathname.startsWith('/api/ai/')) { | ||
| aiRequests.push(url.pathname) | ||
| } | ||
| }) | ||
|
|
||
| await messageInput.fill('this contains blocked') | ||
| await page.getByTestId('chat-send-button').click({ force: true }) | ||
|
|
||
| await expect(page.getByText('不適切な内容が含まれています')).toBeVisible() | ||
| expect(aiRequests).toHaveLength(0) | ||
| }) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, check if the test file exists
fd 'kiosk-mode.spec.ts' --type fRepository: tegnike/aituber-kit
Length of output: 92
🏁 Script executed:
# Read the test file around lines 89-102 to verify the code snippet
head -110 tests/e2e/kiosk-mode.spec.ts | tail -n +85Repository: tegnike/aituber-kit
Length of output: 810
🏁 Script executed:
# Get more context around this test to understand what it's testing
sed -n '75,115p' tests/e2e/kiosk-mode.spec.tsRepository: tegnike/aituber-kit
Length of output: 1379
🏁 Script executed:
# Check for similar request listening patterns in other e2e tests
rg 'page\.on\(.*request' tests/e2e/ -A 3Repository: tegnike/aituber-kit
Length of output: 529
🏁 Script executed:
# Check if waitForRequest is used elsewhere in the codebase
rg 'waitForRequest' tests/ --type ts --type tsxRepository: tegnike/aituber-kit
Length of output: 91
🏁 Script executed:
# Get more context on the test to understand the content filtering mechanism
sed -n '60,110p' tests/e2e/kiosk-mode.spec.tsRepository: tegnike/aituber-kit
Length of output: 1886
🏁 Script executed:
# Check Playwright documentation and common patterns for negative request assertions
# Search for other test patterns that might check for absence of requests
rg 'expect.*request' tests/e2e/ -A 2 -B 2Repository: tegnike/aituber-kit
Length of output: 45
🏁 Script executed:
# Verify the content filtering logic - check what triggers the error message
rg '不適切な内容が含まれています' src/ --type ts --type js -B 5 -A 2Repository: tegnike/aituber-kit
Length of output: 1093
🏁 Script executed:
# Check if the validation prevents the request from being made in the first place
rg -A 20 'validateInput' src/hooks/useKioskMode.ts | head -40Repository: tegnike/aituber-kit
Length of output: 1387
🏁 Script executed:
# Look for where the AI request is actually made to understand the flow
rg 'api/ai/' src/ --type ts --type js -B 5 -A 5Repository: tegnike/aituber-kit
Length of output: 3619
🏁 Script executed:
# Check how the validation is used in the chat sending flow
rg -B 10 -A 10 'validateInput' src/ --type ts | grep -A 20 'send\|submit\|request' | head -50Repository: tegnike/aituber-kit
Length of output: 1401
🏁 Script executed:
# Look for the message send handler to understand the flow
rg 'chat-send-button\|handleSend\|onSend' src/ --type ts -B 3 -A 10 | head -60Repository: tegnike/aituber-kit
Length of output: 45
🏁 Script executed:
# Check the actual validation logic more completely
sed -n '1,100p' src/hooks/useKioskMode.tsRepository: tegnike/aituber-kit
Length of output: 2837
🏁 Script executed:
# Check if there's any async behavior in the message send handler
rg -A 30 'handleValidatedSend' src/components/messageInput.tsxRepository: tegnike/aituber-kit
Length of output: 3229
🏁 Script executed:
# Look for the actual send logic after validation passes
rg -B 5 -A 20 'const handleSend\|function handleSend' src/components/messageInput.tsxRepository: tegnike/aituber-kit
Length of output: 45
🏁 Script executed:
# Verify if waitForRequest is a valid Playwright API
rg 'waitForRequest' --type ts --type js -g '*.ts' -g '*.js' /Repository: tegnike/aituber-kit
Length of output: 1512
🏁 Script executed:
# Check Playwright documentation by searching package.json for version
cat package.json | grep -i playwright -A 2 -B 2Repository: tegnike/aituber-kit
Length of output: 881
🏁 Script executed:
# Search for actual request interception patterns used in the codebase
rg 'page\.on.*request\|waitFor.*request\|page\.route' tests/e2e/ --type ts -A 3Repository: tegnike/aituber-kit
Length of output: 45
🏁 Script executed:
# Check Playwright API documentation by looking at type definitions
find node_modules/@playwright -name "*.d.ts" -type f | head -5Repository: tegnike/aituber-kit
Length of output: 123
🏁 Script executed:
# Search for page.waitForRequest usage in Playwright types
rg 'waitForRequest' node_modules/@playwright/test/ 2>/dev/null | head -20Repository: tegnike/aituber-kit
Length of output: 45
🏁 Script executed:
# Check if there are other patterns for testing absence of requests in e2e tests
rg 'page\.on.*request' tests/e2e/ --type ts -B 5 -A 15 | head -80Repository: tegnike/aituber-kit
Length of output: 2304
🏁 Script executed:
# Check the Prettier configuration to understand the code style requirements
cat .prettierrc* prettier.config.* 2>/dev/null || cat package.json | grep -A 20 'prettier'Repository: tegnike/aituber-kit
Length of output: 2490
🏁 Script executed:
# Check if the current code uses single quotes or double quotes
sed -n '89,102p' tests/e2e/kiosk-mode.spec.ts | cat -ARepository: tegnike/aituber-kit
Length of output: 635
🏁 Script executed:
# Look at surrounding code in the test file to understand the pattern
sed -n '1,30p' tests/e2e/kiosk-mode.spec.tsRepository: tegnike/aituber-kit
Length of output: 773
AI リクエスト検証のタイミング安定性を改善してください
Line 101 の即時 expect(aiRequests).toHaveLength(0) では、ネットワーク層での遅延リクエストを確実に拾えません。検証ロジック自体は同期的にリクエスト発火を防いでいますが、テスト安定性のため waitForRequest タイムアウトを使用して「一定時間リクエストがないこと」を明示的に確認する方が堅牢です。
修正案
- const aiRequests: string[] = []
- page.on('request', (request) => {
- const url = new URL(request.url())
- if (url.pathname.startsWith('/api/ai/')) {
- aiRequests.push(url.pathname)
- }
- })
+ const aiRequestDetected = page
+ .waitForRequest((request) => {
+ const url = new URL(request.url())
+ return url.pathname.startsWith('/api/ai/')
+ }, { timeout: 1000 })
+ .then(() => true)
+ .catch(() => false)
await messageInput.fill('this contains blocked')
await page.getByTestId('chat-send-button').click({ force: true })
await expect(page.getByText('不適切な内容が含まれています')).toBeVisible()
- expect(aiRequests).toHaveLength(0)
+ expect(await aiRequestDetected).toBe(false)📝 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.
| const aiRequests: string[] = [] | |
| page.on('request', (request) => { | |
| const url = new URL(request.url()) | |
| if (url.pathname.startsWith('/api/ai/')) { | |
| aiRequests.push(url.pathname) | |
| } | |
| }) | |
| await messageInput.fill('this contains blocked') | |
| await page.getByTestId('chat-send-button').click({ force: true }) | |
| await expect(page.getByText('不適切な内容が含まれています')).toBeVisible() | |
| expect(aiRequests).toHaveLength(0) | |
| }) | |
| const aiRequestDetected = page | |
| .waitForRequest((request) => { | |
| const url = new URL(request.url()) | |
| return url.pathname.startsWith('/api/ai/') | |
| }, { timeout: 1000 }) | |
| .then(() => true) | |
| .catch(() => false) | |
| await messageInput.fill('this contains blocked') | |
| await page.getByTestId('chat-send-button').click({ force: true }) | |
| await expect(page.getByText('不適切な内容が含まれています')).toBeVisible() | |
| expect(await aiRequestDetected).toBe(false) |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@tests/e2e/kiosk-mode.spec.ts` around lines 89 - 102, The test currently
checks aiRequests immediately with expect(aiRequests).toHaveLength(0), which can
miss delayed network calls; replace this synchronous check with an explicit
wait-for-no-request using Playwright's waitForRequest on the page (targeting
paths starting with '/api/ai/') with a short timeout and assert that it times
out (or catch the timeout and treat it as success), or alternatively use
page.waitForRequest in a try/catch to fail if any matching request arrives
within the timeout; update the block around page.on('request', ...) and the
final assertion to use page.waitForRequest (or equivalent timeout-based logic)
to deterministically confirm no AI request was sent.

新機能
改善
multiModalModeを廃止し、enableMultiModalに統一しました。AbortSignalに対応しました。バグ修正
navigator.mediaDevicesモックが既存APIを消してしまう問題を修正しました。テスト
ドキュメント
AGENTS.mdを追加しました。その他
.env.exampleにゲーム実況関連の環境変数を追加しました。.gitignoreとtsconfig.jsonを更新しました。public/speakers_aivis.jsonから不要なデフォルトスピーカーエントリを削除しました。