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.
9.3 KiB
name, type, slice, last_verified
| name | type | slice | last_verified |
|---|---|---|---|
| App lifecycle | architecture-map-page | 02-tauri-runtime | 2026/05/12 |
App lifecycle
Where you are: Architecture map → Tauri runtime → App lifecycle
Plain English summary. This is the entry point. main.rs calls magnotia_lib::run() and lib.rs::run does everything that has to happen before the user sees a window: initialises tracing, checks the Linux launcher env-var contract, installs the Rust panic hook, registers Tauri plugins, opens the SQLite database, prunes the error log, builds a JS preferences-injection script, configures the WebKit media-permission grant on Linux, wires close-to-tray, populates AppState and the per-domain managed states, emits any runtime warnings, sets up the system tray, and finally registers all 71 Tauri commands.
At a glance
- Path:
src-tauri/src/main.rs,src-tauri/src/lib.rs. - LOC: 5 (main) + 491 (lib).
- Tauri commands exposed directly here:
save_preferences(string preferences -> SQLite settings table). All other commands live undercommands::*and are registered viatauri::generate_handler!. - Events emitted directly here: none (runtime warnings are emitted by
commands::models::emit_runtime_warnings, called from setup). - Depends on:
tauri,sqlx::SqlitePool,magnotia_core::types::EngineName,magnotia_llm::LlmEngine,magnotia_storage::{init, database_path, get_setting, set_setting, prune_error_log},magnotia_transcription::LocalEngine, plus thecommands::*andtraymodules. - Called from frontend at: every
invoke()site in slice 01 lands in the handler list registered here.
What's in here
main.rs
Single function. Sets windows_subsystem = "windows" for release builds (no console window) and calls magnotia_lib::run(). (src-tauri/src/main.rs:1).
lib.rs
Module declarations (src-tauri/src/lib.rs:1):
mod commands;#[cfg(not(target_os = "android"))] mod tray;— tray uses Tauri'stray-iconfeature which is desktop-only.
Constants:
ERROR_LOG_RETENTION_DAYS: i64 = 90(src-tauri/src/lib.rs:22). Used by the startup prune.
Types managed in Tauri state:
AppState(src-tauri/src/lib.rs:26). HoldsArc<LocalEngine>for whisper and parakeet, theSqlitePool, and anArc<LlmEngine>. This is the central state that almost every command queries.PreferencesScript(pub String)(src-tauri/src/lib.rs:34). Cached preferences-injection JS used when secondary windows are built (so they do not flash unstyled before Svelte mounts).
Functions:
build_preferences_script(prefs_json: Option<String>) -> String(src-tauri/src/lib.rs:38). Builds an IIFE that reads saved preferences (theme, zone, accessibility settings: font family, font size, letter spacing, line height, transcript size, bionic reading, reduce motion) and applies them to<html>before the rest of the document loads. Embeds the JSON viaserde_json::to_stringto keep it safe.save_preferences(state, preferences) -> Result<(), String>(src-tauri/src/lib.rs:73). The single command inlib.rs. Persists the preferences blob to the SQLite settings table under keymagnotia_preferences.init_tracing()(Linux/macOS/Windows). Initialises the process-wide tracing subscriber once, honoursRUST_LOG, and writes structured startup/runtime logs to stderr.warn_if_x11_env_unset_on_wayland()(Linux only). Emits amagnotia_startupwarning when the launcher has not pre-setWEBKIT_DISABLE_DMABUF_RENDERER(always expected on Linux), orGDK_BACKEND=x11/WINIT_UNIX_BACKEND=x11whenXDG_SESSION_TYPE=wayland. It does not mutate the process environment;run.showns the dev-time contract and package wrappers/.desktop files must own the distribution-time contract.run(). The Tauri builder pipeline.
run() step-by-step
- Tracing init. Calls
init_tracing()before any startup warnings/logs are emitted. - Linux launcher contract check. Calls
warn_if_x11_env_unset_on_wayland()on Linux. Missing env vars produce warnings only; runtime env-var mutation was removed because Rust 2024 treats environment mutation in multi-threaded programs as unsafe. - Panic hook. Calls
commands::diagnostics::install_panic_hook()to dump panic info tocrashes_dir(). - Plugin wiring (always-on).
tauri_plugin_opener,tauri_plugin_dialog,tauri_plugin_notification(src-tauri/src/lib.rs:144). - Plugin wiring (desktop-only).
tauri_plugin_global_shortcut,tauri_plugin_autostart(LaunchAgent on macOS),tauri_plugin_window_state(src-tauri/src/lib.rs:158). - Setup hook. This is where the bulk of startup work lives:
- Initialise SQLite via
magnotia_storage::init(&database_path()).awaitusingtauri::async_runtime::block_on(src-tauri/src/lib.rs:180). TheInstant::now()timing is logged. - Prune
error_logrows older than 90 days (src-tauri/src/lib.rs:189). Best-effort: a failure logs but does not block startup. - Load saved preferences from the settings table; build the JS injection script (
src-tauri/src/lib.rs:204). - Apply the injection script to the main window via
WebviewWindow.eval()(src-tauri/src/lib.rs:215). - On Linux, configure
webkit2gtkpermission requests: enablemedia_streamandmedia_capabilitiessettings; auto-grant audio capture but deny everything else (camera, geolocation, pointer lock, etc.) (src-tauri/src/lib.rs:222). This is the critical piece that makesgetUserMediawork on Linux without a permission dialog (because WebKitGTK has no dialog, it just silently denies by default). - Wire close-to-tray on desktop: intercept
WindowEvent::CloseRequestedand callwindow.hide()instead of letting the platform exit (src-tauri/src/lib.rs:281). - Stash the
PreferencesScriptand the per-domain managed states (HotkeyState,NativeCaptureState,LiveTranscriptionState,TtsState,MeetingState) (src-tauri/src/lib.rs:294). - Build the
AppStateitself: freshLocalEngines for whisper and parakeet, the openSqlitePool, a freshLlmEngine(src-tauri/src/lib.rs:302). - Emit runtime warnings (CPU baseline, Vulkan loader) via
commands::models::emit_runtime_warnings(src-tauri/src/lib.rs:312). - Setup the system tray on desktop (
src-tauri/src/lib.rs:314).
- Initialise SQLite via
- Command registration.
tauri::generate_handler![...]lists 71 commands (src-tauri/src/lib.rs:321). The order in the macro is grouped by domain (preferences, models, LLM, transcription, audio, tasks, feedback, TTS, rituals, nudges, intentions, profiles, transcripts, diagnostics, live, windows, clipboard, fs, paste, meeting, hardware, hotkey, updater). - Run.
.run(tauri::generate_context!())blocks the main thread until the app exits. Panics are wrapped withexpect("error while running Magnotia").
Data flow
- Frontend bootstrap: the webview is built per
tauri.conf.jsonwindow config. Tauri'seval()hook fires the preferences script before the SvelteKit bundle parses, so the user never sees a flash of unstyled content. - Database: opened once, owned by
AppState, cloned byArcsemantics into everystate.dbborrow. - Engine state: the two transcription
LocalEngines and theLlmEngineare sharedArcs; commands clone them and run inference insidetokio::task::spawn_blockingso the async runtime stays responsive. - Per-domain state:
HotkeyState,NativeCaptureState,LiveTranscriptionState,TtsState,MeetingStateare all stashed viaapp.manage(...)and retrieved by their command files viatauri::State<'_, T>.
Watch-outs
tauri::async_runtime::block_oninsidesetupblocks startup. The DB init and prefs read are explicitly timed and logged so regressions show up. Adding more synchronous async work here directly pushes the time-to-first-paint up.- The Linux media-permission wire-up is non-fatal: if
with_webviewfails the app still boots, butgetUserMediawill be silently denied or fall back to a prompt the user cannot answer (no UI). The error path logs amagnotia_startupwarning. - Linux rendering env vars are a launcher contract, not a runtime mutation. In development, use
npm run dev:tauri/./run.sh; packaged Linux builds need an equivalent wrapper or.desktopExec=envpolicy.WEBKIT_DISABLE_DMABUF_RENDERER=0remains the user opt-out for the DMA-BUF workaround. - Close-to-tray works only on desktop (the
cfg!(not(target_os = "android"))block). On Android, closing the activity terminates the process, which is the expected platform behaviour. - The
prewarm_default_modelcall is not wired here.commands::models::prewarm_default_modelexists, butsetupdoes not invoke it. The frontend invokes the matchingprewarm_default_model_cmdcommand after the main page mounts. If you ever want to shift pre-warm into setup, watch the spawn_blocking ordering against the engineArcclones.
See also
- Commands index — every command registered by
lib.rs::run. - System tray — what
tray::setup(app)builds. - Tauri config — the window config that drives the
get_webview_window("main")retrieval. - Capabilities and ACL — the permission set that decides which commands each window can call.
- Cargo and features — the dependency block that determines which plugins compile in.