Fix multiple bugs and add debug features
All checks were successful
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m56s

- Add debug option to show component names at top of each component
- Fix mana lists to hide empty elemental mana types in ManaDisplay and LabTab
- Fix enchantment designing getting stuck at 99% by auto-saving design on completion
- Add resetFloorHP function and debug button to fix stuck floor HP issues
- Store design data in DesignProgress for proper completion handling
This commit is contained in:
Z User
2026-03-29 13:14:26 +00:00
parent 6dc301bd7b
commit 300e43f8be
13 changed files with 321 additions and 153 deletions

View File

@@ -1225,3 +1225,25 @@ Checked 846 installs across 915 packages (no changes) [60.00ms]
$ prisma db push
Failed to load config file "/home/z/my-project" as a TypeScript/JavaScript module. Error: Error: ENOTDIR: not a directory, lstat '/home/z/my-project/.config/prisma'
error: script "db:push" exited with code 1
==========================================
[2026-03-29 11:16:56] Starting: bun install
==========================================
[BUN] Installing dependencies...
[0.08ms] ".env"
bun install v1.3.10 (30e609e0)
+ next@16.1.3
49 packages installed [2.62s]
==========================================
[2026-03-29 11:16:59] Completed: bun install
[LOG] Step: bun install | Duration: 3s
==========================================
==========================================
[2026-03-29 11:16:59] Starting: bun run db:push
==========================================
[BUN] Setting up database...
$ prisma db push
Failed to load config file "/home/z/my-project" as a TypeScript/JavaScript module. Error: Error: ENOTDIR: not a directory, lstat '/home/z/my-project/.config/prisma'
error: script "db:push" exited with code 1

View File

@@ -1 +1 @@
488
486

View File

