fix: SkillsTab barrel export, equipment store reads, LabTab re-export, debug null guards, GrimoireTab loaded state, spire tab switching
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m48s

This commit is contained in:
2026-05-06 21:08:10 +02:00
parent e5308ac239
commit a4004be229
10 changed files with 28 additions and 141 deletions
@@ -9,7 +9,7 @@ import { useAttunementStore } from '@/lib/game/stores';
export function AttunementDebug() {
const attunements = useAttunementStore((s) => s.attunements);
const debugUnlockAttunement = useAttunementStore((s) => s.debugUnlockAttunement);
const debugAddAttunementXP = useAttunementStore((s) => s.debugAddAttunementXP);
const addAttunementXP = useAttunementStore((s) => s.addAttunementXP);
const handleUnlockAttunement = (id: string) => {
if (debugUnlockAttunement) {
@@ -18,8 +18,8 @@ export function AttunementDebug() {
};
const handleAddAttunementXP = (id: string, amount: number) => {
if (debugAddAttunementXP) {
debugAddAttunementXP(id, amount);
if (addAttunementXP) {
addAttunementXP(id, amount);
}
};
@@ -32,7 +32,7 @@ export function AttunementDebug() {
</CardTitle>
</CardHeader>
<CardContent className="space-y-3">
{Object.entries(ATTUNEMENTS_DEF).map(([id, def]) => {
{Object.entries(ATTUNEMENTS_DEF || {}).map(([id, def]) => {
const isActive = attunements?.[id]?.active;
const level = attunements?.[id]?.level || 1;
const xp = attunements?.[id]?.experience || 0;
+2 -2
View File
@@ -17,7 +17,7 @@ export function ElementDebug() {
const handleAddElementalMana = (element: string, amount: number) => {
if (addElementMana) {
addElementMana(element, amount);
addElementMana(element, amount, 100);
}
};
@@ -31,7 +31,7 @@ export function ElementDebug() {
</CardHeader>
<CardContent>
<div className="grid grid-cols-3 sm:grid-cols-4 md:grid-cols-6 gap-2">
{Object.entries(elements).map(([id, elem]) => {
{Object.entries(elements || {}).map(([id, elem]) => {
const def = ELEMENTS[id];
return (
<div
+1 -2
View File
@@ -51,8 +51,7 @@ export function GameStateDebug() {
const getMaxMana = () => {
return computeMaxMana(
{ skills: {}, prestigeUpgrades: {}, skillUpgrades: {}, skillTiers: {} },
{ maxManaBonus: 0, maxManaMultiplier: 1 }
{ skills: {}, prestigeUpgrades: {}, skillUpgrades: {}, skillTiers: {} }
);
};
+2 -2
View File
@@ -24,7 +24,7 @@ export function PactDebug() {
const addLog = useUIStore((s) => s.addLog);
// Get all guardian floors
const guardianFloors = Object.keys(GUARDIANS).map(Number).sort((a, b) => a - b);
const guardianFloors = Object.keys(GUARDIANS || {}).map(Number).sort((a, b) => a - b);
// Force sign a pact with a guardian (bypass costs and time)
const forcePact = (floor: number) => {
@@ -118,7 +118,7 @@ export function PactDebug() {
Floor {floor} | {guardian.pact}x multiplier
</div>
<div className="text-xs text-gray-500">
Unlocks: {guardian.unlocksMana.map(e => ELEMENTS[e]?.name || e).join(', ')}
Element: {ELEMENTS[guardian.element]?.name || guardian.element}
</div>
</div>
<div className="flex gap-1">
+2 -2
View File
@@ -4,12 +4,12 @@ import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Separator } from '@/components/ui/separator';
import { BookOpen } from 'lucide-react';
import { useSkillStore, useGameStore } from '@/lib/game/stores';
import { useSkillStore } from '@/lib/game/stores';
export function SkillDebug() {
const skills = useSkillStore((s) => s.skills);
const setSkills = useSkillStore.setState;
const setGameState = useGameStore.setState;
const setGameState = useSkillStore.setState;
return (
<Card className="bg-gray-900/80 border-gray-700 md:col-span-2">
+3 -3
View File
@@ -24,7 +24,7 @@ import { EnchantmentsPanel } from './EnchantmentsPanel';
import { useGameToast } from '@/components/game/GameToast';
import { ConfirmDialog } from '@/components/game/ConfirmDialog';
import { equipItem, unequipItem, deleteEquipmentInstance } from '@/lib/game/crafting-actions';
import { useCombatStore } from '@/lib/game/stores';
import { useCombatStore, useCraftingStore } from '@/lib/game/stores';
// Rarity color mappings using design system tokens
export const RARITY_BORDER_COLORS: Record<string, string> = {
@@ -120,8 +120,8 @@ export function EquipmentTab() {
const [deleteConfirm, setDeleteConfirm] = useState<{ instanceId: string; name: string } | null>(null);
// Use modular store directly - MUST be called before any conditional returns
const equippedInstances = useCombatStore((s) => s.equippedInstances);
const equipmentInstances = useCombatStore((s) => s.equipmentInstances);
const equippedInstances = useCraftingStore((s) => s.equippedInstances);
const equipmentInstances = useCraftingStore((s) => s.equipmentInstances);
// Get unequipped items - hooks must be called before conditional returns
const equippedIds = useMemo(() =>
+1 -122
View File
@@ -1,122 +1 @@
'use client';
import { GameCard, ElementBadge, ActionButton } from '@/components/ui';
import { Button } from '@/components/ui/button';
import { ELEMENTS } from '@/lib/game/constants';
interface LabTabProps {
store: {
rawMana: number;
elements: Record<string, { current: number; max: number; unlocked: boolean }>;
skills: Record<string, number>;
craftComposite: (target: string) => void;
};
}
export function LabTab({ store }: LabTabProps) {
// Render elemental mana grid - only show elements with current > 0
const renderElementsGrid = () => (
<div className="grid grid-cols-3 sm:grid-cols-4 md:grid-cols-6 lg:grid-cols-8 gap-2">
{Object.entries(store.elements || {})
.filter(([, state]) => state.unlocked && state.current > 0)
.map(([id, state]) => {
const def = ELEMENTS[id];
return (
<div
key={id}
className="p-2 rounded border border-[var(--border-subtle)] bg-[var(--bg-sunken)]"
>
<div className="text-lg text-center">
<ElementBadge elementId={id} size="sm" />
</div>
<div className="text-xs font-semibold text-center" style={{ color: `var(--mana-${id})` }}>{def?.name}</div>
<div className="text-xs text-[var(--text-secondary)] font-[var(--font-mono)] text-center">{state.current}/{state.max}</div>
</div>
);
})}
</div>
);
// Render composite crafting
const renderCompositeCrafting = () => {
const compositeElements = Object.entries(ELEMENTS)
.filter(([, def]) => def.recipe)
.filter(([id]) => (store.elements || {})[id]?.unlocked);
if (compositeElements.length === 0) return null;
return (
<GameCard>
<div className="pb-2">
<h3 className="text-sm font-semibold text-[var(--text-primary)]">Composite Crafting</h3>
</div>
<div className="space-y-2">
{compositeElements.map(([id, def]) => {
const recipe = def.recipe || [];
const canCraft = recipe.every(r => ((store.elements || {})[r]?.current || 0) >= 1);
const craftBonus = 1 + (store.skills.elemCrafting || 0) * 0.25;
const output = Math.floor(craftBonus);
return (
<div key={id} className="p-2 rounded border border-[var(--border-subtle)] bg-[var(--bg-sunken)] flex items-center justify-between">
<div className="flex items-center gap-2">
<ElementBadge elementId={id} size="md" />
<span className="text-sm" style={{ color: `var(--mana-${id})` }}>{def.name}</span>
<span className="text-xs text-[var(--text-muted)]">
({recipe.map(r => {
const rDef = ELEMENTS[r];
return rDef?.sym || r;
}).join(' + ')})
</span>
</div>
<Button
size="sm"
variant="outline"
disabled={!canCraft}
onClick={() => store.craftComposite(id)}
className={!canCraft ? 'opacity-50 cursor-not-allowed' : ''}
>
Craft ({output})
</Button>
</div>
);
})}
</div>
</GameCard>
);
};
// Check if there are any unlocked elements with current > 0
const hasUnlockedElements = Object.values(store.elements || {}).some(e => e.unlocked && e.current > 0);
if (!hasUnlockedElements) {
return (
<GameCard>
<div className="pt-6">
<div className="text-center text-[var(--text-muted)]">
No elemental mana available. Gather or convert mana to see elemental pools.
</div>
</div>
</GameCard>
);
}
return (
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
{/* Elemental Mana Display */}
<GameCard className="lg:col-span-2">
<div className="pb-2">
<h3 className="text-sm font-semibold text-[var(--text-primary)]">Elemental Mana</h3>
</div>
<div>
{renderElementsGrid()}
</div>
</GameCard>
{/* Composite Crafting */}
{renderCompositeCrafting()}
</div>
);
}
LabTab.displayName = "LabTab";
export { LabTab } from '../LabTab';
+3 -3
View File
@@ -1,6 +1,6 @@
'use client';
import { useCombatStore, useManaStore, useSkillStore } from '@/lib/game/stores';
import { useCombatStore, useCraftingStore, useManaStore, useSkillStore } from '@/lib/game/stores';
import { GameCard, ElementBadge } from '@/components/ui';
import { Badge } from '@/components/ui/badge';
import { ELEMENTS, SPELLS_DEF } from '@/lib/game/constants';
@@ -14,8 +14,8 @@ export function SpellsTab() {
const activeSpell = useCombatStore((s) => s.activeSpell);
const setSpell = useCombatStore((s) => s.setSpell);
const equippedInstances = useCombatStore((s) => s.equippedInstances);
const equipmentInstances = useCombatStore((s) => s.equipmentInstances);
const equippedInstances = useCraftingStore((s) => s.equippedInstances);
const equipmentInstances = useCraftingStore((s) => s.equipmentInstances);
const rawMana = useManaStore((s) => s.rawMana);
const elements = useManaStore((s) => s.elements);
+1 -1
View File
@@ -6,7 +6,7 @@ export { SpireTab } from './SpireTab';
export { SpellsTab } from './SpellsTab';
export { LabTab } from './LabTab';
// SkillsTab is now exported from src/components/game/index.ts
// export { SkillsTab } from './SkillsTab';
export { SkillsTab } from '../SkillsTab';
export { StatsTab } from './StatsTab';
export { EquipmentTab } from './EquipmentTab';
export { AttunementsTab } from './AttunementsTab';
+9
View File
@@ -66,6 +66,15 @@ export interface PrestigeState {
// Reset
resetPrestige: () => void;
// Debug helpers
debugSetSignedPacts: (pacts: number[]) => void;
debugSetPactDetails: (details: Record<number, {
floor: number;
guardianId: string;
signedAt: { day: number; hour: number };
skillLevels: Record<string, number>;
}>) => void;
}
const initialState = {