# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project

Lumotia — a local-first, cognitive-load-aware dictation + task-capture desktop app. Tauri 2 + Svelte 5 frontend, Rust workspace backend. Pre-alpha, daily-dogfooded on Linux/Wayland (KDE). Full product context lives in [README.md](README.md) and [docs/brief/](docs/brief/); design principles are non-negotiable and codified in [docs/whisper-ecosystem/lumotia-context.md](docs/whisper-ecosystem/lumotia-context.md).

## Commands

Dev launch (Vite + Tauri, with supply-chain pre-flight):
```bash
./run.sh                # canonical; also: npm run dev:tauri
npm run dev:frontend    # frontend-only iteration, no Tauri
```

Install: `npm ci --ignore-scripts` (never bare `npm install` — `--ignore-scripts` blocks the npm-worm postinstall vector).

Tests + checks:
```bash
cargo test --workspace                          # all Rust tests (220+ lib, 67 Tauri-app)
cargo test -p lumotia-transcription             # single crate
cargo test -p lumotia-llm <test_name>           # single test (substring match)
cargo check --workspace --all-targets           # what CI runs
cargo fmt --check                               # release gate
cargo clippy --workspace --all-targets -- -D warnings  # release gate
npm run check                                   # svelte-check, jsconfig-driven
npm run test                                    # vitest (jsdom; *.test.ts beside source)
npm run test -- src/lib/foo.test.ts             # single vitest file
```

Release build: `npm run tauri build`. CI builds installers on tag push.

Rebrand-migration end-to-end probe (Linux only — Tauri 2 ignores `HOME` overrides on macOS):
```bash
cargo build -p lumotia
scripts/dogfood-rebrand-drill.sh                # sandbox mode
scripts/dogfood-rebrand-drill.sh --against-real-home   # refuses if lumotia data exists
```

## Architecture (the parts to internalise before editing)

Three layers, strict dependency direction: **Svelte (UI) → Tauri commands (OS bridge) → Rust crates (brain)**. The MCP server (`lumotia-mcp`) is a separate read-only binary that opens the same SQLite store — Lumotia-as-primitive for external agents.

Rust workspace (`crates/*` + `src-tauri/`) holds all logic. Tauri command modules in [src-tauri/src/commands/](src-tauri/src/commands/) are thin adapters and should not contain business logic. The 22 command modules map roughly 1:1 to a subsystem (audio, llm, transcription, profiles, tasks, hotkey, paste, windows, …). The utility-only modules in the same directory (`mod`, `power`, `security`) carry no `#[tauri::command]` attribute — don't add them to the invoke handler.

Engine abstractions: `LocalEngine` in `lumotia-transcription` wraps both Whisper (`whisper-rs`) and Parakeet (`transcribe-rs` ONNX) behind a common `Transcriber` trait. LLM surfaces (`cleanup_text`, `decompose_task`, `extract_tasks`) in `lumotia-llm` use GBNF grammars to guarantee parseable JSON. Prompt-injection-hardened cleanup prompt lives in `lumotia-ai-formatting::llm_client::CLEANUP_PROMPT`.

Storage is SQLite via `sqlx` 0.8 in `lumotia-storage`, with FTS5 for transcript search. The MCP binary opens this store read-only.

### Frontend state model

Svelte 5 runes (`$state`, `$derived`, `$effect`) — no Svelte 3/4 store API. State lives in [src/lib/stores/](src/lib/stores/), one file per store. The central store is [page.svelte.ts](src/lib/stores/page.svelte.ts) — transcripts, profiles, taskLists, templates, etc. are fields on it. Secondary windows (`/float`, `/viewer`, `/preview`) use named layouts (`+layout@.svelte`) to skip the main shell and run chrome-free.

### Wiring contracts (enforced socially, not by the compiler)

