Skip to content

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.

┌─────────────────────────────────────────────────────────┐
│ 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 │
└─────────────────────────────────────────────────────────┘

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:

LibraryRole
React 19Component model and rendering
Zustand v5Client-side state (roughly 40+ store slices)
Vite 8Build tool and dev server
shadcn/ui + Tailwind v4Component primitives and utility CSS
CodeMirror 6In-app code editor
xterm.jsEmbedded terminal emulator
sonnerToast notifications

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.).

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:

CrateRole
tauri 2.xNative window, webview, IPC
tokioAsync runtime (process, io-util, sync, time, net)
rusqlite (bundled)SQLite persistence
refinerySchema migrations embedded at compile time
portable-ptyCross-platform PTY for terminal sessions
whisper-rs + ortSpeech-to-text (Whisper model via ONNX Runtime)
tonic + prostgRPC for the OTel collector
interprocessCross-platform local sockets (Unix / named pipes)
reqwestHTTP client (provider API calls, updates)
notifyFilesystem watchers
  1. Tauri initialises the native window and loads the webview.
  2. The Rust setup() hook runs: paths are initialised, the SQLite database is opened and migrated, shared state is registered with app.manage().
  3. The React app mounts and checks is_first_launch().
    • New user: onboarding flow is shown.
    • Returning user: the app calls start_background_sync().
  4. 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.
  5. Discovery is initialised in the frontend (deferred to avoid competing with critical first-render IPC calls).
PurposemacOS 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}/

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.