# 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 `claude` binary (checks `~/.local/bin`, `~/.claude/local`, system paths, and falls back to a login-shell `which 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`, `RateLimitEvent`
- `ContentBlock` — `Text`, `Thinking`, `ToolUse`, `ToolResult`
- `CharacterState` — `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

```bash
# Frontend dev server only
source ~/.nvm/nvm.sh && pnpm dev

# Full Tauri app (Rust + frontend)
source ~/.nvm/nvm.sh && pnpm tauri dev
```

### Running Tests

```bash
# 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

```bash
# Linux build
pnpm build:linux

# Windows cross-compile (requires cargo-xwin)
pnpm build:windows
```

### Adding a New Tauri Command

1. Add the handler function in the appropriate `src-tauri/src/*.rs` file with `#[tauri::command]`
2. Register it in `lib.rs` `invoke_handler![]`
3. Call it from the frontend via `invoke("command_name", { args })` in `src/lib/tauri.ts` or a store

### Adding a New Frontend Store

1. Create `src/lib/stores/my-store.ts` using `writable` or a factory function pattern
2. Create `src/lib/stores/my-store.test.ts` — all stores must have tests
3. 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 `thinking` from a clean state (`idle`/`success`/`error`) → reset all sound flags
- Entering `coding` or `searching` (first time per task) → play task-start sound
- Entering `success` after ≥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.
