refactor: complete error handling standardization (issue #101)
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m26s
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m26s
Prestige Store: - Convert doPrestige() to return Result<void> with specific error codes (INVALID_PRESTIGE_ID, PRESTIGE_MAX_LEVEL, INSUFFICIENT_INSIGHT) - Convert startPactRitual() to return Result<void> with specific error codes (GUARDIAN_NOT_DEFEATED, PACT_ALREADY_SIGNED, PACT_SLOTS_FULL, INSUFFICIENT_MANA, RITUAL_IN_PROGRESS) Combat Actions: - Add try/catch wrapper inside processCombatTick with safe fallback defaults - Add makeDefaultCombatTickResult helper for error recovery LocalStorage Error Handling: - Create safe-persist.ts utility wrapping localStorage with error handling (corrupted JSON, quota exceeded, unexpected failures) - Update all 8 Zustand stores to use createSafeStorage() in persist middleware UI Updates: - Update GuardianPactsTab to use Result pattern for ritual error messages Tests: - Update store-actions-combat-prestige.test.ts for Result return types - Update store-actions.test.ts ManaStore tests for Result pattern - Remove duplicate Prestige/Discipline sections from store-actions.test.ts - All files under 400 line limit 601 tests pass (3 pre-existing failures in spire-utils.test.ts)
This commit is contained in:
@@ -3,8 +3,11 @@
|
||||
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
import { createSafeStorage } from '../utils/safe-persist';
|
||||
import type { Memory } from '../types';
|
||||
import { GUARDIANS, PRESTIGE_DEF } from '../constants';
|
||||
import { ok, okVoid, fail, ErrorCode } from '../utils/result';
|
||||
import type { Result } from '../utils/result';
|
||||
|
||||
// ─── Prestige State (data only) ──────────────────────────────────────────────
|
||||
|
||||
@@ -41,11 +44,11 @@ export interface PrestigeState {
|
||||
// ─── Prestige Actions ────────────────────────────────────────────────────────
|
||||
|
||||
export interface PrestigeActions {
|
||||
doPrestige: (id: string) => boolean;
|
||||
doPrestige: (id: string) => Result<void>;
|
||||
addMemory: (memory: Memory) => void;
|
||||
removeMemory: (skillId: string) => void;
|
||||
clearMemories: () => void;
|
||||
startPactRitual: (floor: number, rawMana: number) => boolean;
|
||||
startPactRitual: (floor: number, rawMana: number) => Result<void>;
|
||||
cancelPactRitual: () => void;
|
||||
completePactRitual: (addLog: (msg: string) => void) => void;
|
||||
updatePactRitualProgress: (hours: number) => void;
|
||||
@@ -112,10 +115,11 @@ export const usePrestigeStore = create<PrestigeStore>()(
|
||||
doPrestige: (id: string) => {
|
||||
const state = get();
|
||||
const pd = PRESTIGE_DEF[id];
|
||||
if (!pd) return false;
|
||||
if (!pd) return fail(ErrorCode.INVALID_PRESTIGE_ID, `Unknown prestige upgrade: ${id}`);
|
||||
|
||||
const lvl = state.prestigeUpgrades[id] || 0;
|
||||
if (lvl >= pd.max || state.insight < pd.cost) return false;
|
||||
if (lvl >= pd.max) return fail(ErrorCode.PRESTIGE_MAX_LEVEL, `Upgrade ${id} is already at max level (${pd.max})`);
|
||||
if (state.insight < pd.cost) return fail(ErrorCode.INSUFFICIENT_INSIGHT, `Need ${pd.cost} insight, have ${state.insight}`);
|
||||
|
||||
const newPU = { ...state.prestigeUpgrades, [id]: lvl + 1 };
|
||||
set({
|
||||
@@ -124,7 +128,7 @@ export const usePrestigeStore = create<PrestigeStore>()(
|
||||
memorySlots: id === 'deepMemory' ? state.memorySlots + 1 : state.memorySlots,
|
||||
pactSlots: id === 'pactBinding' ? state.pactSlots + 1 : state.pactSlots,
|
||||
});
|
||||
return true;
|
||||
return okVoid();
|
||||
},
|
||||
|
||||
addMemory: (memory: Memory) => {
|
||||
@@ -148,19 +152,19 @@ export const usePrestigeStore = create<PrestigeStore>()(
|
||||
startPactRitual: (floor: number, rawMana: number) => {
|
||||
const state = get();
|
||||
const guardian = GUARDIANS[floor];
|
||||
if (!guardian) return false;
|
||||
if (!guardian) return fail(ErrorCode.INVALID_INPUT, `No guardian at floor ${floor}`);
|
||||
|
||||
if (!state.defeatedGuardians.includes(floor)) return false;
|
||||
if (state.signedPacts.includes(floor)) return false;
|
||||
if (state.signedPacts.length >= state.pactSlots) return false;
|
||||
if (rawMana < guardian.pactCost) return false;
|
||||
if (state.pactRitualFloor !== null) return false;
|
||||
if (!state.defeatedGuardians.includes(floor)) return fail(ErrorCode.GUARDIAN_NOT_DEFEATED, `Guardian at floor ${floor} has not been defeated`);
|
||||
if (state.signedPacts.includes(floor)) return fail(ErrorCode.PACT_ALREADY_SIGNED, `Pact with ${guardian.name} is already signed`);
|
||||
if (state.signedPacts.length >= state.pactSlots) return fail(ErrorCode.PACT_SLOTS_FULL, `All pact slots are full (${state.pactSlots})`);
|
||||
if (rawMana < guardian.pactCost) return fail(ErrorCode.INSUFFICIENT_MANA, `Need ${guardian.pactCost} raw mana, have ${rawMana}`);
|
||||
if (state.pactRitualFloor !== null) return fail(ErrorCode.RITUAL_IN_PROGRESS, `A pact ritual is already in progress for floor ${state.pactRitualFloor}`);
|
||||
|
||||
set({
|
||||
pactRitualFloor: floor,
|
||||
pactRitualProgress: 0,
|
||||
});
|
||||
return true;
|
||||
return okVoid();
|
||||
},
|
||||
|
||||
cancelPactRitual: () => {
|
||||
@@ -291,6 +295,7 @@ export const usePrestigeStore = create<PrestigeStore>()(
|
||||
},
|
||||
}),
|
||||
{
|
||||
storage: createSafeStorage(),
|
||||
name: 'mana-loop-prestige',
|
||||
partialize: (state) => ({
|
||||
loopCount: state.loopCount,
|
||||
|
||||
Reference in New Issue
Block a user