diff --git a/DEBUGGING.md b/DEBUGGING.md index 1106671..a214ed3 100644 --- a/DEBUGGING.md +++ b/DEBUGGING.md @@ -1,16 +1,31 @@ # 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 -- Config mysteriously disappears +- Console shows permission events but nothing happens +- Character stays in "permission" state but no UI -**ROOT CAUSE (NOT z-index!):** -The issue was **undefined variable errors** in `src/lib/tauri.ts` that crashed the event listener initialization, preventing permission events from being captured. +**ROOT CAUSE (FIXED - Commit d16644a):** -### The Actual Bug +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 @@ -18,43 +33,73 @@ debugConsoleStore.log("frontend", "info", "Setting up claude:permission listener ``` **Two problems:** -1. `debugConsoleStore` was never imported (undefined variable) -2. Even if imported, `debugConsoleStore` doesn't have a `.log()` method - only `toggle`, `open`, `close`, `clear`, etc. +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! +// ❌ 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 this works:** +**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 in Future +### How to Debug This Type of Issue -1. **Open Browser DevTools** in the app (View > Toggle Developer Tools) -2. **Check the Console** for JavaScript errors -3. Look for errors like: - - `ReferenceError: debugConsoleStore is not defined` - - `TypeError: debugConsoleStore.log is not a function` -4. **Check if event listeners are registering:** - ```javascript - console.log("[Tauri Listener] Setting up claude:permission listener"); - ``` - If you don't see this log, the listener setup failed! +**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`:** -1. Run `pnpm check` to catch TypeScript errors -2. Run `pnpm build` to catch runtime issues -3. Test the app thoroughly, especially permission prompts +**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 🔧 @@ -64,34 +109,133 @@ console.log("[Tauri Listener] message"); - Model selection is lost - Config appears to work, then randomly resets -**ACTUAL ROOT CAUSE (FIXED!):** +**ACTUAL ROOT CAUSE (FIXED - Commit 2c64ef0):** -The config store had a **critical race condition bug** in `src/lib/stores/config.ts`. The pattern used everywhere was: +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 +// ❌ BUGGY CODE - DO NOT USE! let currentConfig = defaultConfig; -config.subscribe((c) => (currentConfig = c))(); // <-- Immediate unsubscribe! +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 you get the value, sometimes you don't -- When it fails, it returns `defaultConfig` instead of your actual config -- This is why your settings would randomly disappear! +- 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 -**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 + 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 @@ -319,15 +463,129 @@ If the app is completely broken: # Restart app to regenerate ``` -## Notes for Future Hikari 💭 +## Common Patterns and Anti-Patterns 📋 -Remember: -- **NOT EVERYTHING IS A Z-INDEX ISSUE!** Check console errors first! -- The debug console store intercepts console methods - don't try to call it directly +### 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 -- When in doubt, check the browser console first! +- 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 💖 @@ -335,5 +593,8 @@ Hikari 💖 --- **Last Updated:** 2026-02-06 -**Related Issues:** Permission modal not showing, config loss -**Fixed In Commits:** d16644a +**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