agent: engine slop pass — DSP, typed errors, regex parsing, tracing, audit fixes
External code review on 2026-05-12 rated the codebase 4/10 across audio DSP, error typing, JS injection, env-var safety, ALSA parsing, and async logging. This commit lands the prognosis-level fixes plus three audit follow-ups. Audio/DSP: - StreamingResampler/rubato confirmed in the live capture path - regression test at 12 kHz (rms < 0.01, ~40 dB) catches naive decimation - near-Nyquist test at 9 kHz (rms < 0.05, ~26 dB) exercises transition band Core errors: - Other(String) removed; ProviderNotRegistered introduced - Io variant restructured as struct with kind/message/raw_os_error - FileNotFound display quotes paths - Configuration variant removed (unused) Core types: - ModelId, EngineName backed by Cow<'static, str>; const borrowed ctor - Megabytes::from_gb takes u64 (was f64) - AudioSamples::sample_rate is NonZeroU32; zero-rate defensive branch removed Capture: - /proc/asound/cards parsing rewritten as anchored regex (OnceLock) - regression test covers product names with embedded colons - monitor_pattern_detection test restored alongside the regex test - DEAD_SILENCE_FLOOR promoted to module-level with rationale - DEVICE_VALIDATION_MS, SILENCE_RMS_FLOOR documented with field-observation rationale - RMS validation loop made idiomatic - eprintln! migrated to tracing with structured fields and targets Tauri startup: - unsafe std::env::set_var removed; ensure_x11_on_wayland renamed to warn_if_x11_env_unset_on_wayland (launcher/wrapper owns env-var contract) - DB init + log prune + preferences load collapsed to one block_on - build_preferences_script rewrites JS injection from JSON.parse string to direct object literal plus malformed-JSON guard and unit tests - WebKitGTK microphone auto-grant logs warning at startup - tracing subscriber initialised at top of run() (warn,magnotia=info,... on stderr; honors RUST_LOG); previously eprintln→tracing migration was silent because no subscriber existed Filename counter: - RECORDING_COUNTER uses SeqCst Tests: cargo test --workspace --lib green (322 passed, 0 failed across 10 crates). Three independent audits (original cleanup → Wren → fresh Codex subagent) concur on no critical findings. Deferred to docs/superpowers/plans/2026-05-12-engine-slop-residuals.md: storage-layer typed errors, remaining eprintln→tracing sweep, capture actor-model refactor, property-based DSP testing, frontend/backend error boundary cleanup.
This commit is contained in:
@@ -52,6 +52,8 @@ tauri-plugin-notification = "2"
|
||||
# Serialisation
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
|
||||
# Async runtime (spawn_blocking for inference)
|
||||
tokio = { version = "1", features = ["rt", "sync"] }
|
||||
|
||||
@@ -60,7 +60,7 @@ fn recording_filename() -> String {
|
||||
.unwrap_or_default();
|
||||
let secs = duration.as_secs();
|
||||
let nanos = duration.subsec_nanos();
|
||||
let counter = RECORDING_COUNTER.fetch_add(1, Ordering::Relaxed);
|
||||
let counter = RECORDING_COUNTER.fetch_add(1, Ordering::SeqCst);
|
||||
format!("magnotia-{secs}-{nanos:09}-{counter:04}.wav")
|
||||
}
|
||||
|
||||
|
||||
@@ -25,14 +25,14 @@ fn whisper_model_id(size: &str) -> ModelId {
|
||||
"distil-large" | "distil-large-v3" | "distillarge" => {
|
||||
ModelId::new("whisper-distil-large-v3")
|
||||
}
|
||||
other => ModelId::new(other),
|
||||
other => ModelId::new(other.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
fn parakeet_model_id(name: &str) -> ModelId {
|
||||
match name {
|
||||
"ctc-int8" => ModelId::new("parakeet-ctc-0.6b-int8"),
|
||||
other => ModelId::new(other),
|
||||
other => ModelId::new(other.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ pub async fn ensure_model_loaded(
|
||||
model_id: &str,
|
||||
concurrent: Option<bool>,
|
||||
) -> Result<(), String> {
|
||||
let model_id = ModelId::new(model_id);
|
||||
let model_id = ModelId::new(model_id.to_string());
|
||||
let entry = model_registry::find_model(&model_id)
|
||||
.ok_or_else(|| format!("Unknown model: {model_id}"))?;
|
||||
|
||||
|
||||
@@ -5,10 +5,12 @@ mod commands;
|
||||
mod tray;
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::sync::Once;
|
||||
use std::time::Instant;
|
||||
|
||||
use sqlx::SqlitePool;
|
||||
use tauri::Manager;
|
||||
use tracing_subscriber::EnvFilter;
|
||||
|
||||
use magnotia_core::types::EngineName;
|
||||
use magnotia_llm::LlmEngine;
|
||||
@@ -20,6 +22,8 @@ use magnotia_storage::{database_path, get_setting, init as init_db, prune_error_
|
||||
const ERROR_LOG_RETENTION_DAYS: i64 = 90;
|
||||
use magnotia_transcription::LocalEngine;
|
||||
|
||||
static TRACING_INIT: Once = Once::new();
|
||||
|
||||
/// Shared app state holding the transcription engines and database pool.
|
||||
pub struct AppState {
|
||||
pub whisper_engine: Arc<LocalEngine>,
|
||||
@@ -38,12 +42,15 @@ fn build_preferences_script(prefs_json: Option<String>) -> String {
|
||||
if json.is_empty() {
|
||||
return String::new();
|
||||
}
|
||||
// Serialise the JSON string as a JS string literal for safe embedding
|
||||
let js_str = serde_json::to_string(&json).unwrap_or_else(|_| "\"\"".to_string());
|
||||
let js_value: serde_json::Value = match serde_json::from_str(&json) {
|
||||
Ok(value) => value,
|
||||
Err(_) => return String::new(),
|
||||
};
|
||||
let js_obj = serde_json::to_string(&js_value).unwrap_or_else(|_| "{}".to_string());
|
||||
format!(
|
||||
r#"(function() {{
|
||||
try {{
|
||||
var p = JSON.parse({js_str});
|
||||
var p = {js_obj};
|
||||
var el = document.documentElement;
|
||||
if (p.theme === 'system') {{
|
||||
el.dataset.theme = window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark';
|
||||
@@ -67,6 +74,23 @@ fn build_preferences_script(prefs_json: Option<String>) -> String {
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::build_preferences_script;
|
||||
|
||||
#[test]
|
||||
fn preferences_script_injects_object_without_redundant_json_parse() {
|
||||
let script = build_preferences_script(Some(r#"{"theme":"dark"}"#.to_string()));
|
||||
assert!(script.contains("var p = {\"theme\":\"dark\"};"));
|
||||
assert!(!script.contains("JSON.parse"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preferences_script_rejects_malformed_json() {
|
||||
assert!(build_preferences_script(Some("not json".to_string())).is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
/// Save preferences JSON to the SQLite settings table.
|
||||
#[tauri::command]
|
||||
async fn save_preferences(
|
||||
@@ -87,23 +111,25 @@ async fn save_preferences(
|
||||
/// WEBKIT_DISABLE_DMABUF_RENDERER=1 npm run tauri dev
|
||||
/// ```
|
||||
///
|
||||
/// Detect the Wayland session at startup and apply the env vars before
|
||||
/// anything else loads, so users do not need to remember the prefix and
|
||||
/// the dev launcher / packaged app both work out of the box.
|
||||
/// Detect the Wayland session at startup and warn when the process was not
|
||||
/// launched with the safe Linux rendering env vars. Rust 2024 marks runtime
|
||||
/// environment mutation unsafe in multi-threaded programs, so the app must be
|
||||
/// launched with these values by the desktop file, package wrapper, or dev
|
||||
/// shell rather than patching them here.
|
||||
///
|
||||
/// Inspired by Open-Whispr's similar Chromium self-relaunch (with
|
||||
/// --ozone-platform=x11). Day 6 of the upgrade plan.
|
||||
#[cfg(target_os = "linux")]
|
||||
fn ensure_x11_on_wayland() {
|
||||
let set_if_unset = |key: &str, value: &str, why: &str| {
|
||||
fn warn_if_x11_env_unset_on_wayland() {
|
||||
let warn_if_unset = |key: &str, value: &str, why: &str| {
|
||||
if std::env::var_os(key).is_none() {
|
||||
// SAFETY: setting env vars before any threads spawn (we are
|
||||
// pre-Tauri-Builder here). This block is the only place these
|
||||
// are written.
|
||||
unsafe {
|
||||
std::env::set_var(key, value);
|
||||
}
|
||||
eprintln!("[startup] Linux workaround: {key}={value} ({why})");
|
||||
tracing::warn!(
|
||||
target: "magnotia_startup",
|
||||
key,
|
||||
expected = value,
|
||||
why,
|
||||
"Linux rendering workaround env var is not set; configure the launcher instead of mutating process env at runtime"
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -115,7 +141,7 @@ fn ensure_x11_on_wayland() {
|
||||
// magnotia idle CPU 12.30% → 2.80% of one core and idle GPU 17% → 10%.
|
||||
// Apples to both X11 and Wayland sessions; users can opt back in by
|
||||
// setting WEBKIT_DISABLE_DMABUF_RENDERER=0 explicitly.
|
||||
set_if_unset(
|
||||
warn_if_unset(
|
||||
"WEBKIT_DISABLE_DMABUF_RENDERER",
|
||||
"1",
|
||||
"iGPU idle-cost workaround",
|
||||
@@ -128,15 +154,32 @@ fn ensure_x11_on_wayland() {
|
||||
// path.
|
||||
let session_type = std::env::var("XDG_SESSION_TYPE").unwrap_or_default();
|
||||
if session_type.eq_ignore_ascii_case("wayland") {
|
||||
set_if_unset("GDK_BACKEND", "x11", "Wayland XWayland fallback");
|
||||
set_if_unset("WINIT_UNIX_BACKEND", "x11", "Wayland XWayland fallback");
|
||||
warn_if_unset("GDK_BACKEND", "x11", "Wayland XWayland fallback");
|
||||
warn_if_unset("WINIT_UNIX_BACKEND", "x11", "Wayland XWayland fallback");
|
||||
}
|
||||
}
|
||||
|
||||
fn init_tracing() {
|
||||
TRACING_INIT.call_once(|| {
|
||||
let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| {
|
||||
EnvFilter::new(
|
||||
"warn,magnotia=info,magnotia_lib=info,magnotia_audio=info,magnotia_startup=info",
|
||||
)
|
||||
});
|
||||
|
||||
let _ = tracing_subscriber::fmt()
|
||||
.with_env_filter(env_filter)
|
||||
.with_writer(std::io::stderr)
|
||||
.try_init();
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
init_tracing();
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
ensure_x11_on_wayland();
|
||||
warn_if_x11_env_unset_on_wayland();
|
||||
|
||||
// Capture Rust panics to disk so the diagnostic-report bundler in
|
||||
// Settings → About can attach them. Local only; nothing transmitted.
|
||||
@@ -178,36 +221,39 @@ pub fn run() {
|
||||
|
||||
builder
|
||||
.setup(|app| {
|
||||
// Initialise database (blocking in setup — runs once at startup)
|
||||
// Initialise database and startup settings in one runtime entry.
|
||||
let db_path = database_path();
|
||||
let t0 = Instant::now();
|
||||
let db = tauri::async_runtime::block_on(async { init_db(&db_path).await })
|
||||
.map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
|
||||
eprintln!("[startup] DB init: {:?}", t0.elapsed());
|
||||
let (db, init_script) = tauri::async_runtime::block_on(async {
|
||||
let t0 = Instant::now();
|
||||
let db = init_db(&db_path)
|
||||
.await
|
||||
.map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
|
||||
tracing::info!(target: "magnotia_startup", elapsed_ms = t0.elapsed().as_millis(), "DB init complete");
|
||||
|
||||
// Prune old `error_log` rows so the table doesn't grow unbounded
|
||||
// across months of dogfooding. Best-effort — a prune failure is
|
||||
// not worth blocking startup over.
|
||||
let t_prune = Instant::now();
|
||||
let pruned = tauri::async_runtime::block_on(async {
|
||||
prune_error_log(&db, ERROR_LOG_RETENTION_DAYS).await
|
||||
});
|
||||
match pruned {
|
||||
Ok(n) if n > 0 => eprintln!(
|
||||
"[startup] Error log prune: {n} rows removed (>{ERROR_LOG_RETENTION_DAYS}d) in {:?}",
|
||||
t_prune.elapsed()
|
||||
),
|
||||
Ok(_) => {}
|
||||
Err(e) => eprintln!("[startup] Error log prune failed: {e}"),
|
||||
}
|
||||
// Prune old `error_log` rows so the table doesn't grow unbounded
|
||||
// across months of dogfooding. Best-effort — a prune failure is
|
||||
// not worth blocking startup over.
|
||||
let t_prune = Instant::now();
|
||||
match prune_error_log(&db, ERROR_LOG_RETENTION_DAYS).await {
|
||||
Ok(n) if n > 0 => tracing::info!(
|
||||
target: "magnotia_startup",
|
||||
rows_removed = n,
|
||||
retention_days = ERROR_LOG_RETENTION_DAYS,
|
||||
elapsed_ms = t_prune.elapsed().as_millis(),
|
||||
"error log prune complete"
|
||||
),
|
||||
Ok(_) => {}
|
||||
Err(e) => tracing::warn!(target: "magnotia_startup", error = %e, "error log prune failed"),
|
||||
}
|
||||
|
||||
// Load saved preferences for webview injection
|
||||
let t1 = Instant::now();
|
||||
let prefs_json = tauri::async_runtime::block_on(async {
|
||||
get_setting(&db, "magnotia_preferences").await.unwrap_or(None)
|
||||
});
|
||||
eprintln!("[startup] Preferences load: {:?}", t1.elapsed());
|
||||
let init_script = build_preferences_script(prefs_json);
|
||||
// Load saved preferences for webview injection.
|
||||
let t1 = Instant::now();
|
||||
let prefs_json = get_setting(&db, "magnotia_preferences").await.unwrap_or(None);
|
||||
tracing::info!(target: "magnotia_startup", elapsed_ms = t1.elapsed().as_millis(), "preferences load complete");
|
||||
let init_script = build_preferences_script(prefs_json);
|
||||
|
||||
Ok::<_, Box<dyn std::error::Error>>((db, init_script))
|
||||
})?;
|
||||
|
||||
// Apply preferences to the main window (defined in tauri.conf.json)
|
||||
if let Some(main_window) = app.get_webview_window("main") {
|
||||
@@ -240,6 +286,11 @@ pub fn run() {
|
||||
settings.set_enable_media_capabilities(true);
|
||||
}
|
||||
|
||||
tracing::warn!(
|
||||
target: "magnotia_startup",
|
||||
"Linux WebKitGTK microphone permission requests are auto-granted for audio-only capture; other permission classes remain denied"
|
||||
);
|
||||
|
||||
// Auto-grant microphone capture only. Other WebKitGTK
|
||||
// permission requests are denied so future surfaces do
|
||||
// not inherit camera/geolocation/pointer-lock access.
|
||||
@@ -270,8 +321,10 @@ pub fn run() {
|
||||
// Falling back means getUserMedia() prompts (or
|
||||
// silently denies) instead of auto-granting,
|
||||
// which is degraded but recoverable.
|
||||
eprintln!(
|
||||
"[startup] failed to configure webview media permissions: {e}",
|
||||
tracing::warn!(
|
||||
target: "magnotia_startup",
|
||||
error = %e,
|
||||
"failed to configure webview media permissions"
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -314,7 +367,7 @@ pub fn run() {
|
||||
|
||||
#[cfg(not(target_os = "android"))]
|
||||
if let Err(e) = tray::setup(app) {
|
||||
eprintln!("Failed to setup tray: {e}");
|
||||
tracing::warn!(target: "magnotia_startup", error = %e, "failed to setup tray");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
Reference in New Issue
Block a user