Files
Lumotia/docs/architecture-map/02-tauri-runtime/app-lifecycle.md
Jake 792fb5ea08 agent: dev launcher — own Linux env-var contract, add dev:tauri, doc sweep
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.
2026-05-12 22:05:33 +01:00

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 mapTauri 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 under commands::* and are registered via tauri::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 the commands::* and tray modules.
  • 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's tray-icon feature 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). Holds Arc<LocalEngine> for whisper and parakeet, the SqlitePool, and an Arc<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 via serde_json::to_string to keep it safe.
  • save_preferences(state, preferences) -> Result<(), String> (src-tauri/src/lib.rs:73). The single command in lib.rs. Persists the preferences blob to the SQLite settings table under key magnotia_preferences.
  • init_tracing() (Linux/macOS/Windows). Initialises the process-wide tracing subscriber once, honours RUST_LOG, and writes structured startup/runtime logs to stderr.
  • warn_if_x11_env_unset_on_wayland() (Linux only). Emits a magnotia_startup warning when the launcher has not pre-set WEBKIT_DISABLE_DMABUF_RENDERER (always expected on Linux), or GDK_BACKEND=x11 / WINIT_UNIX_BACKEND=x11 when XDG_SESSION_TYPE=wayland. It does not mutate the process environment; run.sh owns the dev-time contract and package wrappers/.desktop files must own the distribution-time contract.
  • run(). The Tauri builder pipeline.

run() step-by-step

  1. Tracing init. Calls init_tracing() before any startup warnings/logs are emitted.
  2. 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.
  3. Panic hook. Calls commands::diagnostics::install_panic_hook() to dump panic info to crashes_dir().
  4. Plugin wiring (always-on). tauri_plugin_opener, tauri_plugin_dialog, tauri_plugin_notification (src-tauri/src/lib.rs:144).
  5. Plugin wiring (desktop-only). tauri_plugin_global_shortcut, tauri_plugin_autostart (LaunchAgent on macOS), tauri_plugin_window_state (src-tauri/src/lib.rs:158).
  6. Setup hook. This is where the bulk of startup work lives:
    • Initialise SQLite via magnotia_storage::init(&database_path()).await using tauri::async_runtime::block_on (src-tauri/src/lib.rs:180). The Instant::now() timing is logged.
    • Prune error_log rows 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 webkit2gtk permission requests: enable media_stream and media_capabilities settings; 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 makes getUserMedia work 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::CloseRequested and call window.hide() instead of letting the platform exit (src-tauri/src/lib.rs:281).
    • Stash the PreferencesScript and the per-domain managed states (HotkeyState, NativeCaptureState, LiveTranscriptionState, TtsState, MeetingState) (src-tauri/src/lib.rs:294).
    • Build the AppState itself: fresh LocalEngines for whisper and parakeet, the open SqlitePool, a fresh LlmEngine (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).
  7. 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).
  8. Run. .run(tauri::generate_context!()) blocks the main thread until the app exits. Panics are wrapped with expect("error while running Magnotia").

Data flow

  • Frontend bootstrap: the webview is built per tauri.conf.json window config. Tauri's eval() 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 by Arc semantics into every state.db borrow.
  • Engine state: the two transcription LocalEngines and the LlmEngine are shared Arcs; commands clone them and run inference inside tokio::task::spawn_blocking so the async runtime stays responsive.
  • Per-domain state: HotkeyState, NativeCaptureState, LiveTranscriptionState, TtsState, MeetingState are all stashed via app.manage(...) and retrieved by their command files via tauri::State<'_, T>.

Watch-outs

  • tauri::async_runtime::block_on inside setup blocks 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_webview fails the app still boots, but getUserMedia will be silently denied or fall back to a prompt the user cannot answer (no UI). The error path logs a magnotia_startup warning.
  • 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 .desktop Exec=env policy. WEBKIT_DISABLE_DMABUF_RENDERER=0 remains 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_model call is not wired here. commands::models::prewarm_default_model exists, but setup does not invoke it. The frontend invokes the matching prewarm_default_model_cmd command after the main page mounts. If you ever want to shift pre-warm into setup, watch the spawn_blocking ordering against the engine Arc clones.

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.