generated from nhcarrigan/template
fix: critical permission modal and config issues #127
-600
@@ -1,600 +0,0 @@
|
||||
# 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<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
|
||||
|
||||
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<PermissionPromptEvent>("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 <commit-hash>
|
||||
```
|
||||
|
||||
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
|
||||
@@ -1,110 +0,0 @@
|
||||
# Critical Bug Fixes - 2026-02-06
|
||||
|
||||
## Summary
|
||||
|
||||
Fixed **TWO CRITICAL BUGS** that were causing permission modal issues and config loss.
|
||||
|
||||
## Bug #1: Permission Modal Not Showing (Commit d16644a)
|
||||
|
||||
**Symptom:** Permission requests triggered but modal never appeared, app hung
|
||||
|
||||
**Root Cause:** Undefined variable error in `src/lib/tauri.ts`
|
||||
- Called `debugConsoleStore.log()` without importing `debugConsoleStore`
|
||||
- Even if imported, that method doesn't exist on the store
|
||||
- This crashed event listener initialization, preventing permission events from being captured
|
||||
|
||||
**Fix:**
|
||||
- Removed the non-existent import
|
||||
- Changed to `console.log()` which gets automatically captured by the debug console
|
||||
- Added logging to track permission event flow
|
||||
|
||||
**Files Changed:**
|
||||
- `src/lib/tauri.ts` - Fixed undefined variable, added proper logging
|
||||
|
||||
## Bug #2: Config Constantly Resetting (Commit 2c64ef0)
|
||||
|
||||
**Symptom:** Config settings would randomly reset to defaults, especially after permission approvals
|
||||
|
||||
**Root Cause:** Race condition in `src/lib/stores/config.ts`
|
||||
- Used buggy pattern: `config.subscribe((c) => (currentConfig = c))()`
|
||||
- The `()` at the end immediately unsubscribes, creating a race condition
|
||||
- Sometimes got the value, sometimes returned defaults
|
||||
- Affected **12 different methods** in the config store!
|
||||
|
||||
**Fix:**
|
||||
- Created proper `getCurrentConfig()` helper function
|
||||
- Stores unsubscribe function, calls it AFTER getting value
|
||||
- Replaced all 12 buggy occurrences with the safe pattern
|
||||
|
||||
**Methods Fixed:**
|
||||
- `getConfig()` - Core method used everywhere
|
||||
- `updateConfig()` - Base for all updates
|
||||
- `toggleStreamerMode()` / `toggleCompactMode()`
|
||||
- `increaseFontSize()` / `decreaseFontSize()`
|
||||
- `addAutoGrantedTool()` / `removeAutoGrantedTool()`
|
||||
- `setTheme()` / `setCustomThemeColors()`
|
||||
- And more...
|
||||
|
||||
**Files Changed:**
|
||||
- `src/lib/stores/config.ts` - Fixed race condition in 12 methods
|
||||
- `src-tauri/src/config.rs` - Added `#[serde(default)]` for resilience
|
||||
|
||||
## Additional Improvements
|
||||
|
||||
**Documentation (Commits 80ee25f, e8b8ee1, 4a99848):**
|
||||
- Created comprehensive `DEBUGGING.md` guide
|
||||
- Documented both bugs with code examples
|
||||
- Added step-by-step debugging procedures
|
||||
- Included common patterns and anti-patterns
|
||||
- Prevention tips and testing checklist
|
||||
|
||||
## Testing
|
||||
|
||||
**Before deploying:**
|
||||
1. Test permission modal shows up properly
|
||||
2. Test config persists through app restarts
|
||||
3. Test config survives permission reconnections
|
||||
4. Check browser console for any errors
|
||||
5. Verify all settings save and load correctly
|
||||
|
||||
## Impact
|
||||
|
||||
**High Priority - Breaking Bugs:**
|
||||
- Permission modal was completely broken (app unusable for restricted tools)
|
||||
- Config loss made user experience terrible (settings constantly resetting)
|
||||
|
||||
**Now Fixed:**
|
||||
- Permission modal should display reliably
|
||||
- Config should persist across all operations
|
||||
- No more random resets to default settings
|
||||
|
||||
## Commits
|
||||
|
||||
- `d16644a` - fix: resolve critical runtime errors blocking permission modal
|
||||
- `2c64ef0` - fix: resolve critical config store race condition causing config loss
|
||||
- `80ee25f` - docs: add comprehensive debugging guide for common issues
|
||||
- `e8b8ee1` - docs: update DEBUGGING.md with config race condition fix
|
||||
- `4a99848` - docs: massively expand DEBUGGING.md with comprehensive troubleshooting
|
||||
|
||||
## Prevention
|
||||
|
||||
**Always:**
|
||||
1. Run `pnpm check` before committing
|
||||
2. Test the app, especially permission flow
|
||||
3. Check browser console for errors
|
||||
4. Never use `store.subscribe(...)()` pattern (immediate unsubscribe)
|
||||
5. Import everything you use!
|
||||
|
||||
**Never:**
|
||||
- Call `debugConsoleStore.log()` (method doesn't exist)
|
||||
- Use race-condition patterns with Svelte stores
|
||||
- Assume synchronous code is safe from race conditions
|
||||
- Skip testing after changes to event listeners or stores
|
||||
|
||||
---
|
||||
|
||||
**Date:** 2026-02-06
|
||||
**Fixed By:** Hikari
|
||||
**Branch:** feat/many
|
||||
**Severity:** Critical
|
||||
**Status:** Fixed ✅
|
||||
@@ -1671,7 +1671,7 @@ pub fn check_message_achievements(
|
||||
let mut newly_unlocked = Vec::new();
|
||||
let message_lower = message.to_lowercase();
|
||||
|
||||
println!("Checking message achievements for: {}", message);
|
||||
tracing::info!("Checking message achievements for: {}", message);
|
||||
|
||||
// Relationship & Greetings
|
||||
if message_lower.contains("good morning") && progress.unlock(AchievementId::GoodMorning) {
|
||||
@@ -1863,18 +1863,18 @@ pub fn check_achievements(
|
||||
) -> Vec<AchievementId> {
|
||||
let mut newly_unlocked = Vec::new();
|
||||
|
||||
println!(
|
||||
tracing::info!(
|
||||
"Checking achievements with stats: messages={}, tokens={}, code_blocks={}",
|
||||
stats.messages_exchanged,
|
||||
stats.total_input_tokens + stats.total_output_tokens,
|
||||
stats.code_blocks_generated
|
||||
);
|
||||
println!("Currently unlocked: {:?}", progress.unlocked);
|
||||
tracing::info!("Currently unlocked: {:?}", progress.unlocked);
|
||||
|
||||
// Token milestones
|
||||
let total_tokens = stats.total_input_tokens + stats.total_output_tokens;
|
||||
if total_tokens >= 1_000 && progress.unlock(AchievementId::FirstSteps) {
|
||||
println!("Unlocked FirstSteps achievement!");
|
||||
tracing::info!("Unlocked FirstSteps achievement!");
|
||||
newly_unlocked.push(AchievementId::FirstSteps);
|
||||
}
|
||||
if total_tokens >= 10_000 && progress.unlock(AchievementId::GrowingStrong) {
|
||||
@@ -2244,7 +2244,7 @@ pub async fn save_achievements(
|
||||
// Create a serializable version with just the unlocked achievement IDs
|
||||
let unlocked_list: Vec<AchievementId> = progress.unlocked.iter().cloned().collect();
|
||||
|
||||
println!("Saving achievements: {:?}", unlocked_list);
|
||||
tracing::info!("Saving achievements: {:?}", unlocked_list);
|
||||
|
||||
store.set(
|
||||
"unlocked",
|
||||
@@ -2252,18 +2252,18 @@ pub async fn save_achievements(
|
||||
);
|
||||
store.save().map_err(|e| e.to_string())?;
|
||||
|
||||
println!("Achievements saved successfully");
|
||||
tracing::info!("Achievements saved successfully");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Load achievements from persistent store
|
||||
pub async fn load_achievements(app: &tauri::AppHandle) -> AchievementProgress {
|
||||
println!("Loading achievements from store...");
|
||||
tracing::info!("Loading achievements from store...");
|
||||
|
||||
let store = match app.store("achievements.json") {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
println!("Failed to open achievements store: {}", e);
|
||||
tracing::error!("Failed to open achievements store: {}", e);
|
||||
return AchievementProgress::new();
|
||||
}
|
||||
};
|
||||
@@ -2272,19 +2272,19 @@ pub async fn load_achievements(app: &tauri::AppHandle) -> AchievementProgress {
|
||||
|
||||
// Get unlocked achievements
|
||||
if let Some(unlocked_value) = store.get("unlocked") {
|
||||
println!("Found unlocked value in store: {:?}", unlocked_value);
|
||||
tracing::info!("Found unlocked value in store: {:?}", unlocked_value);
|
||||
if let Ok(unlocked_list) =
|
||||
serde_json::from_value::<Vec<AchievementId>>(unlocked_value.clone())
|
||||
{
|
||||
println!("Loaded {} achievements", unlocked_list.len());
|
||||
tracing::info!("Loaded {} achievements", unlocked_list.len());
|
||||
for achievement_id in unlocked_list {
|
||||
progress.unlocked.insert(achievement_id);
|
||||
}
|
||||
} else {
|
||||
println!("Failed to parse unlocked achievements");
|
||||
tracing::error!("Failed to parse unlocked achievements");
|
||||
}
|
||||
} else {
|
||||
println!("No unlocked achievements found in store");
|
||||
tracing::info!("No unlocked achievements found in store");
|
||||
}
|
||||
|
||||
progress
|
||||
|
||||
@@ -30,7 +30,7 @@ impl DiscordRpcManager {
|
||||
if let Ok(app_data_dir) = app_handle.path().app_data_dir() {
|
||||
// Ensure the directory exists
|
||||
if let Err(e) = std::fs::create_dir_all(&app_data_dir) {
|
||||
eprintln!("Failed to create app data directory: {}", e);
|
||||
tracing::error!("Failed to create app data directory: {}", e);
|
||||
return;
|
||||
}
|
||||
let log_path = app_data_dir.join("hikari_discord_rpc.log");
|
||||
|
||||
@@ -87,7 +87,7 @@ pub fn run() {
|
||||
|
||||
// Set up system tray
|
||||
if let Err(e) = setup_tray(app.handle()) {
|
||||
eprintln!("Failed to set up system tray: {}", e);
|
||||
tracing::error!("Failed to set up system tray: {}", e);
|
||||
}
|
||||
|
||||
// Handle window close event for minimize to tray and close confirmation
|
||||
|
||||
@@ -618,7 +618,7 @@ pub async fn save_stats(app: &tauri::AppHandle, stats: &UsageStats) -> Result<()
|
||||
|
||||
let persisted = PersistedStats::from(stats);
|
||||
|
||||
println!("Saving stats: {:?}", persisted);
|
||||
tracing::info!("Saving stats: {:?}", persisted);
|
||||
|
||||
store.set(
|
||||
"lifetime_stats",
|
||||
@@ -626,32 +626,32 @@ pub async fn save_stats(app: &tauri::AppHandle, stats: &UsageStats) -> Result<()
|
||||
);
|
||||
store.save().map_err(|e| e.to_string())?;
|
||||
|
||||
println!("Stats saved successfully");
|
||||
tracing::info!("Stats saved successfully");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Load lifetime stats from persistent store
|
||||
pub async fn load_stats(app: &tauri::AppHandle) -> Option<PersistedStats> {
|
||||
println!("Loading stats from store...");
|
||||
tracing::info!("Loading stats from store...");
|
||||
|
||||
let store = match app.store("stats.json") {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
println!("Failed to open stats store: {}", e);
|
||||
tracing::error!("Failed to open stats store: {}", e);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(stats_value) = store.get("lifetime_stats") {
|
||||
println!("Found lifetime stats in store: {:?}", stats_value);
|
||||
tracing::info!("Found lifetime stats in store: {:?}", stats_value);
|
||||
if let Ok(persisted) = serde_json::from_value::<PersistedStats>(stats_value.clone()) {
|
||||
println!("Loaded lifetime stats successfully");
|
||||
tracing::info!("Loaded lifetime stats successfully");
|
||||
return Some(persisted);
|
||||
} else {
|
||||
println!("Failed to parse lifetime stats");
|
||||
tracing::error!("Failed to parse lifetime stats");
|
||||
}
|
||||
} else {
|
||||
println!("No lifetime stats found in store");
|
||||
tracing::info!("No lifetime stats found in store");
|
||||
}
|
||||
|
||||
None
|
||||
|
||||
@@ -77,8 +77,8 @@ impl TempFileManager {
|
||||
for file_path in files {
|
||||
if file_path.exists() {
|
||||
if let Err(e) = fs::remove_file(&file_path) {
|
||||
eprintln!(
|
||||
"Warning: Failed to remove temp file {:?}: {}",
|
||||
tracing::warn!(
|
||||
"Failed to remove temp file {:?}: {}",
|
||||
file_path, e
|
||||
);
|
||||
}
|
||||
@@ -115,7 +115,7 @@ impl TempFileManager {
|
||||
let path = entry.path();
|
||||
if path.is_file() && !tracked_files.contains(&path) {
|
||||
if let Err(e) = fs::remove_file(&path) {
|
||||
eprintln!("Warning: Failed to remove orphaned file {:?}: {}", path, e);
|
||||
tracing::warn!("Failed to remove orphaned file {:?}: {}", path, e);
|
||||
} else {
|
||||
cleaned_count += 1;
|
||||
}
|
||||
|
||||
+54
-47
@@ -133,21 +133,21 @@ impl WslBridge {
|
||||
let app_clone = app.clone();
|
||||
let stats = self.stats.clone();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
println!("Loading saved achievements...");
|
||||
tracing::info!("Loading saved achievements...");
|
||||
let achievements = crate::achievements::load_achievements(&app_clone).await;
|
||||
println!(
|
||||
tracing::info!(
|
||||
"Loaded {} unlocked achievements",
|
||||
achievements.unlocked.len()
|
||||
);
|
||||
|
||||
println!("Loading saved stats...");
|
||||
tracing::info!("Loading saved stats...");
|
||||
let persisted_stats = crate::stats::load_stats(&app_clone).await;
|
||||
|
||||
let mut stats_guard = stats.write();
|
||||
stats_guard.achievements = achievements;
|
||||
|
||||
if let Some(persisted) = persisted_stats {
|
||||
println!("Applying persisted lifetime stats");
|
||||
tracing::info!("Applying persisted lifetime stats");
|
||||
stats_guard.apply_persisted(persisted);
|
||||
}
|
||||
});
|
||||
@@ -189,8 +189,8 @@ impl WslBridge {
|
||||
|
||||
// Detect if we're running inside WSL or on Windows
|
||||
let is_wsl = detect_wsl();
|
||||
eprintln!("[DEBUG] is_wsl: {}", is_wsl);
|
||||
eprintln!("[DEBUG] options: {:?}", options);
|
||||
tracing::debug!("is_wsl: {}", is_wsl);
|
||||
tracing::debug!("options: {:?}", options);
|
||||
|
||||
let mut command = if is_wsl {
|
||||
// Running inside WSL - call claude directly
|
||||
@@ -199,8 +199,8 @@ impl WslBridge {
|
||||
"Could not find claude binary. Is Claude Code installed?".to_string()
|
||||
})?;
|
||||
|
||||
eprintln!("[DEBUG] Found claude at: {}", claude_path);
|
||||
eprintln!("[DEBUG] Working dir: {}", working_dir);
|
||||
tracing::debug!("Found claude at: {}", claude_path);
|
||||
tracing::debug!("Working dir: {}", working_dir);
|
||||
|
||||
let mut cmd = Command::new(&claude_path);
|
||||
cmd.args([
|
||||
@@ -256,7 +256,7 @@ impl WslBridge {
|
||||
cmd
|
||||
} else {
|
||||
// Running on Windows - use wsl with bash login shell to ensure PATH is loaded
|
||||
eprintln!("[DEBUG] Windows path - using wsl");
|
||||
tracing::debug!("Windows path - using wsl");
|
||||
let mut cmd = Command::new("wsl");
|
||||
|
||||
// Build the claude command with all arguments
|
||||
@@ -322,7 +322,7 @@ impl WslBridge {
|
||||
.stderr(Stdio::piped());
|
||||
|
||||
let mut child = command.spawn().map_err(|e| {
|
||||
eprintln!("[DEBUG] Spawn error: {:?}", e);
|
||||
tracing::error!("Spawn error: {:?}", e);
|
||||
format!("Failed to spawn process: {}", e)
|
||||
})?;
|
||||
|
||||
@@ -500,7 +500,7 @@ impl WslBridge {
|
||||
(input_chars, stats.current_request_output_chars, stats.current_request_thinking_chars, stats.current_request_tools.clone(), model)
|
||||
};
|
||||
|
||||
println!("[COST ESTIMATION] Estimating cost for interrupted request");
|
||||
tracing::info!("[COST ESTIMATION] Estimating cost for interrupted request");
|
||||
|
||||
// Use conservative 3.5 chars/token for estimation (vs standard 4)
|
||||
let estimated_input_tokens = (input_chars as f64 / 3.5).ceil() as u64;
|
||||
@@ -518,7 +518,7 @@ impl WslBridge {
|
||||
let avg_tokens = (tool_stats.estimated_input_tokens + tool_stats.estimated_output_tokens)
|
||||
/ tool_stats.call_count;
|
||||
tool_overhead_tokens += avg_tokens;
|
||||
println!("[COST ESTIMATION] Tool {} average: {} tokens", tool_name, avg_tokens);
|
||||
tracing::info!("[COST ESTIMATION] Tool {} average: {} tokens", tool_name, avg_tokens);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -532,9 +532,9 @@ impl WslBridge {
|
||||
let conservative_input = (total_estimated_input as f64 * safety_margin).ceil() as u64;
|
||||
let conservative_output = (total_estimated_output as f64 * safety_margin).ceil() as u64;
|
||||
|
||||
println!("[COST ESTIMATION] Input: {} chars → {} tokens (+ {} tool overhead) × 1.2 safety = {} tokens",
|
||||
tracing::info!("[COST ESTIMATION] Input: {} chars → {} tokens (+ {} tool overhead) × 1.2 safety = {} tokens",
|
||||
input_chars, estimated_input_tokens, tool_overhead_tokens, conservative_input);
|
||||
println!("[COST ESTIMATION] Output: {} chars → {} tokens × 1.2 safety = {} tokens",
|
||||
tracing::info!("[COST ESTIMATION] Output: {} chars → {} tokens × 1.2 safety = {} tokens",
|
||||
output_chars + thinking_chars,
|
||||
estimated_output_tokens, conservative_output);
|
||||
|
||||
@@ -547,7 +547,7 @@ impl WslBridge {
|
||||
None,
|
||||
);
|
||||
|
||||
println!("[COST ESTIMATION] Estimated cost: ${:.4} (conservative)", estimated_cost);
|
||||
tracing::info!("[COST ESTIMATION] Estimated cost: ${:.4} (conservative)", estimated_cost);
|
||||
|
||||
// Add to stats with estimated flag
|
||||
{
|
||||
@@ -587,11 +587,11 @@ impl WslBridge {
|
||||
let stats_snapshot = self.stats.read().clone();
|
||||
let app_clone = app.clone();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
println!("Saving stats on session stop...");
|
||||
tracing::info!("Saving stats on session stop...");
|
||||
if let Err(e) = crate::stats::save_stats(&app_clone, &stats_snapshot).await {
|
||||
eprintln!("Failed to save stats: {}", e);
|
||||
tracing::error!("Failed to save stats: {}", e);
|
||||
} else {
|
||||
println!("Stats saved successfully on session stop");
|
||||
tracing::info!("Stats saved successfully on session stop");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -636,11 +636,11 @@ fn handle_stdout(
|
||||
match line {
|
||||
Ok(line) if !line.is_empty() => {
|
||||
if let Err(e) = process_json_line(&line, &app, &stats, &conversation_id) {
|
||||
eprintln!("Error processing line: {}", e);
|
||||
tracing::error!("Error processing line: {}", e);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Error reading stdout: {}", e);
|
||||
tracing::error!("Error reading stdout: {}", e);
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
@@ -663,7 +663,7 @@ fn handle_stderr(
|
||||
// Check if this is a SubagentStart hook message
|
||||
if line.contains("[SubagentStart Hook]") {
|
||||
if let Some(agent_data) = parse_subagent_start_hook(&line) {
|
||||
eprintln!("[DEBUG] Parsed SubagentStart hook: agent_id={}, parent={:?}",
|
||||
tracing::debug!("Parsed SubagentStart hook: agent_id={}, parent={:?}",
|
||||
agent_data.agent_id, agent_data.parent_tool_use_id);
|
||||
|
||||
// Emit an agent-update event with the agent_id
|
||||
@@ -815,7 +815,7 @@ fn process_json_line(
|
||||
let stats_guard = stats.read();
|
||||
stats_guard.model.clone()
|
||||
}).unwrap_or_else(|| {
|
||||
println!("[WARNING] No model info available for cost calculation, using default");
|
||||
tracing::warn!("No model info available for cost calculation, using default");
|
||||
"claude-sonnet-4-5-20250929".to_string()
|
||||
});
|
||||
|
||||
@@ -828,7 +828,7 @@ fn process_json_line(
|
||||
usage.cache_read_input_tokens,
|
||||
);
|
||||
|
||||
println!("Assistant message tokens - input: {}, output: {}, cache_creation: {:?}, cache_read: {:?}, cost: ${:.4}",
|
||||
tracing::info!("Assistant message tokens - input: {}, output: {}, cache_creation: {:?}, cache_read: {:?}, cost: ${:.4}",
|
||||
usage.input_tokens,
|
||||
usage.output_tokens,
|
||||
usage.cache_creation_input_tokens,
|
||||
@@ -917,8 +917,8 @@ fn process_json_line(
|
||||
.unwrap_or_default()
|
||||
.as_millis() as u64;
|
||||
|
||||
eprintln!(
|
||||
"[DEBUG] Emitting agent-start: id={}, desc={}, type={}, parent={:?}",
|
||||
tracing::debug!(
|
||||
"Emitting agent-start: id={}, desc={}, type={}, parent={:?}",
|
||||
id, description, subagent_type, parent_tool_use_id
|
||||
);
|
||||
|
||||
@@ -1063,6 +1063,13 @@ fn process_json_line(
|
||||
duration_ms,
|
||||
num_turns,
|
||||
} => {
|
||||
tracing::info!(
|
||||
"Received Result message: subtype={}, has_denials={}, denial_count={:?}",
|
||||
subtype,
|
||||
permission_denials.is_some(),
|
||||
permission_denials.as_ref().map(|d| d.len())
|
||||
);
|
||||
|
||||
let state = if subtype == "success" {
|
||||
CharacterState::Success
|
||||
} else {
|
||||
@@ -1078,12 +1085,18 @@ fn process_json_line(
|
||||
tools
|
||||
});
|
||||
|
||||
tracing::debug!(
|
||||
"Captured {} pending tool use(s): {:?}",
|
||||
captured_pending_tools.len(),
|
||||
captured_pending_tools.iter().map(|t| &t.tool_name).collect::<Vec<_>>()
|
||||
);
|
||||
|
||||
// Log turn metrics if available
|
||||
if let Some(duration) = duration_ms {
|
||||
println!("Turn completed in {}ms", duration);
|
||||
tracing::info!("Turn completed in {}ms", duration);
|
||||
}
|
||||
if let Some(turns) = num_turns {
|
||||
println!("Turn count: {}", turns);
|
||||
tracing::info!("Turn count: {}", turns);
|
||||
}
|
||||
|
||||
// Track token usage from Result messages if available
|
||||
@@ -1113,7 +1126,7 @@ fn process_json_line(
|
||||
usage_info.cache_creation_input_tokens,
|
||||
usage_info.cache_read_input_tokens,
|
||||
);
|
||||
println!("Result message tokens - input: {}, output: {}, cache_creation: {:?}, cache_read: {:?}",
|
||||
tracing::info!("Result message tokens - input: {}, output: {}, cache_creation: {:?}, cache_read: {:?}",
|
||||
usage_info.input_tokens,
|
||||
usage_info.output_tokens,
|
||||
usage_info.cache_creation_input_tokens,
|
||||
@@ -1143,9 +1156,9 @@ fn process_json_line(
|
||||
let newly_unlocked = {
|
||||
let mut stats_guard = stats.write();
|
||||
stats_guard.get_session_duration();
|
||||
println!("Checking achievements after result message...");
|
||||
tracing::info!("Checking achievements after result message...");
|
||||
let unlocked = stats_guard.check_achievements();
|
||||
println!("Newly unlocked achievements: {:?}", unlocked);
|
||||
tracing::info!("Newly unlocked achievements: {:?}", unlocked);
|
||||
unlocked
|
||||
};
|
||||
|
||||
@@ -1160,20 +1173,20 @@ fn process_json_line(
|
||||
|
||||
// Save achievements after unlocking new ones
|
||||
if !newly_unlocked.is_empty() {
|
||||
println!("Saving newly unlocked achievements: {:?}", newly_unlocked);
|
||||
tracing::info!("Saving newly unlocked achievements: {:?}", newly_unlocked);
|
||||
let app_handle = app.clone();
|
||||
let achievements_progress = stats.read().achievements.clone();
|
||||
|
||||
// Use Tauri's async runtime instead of tokio::spawn
|
||||
tauri::async_runtime::spawn(async move {
|
||||
println!("Spawned save task for achievements");
|
||||
tracing::info!("Spawned save task for achievements");
|
||||
if let Err(e) =
|
||||
crate::achievements::save_achievements(&app_handle, &achievements_progress)
|
||||
.await
|
||||
{
|
||||
eprintln!("Failed to save achievements: {}", e);
|
||||
tracing::error!("Failed to save achievements: {}", e);
|
||||
} else {
|
||||
println!("Achievement save task completed successfully");
|
||||
tracing::info!("Achievement save task completed successfully");
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1190,9 +1203,9 @@ fn process_json_line(
|
||||
{
|
||||
let app_handle = app.clone();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
println!("Periodic stats save (every 10 messages)...");
|
||||
tracing::info!("Periodic stats save (every 10 messages)...");
|
||||
if let Err(e) = crate::stats::save_stats(&app_handle, ¤t_stats).await {
|
||||
eprintln!("Failed to save stats: {}", e);
|
||||
tracing::error!("Failed to save stats: {}", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1218,12 +1231,6 @@ fn process_json_line(
|
||||
if let Some(denials) = permission_denials {
|
||||
// Only process if there are actually denials
|
||||
if !denials.is_empty() {
|
||||
// Skip permission prompts if the result was successful - tools were already approved
|
||||
if subtype == "success" {
|
||||
emit_state_change(app, state, None, conversation_id.clone());
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut regular_permission_requests = Vec::new();
|
||||
|
||||
// Get denied tool IDs for later comparison
|
||||
@@ -1433,7 +1440,7 @@ fn process_json_line(
|
||||
// Check achievements after user message
|
||||
let newly_unlocked = {
|
||||
let mut stats_guard = stats.write();
|
||||
println!("User sent message, checking achievements...");
|
||||
tracing::info!("User sent message, checking achievements...");
|
||||
|
||||
// Check message-based achievements
|
||||
let mut unlocked = crate::achievements::check_message_achievements(
|
||||
@@ -1450,7 +1457,7 @@ fn process_json_line(
|
||||
|
||||
// Emit achievement events for any newly unlocked achievements
|
||||
for achievement_id in &newly_unlocked {
|
||||
println!("User message unlocked achievement: {:?}", achievement_id);
|
||||
tracing::info!("User message unlocked achievement: {:?}", achievement_id);
|
||||
let info = get_achievement_info(achievement_id);
|
||||
let _ = app.emit(
|
||||
"achievement:unlocked",
|
||||
@@ -1460,7 +1467,7 @@ fn process_json_line(
|
||||
|
||||
// Save achievements after unlocking new ones
|
||||
if !newly_unlocked.is_empty() {
|
||||
println!("Saving newly unlocked achievements from user message");
|
||||
tracing::info!("Saving newly unlocked achievements from user message");
|
||||
let app_handle = app.clone();
|
||||
let achievements_progress = stats.read().achievements.clone();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
@@ -1468,9 +1475,9 @@ fn process_json_line(
|
||||
crate::achievements::save_achievements(&app_handle, &achievements_progress)
|
||||
.await
|
||||
{
|
||||
eprintln!("Failed to save achievements: {}", e);
|
||||
tracing::error!("Failed to save achievements: {}", e);
|
||||
} else {
|
||||
println!("Achievements saved after user message");
|
||||
tracing::info!("Achievements saved after user message");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -48,15 +48,15 @@ $notifier.Show($toast)
|
||||
match output {
|
||||
Ok(result) => {
|
||||
if result.status.success() {
|
||||
println!("WSL notification sent successfully");
|
||||
tracing::info!("WSL notification sent successfully");
|
||||
return Ok(());
|
||||
} else {
|
||||
let stderr = String::from_utf8_lossy(&result.stderr);
|
||||
println!("PowerShell toast failed: {}", stderr);
|
||||
tracing::error!("PowerShell toast failed: {}", stderr);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Failed to run PowerShell: {}", e);
|
||||
tracing::error!("Failed to run PowerShell: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ $notifier.Show($toast)
|
||||
|
||||
if let Ok(result) = notify_result {
|
||||
if result.status.success() {
|
||||
println!("Notification sent via wsl-notify-send");
|
||||
tracing::info!("Notification sent via wsl-notify-send");
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user