Files
Lumotia/docs/architecture-map/02-tauri-runtime/app-lifecycle.md
jars a1f3f3f134 docs: architecture map (initial 5-slice generation, 105 pages)
Five-slice navigable map of the entire codebase under
docs/architecture-map/. Each slice is a self-contained
breadcrumbed sub-tree:

  01-frontend (16)              Svelte/SvelteKit UI
  02-tauri-runtime (26)         src-tauri commands + lifecycle
  03-audio-transcription (16)   audio + transcription crates
  04-llm-formatting-mcp (19)    llm, ai-formatting, mcp, cloud
  05-core-storage-hotkey-build  core, storage, hotkey, workspace,
                          (26) CI, dev glue

Plus master README.md and data-flow-end-to-end.md tracing
audio bytes from microphone to FTS5 search to MCP read.

Generated by 5 parallel subagents on 2026/05/09 against
HEAD 3c47000. Each page has YAML frontmatter, file:line code
refs, sibling cross-links, plain-English summaries.

Aggregated debt surfaced (full lists in master README):
RB-08 macOS power assertion, schema head drift v14 vs v15,
VAD blocked on ort version conflict, streaming primitives
not wired into live.rs, no prompt versioning, MCP has no
auth, cloud-providers in-memory keystore, SettingsPage
2 484 LOC, commands/live.rs 1 737 LOC, dual theme system,
brand rename to Lumenote pending across the codebase.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:04:13 +01:00

8.9 KiB

name, type, slice, last_verified
name type slice last_verified
App lifecycle architecture-map-page 02-tauri-runtime 2026/05/09

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: sets a Linux Wayland workaround, 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) + 448 (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.
  • ensure_x11_on_wayland() (src-tauri/src/lib.rs:99, Linux only). Sets WEBKIT_DISABLE_DMABUF_RENDERER=1 unconditionally on Linux (iGPU idle-cost workaround), and additionally sets GDK_BACKEND=x11 plus WINIT_UNIX_BACKEND=x11 when XDG_SESSION_TYPE=wayland. Idempotent: if a value is already set, the function leaves it alone. Must run before any threads spawn — uses unsafe { std::env::set_var(...) }.
  • run() (src-tauri/src/lib.rs:135). The Tauri builder pipeline.

run() step-by-step

  1. Linux env-var prelude. Calls ensure_x11_on_wayland() on Linux (src-tauri/src/lib.rs:137).
  2. Panic hook. Calls commands::diagnostics::install_panic_hook() to dump panic info to crashes_dir() (src-tauri/src/lib.rs:141).
  3. Plugin wiring (always-on). tauri_plugin_opener, tauri_plugin_dialog, tauri_plugin_notification (src-tauri/src/lib.rs:144).
  4. Plugin wiring (desktop-only). tauri_plugin_global_shortcut, tauri_plugin_autostart (LaunchAgent on macOS), tauri_plugin_window_state (src-tauri/src/lib.rs:158).
  5. 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).
  6. 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).
  7. 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 [startup] failed to configure webview media permissions: ... to stderr.
  • The unsafe std::env::set_var calls in ensure_x11_on_wayland are sound only because nothing else in the binary has spawned a thread yet at that point. Do not introduce another startup-time env mutation outside this function unless the same invariant is preserved.
  • 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.