- **Every new Tauri command** must be (a) implemented in `src-tauri/src/commands/<module>.rs`, (b) registered in the invoke handler in [src-tauri/src/lib.rs](src-tauri/src/lib.rs), and (c) called from the frontend. Forgetting (b) is the most common breakage.
- **Every Settings-visible setting** needs a type field in [src/lib/types/app.ts](src/lib/types/app.ts) and a default in [src/lib/stores/page.svelte.ts](src/lib/stores/page.svelte.ts).
- **Every new workspace crate** needs a `description` in its `Cargo.toml`.
- Smoke test per new command or crate module; workspace floor is "no regressions on main."

## Platform notes that affect code

- **Wayland is a first-class target.** Don't assume X11. The preview overlay uses `WindowTypeHint::Utility`, never steals focus, is pinned across virtual desktops, and is hidden from Alt+Tab. The paste matrix (`wtype` / `xdotool` / `ydotool` on Linux, AppleScript on macOS, SendKeys on Windows) handles the focus-race against the overlay — see [src-tauri/src/commands/paste.rs](src-tauri/src/commands/paste.rs).
- **Linux hotkey is evdev**, not `tauri-plugin-global-shortcut`. Implemented in `lumotia-hotkey`. Requires the user to be in the `input` group; the crate surfaces this as a clear error when `/dev/input/event*` is inaccessible.
- **`run.sh` owns Linux rendering env vars** (`LIBCLANG_PATH`, `WEBKIT_DISABLE_DMABUF_RENDERER`, `GDK_BACKEND=x11` on Wayland to dodge a webkit2gtk issue). Anything that pre-checks these in `lib.rs` is checking what the launcher set, not the user's shell.
- **CI runs on all three OSes** (`.github/workflows/`) — Linux/macOS/Windows. macOS and Windows are CI-validated but not runtime-tested; see [KNOWN-ISSUES.md](KNOWN-ISSUES.md) for tracked gaps (App Nap on Apple Silicon, idle-inhibit on X11/Linux, sleep prevention on Windows).

## Privacy invariants (non-negotiable)

No voice, transcript, or task data leaves the machine unless the user explicitly sends it. No telemetry, no analytics, no crash-reporting service. Cleanup is **additive** — raw Whisper transcript must always be recoverable. LLM scope is narrow: transcription cleanup + task extraction only. Not a wake-word agent, not a chat UI, not a multi-provider cloud fan-out. If a change risks any of this, surface it before implementing.

## v0.1 release scope (active gate)

The v0.1 ship gate lives in [docs/release/v0.1-checklist.md](docs/release/v0.1-checklist.md); the UI hardening boundary is pinned in [docs/release/v0.1-ui-hardening.md](docs/release/v0.1-ui-hardening.md). All release docs live under [docs/release/](docs/release/) — use that folder as the source of truth. Before adding a feature, check the **Out of scope for v0.1** list — reopening a v0.2-flagged item moves the ship date. Garden Inbox, full Settings 7-group regroup, OpenAI Whisper API BYOK, OEM verification, Obsidian plugin, and the Phase B filter-chain refactor are all explicitly v0.2+.

The release acceptance test is the 10-step tester flow (install → onboarding → record → cleanup → task → MicroSteps + timer → history search). Warm activation target: first real recording within 3 minutes of opening the app. UI hardening is a *hardening* pass, not a redesign — every item must be testable, not aesthetic.

## Where to look first

- Latest session context: [HANDOVER.md](HANDOVER.md), then dated handovers in [docs/handovers/](docs/handovers/).
- Tracked limitations + per-step failure-mode catalogue: [KNOWN-ISSUES.md](KNOWN-ISSUES.md).
- Per-platform dependency reference: [docs/dev-setup.md](docs/dev-setup.md).
- Product/strategy framing: [docs/brief/](docs/brief/) — especially `what-lumotia-is.md`, `design-principles.md`, `target-audience.md`.
- Active workstreams + 31-item research backlog: [docs/whisper-ecosystem/brief.md](docs/whisper-ecosystem/brief.md), `workstream-A.md`, `workstream-B.md`.
- GPU tuning roadmap: [docs/gpu-tuning/plan.md](docs/gpu-tuning/plan.md).
