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
+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';