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>
242 lines
14 KiB
Markdown
242 lines
14 KiB
Markdown
---
|
||
name: Engine slop residuals (post-prognosis pass)
|
||
type: plan
|
||
tags: [engine, slop-cleanup, plan, deferred]
|
||
description: Roadmap for the deferred code-quality items that did not fit in the 2026-05-12 prognosis-fix pass. Each area is scoped for its own focused plan when ready to execute.
|
||
---
|
||
|
||
# Engine slop residuals — post-prognosis pass
|
||
|
||
> **For agentic workers:** This is a meta-plan. It catalogues independent residual work areas, each of which should get its own detailed TDD plan when scheduled. Do **not** treat the sections below as a single execution checklist.
|
||
|
||
## Status update — 2026-05-13
|
||
|
||
Completed:
|
||
- B. eprintln → tracing sweep — landed in `184214b`.
|
||
- Tracing finishing pass — hotkey `log::` migrated to `tracing::` in `48c4838`.
|
||
- A. Storage-layer typed errors — survey in `fdab777`, migration in `52565ea`, legacy `StorageError(String)` removal in `a36ae7e`.
|
||
|
||
Next recommended:
|
||
- Phase 10a dogfood before Area E, to validate the new observability and storage-error semantics in real app use.
|
||
|
||
Still open:
|
||
- D. Property-based DSP testing.
|
||
- E. FE/BE error boundary cleanup.
|
||
- C. Actor-model refactor.
|
||
- F. Packaged-binary Linux launcher contract.
|
||
|
||
## Context
|
||
|
||
On 2026-05-12 an external code review rated the codebase 4/10 and laid out a four-bucket de-sloppifying plan. That pass shipped (uncommitted in working tree at time of writing). It hit the *prognosis-level* items only:
|
||
|
||
- DSP: naive decimation removed, `StreamingResampler`/rubato in place, regression tests at 12 kHz (40 dB) + 9 kHz (26 dB).
|
||
- Core errors: `Other(String)` removed; `Io` is structured; `FileNotFound` quotes paths; `ProviderNotRegistered` introduced.
|
||
- Core types: `Cow<'static, str>` for `ModelId`/`EngineName`, `NonZeroU32` for `AudioSamples::sample_rate`, `Megabytes::from_gb` takes `u64`.
|
||
- Capture cleanup: regex-based `/proc/asound/cards` parser (test covers product names with embedded colons), constants documented, RMS sum idiomatic, Codex-review comments removed.
|
||
- Tauri startup: `unsafe std::env::set_var` removed (logs warning instead); function renamed `warn_if_x11_env_unset_on_wayland`; setup collapsed to single `block_on`; JS injection rewritten (`var p = {...}` direct, no `JSON.parse`); WebKitGTK mic auto-grant logs warning at startup.
|
||
- Counter: `RECORDING_COUNTER` uses `SeqCst`.
|
||
- Tracing: subscriber initialised at top of `run()` with sensible default filter, honors `RUST_LOG`, writes to stderr.
|
||
|
||
The code-proposal and macro-architecture captures from the same review session went further (10/10 demands and workspace-level critique). Those items are the residuals below.
|
||
|
||
## Residual areas
|
||
|
||
Each area is independent. They can be tackled in any order, but the suggested order minimises rework.
|
||
|
||
### A. Storage-layer typed errors
|
||
|
||
**Status:** identified, not started.
|
||
|
||
**Scope:** ~25 `StorageError(String)` sites in `crates/storage/src/{database.rs, migrations.rs}`.
|
||
|
||
**Why it matters:** The frontend cannot distinguish "schema migration failed" from "FTS search failed" from "transcript not found" — all surface as one stringly-typed variant. This is the same class of bug that motivated removing `Other(String)`.
|
||
|
||
**Approach:** Split `StorageError` into a `StorageError` enum (sub-error in its own right) with variants like `Connect { path, source }`, `MigrationFailed { version, source }`, `TransactionFailed { phase, source }`, `QueryFailed { kind, source }`, `NotFound { table, id }`. Wrap `sqlx::Error` (or whatever `rusqlite::Error` is in use) as `#[source]`.
|
||
|
||
**Decisions needed:**
|
||
- Is `sqlx::Error`/`rusqlite::Error` serializable across the Tauri boundary? If not, what is preserved (kind + display)?
|
||
- Do we keep one flat error type for the crate or nest by domain (`MigrationError`, `TranscriptError`, `TaskError`)?
|
||
- Backwards-compat for any existing error-handling code in frontend.
|
||
|
||
**Files in scope:**
|
||
- `crates/core/src/error.rs` — add `StorageError` variant if changing top-level shape
|
||
- `crates/storage/src/error.rs` — new file (likely)
|
||
- `crates/storage/src/database.rs` — 19 call sites
|
||
- `crates/storage/src/migrations.rs` — 6 call sites
|
||
|
||
**Acceptance:** All `StorageError(format!(...))` calls replaced with typed variants. Frontend can `match` on storage-error kind. `cargo test --workspace` green.
|
||
|
||
### B. eprintln → tracing sweep (rest of repo)
|
||
|
||
**Status:** identified, not started.
|
||
|
||
**Scope:** ~30 `eprintln!` sites in production code paths (excluding tests and tooling binaries — see below).
|
||
|
||
**Why it matters:** Now that the subscriber is wired, every `eprintln!` is a missed observability signal. The startup/capture paths are clean; the runtime hot paths are not.
|
||
|
||
**Approach:** Mechanical migration, one file at a time, with appropriate target and structured fields.
|
||
|
||
**Files in scope (production):**
|
||
- `src-tauri/src/commands/live.rs` — 8 sites (highest priority — session lifecycle + inference errors)
|
||
- `src-tauri/src/commands/models.rs` — 5 sites (Whisper warmup)
|
||
- `src-tauri/src/commands/power.rs` — 4 sites (macOS App Nap)
|
||
- `src-tauri/src/commands/transcription.rs` — 1 site
|
||
- `src-tauri/src/commands/diagnostics.rs` — 1 site
|
||
- `src-tauri/src/commands/tasks.rs` — 1 site
|
||
- `crates/hotkey/src/linux.rs` — 2 sites
|
||
|
||
**Files explicitly out of scope:**
|
||
- `crates/mcp/src/main.rs` — separate binary, its own stdio protocol; keep eprintln as-is unless that binary gets its own subscriber.
|
||
- `crates/*/tests/*.rs` — test diagnostic output, `--nocapture` semantics depend on stdio.
|
||
|
||
**Decisions needed:**
|
||
- New tracing target names for `commands::live`, `commands::models`, etc. — extend the default filter in `src-tauri/src/lib.rs::init_tracing`.
|
||
|
||
**Acceptance:** No `eprintln!` in `src-tauri/src/commands/` or `crates/hotkey/src/linux.rs`. Default filter shows live-session events at INFO. `cargo test --workspace` green.
|
||
|
||
### C. Actor-model refactor for capture + inference
|
||
|
||
**Status:** identified, not started. **Largest scope.**
|
||
|
||
**Scope:** Replace `Arc<Mutex<Vec<f32>>>` shared-state pattern in `CaptureWorker` (and analogous patterns in live inference) with isolated actor loops + `mpsc` channels.
|
||
|
||
**Why it matters:** Reviewer's core architectural critique. Shared mutable state across async boundaries with `std::sync::Mutex` risks executor stalls under contention. Architecturally also makes it impossible to add features like multi-source capture, parallel inference, or recording-while-streaming without lock contention.
|
||
|
||
**Approach:** Spawn dedicated tokio tasks for the capture worker and the inference worker. Communicate exclusively via bounded `mpsc` channels. Tauri command handlers hold only channel senders.
|
||
|
||
**Decisions needed (before plan):**
|
||
- Bounded vs unbounded mpsc? Recommend bounded for backpressure visibility.
|
||
- Channel capacity? (current ad-hoc cap is `AUDIO_CHANNEL_CAPACITY = 32`)
|
||
- Backpressure policy when capture outpaces inference: drop oldest, drop newest, await consumer?
|
||
- Worker shutdown protocol: drop-channel-and-await, explicit stop message, or cancellation token?
|
||
- How are runtime errors (already typed via `CaptureRuntimeError`) propagated through the actor boundary?
|
||
- Migration plan: parallel implementation behind a feature flag, or in-place replacement?
|
||
|
||
**Risk:** This is a substantial refactor. Likely worth a brainstorming session before plan-writing.
|
||
|
||
**Files in scope:**
|
||
- `crates/audio/src/capture.rs` — worker lifecycle rewrite
|
||
- `src-tauri/src/commands/audio.rs` — command handler refactor
|
||
- `src-tauri/src/commands/live.rs` — inference worker integration
|
||
- Likely new files: `crates/audio/src/capture_actor.rs`, `crates/transcription/src/inference_actor.rs`
|
||
|
||
**Acceptance:** No `Arc<Mutex<T>>` across `.await` points in capture or inference code paths. Capture and inference are independent tokio tasks. End-to-end recording flow works in dev mode. `cargo test --workspace` green. Manual dogfood pass.
|
||
|
||
### D. Property-based DSP testing
|
||
|
||
**Status:** identified, not started.
|
||
|
||
**Scope:** Add `proptest` (or similar) generators for audio inputs and assert invariants of the resampling and VAD pipelines.
|
||
|
||
**Why it matters:** The current DSP tests are point checks at 12 kHz and 9 kHz. The reviewer's 10/10 demand is property-based testing that covers the input space (any frequency, any chunk size, any noise floor) and asserts invariants (output is bounded, energy is bounded, duration is approximately preserved).
|
||
|
||
**Approach:** Add `proptest` dev-dependency. Write generators for `(sample_rate, signal_frequency, amplitude, chunk_size, total_secs)` tuples. Assert:
|
||
- Output samples are finite (no NaN/Inf).
|
||
- Output peak ≤ input peak + small tolerance (no amplification).
|
||
- Output duration within ±5% of expected.
|
||
- Output energy at frequencies above Nyquist of target rate is at least 30 dB below input energy at those frequencies.
|
||
|
||
**Decisions needed:**
|
||
- `proptest` vs `quickcheck`? Recommend `proptest` (more flexible shrinking).
|
||
- Run as part of standard `cargo test` or as a separate `cargo test --features slow-tests`?
|
||
|
||
**Files in scope:**
|
||
- `crates/audio/Cargo.toml` — add `proptest` to `[dev-dependencies]`
|
||
- `crates/audio/src/streaming_resample.rs` — extend `#[cfg(test)]` module
|
||
- Likely also `crates/transcription/src/streaming/rms_vad.rs` — VAD has equally testable invariants
|
||
|
||
**Acceptance:** `cargo test -p lumotia-audio` includes property tests. Property tests have run with at least 1000 cases per invariant.
|
||
|
||
### E. Frontend/backend error boundary cleanup
|
||
|
||
**Status:** partially complete (orchestrator's `ProviderNotRegistered`).
|
||
|
||
**Scope:** Audit every Tauri command's return type and ensure errors propagate with full type information across the IPC boundary. Currently many commands return `Result<T, String>` instead of `Result<T, MagnotiaError>`.
|
||
|
||
**Why it matters:** Reviewer's frontend-backend boundary critique. The frontend can't `switch` on error kind if the backend serialises errors as strings.
|
||
|
||
**Approach:** Inventory all `#[tauri::command]` functions, identify those returning `Result<T, String>`, convert to `Result<T, MagnotiaError>` (or a command-specific error enum that serializes cleanly). Update frontend handlers to use the typed shape.
|
||
|
||
**Decisions needed:**
|
||
- One `MagnotiaError` for all commands, or per-command enums?
|
||
- How do we surface the new shape to the Svelte side without manually maintaining TypeScript types? (specta? hand-rolled?)
|
||
|
||
**Files in scope:**
|
||
- All files in `src-tauri/src/commands/`
|
||
- All call sites in `src/lib/` (Svelte)
|
||
|
||
**Acceptance:** No `Result<T, String>` in `src-tauri/src/commands/`. Frontend has typed error access. `cargo test --workspace` green; frontend builds.
|
||
|
||
### F. Packaged-binary launcher contract (Linux)
|
||
|
||
**Status:** identified, not started. Direct follow-on from the 2026-05-12 dev-launcher fix; same contract, distribution-time scope.
|
||
|
||
**Scope:** Set the rendering env-vars at app launch time for distributed Linux builds.
|
||
|
||
**Env-var contract per distribution path:**
|
||
|
||
- `WEBKIT_DISABLE_DMABUF_RENDERER=1` — always set on Linux (iGPU idle-cost workaround, applies on X11 and Wayland alike).
|
||
- `GDK_BACKEND=x11`, `WINIT_UNIX_BACKEND=x11` — set only when `XDG_SESSION_TYPE=wayland`. Static `.desktop` `Exec=env` cannot do this conditional; either ship a wrapper or accept always-on X11 backend.
|
||
|
||
**Preferred — wrapper script:**
|
||
|
||
A wrapper preserves user-set values via shell defaults:
|
||
|
||
```sh
|
||
#!/usr/bin/env bash
|
||
export WEBKIT_DISABLE_DMABUF_RENDERER="${WEBKIT_DISABLE_DMABUF_RENDERER:-1}"
|
||
if [ "${XDG_SESSION_TYPE:-}" = "wayland" ]; then
|
||
export GDK_BACKEND="${GDK_BACKEND:-x11}"
|
||
export WINIT_UNIX_BACKEND="${WINIT_UNIX_BACKEND:-x11}"
|
||
fi
|
||
exec /usr/lib/lumotia/lumotia-bin "$@"
|
||
```
|
||
|
||
Per-format wrapper locations:
|
||
|
||
- `.deb` / `.rpm`: ship the wrapper as `/usr/bin/lumotia`; binary lives elsewhere.
|
||
- AppImage: `AppRun` is the wrapper.
|
||
- Flatpak: wrapper under `/app/bin/`; the manifest's `command` points at it.
|
||
|
||
**Fallback — static `.desktop` `Exec`:**
|
||
|
||
```desktop
|
||
Exec=env WEBKIT_DISABLE_DMABUF_RENDERER=1 GDK_BACKEND=x11 WINIT_UNIX_BACKEND=x11 lumotia %U
|
||
```
|
||
|
||
User-set values do not win through this path (the `env` invocation hardcodes them in the child env). Document the terminal-launch override path in user-facing docs.
|
||
|
||
**Decisions needed:**
|
||
|
||
- Wrapper vs hardcoded-`Exec` per format. Default to wrapper.
|
||
- For Flatpak: finish-args env defaults vs wrapper — both work; pick consistency.
|
||
|
||
**Acceptance:**
|
||
|
||
- Each Linux distribution path sets the contract on launch.
|
||
- Where the packaging format uses a wrapper, user-set values still win.
|
||
- Where the `.desktop` `Exec` hardcodes env directly, the terminal-launch override path is documented in user docs.
|
||
- `lib.rs::warn_if_x11_env_unset_on_wayland` continues to surface missed contracts during dev/diagnostics.
|
||
|
||
## Suggested sequencing
|
||
|
||
1. **B (eprintln sweep)** first — mechanical, low-risk, immediate observability win.
|
||
2. **A (storage typed errors)** next — well-scoped, no architectural change, unblocks E.
|
||
3. **D (property-based DSP)** in parallel with A — independent surface, can be done by anyone any time.
|
||
4. **E (FE/BE error boundary)** after A — depends on storage error shape.
|
||
5. **C (actor model)** last — biggest surface, deserves its own brainstorm + plan + likely its own branch.
|
||
6. **F (packaged launcher contract)** when packaging resumes — independent of A–E but required before distributed Linux builds are treated as covered.
|
||
|
||
## Cross-cutting deferrals (Phase 10a-blocking)
|
||
|
||
Two things are explicitly **not** in these residuals because they belong to higher-level workstreams:
|
||
|
||
- **Lumotia rebrand cascade** (Wyrdnote → Lumotia in code, paths, GitHub repo, bundle ID). Hold until A–B done so rename diff doesn't hide regressions.
|
||
- **Phase 10a QC dogfood** — `docs/superpowers/audits/2026-05-10-phase10a-dogfood-notes.md` is queued. Best done after B at minimum so live-session logs are visible.
|
||
|
||
## What this plan does not do
|
||
|
||
- No fix recipes for bugs not yet found. Each area's plan should start by reading the current state and writing failing tests against the current behaviour.
|
||
- No timeline commitments. Sequencing is suggested, not mandatory.
|
||
- No architectural decisions deferred to "Phase B" earlier are pre-committed here. Each area writes its own brainstorm + plan when scheduled.
|