@@ -2,6 +2,7 @@ import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
import { Toaster } from "@/components/ui/toaster";
import { DebugProvider } from "@/lib/game/debug-context";
const geistSans = Geist({
variable: "--font-geist-sans",
@@ -14,25 +15,10 @@ const geistMono = Geist_Mono({
});
export const metadata: Metadata = {
title: "Z.ai Code Scaffold - AI-Powered Development",
description: "Modern Next.js scaffold optimized for AI-powered development with Z.ai. Built with TypeScript, Tailwind CSS, and shadcn/ui.",
keywords: ["Z.ai", "Next.js", "TypeScript", "Tailwind CSS", "shadcn/ui", "AI development", "React"],
authors: [{ name: "Z.ai Team" }],
icons: {
icon: "https://z-cdn.chatglm.cn/z-ai/static/logo.svg",
},
openGraph: {
title: "Z.ai Code Scaffold",
description: "AI-powered development with modern React stack",
url: "https://chat.z.ai",
siteName: "Z.ai",
type: "website",
},
twitter: {
card: "summary_large_image",
title: "Z.ai Code Scaffold",
description: "AI-powered development with modern React stack",
},
title: "Mana Loop",
description: "A time-loop incremental game where you climb the Spire and sign pacts with guardians.",
keywords: ["Mana Loop", "incremental game", "idle game", "time loop"],
authors: [{ name: "Mana Loop Team" }],
};
export default function RootLayout({
@@ -45,7 +31,9 @@ export default function RootLayout({
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased bg-background text-foreground`}
>
<DebugProvider>
{children}
</DebugProvider>
<Toaster />
</body>
</html>

View File

@@ -17,6 +17,7 @@ import { CraftingTab, SpireTab, SpellsTab, LabTab, SkillsTab, StatsTab, Equipmen
import { ActionButtons, CalendarDisplay, ManaDisplay, TimeDisplay } from '@/components/game';
import { LootInventoryDisplay } from '@/components/game/LootInventory';
import { AchievementsDisplay } from '@/components/game/AchievementsDisplay';
import { DebugName } from '@/lib/game/debug-context';
export default function ManaLoopGame() {
const [activeTab, setActiveTab] = useState('spire');
@@ -175,6 +176,7 @@ export default function ManaLoopGame() {
{/* Left Panel - Mana & Actions */}
<div className="md:w-80 space-y-4 flex-shrink-0">
{/* Mana Display */}
<DebugName name="ManaDisplay">
<ManaDisplay
rawMana={store.rawMana}
maxMana={maxMana}
@@ -186,8 +188,10 @@ export default function ManaLoopGame() {
onGatherEnd={handleGatherEnd}
elements={store.elements}
/>
</DebugName>
{/* Action Buttons */}
<DebugName name="ActionButtons">
<ActionButtons
currentAction={store.currentAction}
designProgress={store.designProgress}
@@ -195,15 +199,19 @@ export default function ManaLoopGame() {
applicationProgress={store.applicationProgress}
setAction={store.setAction}
/>
</DebugName>
{/* Calendar */}
<DebugName name="CalendarDisplay">
<CalendarDisplay
day={store.day}
hour={store.hour}
incursionStrength={incursionStrength}
/>
</DebugName>
{/* Loot Inventory */}
<DebugName name="LootInventoryDisplay">
<LootInventoryDisplay
inventory={store.lootInventory}
elements={store.elements}
@@ -211,8 +219,10 @@ export default function ManaLoopGame() {
onDeleteMaterial={store.deleteMaterial}
onDeleteEquipment={store.deleteEquipmentInstance}
/>
</DebugName>
{/* Achievements */}
<DebugName name="AchievementsDisplay">
<AchievementsDisplay
achievements={store.achievements}
gameState={{
@@ -225,6 +235,7 @@ export default function ManaLoopGame() {
combo: store.combo,
}}
/>
</DebugName>
</div>
{/* Right Panel - Tabs */}
@@ -244,34 +255,49 @@ export default function ManaLoopGame() {
</TabsList>
<TabsContent value="spire">
<DebugName name="SpireTab">
<SpireTab store={store} />
</DebugName>
</TabsContent>
<TabsContent value="attunements">
<DebugName name="AttunementsTab">
<AttunementsTab store={store} />
</DebugName>
</TabsContent>
<TabsContent value="skills">
<DebugName name="SkillsTab">
<SkillsTab store={store} />
</DebugName>
</TabsContent>
<TabsContent value="spells">
<DebugName name="SpellsTab">
<SpellsTab store={store} />
</DebugName>
</TabsContent>
<TabsContent value="equipment">
<DebugName name="EquipmentTab">
<EquipmentTab store={store} />
</DebugName>
</TabsContent>
<TabsContent value="crafting">
<DebugName name="CraftingTab">
<CraftingTab store={store} />
</DebugName>
</TabsContent>
<TabsContent value="lab">
<DebugName name="LabTab">
<LabTab store={store} />
</DebugName>
</TabsContent>
<TabsContent value="stats">
<DebugName name="StatsTab">
<StatsTab
store={store}
upgradeEffects={upgradeEffects}
@@ -285,14 +311,19 @@ export default function ManaLoopGame() {
studySpeedMult={studySpeedMult}
studyCostMult={studyCostMult}
/>
</DebugName>
</TabsContent>
<TabsContent value="grimoire">
<DebugName name="GrimoireTab">
{renderGrimoireTab()}
</DebugName>
</TabsContent>
<TabsContent value="debug">
<DebugName name="DebugTab">
<DebugTab store={store} />
</DebugName>
</TabsContent>
</Tabs>
</div>

View File

@@ -33,9 +33,9 @@ export function ManaDisplay({
}: ManaDisplayProps) {
const [expanded, setExpanded] = useState(true);
// Get unlocked elements sorted by current amount
// Get unlocked elements with current > 0, sorted by current amount
const unlockedElements = Object.entries(elements)
.filter(([, state]) => state.unlocked)
.filter(([, state]) => state.unlocked && state.current > 0)
.sort((a, b) => b[1].current - a[1].current);
return (

View File

@@ -144,26 +144,6 @@ export function CraftingTab({ store }: CraftingTabProps) {
}
};
// Complete design after progress
const handleCompleteDesign = () => {
if (!designProgress || !selectedEquipmentType) return;
const design: EnchantmentDesign = {
id: designProgress.designId,
name: designName || 'Untitled Design',
equipmentType: selectedEquipmentType,
effects: selectedEffects,
totalCapacityUsed: designCapacityCost,
designTime,
created: Date.now(),
};
saveDesign(design);
setDesignName('');
setSelectedEquipmentType(null);
setSelectedEffects([]);
};
// Get available effects for selected equipment type (only unlocked ones)
const getAvailableEffects = () => {
if (!selectedEquipmentType) return [];
@@ -188,15 +168,15 @@ export function CraftingTab({ store }: CraftingTabProps) {
<CardContent>
{designProgress ? (
<div className="space-y-3">
<div className="text-sm text-gray-400">Designing for: {EQUIPMENT_TYPES[selectedEquipmentType || '']?.name}</div>
<div className="text-sm text-gray-400">
Designing for: {EQUIPMENT_TYPES[designProgress.equipmentType]?.name}
</div>
<div className="text-sm font-semibold text-amber-300">{designProgress.name}</div>
<Progress value={(designProgress.progress / designProgress.required) * 100} className="h-3" />
<div className="flex justify-between text-xs text-gray-400">
<span>{designProgress.progress.toFixed(1)}h / {designProgress.required.toFixed(1)}h</span>
<Button size="sm" variant="outline" onClick={cancelDesign}>Cancel</Button>
</div>
{designProgress.progress >= designProgress.required && (
<Button onClick={handleCompleteDesign} className="w-full">Complete Design</Button>
)}
</div>
) : (
<ScrollArea className="h-64">
@@ -235,7 +215,7 @@ export function CraftingTab({ store }: CraftingTabProps) {
) : designProgress ? (
<div className="space-y-2">
<div className="text-sm text-gray-400">Design in progress...</div>
{selectedEffects.map(eff => {
{designProgress.effects.map(eff => {
const def = ENCHANTMENT_EFFECTS[eff.effectId];
return (
<div key={eff.effectId} className="flex justify-between text-sm">

View File

@@ -5,14 +5,17 @@ import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Separator } from '@/components/ui/separator';
import { Switch } from '@/components/ui/switch';
import { Label } from '@/components/ui/label';
import {
RotateCcw, Bug, Plus, Minus, Lock, Unlock, Zap,
Clock, Star, AlertTriangle, Sparkles, Settings
Clock, Star, AlertTriangle, Sparkles, Settings, Eye
} from 'lucide-react';
import type { GameStore } from '@/lib/game/types';
import { ATTUNEMENTS_DEF } from '@/lib/game/data/attunements';
import { ELEMENTS } from '@/lib/game/constants';
import { fmt } from '@/lib/game/store';
import { useDebug } from '@/lib/game/debug-context';
interface DebugTabProps {
store: GameStore;
@@ -20,6 +23,7 @@ interface DebugTabProps {
export function DebugTab({ store }: DebugTabProps) {
const [confirmReset, setConfirmReset] = useState(false);
const { showComponentNames, toggleComponentNames } = useDebug();
const handleReset = () => {
if (confirmReset) {
@@ -86,6 +90,31 @@ export function DebugTab({ store }: DebugTabProps) {
</CardContent>
</Card>
{/* Display Options */}
<Card className="bg-gray-900/80 border-gray-700">
<CardHeader className="pb-2">
<CardTitle className="text-cyan-400 text-sm flex items-center gap-2">
<Eye className="w-4 h-4" />
Display Options
</CardTitle>
</CardHeader>
<CardContent>
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<Label htmlFor="show-component-names" className="text-sm">Show Component Names</Label>
<p className="text-xs text-gray-400">
Display component names at the top of each component for debugging
</p>
</div>
<Switch
id="show-component-names"
checked={showComponentNames}
onCheckedChange={toggleComponentNames}
/>
</div>
</CardContent>
</Card>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{/* Game Reset */}
<Card className="bg-gray-900/80 border-gray-700">
@@ -371,6 +400,18 @@ export function DebugTab({ store }: DebugTabProps) {
>
Skip to Floor 100
</Button>
<Button
size="sm"
variant="outline"
onClick={() => {
// Reset floor HP
if (store.resetFloorHP) {
store.resetFloorHP();
}
}}
>
Reset Floor HP
</Button>
</div>
</CardContent>
</Card>

View File

@@ -14,11 +14,11 @@ interface LabTabProps {
}
export function LabTab({ store }: LabTabProps) {
// Render elemental mana grid
// 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)
.filter(([, state]) => state.unlocked && state.current > 0)
.map(([id, state]) => {
const def = ELEMENTS[id];
return (
@@ -82,15 +82,15 @@ export function LabTab({ store }: LabTabProps) {
);
};
// Check if there are any unlocked elements
const hasUnlockedElements = Object.values(store.elements).some(e => e.unlocked);
// 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 (
<Card className="bg-gray-900/80 border-gray-700">
<CardContent className="pt-6">
<div className="text-center text-gray-500">
No elemental mana available. Elements are unlocked through gameplay.
No elemental mana available. Gather or convert mana to see elemental pools.
</div>
</CardContent>
</Card>

View File

@@ -289,24 +289,20 @@ export function createCraftingSlice(
const efficiencyBonus = (state.skills.efficientEnchant || 0) * 0.05;
const totalCapacityCost = calculateDesignCapacityCost(effects, efficiencyBonus);
// Create design
// Create design ID
const designId = `design_${Date.now()}`;
const designTime = calculateDesignTime(effects);
const design: EnchantmentDesign = {
id: `design_${Date.now()}`,
name,
equipmentType: equipmentTypeId,
effects,
totalCapacityUsed: totalCapacityCost,
designTime,
created: Date.now(),
};
// Store design data in progress
set(() => ({
currentAction: 'design',
designProgress: {
designId: design.id,
designId,
progress: 0,
required: designTime,
name,
equipmentType: equipmentTypeId,
effects,
},
}));
@@ -616,11 +612,27 @@ export function processCraftingTick(
if (state.currentAction === 'design' && state.designProgress) {
const progress = state.designProgress.progress + 0.04; // HOURS_PER_TICK
if (progress >= state.designProgress.required) {
// Design complete - but we need the design data to save it
// This will be handled by the UI calling saveDesign
// Design complete - auto-save the design using stored data
const dp = state.designProgress;
const efficiencyBonus = (state.skills.efficientEnchant || 0) * 0.05;
const totalCapacityCost = calculateDesignCapacityCost(dp.effects, efficiencyBonus);
const completedDesign: EnchantmentDesign = {
id: dp.designId,
name: dp.name,
equipmentType: dp.equipmentType,
effects: dp.effects,
totalCapacityUsed: totalCapacityCost,
designTime: dp.required,
created: Date.now(),
};
updates = {
...updates,
log: ['✅ Enchantment design complete!', ...log],
designProgress: null,
currentAction: 'meditate',
enchantmentDesigns: [...state.enchantmentDesigns, completedDesign],
log: [`✅ Enchantment design "${dp.name}" complete!`, ...log],
};
} else {
updates = {

View File

@@ -0,0 +1,67 @@
'use client';
import { createContext, useContext, useState, useEffect, type ReactNode } from 'react';
interface DebugContextType {
showComponentNames: boolean;
toggleComponentNames: () => void;
}
const DebugContext = createContext<DebugContextType | null>(null);
export function DebugProvider({ children }: { children: ReactNode }) {
// Initialize from localStorage if available
const [showComponentNames, setShowComponentNames] = useState(() => {
if (typeof window !== 'undefined') {
const saved = localStorage.getItem('debug-show-component-names');
return saved === 'true';
}
return false;
});
const toggleComponentNames = () => {
setShowComponentNames(prev => {
const newValue = !prev;
localStorage.setItem('debug-show-component-names', String(newValue));
return newValue;
});
};
return (
<DebugContext.Provider value={{ showComponentNames, toggleComponentNames }}>
{children}
</DebugContext.Provider>
);
}
export function useDebug() {
const context = useContext(DebugContext);
if (!context) {
// Return default values if used outside provider
return { showComponentNames: false, toggleComponentNames: () => {} };
}
return context;
}
// Wrapper component to show component name in debug mode
interface DebugNameProps {
name: string;
children: ReactNode;
}
export function DebugName({ name, children }: DebugNameProps) {
const { showComponentNames } = useDebug();
if (!showComponentNames) {
return <>{children}</>;
}
return (
<div className="relative">
<div className="absolute -top-5 left-0 text-[10px] font-mono text-yellow-400 bg-yellow-900/50 px-1 rounded z-50">
{name}
</div>
{children}
</div>
);
}

View File

@@ -10,6 +10,7 @@ export interface NavigationActions {
// Floor Navigation
setClimbDirection: (direction: 'up' | 'down') => void;
changeFloor: (direction: 'up' | 'down') => void;
resetFloorHP: () => void;
}
// ─── Navigation Slice Factory ─────────────────────────────────────────────────
@@ -59,5 +60,16 @@ export function createNavigationSlice(
log: [`🚶 Moved to floor ${nextFloor}${nextFloorCleared ? ' (respawned)' : ''}.`, ...state.log.slice(0, 49)],
});
},
// Reset current floor HP to max (useful when floor HP gets stuck)
resetFloorHP: () => {
const state = get();
const maxHP = getFloorMaxHP(state.currentFloor);
set({
floorMaxHP: maxHP,
floorHP: maxHP,
log: [`🔄 Floor ${state.currentFloor} HP reset to full.`, ...state.log.slice(0, 49)],
});
},
};
}

View File

@@ -578,6 +578,7 @@ interface GameStore extends GameState, CraftingActions {
debugSetTime: (day: number, hour: number) => void;
debugAddAttunementXP: (attunementId: string, amount: number) => void;
debugSetFloor: (floor: number) => void;
resetFloorHP: () => void;
// Computed getters
getMaxMana: () => number;
@@ -1463,26 +1464,26 @@ export const useGameStore = create<GameStore>()(
if (eff.stacks > effectDef.maxStacks) return false;
}
// Calculate capacity cost
const efficiencyBonus = (state.skills.efficientEnchant || 0) * 0.05;
const totalCapacityCost = effects.reduce((total, eff) =>
total + calculateEffectCapacityCost(eff.effectId, eff.stacks, efficiencyBonus), 0);
// Calculate design time
let designTime = 1;
for (const eff of effects) {
designTime += 0.5 * eff.stacks;
}
// Store pending design in designProgress
// Create design ID
const designId = `design_${Date.now()}`;
// Store design data in progress
set(() => ({
currentAction: 'design',
designProgress: {
designId: `design_${Date.now()}`,
designId,
progress: 0,
required: designTime,
name,
equipmentType: equipmentTypeId,
effects,
},
// Store design data temporarily
log: [`🔮 Designing enchantment: ${name}...`, ...state.log.slice(0, 49)],
}));
@@ -1816,6 +1817,16 @@ export const useGameStore = create<GameStore>()(
maxFloorReached: Math.max(state.maxFloorReached, floor),
});
},
resetFloorHP: () => {
const state = get();
const maxHP = getFloorMaxHP(state.currentFloor);
set({
floorMaxHP: maxHP,
floorHP: maxHP,
log: [`🔄 Floor ${state.currentFloor} HP reset to full.`, ...state.log.slice(0, 49)],
});
},
}),
{
name: 'mana-loop-storage',

View File

@@ -213,6 +213,10 @@ export interface DesignProgress {
designId: string;
progress: number; // Hours spent designing
required: number; // Total hours needed
// Design data stored during progress
name: string;
equipmentType: string;
effects: DesignEffect[];
}
export interface PreparationProgress {