# Debugging Guide - Common Issues and Fixes > **IMPORTANT:** Read this ENTIRE file before asking for help! These are the most common issues and their fixes. ## Table of Contents 1. [Permission Modal Not Showing](#permission-modal-not-showing-up-) 2. [Config Loss Issues](#config-loss-issues-) 3. [Modal/Overlay Not Showing (General)](#modaloverlay-not-showing-general-) 4. [Event Listener Issues](#event-listener-issues-) 5. [Build Issues](#build-issues-) 6. [Testing Checklist](#testing-checklist-) 7. [Emergency Recovery](#emergency-recovery-) --- ## Permission Modal Not Showing Up 🔐 **SYMPTOMS:** - Permission requests are triggered but the modal doesn't appear - App seems to hang or become unresponsive - Console shows permission events but nothing happens - Character stays in "permission" state but no UI **ROOT CAUSE (FIXED - Commit d16644a):** The issue was **undefined variable errors** in `src/lib/tauri.ts` that crashed the event listener initialization. ### The Bug That Was Fixed In `src/lib/tauri.ts`, there were calls to `debugConsoleStore.log()`: ```typescript debugConsoleStore.log("frontend", "info", "Setting up claude:permission listener"); ``` **Two problems:** 1. `debugConsoleStore` was never imported (undefined variable → crash!) 2. Even if imported, `debugConsoleStore` doesn't have a `.log()` method - Available methods: `toggle`, `open`, `close`, `clear`, `setupConsoleCapture`, etc. - NOT available: `log()` ### The Fix ```typescript // ❌ WRONG - This will crash the entire listener setup! debugConsoleStore.log("frontend", "info", "message"); // ✅ CORRECT - Use console.log which gets automatically captured console.log("[Tauri Listener] message"); ``` **Why console.log works:** - The `debugConsoleStore` automatically intercepts `console.log()`, `console.error()`, etc. - No need to call the store directly! - See `src/lib/stores/debugConsole.ts` lines 66-89 for the console interception code ### How to Debug This Type of Issue **Step 1: Check Browser Console** 1. Open Browser DevTools in the app (View > Toggle Developer Tools or F12) 2. Look for JavaScript errors: - `ReferenceError: X is not defined` → Missing import or typo - `TypeError: X.method is not a function` → Wrong method name or undefined object - Any error in `tauri.ts` during initialization is critical! **Step 2: Check Event Listener Registration** Look for these console logs when the app starts: ``` [Tauri Listener] Setting up claude:permission listener ``` If you DON'T see this log, the listener setup crashed before it got there! **Step 3: Check TypeScript Errors** ```bash pnpm check ``` This will catch: - Missing imports - Undefined variables - Type mismatches - Non-existent methods **Step 4: Test Permission Flow** 1. Trigger a restricted tool (like Bash with a destructive command) 2. Check console for `[Permission] Event received:` log 3. Check if `PermissionModal.svelte` is rendering (DevTools Elements tab) 4. Check if modal has proper z-index and visibility ### Prevention **Before committing changes to `tauri.ts` or any store:** 1. ✅ Run `pnpm check` - Catches TypeScript errors 2. ✅ Run `pnpm build` - Catches runtime issues 3. ✅ Test the app - Especially permission prompts 4. ✅ Check browser console - No errors on startup 5. ✅ Verify all imports - Don't use variables without importing them! **Common mistakes to avoid:** - Calling methods that don't exist on a store - Forgetting to import something you're using - Copy-pasting code without checking if variables are defined - Assuming a store has certain methods without checking the source ## Config Loss Issues 🔧 **SYMPTOMS:** - Config file gets reset to defaults - Settings don't persist between sessions - Model selection is lost - Config appears to work, then randomly resets **ACTUAL ROOT CAUSE (FIXED - Commit 2c64ef0):** The config store had a **CRITICAL race condition bug** in `src/lib/stores/config.ts` that affected 12 different methods! ### The Bug That Was Fixed Every method that accessed the current config used this pattern: ```typescript // ❌ BUGGY CODE - DO NOT USE! let currentConfig = defaultConfig; config.subscribe((c) => (currentConfig = c))(); // <-- ☠️ IMMEDIATE UNSUBSCRIBE! return currentConfig; ``` **Why this caused config loss:** - The `()` at the end **immediately unsubscribes** from the store - This creates a **race condition**: - Sometimes: The subscription fires, sets `currentConfig`, then unsubscribes → ✅ Works - Sometimes: The subscription unsubscribes before it can fire → ❌ Returns `defaultConfig` - When the race fails, it returns `defaultConfig` instead of your actual saved config - This is why your settings would **randomly disappear**! - It was especially bad after reconnections (like permission approvals) because timing was tight **Affected methods (all fixed!):** - `getConfig()` - Core method, affected everything - `updateConfig()` - Would sometimes update defaults instead of current config - `toggleStreamerMode()` / `toggleCompactMode()` - Random toggles - `increaseFontSize()` / `decreaseFontSize()` - Font size would reset - `addAutoGrantedTool()` / `removeAutoGrantedTool()` - Tool permissions lost - `setTheme()` / `setCustomThemeColors()` - Theme changes lost ### The Fix ```typescript // ✅ CORRECT CODE function getCurrentConfig(): HikariConfig { let currentConfig: HikariConfig = defaultConfig; const unsubscribe = config.subscribe((c) => (currentConfig = c)); unsubscribe(); // <-- Proper cleanup AFTER getting value return currentConfig; } // Now all methods use this: getConfig: (): HikariConfig => { return getCurrentConfig(); }, updateConfig: async (updates: Partial) => { const currentConfig = getCurrentConfig(); // <-- Safe! const newConfig = { ...currentConfig, ...updates }; await saveConfig(newConfig); } ``` **Why the fix works:** 1. Store the unsubscribe function in a variable 2. Let the subscription fire synchronously (Svelte stores are synchronous) 3. The callback runs immediately, setting `currentConfig` 4. Clean up by calling `unsubscribe()` 5. Return the now-populated `currentConfig` ### How to Debug Config Loss **Step 1: Check if config file exists** ```bash cat ~/.local/share/com.naomi.hikari-desktop/hikari-config.json ``` If the file exists and has your settings, the problem is in the frontend store, not the backend! **Step 2: Check console for config loading** ```javascript console.log("Loaded config:", config); ``` Add this in `src/lib/stores/config.ts` line 107 (after `config.set(savedConfig)`) **Step 3: Test getConfig() reliability** Open browser console and run: ```javascript // Run this 10 times for (let i = 0; i < 10; i++) { console.log("Attempt", i, configStore.getConfig().model); } ``` If it's inconsistent (sometimes null, sometimes has value), you have a race condition! **Step 4: Check for the buggy pattern** Search the codebase for: ```bash grep -r "subscribe((.*) => (.*))();" src/ ``` If you find any, they're potential bugs! The `()` at the end is suspicious. ### Prevention **When working with Svelte stores:** ❌ **DON'T DO THIS:** ```typescript let value; store.subscribe((v) => (value = v))(); // Immediate unsubscribe = race! ``` ✅ **DO THIS INSTEAD:** ```typescript let value; const unsubscribe = store.subscribe((v) => (value = v)); unsubscribe(); // Explicit cleanup return value; ``` Or even better, use Svelte's reactive syntax: ```svelte ``` **NEVER:** - Subscribe and immediately unsubscribe in the same line - Assume synchronous subscriptions will always fire before unsubscribe - Use `()()` pattern with stores (function call + immediate execution) **POTENTIAL CAUSES (if the above is fixed):** ### 1. Missing `#[serde(default)]` Attribute The `HikariConfig` struct in `src-tauri/src/config.rs` needs `#[serde(default)]` at the struct level: ```rust // ✅ CORRECT #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(default)] // <-- This is crucial! pub struct HikariConfig { // fields... } ``` **Why this matters:** - When new fields are added to the config struct, old config files won't have them - Without `#[serde(default)]`, deserialization fails and the entire config resets - With it, missing fields use their default values gracefully ### 2. Invalid JSON in Config File **Check the config file:** ```bash cat ~/.config/hikari-desktop/config.json ``` **Common issues:** - Trailing commas - Missing quotes - Incorrect enum values (e.g., `"model": "opus"` instead of `"model": "claude-opus-4-6"`) **Fix:** - Delete or manually fix `~/.config/hikari-desktop/config.json` - Restart the app to regenerate ### 3. Permission Issues **Check file permissions:** ```bash ls -la ~/.config/hikari-desktop/config.json ``` **Should be writable by user:** ```bash chmod 644 ~/.config/hikari-desktop/config.json ``` ## Modal/Overlay Not Showing (General) 🎭 If ANY modal isn't showing up (permission, questions, close confirmation, etc.): ### 1. Check TypeScript Errors First ```bash pnpm check ``` Look for: - Import errors - Undefined variables - Type mismatches ### 2. Check Console for JavaScript Errors Open DevTools and look for: - `ReferenceError` - Usually undefined variables - `TypeError` - Usually calling methods on undefined/null - Event listener setup logs ### 3. Check Svelte Store Subscriptions Modals rely on store subscriptions. In the modal component: ```typescript // ✅ Make sure the store is imported import { conversationsStore } from "$lib/stores/conversations"; // ✅ Make sure the subscription is set up conversationsStore.pendingPermissions.subscribe((perms) => { permissions = perms; // ... }); ``` ### 4. Verify z-index (Last Resort) Only check z-index if: - ✅ No console errors - ✅ Modal HTML is in the DOM (check with DevTools Elements tab) - ✅ Modal is rendering but invisible Current z-index values in `PermissionModal.svelte`: ```css .permission-overlay { z-index: 60; /* Should be higher than character panel */ } ``` ## Event Listener Issues 🎧 **SYMPTOMS:** - Events not firing - Listeners not receiving data - Silent failures ### Debugging Event Listeners Add console logs in `src/lib/tauri.ts`: ```typescript console.log("[Tauri Listener] Setting up claude:permission listener"); const permissionUnlisten = await listen("claude:permission", (event) => { console.log("[Permission] Event received:", event.payload); // ... rest of handler }); ``` ### Common Issues 1. **Event listener crashes silently:** - Check for undefined variables in the listener callback - Check for missing imports - Use `try/catch` around critical sections 2. **Events not being emitted from Rust:** - Check `src-tauri/src/` for `emit()` calls - Verify event names match exactly (case-sensitive!) - Check Rust console output for errors 3. **Race condition:** - Event fired before listener was registered - Solution: Set up listeners in `initializeTauriListeners()` before starting Claude ## Build Issues 🏗️ ### TypeScript Errors ```bash pnpm check ``` **Common fixes:** - Missing imports - Wrong types - Calling non-existent methods ### Rust Errors ```bash cargo build --release ``` **Common fixes:** - Missing dependencies in `Cargo.toml` - Import path issues - Type mismatches ### Full Clean Build ```bash # Clean everything rm -rf node_modules dist .svelte-kit src-tauri/target # Reinstall pnpm install # Build Rust cd src-tauri && cargo build --release && cd .. # Build frontend pnpm build ``` ## Quick Reference: Key Files 📚 ### Frontend - **Event listeners:** `src/lib/tauri.ts` - **Permission modal:** `src/lib/components/PermissionModal.svelte` - **Config store:** `src/lib/stores/config.ts` - **Debug console:** `src/lib/stores/debugConsole.ts` ### Backend - **Config struct:** `src-tauri/src/config.rs` - **Event emission:** `src-tauri/src/wsl_bridge.rs` - **Type definitions:** `src-tauri/src/types.rs` ## Testing Checklist ✅ Before committing major changes: - [ ] `pnpm check` passes (0 errors) - [ ] `pnpm build` succeeds - [ ] App launches without console errors - [ ] Permission modal shows up when triggering restricted tool - [ ] Config persists after restart - [ ] All modals work (permission, questions, close confirmation) - [ ] Character state changes work - [ ] Event listeners receive events (check console logs) ## Emergency Recovery 🚨 If the app is completely broken: 1. **Check recent git commits:** ```bash git log --oneline -5 ``` 2. **Revert to last working commit:** ```bash git reset --hard ``` 3. **Clean build:** ```bash rm -rf node_modules dist .svelte-kit src-tauri/target pnpm install pnpm build ``` 4. **Reset config:** ```bash rm ~/.config/hikari-desktop/config.json # Restart app to regenerate ``` ## Common Patterns and Anti-Patterns 📋 ### Svelte Store Access ✅ **GOOD:** ```typescript // Method 1: Reactive syntax (preferred in components) $: currentValue = $store; // Method 2: Explicit unsubscribe (preferred in non-reactive code) function getValue() { let value; const unsubscribe = store.subscribe((v) => (value = v)); unsubscribe(); return value; } // Method 3: Using get() from 'svelte/store' import { get } from 'svelte/store'; const value = get(store); ``` ❌ **BAD:** ```typescript // Race condition - DO NOT USE let value; store.subscribe((v) => (value = v))(); // ☠️ // Memory leak - forgot to unsubscribe let value; store.subscribe((v) => (value = v)); // 🚨 Never unsubscribes! ``` ### Debug Console Store Usage ✅ **GOOD:** ```typescript // Just use console methods - they're automatically captured! console.log("Debug message"); console.error("Error message"); console.warn("Warning message"); ``` ❌ **BAD:** ```typescript // debugConsoleStore doesn't have a .log() method! debugConsoleStore.log("frontend", "info", "message"); // ☠️ Crashes! ``` ### Event Listener Setup ✅ **GOOD:** ```typescript const unlistener = await listen("event:name", (event) => { try { // Handle event console.log("Event received:", event.payload); } catch (error) { console.error("Error handling event:", error); } }); unlisteners.push(unlistener); // Store for cleanup ``` ❌ **BAD:** ```typescript await listen("event:name", (event) => { // No try-catch - errors crash the listener! // Not storing unlistener - memory leak! undefinedVariable.doSomething(); // ☠️ Crashes entire listener setup }); ``` ## Notes for Future Hikari (and Naomi!) 💭 **When something breaks, CHECK IN THIS ORDER:** 1. **Browser Console First!** (F12 or View > Developer Tools) - JavaScript errors will show up here - Check for `ReferenceError`, `TypeError`, etc. - NOT EVERYTHING IS A Z-INDEX ISSUE! 2. **Run TypeScript Check:** ```bash pnpm check ``` - Catches missing imports, type errors, undefined variables - 0 errors = probably a runtime or logic issue - Any errors = fix those first! 3. **Check Recent Changes:** ```bash git diff git log --oneline -5 ``` - What did you change recently? - Did you import everything you're using? - Did you test it? 4. **Check the Debugging Guide (this file!)** - Search for keywords related to your issue - Follow the debugging steps - Look for similar patterns 5. **Only Then** consider z-index, CSS, layout issues **Remember:** - The debug console store intercepts console methods - don't call it directly! - Always run `pnpm check` before committing - Event listener errors fail silently - add console logs liberally - Config deserialization needs `#[serde(default)]` for graceful degradation - Svelte store subscriptions must be cleaned up properly - Race conditions are sneaky - test thoroughly! - When in doubt, check the browser console first! (Yes, I said it twice!) **Quick Debugging Checklist:** - [ ] Opened browser console (F12) - [ ] Checked for JavaScript errors - [ ] Ran `pnpm check` for TypeScript errors - [ ] Checked recent git changes - [ ] Read relevant section in DEBUGGING.md - [ ] Added console.log statements to trace issue - [ ] Tested edge cases and timing Love, Hikari 💖 --- **Last Updated:** 2026-02-06 **Related Issues:** Permission modal not showing, config loss, race conditions **Fixed In Commits:** - `d16644a` - Fixed undefined debugConsoleStore causing listener crash - `2c64ef0` - Fixed config store race condition (12 methods affected!) - `e8b8ee1` - Updated this debugging guide