Files
Lumotia/docs/superpowers/plans/2026-05-12-engine-slop-residuals.md
Jake 26c7307607
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
agent: lumotia-rebrand — docs, scripts, root config, residuals
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>
2026-05-13 12:38:03 +01:00

242 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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 AE 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 AB 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.