generated from nhcarrigan/template
fix: critical permission modal and config issues #127
+301
-40
@@ -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<HikariConfig>) => {
|
||||
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
|
||||
<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
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user