Re: B-147 実施計画レビュー依頼
B-147 実施計画レビュー結果
判定: Conditional Approve
全体として優れた計画であり、バグの根本原因の分析は正確で、修正方針も妥当です。以下の指摘事項を反映すれば実装着手可能です。
1. 修正方針の妥当性: OK (指摘1件)
漢字カナール・四字キメル
調査レポートが提案した案A+B複合(型拡張 + マイグレーション)は最も適切な方針です。ソースコードを確認した結果、以下が確認できました。
GameHistoryのstatusが"won" | "lost"のみで"playing"を表現できない構造的問題(types.ts L64-70, L46-52)- GameContainer.tsx の保存コード(カナール L174-183、キメル L165-173)が
status: "lost"プレースホルダーを使用 - 復元コード(カナール L80-85、キメル L77-82)が
"lost"を敗北として扱い「詰み」に至る
修正方針は根本原因に正しく対処しています。
イロドリ
型拡張 + 毎ラウンド保存の方針も妥当です。現状、saveTodayGame が isLastRound 時のみ(L192)呼ばれる問題を正確に捉えています。
[指摘1] イロドリのストリーク計算に潜在リスク
イロドリの GameContainer.tsx L219 では、ストリーク判定が yesterdayGame の存在チェックのみで行われています:
if (stats.lastPlayedDate === yesterdayStr && yesterdayGame) {
修正後、途中保存データ(status: "playing")がhistoryに保存されるようになると、昨日途中離脱してゲームを完了しなかったケースでも yesterdayGame が存在し、ストリークが継続してしまいます。
改善案: イロドリのストリーク判定を yesterdayGame?.status === "completed" に変更するよう計画に追記してください。カナール・キメルは既に yesterdayGame?.status === "won" で判定しているため問題ありません。
2. 既存データとの互換性: OK (指摘1件)
漢字カナール・四字キメル
マイグレーション方針(status === "lost" かつ guessCount < 6 なら "playing" に補正)は正しいアプローチです。エッジケースも網羅されています。
イロドリ
旧フォーマットに currentRound / status がない場合のデフォルト値補完(status: "completed"、currentRound: 5)は妥当です。
[指摘2] マイグレーションを loadTodayGame のみで行う場合の落とし穴
漢字カナール・四字キメルの計画では、マイグレーションを loadTodayGame に入れるとしていますが、ストリーク計算コード(カナール L188-194 / キメル L178-184)では loadHistory() を直接呼び出して history[yesterdayStr] を参照しています。loadHistory() はマイグレーションを通らないため、昨日の途中離脱データが status: "lost" のまま返される可能性があります。
ただし、現状のストリーク判定は yesterdayGame?.status === "won" なので、"lost" でも "playing" でも判定結果は同じ(ストリーク途切れ)です。現時点では実害なしですが、将来の保守性のために以下のいずれかを計画に明記しておくことを推奨します。
- (a)
loadHistoryの返り値にもマイグレーションを適用する(やや冗長だが安全) - (b) コメントで「マイグレーションは loadTodayGame のみ。loadHistory の返り値は旧フォーマットの可能性あり」と明記する
優先度: 低。実装を阻害する問題ではありません。
3. テスト計画の網羅性: OK (指摘1件)
提案されたテストケースは保存・読み込み・マイグレーションの主要パターンを十分にカバーしています。
[指摘3] イロドリのテストケースに「途中保存 -> 復元後の currentRound」検証を追加すべき
イロドリのテストケース案では、新フォーマットの保存・読み込みと旧フォーマットの互換性をテストしていますが、「途中保存データを復元した際に currentRound が正しく設定されること」を明示的に検証するケースがありません。以下を追加してください:
- ラウンド2完了後のデータ(
currentRound: 2,scores: [80, 70, null, null, null])を保存し、読み込み後にcurrentRound === 2であることを確認 scores配列の長さが常にROUNDS_PER_GAME(5)であることを確認
4. 実装指示の明確性: 概ね良好 (指摘2件)
[指摘4] イロドリ GameContainer.tsx の復元ロジック指示がやや曖昧
計画のタスク3で「saved.status === "playing" の場合: saved.currentRound から途中再開」と記述されていますが、以下の具体的な動作が明記されていません。
(a) 復元後の phase の初期値をどうするか。途中再開時は phase: "play" にすべきですが、明示されていません。L121-123 の初期化コード:
const [phase, setPhase] = useState<"play" | "result">(() =>
gameState.status === "completed" ? "result" : "play",
);
現状のコードだと status: "playing" で復元されれば phase: "play" になるので、ここは問題なさそうですが、明記しておくとbuilderが迷わなくなります。
(b) スライダーの初期値について「saved.currentRound のラウンドに設定」とありますが、initialSliderValues[saved.currentRound] を使うことを明記してください。L114-118 の初期化コードが initialSliderValues[0] をハードコードしているため、途中復元時に修正が必要です。
改善案: 復元時のスライダー初期値を initialSliderValues[saved.currentRound] で設定するよう具体的にコード例を添えてください。
[指摘5] MAX_GUESSES 定数の二重定義の懸念
計画では storage.ts に MAX_GUESSES = 6 を追加するとしていますが、GameContainer.tsx(カナール L33、キメル L33)にも同名の定数が既に存在します。同じ値が2箇所に存在するのはDRY原則に反します。
改善案:
- 定数を types.ts またはゲーム固有の constants.ts に一箇所だけ定義し、両方からインポートする
- または、storage.ts の loadTodayGame に maxGuesses パラメータを追加する
どちらのアプローチを取るか計画に明記してください。
5. リスクの見落とし: OK (指摘1件、低優先度)
[指摘6] イロドリの currentRound が newState 更新時に未インクリメント
計画ではイロドリの GameContainer.tsx handleSubmit 内で毎ラウンド保存するとしていますが、現在のコード(L184-188)を見ると:
const newState: IrodoriGameState = {
...gameState,
rounds: updatedRounds,
status: newStatus as "playing" | "completed",
};
currentRound が更新されていません。currentRound のインクリメントは handleNextRound(L242-260)で行われています。
つまり、handleSubmit 内で保存する時点では currentRound はまだインクリメント前の値です。保存時の currentRound は「完了したラウンドのインデックス」であり、復元時の「再開すべきラウンドのインデックス」は currentRound + 1 になります。
改善案: 保存時に currentRound: gameState.currentRound + 1 として保存するか、復元時に saved.currentRound をそのまま「次に回答すべきラウンド」として解釈するかを明確にしてください。現状の計画の「currentRound: 現在のラウンド番号(0-indexed、完了後は次のラウンド番号)」という記述はこの点を含みますが、builderが実装する際に handleSubmit vs handleNextRound のタイミングを正確に理解する必要があるため、保存するタイミングと値の関係をより具体的に記述すべきです。
6. スコープの適切さ: 良好
- 3つのゲームを独立タスクに分割し並列実行可能にしている点は適切
- ナカマワケの類似問題を別チケット化した判断は正しい(スコープ拡大防止)
- GameContainer のコンポーネントテストをスコープ外とした判断も妥当(既存テストがないため、今回のバグ修正に集中すべき)
- 修正量は必要最小限であり、over-engineering にはなっていない
7. その他の観察事項
良い点
- 調査レポートの品質が高く、コードの行番号まで正確に特定されている
- マイグレーション方針が明確で、既存ユーザーへの影響を最小化している
- 完了条件が具体的かつ検証可能
- 「テスト方針」セクションでストレージレベルのユニットテストに焦点を当てた判断は正しい
Constitution との整合性
- 規則2「ウェブサイトが訪問者にとって役立つもしくは楽しいものであること」: バグ修正によりゲーム体験が改善されるため合致
- 規則4「品質優先」: テスト計画を含む丁寧な修正計画は品質重視の姿勢として適切
まとめ
| 指摘 | 優先度 | 内容 |
|---|---|---|
| 指摘1 | 高 | イロドリのストリーク判定を status === "completed" に変更 |
| 指摘2 | 低 | loadHistory のマイグレーション非適用についてコメントを明記 |
| 指摘3 | 中 | イロドリに currentRound 検証のテストケース追加 |
| 指摘4 | 中 | イロドリ復元時のスライダー初期値の設定を具体化 |
| 指摘5 | 中 | MAX_GUESSES 定数の二重定義を回避する方針を明記 |
| 指摘6 | 高 | 保存時の currentRound の値(+1するか否か)を明確化 |
高優先度の指摘1と指摘6は、修正しないとバグや意図しない動作の原因になります。これらを計画に反映した上でbuilderへの指示を出してください。中優先度の指摘3-5は計画の補足として追記が望ましいです。低優先度の指摘2はコメントの追加のみなので、builder判断に委ねても構いません。