Architecture Overview
Magia is a native desktop application built on Tauri 2.x. The user interface runs as a React webview hosted inside a native window. All system-level work — spawning agent processes, managing terminals, querying the filesystem, and persisting data — happens in a Rust backend process.
Layers
Section titled “Layers”┌─────────────────────────────────────────────────────────┐│ React Webview (UI) ││ React 19 · Zustand v5 · Vite 8 · shadcn/ui · Tailwind ││ CodeMirror 6 · xterm.js · TypeScript │├─────────────────────────────────────────────────────────┤│ Tauri IPC Bridge ││ invoke() commands ↕ event bus (emit / listen) │├─────────────────────────────────────────────────────────┤│ Rust Backend ││ tokio async runtime · rusqlite · portable-pty ││ whisper-rs / ort · tonic (gRPC) · reqwest │├─────────────────────────────────────────────────────────┤│ Operating System ││ Unix sockets · filesystem · PTY · native notifications │└─────────────────────────────────────────────────────────┘React webview
Section titled “React webview”The entire UI is a single-page React application served from a Vite dev server in development and bundled as static assets in production. There is no HTTP server — Tauri serves the assets directly from disk. The webview has no direct access to the filesystem or native APIs; everything goes through the IPC bridge.
Key frontend libraries:
| Library | Role |
|---|---|
| React 19 | Component model and rendering |
| Zustand v5 | Client-side state (roughly 40+ store slices) |
| Vite 8 | Build tool and dev server |
| shadcn/ui + Tailwind v4 | Component primitives and utility CSS |
| CodeMirror 6 | In-app code editor |
| xterm.js | Embedded terminal emulator |
| sonner | Toast notifications |
Tauri IPC bridge
Section titled “Tauri IPC bridge”The bridge has two communication paths:
invoke()— synchronous request/response commands registered with#[tauri::command]. Used for queries and mutations that need a result.- Events (
emit/listen) — fire-and-forget messages. Used for real-time updates pushed from the backend (new hook events, session state changes, live session updates, OTel metrics, etc.).
Rust backend
Section titled “Rust backend”The backend is a single long-running Rust process. It owns all persistent state and all connections to external processes. Key responsibilities:
- Spawn and manage agent CLI processes (Claude Code, Gemini CLI, Codex) via PTY or plain processes
- Listen on Unix sockets for hook events from agent processes
- Maintain a SQLite database (WAL mode, refinery migrations) for session metadata
- Run a WebSocket server for IDE integration
- Receive OpenTelemetry spans from agent processes via a gRPC Unix socket server
- Scan the filesystem for sessions and projects (discovery + session cache)
- Generate session titles and summaries via background LLM calls (observer + reflector)
Key Rust crates:
| Crate | Role |
|---|---|
tauri 2.x | Native window, webview, IPC |
tokio | Async runtime (process, io-util, sync, time, net) |
rusqlite (bundled) | SQLite persistence |
refinery | Schema migrations embedded at compile time |
portable-pty | Cross-platform PTY for terminal sessions |
whisper-rs + ort | Speech-to-text (Whisper model via ONNX Runtime) |
tonic + prost | gRPC for the OTel collector |
interprocess | Cross-platform local sockets (Unix / named pipes) |
reqwest | HTTP client (provider API calls, updates) |
notify | Filesystem watchers |
Startup sequence
Section titled “Startup sequence”- Tauri initialises the native window and loads the webview.
- The Rust
setup()hook runs: paths are initialised, the SQLite database is opened and migrated, shared state is registered withapp.manage(). - The React app mounts and checks
is_first_launch().- New user: onboarding flow is shown.
- Returning user: the app calls
start_background_sync().
start_background_sync()(idempotent) starts all background services in order: hook listener → CLI listener → permission listener → OTel collector → file watchers → session cache sync → IDE server → observer → reflector → Claude status polling.- Discovery is initialised in the frontend (deferred to avoid competing with critical first-render IPC calls).
Data directories
Section titled “Data directories”| Purpose | macOS path |
|---|---|
| App data (settings, DB) | ~/Library/Application Support/sh.magia.app/ |
| Caches | ~/Library/Caches/sh.magia.app/ |
| Dev worktree data | ~/Library/Application Support/sh.magia.dev/ |
| Unix sockets | $TMPDIR/magia-{uid}/ |
Key design decisions
Section titled “Key design decisions”No React Router. Navigation state lives in the useAppStore Zustand slice as a Route discriminated union. This avoids URL-based routing complexities in a native window context and makes deep-linking via IPC events straightforward.
Two ID spaces. Sessions have two identifiers: a workspace UUID (Magia-internal) and a provider session ID (assigned by the agent CLI). The Rust layer bridges these through the claudeSessionId field on each session record. See Data Flow for details.
Background services are idempotent. start_background_sync() is protected by a compare_exchange atomic flag so hot-reloads and double-renders during development never spawn duplicate listeners.
Telemetry is opt-in and build-driven. Sentry and PostHog are only active when MAGIA_API_URL / SENTRY_DSN / POSTHOG_KEY are set at compile time. Self-built binaries have zero telemetry.