---
name: Lumotia architecture map
type: architecture-map-root-index
last_verified: 2026/05/09
---

# Lumotia architecture map

> **Where you are:** Architecture map root.
> **Reading order if this is your first time:** read this page top to bottom, pick the slice that matches what you are trying to do, follow the breadcrumb trail.

## What Lumotia is in 60 seconds

Lumotia is a local-first dictation desktop app. You press a hotkey, you speak, your speech becomes formatted text on disk and on the clipboard. Everything that touches your voice or your transcript runs on your own machine. Zero telemetry, zero analytics, no calls home unless you turn on auto-update.

It is a Tauri 2 app. The shell is Svelte 5. The brain is a Rust workspace of nine crates. Speech-to-text runs through Whisper (whisper-rs) by default with Parakeet (transcribe-rs ONNX) as an alternative. Optional cleanup runs through a local llama-cpp-2 LLM (Qwen3.5 / 3.6, four-tier model picker). Transcripts live in a SQLite database with a FTS5 full-text index. A standalone `lumotia-mcp` binary opens that database read-only and serves four tools over the Model Context Protocol so external agents can read your history without write access.

Targets: Linux (primary, Wayland-friendly), macOS, Windows. Android is on the roadmap (a few crate features carry `android` cfgs).

> **Brand rename in flight.** Lumotia was approved as "Lumenote" on 2026/05/08. The codebase still says "lumotia" and "Lumotia" everywhere. The rename is a separate session.

## The three layers

```
┌─────────────────────────────────────────────────────────────────────┐
│ Frontend  (slice 01)                                                │
│ Svelte 5 + SvelteKit. 4 windows, 7 pages, 25 components, 10 stores. │
│ ~17 200 LOC.                                                        │
├─────────────────────────────────────────────────────────────────────┤
│ Tauri runtime  (slice 02)                                           │
│ src-tauri/. 91 commands across 22 modules + 3 utility modules,      │
│ system tray, ACL capabilities, plugins, panic + log + CSP guards.   │
├─────────────────────────────────────────────────────────────────────┤
│ Rust workspace                                                      │
│ ┌────────────────────┬──────────────────────┬─────────────────────┐ │
│ │ Audio +            │ LLM + Formatting +   │ Core + Storage +    │ │
│ │ Transcription      │ MCP + Cloud          │ Hotkey + Build      │ │
│ │ (slice 03)         │ (slice 04)           │ (slice 05)          │ │
│ │ lumotia-audio     │ lumotia-llm         │ lumotia-core       │ │
│ │ lumotia-          │ lumotia-            │ lumotia-storage    │ │
│ │   transcription    │   ai-formatting      │ lumotia-hotkey     │ │
│ │                    │ lumotia-mcp         │ workspace + CI      │ │
│ │                    │ lumotia-            │                     │ │
│ │                    │   cloud-providers    │                     │ │
│ └────────────────────┴──────────────────────┴─────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
```

The frontend never touches the filesystem, the audio device, the models, or the LLM directly. Everything that crosses the WebView boundary uses `invoke()` (calling a Rust command) or Tauri events (one direction or the other). The Rust workspace is the brain. The Tauri layer is the integration surface.

## Slices

| # | Slice | Owns | Files |
|---|---|---|---|
| 01 | [Frontend](01-frontend/README.md) | Svelte UI, routes, pages, components, stores, i18n, design system, the WebView side of the bridge | 16 |
| 02 | [Tauri runtime](02-tauri-runtime/README.md) | App lifecycle, 91 commands, system tray, ACL, plugins, configs, build.rs | 26 |
| 03 | [Audio + Transcription](03-audio-transcription/README.md) | Microphone capture, resampling, VAD, file decode, Whisper + Parakeet engines, streaming primitives, model manager | 16 |
| 04 | [LLM + Formatting + MCP](04-llm-formatting-mcp/README.md) | Llama-cpp engine, four LLM surfaces (cleanup, decompose, extract tasks, content tags), formatting pipeline, MCP server, cloud-provider stubs | 19 |
| 05 | [Core + Storage + Hotkey + Build](05-core-storage-hotkey-build/README.md) | Shared types, hardware probe, power, thread tuning, model registry, SQLite + FTS5 + 15 migrations, evdev hotkey listener, workspace, CI | 26 |

