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>
14 KiB
name, type, tags, description
| name | type | tags | description | ||||
|---|---|---|---|---|---|---|---|
| Engine slop residuals (post-prognosis pass) | plan |
|
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 totracing::in48c4838. - A. Storage-layer typed errors — survey in
fdab777, migration in52565ea, legacyStorageError(String)removal ina36ae7e.
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;Iois structured;FileNotFoundquotes paths;ProviderNotRegisteredintroduced. - Core types:
Cow<'static, str>forModelId/EngineName,NonZeroU32forAudioSamples::sample_rate,Megabytes::from_gbtakesu64. - Capture cleanup: regex-based
/proc/asound/cardsparser (test covers product names with embedded colons), constants documented, RMS sum idiomatic, Codex-review comments removed. - Tauri startup:
unsafe std::env::set_varremoved (logs warning instead); function renamedwarn_if_x11_env_unset_on_wayland; setup collapsed to singleblock_on; JS injection rewritten (var p = {...}direct, noJSON.parse); WebKitGTK mic auto-grant logs warning at startup. - Counter:
RECORDING_COUNTERusesSeqCst. - Tracing: subscriber initialised at top of
run()with sensible default filter, honorsRUST_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::Errorserializable 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— addStorageErrorvariant if changing top-level shapecrates/storage/src/error.rs— new file (likely)crates/storage/src/database.rs— 19 call sitescrates/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 sitesrc-tauri/src/commands/diagnostics.rs— 1 sitesrc-tauri/src/commands/tasks.rs— 1 sitecrates/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,--nocapturesemantics depend on stdio.
Decisions needed:
- New tracing target names for
commands::live,commands::models, etc. — extend the default filter insrc-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 rewritesrc-tauri/src/commands/audio.rs— command handler refactorsrc-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:
proptestvsquickcheck? Recommendproptest(more flexible shrinking).- Run as part of standard
cargo testor as a separatecargo test --features slow-tests?
Files in scope:
crates/audio/Cargo.toml— addproptestto[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
MagnotiaErrorfor 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 whenXDG_SESSION_TYPE=wayland. Static.desktopExec=envcannot 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:
#!/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:
AppRunis the wrapper. - Flatpak: wrapper under
/app/bin/; the manifest'scommandpoints at it.
Fallback — static .desktop Exec:
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-
Execper 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
.desktopExechardcodes env directly, the terminal-launch override path is documented in user docs. lib.rs::warn_if_x11_env_unset_on_waylandcontinues to surface missed contracts during dev/diagnostics.
Suggested sequencing
- B (eprintln sweep) first — mechanical, low-risk, immediate observability win.
- A (storage typed errors) next — well-scoped, no architectural change, unblocks E.
- D (property-based DSP) in parallel with A — independent surface, can be done by anyone any time.
- E (FE/BE error boundary) after A — depends on storage error shape.
- C (actor model) last — biggest surface, deserves its own brainstorm + plan + likely its own branch.
- 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.mdis 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.