diff --git a/README.md b/README.md index 596bd7c..6478fbe 100644 --- a/README.md +++ b/README.md @@ -271,20 +271,22 @@ See [`docs/dev-setup.md`](docs/dev-setup.md) for the authoritative per-platform ### Dev launch -The fast path — starts Vite, waits for port 1420, then launches Tauri: +Canonical full-stack dev launch — starts Vite, waits for port 1420, then launches Tauri: + +```bash +npm run dev:tauri +``` + +Direct shell equivalent: ```bash ./run.sh ``` -Or manually: +For pure frontend iteration without Tauri: ```bash -# Terminal 1 npm run dev:frontend - -# Terminal 2 -npm run tauri dev ``` ### Build diff --git a/docs/architecture-map/02-tauri-runtime/README.md b/docs/architecture-map/02-tauri-runtime/README.md index 6b0975c..561c253 100644 --- a/docs/architecture-map/02-tauri-runtime/README.md +++ b/docs/architecture-map/02-tauri-runtime/README.md @@ -2,7 +2,7 @@ name: Slice 02 — Tauri runtime type: architecture-map-page slice: 02-tauri-runtime -last_verified: 2026/05/09 +last_verified: 2026/05/12 --- # Slice 02 — Tauri runtime @@ -29,7 +29,7 @@ last_verified: 2026/05/09 App boot, config, tests: -- [App lifecycle](app-lifecycle.md). `src-tauri/src/main.rs` and `src-tauri/src/lib.rs`. Run entry, AppState construction, plugin wiring, preferences injection, X11-on-Wayland workaround, panic hook, error-log pruning, command registration. +- [App lifecycle](app-lifecycle.md). `src-tauri/src/main.rs` and `src-tauri/src/lib.rs`. Run entry, AppState construction, plugin wiring, preferences injection, Linux launcher-env warning, panic hook, error-log pruning, command registration. - [System tray](system-tray.md). Desktop-only tray icon and menu (`src-tauri/src/tray.rs`). - [Tauri config](tauri-config.md). `tauri.conf.json` plus the Linux native-decorations overlay; CSP, window defaults, bundle settings. - [Capabilities and ACL](capabilities-and-acl.md). The two ACL files in `capabilities/`, what each scopes, and the Phase 9 high-risk-permission firewall. @@ -57,7 +57,7 @@ Individual command pages (linked from the commands index): - `commands::update::install_update` returns a hard-coded "Updates are disabled until release signing is configured." (`src-tauri/src/commands/update.rs:15`). The integration test `updater_is_signed_or_absent` (`src-tauri/tests/config_hardening.rs:35`) only asserts that *if* an updater config ships, it carries a non-empty pubkey. There is currently no updater config in `tauri.conf.json`, so the test is an empty no-op. - `commands::power::PowerAssertion` is a no-op on Linux and Windows (`src-tauri/src/commands/power.rs:90`). Only macOS has a real implementation via `objc2`. The doc comment promises `SetThreadExecutionState` (Windows) and logind inhibitors (Linux), but neither is wired up. Symptom: long live-dictation sessions on Linux can be idled by the compositor. - `src-tauri/.cargo/config.toml` hard-codes `LIBCLANG_PATH = "C:\\Program Files\\LLVM\\bin"` (Windows). It is harmless on non-Windows hosts (env var is just unused) but it is a foot-gun if someone moves Clang elsewhere on Windows. See [Cargo and features](cargo-and-features.md). -- The Linux Wayland workaround in `lib.rs` (`ensure_x11_on_wayland`) sets env vars before any threads spawn. This is the right pattern, but it ships even when the user is on a working DMA-BUF stack. The override knob is `WEBKIT_DISABLE_DMABUF_RENDERER=0` set by the user; documented in the function header but not surfaced anywhere user-visible. +- The Linux rendering workaround in `lib.rs` (`warn_if_x11_env_unset_on_wayland`) now warns when the launcher has not set the expected env vars; it no longer mutates the process environment. In development, `run.sh` / `npm run dev:tauri` owns the defaults (`WEBKIT_DISABLE_DMABUF_RENDERER=1` on Linux; `GDK_BACKEND=x11` and `WINIT_UNIX_BACKEND=x11` on Wayland). The user opt-out remains `WEBKIT_DISABLE_DMABUF_RENDERER=0`, set before launching. - `commands::diagnostics::install_panic_hook` writes a "minimal text dump" without backtraces unless the user has `RUST_BACKTRACE=1` set (`src-tauri/src/commands/diagnostics.rs:42`). The packaged binary does not set it, so production crash dumps will lack stack traces. Document or default-enable. - `commands::audio::list_audio_devices` is gated `ensure_main_window` but the `secondary-windows` capability does not invoke it, which is correct. The `clipboard::copy_to_clipboard` command, by contrast, has no main-window guard and any window with the `core:default` permission can call it; intentional, but worth flagging. diff --git a/docs/architecture-map/02-tauri-runtime/app-lifecycle.md b/docs/architecture-map/02-tauri-runtime/app-lifecycle.md index f7f49be..30a2cae 100644 --- a/docs/architecture-map/02-tauri-runtime/app-lifecycle.md +++ b/docs/architecture-map/02-tauri-runtime/app-lifecycle.md @@ -2,19 +2,19 @@ name: App lifecycle type: architecture-map-page slice: 02-tauri-runtime -last_verified: 2026/05/09 +last_verified: 2026/05/12 --- # App lifecycle > **Where you are:** [Architecture map](../README.md) → [Tauri runtime](README.md) → 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. +**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) + 448 (lib). +- 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. @@ -46,16 +46,18 @@ Functions: - `build_preferences_script(prefs_json: Option) -> 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 `` 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. +- `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. **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: +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`). @@ -66,8 +68,8 @@ Functions: - Build the `AppState` itself: fresh `LocalEngine`s 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")`. +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 @@ -79,8 +81,8 @@ Functions: ## 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. +- 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. diff --git a/docs/architecture-map/05-core-storage-hotkey-build/dev-launcher-and-scripts.md b/docs/architecture-map/05-core-storage-hotkey-build/dev-launcher-and-scripts.md index e99515b..0c755c9 100644 --- a/docs/architecture-map/05-core-storage-hotkey-build/dev-launcher-and-scripts.md +++ b/docs/architecture-map/05-core-storage-hotkey-build/dev-launcher-and-scripts.md @@ -2,65 +2,107 @@ name: Dev launcher and scripts type: architecture-map-page slice: 05-core-storage-hotkey-build -last_verified: 2026/05/09 +last_verified: 2026/05/12 --- # Dev launcher and scripts > **Where you are:** [Architecture map](../README.md) → [Core, Storage, Hotkey, Build](README.md) → Dev launcher and scripts -**Plain English summary.** Two ways to start Magnotia in development. `run.sh` is the bespoke launcher that starts Vite first and then Tauri (avoiding a Tauri-spawned-second-Vite mess). The `package.json` scripts are the canonical npm-run interface — the same scripts CI uses for the frontend gate. +**Plain English summary.** `run.sh` is the canonical full-stack development launcher and is exposed through `npm run dev:tauri`. It starts Vite first, waits for it to bind port 1420, then runs Tauri with Tauri's own `beforeDevCommand` disabled so a second Vite instance does not race the first one. On Linux it also owns the rendering env-var contract that `src-tauri/src/lib.rs::warn_if_x11_env_unset_on_wayland` checks. ## At a glance -- Files: `run.sh` (636 bytes, +x), `package.json` (1,211 bytes), `package-lock.json` (109 KB). -- External tools: `npm`, `npx`, `curl`, `bash` (run.sh); the Node toolchain (package.json scripts). -- Consumers: `run.sh` is invoked by Jake on his Monolith. The `package.json` scripts are invoked by both CI (`npm run build`) and developers (`npm run dev`). +- Files: `run.sh` (+x), `package.json`, `package-lock.json`. +- External tools: `npm`, `npx`, `curl`, `bash` (`run.sh`); the Node toolchain (`package.json` scripts). +- Consumers: developers use `npm run dev:tauri` (canonical) or `./run.sh` (direct shell equivalent). CI uses package scripts such as `npm run build` and `npm run check`. -## `run.sh` — the local launcher +## `run.sh` — the full-stack dev launcher ```bash #!/usr/bin/env bash # Magnotia dev launcher. Starts Vite first, waits for it to bind port 1420, # then runs Tauri without triggering a second Vite instance. +# Sets the Linux rendering env vars that warn_if_x11_env_unset_on_wayland +# (src-tauri/src/lib.rs) expects the launcher to own. # For performance testing use: npm run tauri build set -euo pipefail cd "$(dirname "$0")" -export LIBCLANG_PATH=/usr/lib64/llvm21/lib64 +case "$(uname -s)" in + Linux) + # Bindgen libclang path. Fedora 41/LLVM 21 default; user-set wins. + export LIBCLANG_PATH="${LIBCLANG_PATH:-/usr/lib64/llvm21/lib64}" + # iGPU idle-cost workaround. Set to 0 explicitly to opt out. + 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 + ;; +esac printf 'Starting Vite dev server...\n' >&2 npm run dev:frontend & VITE_PID=$! -until curl -sf http://localhost:1420 > /dev/null 2>&1; do sleep 0.5; done -printf 'Vite ready. Launching Tauri...\n' >&2 - cleanup() { kill "$VITE_PID" 2>/dev/null || true } trap cleanup EXIT INT TERM -npx tauri dev --config '{"build":{"beforeDevCommand":""}}' +# Wait up to 60s for Vite, bailing if it exits early. +for _ in $(seq 1 120); do + if curl -sf http://localhost:1420 > /dev/null 2>&1; then + break + fi + if ! kill -0 "$VITE_PID" 2>/dev/null; then + printf 'Vite dev server exited before becoming ready.\n' >&2 + wait "$VITE_PID" + exit 1 + fi + sleep 0.5 +done + +if ! curl -sf http://localhost:1420 > /dev/null 2>&1; then + printf 'Timed out waiting for Vite on http://localhost:1420\n' >&2 + exit 1 +fi + +printf 'Vite ready. Launching Tauri...\n' >&2 +npx tauri dev --config '{"build":{"beforeDevCommand":""}}' "$@" ``` ### Why this exists -The default `npm run tauri dev` invokes `tauri dev`, which honours `beforeDevCommand` from `tauri.conf.json` and spawns its own Vite. If Vite is already running (eg during interactive iteration), you get two instances racing for port 1420. The launcher solves it by starting Vite explicitly and disabling Tauri's spawn via `--config '{"build":{"beforeDevCommand":""}}'`. +The default `tauri dev` path honours `beforeDevCommand` from `tauri.conf.json` and spawns Vite itself. That is convenient for the simple path, but during interactive iteration it can create a second Vite instance racing for port 1420. The launcher solves this by starting Vite explicitly and disabling Tauri's spawn via `--config '{"build":{"beforeDevCommand":""}}'`. + +The launcher is also the dev-time owner of Linux rendering defaults. Rust startup no longer calls `std::env::set_var`; it only warns when the launcher contract was missed. ### Steps 1. `set -euo pipefail` — early exit on any command failure or unset variable. 2. `cd "$(dirname "$0")"` — anchor to the repo root. -3. **Hard-codes `LIBCLANG_PATH=/usr/lib64/llvm21/lib64`** — Fedora 41 default LLVM 21 path. **Breaks on Debian / Ubuntu / macOS.** Worth softening to `LIBCLANG_PATH=${LIBCLANG_PATH:-/usr/lib64/llvm21/lib64}` so a developer-set value wins. -4. Spawn `npm run dev:frontend &`; capture PID in `VITE_PID`. -5. Poll `curl -sf http://localhost:1420` every 500 ms until 200 OK. -6. Trap `EXIT INT TERM` to kill the Vite process group on script exit. -7. `npx tauri dev` with `beforeDevCommand` disabled. +3. On Linux, default `LIBCLANG_PATH` to Fedora's LLVM path only if the user has not set it already. +4. On Linux, default `WEBKIT_DISABLE_DMABUF_RENDERER=1`; users can opt out with `WEBKIT_DISABLE_DMABUF_RENDERER=0 ./run.sh`. +5. On Wayland sessions, default `GDK_BACKEND=x11` and `WINIT_UNIX_BACKEND=x11`; user-set values win. +6. Spawn `npm run dev:frontend &`; capture its PID. +7. Install a trap that kills the spawned `npm run dev:frontend` process on launcher exit/interrupt. +8. Poll `curl -sf http://localhost:1420` every 500 ms for up to 60 seconds; if Vite exits early or never becomes ready, fail instead of hanging forever. +9. Run `npx tauri dev` with `beforeDevCommand` disabled and forward any extra args (`./run.sh --release`). + +### Linux rendering env-var contract + +`src-tauri/src/lib.rs::warn_if_x11_env_unset_on_wayland` is a safety net. It warns when the process starts without env vars that must be set before WebKitGTK/WINIT initialise: + +- `WEBKIT_DISABLE_DMABUF_RENDERER=1` — Linux-wide iGPU idle-cost workaround. Set `WEBKIT_DISABLE_DMABUF_RENDERER=0` before launching to opt out. +- `GDK_BACKEND=x11` and `WINIT_UNIX_BACKEND=x11` — only expected when `XDG_SESSION_TYPE=wayland`. + +Development uses `run.sh` for that contract. Packaged Linux builds need an equivalent wrapper or `.desktop` `Exec=env` policy; see the residual packaging plan. ### `LIBCLANG_PATH` rationale -Bindgen (pulled by `whisper-rs-sys` and `llama-cpp-sys-2`) needs a `libclang.so` at build time. On Fedora's LLVM 21 packaging, the canonical location is `/usr/lib64/llvm21/lib64`. On Debian-family the path is `/usr/lib/llvm-N/lib`; on macOS Homebrew it's `$(brew --prefix llvm)/lib`. The hard-coded value means a clean `git clone` on a non-Fedora machine fails the first build until the developer sets `LIBCLANG_PATH` themselves. +Bindgen (pulled by `whisper-rs-sys` and `llama-cpp-sys-2`) needs a `libclang.so` at build time. On Fedora's LLVM 21 packaging, the canonical location is `/usr/lib64/llvm21/lib64`. On Debian-family the path is `/usr/lib/llvm-N/lib`; on macOS Homebrew it's `$(brew --prefix llvm)/lib`. `run.sh` now uses `LIBCLANG_PATH=${LIBCLANG_PATH:-/usr/lib64/llvm21/lib64}` on Linux so Fedora works out of the box while developer-set values win. ## `package.json` — npm scripts and deps @@ -70,6 +112,7 @@ Bindgen (pulled by `whisper-rs-sys` and `llama-cpp-sys-2`) needs a `libclang.so` "scripts": { "dev": "npm run dev:frontend", "dev:frontend": "svelte-kit sync && vite dev", + "dev:tauri": "./run.sh", "build": "vite build", "preview": "vite preview", "check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json", @@ -79,9 +122,10 @@ Bindgen (pulled by `whisper-rs-sys` and `llama-cpp-sys-2`) needs a `libclang.so` ``` - `npm run dev` — frontend only (no Tauri). Useful for pure-UI iteration. +- `npm run dev:tauri` — canonical full-stack dev launcher; calls `./run.sh`. - `npm run build` — Vite production build. CI gate. - `npm run check` — `svelte-check` against `jsconfig.json`. CI gate. -- `npm run tauri` — passthrough. `npm run tauri dev`, `npm run tauri build`, etc. +- `npm run tauri` — passthrough for non-dev Tauri commands such as `npm run tauri build`. ### Runtime dependencies @@ -104,8 +148,7 @@ Bindgen (pulled by `whisper-rs-sys` and `llama-cpp-sys-2`) needs a `libclang.so` ## Watch-outs -- **`run.sh` `LIBCLANG_PATH` is Fedora-specific.** A new developer on Ubuntu / macOS hits a build failure on first run and has to either edit `run.sh` or pre-set the env var. Worth the conditional default. -- **No `dev:fullstack` script.** The Tauri dev path goes through `npx tauri dev` (or `run.sh`), not through an npm script. A `npm run dev:fullstack` shortcut would make discovery easier. +- **The trap kills the spawned npm process, not a full process group.** If Vite-orphan leaks become a real dev annoyance, switch to starting a process group (`setsid`) and killing `-- -$VITE_PID` on Linux. - **Tailwind v4 is in flight.** Major version, no PostCSS config. Cross-link to slice 1 for the consumed surface. - **`@tauri-apps/api` is pinned to `2.10.1` (no caret).** Intentional because Tauri's JS bridge can shift behaviour subtly between minors. Pinned ensures we test what we ship. - **Plugin versions are caret-ranged.** Compatible-change updates land transparently. `npm audit` (CI) catches advisories. diff --git a/docs/architecture-map/05-core-storage-hotkey-build/workspace-cargo.md b/docs/architecture-map/05-core-storage-hotkey-build/workspace-cargo.md index 3cfbb22..6ca08cf 100644 --- a/docs/architecture-map/05-core-storage-hotkey-build/workspace-cargo.md +++ b/docs/architecture-map/05-core-storage-hotkey-build/workspace-cargo.md @@ -78,5 +78,5 @@ The `panic = "abort"` setting is a deliberate trade-off: ## See also - [CI pipeline](ci-pipeline.md) — uses this profile for release builds. -- [Dev launcher and scripts](dev-launcher-and-scripts.md) — `npm run tauri dev` uses the default debug profile. +- [Dev launcher and scripts](dev-launcher-and-scripts.md) — `npm run dev:tauri` uses the default debug profile via the canonical `run.sh` launcher. - [Storage Cargo configuration](storage-overview.md) — the per-crate sqlx feature stripping. diff --git a/docs/dev-setup.md b/docs/dev-setup.md index b566939..663dfe0 100644 --- a/docs/dev-setup.md +++ b/docs/dev-setup.md @@ -7,7 +7,7 @@ description: Authoritative build dependencies and launch instructions for Magnot # Magnotia — Developer Setup -Last updated: 2026/04/18. Primary dev target: Fedora 43, x86_64, KDE Wayland, NVIDIA RTX 4070. +Last updated: 2026/05/12. Primary dev target: Fedora Linux, x86_64, KDE Wayland, NVIDIA RTX 4070. --- @@ -24,16 +24,18 @@ sudo dnf install cmake clang-devel | `cmake` | whisper-rs-sys build system | | `clang-devel` | bindgen header generation for whisper-rs-sys | -**Fedora-specific:** `libclang.so` lives in `/usr/lib64/llvm21/lib64/`, not on the standard search path. Set permanently: +**Fedora-specific:** `libclang.so` lives in `/usr/lib64/llvm21/lib64/`, not on the standard search path. The dev launcher defaults `LIBCLANG_PATH` to that path on Linux, but a value you set in your shell wins. + +To set it permanently in fish: ```bash set -Ux LIBCLANG_PATH /usr/lib64/llvm21/lib64 ``` -Or prefix every build command: +Or prefix one command if you are bypassing the launcher: ```bash -LIBCLANG_PATH=/usr/lib64/llvm21/lib64 npm run tauri dev +LIBCLANG_PATH=/usr/lib64/llvm21/lib64 cargo check -p magnotia ``` ### Required (Vulkan GPU build) @@ -66,15 +68,34 @@ Rust toolchain managed by `rustup`. No extra steps needed beyond what Tauri requ ### CPU build (default) +Canonical dev launch: + ```bash -cd /home/jake/Documents/CORBEL-Projects/magnotia -LIBCLANG_PATH=/usr/lib64/llvm21/lib64 npm run tauri dev +cd /home/jake/Documents/CORBEL-Projects/transcription-app +npm run dev:tauri ``` -Once `set -Ux LIBCLANG_PATH` is in fish config, this becomes: +Direct shell equivalent: ```bash -npm run tauri dev +./run.sh +``` + +`run.sh` starts Vite, waits for port 1420, then launches Tauri with its `beforeDevCommand` disabled so a second Vite instance does not race the first one. + +On Linux, `run.sh` also owns the rendering environment contract expected by `src-tauri/src/lib.rs::warn_if_x11_env_unset_on_wayland`: + +- `WEBKIT_DISABLE_DMABUF_RENDERER=1` by default on Linux. Set `WEBKIT_DISABLE_DMABUF_RENDERER=0` explicitly to opt out. +- `GDK_BACKEND=x11` and `WINIT_UNIX_BACKEND=x11` by default only when `XDG_SESSION_TYPE=wayland`. +- Any value already set in your shell wins because the launcher uses shell defaults (`${VAR:-default}`). + +If you prefer shell-rc setup, these manual exports are equivalent on a Wayland session: + +```bash +export WEBKIT_DISABLE_DMABUF_RENDERER="${WEBKIT_DISABLE_DMABUF_RENDERER:-1}" +export GDK_BACKEND="${GDK_BACKEND:-x11}" +export WINIT_UNIX_BACKEND="${WINIT_UNIX_BACKEND:-x11}" +npm run dev:tauri ``` ### Vulkan GPU build @@ -97,16 +118,21 @@ whisper_backend_init_gpu: device 0: CPU (type: 0) ← no GPU ## Startup log reference -Normal startup sequence: +Normal Rust startup logs now come through `tracing` rather than raw `eprintln!`, so the exact timestamp/format depends on `RUST_LOG` and the subscriber formatter. Typical messages include: ``` -[startup] Wayland workaround: GDK_BACKEND=x11 -[startup] DB init: ~4ms -[startup] Preferences load: ~200µs -[startup] Whisper model pre-warmed successfully +INFO magnotia_startup: DB init complete elapsed_ms=4 +INFO magnotia_startup: preferences load complete elapsed_ms=0 +WARN magnotia_startup: Linux WebKitGTK microphone permission requests are auto-granted for audio-only capture; other permission classes remain denied ``` -The Wayland workarounds are injected automatically by `ensure_x11_on_wayland()` in `src-tauri/src/lib.rs` — no manual env-var prefix needed. +When launched through `npm run dev:tauri` / `./run.sh`, you should not see `magnotia_startup` warnings containing: + +``` +Linux rendering workaround env var is not set +``` + +Those warnings mean the launcher contract was bypassed or broken. --- @@ -114,7 +140,7 @@ The Wayland workarounds are injected automatically by `ensure_x11_on_wayland()` | Issue | Cause | Fix | |---|---|---| -| `Unable to find libclang` | Fedora puts clang libs in versioned path | `set -Ux LIBCLANG_PATH /usr/lib64/llvm21/lib64` | +| `Unable to find libclang` | Fedora puts clang libs in versioned path | Use `npm run dev:tauri` / `./run.sh`, or set `LIBCLANG_PATH=/usr/lib64/llvm21/lib64` | | `Could NOT find Vulkan (missing: glslc)` | Shader compiler not installed | `sudo dnf install vulkan-headers vulkan-loader-devel glslc` | | `there is no reactor running` | `tokio::spawn` called before runtime starts in `setup()` | Use `tauri::async_runtime::spawn` instead | | `effect_update_depth_exceeded` | Svelte 5 `$state` object reassigned instead of mutated | Use `Object.assign(state, updates)` — never spread-replace module-level state | diff --git a/docs/gpu-tuning/plan.md b/docs/gpu-tuning/plan.md index cd8c5f8..f85d007 100644 --- a/docs/gpu-tuning/plan.md +++ b/docs/gpu-tuning/plan.md @@ -43,7 +43,7 @@ Metal / CUDA counterparts slot in when those backends grow in Magnotia. Today Ma - New `SettingsState.gpuTuning: { disableCoopmat: boolean, forceFp32: boolean, disableF16: boolean, disableIntegerDotProduct: boolean, enableValidation: boolean }` in [src/lib/types/app.ts](../../src/lib/types/app.ts) - All defaults `false` in [src/lib/stores/page.svelte.ts](../../src/lib/stores/page.svelte.ts) - Persistence uses the existing `save_preferences` → SQLite `magnotia_preferences` path -- Backend reads preferences at the **very top** of `run()` in [src-tauri/src/lib.rs](../../src-tauri/src/lib.rs) — before `tauri::Builder::default()` spawns threads — and writes via `unsafe { std::env::set_var(...) }`. Matches the existing `ensure_x11_on_wayland` pattern +- Backend process env must be established before GPU backend initialisation. **Superseded note (2026-05-12):** runtime `std::env::set_var` mutation and the old `ensure_x11_on_wayland` pattern were removed from `src-tauri/src/lib.rs`; launcher/wrapper processes now own env-var setup. Preserve this startup-order requirement when revisiting GPU tuning, but do not reintroduce in-process env mutation. - Settings UI shows a sticky "Restart required for changes to take effect" banner when any toggle has drifted from its launch-time value - A "Reset to defaults" button zeroes all toggles diff --git a/docs/superpowers/plans/2026-05-12-engine-slop-residuals.md b/docs/superpowers/plans/2026-05-12-engine-slop-residuals.md index 45879c7..0319207 100644 --- a/docs/superpowers/plans/2026-05-12-engine-slop-residuals.md +++ b/docs/superpowers/plans/2026-05-12-engine-slop-residuals.md @@ -151,6 +151,57 @@ Each area is independent. They can be tackled in any order, but the suggested or **Acceptance:** No `Result` 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 when `XDG_SESSION_TYPE=wayland`. Static `.desktop` `Exec=env` cannot 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: + +```sh +#!/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: `AppRun` is the wrapper. +- Flatpak: wrapper under `/app/bin/`; the manifest's `command` points at it. + +**Fallback — static `.desktop` `Exec`:** + +```desktop +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-`Exec` per 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 `.desktop` `Exec` hardcodes env directly, the terminal-launch override path is documented in user docs. +- `lib.rs::warn_if_x11_env_unset_on_wayland` continues to surface missed contracts during dev/diagnostics. + ## Suggested sequencing 1. **B (eprintln sweep)** first — mechanical, low-risk, immediate observability win. @@ -158,6 +209,7 @@ Each area is independent. They can be tackled in any order, but the suggested or 3. **D (property-based DSP)** in parallel with A — independent surface, can be done by anyone any time. 4. **E (FE/BE error boundary)** after A — depends on storage error shape. 5. **C (actor model)** last — biggest surface, deserves its own brainstorm + plan + likely its own branch. +6. **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) diff --git a/package.json b/package.json index bdc93da..6e1e963 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "scripts": { "dev": "npm run dev:frontend", "dev:frontend": "svelte-kit sync && vite dev", + "dev:tauri": "./run.sh", "build": "vite build", "preview": "vite preview", "check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json", diff --git a/run.sh b/run.sh index 593e362..e9ae04c 100755 --- a/run.sh +++ b/run.sh @@ -1,22 +1,51 @@ #!/usr/bin/env bash # Magnotia dev launcher. Starts Vite first, waits for it to bind port 1420, # then runs Tauri without triggering a second Vite instance. +# Sets the Linux rendering env vars that warn_if_x11_env_unset_on_wayland +# (src-tauri/src/lib.rs) expects the launcher to own. # For performance testing use: npm run tauri build set -euo pipefail cd "$(dirname "$0")" -export LIBCLANG_PATH=/usr/lib64/llvm21/lib64 +case "$(uname -s)" in + Linux) + # Bindgen libclang path. Fedora 41/LLVM 21 default; user-set wins. + export LIBCLANG_PATH="${LIBCLANG_PATH:-/usr/lib64/llvm21/lib64}" + # iGPU idle-cost workaround. Set to 0 explicitly to opt out. + 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 + ;; +esac printf 'Starting Vite dev server...\n' >&2 npm run dev:frontend & VITE_PID=$! -until curl -sf http://localhost:1420 > /dev/null 2>&1; do sleep 0.5; done -printf 'Vite ready. Launching Tauri...\n' >&2 - cleanup() { kill "$VITE_PID" 2>/dev/null || true } trap cleanup EXIT INT TERM -npx tauri dev --config '{"build":{"beforeDevCommand":""}}' +# Wait up to 60s for Vite, bailing if it exits early. +for _ in $(seq 1 120); do + if curl -sf http://localhost:1420 > /dev/null 2>&1; then + break + fi + if ! kill -0 "$VITE_PID" 2>/dev/null; then + printf 'Vite dev server exited before becoming ready.\n' >&2 + wait "$VITE_PID" + exit 1 + fi + sleep 0.5 +done + +if ! curl -sf http://localhost:1420 > /dev/null 2>&1; then + printf 'Timed out waiting for Vite on http://localhost:1420\n' >&2 + exit 1 +fi + +printf 'Vite ready. Launching Tauri...\n' >&2 +npx tauri dev --config '{"build":{"beforeDevCommand":""}}' "$@" diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 61030c7..1e0de59 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -103,19 +103,18 @@ async fn save_preferences( } /// On Linux Wayland sessions (KDE, GNOME) WebKitGTK + DMABUF rendering hits -/// known crashes that the HANDOVER documents working around with a manual -/// env-var prefix: +/// known crashes that the dev launcher works around by setting rendering +/// env vars before WebKitGTK/WINIT initialise. In development, launch via: /// /// ```sh -/// env GDK_BACKEND=x11 WINIT_UNIX_BACKEND=x11 \ -/// WEBKIT_DISABLE_DMABUF_RENDERER=1 npm run tauri dev +/// npm run dev:tauri /// ``` /// /// 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. +/// launched with these values by `run.sh`, the desktop file, or a package +/// wrapper rather than patching them here. /// /// Inspired by Open-Whispr's similar Chromium self-relaunch (with /// --ozone-platform=x11). Day 6 of the upgrade plan. @@ -139,7 +138,7 @@ fn warn_if_x11_env_unset_on_wayland() { // renderer for no visible quality difference. Empirically (env-var // matrix on Ryzen 5 4650U / Vega 6, May 2026) toggling this dropped // 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 + // Applies to both X11 and Wayland sessions; users can opt back in by // setting WEBKIT_DISABLE_DMABUF_RENDERER=0 explicitly. warn_if_unset( "WEBKIT_DISABLE_DMABUF_RENDERER",