agent: lumotia-rebrand — docs, scripts, root config, residuals
Some checks failed
check / cargo check (macos-latest) (push) Has been cancelled
check / cargo check (ubuntu-22.04) (push) Has been cancelled
check / cargo check (windows-latest) (push) Has been cancelled
check / svelte build + lint (push) Has been cancelled

Phase 9 of the rebrand cascade. Sweep covers everything the Phase 8
frontend pass deliberately skipped: docs/, root markdown, scripts,
Cargo.toml descriptions, code comments that survived earlier
word-boundary sed, plus a handful of identifiers caught on the final
verify pass.

transcription-app changes:
- README.md, HANDOVER.md, KNOWN-ISSUES.md, run.sh — magnotia/Magnotia
  -> lumotia/Lumotia.
- docs/ — sweep across all subdirs except docs/handovers/ (preserved
  as immutable audit trail). Includes architecture-map references
  to magnotia_core::*, magnotia_storage::*, etc. now pointing at
  lumotia_*; dev-setup.md tracing output examples (lumotia_startup
  target); brief/ + superpowers/ + issues/ + whisper-ecosystem/ +
  audit/.
- Cargo.toml descriptions on 9 crates (core, audio, cloud-providers,
  hotkey, llm, mcp, plus referenced others).
- crates/core/src/{error,hardware,recommendation,paths}.rs +
  crates/audio/src/wav.rs + crates/llm/src/model_manager.rs +
  crates/cloud-providers/src/keystore.rs + crates/mcp/src/lib.rs —
  doc comments and a model-manager user-agent string.
- Caught on final pass: BroadcastChannel("magnotia_task_sync") -> ...
  ("lumotia_task_sync"); magnotia_locale i18n localStorage key
  renamed + migration shim added; CSS keyframe names
  magnotiaPulse / magnotiaBar / magnotiaFade renamed in the design-
  system kit; magnotia_viewer_item / magnotia_viewer_mode handoff
  keys renamed in HistoryPage + viewer/+page.svelte; src/assets/
  wordmark.svg text.
- src-tauri/src/lib.rs comment cleanup ("magnotia era" was sed'd
  to "lumotia era" earlier — restored).

Preserved (intentional):
- crates/core/src/paths.rs — keeps "magnotia" / "Magnotia" / ".magnotia"
  legacy detection strings in legacy_and_target_paths() so the
  migration shim can still find user data from the magnotia era.
- src/lib/stores/{page,focusTimer}.svelte.ts + src/lib/i18n/index.ts
  — migration call sites reference the legacy magnotia keys
  deliberately.
- docs/handovers/ — historical audit trail.

cargo build --workspace passes. npm run check: 0 errors / 0 warnings
(3958 files). cargo test --workspace: 339 pass / 0 fail.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-13 12:38:03 +01:00
parent 681a9b26dc
commit 26c7307607
213 changed files with 1175 additions and 1170 deletions

View File

