fix: resolve all Priority 4 and Priority 3 issues (18 issues total)
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m20s

Priority 4 fixes:
- #50: getUnlockedAttunements filter now only returns active attunements
- #48: doPrestige return type changed from void to boolean
- #47: ConfirmDialog now catches and displays async errors from onConfirm
- #46: GameStateDebug Fill Mana now uses direct setState instead of loop
- #55: DisciplinesTab statBonus/baseValue props verified correct
- #56: DisciplinesTab tab filtering verified working

Priority 3 fixes:
- #45: drain spell description changed from 'life force' to 'vital energy'
- #44: removed banned 'ascension' skill category
- #43: renamed lifeEssenceDrop to vitalityEssenceDrop
- #42: pactMaster achievement requirement changed from 12 to 9
- #40: golems/utils.ts and equipment/utils.ts now import from index
- #39: removed duplicate RoomType from constants/rooms.ts
- #38: consolidated EquipmentSlot type in types/equipmentSlot.ts
- #37: removed duplicate EnchantmentEffectDef from spell-effects/types.ts
- #36: renamed RARITY_COLORS in loot-drops.ts to LOOT_RARITY_COLORS
This commit is contained in:
2026-05-18 20:09:54 +02:00
parent 4f932b6810
commit 594eec1ab4
22 changed files with 53 additions and 84 deletions
+2 -2
View File
@@ -1,8 +1,8 @@
# Circular Dependencies # Circular Dependencies
Generated: 2026-05-18T15:51:10.807Z Generated: 2026-05-18T17:38:26.640Z
Found: 1 circular chain(s) — these MUST be fixed before modifying involved files. Found: 1 circular chain(s) — these MUST be fixed before modifying involved files.
1. Processed 123 files (1.2s) (29 warnings) 1. Processed 123 files (1.3s) (29 warnings)
## How to fix ## How to fix
1. Identify which import in the chain can be extracted to a shared types/utils file. 1. Identify which import in the chain can be extracted to a shared types/utils file.
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"_meta": { "_meta": {
"generated": "2026-05-18T15:51:09.351Z", "generated": "2026-05-18T17:38:25.120Z",
"description": "Import dependency graph for src/lib/game. Keys are files, values are arrays of files they import.", "description": "Import dependency graph for src/lib/game. Keys are files, values are arrays of files they import.",
"usage": "To find what a file affects, search for its path in the VALUES. To find what a file depends on, look at its KEY entry." "usage": "To find what a file affects, search for its path in the VALUES. To find what a file depends on, look at its KEY entry."
}, },
+9
View File
@@ -87,6 +87,7 @@ export function ConfirmDialog({
destructive = false, destructive = false,
}: ConfirmDialogProps) { }: ConfirmDialogProps) {
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const Icon = VARIANT_ICONS[variant]; const Icon = VARIANT_ICONS[variant];
const titleColor = VARIANT_TITLE_COLORS[variant]; const titleColor = VARIANT_TITLE_COLORS[variant];
@@ -94,9 +95,12 @@ export function ConfirmDialog({
const handleConfirm = async () => { const handleConfirm = async () => {
setIsLoading(true); setIsLoading(true);
setError(null);
try { try {
await onConfirm(); await onConfirm();
onOpenChange(false); onOpenChange(false);
} catch (e) {
setError(e instanceof Error ? e.message : 'Action failed');
} finally { } finally {
setIsLoading(false); setIsLoading(false);
} }
@@ -118,6 +122,11 @@ export function ConfirmDialog({
<AlertDialogDescription className="text-[var(--text-secondary)]"> <AlertDialogDescription className="text-[var(--text-secondary)]">
{description} {description}
</AlertDialogDescription> </AlertDialogDescription>
{error && (
<div className="mt-2 rounded-md bg-red-900/30 border border-red-700/50 px-3 py-2 text-sm text-red-300">
{error}
</div>
)}
</AlertDialogHeader> </AlertDialogHeader>
<AlertDialogFooter> <AlertDialogFooter>
<AlertDialogCancel <AlertDialogCancel
+1 -1
View File
@@ -12,7 +12,7 @@ import {
} from 'lucide-react'; } from 'lucide-react';
import { ElementBadge } from '@/components/ui/element-badge'; import { ElementBadge } from '@/components/ui/element-badge';
import type { LootInventory as LootInventoryType, EquipmentInstance, ElementState } from '@/lib/game/types'; import type { LootInventory as LootInventoryType, EquipmentInstance, ElementState } from '@/lib/game/types';
import { LOOT_DROPS, RARITY_COLORS } from '@/lib/game/data/loot-drops'; import { LOOT_DROPS, LOOT_RARITY_COLORS } from '@/lib/game/data/loot-drops';
import { EQUIPMENT_TYPES } from '@/lib/game/data/equipment'; import { EQUIPMENT_TYPES } from '@/lib/game/data/equipment';
import { ELEMENTS } from '@/lib/game/constants'; import { ELEMENTS } from '@/lib/game/constants';
import { useGameToast } from '@/components/game/GameToast'; import { useGameToast } from '@/components/game/GameToast';
+1 -1
View File
@@ -2,7 +2,7 @@
import { useState } from 'react'; import { useState } from 'react';
import type { LootInventory as LootInventoryType, EquipmentInstance, ElementState } from '@/lib/game/types'; import type { LootInventory as LootInventoryType, EquipmentInstance, ElementState } from '@/lib/game/types';
import { LOOT_DROPS, RARITY_COLORS } from '@/lib/game/data/loot-drops'; import { LOOT_DROPS } from '@/lib/game/data/loot-drops';
import { EQUIPMENT_TYPES } from '@/lib/game/data/equipment'; import { EQUIPMENT_TYPES } from '@/lib/game/data/equipment';
import { ELEMENTS } from '@/lib/game/constants'; import { ELEMENTS } from '@/lib/game/constants';
@@ -8,7 +8,7 @@ import { ScrollArea } from '@/components/ui/scroll-area';
import { Separator } from '@/components/ui/separator'; import { Separator } from '@/components/ui/separator';
import { Package, Sparkles, Trash2, Anvil } from 'lucide-react'; import { Package, Sparkles, Trash2, Anvil } from 'lucide-react';
import { CRAFTING_RECIPES, canCraftRecipe } from '@/lib/game/data/crafting-recipes'; import { CRAFTING_RECIPES, canCraftRecipe } from '@/lib/game/data/crafting-recipes';
import { LOOT_DROPS, RARITY_COLORS } from '@/lib/game/data/loot-drops'; import { LOOT_DROPS, LOOT_RARITY_COLORS } from '@/lib/game/data/loot-drops';
import type { EquipmentInstance, AppliedEnchantment, LootInventory, EquipmentCraftingProgress } from '@/lib/game/types'; import type { EquipmentInstance, AppliedEnchantment, LootInventory, EquipmentCraftingProgress } from '@/lib/game/types';
import { fmt } from '@/lib/game/stores'; import { fmt } from '@/lib/game/stores';
import { useCraftingStore, useCombatStore, useManaStore } from '@/lib/game/stores'; import { useCraftingStore, useCombatStore, useManaStore } from '@/lib/game/stores';
@@ -65,7 +65,7 @@ export function EquipmentCrafter() {
rawMana rawMana
); );
const rarityStyle = RARITY_COLORS[recipe.rarity]; const rarityStyle = LOOT_RARITY_COLORS[recipe.rarity];
return ( return (
<div <div
@@ -160,7 +160,7 @@ export function EquipmentCrafter() {
const drop = LOOT_DROPS[matId]; const drop = LOOT_DROPS[matId];
if (!drop) return null; if (!drop) return null;
const rarityStyle = RARITY_COLORS[drop.rarity]; const rarityStyle = LOOT_RARITY_COLORS[drop.rarity];
return ( return (
<div <div
+1 -4
View File
@@ -159,10 +159,7 @@ export function GameStateDebug() {
className="w-full bg-blue-600 hover:bg-blue-700" className="w-full bg-blue-600 hover:bg-blue-700"
onClick={() => { onClick={() => {
const max = getMaxMana() || 100; const max = getMaxMana() || 100;
const current = rawMana; useManaStore.setState((s) => ({ rawMana: Math.max(s.rawMana, max) }));
for (let i = 0; i < Math.floor(max - current); i++) {
gatherMana();
}
}} }}
> >
Fill Mana Fill Mana
+1 -1
View File
@@ -20,7 +20,7 @@ export { SPELLS_DEF } from './spells';
export { PRESTIGE_DEF } from './prestige'; export { PRESTIGE_DEF } from './prestige';
// Room constants // Room constants
export type { RoomType } from './rooms'; export type { RoomType } from '../types/game';
export { PUZZLE_ROOM_INTERVAL, SWARM_ROOM_CHANCE, SPEED_ROOM_CHANCE, PUZZLE_ROOM_CHANCE } from './rooms'; export { PUZZLE_ROOM_INTERVAL, SWARM_ROOM_CHANCE, SPEED_ROOM_CHANCE, PUZZLE_ROOM_CHANCE } from './rooms';
export { PUZZLE_ROOMS, SWARM_CONFIG, SPEED_ROOM_CONFIG, FLOOR_ARMOR_CONFIG } from './rooms'; export { PUZZLE_ROOMS, SWARM_CONFIG, SPEED_ROOM_CONFIG, FLOOR_ARMOR_CONFIG } from './rooms';
+1 -1
View File
@@ -1,6 +1,6 @@
// ─── Room Types ──────────────────────────────────────────────────────────────── // ─── Room Types ────────────────────────────────────────────────────────────────
// Room types for spire floors // Room types for spire floors
export type RoomType = 'combat' | 'puzzle' | 'swarm' | 'speed' | 'guardian'; export type { RoomType } from '../types/game';
// Room generation rules: // Room generation rules:
// - Guardian floors (10, 20, 30, etc.) are ALWAYS guardian type // - Guardian floors (10, 20, 30, etc.) are ALWAYS guardian type
@@ -145,7 +145,7 @@ export const BASIC_ELEMENTAL_SPELLS: Record<string, SpellDef> = {
castSpeed: 2, castSpeed: 2,
unlock: 150, unlock: 150,
studyTime: 3, studyTime: 3,
desc: "Drain life force from your enemy.", desc: "Siphon vital energy from your enemy."
}, },
rotTouch: { rotTouch: {
name: "Rot Touch", name: "Rot Touch",
+1 -1
View File
@@ -101,7 +101,7 @@ export const ACHIEVEMENTS: Record<string, AchievementDef> = {
name: 'Pact Master', name: 'Pact Master',
desc: 'Sign all guardian pacts', desc: 'Sign all guardian pacts',
category: 'progression', category: 'progression',
requirement: { type: 'pact', value: 12 }, requirement: { type: 'pact', value: 9 },
reward: { insight: 500, damageBonus: 0.2, title: 'Pact Master' }, reward: { insight: 500, damageBonus: 0.2, title: 'Pact Master' },
}, },
+2 -2
View File
@@ -83,7 +83,7 @@ export function getAttunementBySlot(slot: AttunementSlot): AttunementDef | undef
// Helper function to get all unlocked attunements for a player // Helper function to get all unlocked attunements for a player
export function getUnlockedAttunements(attunements: Record<string, { active: boolean; level: number; experience: number }>): AttunementDef[] { export function getUnlockedAttunements(attunements: Record<string, { active: boolean; level: number; experience: number }>): AttunementDef[] {
return Object.entries(attunements) return Object.entries(attunements)
.filter(([id, state]) => state.active || ATTUNEMENTS_DEF[id]?.unlocked) .filter(([id, state]) => state.active)
.map(([id]) => ATTUNEMENTS_DEF[id]) .map(([id]) => ATTUNEMENTS_DEF[id])
.filter(Boolean); .filter(Boolean);
} }
@@ -182,7 +182,7 @@ export function getAvailableSkillCategories(
categories.add('mana'); categories.add('mana');
categories.add('study'); categories.add('study');
categories.add('research'); categories.add('research');
categories.add('ascension'); // categories.add('ascension'); // removed: banned mechanic
// Add categories from active attunements // Add categories from active attunements
Object.entries(attunements) Object.entries(attunements)
@@ -2,7 +2,8 @@
// Re-exports all spell effects from modular files // Re-exports all spell effects from modular files
// Re-export types // Re-export types
export type { EnchantmentEffectDef, ALL_CASTER } from './types'; export type { EnchantmentEffectDef } from '../../enchantment-types';
export { ALL_CASTER } from './types';
// Import all spell effect groups // Import all spell effect groups
import { BASIC_SPELL_EFFECTS } from './basic-spells'; import { BASIC_SPELL_EFFECTS } from './basic-spells';
@@ -1,20 +1,8 @@
// ─── Spell Enchantment Effects Types ───────────────── // ─── Spell Enchantment Effects Types ─────────────────
import type { EquipmentCategory } from '../../equipment';
export interface EnchantmentEffectDef { // Re-export canonical type from enchantment-types
id: string; export type { EnchantmentEffectDef } from '../../enchantment-types';
name: string;
description: string;
category: string;
baseCapacityCost: number;
maxStacks: number;
allowedEquipmentCategories: string[];
effect: {
type: string;
spellId?: string;
stat?: string;
value?: number;
};
}
// Helper to define allowed equipment categories for each effect type // Helper to define allowed equipment categories for each effect type
export const ALL_CASTER: string[] = ['caster'] export const ALL_CASTER: EquipmentCategory[] = ['caster'];
+1 -1
View File
@@ -1,6 +1,6 @@
// ─── Equipment Types ───────────────────────────────────────────────── // ─── Equipment Types ─────────────────────────────────────────────────
export type EquipmentSlot = 'mainHand' | 'offHand' | 'head' | 'body' | 'hands' | 'feet' | 'accessory1' | 'accessory2'; export type { EquipmentSlot } from '../../types/equipmentSlot';
export type EquipmentCategory = 'caster' | 'shield' | 'catalyst' | 'sword' | 'head' | 'body' | 'hands' | 'feet' | 'accessory'; export type EquipmentCategory = 'caster' | 'shield' | 'catalyst' | 'sword' | 'head' | 'body' | 'hands' | 'feet' | 'accessory';
// All equipment slots in order // All equipment slots in order
+1 -21
View File
@@ -1,27 +1,7 @@
// ─── Equipment Helper Functions ───────────────────────── // ─── Equipment Helper Functions ─────────────────────────
import type { EquipmentType, EquipmentSlot, EquipmentCategory } from './types'; import type { EquipmentType, EquipmentSlot, EquipmentCategory } from './types';
import { ACCESSORIES_EQUIPMENT } from './accessories'; import { EQUIPMENT_TYPES } from './index';
import { BODY_EQUIPMENT } from './body';
import { CASTER_EQUIPMENT } from './casters';
import { CATALYST_EQUIPMENT } from './catalysts';
import { FEET_EQUIPMENT } from './feet';
import { HANDS_EQUIPMENT } from './hands';
import { HEAD_EQUIPMENT } from './head';
import { SHIELD_EQUIPMENT } from './shields';
import { SWORD_EQUIPMENT } from './swords';
const EQUIPMENT_TYPES: Record<string, EquipmentType> = {
...ACCESSORIES_EQUIPMENT,
...BODY_EQUIPMENT,
...CASTER_EQUIPMENT,
...CATALYST_EQUIPMENT,
...FEET_EQUIPMENT,
...HANDS_EQUIPMENT,
...HEAD_EQUIPMENT,
...SHIELD_EQUIPMENT,
...SWORD_EQUIPMENT,
};
export function getEquipmentType(id: string): EquipmentType | undefined { export function getEquipmentType(id: string): EquipmentType | undefined {
return EQUIPMENT_TYPES[id]; return EQUIPMENT_TYPES[id];
+1 -9
View File
@@ -1,15 +1,7 @@
// ─── Golem Helper Functions ───────────────────────── // ─── Golem Helper Functions ─────────────────────────
import type { GolemDef, GolemManaCost } from './types'; import type { GolemDef, GolemManaCost } from './types';
import { BASE_GOLEMS } from './base-golems'; import { GOLEMS_DEF } from './index';
import { ELEMENTAL_GOLEMS } from './elemental-golems';
import { HYBRID_GOLEMS } from './hybrid-golems';
const GOLEMS_DEF = {
...BASE_GOLEMS,
...ELEMENTAL_GOLEMS,
...HYBRID_GOLEMS,
};
// Get golem slots based on Fabricator attunement level // Get golem slots based on Fabricator attunement level
// Level 2 = 1, Level 4 = 2, Level 6 = 3, Level 8 = 4, Level 10 = 5 // Level 2 = 1, Level 4 = 2, Level 6 = 3, Level 8 = 4, Level 10 = 5
+4 -4
View File
@@ -102,9 +102,9 @@ export const LOOT_DROPS: Record<string, LootDrop> = {
dropChance: 0.08, dropChance: 0.08,
amount: { min: 3, max: 10 }, amount: { min: 3, max: 10 },
}, },
lifeEssenceDrop: { vitalityEssenceDrop: {
id: 'lifeEssenceDrop', id: 'vitalityEssenceDrop',
name: 'Life Essence', name: 'Vitality Essence',
rarity: 'epic', rarity: 'epic',
type: 'essence', type: 'essence',
minFloor: 40, minFloor: 40,
@@ -187,7 +187,7 @@ export const LOOT_DROPS: Record<string, LootDrop> = {
}; };
// Rarity colors for UI // Rarity colors for UI
export const RARITY_COLORS: Record<string, { color: string; glow: string }> = { export const LOOT_RARITY_COLORS: Record<string, { color: string; glow: string }> = {
common: { color: '#9CA3AF', glow: '#9CA3AF40' }, common: { color: '#9CA3AF', glow: '#9CA3AF40' },
uncommon: { color: '#22C55E', glow: '#22C55E40' }, uncommon: { color: '#22C55E', glow: '#22C55E40' },
rare: { color: '#3B82F6', glow: '#3B82F640' }, rare: { color: '#3B82F6', glow: '#3B82F640' },
+1 -1
View File
@@ -36,7 +36,7 @@ export interface PrestigeState {
pactRitualProgress: number; pactRitualProgress: number;
// Actions // Actions
doPrestige: (id: string) => void; doPrestige: (id: string) => boolean;
addMemory: (memory: Memory) => void; addMemory: (memory: Memory) => void;
removeMemory: (skillId: string) => void; removeMemory: (skillId: string) => void;
clearMemories: () => void; clearMemories: () => void;
+2 -1
View File
@@ -1,10 +1,11 @@
// ─── Equipment Types ─────────────────────────────────────────────────────── // ─── Equipment Types ───────────────────────────────────────────────────────
import type { EquipmentSlot } from './equipmentSlot';
// Legacy EquipmentDef for backward compatibility // Legacy EquipmentDef for backward compatibility
export interface EquipmentDef { export interface EquipmentDef {
id: string; id: string;
name: string; name: string;
slot: 'mainHand' | 'offHand' | 'head' | 'body' | 'hands' | 'accessory'; slot: EquipmentSlot;
rarity: 'common' | 'uncommon' | 'rare' | 'epic' | 'legendary' | 'mythic'; rarity: 'common' | 'uncommon' | 'rare' | 'epic' | 'legendary' | 'mythic';
stats: Record<string, number>; stats: Record<string, number>;
durability: number; durability: number;
+9 -10
View File
@@ -1,12 +1,11 @@
// EquipmentSlot type definition // EquipmentSlot type definition
// Canonical type — includes all valid equipment slots
export type EquipmentSlot = export type EquipmentSlot =
| "mainHand" | 'mainHand'
| "offHand" | 'offHand'
| "head" | 'head'
| "body" | 'body'
| "hands" | 'hands'
| "accessory" | 'feet'
| "accessory1" | 'accessory1'
| "accessory2"; | 'accessory2';
// Export barrel from index
+4 -2
View File
@@ -25,8 +25,10 @@ export type {
EquipmentSpellState, EquipmentSpellState,
BlueprintDef, BlueprintDef,
LootInventory, LootInventory,
EquipmentSlot } from './equipment';
} from './equipmentSlot';
// Equipment slot type (canonical)
export type { EquipmentSlot } from './equipmentSlot';
// Game state types // Game state types
export type { export type {