Architecture overview
ClaudeScope is a Tauri 2 app — Rust backend, web front end, single binary output. The split:
┌─────────────────────────────────────────────────────┐
│ Vanilla TS + Vite (front end) │
│ src/main.ts state + IPC + keybindings │
│ src/ui.ts DOM rendering, diff modal, … │
│ src/lint.ts shape-level rule-string lint │
│ src/types.ts shared types mirrored from Rust │
└────────────────────┬────────────────────────────────┘
│ Tauri IPC (#[tauri::command])
┌────────────────────┴────────────────────────────────┐
│ Rust backend │
│ src-tauri/src/ │
│ main.rs Tauri entry │
│ lib.rs Builder + managed state + IPC reg. │
│ scope.rs Scope discovery (walk-up, paths) │
│ projects.rs ~/.claude/projects/ enumeration │
│ io_atomic.rs Atomic read/write + .bak tracker │
│ model.rs SettingsDoc, perm add/remove │
│ commands.rs #[tauri::command] handlers │
│ watcher.rs notify-based file watcher │
│ audit.rs JSONL audit log + rotation │
│ preferences.rs Per-user config (theme, LRU, …) │
│ app_info.rs Build/runtime diagnostics │
│ runtime.rs --home / --project override layer │
└──────────────────────────────────────────────────────┘
A second binary claude-scope-cli (src-tauri/src/bin/cli.rs) re-uses
the same library — same scope discovery, same atomic-write machinery,
same audit log. Anything the GUI can do via IPC, the CLI eventually
exposes as a subcommand. See CLI for the
current surface.
Boundaries
- Front end never writes files directly. Every file-touching operation goes through a Tauri command. That keeps the atomic-write invariants concentrated on the Rust side where they can't be reordered by a re-render.
- Wire types live in one place per direction. Rust structs in
commands.rsserialize to JSON;src/types.tsmirrors the same shapes by hand. A schema generator would be over-engineering for the surface area today. - The audit log is the source of truth for history; the filesystem is the source of truth for state. They can diverge — if a user hand-edits a settings file between sessions, the log is stale, and restore (when it lands) re-reads the current file before computing any delta.
Scope discovery
scope::resolve_with_home() walks up from a starting directory
looking for a .git entry (preferred), or a .claude/ directory, to
identify the project root. From there it derives:
project_dir/.claude/settings.local.json→ Localproject_dir/.claude/settings.json→ Project<home>/.claude/settings.local.json→ User-Local<home>/.claude/settings.json→ User
The <home> argument defaults to dirs::home_dir() but is
overridable for sandbox runs (--home, CLAUDE_SCOPE_HOME). See
Moving rules → Sandbox mode.
Stack
- Tauri 2 — Rust backend + webview.
- Vanilla TypeScript + Vite — front end (intentionally tiny, no framework).
serde_jsonwithpreserve_order— key order survives round-trips.notify-debouncer-mini— cross-platform file watching with built-in debouncing.ulid— chronologically-sortable IDs for audit-log records.- Biome 2 — formatter + linter + import sorter for the front end.