The test for unknown result subtype was using an invalid subtype value
directly, which caused a TypeScript error. Changed to use a type
assertion to properly test the unknown subtype case, matching the
pattern used for testing unknown message types.
This fix ensures all TypeScript checks pass whilst still testing the
edge case behaviour for unexpected subtype values.
Add comprehensive test coverage to prevent regressions:
**Snippets Store:**
- Test filteredSnippets derived store with and without category filter
- Cover all branches of the filter logic
**State Mapper:**
- Test unknown tool handling (defaults to typing)
- Test thinking content blocks in assistant messages
- Test empty content arrays
- Test all stream_event delta types (thinking_delta, text_delta)
- Test all content_block_start types (thinking, text, tool_use)
- Test unrecognized stream event types
- Test unknown result subtypes
- Test unknown message types
- Test extractTextFromMessage edge cases (null result, no delta text)
These additions bring test count from 335 to 351 and significantly improve
branch coverage for these critical utility functions.
Add extensive test coverage to prevent regressions of bugs fixed in this branch:
- Test rapid sequential config updates
- Test concurrent updates preserving all fields
- Test overlapping save operations
- Test config data persistence across operations
- Test auto-granted tools not being lost
- Test custom theme colours persisting
- Test graceful error handling during saves
- Test config load/save cycle
Also update check-all.sh to run coverage for both frontend and backend tests,
matching the CI pipeline behaviour.
These tests would have caught both the config race condition and persistence
bugs we encountered, preventing future regressions.
In production builds with windows_subsystem = "windows", stdout is suppressed.
The fmt::layer() was outputting to this hidden stdout, making logs invisible.
Now all tracing logs only go through TauriLogLayer to the debug console.
NOTE: There are still many println!/eprintln! calls throughout the codebase
that bypass tracing entirely. These should be gradually migrated to use
tracing::info!/tracing::error! macros for visibility in production builds.
Added extensive logging to help diagnose permission modal issues:
Frontend:
- Capture unhandled errors via window.addEventListener('error')
- Capture unhandled promise rejections
- All errors now visible in debug console
Backend:
- Log when emitting permission events (count and conversation ID)
- Log each individual permission request being processed
- Log when system tools are skipped
- Log each denial being processed
This will help identify where the permission flow breaks in production builds.
Updated the permission modal overlay z-index from z-50 to z-[60] to ensure
it displays above the character panel (z-50). This prevents the modal from
being obscured by other UI elements.
Part of the broader permission modal fix effort.
Added:
- Table of contents for easy navigation
- Detailed explanations of BOTH bugs (permission modal + config loss)
- Step-by-step debugging procedures for each issue
- Code examples showing wrong vs right patterns
- Common anti-patterns to avoid (Svelte stores, event listeners)
- Quick debugging checklist
- Prevention tips and testing procedures
- Clear ordering: check console FIRST, not z-index!
This guide now documents the complete troubleshooting process so Naomi
never has to explain these issues again.
CRITICAL BUG: The config store had a race condition in all methods that
accessed the current config. The pattern:
```typescript
let currentConfig = defaultConfig;
config.subscribe((c) => (currentConfig = c))();
```
This immediately unsubscribes (the `()` at the end), creating a race where
sometimes it would get the value, sometimes it wouldn't. This caused:
- Config appearing "lost" after permission approvals
- Settings resetting to defaults randomly
- Model selection not persisting
FIX:
- Created a proper `getCurrentConfig()` helper that unsubscribes cleanly
- Replaced ALL instances of the buggy pattern (12 occurrences!)
- getConfig(), updateConfig(), toggles, theme setters, font methods, etc.
This should completely fix the config loss issue.
Created DEBUGGING.md to document:
- Permission modal troubleshooting (NOT z-index!)
- Config loss prevention and fixes
- Event listener debugging
- Testing checklist
- Emergency recovery procedures
This should prevent us from going through the same debugging nightmare twice.
This fixes two severe bugs:
1. Missing debugConsoleStore import causing undefined variable errors
2. Replace non-existent .log() method with console.log()
3. Add #[serde(default)] to HikariConfig to handle missing fields gracefully
The undefined variable was causing initialization to fail, which prevented
the permission modal from displaying properly.
Implemented a confirmation modal when users try to close the application:
- Modal always shows with three options: Cancel, Minimize to Tray, Close Application
- Detects if Claude is actively running and shows appropriate warning message
- Removed minimize_to_tray config setting (no longer needed)
- Added core:window:allow-hide permission for window hiding
- Created CloseAppConfirmModal component with keyboard shortcuts (Escape to cancel)
- Added close_application command to properly exit the app
- Backend emits window-close-requested event for frontend to handle
This provides better UX by giving users clear choices every time they close,
preventing accidental closures during active work sessions.
Fixes#113 - EnterPlanMode/ExitPlanMode infinite permission loops
This commit addresses multiple related issues with the permission system:
1. Added system tool filtering to sibling tools loop to prevent
EnterPlanMode/ExitPlanMode from appearing in permission modals
2. Skip permission modal processing entirely when operations succeed
(subtype == "success"), since tools were already approved and executed
3. Emit proper state change before early return to prevent Hikari
from getting stuck in "typing" state
The early return happens after all stats, costs, and achievements
are processed, so no data tracking is affected.
✨ This issue was fixed with help from Hikari~ 🌸
Fixed infinite permission loop when using ExitPlanMode and other
system tools. These tools are now automatically allowed without
requiring user approval.
The issue occurred because all tool denials triggered permission
prompts, including system tools like ExitPlanMode that should never
require permission. This caused an infinite loop where:
1. Claude Code calls ExitPlanMode
2. Hikari shows permission modal
3. User approves
4. Claude Code retries ExitPlanMode
5. Loop repeats
Solution:
- Added is_system_tool helper function to identify system tools
- System tools (ExitPlanMode, EnterPlanMode) are now skipped in
permission denial processing
- These tools execute immediately without user intervention
Closes#113
Implemented intelligent permission batching that detects cancelled sibling
tool calls and presents them together in a single modal. This dramatically
improves the user experience when multiple tools require permission.
Key changes:
- Track pending tool uses from Assistant messages in thread-local storage
- Capture and batch sibling tools that get cancelled due to permission denials
- Clear pending tools on each Result message to prevent accumulation
- Use SvelteSet for reactive permission selection in the modal
- Update permission modal to display count when multiple permissions requested
- Fix check-all.sh to source nvm for pnpm access
- Add git commit instructions to CLAUDE.md for this project
Technical improvements:
- Thread-local storage for cross-message tool tracking
- Proper null checking in TypeScript permission handling
- Clippy-compliant const initialisation for thread_local
- All ESLint, TypeScript, and Rust checks passing
The modal now shows both the explicitly denied tool AND any sibling tools
that were called in parallel, allowing users to approve all permissions
in one go instead of clicking through multiple modals.