@@ -9,13 +9,13 @@ last_verified: 2026/05/09
> **Where you are:** [Architecture map](../README.md) → Core, Storage, Hotkey, Build
**Plain English summary.** This slice is Magnotia's foundations. Three Rust crates the rest of the workspace builds on top of: `magnotia-core` (shared types, hardware probes, model registry, thread tuning), `magnotia-storage` (the SQLite database, FTS5 search, file-system paths), and `magnotia-hotkey` (a Wayland-friendly evdev hotkey listener for Linux). Plus the workspace-level glue that wires the Rust workspace together with the SvelteKit frontend: `Cargo.toml`, the GitHub Actions pipelines, the dev launcher, the static asset folder, and the package configuration files.
**Plain English summary.** This slice is Lumotia's foundations. Three Rust crates the rest of the workspace builds on top of: `lumotia-core` (shared types, hardware probes, model registry, thread tuning), `lumotia-storage` (the SQLite database, FTS5 search, file-system paths), and `lumotia-hotkey` (a Wayland-friendly evdev hotkey listener for Linux). Plus the workspace-level glue that wires the Rust workspace together with the SvelteKit frontend: `Cargo.toml`, the GitHub Actions pipelines, the dev launcher, the static asset folder, and the package configuration files.
If a new engineer wants to know where a public type comes from, where a setting is persisted, or how a release artefact gets built, the answer is somewhere in this slice.
## At a glance
- **Crates:** three. `magnotia-core` (1,805 LOC), `magnotia-storage` (3,771 LOC), `magnotia-hotkey` (632 LOC). Total ~6,200 LOC.
- **Crates:** three. `lumotia-core` (1,805 LOC), `lumotia-storage` (3,771 LOC), `lumotia-hotkey` (632 LOC). Total ~6,200 LOC.
- **Workspace glue:** `Cargo.toml` (161 bytes), `package.json`, `vite.config.js`, `svelte.config.js`, `jsconfig.json`, `run.sh`, `static/`, `.gitignore`.
- **CI:** three workflows — `check.yml` (per-push compile + lint + libs tests + frontend), `build.yml` (release-bundle build for tags + manual dispatch), `audit.yml` (weekly Mondays cargo-audit + npm-audit).
- **Database head:** schema version **15** (commit on disk, supersedes the v14 figure quoted in `HANDOVER.md` 2026/04/25).
@@ -25,7 +25,7 @@ If a new engineer wants to know where a public type comes from, where a setting
## Map of this slice
`magnotia-core`:
`lumotia-core`:
- [Public types and enums (Segment, Transcript, Megabytes, ModelId, EngineName)](core-types-and-enums.md)
- [Constants module (sample rate, VAD, RAM thresholds, chunk timing)](core-constants.md)
@@ -38,7 +38,7 @@ If a new engineer wants to know where a public type comes from, where a setting
- [Process-watch (meeting detection by process name)](core-process-watch.md)
- [App paths (database, recordings, models, logs)](core-paths.md)
`magnotia-storage`:
`lumotia-storage`:
- [Storage overview (sqlx config, init flow, default features rationale)](storage-overview.md)
- [Schema and migrations (v1-v15 catalogue)](storage-schema-and-migrations.md)
@@ -49,7 +49,7 @@ If a new engineer wants to know where a public type comes from, where a setting
- [Settings, error log, feedback, implementation rules](storage-crud-settings-and-misc.md)
- [File storage paths (database, recordings, crashes, logs)](storage-file-paths.md)
`magnotia-hotkey`:
`lumotia-hotkey`:
- [Linux evdev listener (devices, hotplug, modifiers, Pressed/Released)](hotkey-linux-evdev.md)
@@ -64,11 +64,11 @@ Workspace and build glue:
## How this slice connects to others
- **`magnotia-core` is the universal lower bound.** Every other crate in the workspace depends on it. The shape contract is: `Segment`, `Transcript`, `ModelId`, `EngineName`, `Megabytes`, `AudioSamples`, `MagnotiaError`, `Result`, plus `paths::AppPaths`. See [`core-types-and-enums.md`](core-types-and-enums.md).
- **Slice 1 (frontend)** never imports any of these crates directly. It reaches them through Tauri commands (slice 2). The single setting key the frontend cares about is `magnotia_preferences` (a JSON blob), persisted via `magnotia_storage::get_setting` / `set_setting` (slice 5) called from the Tauri preferences command (slice 2).
- **Slice 2 (Tauri runtime)** is the heaviest consumer. It calls `magnotia_storage::init` at startup, registers the SQLite pool as Tauri-managed state, and every command in `src-tauri/src/commands/*.rs` reaches into `magnotia_storage` for persistence and `magnotia_core::paths` for filesystem locations. The hotkey command at `src-tauri/src/commands/hotkey.rs` is the sole consumer of `magnotia_hotkey::EvdevHotkeyListener`.
- **Slice 3 (audio + transcription)** consumes `magnotia_core::types::{AudioSamples, Segment, Transcript}` and `magnotia_core::constants::{WHISPER_SAMPLE_RATE, WHISPER_CHANNELS, PARAKEET_*, CHUNK_INTERVAL_MS}`. The transcription engines also consume `magnotia_core::tuning::inference_thread_count(Workload::Whisper, gpu_offloaded)` for thread sizing.
- **Slice 4 (LLM + formatting + MCP)** consumes `magnotia_core::tuning::{inference_thread_count, Workload::Llm}`, `magnotia_core::paths::AppPaths::llm_models_dir()`, `magnotia_core::types::Segment` (formatting), and `magnotia_core::constants::SMART_PARAGRAPH_GAP_SECS` (formatting). The MCP server is the only consumer of `magnotia_storage::init_readonly` — it opens the DB read-only so no MCP tool can mutate user data even if the dispatcher misroutes a request.
- **`lumotia-core` is the universal lower bound.** Every other crate in the workspace depends on it. The shape contract is: `Segment`, `Transcript`, `ModelId`, `EngineName`, `Megabytes`, `AudioSamples`, `MagnotiaError`, `Result`, plus `paths::AppPaths`. See [`core-types-and-enums.md`](core-types-and-enums.md).
- **Slice 1 (frontend)** never imports any of these crates directly. It reaches them through Tauri commands (slice 2). The single setting key the frontend cares about is `lumotia_preferences` (a JSON blob), persisted via `lumotia_storage::get_setting` / `set_setting` (slice 5) called from the Tauri preferences command (slice 2).
- **Slice 2 (Tauri runtime)** is the heaviest consumer. It calls `lumotia_storage::init` at startup, registers the SQLite pool as Tauri-managed state, and every command in `src-tauri/src/commands/*.rs` reaches into `lumotia_storage` for persistence and `lumotia_core::paths` for filesystem locations. The hotkey command at `src-tauri/src/commands/hotkey.rs` is the sole consumer of `lumotia_hotkey::EvdevHotkeyListener`.
- **Slice 3 (audio + transcription)** consumes `lumotia_core::types::{AudioSamples, Segment, Transcript}` and `lumotia_core::constants::{WHISPER_SAMPLE_RATE, WHISPER_CHANNELS, PARAKEET_*, CHUNK_INTERVAL_MS}`. The transcription engines also consume `lumotia_core::tuning::inference_thread_count(Workload::Whisper, gpu_offloaded)` for thread sizing.
- **Slice 4 (LLM + formatting + MCP)** consumes `lumotia_core::tuning::{inference_thread_count, Workload::Llm}`, `lumotia_core::paths::AppPaths::llm_models_dir()`, `lumotia_core::types::Segment` (formatting), and `lumotia_core::constants::SMART_PARAGRAPH_GAP_SECS` (formatting). The MCP server is the only consumer of `lumotia_storage::init_readonly` — it opens the DB read-only so no MCP tool can mutate user data even if the dispatcher misroutes a request.
## Existing in-repo docs
@@ -76,9 +76,9 @@ These are the critical-issue write-ups from the 2026-04-22 review that overlap w
- [`docs/issues/c3-migrations-atomicity.md`](../../issues/c3-migrations-atomicity.md) — drove the v9-onwards transactional migration design. See [`storage-schema-and-migrations.md`](storage-schema-and-migrations.md).
- [`docs/issues/c4-transcript-profile-fk.md`](../../issues/c4-transcript-profile-fk.md) — drove migration v9, the table rebuild that landed `transcripts.profile_id` with a real foreign key. See [`storage-schema-and-migrations.md`](storage-schema-and-migrations.md).
- [`docs/issues/keystore-thread-safety.md`](../../issues/keystore-thread-safety.md) — keystore lives in `crates/cloud-providers/` (slice 4) but the lesson generalises to every shared mutable state in `magnotia-core`. Cross-referenced from the tuning page.
- [`docs/issues/keystore-thread-safety.md`](../../issues/keystore-thread-safety.md) — keystore lives in `crates/cloud-providers/` (slice 4) but the lesson generalises to every shared mutable state in `lumotia-core`. Cross-referenced from the tuning page.
- [`docs/issues/hotkey-linux-device-filter.md`](../../issues/hotkey-linux-device-filter.md) — the RB-12 hard-coded `KEY_A` / `KEY_R` filter that the current `device_supports_combo` replaced. See [`hotkey-linux-evdev.md`](hotkey-linux-evdev.md).
- [`docs/issues/power-assertion-macos-objc2.md`](../../issues/power-assertion-macos-objc2.md) — RB-08, the only open MAJOR per `HANDOVER.md`. The implementation lives in slice 2 (`src-tauri`) but the API design lives near `magnotia-core` conceptually. Linked from the slice debt section.
- [`docs/issues/power-assertion-macos-objc2.md`](../../issues/power-assertion-macos-objc2.md) — RB-08, the only open MAJOR per `HANDOVER.md`. The implementation lives in slice 2 (`src-tauri`) but the API design lives near `lumotia-core` conceptually. Linked from the slice debt section.
- [`docs/audit/phase0-cartography.md`](../../audit/phase0-cartography.md) — phase-0 codebase cartography, predates this map but covers crate-graph topology in summary form.
- [`docs/code-review-2026-04-22.md`](../../code-review-2026-04-22.md) — full audit. Slice 5 work items: RB-08 (macOS power assertion), RB-12 (hotkey filter, fixed), C3 (migrations atomicity, fixed), C4 (transcript profile FK, fixed). Several MAJORs across the slice 2 / 3 / 4 surface area too — see the document.
@@ -88,7 +88,7 @@ These are the critical-issue write-ups from the 2026-04-22 review that overlap w
- **Schema head drift in human-facing docs.** `HANDOVER.md` (2026/04/25) says v14; the migration registry on disk is v15 (commit 2026/05/09 added `idx_transcripts_profile_created`). Worth a single sweep of human-facing handovers when the next session opens.
- **Hotkey is Linux-only at runtime.** macOS and Windows fall back to Tauri's global-shortcut plugin (slice 2). The crate compiles cleanly on all three OSes (the stub module is a no-op), but feature parity across platforms is provided by two different mechanisms. A single user-visible inconsistency: per-device hotplug. The Tauri plugin doesn't have it.
- **Power probe is Linux-only.** macOS / Windows return `PowerState::Unknown`, which callers treat as `OnAc`. Native probes (`IOPSGetProvidingPowerSourceType`, `GetSystemPowerStatus`) are noted-deferred in [`core-power.md`](core-power.md).
- **GPU probe is a stub.** `magnotia_core::hardware::probe_gpu()` returns `None`. CPU probe is wired (sysinfo + CPUID), Vulkan loader is probed via `libloading`, but the actual GPU vendor / VRAM are never populated. Recommendation scoring still works because it inspects `Some(gpu).acceleration` and gracefully scores zero-bonus when `None`. See [`core-hardware-probe.md`](core-hardware-probe.md).
- **GPU probe is a stub.** `lumotia_core::hardware::probe_gpu()` returns `None`. CPU probe is wired (sysinfo + CPUID), Vulkan loader is probed via `libloading`, but the actual GPU vendor / VRAM are never populated. Recommendation scoring still works because it inspects `Some(gpu).acceleration` and gracefully scores zero-bonus when `None`. See [`core-hardware-probe.md`](core-hardware-probe.md).
- **`migration_v15` test exists but no migration v15 reverse path.** Migrations are append-only; the registry's contract is forward-only and idempotent. Reverting requires a fresh DB or a `DROP INDEX` migration v16. The contract is documented in [`storage-schema-and-migrations.md`](storage-schema-and-migrations.md).
- **Default profile is enforced by SQL triggers, not by code.** `00000000-0000-0000-0000-000000000001` cannot be deleted or renamed, but if a future migration drops or alters the trigger names (`trg_protect_default_profile_delete`, `trg_protect_default_profile_rename`), the protection silently disappears. No regression test asserts the triggers still exist on the head schema; today the integration tests only assert the protection at DML time.
- **Code-signing not configured.** Both macOS (Apple Developer ID) and Windows (code-signing certificate) are commented out in `build.yml`. Users hit Gatekeeper / SmartScreen on first run. Documented in [`ci-pipeline.md`](ci-pipeline.md).

