refactor: split bloated state types into State + Actions interfaces (issue #102)
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m19s

- CombatState: split into CombatState (data) + CombatActions + CombatStore
- PrestigeState: split into PrestigeState (data) + PrestigeActions + PrestigeStore
- ManaState: split into ManaState (data) + ManaActions + ManaStore
- GameState: deprecated, removed from barrel exports
- crafting-actions: updated to use CraftingState instead of GameState
- combat-utils/mana-utils: replaced Pick<GameState,...> with focused interfaces
- DisciplineCardProps: split into Definition + Runtime + Callbacks
- stores/index.ts: now exports both State and Actions types
This commit is contained in:
2026-05-20 21:05:22 +02:00
parent ee893e8973
commit 8a7ddaae27
24 changed files with 411 additions and 321 deletions
+50 -34
View File
@@ -8,6 +8,8 @@ import { invokerDisciplines } from '@/lib/game/data/disciplines/invoker';
import { calculateStatBonus, calculateManaDrain } from '@/lib/game/utils/discipline-math';
import clsx from 'clsx';
// ─── Attunement Tabs ─────────────────────────────────────────────────────────
interface AttunementTab {
key: string;
label: string;
@@ -21,7 +23,9 @@ const ATTUNEMENT_TABS: AttunementTab[] = [
{ key: 'invoker', label: 'Invoker', items: invokerDisciplines },
];
interface DisciplineCardProps {
// ─── Discipline Card Props (split from monolithic 15-field interface) ────────
export interface DisciplineCardDefinition {
id: string;
name: string;
description: string;
@@ -33,32 +37,36 @@ interface DisciplineCardProps {
drainBase: number;
difficultyFactor: number;
scalingFactor: number;
}
export interface DisciplineCardRuntime {
xp: number;
paused: boolean;
concurrentLimit: number;
}
export interface DisciplineCardCallbacks {
onToggle: (id: string, paused: boolean) => void;
}
const DisciplineCard: React.FC<DisciplineCardProps> = ({
id,
name,
description,
perkThresholds,
perkValues,
perkTypes,
statBonus,
baseValue,
drainBase,
difficultyFactor,
scalingFactor,
xp,
paused,
concurrentLimit,
onToggle,
}) => {
interface DisciplineCardProps {
definition: DisciplineCardDefinition;
runtime: DisciplineCardRuntime;
callbacks: DisciplineCardCallbacks;
}
// ─── Discipline Card Component ───────────────────────────────────────────────
const DisciplineCard: React.FC<DisciplineCardProps> = ({ definition, runtime, callbacks }) => {
const {
id, name, description, perkThresholds, perkValues, perkTypes,
statBonus, baseValue, drainBase, difficultyFactor, scalingFactor,
} = definition;
const { xp, paused: isPaused, concurrentLimit } = runtime;
const { onToggle } = callbacks;
const displayXp = xp;
const progressPercent = Math.min(displayXp / Math.max(1, concurrentLimit * 100), 100);
const isPaused = paused;
const activeStatBonus = calculateStatBonus(baseValue, displayXp, scalingFactor);
const estimatedDrain = calculateManaDrain(drainBase, displayXp, difficultyFactor);
@@ -134,6 +142,8 @@ const DisciplineCard: React.FC<DisciplineCardProps> = ({
);
};
// ─── Disciplines Tab ─────────────────────────────────────────────────────────
export const DisciplinesTab: React.FC = () => {
const activeIds = useDisciplineStore((s) => s.activeIds);
const concurrentLimit = useDisciplineStore((s) => s.concurrentLimit);
@@ -194,21 +204,27 @@ export const DisciplinesTab: React.FC = () => {
return (
<DisciplineCard
key={disc.id}
id={disc.id}
name={disc.name}
description={disc.description}
perkThresholds={disc.perks?.map((p) => p.threshold)}
perkValues={disc.perks?.map((p) => p.value)}
perkTypes={disc.perks?.map((p) => p.type)}
statBonus={disc.statBonus.stat}
baseValue={disc.statBonus.baseValue}
drainBase={disc.drainBase}
difficultyFactor={disc.difficultyFactor}
scalingFactor={disc.scalingFactor}
xp={discState.xp}
paused={discState.paused}
concurrentLimit={concurrentLimit}
onToggle={handleToggle}
definition={{
id: disc.id,
name: disc.name,
description: disc.description,
perkThresholds: disc.perks?.map((p) => p.threshold),
perkValues: disc.perks?.map((p) => p.value),
perkTypes: disc.perks?.map((p) => p.type),
statBonus: disc.statBonus.stat,
baseValue: disc.statBonus.baseValue,
drainBase: disc.drainBase,
difficultyFactor: disc.difficultyFactor,
scalingFactor: disc.scalingFactor,
}}
runtime={{
xp: discState.xp,
paused: discState.paused,
concurrentLimit,
}}
callbacks={{
onToggle: handleToggle,
}}
/>
);
})}
@@ -2,7 +2,6 @@
import { useState, useEffect, useCallback, useMemo } from 'react';
import { useCraftingStore } from '@/lib/game/stores/craftingStore';
import { equipItem, unequipItem, deleteEquipmentInstance } from '@/lib/game/crafting-actions/equipment-actions';
import type { EquipmentSlot } from '@/lib/game/types';
import { DebugName } from '@/components/game/debug/debug-context';
import { EquipmentSlotGrid } from './EquipmentTab/EquipmentSlotGrid';