103 markdown pages in total under this directory.

## Data flow

For an end-to-end trace of a single recording session from microphone to FTS5 search to MCP read, see [data-flow-end-to-end.md](data-flow-end-to-end.md).

## "I want to find X"

| If you want to ... | Start here |
|---|---|
| Understand the dictation UI | [01-frontend/pages/dictation.md](01-frontend/pages/dictation.md) |
| Add a new Tauri command | [02-tauri-runtime/app-lifecycle.md](02-tauri-runtime/app-lifecycle.md) and the relevant module under [02-tauri-runtime/commands/](02-tauri-runtime/commands/README.md) |
| Touch microphone capture or device handling | [03-audio-transcription/audio-capture-pipeline.md](03-audio-transcription/audio-capture-pipeline.md) |
| Change resampler or VAD behaviour | [03-audio-transcription/audio-resampling.md](03-audio-transcription/audio-resampling.md) and [audio-vad.md](03-audio-transcription/audio-vad.md) |
| Wire a new Whisper or Parakeet model | [03-audio-transcription/transcription-model-manager.md](03-audio-transcription/transcription-model-manager.md) and [05-core-storage-hotkey-build/core-model-registry.md](05-core-storage-hotkey-build/core-model-registry.md) |
| Edit a system prompt or GBNF grammar | [04-llm-formatting-mcp/llm-prompts-and-grammars.md](04-llm-formatting-mcp/llm-prompts-and-grammars.md) |
| Tune the formatting pipeline (filler, British English, anti-hallucination) | [04-llm-formatting-mcp/formatting-pipeline.md](04-llm-formatting-mcp/formatting-pipeline.md) |
| Expose a new MCP tool | [04-llm-formatting-mcp/mcp-server.md](04-llm-formatting-mcp/mcp-server.md) and [mcp-tools.md](04-llm-formatting-mcp/mcp-tools.md) |
| Add a database column or migration | [05-core-storage-hotkey-build/storage-schema-and-migrations.md](05-core-storage-hotkey-build/storage-schema-and-migrations.md) |
| Search transcripts (FTS5) | [05-core-storage-hotkey-build/storage-fts5-search.md](05-core-storage-hotkey-build/storage-fts5-search.md) |
| Change hotkey behaviour on Linux | [05-core-storage-hotkey-build/hotkey-linux-evdev.md](05-core-storage-hotkey-build/hotkey-linux-evdev.md) |
| Tune inference threads / power / GPU offload | [05-core-storage-hotkey-build/core-tuning.md](05-core-storage-hotkey-build/core-tuning.md) and [core-power.md](05-core-storage-hotkey-build/core-power.md) |
| Adjust CI gates or release artefacts | [05-core-storage-hotkey-build/ci-pipeline.md](05-core-storage-hotkey-build/ci-pipeline.md) |
| Touch ACL / capabilities | [02-tauri-runtime/capabilities-and-acl.md](02-tauri-runtime/capabilities-and-acl.md) |
| Trace what every `invoke()` call does | [01-frontend/frontend-tauri-bridge.md](01-frontend/frontend-tauri-bridge.md) → match command name to file under [02-tauri-runtime/commands/](02-tauri-runtime/commands/README.md) |
| Understand model recommendation logic | [05-core-storage-hotkey-build/core-recommendation.md](05-core-storage-hotkey-build/core-recommendation.md) |

## Cross-cutting concerns and known debt (aggregated 2026/05/09)

Open items surfaced by the slice maps. Each slice's README has its own debt section with the full detail.

### Release blockers (gating v0.1)

- **RB-08 macOS power assertion (MAJOR, open).** `PowerAssertion::begin` is a non-functional stub awaiting an `objc2` rewrite plus runtime verification on Apple Silicon. Source: [02-tauri-runtime/commands/power-and-security.md](02-tauri-runtime/commands/power-and-security.md). Ref `HANDOVER.md`.
- **`PowerAssertion` is a no-op on Linux and Windows too** (separate from RB-08). Doc comments promise logind inhibitors and `SetThreadExecutionState`; neither implemented. Long Linux dictation can be idled by the compositor.

