Setup
Prerequisites
- Rust 1.70+ (
rustup default stable-msvcon Windows;rustup default stableon macOS). - Node.js 20+ and npm 10+ (CI uses Node 24).
- Platform toolchain:
- Windows: Microsoft C++ Build Tools (Visual Studio Installer → “Desktop development with C++”) and WebView2 (preinstalled on Windows 10 1803+; the installer fetches it if missing on older machines).
- macOS: Xcode Command Line Tools (
xcode-select --install). WKWebView ships with the OS — nothing to install.
Install
git clone git@github.com:AnotherSava/claude-code-dashboard.git
cd claude-code-dashboard
npm install
Run from source
npm run tauri dev
Compiles the Rust backend, starts Vite on localhost:1420, and launches the native window. Frontend edits hot-reload; Rust edits trigger a rebuild on save.
Commands
npm run tauri dev— dev build with HMR.npm run tauri build— release build; bundles land insrc-tauri/target/release/bundle/(nsis/on Windows,dmg/on macOS).npm run check— TypeScript + Svelte check (no build).npm run tauri icon <path/to/1024.png>— regenerate the Windows / macOS icon set from a source PNG.cargo test --manifest-path src-tauri/Cargo.toml --lib— Rust unit tests (state machine, transcript parser, merge policy, Claude adapter, label policy).
Architecture
The app pairs a Rust backend (Tauri v2) with a Svelte 5 + Vite frontend rendered in the system webview (WebView2 on Windows, WKWebView on macOS). The Rust side owns all state and external I/O; the frontend is a pure view that subscribes to Tauri events and issues invoke-style commands for window control. External tools integrate via an embedded axum HTTP server on 127.0.0.1:9077, bypassing the frontend entirely.
The source-of-truth AgentSession state lives behind a Mutex in Rust. Three paths mutate it — the HTTP server, the per-session transcript watcher, and Tauri commands invoked from the Svelte UI — and every mutation funnels through state::apply_set or state::apply_clear so the sticky-label state machine is enforced in exactly one place.
Project structure
Under the repo root claude-code-dashboard/:
src/— Svelte frontend (Vite)App.svelte— top-level layout, subscribes to Tauri eventsHistoryApp.svelte— root component of the history windowAboutApp.svelte— root component of the About window (Help → About)main.ts— mount entry pointlib/types.ts— shared TS types and display helpersmockSessions.ts— dev-only fixtures (unused in release)api.ts— invoke / listen wrapperscomponents/SessionList.svelte— list container, empty-stateSessionItem.svelte— per-row rendering (status badge, timer, tokens, label)SetupPanel.svelte— onboarding panel: bundled hook snippet, copy-to-clipboard, hide affordanceLimitBar.svelte— header 5h / 7d usage bar (segmented fill, percent + timer caps)
src-tauri/Cargo.toml— Rust deps: tauri, axum, notify, tracing, serde, reqwest, chrono, opentauri.conf.json— NSIS + DMG bundle targets, WebView2 bootstrapper, window configcapabilities/default.json— capability-based permissions for the main windowsrc/main.rs— entry; calls lib::run()lib.rs— Builder: plugins, state, commands, setup hookstate.rs— AgentSession struct, apply_set sticky-label machineconfig.rs— Config struct, load/save, ConfigState wrapperconfig_watcher.rs— notify watcher for config.json hot-reloadcommands.rs— Tauri commands + event emitterssetup.rs— embedded Python hook + settings.json snippet builder for onboardinghttp_server.rs— axum routes for POST /api/eventsync.rs— multi-device session sync: bearer-gated listener + chunked delta pushlog_watcher.rs— per-session transcript tailing + infer_state + assistant text upserttray.rs— TrayIconBuilder, menu handlers, autostartnotifications.rs— 1s-tick reconciler + Notifier traittelegram.rs— reqwest-based Telegram Bot API clientusage_limits.rs— Anthropic OAuth usage poller + refresh (5h / 7d buckets)usage_history.rs— appends each successful usage poll tousage_history.jsonlprompt_history.rs— per-session dialog persistence toprompt_history.jsonremote_history.rs— per-device remote-session dialog persistence underremote_history/chat_id_registry.rs— persistedsession_id → chat_idlock insession_chat_ids.jsoncustom_names.rs— user-assigned display names persisted tocustom_names.jsonterminal_title.rs— mirrors session status onto terminal tab titlesauto_resize.rs— Up/Down content-fit window + Win32 resize lock + dark class brushlabel_policy.rs— shared (label, original_prompt) decision used by adaptersadapters.rs— adapter dispatch for /api/event payloadsadapters/claude.rs— Claude Code lifecycle classifier + chat-id derivationlogging.rs— tracing subscriber → widget.jsonl + FrontendLogger for IPC log lines
integrations/claude_hook.py— thin Claude Code hook that forwards the stdin payload to /api/eventdocs/— this site.github/workflows/build.yml— CI: check + cargo test + frontend build on push/PR (Windows + macOS matrix)release.yml— CI: build NSIS + DMG installers on tag push (Windows + macOS matrix)
Where state lives at runtime
- In-memory —
AppState(sessions) andConfigState(config) viatauri::State. - On disk —
config.json,widget.jsonl,prompt_history.json,session_chat_ids.json,custom_names.json,usage_history.jsonl, and theremote_history/directory underapp_data_dir():- Windows:
%APPDATA%\com.anothersava.claude-code-dashboard\ - macOS:
~/Library/Application Support/com.anothersava.claude-code-dashboard/
- Windows:
Architecture reference
- Classification — how the Claude adapter turns a raw lifecycle payload into the
(chat_id, status, label)tuple the widget renders. - Sticky labels — the state machine that keeps a meaningful caption next to a session row across approval cycles, cancellations, and continuation prompts.
- Data flow — end-to-end paths from a Python hook POST or a transcript file change to a rendered pixel.
- HTTP API —
POST /api/eventenvelope shape and how to write a new adapter for a non-Claude agent.
Testing
Rust tests live inline in #[cfg(test)] modules next to the code they cover:
state::tests— sticky-label machine, working-time accumulator, error transitions.label_policy::tests— the(label, original_prompt)decision extracted fromapply_set.log_watcher::tests— the transcript parser (infer_state,split_complete), the upgrade-only merge policy, and theflushed_turn_verdictquestion corrections (done → awaitingandawaiting → done).sync::tests— the receive-sideingest(namespacing, dialog seeding, contiguity guard) and the oldest-first chunkedbuild_push_chunk.adapters::claude::tests—classify,derive_chat_id,clean_prompt,last_assistant_text,is_a_question, and the outerdispatch.
CI runs Rust tests on every push and PR (build.yml) and again before bundling on every tag push (release.yml), so a broken state machine can’t ship a release.