fix: wrap GameOverScreen in ErrorBoundary and add defensive checks for day 30 blank page bug
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 2m25s

- Wrap GameOverScreen in ErrorBoundary in page.tsx to prevent blank page on render errors
- Add defensive Number.isFinite checks in GameOverScreen for all numeric props
- Add regression test for day 30 → game-over flow (day30-blank-page.test.ts)

Fixes #375
This commit is contained in:
2026-06-12 07:01:43 +02:00
parent 8b41f137d5
commit 608d4c4ff7
6 changed files with 229 additions and 7 deletions
+10 -4
View File
@@ -17,6 +17,12 @@ export function GameOverScreen({ day, hour, insightGained, totalInsight }: GameO
useGameStore.getState().startNewLoop();
};
// Defensive: ensure all values are valid numbers (guard against render-time edge cases)
const safeDay = Number.isFinite(day) ? day : 0;
const safeHour = Number.isFinite(hour) ? hour : 0;
const safeInsightGained = Number.isFinite(insightGained) ? insightGained : 0;
const safeTotalInsight = Number.isFinite(totalInsight) ? totalInsight : 0;
return (
<div className="fixed inset-0 game-overlay flex items-center justify-center z-50">
<Card className="bg-gray-900 border-gray-600 max-w-md w-full mx-4 shadow-2xl">
@@ -32,19 +38,19 @@ export function GameOverScreen({ day, hour, insightGained, totalInsight }: GameO
<div className="grid grid-cols-2 gap-3">
<div className="p-3 bg-gray-800 rounded">
<div className="text-xl font-bold text-amber-400 game-mono">{fmt(insightGained)}</div>
<div className="text-xl font-bold text-amber-400 game-mono">{fmt(safeInsightGained)}</div>
<div className="text-xs text-gray-400">Insight Gained</div>
</div>
<div className="p-3 bg-gray-800 rounded">
<div className="text-xl font-bold text-blue-400 game-mono">{day}</div>
<div className="text-xl font-bold text-blue-400 game-mono">{safeDay}</div>
<div className="text-xs text-gray-400">Day Reached</div>
</div>
<div className="p-3 bg-gray-800 rounded">
<div className="text-xl font-bold text-purple-400 game-mono">{formatHour(hour)}</div>
<div className="text-xl font-bold text-purple-400 game-mono">{formatHour(safeHour)}</div>
<div className="text-xs text-gray-400">Hour</div>
</div>
<div className="p-3 bg-gray-800 rounded">
<div className="text-xl font-bold text-green-400 game-mono">{fmt(totalInsight)}</div>
<div className="text-xl font-bold text-green-400 game-mono">{fmt(safeTotalInsight)}</div>
<div className="text-xs text-gray-400">Total Insight</div>
</div>
</div>
+9 -1
View File
@@ -169,7 +169,15 @@ export default function ManaLoopGame() {
useEffect(() => { setMounted(true); }, []); // eslint-disable-line react-hooks/set-state-in-effect
if (gameOver) {
return <GameOverScreen day={day} hour={hour} insightGained={loopInsight} totalInsight={insight} />;
return (
<ErrorBoundary
onReset={() => {
useGameStore.getState().resetGame();
}}
>
<GameOverScreen day={day} hour={hour} insightGained={loopInsight} totalInsight={insight} />
</ErrorBoundary>
);
}
if (!mounted) return <div className="p-4 text-center text-gray-400">Loading...</div>;