### Brand rename pending

- The codebase still says "lumotia" and "Lumotia" everywhere. Strings include `"lumotia live dictation session"`, `lumotia_preferences` settings key, `lumotia_settings` localStorage key, prefixed events (`lumotia:hotkey-pressed`, `lumotia:llm-download-progress`, `lumotia:open-wind-down`, `lumotia:preferences-changed`), the bundle identifier `uk.co.corbel.lumotia`, every crate name (`lumotia-core`, `lumotia-audio`, etc), the `lumotia-mcp` binary, the brand asset folders. A rebrand sweep is a single dedicated session.

### Bridge surface drift

- **Slice 02 registers 91 Tauri commands. Slice 01 invokes 60.** The 31 unaccounted commands are likely registered for tests, internal lifecycle, or removed callers. Worth an audit pass.
- **Backend events emitted but not listened to (apparent).** `runtime-warning`, `transcription-result`, `native-pcm`, `lumotia:hotkey-released`. Some may be deprecated, some may be observed only in specific routes. Verify before any cleanup.

### Schema drift vs HANDOVER

- `HANDOVER.md` (2026/04/25) quotes schema head v14. Disk head is v15 (`idx_transcripts_profile_created`). HANDOVER needs a refresh or a session-end note.

### Audio pipeline gaps

- **VAD is a stub** in `lumotia-audio` because both candidate Silero crates pin `ort 2.0.0-rc.10` while transcribe-rs (Parakeet) needs `2.0.0-rc.12`. Live capture leans on the energy-based `RmsVadChunker` in `lumotia-transcription` as a working fallback. Two VADs in two crates with no shared code or thresholds.
- **Streaming primitives** (`RmsVadChunker`, `LocalAgreement`, `trim_buffer_to_commit_point`) are unit-tested but not yet wired into `src-tauri/src/commands/live.rs`. Comments in `streaming/mod.rs`, `buffer_trim.rs`, `commit_policy.rs` flag the integration as a follow-up.

### LLM and formatting

- **No prompt versioning.** No `PROMPT_VERSION` constant. Cached cleanup output will silently drift across prompt revisions.
- **Trivial-true GPU cross-check (commit `052265b`).** `gpu_offloaded` derived from `use_gpu && gpu_layers >= n_layer` is provably true today because `gpu_layers = u32::MAX` whenever `use_gpu` is set. Real residency observability needs llama.cpp log parsing.
- **MAX_CONTEXT_TOKENS is a process-wide cap of 8192** regardless of model tier. The 27B tier has a much larger native context that is never advertised.
- **`LUMOTIA_LLM_TEST_MODEL` env-var gate** is the only thing keeping the LLM smoke tests honest. Worth documenting in a contributor note.
- **British English regex misses `-ing` forms** of `-ize` verbs (e.g. `organizing` slips through unconverted).
- **Multi-token phrase repetition not detected** by the anti-hallucination filter. Five repetitions of `"thank you"` does not trigger.

### MCP and security

- **MCP has no auth.** Stdio is the trust boundary. Any future HTTP / SSE transport needs an auth layer before it ships.
- **`lumotia-cloud-providers` is empty scaffolding.** In-memory keystore only. API keys vanish on restart. Documented TODO for `keyring` migration.

### Frontend debt

- **`SettingsPage.svelte` is 2 484 LOC** and Phase 9c handover already deferred the seven-group restructure plus search box.
- **Dual theme system.** `settings.theme` ("Light" / "Dark" / "System") and `preferences.theme` ("light" / "dark" / "system") coexist with a migration `$effect` running every cycle.
- **`@ts-nocheck` on six files** including `+layout.svelte` and four pages.
- **Triple transcript font-size knobs** are in tension and likely drifted.
- **i18n is a stub.** Each locale carries 19 lines.
- **Two font directories** (`src/fonts/` and `static/fonts/`) ship the same five woff2 files.
- **Default SvelteKit demo assets** (`static/svelte.svg`, `tauri.svg`, `vite.svg`) likely unused.
- **Dead `profiles` route.** Rewritten to `settings` at `+page.svelte:13`.

