diff --git a/src/Koogle.Web/Store/GameState/GameEffects.cs b/src/Koogle.Web/Store/GameState/GameEffects.cs
index f587445..4fe1a58 100644
--- a/src/Koogle.Web/Store/GameState/GameEffects.cs
+++ b/src/Koogle.Web/Store/GameState/GameEffects.cs
@@ -468,9 +468,10 @@ public class GameEffects
afterThrowState.PinsKnocked);
}
- // If game is over, skip save (user must confirm end via UI)
+ // If game is over, don't start debounce timer - the ProcessThrowResultAction effect handles saving
if (isGameOver)
{
+ _saveDebounceTimer?.Dispose();
return;
}
@@ -483,6 +484,30 @@ public class GameEffects
Timeout.Infinite);
}
+ ///
+ /// Handles ProcessThrowResultAction - saves game state immediately when game is over.
+ /// This effect runs after the reducer, so the state is already updated.
+ ///
+ [EffectMethod]
+ public async Task HandleProcessThrowResult(ProcessThrowResultAction action, IDispatcher dispatcher)
+ {
+ // Only save immediately if game is over
+ if (!action.IsGameOver) return;
+
+ var state = _gameState.Value;
+ if (!state.ActiveGameId.HasValue) return;
+
+ var success = await SaveCurrentStateAsync(state.ActiveGameId.Value);
+ if (success)
+ {
+ _logger.LogInformation("Game over state saved for game {GameId}", state.ActiveGameId.Value);
+ }
+ else
+ {
+ _logger.LogWarning("Failed to save game over state for game {GameId}", state.ActiveGameId.Value);
+ }
+ }
+
///
/// Handles ExecuteGameActionAction - executes game-specific action via IGameLogicService.
///
@@ -538,7 +563,7 @@ public class GameEffects
dispatcher);
}
- // If game is over, skip save (user must confirm end via UI)
+ // If game is over, skip debounce - the ExecuteGameActionSuccessAction effect handles saving
if (result.IsGameOver)
{
return;
@@ -564,6 +589,30 @@ public class GameEffects
}
}
+ ///
+ /// Handles ExecuteGameActionSuccessAction - saves game state immediately when game is over.
+ /// This effect runs after the reducer, so the state is already updated.
+ ///
+ [EffectMethod]
+ public async Task HandleExecuteGameActionSuccess(ExecuteGameActionSuccessAction action, IDispatcher dispatcher)
+ {
+ // Only save immediately if game is over
+ if (!action.IsGameOver) return;
+
+ var state = _gameState.Value;
+ if (!state.ActiveGameId.HasValue) return;
+
+ var success = await SaveCurrentStateAsync(state.ActiveGameId.Value);
+ if (success)
+ {
+ _logger.LogInformation("Game over state saved (from action) for game {GameId}", state.ActiveGameId.Value);
+ }
+ else
+ {
+ _logger.LogWarning("Failed to save game over state (from action) for game {GameId}", state.ActiveGameId.Value);
+ }
+ }
+
private static ThrowPanelSnapshot CreateThrowPanelSnapshot(ThrowPanelState state)
{
return ThrowPanelSnapshot.FromPins(