## Summary A large productivity-focused feature branch delivering a suite of improvements across automation, project management, theming, performance, and documentation. ### Features - **Guided Project Workflow** (#189) — Four-phase workflow panel (Discuss → Plan → Execute → Verify) to keep projects structured from idea to completion - **Automated Task Loop** (#179) — Per-task conversation orchestration with wave-based parallel execution, blocked-task detection, and concurrency control - **Wave-Based Parallel Execution** (#191) — Tasks run in dependency-aware waves with configurable concurrency; independent tasks execute in parallel - **Auto-Commit After Task Completion** (#192) — Task Loop optionally commits after each completed task so progress is never lost - **PRD Creator** (#180) — AI-assisted PRD and task list panel that outputs `hikari-tasks.json` for the Task Loop to consume - **Project Context Panel** (#188) — Persistent `PROJECT.md`, `REQUIREMENTS.md`, `ROADMAP.md`, and `STATE.md` files injected into Claude's context automatically - **Codebase Mapper** (#190) — Generates a `CODEBASE.md` architectural summary so Claude always understands the project structure - **Community Preset Themes** (#181) — Six built-in community themes: Dracula, Catppuccin Mocha, Nord, Solarized Dark, Gruvbox Dark, and Rosé Pine - **In-App Changelog Panel** (#193) — Fetches release notes from GitHub at runtime and displays them inside the app - **Full Embedded Documentation** (#196) — Replaced the single-page help modal with a 12-page paginated docs browser featuring a sidebar TOC, prev/next navigation, keyboard navigation (arrow keys, `?` shortcut), and comprehensive coverage of every feature ### Performance & Fixes - **Lazy Loading & Virtualisation** (#194) — Virtual windowing for conversation history, markdown memoisation, and debounced search for smooth rendering of large sessions - **Ctrl+C Copy Fix** (#195) — `Ctrl+C` now copies selected text as expected; interrupt-Claude behaviour only fires when no text is selected ### UX - Back-to-workflow button in PRD Creator and Task Loop panels for easy navigation - Navigation icon cluster replaced with a single clean dropdown menu ## Closes Closes #179 Closes #180 Closes #181 Closes #188 Closes #189 Closes #190 Closes #191 Closes #192 Closes #193 Closes #194 Closes #195 Closes #196 --- ✨ This PR was created with help from Hikari~ 🌸 Reviewed-on: #197 Co-authored-by: Hikari <hikari@nhcarrigan.com> Co-committed-by: Hikari <hikari@nhcarrigan.com>
26 KiB
Hikari Desktop — Codebase Map
Auto-generated codebase overview. Last updated: 2026-03-06.
Overview
Hikari Desktop is a Tauri v2 desktop application that wraps the Claude Code CLI with a visual anime character avatar (Hikari) who appears on-screen and reacts in real-time to Claude's activity. When Claude is thinking, she thinks. When it's editing code, she codes. When it's using MCP tools, she glows with magical energy.
The app supports multiple simultaneous conversations (tabs), each with its own isolated Claude CLI process. It provides a rich UI layer on top of Claude Code, including a built-in file editor, git panel, achievement system, cost tracking, session history, notifications, and more.
Repositories:
- Primary:
git.nhcarrigan.com(Gitea) —nhcarrigan/hikari-desktop - Mirror:
github.com/naomi-lgbt/hikari-desktop
Current version: 1.10.0
Architecture
The application follows a standard Tauri architecture:
┌──────────────────────────────────────────────────────────────┐
│ Frontend (WebView) │
│ SvelteKit + Svelte 5 + TailwindCSS 4 + TypeScript │
│ │
│ ┌─────────┐ ┌──────────┐ ┌──────────────┐ ┌──────────┐ │
│ │AnimeGirl│ │ Terminal │ │ InputBar │ │ Editor │ │
│ │ Sprites │ │ View │ │ + Slash Cmds│ │CodeMirror│ │
│ └────┬────┘ └────┬─────┘ └──────┬───────┘ └────┬─────┘ │
│ │ │ │ │ │
│ ┌────▼─────────────▼───────────────▼────────────────▼──────┐ │
│ │ Svelte Stores (reactive state) │ │
│ │ conversations · character · config · agents · stats … │ │
│ └──────────────────────────┬───────────────────────────────┘ │
│ │ tauri.ts (event listeners) │
└─────────────────────────────┼────────────────────────────────┘
│ Tauri IPC (invoke / emit)
┌─────────────────────────────┼────────────────────────────────┐
│ Backend (Rust) │
│ ┌──────────────────────────▼───────────────────────────────┐ │
│ │ commands.rs (invoke handlers) │ │
│ └──────────────────────────┬───────────────────────────────┘ │
│ │ │
│ ┌──────────────────────────▼───────────────────────────────┐ │
│ │ BridgeManager — HashMap<conversation_id, WslBridge> │ │
│ └──────────────────────────┬───────────────────────────────┘ │
│ │ │
│ ┌──────────────────────────▼───────────────────────────────┐ │
│ │ WslBridge — spawns `claude --output-format stream-json`│ │
│ │ reads NDJSON stdout → emits events to frontend │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ config · stats · cost_tracking · sessions · git · clipboard │
│ achievements · discord_rpc · notifications · snippets … │
└──────────────────────────────────────────────────────────────┘
Directory Structure
hikari-desktop/
├── src/ # SvelteKit frontend
│ ├── routes/
│ │ ├── +page.svelte # Main app layout (root page)
│ │ ├── +layout.svelte # App-level layout wrapper
│ │ ├── +layout.ts # SvelteKit layout config (SSR disabled)
│ │ └── test-achievement/ # Dev-only achievement test page
│ ├── lib/
│ │ ├── tauri.ts # Tauri event listeners + IPC bridge
│ │ ├── commands/ # Slash command definitions
│ │ ├── components/ # 60+ Svelte components
│ │ │ └── editor/ # CodeMirror-based file editor components
│ │ ├── notifications/ # Notification system
│ │ ├── sounds/ # Sound effect triggers
│ │ ├── stores/ # All Svelte reactive stores
│ │ ├── types/ # TypeScript type definitions
│ │ └── utils/ # Pure utility functions
│ ├── app.css # Global styles + CSS variables (themes)
│ └── app.html # HTML shell
│
├── src-tauri/ # Tauri Rust backend
│ ├── src/
│ │ ├── main.rs # Process entry point
│ │ ├── lib.rs # Tauri app setup + command registration
│ │ ├── types.rs # All shared Rust types + serialisation
│ │ ├── wsl_bridge.rs # Claude CLI process management + NDJSON parser
│ │ ├── bridge_manager.rs # Per-conversation WslBridge registry
│ │ ├── commands.rs # All #[tauri::command] handlers
│ │ ├── config.rs # Config read/write (tauri-plugin-store)
│ │ ├── stats.rs # Token usage + cost calculation
│ │ ├── cost_tracking.rs # Budget alerts + cost history (CSV export)
│ │ ├── achievements.rs # Achievement unlock logic
│ │ ├── sessions.rs # Conversation session persistence (JSON)
│ │ ├── git.rs # Git operations via CLI
│ │ ├── clipboard.rs # Clipboard history management
│ │ ├── notifications.rs # System notification dispatch
│ │ ├── discord_rpc.rs # Discord Rich Presence manager
│ │ ├── drafts.rs # Draft message persistence
│ │ ├── snippets.rs # Snippet library CRUD
│ │ ├── quick_actions.rs # Quick action CRUD
│ │ ├── debug_logger.rs # TauriLogLayer (routes tracing → frontend)
│ │ ├── temp_manager.rs # Temporary file lifecycle management
│ │ ├── tool_cache.rs # Tool call result caching
│ │ ├── tray.rs # System tray setup
│ │ ├── process_ext.rs # HideWindow trait (Windows console hiding)
│ │ ├── vbs_notification.rs # VBScript-based notification fallback (Windows)
│ │ ├── windows_toast.rs # Windows native toast notifications
│ │ └── wsl_notifications.rs# WSL notify-send bridge
│ ├── capabilities/ # Tauri permission capabilities
│ ├── tests/ # Rust integration tests
│ ├── Cargo.toml
│ ├── Cargo.lock
│ └── tauri.conf.json # Tauri app configuration
│
├── static/
│ ├── sprites/ # Anime character PNG sprites (one per state)
│ └── sounds/ # MP3 sound effects (connected, working, done…)
│
├── check-all.sh # Full QA script (lint → format → types → test)
├── vitest.config.ts # Frontend test configuration
├── vitest.setup.ts # Tauri API mocks for tests
├── svelte.config.js # SvelteKit config (static adapter)
├── vite.config.js # Vite config
├── eslint.config.js # ESLint 9 flat config
├── tsconfig.json # TypeScript config
└── .gitea/workflows/ # CI/CD (Gitea Actions)
Key Components
Backend (Rust)
wsl_bridge.rs — Claude CLI Process Manager
The most critical backend file. WslBridge spawns a single claude CLI process per conversation using --output-format stream-json, which causes Claude Code to emit NDJSON messages on stdout. A dedicated reader thread consumes stdout line-by-line, parses each line into a ClaudeMessage enum variant, and emits the appropriate frontend events.
Key responsibilities:
- Locates the
claudebinary (checks~/.local/bin,~/.claude/local, system paths, and falls back to a login-shellwhich claude) - Detects WSL environment to handle cross-platform path differences
- Maps tool names to character states (Read/Glob/Grep →
searching, Edit/Write →coding,mcp__*→mcp) - Batches permission requests from a single assistant message
- Tracks token usage per session
bridge_manager.rs — Multi-Conversation Orchestrator
BridgeManager holds a HashMap<String, WslBridge> keyed by conversation_id. This enables true parallel conversations — each tab has its own isolated Claude process. The manager is wrapped in Arc<Mutex<BridgeManager>> (using parking_lot) and injected into Tauri's managed state.
types.rs — Shared Type Definitions
Defines the complete Claude stream-JSON protocol as Rust enums/structs:
ClaudeMessage— top-level message variants:System,Assistant,User,StreamEvent,Result,RateLimitEventContentBlock—Text,Thinking,ToolUse,ToolResultCharacterState—Idle | Thinking | Typing | Searching | Coding | Mcp | Permission | Success | Error- All frontend event types (
OutputEvent,StateChangeEvent,PermissionPromptEvent,AgentStartEvent, etc.)
commands.rs — IPC Command Handlers
Registers all Tauri commands exposed to the frontend. Over 80 commands covering: Claude process management, configuration, stats, sessions, git, clipboard, cost tracking, MCP servers, plugins, drafts, snippets, quick actions, file system operations, authentication, and notifications.
debug_logger.rs — In-App Debug Console
A custom tracing subscriber layer (TauriLogLayer) that captures all tracing::info!/warn!/error! calls and emits them as debug:log events to the frontend debug console — essential since production Windows builds have no stdout.
Frontend (TypeScript/Svelte 5)
src/routes/+page.svelte — Root Layout
The main page. Renders a two-panel layout:
- Left panel:
<AnimeGirl>character display with state-reactive glow effects (trans pride gradient colours per state) - Right panel:
<Terminal>+<InputBar>(or<EditorPanel>when the editor is open)
Also handles: global keyboard shortcuts, compact mode (280×400 mini widget), window close confirmation, Discord RPC updates, and background image loading.
src/lib/tauri.ts — Event Bridge
Sets up all Tauri event listeners on app mount. Translates backend events into store mutations:
| Event | Action |
|---|---|
claude:connection |
Updates conversation connection status; sends greeting on first connect |
claude:state |
Updates character state; triggers per-conversation sound effects |
claude:output |
Appends lines to the correct conversation's terminal history |
claude:session |
Stores the Claude session ID |
claude:cwd |
Updates working directory (used by the editor) |
claude:permission |
Adds permission requests to conversation state |
claude:agent-start/end |
Updates agent monitor panel |
claude:question |
Stores pending user question |
Also manages Discord RPC updates and the session greeting flow.
src/lib/stores/conversations.ts — Core State Store
The central state container. Each conversation (Conversation interface) tracks:
- Terminal lines (
TerminalLine[]) - Connection status, session ID, working directory
- Character state, processing flag
- Granted/pending tool permissions
- Pending user questions
- Scroll position, attachments, draft text
- Sound tracking (per-conversation, prevents replays on tab switch)
- Conversation summary (for compaction)
Tab names are randomly chosen from a curated list of whimsical names (Starfall, Moonbeam, Sakura, etc.).
src/lib/stores/claude.ts — Backwards-Compat Facade
A thin wrapper that re-exports conversationsStore methods under the original claudeStore API. Maintains backwards compatibility whilst the codebase migrated to multi-conversation support.
src/lib/stores/character.ts — Character State Store
Manages the global character state displayed by <AnimeGirl>. Supports setState() (persistent) and setTemporaryState(state, durationMs) (auto-reverts to idle after a timeout — used for success/error flashes).
src/lib/utils/stateMapper.ts — Stream → State Mapping
Pure utility that maps Claude stream-JSON message types to CharacterState values. Tool categorisation mirrors the Rust side: search tools → searching, coding tools → coding, MCP tools → mcp, Task tool → thinking.
src/lib/components/
Key components beyond the basics:
| Component | Purpose |
|---|---|
AnimeGirl.svelte |
Displays the character sprite, subscribes to characterState |
Terminal.svelte |
Renders the conversation message history |
InputBar.svelte |
User input with slash command menu, attachment support |
StatusBar.svelte |
Top bar: connection indicator, token/cost stats, controls |
ConversationTabs.svelte |
Multi-tab navigation with per-tab status indicators |
ConfigSidebar.svelte |
Settings panel (model, theme, notifications, budget, etc.) |
PermissionModal.svelte |
Handles tool permission grant/deny UI |
UserQuestionModal.svelte |
Renders AskUserQuestion prompts from Claude |
AgentMonitorPanel.svelte |
Live subagent tree with status badges |
GitPanel.svelte |
Git status, diff, stage/unstage, commit, push/pull |
editor/EditorPanel.svelte |
Full CodeMirror editor with file browser and tabs |
DiffViewer.svelte |
Syntax-highlighted diff display |
AchievementsPanel.svelte |
Achievement gallery |
CostSummary.svelte |
Cost breakdown by session/day/week/month |
MemoryBrowserPanel.svelte |
Browse Claude memory files |
McpManagementPanel.svelte |
MCP server configuration UI |
DebugConsole.svelte |
In-app log viewer (receives debug:log events) |
ThinkingBlock.svelte |
Collapsible extended thinking display |
ToolCallBlock.svelte |
Formatted tool use/result display |
Data Flow
User Sends a Message
User types → InputBar
→ invoke("send_prompt", { conversationId, message })
→ BridgeManager.send_prompt(conversation_id, message)
→ WslBridge.send_message() → writes JSON to Claude CLI stdin
Claude Responds (NDJSON Stream)
Claude CLI stdout (NDJSON)
→ WslBridge reader thread (line-by-line)
→ serde_json::from_str::<ClaudeMessage>()
→ match message type:
System(init) → emit claude:connection(connected) + claude:cwd
StreamEvent → emit claude:state(thinking|typing|searching|coding|mcp)
Assistant → emit claude:output(assistant|tool|thinking lines)
User(tool_result)→ emit claude:output(tool result lines)
Result(success) → emit claude:state(success) + claude:output(result)
Result(error) → emit claude:state(error)
RateLimitEvent → emit claude:output(rate-limit line)
PermissionRequest→ emit claude:permission
Frontend Reacts
tauri.ts event listeners
→ conversationsStore mutations
→ Svelte reactivity propagates to components
→ AnimeGirl.svelte: sprite changes to match characterState
→ Terminal.svelte: new line appended
→ StatusBar.svelte: token counts update
→ ConversationTabs.svelte: tab glow colour updates
Permission Flow
Claude requests tool permission
→ WslBridge batches pending tool uses
→ emit claude:permission (one or more requests)
→ tauri.ts → claudeStore.requestPermissionForConversation()
→ PermissionModal.svelte renders
→ User clicks Allow/Deny
→ invoke("answer_question", { conversationId, toolUseId, granted })
→ WslBridge.send_tool_result() → writes result to Claude stdin
→ Claude CLI resumes
State Machine
The CharacterState enum drives both the sprite displayed and the panel glow colour:
| State | Trigger | Sprite | Panel Glow |
|---|---|---|---|
idle |
Connected, no activity | Standing with clipboard | None |
thinking |
Thinking block / Task tool | Hand on chin | Purple/trans gradient |
typing |
Text content block | At keyboard | Blue/trans gradient |
searching |
Read/Glob/Grep/WebSearch/WebFetch | Magnifying glass | Yellow/trans gradient |
coding |
Edit/Write/NotebookEdit | At monitor | Green/trans gradient |
mcp |
Any mcp__* tool |
Magical blue energy | Trans pride vibrant |
permission |
Permission requested | Confused shrug | — |
success |
Result: success | Celebrating | Emerald/trans gradient |
error |
Result: error | Worried | Red/trans gradient |
success and error are temporary states (3-second auto-revert to idle).
Dependencies
Frontend (key packages)
| Package | Purpose |
|---|---|
@sveltejs/kit svelte |
SvelteKit framework + Svelte 5 |
@tauri-apps/api |
Core Tauri IPC (invoke, listen) |
@tauri-apps/plugin-* |
FS, clipboard, notifications, dialog, shell, store, os, opener |
tailwindcss v4 |
Utility-first CSS |
codemirror + @codemirror/* |
Code editor with 20+ language modes |
marked |
Markdown → HTML rendering |
highlight.js |
Syntax highlighting in markdown blocks |
lucide-svelte |
Icon library |
Backend (key crates)
| Crate | Purpose |
|---|---|
tauri v2 |
Desktop app framework |
tokio |
Async runtime |
serde / serde_json |
JSON serialisation/deserialisation |
parking_lot |
Fast mutex (used for BridgeManager) |
uuid |
Unique ID generation |
discord-rich-presence |
Discord RPC integration |
chrono |
Date/time handling for cost tracking |
semver |
Version comparison for update checks |
tempfile |
Temporary file management |
tracing + tracing-subscriber |
Structured logging |
dirs |
Cross-platform home directory resolution |
windows (Windows-only) |
Native toast notifications |
Dev / Tooling
| Tool | Purpose |
|---|---|
vitest + @vitest/coverage-v8 |
Frontend unit tests with v8 coverage |
@testing-library/svelte |
Component testing utilities |
jsdom |
DOM environment for tests |
eslint v9 (flat config) |
Linting |
prettier |
Formatting |
svelte-check |
TypeScript type checking for Svelte files |
cargo test + cargo llvm-cov |
Rust unit tests and coverage |
Development Notes
Running the App
# Frontend dev server only
source ~/.nvm/nvm.sh && pnpm dev
# Full Tauri app (Rust + frontend)
source ~/.nvm/nvm.sh && pnpm tauri dev
Running Tests
# All checks (lint → format → type-check → frontend tests → backend tests)
./check-all.sh
# Frontend tests only
source ~/.nvm/nvm.sh && pnpm test
# Frontend with coverage
source ~/.nvm/nvm.sh && pnpm test:coverage
# Backend tests only
pnpm test:backend
Building
# Linux build
pnpm build:linux
# Windows cross-compile (requires cargo-xwin)
pnpm build:windows
Adding a New Tauri Command
- Add the handler function in the appropriate
src-tauri/src/*.rsfile with#[tauri::command] - Register it in
lib.rsinvoke_handler![] - Call it from the frontend via
invoke("command_name", { args })insrc/lib/tauri.tsor a store
Adding a New Frontend Store
- Create
src/lib/stores/my-store.tsusingwritableor a factory function pattern - Create
src/lib/stores/my-store.test.ts— all stores must have tests - Expose the store from the appropriate component
Claude Stream-JSON Protocol
Claude Code is invoked with --output-format stream-json --verbose. See src-tauri/src/types.rs for the complete message type definitions. The key field distinguishing subagent messages from top-level messages is parent_tool_use_id on Assistant messages.
Multi-Conversation Architecture
Each tab (Conversation) in conversationsStore has a unique conversation_id string. The backend BridgeManager maps these IDs to WslBridge instances. All Tauri events carry conversation_id in their payload so the frontend can route them to the correct conversation without affecting others.
WSL Detection
wsl_bridge.rs detects WSL by checking /proc/version for "microsoft"/"wsl" strings, checking for /proc/sys/fs/binfmt_misc/WSLInterop, and checking $WSL_DISTRO_NAME. On native Windows builds, WSL detection always returns false (even if launched from a WSL terminal).
Character State Sound Rules
Sound effects are managed in src/lib/tauri.ts per-conversation to prevent replays when switching tabs. The rules are:
- Entering
thinkingfrom a clean state (idle/success/error) → reset all sound flags - Entering
codingorsearching(first time per task) → play task-start sound - Entering
successafter ≥2 seconds in a long-running phase → play completion sound - Entering
error→ play error sound (always) - Entering
permission→ play permission sound (always)
Workspace Trust Gate
On first connection to a new working directory, the app checks for Claude hooks and prompts the user to trust the workspace. Trusted workspaces are persisted in HikariConfig.trusted_workspaces.
Configuration Storage
All settings are persisted via tauri-plugin-store to a JSON file in the app data directory. The frontend configStore (src/lib/stores/config.ts) loads configuration on startup and provides reactive derived stores. Changes invoke save_config to persist to disk.