Added: - Table of contents for easy navigation - Detailed explanations of BOTH bugs (permission modal + config loss) - Step-by-step debugging procedures for each issue - Code examples showing wrong vs right patterns - Common anti-patterns to avoid (Svelte stores, event listeners) - Quick debugging checklist - Prevention tips and testing procedures - Clear ordering: check console FIRST, not z-index! This guide now documents the complete troubleshooting process so Naomi never has to explain these issues again.
16 KiB
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
- Permission Modal Not Showing
- Config Loss Issues
- Modal/Overlay Not Showing (General)
- Event Listener Issues
- Build Issues
- Testing Checklist
- 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():
debugConsoleStore.log("frontend", "info", "Setting up claude:permission listener");
Two problems:
debugConsoleStorewas never imported (undefined variable → crash!)- Even if imported,
debugConsoleStoredoesn't have a.log()method- Available methods:
toggle,open,close,clear,setupConsoleCapture, etc. - NOT available:
log()
- Available methods:
The Fix
// ❌ 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
debugConsoleStoreautomatically interceptsconsole.log(),console.error(), etc. - No need to call the store directly!
- See
src/lib/stores/debugConsole.tslines 66-89 for the console interception code
How to Debug This Type of Issue
Step 1: Check Browser Console
- Open Browser DevTools in the app (View > Toggle Developer Tools or F12)
- Look for JavaScript errors:
ReferenceError: X is not defined→ Missing import or typoTypeError: X.method is not a function→ Wrong method name or undefined object- Any error in
tauri.tsduring 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
pnpm check
This will catch:
- Missing imports
- Undefined variables
- Type mismatches
- Non-existent methods
Step 4: Test Permission Flow
- Trigger a restricted tool (like Bash with a destructive command)
- Check console for
[Permission] Event received:log - Check if
PermissionModal.svelteis rendering (DevTools Elements tab) - Check if modal has proper z-index and visibility
Prevention
Before committing changes to tauri.ts or any store:
- ✅ Run
pnpm check- Catches TypeScript errors - ✅ Run
pnpm build- Catches runtime issues - ✅ Test the app - Especially permission prompts
- ✅ Check browser console - No errors on startup
- ✅ 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:
// ❌ 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
- Sometimes: The subscription fires, sets
- When the race fails, it returns
defaultConfiginstead 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 everythingupdateConfig()- Would sometimes update defaults instead of current configtoggleStreamerMode()/toggleCompactMode()- Random togglesincreaseFontSize()/decreaseFontSize()- Font size would resetaddAutoGrantedTool()/removeAutoGrantedTool()- Tool permissions lostsetTheme()/setCustomThemeColors()- Theme changes lost
The Fix
// ✅ 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<HikariConfig>) => {
const currentConfig = getCurrentConfig(); // <-- Safe!
const newConfig = { ...currentConfig, ...updates };
await saveConfig(newConfig);
}
Why the fix works:
- Store the unsubscribe function in a variable
- Let the subscription fire synchronously (Svelte stores are synchronous)
- The callback runs immediately, setting
currentConfig - Clean up by calling
unsubscribe() - Return the now-populated
currentConfig
How to Debug Config Loss
Step 1: Check if config file exists
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
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:
// 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:
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:
let value;
store.subscribe((v) => (value = v))(); // Immediate unsubscribe = race!
✅ DO THIS INSTEAD:
let value;
const unsubscribe = store.subscribe((v) => (value = v));
unsubscribe(); // Explicit cleanup
return value;
Or even better, use Svelte's reactive syntax:
<script>
$: currentValue = $store; // Auto-subscribes safely
</script>
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:
// ✅ 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:
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:
ls -la ~/.config/hikari-desktop/config.json
Should be writable by user:
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
pnpm check
Look for:
- Import errors
- Undefined variables
- Type mismatches
2. Check Console for JavaScript Errors
Open DevTools and look for:
ReferenceError- Usually undefined variablesTypeError- Usually calling methods on undefined/null- Event listener setup logs
3. Check Svelte Store Subscriptions
Modals rely on store subscriptions. In the modal component:
// ✅ 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:
.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:
console.log("[Tauri Listener] Setting up claude:permission listener");
const permissionUnlisten = await listen<PermissionPromptEvent>("claude:permission", (event) => {
console.log("[Permission] Event received:", event.payload);
// ... rest of handler
});
Common Issues
-
Event listener crashes silently:
- Check for undefined variables in the listener callback
- Check for missing imports
- Use
try/catcharound critical sections
-
Events not being emitted from Rust:
- Check
src-tauri/src/foremit()calls - Verify event names match exactly (case-sensitive!)
- Check Rust console output for errors
- Check
-
Race condition:
- Event fired before listener was registered
- Solution: Set up listeners in
initializeTauriListeners()before starting Claude
Build Issues 🏗️
TypeScript Errors
pnpm check
Common fixes:
- Missing imports
- Wrong types
- Calling non-existent methods
Rust Errors
cargo build --release
Common fixes:
- Missing dependencies in
Cargo.toml - Import path issues
- Type mismatches
Full Clean Build
# 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 checkpasses (0 errors)pnpm buildsucceeds- 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:
-
Check recent git commits:
git log --oneline -5 -
Revert to last working commit:
git reset --hard <commit-hash> -
Clean build:
rm -rf node_modules dist .svelte-kit src-tauri/target pnpm install pnpm build -
Reset config:
rm ~/.config/hikari-desktop/config.json # Restart app to regenerate
Common Patterns and Anti-Patterns 📋
Svelte Store Access
✅ GOOD:
// 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:
// 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:
// Just use console methods - they're automatically captured!
console.log("Debug message");
console.error("Error message");
console.warn("Warning message");
❌ BAD:
// debugConsoleStore doesn't have a .log() method!
debugConsoleStore.log("frontend", "info", "message"); // ☠️ Crashes!
Event Listener Setup
✅ GOOD:
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:
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:
-
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!
-
Run TypeScript Check:
pnpm check- Catches missing imports, type errors, undefined variables
- 0 errors = probably a runtime or logic issue
- Any errors = fix those first!
-
Check Recent Changes:
git diff git log --oneline -5- What did you change recently?
- Did you import everything you're using?
- Did you test it?
-
Check the Debugging Guide (this file!)
- Search for keywords related to your issue
- Follow the debugging steps
- Look for similar patterns
-
Only Then consider z-index, CSS, layout issues
Remember:
- The debug console store intercepts console methods - don't call it directly!
- Always run
pnpm checkbefore 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 checkfor 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 crash2c64ef0- Fixed config store race condition (12 methods affected!)e8b8ee1- Updated this debugging guide