chore: remove debugging infrastructure after permission fixes
Security Scan and Upload / Security & DefectDojo Upload (pull_request) Successful in 1m0s
CI / Lint & Test (pull_request) Successful in 16m11s
CI / Build Linux (pull_request) Successful in 20m14s
CI / Build Windows (cross-compile) (pull_request) Successful in 30m1s

Removes temporary debugging docs and excessive logging that were added to diagnose and fix permission modal issues.

Cleaned up:
- Deleted DEBUGGING.md (temporary troubleshooting guide)
- Deleted FIXES-2026-02-06.md (temporary fix summary)
- Removed debug logging from all Rust modules
This commit is contained in:
2026-02-06 23:40:09 -08:00
committed by Naomi Carrigan
parent 4078b2b640
commit ea111569af
9 changed files with 83 additions and 786 deletions
-600
View File
@@ -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
-110
View File
@@ -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 ✅
+12 -12
View File
@@ -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
+1 -1
View File
@@ -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");
+1 -1
View File
@@ -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
+8 -8
View File
@@ -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
+3 -3
View File
@@ -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
View File
@@ -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, &current_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");
}
});
}
+4 -4
View File
@@ -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(());
}
}