### Tauri runtime debt

- **`commands::live` is 1 737 LOC** in one file. Splitting per concern (runtime, speech gate, dedup, chunker, progressive WAV) is overdue.
- **`build.rs` link arg `--allow-multiple-definition`** masks a real ggml duplication issue between `whisper-rs-sys` and `llama-cpp-sys-2`. Documented as INTERIM.
- **Updater stubs ship disabled.** `install_update` returns "Updates are disabled until release signing is configured." Matching `tauri.conf.json` `plugins.updater` block is absent.
- **Production crash dumps lack stack traces** because `RUST_BACKTRACE` is not set in the packaged binary and the panic hook does not capture a backtrace.
- **`commands::tasks::decompose_and_store` and `extract_tasks_from_transcript_cmd` lack `PowerAssertion::begin`** despite running multi-second LLM inference. `commands::llm::cleanup_transcript_text_cmd` has it; tasks should too.
- **`commands::transcribe_pcm` does not call `ensure_model_loaded`** unlike its file sibling. Asymmetry could panic if invoked before a load step.
- **`tauri-plugin-cloud-providers` crate** is depended-on in `Cargo.toml` but no command file imports it. Reserved for future upload flow.
- **`parallel_mode_available` hard-wired to `false`** in `commands/models.rs:509`. Phase A.4 marker for unblocking.

### Build / CI / dev glue

- **`run.sh` hard-codes `LIBCLANG_PATH=/usr/lib64/llvm21/lib64`.** Fedora 41 default. Breaks on Debian / Ubuntu / macOS without override.
- **`src-tauri/.cargo/config.toml` hard-codes `LIBCLANG_PATH = "C:\\Program Files\\LLVM\\bin"`** for Windows.
- **No code signing.** Apple and Windows signing env blocks are commented out. First-run users hit Gatekeeper / SmartScreen.
- **`tauri-action@v0` is unpinned.** Floats. Pin before v0.1.
- **`pcm-processor.js` hard-codes `16000` and `8000`** which must move in lock-step with `lumotia_core::constants::{WHISPER_SAMPLE_RATE, MIN_CHUNK_SAMPLES}`.
- **`uuid` Cargo feature is `v4` despite the storage `Cargo.toml` comment claiming "v7 random"**. Cosmetic but worth cleaning up.
- **GPU probe is a stub.** `lumotia_core::hardware::probe_gpu()` returns `None`; no NVIDIA / AMD / Intel / Apple discrimination at runtime.
- **Power probe is Linux-only.** macOS and Windows return `PowerState::Unknown`, which callers treat as `OnAc`.

## How this map was generated

Five subagents ran in parallel on 2026/05/09, one per slice. Each read the code under its slice, wrote breadcrumbed markdown with file:line refs, returned an executive summary, a cross-slice dependency list, a public-surface inventory, and a debt list. This master page is the synthesis. The data-flow trace is a separate page that follows the audio bytes from microphone to FTS5 search to MCP read.

## Refresh history

- **2026/05/09** — initial generation. Five slices, 103 pages. Generated against codebase head per `git log` at the time of capture (most recent commit `3c47000 chore(core): add tuning_log_demo example for live heuristic validation`).

## When to refresh

Re-run the five-slice generation when any of these is true:
- A new Rust crate is added or removed from the workspace.
- A new top-level frontend route or window is added.
- The schema head moves by more than one migration.
- A new MCP tool ships.
- A new top-level Tauri plugin is wired.
- Any slice's debt section has been materially knocked down.
- Brand rename lands (a sweep across every page is needed).

The procedure for a refresh is in [`/home/jake/.claude/projects/-home-jake-Documents-CORBEL-Main/memory/`](file:///home/jake/.claude/projects/-home-jake-Documents-CORBEL-Main/memory/) — search for "architecture-map".
