The 2026-05-12 engine-slop pass removed runtime std::env::set_var mutation from src-tauri/src/lib.rs and replaced it with warn_if_x11_env_unset_on_wayland. With no launcher owning the contract, Linux Wayland dogfood was about to regress. run.sh: - now owns Linux launcher env defaults via case "$(uname -s)" LIBCLANG_PATH=/usr/lib64/llvm21/lib64 (user-set wins) WEBKIT_DISABLE_DMABUF_RENDERER=1 (always on Linux; user-set wins) GDK_BACKEND=x11, WINIT_UNIX_BACKEND=x11 (Wayland only; user-set wins) - 60s Vite readiness timeout - detects early Vite exit (kill -0 + wait) instead of hanging forever - trap installed before wait so Ctrl-C cleans up - args forwarded: ./run.sh --release etc. - non-exec final Tauri launch preserved so cleanup trap fires package.json: - "dev:tauri": "./run.sh" — canonical discoverable dev command Docs: - README, dev-setup, architecture-map runtime + launcher pages updated with the new contract; canonical command is npm run dev:tauri (./run.sh as direct equivalent) - dev-launcher-and-scripts.md replaces the incorrect "kills process group" claim with honest "kills the spawned npm process" - dev-setup.md path /CORBEL-Projects/magnotia → /CORBEL-Projects/transcription-app - gpu-tuning/plan.md gets a superseded note rather than rewriting the original rationale - engine-slop-residuals.md gains Area F (packaged-binary launcher contract) with honest wrapper-vs-.desktop trade-off documented Verification: bash -n run.sh; shellcheck clean; cargo check -p magnotia green; npm pkg get 'scripts.dev:tauri' returns "./run.sh"; stale-ref sweep clean across living docs.
13 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.
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 magnotia-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/magnotia/magnotia-bin "$@"
Per-format wrapper locations:
.deb/.rpm: ship the wrapper as/usr/bin/magnotia; 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 magnotia %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.