View File

@@ -43,7 +43,7 @@ Steps per OS:
1. **Install system deps** — libwebkit2gtk-4.1, libappindicator3, librsvg2, libasound2, libudev, patchelf, cmake, build-essential, libclang, clang, libvulkan, glslang-tools, spirv-tools (Linux); Homebrew vulkan-headers, vulkan-loader, molten-vk, shaderc, llvm (macOS); choco llvm + vulkan-sdk (Windows). Each install resolves `LIBCLANG_PATH` / `VULKAN_SDK` dynamically so a minor SDK version bump does not hardcode-break the step.
2. `dtolnay/rust-toolchain@stable` with `rustfmt, clippy`.
3. `Swatinem/rust-cache@v2` with `workspaces: .` and shared key `magnotia-${{ matrix.os }}`.
3. `Swatinem/rust-cache@v2` with `workspaces: .` and shared key `lumotia-${{ matrix.os }}`.
4. `cargo check --workspace --all-targets`.
5. `cargo fmt --all -- --check`.
6. `cargo clippy --workspace --all-targets -- -D warnings`.
@@ -104,7 +104,7 @@ include:
1. System deps (same as `check.yml`).
2. Rust toolchain.
3. Cache (`shared-key: magnotia-build-${{ matrix.os }}`, distinct from check.yml's key so the build cache is not invalidated by check runs).
3. Cache (`shared-key: lumotia-build-${{ matrix.os }}`, distinct from check.yml's key so the build cache is not invalidated by check runs).
4. `npm ci`.
5. `tauri-apps/tauri-action@v0` — runs `tauri build`. On tag pushes, attaches artefacts to a draft GitHub Release. Empty `tagName` for `workflow_dispatch` so we get artefacts only.
6. `actions/upload-artifact@v4` — always uploads to the run page (30-day retention) so the workflow_dispatch path produces something downloadable too.

View File

@@ -58,7 +58,7 @@ Used by the live-session loop (slice 3). 3-second chunks at 16 kHz mean 48 000-s
pub const SMART_PARAGRAPH_GAP_SECS: f64 = 2.0;
```
Consumed by `magnotia_ai_formatting::pipeline` (slice 4). When the gap between adjacent `Segment`s exceeds 2 seconds, a paragraph break is inserted. Cross-link: [Slice 4 formatting pipeline](../04-llm-formatting-mcp/README.md).
Consumed by `lumotia_ai_formatting::pipeline` (slice 4). When the gap between adjacent `Segment`s exceeds 2 seconds, a paragraph break is inserted. Cross-link: [Slice 4 formatting pipeline](../04-llm-formatting-mcp/README.md).
### History limits (`crates/core/src/constants.rs:21-22`)

View File

@@ -9,13 +9,13 @@ last_verified: 2026/05/09
> **Where you are:** [Architecture map](../README.md) → [Core, Storage, Hotkey, Build](README.md) → Core error type
**Plain English summary.** A single structured error enum every Magnotia crate uses, plus a `Result<T>` alias. Errors serialise to JSON so the frontend can switch behaviour on the variant rather than parse strings.
**Plain English summary.** A single structured error enum every Lumotia crate uses, plus a `Result<T>` alias. Errors serialise to JSON so the frontend can switch behaviour on the variant rather than parse strings.
## At a glance
- File: `crates/core/src/error.rs` (60 LOC).
- External deps: `thiserror 2`, `serde 1`.
- Re-exported as `magnotia_core::{MagnotiaError, Result}`.
- Re-exported as `lumotia_core::{MagnotiaError, Result}`.
- Consumers: every workspace crate. Tauri commands wrap their internal errors in `MagnotiaError` so the frontend (slice 1) sees a stable JSON shape.
## What's in here
@@ -34,7 +34,7 @@ last_verified: 2026/05/09
| `AudioCaptureFailed(String)` | `audio capture failed: {0}` | cpal / native capture failures (slice 3). |
| `DownloadFailed(String)` | `model download failed: {0}` | Resumable download errors. |
| `FileNotFound(PathBuf)` | `file not found: {<displayed>}` | `PathBuf::display()` interpolated. |
| `Storage { kind, operation, detail }` | `{detail}` | Boundary shape produced by `From<magnotia_storage::Error>`. `kind` is the serialisable discriminator (`StorageKind`), `operation` is the typed operation label, `detail` is the storage crate's own `Display` output (no double prefix). |
| `Storage { kind, operation, detail }` | `{detail}` | Boundary shape produced by `From<lumotia_storage::Error>`. `kind` is the serialisable discriminator (`StorageKind`), `operation` is the typed operation label, `detail` is the storage crate's own `Display` output (no double prefix). |
| `Io(std::io::Error)` | `io error: {0}` | `#[from]` so `?`-conversion from `std::io::Error` is automatic. |
| `Other(String)` | `{0}` | Catch-all bucket. |
@@ -44,17 +44,17 @@ last_verified: 2026/05/09
pub type Result<T> = std::result::Result<T, MagnotiaError>;
```
Every public function in the workspace that can fail returns `magnotia_core::Result<T>` rather than `std::result::Result<T, MagnotiaError>` so the imports stay short.
Every public function in the workspace that can fail returns `lumotia_core::Result<T>` rather than `std::result::Result<T, MagnotiaError>` so the imports stay short.
## Data flow / contract
- All variants are `Serialize`-able. `std::io::Error` does not derive `Serialize`, so the `Io` variant uses a custom `serialize_with` adaptor (`serialize_io_error` at `crates/core/src/error.rs:53`) that emits the error's `Display` string.
- Variants do not carry source-location information. The storage CRUD layer attaches context via the typed `magnotia_storage::Error::Query { operation, source }` shape; the `operation` label survives into `MagnotiaError::Storage.operation` at the boundary.
- Variants do not carry source-location information. The storage CRUD layer attaches context via the typed `lumotia_storage::Error::Query { operation, source }` shape; the `operation` label survives into `MagnotiaError::Storage.operation` at the boundary.
- Tauri serialises the enum verbatim. The frontend can switch on the discriminant by reading the JSON tag (the variant name).
## Watch-outs
- **No `From<sqlx::Error>` impl in core.** The storage crate manually converts every sqlx error to a typed `magnotia_storage::Error::Query` with an operation label, then `From<storage::Error> for MagnotiaError` (defined inside the storage crate to avoid a `core -> storage` dependency cycle) flattens it into `MagnotiaError::Storage { kind, operation, detail }` at the boundary. Adding an automatic `From<sqlx::Error>` would erase the per-site operation label; the explicit map step is intentional.
- **No `From<sqlx::Error>` impl in core.** The storage crate manually converts every sqlx error to a typed `lumotia_storage::Error::Query` with an operation label, then `From<storage::Error> for MagnotiaError` (defined inside the storage crate to avoid a `core -> storage` dependency cycle) flattens it into `MagnotiaError::Storage { kind, operation, detail }` at the boundary. Adding an automatic `From<sqlx::Error>` would erase the per-site operation label; the explicit map step is intentional.
- **No `Source` chain.** `thiserror` would let you wrap source errors in fields with `#[source]` for chained `Display`. Today every wrapped error is flattened to `String` to keep the JSON shape simple.
- **`Other(String)` is a leaky bucket.** New error categories should get their own variant rather than reaching for `Other`. Audit `Other` usage if the error log starts hiding distinct failure modes behind the same string.

View File

@@ -9,7 +9,7 @@ last_verified: 2026/05/09
> **Where you are:** [Architecture map](../README.md) → [Core, Storage, Hotkey, Build](README.md) → Core model registry
**Plain English summary.** The static catalogue of every speech-to-text model Magnotia ships or knows how to download. One Parakeet ONNX model and six Whisper GGML variants, each with pinned Hugging Face revisions and SHA256 digests so a downloaded file can be verified bit-for-bit against the registry. Pure data — the recommendation scoring lives in [`core-recommendation.md`](core-recommendation.md).
**Plain English summary.** The static catalogue of every speech-to-text model Lumotia ships or knows how to download. One Parakeet ONNX model and six Whisper GGML variants, each with pinned Hugging Face revisions and SHA256 digests so a downloaded file can be verified bit-for-bit against the registry. Pure data — the recommendation scoring lives in [`core-recommendation.md`](core-recommendation.md).
## At a glance

View File

@@ -9,14 +9,14 @@ last_verified: 2026/05/09
> **Where you are:** [Architecture map](../README.md) → [Core, Storage, Hotkey, Build](README.md) → Core app paths
**Plain English summary.** Where Magnotia stores its files on disk. One root, derived from the OS conventions (LOCALAPPDATA on Windows, `~/Library/Application Support/Magnotia` on macOS, `$XDG_DATA_HOME/magnotia` on Linux), and named accessor methods for each subdirectory. Every other crate that needs to know "where does the database live?" reaches for `magnotia_core::paths::AppPaths::current()`.
**Plain English summary.** Where Lumotia stores its files on disk. One root, derived from the OS conventions (LOCALAPPDATA on Windows, `~/Library/Application Support/Lumotia` on macOS, `$XDG_DATA_HOME/lumotia` on Linux), and named accessor methods for each subdirectory. Every other crate that needs to know "where does the database live?" reaches for `lumotia_core::paths::AppPaths::current()`.
## At a glance
- File: `crates/core/src/paths.rs` (125 LOC).
- External deps: standard library only.
- Public surface: `AppPaths` (struct), `app_paths`, `app_data_dir`.
- Consumers: `magnotia-storage::file_storage` (re-exports the path helpers), the Tauri startup code (slice 2), the model manager (slices 3 + 4), the diagnostic-report bundler (slice 2).
- Consumers: `lumotia-storage::file_storage` (re-exports the path helpers), the Tauri startup code (slice 2), the model manager (slices 3 + 4), the diagnostic-report bundler (slice 2).
## What's in here
@@ -28,7 +28,7 @@ pub struct AppPaths { app_data_dir: PathBuf }
impl AppPaths {
pub fn current() -> Self; // resolves at construction
pub fn app_data_dir(&self) -> PathBuf;
pub fn database_path(&self) -> PathBuf; // <root>/magnotia.db
pub fn database_path(&self) -> PathBuf; // <root>/lumotia.db
pub fn recordings_dir(&self) -> PathBuf; // <root>/recordings
pub fn crashes_dir(&self) -> PathBuf; // <root>/crashes
pub fn logs_dir(&self) -> PathBuf; // <root>/logs
@@ -49,12 +49,12 @@ The `resolve_app_data_dir` function picks the root by `cfg(target_os = ...)`:
| OS | Root |
|---|---|
| Windows | `%LOCALAPPDATA%/magnotia` |
| macOS | `$HOME/Library/Application Support/Magnotia` |
| Linux | `$HOME/.magnotia` if it already exists (legacy), else `$XDG_DATA_HOME/magnotia` if set, else `$HOME/.local/share/magnotia` |
| other | `$HOME/.magnotia` |
| Windows | `%LOCALAPPDATA%/lumotia` |
| macOS | `$HOME/Library/Application Support/Lumotia` |
| Linux | `$HOME/.lumotia` if it already exists (legacy), else `$XDG_DATA_HOME/lumotia` if set, else `$HOME/.local/share/lumotia` |
| other | `$HOME/.lumotia` |
The Linux legacy-path branch keeps existing users on `~/.magnotia` (early dogfooding default) without forcing a migration when the canonical XDG location is preferred.
The Linux legacy-path branch keeps existing users on `~/.lumotia` (early dogfooding default) without forcing a migration when the canonical XDG location is preferred.
### Sentinel files — `crates/core/src/paths.rs:53`
@@ -77,7 +77,7 @@ The Linux legacy-path branch keeps existing users on `~/.magnotia` (early dogfoo
- **`std::env::var(...).unwrap_or_else(|_| "/tmp".to_string())` is the fallback for missing `HOME`.** Should never trigger in practice; defensive.
- **`AppPaths::current()` reads env vars at every call.** Cheap, but repeated calls are wasteful. Slice 2 caches a `OnceLock<AppPaths>` so the value is resolved once at startup.
- **No Windows fallback for missing `LOCALAPPDATA`.** Falls through to `.` (current working directory). On a misconfigured Windows host this could write the database next to the binary. Not great; the impact is limited to first-run scenarios where the env is broken.
- **Sentinel files are hidden on Unix (`.<name>.sentinel`) but visible on Windows.** Acceptable; sentinels live alongside `magnotia.db` so the user sees both.
- **Sentinel files are hidden on Unix (`.<name>.sentinel`) but visible on Windows.** Acceptable; sentinels live alongside `lumotia.db` so the user sees both.
## See also

View File

@@ -9,7 +9,7 @@ last_verified: 2026/05/09
> **Where you are:** [Architecture map](../README.md) → [Core, Storage, Hotkey, Build](README.md) → Core process-watch
**Plain English summary.** A lightweight "is the user in a meeting right now?" probe. One signal only: poll the running-process list and match user-editable substrings. No mic-activity heuristic, no calendar integration. If the user has opted in, Magnotia surfaces a non-modal toast so they can choose to start recording. The app never starts recording on its own from this signal.
**Plain English summary.** A lightweight "is the user in a meeting right now?" probe. One signal only: poll the running-process list and match user-editable substrings. No mic-activity heuristic, no calendar integration. If the user has opted in, Lumotia surfaces a non-modal toast so they can choose to start recording. The app never starts recording on its own from this signal.
## At a glance
@@ -45,7 +45,7 @@ Pure function. Case-insensitive substring match. Returns the set of patterns tha
- The slice-2 command holds the `ProcessLister` in `tauri::State` behind a `Mutex` and polls every 15 seconds.
- Pattern list comes from user settings (the UI surfaces it as a comma-separated text box).
- Match output drives a non-modal toast in the frontend. Magnotia never starts recording itself — the user decides.
- Match output drives a non-modal toast in the frontend. Lumotia never starts recording itself — the user decides.
## Tests

View File

@@ -9,7 +9,7 @@ last_verified: 2026/05/09
> **Where you are:** [Architecture map](../README.md) → [Core, Storage, Hotkey, Build](README.md) → Core recommendation scoring
**Plain English summary.** Given a `SystemProfile` (RAM, CPU, GPU, OS), score every model in the registry and rank them. The top entry is what Magnotia recommends. No boolean flags or scattered "is recommended" markers — position in the ranked list **is** the recommendation.
**Plain English summary.** Given a `SystemProfile` (RAM, CPU, GPU, OS), score every model in the registry and rank them. The top entry is what Lumotia recommends. No boolean flags or scattered "is recommended" markers — position in the ranked list **is** the recommendation.
## At a glance

View File

@@ -52,7 +52,7 @@ Resolution order:
1. **`MAGNOTIA_INFERENCE_THREADS=N`** — absolute bypass, returns `N` without any clamps.
2. **Base** = `num_cpus::get_physical()`. Falls back to `std::thread::available_parallelism()`, then to `MIN_INFERENCE_THREADS = 2`.
3. **On battery**`base /= 2`. Power state is read from `magnotia_core::power::probe_power_state()`. The 10-second cache there means the call is cheap; see [`core-power.md`](core-power.md).
3. **On battery**`base /= 2`. Power state is read from `lumotia_core::power::probe_power_state()`. The 10-second cache there means the call is cheap; see [`core-power.md`](core-power.md).
4. **GPU offloaded** → clamp to `GPU_FLOOR_<workload>`. Whisper floor is 4, LLM floor is 2.
5. **Final clamp** to `[MIN_INFERENCE_THREADS, MAX_INFERENCE_THREADS]`.

View File

@@ -9,7 +9,7 @@ last_verified: 2026/05/09
> **Where you are:** [Architecture map](../README.md) → [Core, Storage, Hotkey, Build](README.md) → Core types and enums
**Plain English summary.** This is the public type vocabulary every other crate in Magnotia builds on top of. Newtype wrappers (`ModelId`, `EngineName`, `Megabytes`) prevent stringly-typed and unit-mistake bugs at compile time. `Segment` and `Transcript` are the structured shape of a transcription result. Everything is `Serialize` / `Deserialize` so it crosses the Tauri IPC boundary unchanged.
**Plain English summary.** This is the public type vocabulary every other crate in Lumotia builds on top of. Newtype wrappers (`ModelId`, `EngineName`, `Megabytes`) prevent stringly-typed and unit-mistake bugs at compile time. `Segment` and `Transcript` are the structured shape of a transcription result. Everything is `Serialize` / `Deserialize` so it crosses the Tauri IPC boundary unchanged.
## At a glance

View File

@@ -23,7 +23,7 @@ The handover is the source of truth for "what state is this project actually in
## `README.md` (repo root)
- File: `README.md` (~24 KB).
- Purpose: standing project README for a contributor or curious reader. Covers what Magnotia is, why it exists, quick-start instructions, the `magnotia-` crate map, the rebrand history (Magnotia / Lumenote), and pointers into the documentation.
- Purpose: standing project README for a contributor or curious reader. Covers what Lumotia is, why it exists, quick-start instructions, the `lumotia-` crate map, the rebrand history (Lumotia / Lumenote), and pointers into the documentation.
- Audience: external. The architecture map is internal.
The architecture map (this slice) is the implementation-level reference; the README is the introduction.

View File

@@ -21,7 +21,7 @@ last_verified: 2026/05/12
```bash
#!/usr/bin/env bash
# Magnotia dev launcher. Starts Vite first, waits for it to bind port 1420,
# Lumotia dev launcher. Starts Vite first, waits for it to bind port 1420,
# then runs Tauri without triggering a second Vite instance.
# Sets the Linux rendering env vars that warn_if_x11_env_unset_on_wayland
# (src-tauri/src/lib.rs) expects the launcher to own.
@@ -132,7 +132,7 @@ Bindgen (pulled by `whisper-rs-sys` and `llama-cpp-sys-2`) needs a `libclang.so`
- `@tauri-apps/api 2.10.1` — JS bridge to Tauri commands.
- `@tauri-apps/plugin-autostart 2.5.1` — autostart on boot.
- `@tauri-apps/plugin-dialog 2.7.1` — native file dialogs.
- `@tauri-apps/plugin-global-shortcut 2.3.1` — non-Linux hotkey backend (Linux uses our own `magnotia-hotkey` crate, slice 5).
- `@tauri-apps/plugin-global-shortcut 2.3.1` — non-Linux hotkey backend (Linux uses our own `lumotia-hotkey` crate, slice 5).
- `@tauri-apps/plugin-notification 2.3.3` — toast notifications.
- `@tauri-apps/plugin-opener 2.x` — open external URLs.
- `@chenglou/pretext 0.0.5` — stylable text preview.

View File

@@ -71,7 +71,7 @@ export default config;
### Notable settings
- **`adapter-static`** — Magnotia is a Tauri app, not a server-rendered web app. Static adapter outputs a fully pre-rendered HTML/JS bundle that Tauri serves from its embedded webview.
- **`adapter-static`** — Lumotia is a Tauri app, not a server-rendered web app. Static adapter outputs a fully pre-rendered HTML/JS bundle that Tauri serves from its embedded webview.
- **`fallback: "index.html"`** — every unknown route serves `index.html`, which lets the SvelteKit client router take over. Without this, `/history` typed directly into the URL bar would 404.
## `jsconfig.json`

View File

@@ -13,7 +13,7 @@ last_verified: 2026/05/09
## At a glance
- Crate: `magnotia-hotkey`.
- Crate: `lumotia-hotkey`.
- LOC: 632 (`lib.rs` 177, `linux.rs` 426, `stub.rs` 29).
- External deps: `tokio 1` (rt + sync + macros + time), `serde 1`, `log 0.4`, plus Linux-only: `evdev 0.12` (with `tokio` feature), `notify 7` (default features off, `macos_fsevent` only), `nix 0.29` (`fs` feature).
- Public surface: `HotkeyCombo`, `HotkeyEvent`, `EvdevHotkeyListener`, `check_evdev_access`. Plus the parser `HotkeyCombo::from_tauri_str`.

View File

@@ -69,7 +69,7 @@ registerProcessor("pcm-processor", PcmProcessor);
1. Picks up the device-native sample rate (e.g. 48 kHz) from the `sampleRate` global available inside an `AudioWorkletGlobalScope`.
2. Computes a downsampling ratio against 16 kHz. If the device is already 16 kHz (rare), passes samples through.
3. The downsampler is **dropwise**: it accumulates a fractional position and emits a sample whenever the position crosses an integer step. No anti-aliasing filter. Acceptable for speech; subtly hurts on music or fast transients but Magnotia is a speech app.
3. The downsampler is **dropwise**: it accumulates a fractional position and emits a sample whenever the position crosses an integer step. No anti-aliasing filter. Acceptable for speech; subtly hurts on music or fast transients but Lumotia is a speech app.
4. Buffers up to 8 000 samples (≈ 0.5 s at 16 kHz).
5. Posts `{ type: "pcm", samples: [...] }` to the AudioWorklet's port. The main thread bridges this onward to the Rust live-session command.
@@ -81,9 +81,9 @@ If we moved this into `src/`, Vite would bundle it and break the worklet load.
### Cross-link
The 16 kHz target rate is also the value of `magnotia_core::constants::WHISPER_SAMPLE_RATE`. **The worklet hard-codes the literal `16000` rather than importing the Rust constant** because audio worklets cannot import from the bundle. A coordinated change requires editing both places. See [`core-constants.md`](core-constants.md) for the Rust side.
The 16 kHz target rate is also the value of `lumotia_core::constants::WHISPER_SAMPLE_RATE`. **The worklet hard-codes the literal `16000` rather than importing the Rust constant** because audio worklets cannot import from the bundle. A coordinated change requires editing both places. See [`core-constants.md`](core-constants.md) for the Rust side.
The 8 000-sample emit threshold is also the value of `magnotia_core::constants::MIN_CHUNK_SAMPLES`. Same hard-coding issue.
The 8 000-sample emit threshold is also the value of `lumotia_core::constants::MIN_CHUNK_SAMPLES`. Same hard-coding issue.
## Fonts
@@ -103,7 +103,7 @@ Loaded via `@font-face` in `src/app.css` (slice 1). Local-first; no Google Fonts
## Watch-outs
- **`pcm-processor.js` literally hard-codes `16000` and `8000`.** Two magic numbers that must move in lock-step with the Rust constants. If Magnotia ever supports a different speech-model sample rate, this file is one of the touch points. Worth a build-time substitution mechanism (Vite plugin) to inject the constants from a single source.
- **`pcm-processor.js` literally hard-codes `16000` and `8000`.** Two magic numbers that must move in lock-step with the Rust constants. If Lumotia ever supports a different speech-model sample rate, this file is one of the touch points. Worth a build-time substitution mechanism (Vite plugin) to inject the constants from a single source.
- **The downsampler does no anti-aliasing.** A device sampling at 48 kHz has frequency content above 8 kHz that should be filtered out before decimation. The current dropwise downsampler aliases that content into the 0-8 kHz band. Whisper handles the artefacts well in practice but a one-pole lowpass would be a cheap accuracy gain. Tracked verbally; no doc yet.
- **Bypasses Vite optimisation.** Files in `static/` are not minified or fingerprinted. Acceptable for a worklet (load is once per session); cache invalidation is a non-issue because Tauri loads from local disk.
- **Browser permission gate.** AudioWorklets require a `MediaStream` and run inside the audio context. Tauri's webview honours `getUserMedia` so the mic permission UI just works on Linux / Windows / macOS.

View File

@@ -16,7 +16,7 @@ last_verified: 2026/05/09
- Source: `crates/storage/src/database.rs:776-1124`.
- Public surface: `list_profiles`, `get_profile`, `create_profile`, `update_profile`, `delete_profile`, `list_profile_terms`, `add_profile_term`, `delete_profile_term`.
- Public types: `ProfileRow`, `ProfileTermRow`.
- Public constant: `magnotia_storage::DEFAULT_PROFILE_ID = "00000000-0000-0000-0000-000000000001"` at `crates/storage/src/lib.rs:7`.
- Public constant: `lumotia_storage::DEFAULT_PROFILE_ID = "00000000-0000-0000-0000-000000000001"` at `crates/storage/src/lib.rs:7`.
- Consumers: slice 2 profiles command, transcript inserts (every transcript carries a `profile_id`), the LLM prompt builder (slice 4) reads the profile's `initial_prompt` and `profile_terms` to condition cleanup.
## Public types
@@ -56,11 +56,11 @@ Single-row select.
### `create_profile(pool, id, name, initial_prompt) -> Result<ProfileRow>` — `crates/storage/src/database.rs:968`
UUID is generated by the caller. `name` has a `UNIQUE` constraint at the column level (migration v6); a duplicate name returns `magnotia_storage::Error::Query { operation: "create_profile", source }` carrying the sqlx UNIQUE-constraint error.
UUID is generated by the caller. `name` has a `UNIQUE` constraint at the column level (migration v6); a duplicate name returns `lumotia_storage::Error::Query { operation: "create_profile", source }` carrying the sqlx UNIQUE-constraint error.
### `update_profile(pool, id, name, initial_prompt)` — `crates/storage/src/database.rs:995`
Renames the profile and / or rewrites the initial prompt. **Updating the default profile's name** is short-circuited in Rust before hitting sqlite, raising `magnotia_storage::Error::InvalidReference { entity: Entity::Profile, reason: "Default profile cannot be renamed" }`. The `trg_protect_default_profile_rename` trigger (migration v6) is the structural backstop. Updating only `initial_prompt` is allowed.
Renames the profile and / or rewrites the initial prompt. **Updating the default profile's name** is short-circuited in Rust before hitting sqlite, raising `lumotia_storage::Error::InvalidReference { entity: Entity::Profile, reason: "Default profile cannot be renamed" }`. The `trg_protect_default_profile_rename` trigger (migration v6) is the structural backstop. Updating only `initial_prompt` is allowed.
### `delete_profile(pool, id) -> Result<()>` — `crates/storage/src/database.rs:1024`

View File

@@ -26,7 +26,7 @@ pub async fn get_setting(pool: &SqlitePool, key: &str) -> Result<Option<String>>
The `settings` table is keyed `TEXT PRIMARY KEY, value TEXT NOT NULL`. The conventions are:
- **`magnotia_preferences`** — JSON blob. The frontend's full preferences object. Read at boot, written on every preference change. This is the bridge between the frontend's reactive preferences store (slice 1) and persistence (slice 5). See [Slice 1 preferences store](../01-frontend/README.md) and [Slice 2 preferences command](../02-tauri-runtime/README.md).
- **`lumotia_preferences`** — JSON blob. The frontend's full preferences object. Read at boot, written on every preference change. This is the bridge between the frontend's reactive preferences store (slice 1) and persistence (slice 5). See [Slice 1 preferences store](../01-frontend/README.md) and [Slice 2 preferences command](../02-tauri-runtime/README.md).
- **`active_profile_id`** — string. The profile id the user is actively transcribing into.
- Other keys are added ad-hoc per feature; no enumeration in code today.
@@ -67,7 +67,7 @@ pub struct ErrorLogRow {
## Feedback (HITL)
Phase 2 of the feature-complete roadmap. Captures thumbs + corrections on AI-generated output so the prompt builder can inject recent examples as few-shot exemplars. Storage-only here; the prompt-conditioning logic lives in `magnotia-llm` (slice 4).
Phase 2 of the feature-complete roadmap. Captures thumbs + corrections on AI-generated output so the prompt builder can inject recent examples as few-shot exemplars. Storage-only here; the prompt-conditioning logic lives in `lumotia-llm` (slice 4).
### Schema (migration v10)
@@ -173,15 +173,15 @@ pub struct ImplementationRuleRow {
## Watch-outs
- **`magnotia_preferences` is a JSON blob in TEXT.** Schema-on-read. A typo in the frontend store can land in the database and silently round-trip through future loads. A periodic schema-validation pass would catch it.
- **`lumotia_preferences` is a JSON blob in TEXT.** Schema-on-read. A typo in the frontend store can land in the database and silently round-trip through future loads. A periodic schema-validation pass would catch it.
- **`error_log` has no severity column.** Every error is created equal. Worth a future migration if the diagnostic-report bundler needs to highlight critical errors.
- **`feedback.profile_id` defaults to `DEFAULT_PROFILE_ID` at the column level.** Combined with the `record_feedback` fallback, this means feedback can never end up profile-orphaned. Good.
- **`implementation_rules.actions_json` is unschematised TEXT.** The dispatcher logic owns the parsing. A future schema for actions would catch malformed JSON at the storage layer.
- **`set_setting` is unbounded.** A user with a huge `magnotia_preferences` blob (which has happened in dogfooding) drags every read of that key. Today the preferences blob is small (KB-scale).
- **`set_setting` is unbounded.** A user with a huge `lumotia_preferences` blob (which has happened in dogfooding) drags every read of that key. Today the preferences blob is small (KB-scale).
## See also
- [Schema and migrations](storage-schema-and-migrations.md) — v1, v10, v12.
- [Storage overview](storage-overview.md)
- [Slice 4 LLM prompt builder](../04-llm-formatting-mcp/README.md) — caller of `list_feedback_examples`.
- [Slice 2 preferences command](../02-tauri-runtime/README.md) — caller of `set_setting("magnotia_preferences", ...)`.
- [Slice 2 preferences command](../02-tauri-runtime/README.md) — caller of `set_setting("lumotia_preferences", ...)`.

View File

@@ -81,7 +81,7 @@ pub struct TranscriptRow {
### `insert_transcript(pool, &params) -> Result<()>` — `crates/storage/src/database.rs:80`
1. **Pre-flight FK check.** `profile_exists(pool, params.profile_id)` (private, `database.rs:1094`) runs `SELECT 1 FROM profiles WHERE id = ?`. If the profile is not present, returns a typed `magnotia_storage::Error::InvalidReference { entity: Entity::Profile, reason: "unknown profile id '...'" }`. Without this, sqlite would raise `FOREIGN KEY constraint failed` which the frontend cannot easily disambiguate.
1. **Pre-flight FK check.** `profile_exists(pool, params.profile_id)` (private, `database.rs:1094`) runs `SELECT 1 FROM profiles WHERE id = ?`. If the profile is not present, returns a typed `lumotia_storage::Error::InvalidReference { entity: Entity::Profile, reason: "unknown profile id '...'" }`. Without this, sqlite would raise `FOREIGN KEY constraint failed` which the frontend cannot easily disambiguate.
2. Single `INSERT INTO transcripts (...) VALUES (...)` with all 16 fields.
### `get_transcript(pool, id) -> Result<Option<TranscriptRow>>` — `crates/storage/src/database.rs:117`

View File

@@ -9,12 +9,12 @@ last_verified: 2026/05/09
> **Where you are:** [Architecture map](../README.md) → [Core, Storage, Hotkey, Build](README.md) → Storage file paths
**Plain English summary.** Thin re-export layer. `magnotia-storage::file_storage` proxies the `magnotia-core::paths::AppPaths` accessors so callers that already depend on `magnotia-storage` do not need to add an explicit `magnotia-core` import to reach the database path.
**Plain English summary.** Thin re-export layer. `lumotia-storage::file_storage` proxies the `lumotia-core::paths::AppPaths` accessors so callers that already depend on `lumotia-storage` do not need to add an explicit `lumotia-core` import to reach the database path.
## At a glance
- File: `crates/storage/src/file_storage.rs` (28 LOC).
- External deps: standard library only. Forwards into `magnotia_core::paths`.
- External deps: standard library only. Forwards into `lumotia_core::paths`.
- Public surface: `app_data_dir`, `database_path`, `recordings_dir`, `crashes_dir`, `logs_dir`. Re-exported from `crates/storage/src/lib.rs:23`.
- Consumers: slice 2 startup; the diagnostic-report bundler; the live-session recorder writing audio to `recordings_dir`.
@@ -24,30 +24,30 @@ Every function is a one-liner forwarding into the core path API:
```rust
pub fn app_data_dir() -> PathBuf {
magnotia_core::paths::app_paths().app_data_dir()
lumotia_core::paths::app_paths().app_data_dir()
}
pub fn database_path() -> PathBuf {
magnotia_core::paths::app_paths().database_path()
lumotia_core::paths::app_paths().database_path()
}
pub fn recordings_dir() -> PathBuf {
magnotia_core::paths::app_paths().recordings_dir()
lumotia_core::paths::app_paths().recordings_dir()
}
pub fn crashes_dir() -> PathBuf {
magnotia_core::paths::app_paths().crashes_dir()
lumotia_core::paths::app_paths().crashes_dir()
}
pub fn logs_dir() -> PathBuf {
magnotia_core::paths::app_paths().logs_dir()
lumotia_core::paths::app_paths().logs_dir()
}
```
## Notes per directory
- **`database_path`** — `<app_data>/magnotia.db`. Where `magnotia_storage::init` creates / opens the SQLite database. Absent on first run; created with `create_if_missing(true)`.
- **`database_path`** — `<app_data>/lumotia.db`. Where `lumotia_storage::init` creates / opens the SQLite database. Absent on first run; created with `create_if_missing(true)`.
- **`recordings_dir`** — `<app_data>/recordings/`. Audio captures live here. The transcription command (slice 2) writes WAV files; their relative path is stamped onto `transcripts.audio_path`.
- **`crashes_dir`** — `<app_data>/crashes/`. The Rust panic hook writes `<unix-ts>-<short-id>.crash` files here. Read by the diagnostic-report bundler in Settings → About.
- **`logs_dir`** — `<app_data>/logs/`. Rolling log file (`magnotia.log`, rotated `magnotia.log.1`, etc). The `tracing-subscriber` set up in slice 2 startup (`src-tauri/src/lib.rs`) writes here.
- **`logs_dir`** — `<app_data>/logs/`. Rolling log file (`lumotia.log`, rotated `lumotia.log.1`, etc). The `tracing-subscriber` set up in slice 2 startup (`src-tauri/src/lib.rs`) writes here.
The model directories (`models_dir`, `speech_model_dir`, `llm_models_dir`) are **not re-exported here**. Callers reach them directly via `magnotia_core::paths::AppPaths` — those calls live in slices 3 (speech models) and 4 (LLM models).
The model directories (`models_dir`, `speech_model_dir`, `llm_models_dir`) are **not re-exported here**. Callers reach them directly via `lumotia_core::paths::AppPaths` — those calls live in slices 3 (speech models) and 4 (LLM models).
## Watch-outs

View File

@@ -9,13 +9,13 @@ last_verified: 2026/05/09
> **Where you are:** [Architecture map](../README.md) → [Core, Storage, Hotkey, Build](README.md) → Storage overview
**Plain English summary.** The storage crate owns Magnotia's SQLite database, its FTS5 transcript index, the file-system paths it writes to, and the migration machinery that brings a fresh DB up to head. It uses `sqlx 0.8` in a slimmed-down configuration that strips out features the crate does not need.
**Plain English summary.** The storage crate owns Lumotia's SQLite database, its FTS5 transcript index, the file-system paths it writes to, and the migration machinery that brings a fresh DB up to head. It uses `sqlx 0.8` in a slimmed-down configuration that strips out features the crate does not need.
## At a glance
- Crate: `magnotia-storage`.
- Crate: `lumotia-storage`.
- LOC: 3,771 (database 2,534, migrations 1,185, file_storage 28, lib 24).
- External deps: `sqlx 0.8` (`runtime-tokio`, `sqlite`; **no default features**), `tokio 1`, `serde 1`, `log 0.4`, `uuid 1` (v4), `magnotia-core` (path).
- External deps: `sqlx 0.8` (`runtime-tokio`, `sqlite`; **no default features**), `tokio 1`, `serde 1`, `log 0.4`, `uuid 1` (v4), `lumotia-core` (path).
- Public surface: 46 `pub async fn` (every CRUD verb listed in `crates/storage/src/lib.rs`), one `pub fn` (`as_str`), 9 `pub struct`s (param types + row types), 1 `pub enum` (`FeedbackTargetType`), 1 `pub const` (`DEFAULT_PROFILE_ID`), plus the file-storage path re-exports.
- Consumers: every Tauri command that persists or reads (slice 2); the MCP server (slice 4) opens the same database read-only via `init_readonly`.
@@ -82,7 +82,7 @@ Per-table CRUD is split across the per-page docs in this slice. See:
## Watch-outs
- **`PRAGMA foreign_keys = ON` is per-connection, not per-database.** The pool's `max_connections = 5` means we run the pragma once at init on the first connection. SQLite re-applies the pragma on each new pool connection because we set it via the connect options... but actually we don't, we set it after `connect_with`. **This is a latent issue worth verifying:** if a second pool connection opens later, foreign keys may not be enforced on it. Audit candidate.
- **No connection-level retry on locked DB.** `SQLITE_BUSY` propagates as `magnotia_storage::Error::Query { ... }` (flattened to `MagnotiaError::Storage { kind: Query, ... }` at the boundary). With WAL mode + 5 max connections this is rare, but a long-running write under a slow filesystem could trigger it.
- **No connection-level retry on locked DB.** `SQLITE_BUSY` propagates as `lumotia_storage::Error::Query { ... }` (flattened to `MagnotiaError::Storage { kind: Query, ... }` at the boundary). With WAL mode + 5 max connections this is rare, but a long-running write under a slow filesystem could trigger it.
- **Custom migration runner.** sqlx's bundled `migrate!` macro is not used. The custom runner is documented in [`storage-schema-and-migrations.md`](storage-schema-and-migrations.md) and was the subject of the C3 critical-issue write-up at `docs/issues/c3-migrations-atomicity.md`.
## Existing in-repo docs

View File

@@ -14,10 +14,10 @@ last_verified: 2026/05/09
## At a glance
- File: `crates/storage/src/migrations.rs` (1,185 LOC).
- External deps: `sqlx 0.8` (`SqlitePool`), `magnotia_core::error`.
- External deps: `sqlx 0.8` (`SqlitePool`), `lumotia_core::error`.
- Public surface: `run_migrations(pool: &SqlitePool) -> Result<()>`. That is the only `pub` symbol.
- Schema head: **v15**.
- Consumers: called by `magnotia_storage::database::init` at `crates/storage/src/database.rs:54`.
- Consumers: called by `lumotia_storage::database::init` at `crates/storage/src/database.rs:54`.
## Migration runner contract
@@ -195,7 +195,7 @@ UUIDs for `id` are generated as v4 random per `crates/storage/Cargo.toml`. (The
### `settings`
Plain key-value store. `key TEXT PRIMARY KEY, value TEXT NOT NULL`. The frontend's preferences blob lives at key `magnotia_preferences`.
Plain key-value store. `key TEXT PRIMARY KEY, value TEXT NOT NULL`. The frontend's preferences blob lives at key `lumotia_preferences`.
### `error_log`
@@ -223,7 +223,7 @@ Bookkeeping. Created by the migration runner itself.
| 2 | transcripts FTS5 + dictionary table | Creates `transcripts_fts` virtual table, three sync triggers, `dictionary` table (later removed). |
| 3 | micro-stepping: `parent_task_id` on tasks | `ALTER tasks ADD COLUMN parent_task_id` (with FK + cascade); `idx_tasks_parent`. |
| 4 | tasks_meta: notes column | `ALTER tasks ADD COLUMN notes TEXT NOT NULL DEFAULT ''`. |
| 5 | transcripts_meta | Adds `starred`, `manual_tags`, `template`, `language`, `segments_json` columns. Persists what was previously in `localStorage` `magnotia_history`. |
| 5 | transcripts_meta | Adds `starred`, `manual_tags`, `template`, `language`, `segments_json` columns. Persists what was previously in `localStorage` `lumotia_history`. |
| 6 | profiles | Creates `profiles` and `profile_terms` tables; seeds the default profile; copies `dictionary` rows to the default profile's `profile_terms`; installs the two protection triggers. |
| 7 | drop_dictionary | Removes the legacy `dictionary` table and its index. Data has already been copied in v6. |
| 8 | transcript_profile_provenance | Adds `transcripts.profile_id` (nullable initially); `idx_transcripts_profile_id`. |

View File

@@ -36,16 +36,16 @@ strip = "symbols"
The `crates/*` glob picks up every directory under `crates/` that contains a `Cargo.toml`. Members at the time of writing:
- `crates/core/``magnotia-core`
- `crates/storage/``magnotia-storage`
- `crates/hotkey/``magnotia-hotkey`
- `crates/core/``lumotia-core`
- `crates/storage/``lumotia-storage`
- `crates/hotkey/``lumotia-hotkey`
- `crates/audio/` → audio capture (slice 3)
- `crates/transcription/` → transcription engines (slice 3)
- `crates/llm/``magnotia-llm` (slice 4)
- `crates/ai-formatting/``magnotia-ai-formatting` (slice 4)
- `crates/mcp/``magnotia-mcp` (slice 4)
- `crates/cloud-providers/``magnotia-cloud-providers` (slice 4)
- `src-tauri/` → the Tauri binary `magnotia` (slice 2)
- `crates/llm/``lumotia-llm` (slice 4)
- `crates/ai-formatting/``lumotia-ai-formatting` (slice 4)
- `crates/mcp/``lumotia-mcp` (slice 4)
- `crates/cloud-providers/``lumotia-cloud-providers` (slice 4)
- `src-tauri/` → the Tauri binary `lumotia` (slice 2)
## Resolver
@@ -70,7 +70,7 @@ The `panic = "abort"` setting is a deliberate trade-off:
## Watch-outs
- **No `[workspace.dependencies]` block.** Each member crate pins its own dep versions independently. Side effect: `sqlx 0.8` in `magnotia-storage` and `sysinfo 0.35` in `magnotia-core` could drift between members. A future tidy would centralise common pins.
- **No `[workspace.dependencies]` block.** Each member crate pins its own dep versions independently. Side effect: `sqlx 0.8` in `lumotia-storage` and `sysinfo 0.35` in `lumotia-core` could drift between members. A future tidy would centralise common pins.
- **No `[patch]` overrides.** Useful to know — every crate is consumed at its registered version.
- **`codegen-units = 1` makes release builds slow.** ~10 minutes on Jake's Monolith for a clean `cargo tauri build`. Acceptable for release; use `cargo tauri dev` (debug, default) for iteration.
- **No `[profile.dev]` overrides.** Default debug build. `cargo build` produces a binary in `target/debug/`. The workspace target dir is `./target` at the repo root, **not `src-tauri/target`** — this caught the CI cache step (see [`ci-pipeline.md`](ci-pipeline.md)).