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

@@ -1,16 +1,16 @@
# Magnotia — Whisper Ecosystem Research Brief
# Lumotia — Whisper Ecosystem Research Brief
*Spec handover for Claude Code. Mined across 10 open-source Whisper apps, synthesised April 2026. British English. Every claim URL-backed — see Sources.*
## TL;DR
Ten repos surveyed (whisper.cpp, whisper-rs, Handy, Buzz, Whispering/Epicenter, faster-whisper, WhisperLive, whisper_streaming, Scriberr, Vibe, plus OpenWhispr). The dominant pre-emptive patches for Magnotia cluster around **silent CUDA fallback, global-hotkey edge cases, MSVC C-runtime linking between `whisper-rs-sys` and `tokenizers`, clipboard overwrite on paste, and Tauri HTTP scope blocking localhost LLM calls**. The highest-leverage features to pinch are **an engine-abstraction crate (`transcribe-rs` pattern), a named-prompt transformation pipeline with raw-transcript preservation, Vulkan as the default GPU path on Windows, VAD-gated chunking with a hallucination blocklist, and a warm-up audio file at launch**.
Ten repos surveyed (whisper.cpp, whisper-rs, Handy, Buzz, Whispering/Epicenter, faster-whisper, WhisperLive, whisper_streaming, Scriberr, Vibe, plus OpenWhispr). The dominant pre-emptive patches for Lumotia cluster around **silent CUDA fallback, global-hotkey edge cases, MSVC C-runtime linking between `whisper-rs-sys` and `tokenizers`, clipboard overwrite on paste, and Tauri HTTP scope blocking localhost LLM calls**. The highest-leverage features to pinch are **an engine-abstraction crate (`transcribe-rs` pattern), a named-prompt transformation pipeline with raw-transcript preservation, Vulkan as the default GPU path on Windows, VAD-gated chunking with a hallucination blocklist, and a warm-up audio file at launch**.
---
## Cross-repo pain pattern matrix
| Pain point | Repos where observed | Severity for Magnotia | Recommended Magnotia mitigation |
| Pain point | Repos where observed | Severity for Lumotia | Recommended Lumotia mitigation |
|---|---|---|---|
| Silent CPU fallback when CUDA version mismatches | whisper.cpp #2857, #2214, #2297; faster-whisper #1398; Buzz FAQ; Handy #209 | **H** | Detect actual compute device at runtime, log it, show in UI; prefer Vulkan default on Windows |
| Global-hotkey bugs (modifier-only, right-hand mods, F13F24, single-key on Linux, Fn on macOS) | Handy #1143/#1105/#1019/#966/#956/#917/#47; Whispering #491/#500/#484/#549 | **H** | Dedicated cross-platform hotkey layer; per-OS capability matrix; reject invalid combos in UI |
@@ -37,7 +37,7 @@ Ten repos surveyed (whisper.cpp, whisper-rs, Handy, Buzz, Whispering/Epicenter,
## Killer features inventory
| Feature | Source repo(s) | Implementation complexity | Magnotia priority |
| Feature | Source repo(s) | Implementation complexity | Lumotia priority |
|---|---|---|---|
| Engine abstraction crate (Whisper / Parakeet / Moonshine behind one trait) | Handy, Whispering (both use `transcribe-rs`) | **M** | MVP — unblocks Windows fallback off whisper-rs |
| Vulkan backend as default Windows GPU path | whisper.cpp, Buzz, Vibe | **S** | MVP |
@@ -45,7 +45,7 @@ Ten repos surveyed (whisper.cpp, whisper-rs, Handy, Buzz, Whispering/Epicenter,
| VAD-gated chunking with Silero | whisper.cpp, Buzz v1.4.4, faster-whisper | **M** | MVP (already in streaming scope) |
| Hallucination blocklist + confidence gate | WhisperLive #185, ufal #121 | **S** | MVP |
| Warm-up WAV at launch | ufal (warm-up file PR) | **S** | MVP |
| LocalAgreement-n streaming commit policy | ufal | **M** | v1 (if Magnotia streaming currently naive) |
| LocalAgreement-n streaming commit policy | ufal | **M** | v1 (if Lumotia streaming currently naive) |
| Initial prompt / custom-vocab biasing | Whispering PR #1132, Buzz, Handy custom-words | **S** | MVP |
| Progressive audio save (crash-safe) | Whispering | **S** | MVP |
| Keep audio on disk when transcription fails, offer retry | Handy v0.8.0 | **S** | MVP |
@@ -74,16 +74,16 @@ Ten repos surveyed (whisper.cpp, whisper-rs, Handy, Buzz, Whispering/Epicenter,
- **LocalAgreement-n is the de-facto streaming policy** for un-fine-tuned Whisper. Emission latency ≈ 2× chunk size, and every chunk is re-transcribed multiple times, which is costly but correct. Newer AlignAtt (ufal/SimulStreaming, IWSLT 2025 winner) is ~5× faster — flag for a later swap.
- **Prompt carry-over is a two-edged sword.** Helpful for proper nouns (ufal's `static_init_prompt`), lethal on repetition loops (#161) or when the model wasn't trained with prompt conditioning (#133). Expose `condition_on_previous_text` as a runtime toggle and reset context on repetition detection.
- **Cold-start latency is universal (~45 s on first chunk).** Run a short silent WAV at app launch and after any GPU/ANE idle.
- **Backend abstraction is mandatory for Magnotia's five-platform target.** WhisperLive runs faster-whisper / TensorRT-LLM / OpenVINO behind one socket; ufal swaps faster-whisper / whisper-timestamped / MLX / OpenAI API. Bake this in early.
- **Backend abstraction is mandatory for Lumotia's five-platform target.** WhisperLive runs faster-whisper / TensorRT-LLM / OpenVINO behind one socket; ufal swaps faster-whisper / whisper-timestamped / MLX / OpenAI API. Bake this in early.
- **Reconnect/resume logic is under-engineered in both reference projects** (WhisperLive #388). For mobile targets, design resumable streaming from day one — no good OSS reference exists.
- **First chunk triggers the full Whisper context window to allocate**, causing the latency cliff. Pre-allocate on warm-up.
- **Multi-client fan-out** (WhisperLive PR #174) is a clean pattern if Magnotia ever wants simultaneous transcribe + translate on one mic.
- **Multi-client fan-out** (WhisperLive PR #174) is a clean pattern if Lumotia ever wants simultaneous transcribe + translate on one mic.
---
## LLM formatting layer findings
- **Auto-apply LLM cleanup is where "LLM changed my meaning" complaints originate.** None of Scriberr/Vibe/OpenWhispr have this class of issue in volume because they keep LLM post-processing explicitly opt-in. Magnotia auto-applies — make it one-keystroke revertable and always show the raw transcript.
- **Auto-apply LLM cleanup is where "LLM changed my meaning" complaints originate.** None of Scriberr/Vibe/OpenWhispr have this class of issue in volume because they keep LLM post-processing explicitly opt-in. Lumotia auto-applies — make it one-keystroke revertable and always show the raw transcript.
- **Ollama connectivity is the #1 source of user-facing LLM bugs** — Scriberr #111/#144, Vibe #438/#487. Solve with auto-detect, "Test connection", pre-approved Tauri HTTP capability for `127.0.0.1:11434` (and whichever port bundled llama.cpp uses), and a visible status chip.
- **VRAM contention** (Scriberr #217): bundled LLM + Whisper on one consumer GPU evict each other. Default to sequential execution: finish Whisper, unload, run LLM.
- **Feed plain text to the LLM, not raw Whisper JSON with timestamps** — Scriberr PR #288 made this switch and materially improved quality.
@@ -91,7 +91,7 @@ Ten repos surveyed (whisper.cpp, whisper-rs, Handy, Buzz, Whispering/Epicenter,
- **CUDA/cuDNN fragmentation pushes projects towards Vulkan** — Vibe migrated off CUDA-exe (300 MB) to Vulkan whisper.cpp precisely to escape version hell. Same logic applies to llama.cpp — prefer Vulkan backend on Windows.
- **Non-English transcripts are already weaker** (Vibe Vietnamese/Hebrew). LLM "cleanup" will compound errors. Always keep raw-transcript revert; never rewrite history in the paste buffer.
- **System-prompt framing matters.** Whispering's published baseline — *"translator from spoken to written form, not an editor trying to improve the content"* — directly mitigates overcorrection. Copy this framing.
- **Settings per task** (tiny model for titles, bigger for summaries) — Scriberr pattern. Magnotia can extend: faster model for paste-time cleanup, bigger for explicit summarise action.
- **Settings per task** (tiny model for titles, bigger for summaries) — Scriberr pattern. Lumotia can extend: faster model for paste-time cleanup, bigger for explicit summarise action.
- **Streaming LLM output with cancellation** — standard Ollama/llama.cpp capability but rarely shipped; users hit X on 30-second summaries.
---
@@ -146,15 +146,15 @@ Ten repos surveyed (whisper.cpp, whisper-rs, Handy, Buzz, Whispering/Epicenter,
## What couldn't be verified
- **GitHub `/issues?q=...sort=reactions-%2B1-desc` URLs were blocked at the fetch layer for several repos.** Ordering of feature requests by raw reaction count is therefore inferred from comment volume, maintainer engagement, and release-note inclusion rather than numeric reaction tallies.
- **whisper-rs (GitHub) was archived on 30 July 2025; development continues at `codeberg.org/tazz4843/whisper-rs`.** Issue numbers cited post that date will not resolve on GitHub. Magnotia should track Codeberg or fork.
- **whisper-rs (GitHub) was archived on 30 July 2025; development continues at `codeberg.org/tazz4843/whisper-rs`.** Issue numbers cited post that date will not resolve on GitHub. Lumotia should track Codeberg or fork.
- **OpenWhispr issue tracker was not fully mined within budget** — included as a named reference for the bundled-llama.cpp architecture only. Pain-point claims for it are sourced from its README and a third-party dev.to writeup, not from its issues.
- **Whispering / EpicenterHQ issue tracker** was not fully mined — the repo was cited via its release notes and README; its issue base may contain further pain patterns worth a follow-up.
- **Scriberr, Vibe PR numbers (#288, #294, #438, #487, #217, #111, #144)** were sourced via search-result snippets where direct issue-page fetches were refused; titles and content are quoted but exact current status (open/closed/merged as of 21 Apr 2026) was not re-checked on each.
- **faster-whisper is named MIT** but its heavy Python + CUDA runtime footprint makes it a pattern source, not a recommended Magnotia dependency.
- **faster-whisper is named MIT** but its heavy Python + CUDA runtime footprint makes it a pattern source, not a recommended Lumotia dependency.
- **Scriberr** is **MIT, not AGPL** as the brief had hedged — full lifting is permitted; the "reference only" warning in the original task spec does not apply.
- **WhisperLive** is **MIT confirmed** — full lifting permitted.
- **`ufal/whisper_streaming` is maintenance-only**; upstream momentum has moved to `ufal/SimulStreaming` (also MIT). Consider tracking that repo for v1+.
- **Mobile (iOS, Android) pain surface** is under-represented in this survey — none of the ten desktop repos have a mature mobile story beyond whisper.cpp's XCFramework and WhisperLive's `ios-client`. Magnotia's mobile backlog will need a dedicated pass.
- **Mobile (iOS, Android) pain surface** is under-represented in this survey — none of the ten desktop repos have a mature mobile story beyond whisper.cpp's XCFramework and WhisperLive's `ios-client`. Lumotia's mobile backlog will need a dedicated pass.
---

View File

@@ -1,12 +1,12 @@
# Magnotia — Engineering Context for Cursor Agents
# Lumotia — Engineering Context for Cursor Agents
*Canonical project context for Workstream A (Codex) and Workstream B (Opus). If you are a cloud-based AI agent working on Magnotia via Cursor, read this **before** reading `brief.md` — it tells you what is already shipped, what is intentionally deferred, and the ideology rules you cannot override.*
*Canonical project context for Workstream A (Codex) and Workstream B (Opus). If you are a cloud-based AI agent working on Lumotia via Cursor, read this **before** reading `brief.md` — it tells you what is already shipped, what is intentionally deferred, and the ideology rules you cannot override.*
---
## What Magnotia is
## What Lumotia is
Magnotia is a local-first, cognitive-load-aware dictation + task-capture desktop app. Stack: Tauri 2 + Rust workspace + Svelte 5 frontend. Current primary target is Linux (KDE Plasma Wayland); macOS and Windows are in scope. All transcription and LLM inference run locally — no cloud call is ever made implicitly.
Lumotia is a local-first, cognitive-load-aware dictation + task-capture desktop app. Stack: Tauri 2 + Rust workspace + Svelte 5 frontend. Current primary target is Linux (KDE Plasma Wayland); macOS and Windows are in scope. All transcription and LLM inference run locally — no cloud call is ever made implicitly.
Key paths:
@@ -44,7 +44,7 @@ Cloud provider support exists (`crates/cloud-providers`) but is empty. When it g
### 2. No second notes surface
Magnotia is not a notes app. The transcript surface is History + YAML-frontmatter markdown export to the user's existing notes tool (Obsidian). Transcript editing (fix recognition errors in the viewer window) is allowed. Tagging, starring, searching transcripts is allowed. Anything that starts to look like "edit long-form prose" or "sync notes to another device/cloud" is out of scope — stop and reconsider.
Lumotia is not a notes app. The transcript surface is History + YAML-frontmatter markdown export to the user's existing notes tool (Obsidian). Transcript editing (fix recognition errors in the viewer window) is allowed. Tagging, starring, searching transcripts is allowed. Anything that starts to look like "edit long-form prose" or "sync notes to another device/cloud" is out of scope — stop and reconsider.
**Do not propose or build:**
@@ -66,7 +66,7 @@ No feature may assume cloud availability. Offline install is the baseline; cloud
### 6. Low cognitive load
Magnotia is deliberately ADHD-aware. A new setting must *earn* its mental real estate. A new flow must *reduce* not add steps. When in doubt, pick the option that requires fewer user decisions.
Lumotia is deliberately ADHD-aware. A new setting must *earn* its mental real estate. A new flow must *reduce* not add steps. When in doubt, pick the option that requires fewer user decisions.
---
@@ -74,7 +74,7 @@ Magnotia is deliberately ADHD-aware. A new setting must *earn* its mental real e
### Whisper runtime: `whisper-rs` + Vulkan
Magnotia uses `whisper-rs` 0.16 with the `vulkan` feature ([crates/transcription/Cargo.toml](../../crates/transcription/Cargo.toml)), wrapping whisper.cpp. **Do NOT swap to faster-whisper / WhisperX / any PyTorch-based fork.** Both drag a Python runtime into the Tauri binary, which is a far larger tax than any speedup justifies. Runtime-side improvements go into whisper-rs / whisper.cpp. Model-side improvements (Distil-Whisper GGUF, Parakeet-as-English-default) are already shipped.
Lumotia uses `whisper-rs` 0.16 with the `vulkan` feature ([crates/transcription/Cargo.toml](../../crates/transcription/Cargo.toml)), wrapping whisper.cpp. **Do NOT swap to faster-whisper / WhisperX / any PyTorch-based fork.** Both drag a Python runtime into the Tauri binary, which is a far larger tax than any speedup justifies. Runtime-side improvements go into whisper-rs / whisper.cpp. Model-side improvements (Distil-Whisper GGUF, Parakeet-as-English-default) are already shipped.
The `brief.md` references faster-whisper as a pattern source — treat it as one. Do not adopt it as a dependency.
@@ -148,7 +148,7 @@ A per-user speech-gate + initial_prompt priming flow. Discussed and scoped, but
### Cloud-endpoint contract test
Pinned for when `magnotia-cloud-providers` actually grows a provider. Not actionable yet.
Pinned for when `lumotia-cloud-providers` actually grows a provider. Not actionable yet.
### ggml system-lib dedup
@@ -160,7 +160,7 @@ Deferred. If ever built, use sherpa-onnx (already linked for Parakeet).
### Dragon-style "100-passage training"
Never. Whisper has no speaker adaptation; fine-tuning is out of Magnotia's shape.
Never. Whisper has no speaker adaptation; fine-tuning is out of Lumotia's shape.
---
@@ -190,7 +190,7 @@ Never. Whisper has no speaker adaptation; fine-tuning is out of Magnotia's shape
- `get_active_compute_device` command — returns `{ engine, backend, device_name, fallback_reason? }`
- `list_gpus` command — enumerates GPUs for the Settings dropdown
- Streaming cleanup variant — `cleanup_transcript_stream` emitting `magnotia:llm-token` events, abortable via `cancel_llm`
- Streaming cleanup variant — `cleanup_transcript_stream` emitting `lumotia:llm-token` events, abortable via `cancel_llm`
Document these in `docs/whisper-ecosystem/workstream-A.md` before coding so Opus can stub frontends against the agreed shape.
@@ -233,6 +233,6 @@ If Codex hasn't shipped these yet, stub the frontend behind a feature flag with
1. This document (you're in it).
2. [`docs/whisper-ecosystem/brief.md`](./brief.md) — the 31-item atomic task backlog is your source of truth.
3. [`HANDOVER.md`](../../HANDOVER.md) — latest session handover, Linux/Wayland specifics.
4. The specific Magnotia file pointers referenced under your workstream's "You own" list.
4. The specific Lumotia file pointers referenced under your workstream's "You own" list.
Then write your `docs/whisper-ecosystem/workstream-{A,B}.md` execution plan (sequence, dependencies, contract shapes) and commit it before writing any non-doc code.

View File

@@ -35,7 +35,7 @@ lib tests and moves on; no new code.
The backlog groups itself into four natural phases. Each phase lands as
a sequence of small, one-concern commits, with `cargo test --workspace
--lib && cargo build -p magnotia && npm run check` green at every commit
--lib && cargo build -p lumotia && npm run check` green at every commit
boundary. At the end of each phase we pause for review before the next.
### Phase A.1 — Pre-emptive patches (no new UX surface)
@@ -43,7 +43,7 @@ boundary. At the end of each phase we pause for review before the next.
| Seq | Item | Effect | Depends on |
|---|---|---|---|
| 1 | **#2**: pre-approve `http://127.0.0.1:*` in Tauri capabilities for LLM | Adds `http:default-scope-127.0.0.1` style capability + widens `connect-src` CSP to localhost | — |
| 2 | **#6**: guard `whisper-rs-sys` + `tokenizers` on Windows | Audit: Magnotia doesn't link `tokenizers` directly; we add a `#[cfg(all(target_os = "windows"))]` compile-time assert in `magnotia-transcription/build.rs` that fails the build if anything in the tree pulls in `tokenizers` on Windows | — |
| 2 | **#6**: guard `whisper-rs-sys` + `tokenizers` on Windows | Audit: Lumotia doesn't link `tokenizers` directly; we add a `#[cfg(all(target_os = "windows"))]` compile-time assert in `lumotia-transcription/build.rs` that fails the build if anything in the tree pulls in `tokenizers` on Windows | — |
| 3 | **#7**: CPU feature detection + non-AVX2 fallback surface | Extend `crates/core/src/hardware.rs` with `CpuFeatures` (avx2, avx512, fma, sse4_2) via `std::is_x86_feature_detected!` at runtime; surface as part of `RuntimeCapabilities`; when AVX2 is absent, emit a `warning` log + `runtime-warning` event so the frontend can show a banner | #1 |
| 4 | **#1**: detect and surface active compute device | Extend `RuntimeCapabilities` with an `ActiveComputeDevice` struct (`kind: "cuda" \| "vulkan" \| "metal" \| "cpu"`, `label: String`, `reason: Option<String>`). For Whisper, read the first `whisper_print_system_info` line via a shim in `whisper_rs_backend`; reason is filled on any CPU fallback (e.g., "Vulkan loader not found") | #7 |
| 5 | **#8**: checksum + resumable model downloads; retain audio on failure | Port the `crates/llm/src/model_manager.rs` pattern (SHA256 verify, `.part` file resume, `ResumeUnsupported` error) into `crates/transcription/src/model_manager.rs::download_file`. The existing code already does incremental SHA256 and range resume but lacks (a) validation of an **existing full file** before re-downloading, (b) ResumeUnsupported signalling, (c) test coverage parity. Retaining audio on failure is already covered in `commands/live.rs` via `save_audio` — add an explicit retry-friendly error classification so the frontend can render "Retry transcription" (a B surface, but the error enum ships here) | — |
@@ -52,14 +52,14 @@ boundary. At the end of each phase we pause for review before the next.
**Commit boundary:** `cargo test --workspace --lib` (must include new
tests for checksum resume + CPU feature detect + active-device shim),
`cargo build -p magnotia`, `npm run check`. Then **stop for review.**
`cargo build -p lumotia`, `npm run check`. Then **stop for review.**
### Phase A.2 — Engine abstraction (#13, #19)
| Seq | Item | Effect | Depends on |
|---|---|---|---|
| 8 | **#13 (engine trait)**: `magnotia-transcription` already has `LocalEngine` and a `SpeechBackend` enum over `transcribe-rs` adapter + `WhisperRsBackend`. This is a 90% version of Handy's `transcribe-rs` pattern. We lift it one more inch: replace the enum with a public `Transcriber` trait (`load`, `transcribe_sync`, `capabilities`) + blanket impls for the existing two backends, and gate `WhisperRsBackend` behind the `whisper` feature so non-Whisper builds compile without pulling `whisper-rs-sys` | — |
| 9 | **#19**: progressive WAV write during live capture | Extend `commands/live.rs` to stream captured mono-16kHz samples directly into a `.wav` in the session folder via `magnotia_audio::WavWriter::append`. Replace the in-memory `kept_audio: Vec<f32>` when `save_audio` is on with a disk-backed writer; on crash, the partial file is a playable WAV. Commits the `commands/live.rs` frame-size bookkeeping untouched | #13 (uses `Transcriber::capabilities().sample_rate`) |
| 8 | **#13 (engine trait)**: `lumotia-transcription` already has `LocalEngine` and a `SpeechBackend` enum over `transcribe-rs` adapter + `WhisperRsBackend`. This is a 90% version of Handy's `transcribe-rs` pattern. We lift it one more inch: replace the enum with a public `Transcriber` trait (`load`, `transcribe_sync`, `capabilities`) + blanket impls for the existing two backends, and gate `WhisperRsBackend` behind the `whisper` feature so non-Whisper builds compile without pulling `whisper-rs-sys` | — |
| 9 | **#19**: progressive WAV write during live capture | Extend `commands/live.rs` to stream captured mono-16kHz samples directly into a `.wav` in the session folder via `lumotia_audio::WavWriter::append`. Replace the in-memory `kept_audio: Vec<f32>` when `save_audio` is on with a disk-backed writer; on crash, the partial file is a playable WAV. Commits the `commands/live.rs` frame-size bookkeeping untouched | #13 (uses `Transcriber::capabilities().sample_rate`) |
**Commit boundary:** tests (`transcriber_trait_is_object_safe`,
`wav_writer_survives_crash`), build, check. **Stop for review.**
@@ -105,7 +105,7 @@ repetition detector. **Stop for review.**
| `commands::models::get_runtime_capabilities` | **Extend** | Add `active_compute_device`, `cpu_features`, `parallel_mode_available` fields |
| `commands::live::evaluate_speech_gate` | **Replace** with VAD-gated chunker, keep RMS version as a cheap fallback when VAD ONNX model missing |
| `commands::live::run_live_session` | **Extend** | Switch buffer ownership to `streaming::StreamingSession`, keep the `cpal` plumbing |
| `crates/transcription/src/model_manager::download_file` | **Extend** | Harden to match `magnotia-llm`'s resume + sha behaviour, add `ResumeUnsupported` arm |
| `crates/transcription/src/model_manager::download_file` | **Extend** | Harden to match `lumotia-llm`'s resume + sha behaviour, add `ResumeUnsupported` arm |
| `crates/transcription/src/whisper_rs_backend::transcribe_sync` | **Extend** | Surface `no_speech_prob` + `avg_logprob` per segment |
| `crates/transcription/src/local_engine::SpeechBackend` | **Replace** with public `Transcriber` trait |
| `crates/transcription/src/concurrency` | **Extend** | Already a stub; add GPU guard wrapper |
@@ -225,7 +225,7 @@ behind `runtimeCapabilities.parallelModeAvailable`.
## Test strategy
- Every phase ends green on `cargo test --workspace --lib && cargo
build -p magnotia && npm run check`.
build -p lumotia && npm run check`.
- `phase4-systems-f7d0` never regresses the 116 existing lib tests. If
a test starts failing and the cause is an A change, it's fixed
in-phase, not deferred.
@@ -266,5 +266,5 @@ behind `runtimeCapabilities.parallelModeAvailable`.
- One concern per commit. Subject line `feat(A.2):`, `fix(A.1):`, etc.,
referencing the phase so the reviewer can group them.
- Commit message body states which brief-item the commit closes.
- `cargo test --workspace --lib && cargo build -p magnotia && npm run check`
- `cargo test --workspace --lib && cargo build -p lumotia && npm run check`
green before every commit, per the rules of engagement.

View File

@@ -42,14 +42,14 @@ From the Workstream B prompt:
contract. Load-bearing. Any prompt edit that softens it gets
rejected at review.
4. **Local-first.** No feature may assume cloud availability. Ollama
(#27) is additive — Magnotia works fully offline without it.
(#27) is additive — Lumotia works fully offline without it.
---
## Execution order
Three phases, each one commit per concern, each ending with a green
`cargo build -p magnotia && npm run check`. We stop at each phase boundary
`cargo build -p lumotia && npm run check`. We stop at each phase boundary
for review.
### Phase B.1 — Pre-emptive UX (items #3, #4 UX, #5, #10, #11)
@@ -61,11 +61,11 @@ a bug, which matches the brief's "pre-emptive patches" framing.
|---|---|---|---|
| 1 | **#3**: clipboard snapshot + restore after paste | `src-tauri/src/commands/paste.rs` already paste-matrix-hardens for Wayland. Extend it: before `Clipboard::new().set_text(text)`, read the existing `Clipboard::get_text()` snapshot (+ `get_image()` if supported by arboard on this target). Schedule a `tokio::spawn` task that sleeps 300ms after paste and restores the snapshot. User's prior clipboard content is therefore recovered automatically. Keep the `hide_preview_overlay_for_paste` dance. | No |
| 2 | **#4 (UX side)**: debounce hotkey, add "warming up…" state | A warms the CPAL stream at app start. B owns the `page.status` state machine: the hotkey handler in `DictationPage.svelte` already gates on `page.recording`; add a ~120ms debounce on the hotkey press so a rapid double-tap doesn't double-init. Existing native capture path is untouched. | Stream warm-up lands in A's Phase A.3 (item #23); B's debounce is independent. |
| 3 | **#5**: hotkey capability matrix per OS, UI rejection of invalid combos | `src/lib/components/HotkeyRecorder.svelte` (or equivalent). Add `hotkeyValidity.ts` returning `{ valid: boolean, reason: string \| null }` given a combo + OS. Rules: on X11, reject single-key combos and report "Add a modifier to capture this key outside Magnotia"; on Windows, reject combos whose only modifier is a right-hand variant (RCtrl/RAlt — per `Handy` #966); on macOS, reject Fn-only combos. Block save button + surface inline "why" copy. | No |
| 3 | **#5**: hotkey capability matrix per OS, UI rejection of invalid combos | `src/lib/components/HotkeyRecorder.svelte` (or equivalent). Add `hotkeyValidity.ts` returning `{ valid: boolean, reason: string \| null }` given a combo + OS. Rules: on X11, reject single-key combos and report "Add a modifier to capture this key outside Lumotia"; on Windows, reject combos whose only modifier is a right-hand variant (RCtrl/RAlt — per `Handy` #966); on macOS, reject Fn-only combos. Block save button + surface inline "why" copy. | No |
| 4 | **#10**: detect focused-app class; force clipboard-only paste in terminals | Extend `src-tauri/src/commands/paste.rs::paste_text` to look up the focused window class (GetForegroundWindow → class name on Windows; `xdotool getactivewindow getwindowclassname` on Linux X11; `CGWindowListCopyWindowInfo` → ownerName on macOS). On a known-terminal class (`Alacritty`, `kitty`, `gnome-terminal-server`, `WindowsTerminal`, `Code`, `iTerm2`, `Terminal`, etc.) skip the keystroke and just set the clipboard; return `outcome.pasted = false, outcome.copied = true, message = "Terminal detected — clipboard only"`. UX handles that message already. | No |
| 5 | **#11**: versioned settings schema with forward migration | `src/lib/stores/page.svelte.ts` currently reads `magnotia_settings` localStorage blob via `parseStoredJson` and spreads it over `defaults`. Add a `SettingsSchemaVersion` integer (start at 1) and a `migrateSettings(raw, toVersion)` helper. Persist `{ version, data }` on save; read both on load; run migrations in order; fall back cleanly on corruption (drop to defaults, toast the user once). Every new field B adds (below) bumps the version. | No |
| 5 | **#11**: versioned settings schema with forward migration | `src/lib/stores/page.svelte.ts` currently reads `lumotia_settings` localStorage blob via `parseStoredJson` and spreads it over `defaults`. Add a `SettingsSchemaVersion` integer (start at 1) and a `migrateSettings(raw, toVersion)` helper. Persist `{ version, data }` on save; read both on load; run migrations in order; fall back cleanly on corruption (drop to defaults, toast the user once). Every new field B adds (below) bumps the version. | No |
**Commit boundary:** `cargo build -p magnotia && npm run check` green for
**Commit boundary:** `cargo build -p lumotia && npm run check` green for
every commit; JS-side vitest isn't set up in this repo, so the
migration tests live under `src/lib/utils/__tests__/` as pure TS +
`svelte-check`. If tests need a runner we borrow Codex's setup in
@@ -76,7 +76,7 @@ Phase B.2. **Stop for review.**
| Seq | Item | Effect | Depends on A? |
|---|---|---|---|
| 6 | **#14 (UI)**: GPU enumeration + explicit device selector in Settings | Adds a `Compute device` row to `SettingsPage.svelte` under the existing `openSection === 'transcription'` section. Reads `runtimeCapabilities.activeComputeDevice` (A ships this in Phase A.1 #1) and `list_gpus()` (A ships in Phase A.2). If `list_gpus` isn't shipped yet, B ships the UI behind `if (runtimeCapabilities.activeComputeDevice)` and a TODO to call `list_gpus` later. `set_preferred_gpu(id)` is wired to a save-on-select handler with a "Restart to apply" toast. No default change until user picks. | Yes — A ships `list_gpus` in Phase A.2. B stubs behind the `activeComputeDevice` check until then. |
| 7 | **#15**: named LLM prompt presets | Preset data lives client-side in `src/lib/stores/promptPresets.svelte.ts` (new). Five presets shipped: `Quick clean` (default), `Email`, `Meeting notes`, `Code`, `Summary`. Each is `{ id, name, systemPrompt, modelTier }`. User-editable list stored in `localStorage['magnotia_prompt_presets']` with migration hook. Settings gets a new "Prompt presets" sub-card inside `openSection === 'ai'`; DictationPage gets a small preset pill above the Status bar — active preset is applied at cleanup time. Default preset's `systemPrompt` is `CLEANUP_PROMPT` from `llm_client.rs`, so the existing baseline is preserved. | No — uses existing `cleanup_transcript_text_cmd(transcript, profile_id)`. If the backend later needs `system_prompt_override` (not strictly needed for MVP — UI just restricts _when_ cleanup fires), B adds that with A's sign-off. |
| 7 | **#15**: named LLM prompt presets | Preset data lives client-side in `src/lib/stores/promptPresets.svelte.ts` (new). Five presets shipped: `Quick clean` (default), `Email`, `Meeting notes`, `Code`, `Summary`. Each is `{ id, name, systemPrompt, modelTier }`. User-editable list stored in `localStorage['lumotia_prompt_presets']` with migration hook. Settings gets a new "Prompt presets" sub-card inside `openSection === 'ai'`; DictationPage gets a small preset pill above the Status bar — active preset is applied at cleanup time. Default preset's `systemPrompt` is `CLEANUP_PROMPT` from `llm_client.rs`, so the existing baseline is preserved. | No — uses existing `cleanup_transcript_text_cmd(transcript, profile_id)`. If the backend later needs `system_prompt_override` (not strictly needed for MVP — UI just restricts _when_ cleanup fires), B adds that with A's sign-off. |
| 8 | **#17**: raw-transcript revert with ⌘/Ctrl+Z within 5 s | `src/lib/pages/DictationPage.svelte::finaliseTranscription` keeps `rawTranscript` alongside `transcript`. After paste, start a 5-second window where Ctrl/⌘+Z caught globally (via `@tauri-apps/plugin-global-shortcut` ephemerally, or via Tauri IPC from the main window) replaces the last paste: clipboard reset → previous-transcript paste. Preview overlay adds a 5s countdown chip. Untouched clipboard snapshot from #3 is reused. The revert hook lives in `src/routes/preview/+page.svelte` phase machine: new `revertable: true` transient on `phase === "final"`. | No — builds on #3 and the existing paste command. |
| 9 | **#20**: sound cues for start / stop / complete | Small sound-cue module `src/lib/utils/soundCues.ts`: preloads three short WAV/OGG assets (under `static/sounds/`), uses the WebAudio API, caps volume at the user's mute + slider. Settings adds a new `Sound cues` card under `openSection === 'audio'` with a toggle, three preview buttons, a volume slider. Default: **on**, 50% volume. | No |