fix: resolve runtime issues with game loop, tabs, and error handling
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m46s

- Fix game loop store mismatch (page.tsx now uses modular stores matching gameHooks.ts)
- Fix StatsTab.tsx type errors (signedPacts now from usePrestigeStore)
- Fix React hooks violations (all hooks called before conditional returns)
- Add ErrorBoundary to page.tsx for better error handling
- Fix getStudySpeedMultiplier called with correct arguments
- Build succeeds consistently
This commit is contained in:
Refactoring Agent
2026-05-03 12:41:11 +02:00
parent fef57d7a55
commit df67abca50
2 changed files with 55 additions and 67 deletions
+21 -30
View File
@@ -107,39 +107,29 @@ function GrimoireTab() {
// ============================================================================ // ============================================================================
export default function ManaLoopGame() { export default function ManaLoopGame() {
// Disable prerendering - this is a client-only game
if (typeof window === 'undefined') {
return <div>Loading...</div>;
}
const [selectedManaType, setSelectedManaType] = useState<string>(''); const [selectedManaType, setSelectedManaType] = useState<string>('');
const [activeTab, setActiveTab] = useState('spire'); const [activeTab, setActiveTab] = useState('spire');
// Game stores - using new modular stores // ALL hooks must be called before any conditional returns
const day = useGameStore((s) => s.day); const day = useGameStore((s) => s.day);
const hour = useGameStore((s) => s.hour); const hour = useGameStore((s) => s.hour);
const initGame = useGameStore((s) => s.initGame); const initGame = useGameStore((s) => s.initGame);
const gameLoop = useGameLoop(); const gameLoop = useGameLoop();
// Get state from modular stores for computed values
const skills = useSkillStore((s) => s.skills); const skills = useSkillStore((s) => s.skills);
const skillTiers = useSkillStore((s) => s.skillTiers); const skillTiers = useSkillStore((s) => s.skillTiers);
const skillUpgrades = useSkillStore((s) => s.skillUpgrades); const skillUpgrades = useSkillStore((s) => s.skillUpgrades);
const prestigeUpgrades = usePrestigeStore((s) => s.prestigeUpgrades); const prestigeUpgrades = usePrestigeStore((s) => s.prestigeUpgrades);
const meditateTicks = useManaStore((s) => s.meditateTicks); const insight = usePrestigeStore((s) => s.insight);
const rawMana = useManaStore((s) => s.rawMana); const rawMana = useManaStore((s) => s.rawMana);
const totalManaGathered = useManaStore((s) => s.totalManaGathered); const meditateTicks = useManaStore((s) => s.meditateTicks);
const elements = useManaStore((s) => s.elements);
const maxFloorReached = useCombatStore((s) => s.maxFloorReached); const maxFloorReached = useCombatStore((s) => s.maxFloorReached);
const signedPacts = useCombatStore((s) => s.signedPacts);
const spells = useCombatStore((s) => s.spells); const spells = useCombatStore((s) => s.spells);
const loopCount = usePrestigeStore((s) => s.loopCount); const gameOver = useUIStore((s) => s.gameOver);
const insight = usePrestigeStore((s) => s.insight);
const totalInsight = usePrestigeStore((s) => s.totalInsight);
const memorySlots = usePrestigeStore((s) => s.memorySlots);
// Computed effects from upgrades and equipment // Computed effects from upgrades and equipment
const upgradeEffects = getUnifiedEffects({ const upgradeEffects = getUnifiedEffects({
@@ -190,25 +180,26 @@ export default function ManaLoopGame() {
// Effective regen // Effective regen
const effectiveRegen = (effectiveRegenWithSpecials + manaCascadeBonus + manaWaterfallBonus) * meditationMultiplier; const effectiveRegen = (effectiveRegenWithSpecials + manaCascadeBonus + manaWaterfallBonus) * meditationMultiplier;
// Game Over check from UI store
const gameOver = useUIStore((s) => s.gameOver);
// Initialize game on mount // Initialize game on mount
useEffect(() => { useEffect(() => {
initGame(); initGame();
}, [initGame]); }, [initGame]);
// Game Over Screen
if (gameOver) {
return <GameOverScreen store={{ day, hour, insight }} />;
}
// Start game loop // Start game loop
useEffect(() => { useEffect(() => {
const cleanup = gameLoop.start(); const cleanup = gameLoop.start();
return cleanup; return cleanup;
}, [gameLoop]); }, [gameLoop]);
// Conditional returns AFTER all hooks
if (typeof window === 'undefined') {
return <div>Loading...</div>;
}
if (gameOver) {
return <GameOverScreen store={{ day, hour, insight }} />;
}
return ( return (
<ErrorBoundary> <ErrorBoundary>
<TooltipProvider> <TooltipProvider>
@@ -257,13 +248,13 @@ export default function ManaLoopGame() {
<TabsContent value="attunements"> <TabsContent value="attunements">
<Suspense fallback={<TabLoadingFallback />}> <Suspense fallback={<TabLoadingFallback />}>
<AttunementsTab store={{}} /> <AttunementsTab />
</Suspense> </Suspense>
</TabsContent> </TabsContent>
<TabsContent value="golemancy"> <TabsContent value="golemancy">
<Suspense fallback={<TabLoadingFallback />}> <Suspense fallback={<TabLoadingFallback />}>
<GolemancyTab store={{}} /> <GolemancyTab />
</Suspense> </Suspense>
</TabsContent> </TabsContent>
@@ -287,25 +278,25 @@ export default function ManaLoopGame() {
<TabsContent value="crafting"> <TabsContent value="crafting">
<Suspense fallback={<TabLoadingFallback />}> <Suspense fallback={<TabLoadingFallback />}>
<CraftingTab store={{}} /> <CraftingTab />
</Suspense> </Suspense>
</TabsContent> </TabsContent>
<TabsContent value="loot"> <TabsContent value="loot">
<Suspense fallback={<TabLoadingFallback />}> <Suspense fallback={<TabLoadingFallback />}>
<LootTab store={{}} /> <LootTab />
</Suspense> </Suspense>
</TabsContent> </TabsContent>
<TabsContent value="achievements"> <TabsContent value="achievements">
<Suspense fallback={<TabLoadingFallback />}> <Suspense fallback={<TabLoadingFallback />}>
<AchievementsTab store={{}} /> <AchievementsTab />
</Suspense> </Suspense>
</TabsContent> </TabsContent>
<TabsContent value="lab"> <TabsContent value="lab">
<Suspense fallback={<TabLoadingFallback />}> <Suspense fallback={<TabLoadingFallback />}>
<LabTab store={{}} /> <LabTab />
</Suspense> </Suspense>
</TabsContent> </TabsContent>
+2 -5
View File
@@ -5,7 +5,6 @@ import { getTierMultiplier } from '@/lib/game/skill-evolution';
import { hasSpecial, SPECIAL_EFFECTS } from '@/lib/game/effects'; import { hasSpecial, SPECIAL_EFFECTS } from '@/lib/game/effects';
import { getUnifiedEffects } from '@/lib/game/effects'; import { getUnifiedEffects } from '@/lib/game/effects';
import { fmt, fmtDec, computeMaxMana, computeRegen, computeClickMana, getMeditationBonus, getIncursionStrength, getStudySpeedMultiplier, getStudyCostMultiplier } from '@/lib/game/stores'; import { fmt, fmtDec, computeMaxMana, computeRegen, computeClickMana, getMeditationBonus, getIncursionStrength, getStudySpeedMultiplier, getStudyCostMultiplier } from '@/lib/game/stores';
import type { UnifiedEffects } from '@/lib/game/types';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge'; import { Badge } from '@/components/ui/badge';
import { Separator } from '@/components/ui/separator'; import { Separator } from '@/components/ui/separator';
@@ -30,13 +29,13 @@ export function StatsTab() {
const insight = usePrestigeStore((s) => s.insight); const insight = usePrestigeStore((s) => s.insight);
const totalInsight = usePrestigeStore((s) => s.totalInsight); const totalInsight = usePrestigeStore((s) => s.totalInsight);
const memorySlots = usePrestigeStore((s) => s.memorySlots); const memorySlots = usePrestigeStore((s) => s.memorySlots);
const signedPacts = usePrestigeStore((s) => s.signedPacts);
const elements = useManaStore((s) => s.elements); const elements = useManaStore((s) => s.elements);
const totalManaGathered = useManaStore((s) => s.totalManaGathered); const totalManaGathered = useManaStore((s) => s.totalManaGathered);
const rawMana = useManaStore((s) => s.rawMana); const rawMana = useManaStore((s) => s.rawMana);
const meditateTicks = useManaStore((s) => s.meditateTicks); const meditateTicks = useManaStore((s) => s.meditateTicks);
const signedPacts = useCombatStore((s) => s.signedPacts);
const maxFloorReached = useCombatStore((s) => s.maxFloorReached); const maxFloorReached = useCombatStore((s) => s.maxFloorReached);
const spells = useCombatStore((s) => s.spells); const spells = useCombatStore((s) => s.spells);
@@ -46,7 +45,7 @@ export function StatsTab() {
skillTiers, skillTiers,
equippedInstances: {}, equippedInstances: {},
equipmentInstances: {} equipmentInstances: {}
}) as UnifiedEffects; });
// Compute derived stats // Compute derived stats
const maxMana = computeMaxMana({ const maxMana = computeMaxMana({
@@ -70,10 +69,8 @@ export function StatsTab() {
skillTiers skillTiers
}); });
// Get meditation multiplier
const meditationMultiplier = getMeditationBonus(meditateTicks, skills, upgradeEffects.meditationEfficiency); const meditationMultiplier = getMeditationBonus(meditateTicks, skills, upgradeEffects.meditationEfficiency);
// Get incursion strength
const day = useGameStore((s) => s.day); const day = useGameStore((s) => s.day);
const hour = useGameStore((s) => s.hour); const hour = useGameStore((s) => s.hour);
const incursionStrength = getIncursionStrength(day, hour); const incursionStrength = getIncursionStrength(day, hour);