agent: lumotia-rebrand — docs, scripts, root config, residuals
Phase 9 of the rebrand cascade. Sweep covers everything the Phase 8
frontend pass deliberately skipped: docs/, root markdown, scripts,
Cargo.toml descriptions, code comments that survived earlier
word-boundary sed, plus a handful of identifiers caught on the final
verify pass.
transcription-app changes:
- README.md, HANDOVER.md, KNOWN-ISSUES.md, run.sh — magnotia/Magnotia
-> lumotia/Lumotia.
- docs/ — sweep across all subdirs except docs/handovers/ (preserved
as immutable audit trail). Includes architecture-map references
to magnotia_core::*, magnotia_storage::*, etc. now pointing at
lumotia_*; dev-setup.md tracing output examples (lumotia_startup
target); brief/ + superpowers/ + issues/ + whisper-ecosystem/ +
audit/.
- Cargo.toml descriptions on 9 crates (core, audio, cloud-providers,
hotkey, llm, mcp, plus referenced others).
- crates/core/src/{error,hardware,recommendation,paths}.rs +
crates/audio/src/wav.rs + crates/llm/src/model_manager.rs +
crates/cloud-providers/src/keystore.rs + crates/mcp/src/lib.rs —
doc comments and a model-manager user-agent string.
- Caught on final pass: BroadcastChannel("magnotia_task_sync") -> ...
("lumotia_task_sync"); magnotia_locale i18n localStorage key
renamed + migration shim added; CSS keyframe names
magnotiaPulse / magnotiaBar / magnotiaFade renamed in the design-
system kit; magnotia_viewer_item / magnotia_viewer_mode handoff
keys renamed in HistoryPage + viewer/+page.svelte; src/assets/
wordmark.svg text.
- src-tauri/src/lib.rs comment cleanup ("magnotia era" was sed'd
to "lumotia era" earlier — restored).
Preserved (intentional):
- crates/core/src/paths.rs — keeps "magnotia" / "Magnotia" / ".magnotia"
legacy detection strings in legacy_and_target_paths() so the
migration shim can still find user data from the magnotia era.
- src/lib/stores/{page,focusTimer}.svelte.ts + src/lib/i18n/index.ts
— migration call sites reference the legacy magnotia keys
deliberately.
- docs/handovers/ — historical audit trail.
cargo build --workspace passes. npm run check: 0 errors / 0 warnings
(3958 files). cargo test --workspace: 339 pass / 0 fail.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -21,7 +21,7 @@ last_verified: 2026/05/09
|
||||
- `stop_native_capture(window, state) -> Result<Vec<f32>, String>` — main-window only. Awaits the worker join barrier (RB-06) and returns the accumulated samples.
|
||||
- `save_audio(window, app, samples, output_folder: Option<String>) -> Result<String, String>` — main-window only. Writes a fresh WAV to `app_local_data_dir/recordings/` (or a user-chosen folder) and returns its absolute path.
|
||||
- Events emitted: `native-pcm` (payload `{ samples: Vec<f32> }`, ~0.5 s of 16 kHz mono per emit) — `src-tauri/src/commands/audio.rs:249` and `:274`.
|
||||
- Depends on: `magnotia_audio::{DeviceInfo, MicrophoneCapture, WavWriter, write_wav}`, `magnotia_core::types::AudioSamples`, `magnotia_core::constants::WHISPER_SAMPLE_RATE`. Plus `tokio::{mpsc, Mutex, JoinHandle, spawn_blocking, sleep}` and `std::sync::{Arc, Mutex, atomic::AtomicBool}`.
|
||||
- Depends on: `lumotia_audio::{DeviceInfo, MicrophoneCapture, WavWriter, write_wav}`, `lumotia_core::types::AudioSamples`, `lumotia_core::constants::WHISPER_SAMPLE_RATE`. Plus `tokio::{mpsc, Mutex, JoinHandle, spawn_blocking, sleep}` and `std::sync::{Arc, Mutex, atomic::AtomicBool}`.
|
||||
- Called from frontend at: dictation page (start / stop), Settings → Audio (device list), file-import flow (save_audio).
|
||||
|
||||
## What's in here
|
||||
@@ -75,11 +75,11 @@ Public helper used by `commands::live::start_live_transcription_session`. Resolv
|
||||
|
||||
### `recording_filename` (`src-tauri/src/commands/audio.rs:365`)
|
||||
|
||||
Deterministic filename: `magnotia-<unix_secs>-<nanos:09>-<counter:04>.wav`. The counter is a process-lifetime `AtomicU64` (`RECORDING_COUNTER`) bumped on each call. The combination defeats wall-clock collisions both across launches (secs change) and within the same nanosecond (counter changes).
|
||||
Deterministic filename: `lumotia-<unix_secs>-<nanos:09>-<counter:04>.wav`. The counter is a process-lifetime `AtomicU64` (`RECORDING_COUNTER`) bumped on each call. The combination defeats wall-clock collisions both across launches (secs change) and within the same nanosecond (counter changes).
|
||||
|
||||
### `persist_audio_samples` and `save_audio` (`src-tauri/src/commands/audio.rs:512`, `:531`)
|
||||
|
||||
`save_audio` is the public command. Calls `persist_audio_samples`, which resolves the recording path, then calls `magnotia_audio::write_wav` inside `spawn_blocking`. Returns the absolute path as a string.
|
||||
`save_audio` is the public command. Calls `persist_audio_samples`, which resolves the recording path, then calls `lumotia_audio::write_wav` inside `spawn_blocking`. Returns the absolute path as a string.
|
||||
|
||||
## Data flow
|
||||
|
||||
@@ -90,7 +90,7 @@ Deterministic filename: `magnotia-<unix_secs>-<nanos:09>-<counter:04>.wav`. The
|
||||
## Watch-outs
|
||||
|
||||
- The in-memory buffer is capped at 10 minutes. Anything longer relies on the temp WAV; the frontend cannot just `stop_native_capture` and treat the returned vec as ground truth for long sessions. Today's UI assumes short captures, but the cap should be surfaced if you build a long-form recorder.
|
||||
- The downsampler is naive decimation. Acceptable for speech but not for music. If you ever extend Magnotia to general audio, swap in `magnotia_audio::StreamingResampler` (which is what `commands::live` uses).
|
||||
- The downsampler is naive decimation. Acceptable for speech but not for music. If you ever extend Lumotia to general audio, swap in `lumotia_audio::StreamingResampler` (which is what `commands::live` uses).
|
||||
- `start_native_capture` does *not* engage `PowerAssertion` (App Nap pinning). `commands::live::run_live_session` does. If you build a long-form non-live recorder on top of this, add the assertion.
|
||||
- The worker emits `native-pcm` to the entire app handle (not a specific window). Every webview that listens will receive the chunks. If you ever route audio to a non-main window, double-check this is what you want.
|
||||
- `stop_worker_awaits_full_termination_no_writes_after_join` (`src-tauri/src/commands/audio.rs:454`) is the regression test for RB-06. Do not change `stop_worker` to a fire-and-forget pattern.
|
||||
|
||||
@@ -24,7 +24,7 @@ last_verified: 2026/05/09
|
||||
- `get_os_info() -> OsInfo`.
|
||||
- Public Rust helper used by `lib.rs::run`: `install_panic_hook()`.
|
||||
- Events emitted: none.
|
||||
- Depends on: `magnotia_storage::{app_data_dir, crashes_dir, list_recent_errors, log_error, logs_dir, ErrorLogRow}`, `commands::power::active_assertions_snapshot`, `commands::security::ensure_main_window`. Plus `std::fs`, `std::panic`, `std::time`.
|
||||
- Depends on: `lumotia_storage::{app_data_dir, crashes_dir, list_recent_errors, log_error, logs_dir, ErrorLogRow}`, `commands::power::active_assertions_snapshot`, `commands::security::ensure_main_window`. Plus `std::fs`, `std::panic`, `std::time`.
|
||||
- Called from frontend at: global `window.onerror` / `window.unhandledrejection` (`log_frontend_error`); Settings → About → Diagnostics (the bundle commands and `list_*` commands); top-of-app Cmd-vs-Ctrl labelling (`get_os_info`).
|
||||
|
||||
## What's in here
|
||||
@@ -40,7 +40,7 @@ Creates `crashes_dir()` if missing. Replaces the default panic hook with one tha
|
||||
|
||||
### `log_frontend_error` (`:86`)
|
||||
|
||||
Truncates the stack to 2,000 chars and calls `magnotia_storage::log_error` with context `"frontend"` (or the caller-supplied context), error code `"FRONTEND_ERROR"`, the message, and the stack as metadata.
|
||||
Truncates the stack to 2,000 chars and calls `lumotia_storage::log_error` with context `"frontend"` (or the caller-supplied context), error code `"FRONTEND_ERROR"`, the message, and the stack as metadata.
|
||||
|
||||
### `ErrorLogDto` (`:114`) and `list_recent_errors_command` (`:138`)
|
||||
|
||||
@@ -68,11 +68,11 @@ Builds a markdown document with sections:
|
||||
3. Recent errors (50 rows, redacted).
|
||||
4. Power assertions (snapshots from `commands::power::active_assertions_snapshot`).
|
||||
5. Crash dumps (up to 5 most-recent, plus a count of older ones).
|
||||
6. Log tail (8 KB tail of `logs_dir/magnotia.log`, home-redacted).
|
||||
6. Log tail (8 KB tail of `logs_dir/lumotia.log`, home-redacted).
|
||||
|
||||
### `save_diagnostic_report` (`:513`)
|
||||
|
||||
Generates the report, then writes it to `app_data_dir/diagnostic-reports/magnotia-diagnostic-<unix_secs>.md`. Returns the absolute path.
|
||||
Generates the report, then writes it to `app_data_dir/diagnostic-reports/lumotia-diagnostic-<unix_secs>.md`. Returns the absolute path.
|
||||
|
||||
### `OsInfo` and `get_os_info` (`:449`, `:474`)
|
||||
|
||||
@@ -83,7 +83,7 @@ OS family label, arch, `uses_cmd` boolean (macOS only), `is_wayland` boolean (Li
|
||||
```
|
||||
panic occurs -> install_panic_hook writes crashes_dir/<ts>.crash
|
||||
window.onerror fires in JS -> invoke('log_frontend_error', context, message, stack)
|
||||
-> magnotia_storage::log_error
|
||||
-> lumotia_storage::log_error
|
||||
-> error_log table
|
||||
|
||||
Settings -> About -> Diagnostics
|
||||
|
||||
@@ -19,7 +19,7 @@ last_verified: 2026/05/09
|
||||
- `record_feedback(state, input: RecordFeedbackInput) -> Result<i64, String>` — returns the new row id.
|
||||
- `list_feedback_examples_cmd(state, target_type, limit, min_rating, profile_id) -> Result<Vec<FeedbackDto>, String>`.
|
||||
- Events emitted: none.
|
||||
- Depends on: `magnotia_storage::{record_feedback, list_feedback_examples, FeedbackRow, FeedbackTargetType, RecordFeedbackParams}`.
|
||||
- Depends on: `lumotia_storage::{record_feedback, list_feedback_examples, FeedbackRow, FeedbackTargetType, RecordFeedbackParams}`.
|
||||
- Called from frontend at: dictation result panel (thumb up/down + correction-text on cleanup); Tasks page (thumb on extracted tasks and decomposed microsteps).
|
||||
|
||||
## What's in here
|
||||
@@ -56,7 +56,7 @@ Clamps `limit` to `[1, 64]`, defaults 8. Clamps `min_rating` to `[-1, 1]`, defau
|
||||
|
||||
```
|
||||
dictation result thumbs-up -> invoke('record_feedback', { targetType: 'cleanup', rating: +1, originalText, correctedText, contextJson, profileId })
|
||||
-> magnotia_storage::record_feedback -> row id
|
||||
-> lumotia_storage::record_feedback -> row id
|
||||
|
||||
decomposition thumbs-down + correction -> record_feedback({ targetType: 'microstep', rating: 0, originalText, correctedText: "user's preferred wording", contextJson: {parent_text}, profileId })
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ last_verified: 2026/05/09
|
||||
|
||||
> **Where you are:** [Architecture map](../../README.md) → [Tauri runtime](../README.md) → [Commands](README.md) → Hotkey bridge
|
||||
|
||||
**Plain English summary.** The Linux Wayland-compatible global hotkey backend. Tauri's `tauri-plugin-global-shortcut` works on macOS / Windows and on X11 Linux but fails silently on Wayland (the protocol forbids unprivileged keystroke grabs). Magnotia's bespoke evdev backend reads `/dev/input/event*` directly, parses key combos, and emits `magnotia:hotkey-pressed` / `magnotia:hotkey-released` events. Settings on Linux uses these commands instead of the global-shortcut plugin; everywhere else the plugin path is canonical.
|
||||
**Plain English summary.** The Linux Wayland-compatible global hotkey backend. Tauri's `tauri-plugin-global-shortcut` works on macOS / Windows and on X11 Linux but fails silently on Wayland (the protocol forbids unprivileged keystroke grabs). Lumotia's bespoke evdev backend reads `/dev/input/event*` directly, parses key combos, and emits `lumotia:hotkey-pressed` / `lumotia:hotkey-released` events. Settings on Linux uses these commands instead of the global-shortcut plugin; everywhere else the plugin path is canonical.
|
||||
|
||||
## At a glance
|
||||
|
||||
@@ -21,8 +21,8 @@ last_verified: 2026/05/09
|
||||
- `start_evdev_hotkey(app, state, hotkey: String) -> Result<(), String>`. Parses the Tauri-style combo string, stops any existing listener, starts a new one, spawns a forwarder task that converts evdev events to Tauri events.
|
||||
- `update_evdev_hotkey(state, hotkey: String) -> Result<(), String>`. Updates the combo on a running listener.
|
||||
- `stop_evdev_hotkey(state) -> Result<(), String>`. Stops cleanly.
|
||||
- Events emitted: `magnotia:hotkey-pressed` (no payload), `magnotia:hotkey-released` (no payload). Fired from the forwarder task in `start_evdev_hotkey` (`src-tauri/src/commands/hotkey.rs:67`, `:70`).
|
||||
- Depends on: `magnotia_hotkey::{EvdevHotkeyListener, HotkeyCombo, HotkeyEvent, check_evdev_access}`. Plus `tokio::sync::{mpsc, Mutex}`.
|
||||
- Events emitted: `lumotia:hotkey-pressed` (no payload), `lumotia:hotkey-released` (no payload). Fired from the forwarder task in `start_evdev_hotkey` (`src-tauri/src/commands/hotkey.rs:67`, `:70`).
|
||||
- Depends on: `lumotia_hotkey::{EvdevHotkeyListener, HotkeyCombo, HotkeyEvent, check_evdev_access}`. Plus `tokio::sync::{mpsc, Mutex}`.
|
||||
- Called from frontend at: Settings → Hotkey on Linux. Other platforms call the `tauri-plugin-global-shortcut` plugin's JS API directly.
|
||||
|
||||
## What's in here
|
||||
@@ -37,7 +37,7 @@ Returns true if `WAYLAND_DISPLAY` is set or `XDG_SESSION_TYPE=wayland`. Used by
|
||||
|
||||
### `check_hotkey_access` (`:32`)
|
||||
|
||||
Defers to `magnotia_hotkey::check_evdev_access` — that helper checks the user can read `/dev/input/event*` (typically requires being in the `input` group, or a udev rule). On failure, returns the actionable error string.
|
||||
Defers to `lumotia_hotkey::check_evdev_access` — that helper checks the user can read `/dev/input/event*` (typically requires being in the `input` group, or a udev rule). On failure, returns the actionable error string.
|
||||
|
||||
### `start_evdev_hotkey` (`:41`)
|
||||
|
||||
@@ -45,7 +45,7 @@ Defers to `magnotia_hotkey::check_evdev_access` — that helper checks the user
|
||||
2. Lock the listener mutex; if a listener is already running, stop it and await termination.
|
||||
3. Build a 64-deep `tokio::sync::mpsc` channel for `HotkeyEvent`s.
|
||||
4. Call `EvdevHotkeyListener::start(combo, event_tx)`, stash the listener.
|
||||
5. Spawn a `tokio::spawn` forwarder that reads from the event_rx and emits `magnotia:hotkey-pressed` / `magnotia:hotkey-released` per event variant.
|
||||
5. Spawn a `tokio::spawn` forwarder that reads from the event_rx and emits `lumotia:hotkey-pressed` / `lumotia:hotkey-released` per event variant.
|
||||
|
||||
### `update_evdev_hotkey` (`:81`)
|
||||
|
||||
@@ -65,7 +65,7 @@ Settings -> start_evdev_hotkey("Shift+Cmd+Space")
|
||||
-> stop any prior listener
|
||||
-> EvdevHotkeyListener spawns its own thread reading /dev/input/event*
|
||||
-> forwarder spawn: HotkeyEvent -> Tauri event
|
||||
frontend listens on 'magnotia:hotkey-pressed' / '...-released' and starts/stops dictation
|
||||
frontend listens on 'lumotia:hotkey-pressed' / '...-released' and starts/stops dictation
|
||||
Settings -> update_evdev_hotkey("Ctrl+Alt+M") (when user changes binding)
|
||||
Settings -> stop_evdev_hotkey() (when user disables)
|
||||
```
|
||||
@@ -73,8 +73,8 @@ Settings -> stop_evdev_hotkey() (when user disables)
|
||||
## Watch-outs
|
||||
|
||||
- **No `ensure_main_window` guard.** Settings is in the main window. If you ever expose hotkey re-binding in a secondary window, add the guard.
|
||||
- **Linux only.** macOS and Windows code paths in the frontend talk to `tauri-plugin-global-shortcut` directly — no Rust commands needed because the plugin handles register / unregister via JS. This module compiles and runs on every desktop OS but only *works* on Linux because evdev is Linux-specific (the workspace crate `magnotia_hotkey` has the platform shim).
|
||||
- **`/dev/input/event*` access requires either:** (a) the user is in the `input` group, or (b) a udev rule grants Magnotia explicit access. The Settings UI walks the user through option (a) on first run; option (b) is documented in `docs/dev-setup.md` for power users.
|
||||
- **Linux only.** macOS and Windows code paths in the frontend talk to `tauri-plugin-global-shortcut` directly — no Rust commands needed because the plugin handles register / unregister via JS. This module compiles and runs on every desktop OS but only *works* on Linux because evdev is Linux-specific (the workspace crate `lumotia_hotkey` has the platform shim).
|
||||
- **`/dev/input/event*` access requires either:** (a) the user is in the `input` group, or (b) a udev rule grants Lumotia explicit access. The Settings UI walks the user through option (a) on first run; option (b) is documented in `docs/dev-setup.md` for power users.
|
||||
- **Channel size of 64** is enough for a key smash burst. If a worker stalls and the channel fills, evdev events would be dropped silently. Consider logging a warning when the channel is at capacity.
|
||||
- **The forwarder spawn is detached.** No way to stop the spawn task explicitly; it exits when `event_rx.recv().await` returns None (which happens when the listener is dropped). Acceptable.
|
||||
- **No power assertion.** The evdev listener thread is pinned by the kernel via the file handle; idle CPU is near zero.
|
||||
|
||||
@@ -22,7 +22,7 @@ last_verified: 2026/05/09
|
||||
- `mark_implementation_rule_fired(window, state, id, fired_key) -> Result<ImplementationRuleDto, String>`.
|
||||
- `delete_implementation_rule(window, state, id) -> Result<(), String>`.
|
||||
- Events emitted: none.
|
||||
- Depends on: `magnotia_storage::{insert_implementation_rule, list_implementation_rules, set_implementation_rule_enabled, mark_implementation_rule_fired, delete_implementation_rule, get_task_by_id, ImplementationRuleRow}`, `uuid::Uuid`, `serde_json`. Plus `commands::security::ensure_main_window`.
|
||||
- Depends on: `lumotia_storage::{insert_implementation_rule, list_implementation_rules, set_implementation_rule_enabled, mark_implementation_rule_fired, delete_implementation_rule, get_task_by_id, ImplementationRuleRow}`, `uuid::Uuid`, `serde_json`. Plus `commands::security::ensure_main_window`.
|
||||
- Called from frontend at: Settings → Implementation intentions (CRUD); the rule-runner module (`mark_implementation_rule_fired` after firing).
|
||||
|
||||
## What's in here
|
||||
@@ -67,7 +67,7 @@ Settings -> create_implementation_rule(req)
|
||||
-> validate_request (HH:MM if applicable, action shape, task existence for surface-task)
|
||||
-> uuid::v4 for id
|
||||
-> serde_json serialise actions
|
||||
-> magnotia_storage::insert_implementation_rule
|
||||
-> lumotia_storage::insert_implementation_rule
|
||||
-> list/return DTO
|
||||
|
||||
frontend rule-runner cron -> evaluates triggers in JS
|
||||
@@ -78,7 +78,7 @@ frontend rule-runner cron -> evaluates triggers in JS
|
||||
## Watch-outs
|
||||
|
||||
- **Action timer is hard-capped to 300 seconds.** The frontend has to surface this constraint. Future versions will need to widen the validator to accept arbitrary durations.
|
||||
- **Rule execution lives entirely in the frontend.** If the user closes Magnotia at 08:55 with a 09:00 rule, the rule does not fire. There is no daemon. This is documented in the module header.
|
||||
- **Rule execution lives entirely in the frontend.** If the user closes Lumotia at 08:55 with a 09:00 rule, the rule does not fire. There is no daemon. This is documented in the module header.
|
||||
- **Validation is sync against the DB.** `validate_actions` calls `get_task_by_id` for every surface-task action. Multiple actions in one rule = multiple round-trips. Acceptable today; consider batching if a rule grows huge.
|
||||
- **`mark_implementation_rule_fired` requires a non-empty `fired_key`.** The frontend should always pass an ISO date for daily rules and a per-event key for task-completed rules. Empty key = command rejection.
|
||||
- **No `PowerAssertion`.** None of these actions run inference. No need.
|
||||
|
||||
@@ -18,10 +18,10 @@ last_verified: 2026/05/09
|
||||
- Tauri commands exposed:
|
||||
- `start_live_transcription_session(window, app, state, live_state, config: StartLiveTranscriptionConfig, result_channel: Channel<LiveResultMessage>, status_channel: Channel<LiveStatusMessage>) -> Result<StartLiveTranscriptionResponse, String>` — main-window only.
|
||||
- `stop_live_transcription_session(window, app, live_state, session_id: u64) -> Result<StopLiveTranscriptionResponse, String>` — main-window only.
|
||||
- Events emitted: NONE in the conventional `app.emit(...)` sense. This module uses Tauri 2's typed `tauri::ipc::Channel<T>` API instead. The frontend creates the channel pair on the JS side via `new Channel<T>()`, passes it as a command argument, and Magnotia sends typed messages on it from the worker. Two channels:
|
||||
- Events emitted: NONE in the conventional `app.emit(...)` sense. This module uses Tauri 2's typed `tauri::ipc::Channel<T>` API instead. The frontend creates the channel pair on the JS side via `new Channel<T>()`, passes it as a command argument, and Lumotia sends typed messages on it from the worker. Two channels:
|
||||
- `Channel<LiveResultMessage>` — per-chunk transcription results (segments, language, duration, raw_text, inference_ms, chunk_id, chunk_start_secs).
|
||||
- `Channel<LiveStatusMessage>` — tagged enum: `Warning { message }`, `Overload { dropped_audio_ms, message }`, `Error { message }`, `Finished { audio_path, dropped_audio_ms }`.
|
||||
- Depends on: `magnotia_audio::{AudioChunk, CaptureRuntimeError, MicrophoneCapture, StreamingResampler, WavWriter}`, `magnotia_core::constants::WHISPER_SAMPLE_RATE`, `magnotia_core::types::{AudioSamples, Segment, TranscriptionOptions}`, `magnotia_transcription::LocalEngine`, `magnotia_ai_formatting::{post_process_segments, FormatMode, PostProcessOptions}`, `magnotia_storage::{database::get_profile, database::list_profile_terms, DEFAULT_PROFILE_ID}`. Plus `commands::audio::resolve_recording_path`, `commands::build_initial_prompt`, `commands::models::{default_model_id_for_engine, ensure_model_loaded}`, `commands::power::PowerAssertion`, `commands::security::ensure_main_window`.
|
||||
- Depends on: `lumotia_audio::{AudioChunk, CaptureRuntimeError, MicrophoneCapture, StreamingResampler, WavWriter}`, `lumotia_core::constants::WHISPER_SAMPLE_RATE`, `lumotia_core::types::{AudioSamples, Segment, TranscriptionOptions}`, `lumotia_transcription::LocalEngine`, `lumotia_ai_formatting::{post_process_segments, FormatMode, PostProcessOptions}`, `lumotia_storage::{database::get_profile, database::list_profile_terms, DEFAULT_PROFILE_ID}`. Plus `commands::audio::resolve_recording_path`, `commands::build_initial_prompt`, `commands::models::{default_model_id_for_engine, ensure_model_loaded}`, `commands::power::PowerAssertion`, `commands::security::ensure_main_window`.
|
||||
- Called from frontend at: dictation page (when the user starts and stops a live session — most common entry).
|
||||
|
||||
## What's in here
|
||||
@@ -86,7 +86,7 @@ Methods:
|
||||
1. `ensure_main_window`.
|
||||
2. `lifecycle.lock().await` — barrier against concurrent start/stop.
|
||||
3. Reject if a session is already running.
|
||||
4. Resolve profile_id, fetch profile + profile_terms from `magnotia_storage`.
|
||||
4. Resolve profile_id, fetch profile + profile_terms from `lumotia_storage`.
|
||||
5. Collapse the effective `initial_prompt` via `build_initial_prompt` (so the worker doesn't have to know about profile fallback).
|
||||
6. Resolve model_id via `default_model_id_for_engine` if absent.
|
||||
7. `ensure_model_loaded(state, engine, model_id, None)` — `None` means don't enforce sequential-GPU mode (Settings owns that toggle).
|
||||
@@ -104,7 +104,7 @@ Methods:
|
||||
|
||||
### `run_live_session` (`src-tauri/src/commands/live.rs:646`)
|
||||
|
||||
The blocking entry. Holds a `PowerAssertion::begin("magnotia live dictation session")` for the entire scope. Constructs and runs `LiveSessionRuntime`. The drop on the power assertion ends the macOS App Nap pin.
|
||||
The blocking entry. Holds a `PowerAssertion::begin("lumotia live dictation session")` for the entire scope. Constructs and runs `LiveSessionRuntime`. The drop on the power assertion ends the macOS App Nap pin.
|
||||
|
||||
### `maybe_dispatch_chunk` (`src-tauri/src/commands/live.rs:753`)
|
||||
|
||||
@@ -172,7 +172,7 @@ frontend invoke('stop_live_transcription_session', { session_id })
|
||||
- **Result-listener-lost path is critical.** Without it, closing the main window without a clean stop would leave the worker spinning forever, holding the GPU memory and the WAV file handle until process exit. The self-asserted stop flag is the safety net.
|
||||
- **Power assertion only does work on macOS.** On Linux the function is a no-op (see [Power assertions and security](power-and-security.md)). A long live-dictation session on Linux can still be idled by the compositor.
|
||||
- **The recent-segments history is bounded by time, not count.** A high chunk rate could grow it more than expected; the retention is `DUPLICATE_HISTORY_RETENTION_SECS = 8.0`.
|
||||
- **Channel back-pressure.** The result channel is the JS-side `Channel<T>` queue. If the frontend stops reading, the queue grows. Magnotia's overload-signalling currently uses the in-buffer `MAX_PENDING_SAMPLES` cap; it does NOT detect a JS-side stalled listener except via the `emit_live_result`-failure path.
|
||||
- **Channel back-pressure.** The result channel is the JS-side `Channel<T>` queue. If the frontend stops reading, the queue grows. Lumotia's overload-signalling currently uses the in-buffer `MAX_PENDING_SAMPLES` cap; it does NOT detect a JS-side stalled listener except via the `emit_live_result`-failure path.
|
||||
- **`ensure_model_loaded(state, engine, model_id, None)`** intentionally passes `None` for `concurrent`, so live sessions never trigger the sequential-GPU guard in `commands::models`. If you ever ship a tight-VRAM machine and the user has switched to sequential mode, this could OOM. Today's hardware survey indicates this is uncommon; flag this when revisiting Phase A.4.
|
||||
|
||||
## See also
|
||||
|
||||
@@ -18,7 +18,7 @@ last_verified: 2026/05/09
|
||||
- Tauri commands exposed (10 total):
|
||||
- `recommend_llm_tier() -> Result<String, String>`. No window guard — pure hardware probe.
|
||||
- `check_llm_model(state, model_id) -> Result<LlmModelStatusDto, String>`.
|
||||
- `download_llm_model(window, app, model_id) -> Result<(), String>`. Main-window only. Emits `magnotia:llm-download-progress`.
|
||||
- `download_llm_model(window, app, model_id) -> Result<(), String>`. Main-window only. Emits `lumotia:llm-download-progress`.
|
||||
- `load_llm_model(window, state, model_id, use_gpu, concurrent) -> Result<(), String>`. Main-window only.
|
||||
- `unload_llm_model(window, state) -> Result<(), String>`. Main-window only.
|
||||
- `delete_llm_model(window, state, model_id) -> Result<(), String>`. Main-window only.
|
||||
@@ -26,8 +26,8 @@ last_verified: 2026/05/09
|
||||
- `test_llm_model(window, state, model_id) -> Result<LlmTestResult, String>`. Main-window only.
|
||||
- `cleanup_transcript_text_cmd(window, state, transcript, profile_id, preset) -> Result<String, String>`. Main-window only.
|
||||
- `extract_content_tags_cmd(state, transcript) -> Result<ContentTags, String>`.
|
||||
- Events emitted: `magnotia:llm-download-progress` (`{ modelId, done, total, percent }`) — `src-tauri/src/commands/llm.rs:76`.
|
||||
- Depends on: `magnotia_llm::{LlmEngine, LlmModelId, ContentTags, model_manager::{download_model, model_path, model_info, recommend_tier, is_downloaded, delete_model}}`, `magnotia_ai_formatting::{llm_cleanup_text, LlmPromptPreset}`, `magnotia_core::hardware`, `magnotia_storage::database::list_profile_terms`. Plus `commands::power::PowerAssertion`, `commands::security::ensure_main_window`.
|
||||
- Events emitted: `lumotia:llm-download-progress` (`{ modelId, done, total, percent }`) — `src-tauri/src/commands/llm.rs:76`.
|
||||
- Depends on: `lumotia_llm::{LlmEngine, LlmModelId, ContentTags, model_manager::{download_model, model_path, model_info, recommend_tier, is_downloaded, delete_model}}`, `lumotia_ai_formatting::{llm_cleanup_text, LlmPromptPreset}`, `lumotia_core::hardware`, `lumotia_storage::database::list_profile_terms`. Plus `commands::power::PowerAssertion`, `commands::security::ensure_main_window`.
|
||||
- Called from frontend at: Settings → AI page (download / load / unload / delete / test), dictation result panel (`cleanup_transcript_text_cmd`), History page (`extract_content_tags_cmd`).
|
||||
|
||||
## What's in here
|
||||
@@ -42,7 +42,7 @@ Wraps `model_id.parse()` (which goes through the `LlmModelId` parser).
|
||||
|
||||
### `recommend_llm_tier` (`:28`)
|
||||
|
||||
Probes RAM via `magnotia_core::hardware::probe_system`, multiplies the MB to bytes, then defers to `magnotia_llm::model_manager::recommend_tier(ram, vram)`. No window guard — Settings calls this at first paint to populate the default tier suggestion.
|
||||
Probes RAM via `lumotia_core::hardware::probe_system`, multiplies the MB to bytes, then defers to `lumotia_llm::model_manager::recommend_tier(ram, vram)`. No window guard — Settings calls this at first paint to populate the default tier suggestion.
|
||||
|
||||
### `check_llm_model` (`:40`)
|
||||
|
||||
@@ -50,7 +50,7 @@ Combines `model_info(id)` (static metadata), `model_manager::is_downloaded(id)`,
|
||||
|
||||
### `download_llm_model` (`:61`)
|
||||
|
||||
`ensure_main_window`. Calls `model_manager::download_model(id, progress_cb)` with a closure that emits `magnotia:llm-download-progress` on each chunk. The percent calculation rounds against `total > 0` (avoids division by zero on a server that doesn't return a content-length).
|
||||
`ensure_main_window`. Calls `model_manager::download_model(id, progress_cb)` with a closure that emits `lumotia:llm-download-progress` on each chunk. The percent calculation rounds against `total > 0` (avoids division by zero on a server that doesn't return a content-length).
|
||||
|
||||
### `load_llm_model` (`:90`)
|
||||
|
||||
@@ -75,7 +75,7 @@ Pure string classifier. Tested independently. Buckets: `load-failed-vram` (looks
|
||||
|
||||
### `cleanup_transcript_text_cmd` (`:362`)
|
||||
|
||||
`ensure_main_window`. Resolves `profile_id`. Fetches profile_terms from storage. Resolves the `preset` (Brief item B.1 #15: `Default`, `Email`, `Notes`, `Code`). `spawn_blocking` runs `llm_cleanup_text(&engine, &transcript, &profile_terms, resolved_preset)` inside a `PowerAssertion::begin("magnotia LLM cleanup")` so macOS App Nap doesn't throttle mid-token.
|
||||
`ensure_main_window`. Resolves `profile_id`. Fetches profile_terms from storage. Resolves the `preset` (Brief item B.1 #15: `Default`, `Email`, `Notes`, `Code`). `spawn_blocking` runs `llm_cleanup_text(&engine, &transcript, &profile_terms, resolved_preset)` inside a `PowerAssertion::begin("lumotia LLM cleanup")` so macOS App Nap doesn't throttle mid-token.
|
||||
|
||||
### `extract_content_tags_cmd` (`:407`)
|
||||
|
||||
@@ -91,7 +91,7 @@ Eight `classify_llm_load_error` cases cover the four classification buckets and
|
||||
Settings -> recommend_llm_tier() -> hardware probe -> tier string
|
||||
Settings -> download_llm_model(model_id) -> model_manager::download_model
|
||||
-> per-chunk progress events
|
||||
-> file lands in ~/.magnotia/models/llm/
|
||||
-> file lands in ~/.lumotia/models/llm/
|
||||
Settings -> load_llm_model(model_id, use_gpu, concurrent)
|
||||
-> if concurrent=false: unload whisper + parakeet
|
||||
-> spawn_blocking(engine.load_model)
|
||||
|
||||
@@ -51,7 +51,7 @@ Six test cases cover each branch of the precedence rule plus whitespace handling
|
||||
|
||||
## Data flow
|
||||
|
||||
The helper is called *after* the calling command has read the relevant `ProfileRow` and `ProfileTermRow`s from `magnotia_storage`. The DB I/O lives in the calling command (so the tests in this file stay pure).
|
||||
The helper is called *after* the calling command has read the relevant `ProfileRow` and `ProfileTermRow`s from `lumotia_storage`. The DB I/O lives in the calling command (so the tests in this file stay pure).
|
||||
|
||||
## Watch-outs
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ last_verified: 2026/05/09
|
||||
- Parakeet: `download_parakeet_model(name)`, `check_parakeet_model(name)`, `list_parakeet_models()`, `load_parakeet_model(name, concurrent)`, `check_parakeet_engine()`.
|
||||
- Public Rust helpers (used by other command modules): `default_model_id_for_engine`, `ensure_model_loaded`, `prewarm_default_model`, `load_model_from_disk`, `detect_active_compute_device`, `emit_runtime_warnings`.
|
||||
- Events emitted: `model-download-progress` (whisper), `parakeet-download-progress`, `runtime-warning` (tagged enum: `Avx2Missing`, `VulkanLoaderMissing`, future `CudaFallback`).
|
||||
- Depends on: `magnotia_transcription::{model_manager, load_whisper, load_parakeet, LocalEngine, Transcriber}`, `magnotia_core::{model_registry, hardware, constants, types}`.
|
||||
- Depends on: `lumotia_transcription::{model_manager, load_whisper, load_parakeet, LocalEngine, Transcriber}`, `lumotia_core::{model_registry, hardware, constants, types}`.
|
||||
- Called from frontend at: Settings → Models page (download, load, status), the dictation page boot path (`prewarm_default_model_cmd`), Settings → About (runtime capabilities).
|
||||
|
||||
## What's in here
|
||||
@@ -99,8 +99,8 @@ Five `compose_accelerators` permutations cover the RB-07 regression. Confirm:
|
||||
|
||||
- **The `concurrent` flag is a tri-state.** `None` and `Some(true)` keep the legacy parallel-residency behaviour. Only `Some(false)` triggers the unload-the-other-engine guard. Live transcription explicitly passes `None`. If you ever ship a 4 GB-VRAM Settings preset that flips `concurrent=false` by default, also test the live transcription path.
|
||||
- **`parallel_mode_available` is hard-wired to `false` until Phase A.4 lands a real GPU VRAM probe** (`src-tauri/src/commands/models.rs:509`). Today's frontend reads this and disables the toggle. Flag for follow-up.
|
||||
- **`prewarm_default_model` only runs whisper.** Parakeet has no warm-up. The Parakeet model is also smaller and loads faster, so the cold-start gap is less obvious. If you swap Magnotia's default to Parakeet, add an equivalent warm-up.
|
||||
- **Vulkan loader detection is per-platform** (`magnotia_core::hardware::vulkan_loader_available`). The CPU-fallback `reason` strings are user-visible; keep them actionable (the Linux one names `libvulkan1`, the macOS one names the Vulkan SDK runtime).
|
||||
- **`prewarm_default_model` only runs whisper.** Parakeet has no warm-up. The Parakeet model is also smaller and loads faster, so the cold-start gap is less obvious. If you swap Lumotia's default to Parakeet, add an equivalent warm-up.
|
||||
- **Vulkan loader detection is per-platform** (`lumotia_core::hardware::vulkan_loader_available`). The CPU-fallback `reason` strings are user-visible; keep them actionable (the Linux one names `libvulkan1`, the macOS one names the Vulkan SDK runtime).
|
||||
- **Whisper feature gating.** `--no-default-features` builds compile but `load_model_from_disk` returns a runtime error when the user requests a Whisper model. `list_models` will still show whisper models that happened to be downloaded already; consider hiding them when the feature is off if a no-whisper build ever ships to users.
|
||||
- **Download progress events fire from a closure inside `model_manager::download`.** That closure is called from inside an async `spawn_blocking`-equivalent. The `let _ = app_clone.emit(...)` pattern silently drops emit errors; if a window has been closed mid-download the progress events stop landing but the download itself completes.
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ last_verified: 2026/05/09
|
||||
|
||||
> **Where you are:** [Architecture map](../../README.md) → [Tauri runtime](../README.md) → [Commands](README.md) → Paste
|
||||
|
||||
**Plain English summary.** Auto-insert-at-cursor: copy the transcript onto the clipboard and synthesise the platform's paste keystroke (Ctrl+V / Cmd+V) so the text lands in whatever app the user was already focused on. Adds a replace-with-raw flow that fires undo first. Skips the keystroke when the focused window is a terminal emulator (terminals duplicate the keystroke through the PTY). Hides the always-on-top preview overlay before the keystroke so a Wayland compositor doesn't accidentally route the paste back into Magnotia. Restores the user's prior clipboard 300 ms after the paste to honour the "never silently clobber the user's clipboard" contract.
|
||||
**Plain English summary.** Auto-insert-at-cursor: copy the transcript onto the clipboard and synthesise the platform's paste keystroke (Ctrl+V / Cmd+V) so the text lands in whatever app the user was already focused on. Adds a replace-with-raw flow that fires undo first. Skips the keystroke when the focused window is a terminal emulator (terminals duplicate the keystroke through the PTY). Hides the always-on-top preview overlay before the keystroke so a Wayland compositor doesn't accidentally route the paste back into Lumotia. Restores the user's prior clipboard 300 ms after the paste to honour the "never silently clobber the user's clipboard" contract.
|
||||
|
||||
## At a glance
|
||||
|
||||
@@ -48,7 +48,7 @@ Step-by-step:
|
||||
|
||||
### `paste_text_replacing` (`src-tauri/src/commands/paste.rs:185`)
|
||||
|
||||
Same shape as `paste_text` but with an extra step: after copying and hiding the preview, fire `trigger_undo_keystroke()`, sleep 60 ms, then paste. The undo removes whatever text Magnotia already inserted (the cleaned-up transcript), the paste inserts the raw text. Used by the "replace with raw" frontend button per brief item #17.
|
||||
Same shape as `paste_text` but with an extra step: after copying and hiding the preview, fire `trigger_undo_keystroke()`, sleep 60 ms, then paste. The undo removes whatever text Lumotia already inserted (the cleaned-up transcript), the paste inserts the raw text. Used by the "replace with raw" frontend button per brief item #17.
|
||||
|
||||
### `detect_paste_backends` (`src-tauri/src/commands/paste.rs:258`)
|
||||
|
||||
@@ -97,13 +97,13 @@ Replace flow inserts an `undo` keystroke and a 60 ms gap between hide and paste.
|
||||
|
||||
## Watch-outs
|
||||
|
||||
- **Focus must already be on the target window when the keystroke fires.** The global hotkey flow preserves this naturally; clicking Magnotia's own UI does not. The frontend Settings page surfaces the caveat next to the toggle.
|
||||
- **Focus must already be on the target window when the keystroke fires.** The global hotkey flow preserves this naturally; clicking Lumotia's own UI does not. The frontend Settings page surfaces the caveat next to the toggle.
|
||||
- **Wayland focused-window probe is intentionally absent.** No way to do this from an unprivileged Wayland client. Result: terminal detection on Wayland-Kitty users will miss; they fall back to the manual Ctrl+Shift+V they already use.
|
||||
- **Clipboard restore is best-effort.** If the user copies something else within the 300 ms window, `should_restore` will (correctly) decline to write back. If a slow Wayland compositor delays the keystroke past 300 ms, we restore too early and the synthesised Ctrl+V pastes the user's old clipboard. Tradeoff documented in Handy #921.
|
||||
- **Linux backend order is session-aware.** A user with `XDG_SESSION_TYPE=wayland` and only xdotool installed will fall through to xdotool with a warning; their X11-app focus on a Wayland session works fine but Wayland-native apps will not see the keystroke.
|
||||
- **No backend = clipboard-only.** If `trigger_paste_keystroke` fails on Linux (no tools installed), the user still has the transcript on the clipboard; Settings shows the install-wtype hint via `detect_paste_backends`.
|
||||
- **PowerShell process spawn cost on Windows.** Each paste spawns a fresh PowerShell. Acceptable for one-off paste invocations; if you ever build a streaming-paste mode, switch to native `SendInput` via the `windows` crate.
|
||||
- **Permissions.** The macOS path requires the user to have granted Accessibility permissions to Magnotia (System Settings → Privacy → Accessibility). Without that, `osascript` returns success but the keystroke is silently dropped. There is no probe today; flag this in onboarding.
|
||||
- **Permissions.** The macOS path requires the user to have granted Accessibility permissions to Lumotia (System Settings → Privacy → Accessibility). Without that, `osascript` returns success but the keystroke is silently dropped. There is no probe today; flag this in onboarding.
|
||||
|
||||
## See also
|
||||
|
||||
|
||||
@@ -55,9 +55,9 @@ macOS-only. Wraps `NSProcessInfo::processInfo().beginActivityWithOptions_reason(
|
||||
|
||||
### Where `PowerAssertion::begin` is called
|
||||
|
||||
- `commands::live::run_live_session` (`live.rs:660`) — `"magnotia live dictation session"`.
|
||||
- `commands::llm::cleanup_transcript_text_cmd` (`llm.rs:394`) — `"magnotia LLM cleanup"`.
|
||||
- `commands::llm::extract_content_tags_cmd` (`llm.rs:417`) — `"magnotia LLM content-tag extraction"`.
|
||||
- `commands::live::run_live_session` (`live.rs:660`) — `"lumotia live dictation session"`.
|
||||
- `commands::llm::cleanup_transcript_text_cmd` (`llm.rs:394`) — `"lumotia LLM cleanup"`.
|
||||
- `commands::llm::extract_content_tags_cmd` (`llm.rs:417`) — `"lumotia LLM content-tag extraction"`.
|
||||
|
||||
NOT called from `commands::tasks::decompose_and_store` or `commands::tasks::extract_tasks_from_transcript_cmd`, even though both run multi-second LLM inference. Flag for follow-up.
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ last_verified: 2026/05/09
|
||||
- `learn_profile_terms_from_edit_cmd(state, profile_id, original_text, edited_text) -> Result<Vec<ProfileTermDto>, String>`.
|
||||
- `delete_profile_term_cmd(state, id) -> Result<(), String>`.
|
||||
- Events emitted: none.
|
||||
- Depends on: `magnotia_storage::{create_profile, update_profile, delete_profile, list_profiles, get_profile, add_profile_term, list_profile_terms, delete_profile_term, ProfileRow, ProfileTermRow}`, `magnotia_ai_formatting::extract_corrections`.
|
||||
- Depends on: `lumotia_storage::{create_profile, update_profile, delete_profile, list_profiles, get_profile, add_profile_term, list_profile_terms, delete_profile_term, ProfileRow, ProfileTermRow}`, `lumotia_ai_formatting::extract_corrections`.
|
||||
- Called from frontend at: Settings → Profiles (full CRUD), profile picker, History viewer (the auto-learn flow runs after the user saves an edit).
|
||||
|
||||
## What's in here
|
||||
@@ -47,7 +47,7 @@ Constant so the auto-learn rows are uniformly tagged.
|
||||
### `learn_profile_terms_from_edit_cmd` (`:151`)
|
||||
|
||||
1. Pull the existing terms (so the diff doesn't propose duplicates).
|
||||
2. Call `magnotia_ai_formatting::extract_corrections(&original, &edited, &existing_terms)`.
|
||||
2. Call `lumotia_ai_formatting::extract_corrections(&original, &edited, &existing_terms)`.
|
||||
3. Persist each new term via `add_profile_term` with the `AUTO_LEARNED_NOTE`.
|
||||
4. Return the freshly-inserted DTOs.
|
||||
|
||||
|
||||
@@ -47,11 +47,11 @@ Frontend-facing structs. `SystemInfo` carries `ram_mb`, `cpu_brand`, `cpu_cores`
|
||||
|
||||
### `probe_system() -> Result<SystemInfo, String>` (`src-tauri/src/commands/hardware.rs:29`)
|
||||
|
||||
Wraps `magnotia_core::hardware::probe_system`. Maps the OS enum to a string and the GPU vendor (if probed) to its `Debug` form.
|
||||
Wraps `lumotia_core::hardware::probe_system`. Maps the OS enum to a string and the GPU vendor (if probed) to its `Debug` form.
|
||||
|
||||
### `rank_models() -> Result<Vec<ModelRecommendation>, String>` (`src-tauri/src/commands/hardware.rs:49`)
|
||||
|
||||
Calls `magnotia_core::recommendation::rank_recommendations` and decorates each entry with `magnotia_transcription::is_downloaded`. Used by Settings → Models for the "recommended for your hardware" list.
|
||||
Calls `lumotia_core::recommendation::rank_recommendations` and decorates each entry with `lumotia_transcription::is_downloaded`. Used by Settings → Models for the "recommended for your hardware" list.
|
||||
|
||||
Both commands are unguarded (any window can call). Pure read-only probes.
|
||||
|
||||
@@ -65,7 +65,7 @@ Tauri-managed: `lister: Mutex<ProcessLister>`. Holds a long-lived `ProcessLister
|
||||
|
||||
Phase 8 meeting auto-capture (single-signal variant). Frontend polls this on an interval with the user's app patterns. On a positive hit, the frontend surfaces a non-modal toast that reminds the user to start recording with their hotkey. We do NOT start recording from this signal — the user decides.
|
||||
|
||||
If `patterns` is empty, returns an empty Vec without locking. Otherwise locks the `lister`, snapshots the process list, and runs `magnotia_core::process_watch::match_meeting_patterns` to filter. Returns the matched process names.
|
||||
If `patterns` is empty, returns an empty Vec without locking. Otherwise locks the `lister`, snapshots the process list, and runs `lumotia_core::process_watch::match_meeting_patterns` to filter. Returns the matched process names.
|
||||
|
||||
Watch-out: the `ProcessLister` lock is `std::sync::Mutex`. If the snapshot ever takes meaningful time, switch to async.
|
||||
|
||||
@@ -77,7 +77,7 @@ Watch-out: the `ProcessLister` lock is `std::sync::Mutex`. If the snapshot ever
|
||||
|
||||
### `deliver_nudge(app, window, input: DeliverNudgeInput) -> Result<(), String>` (`src-tauri/src/commands/nudges.rs:42`)
|
||||
|
||||
Phase 6. Main-window only via `ensure_main_window`. Trims title and body; if both are empty, return Ok silently (a blank nudge is worse than no nudge). Defaults the title to `"Magnotia"` if only the body is present. Calls `tauri_plugin_notification::NotificationExt::notification().builder().title(...).body(...).show()`.
|
||||
Phase 6. Main-window only via `ensure_main_window`. Trims title and body; if both are empty, return Ok silently (a blank nudge is worse than no nudge). Defaults the title to `"Lumotia"` if only the body is present. Calls `tauri_plugin_notification::NotificationExt::notification().builder().title(...).body(...).show()`.
|
||||
|
||||
The frontend nudge bus (`nudgeBus.svelte.ts`) owns cadence, suppression, and the hourly cap. This command is a blunt "push it now" primitive — no rate limiting at the Rust layer. Errors propagate verbatim so the bus can log + swallow.
|
||||
|
||||
@@ -85,7 +85,7 @@ The frontend nudge bus (`nudgeBus.svelte.ts`) owns cadence, suppression, and the
|
||||
|
||||
### Morning-triage sentinel
|
||||
|
||||
Frontend owns rendering and logic; this module only persists the "last date the morning triage modal was shown" sentinel under SQLite settings key `magnotia_morning_triage_last_shown`.
|
||||
Frontend owns rendering and logic; this module only persists the "last date the morning triage modal was shown" sentinel under SQLite settings key `lumotia_morning_triage_last_shown`.
|
||||
|
||||
- `get_last_morning_triage(state) -> Result<Option<String>, String>` (`src-tauri/src/commands/rituals.rs:23`).
|
||||
- `mark_morning_triage_shown(state, date: String) -> Result<(), String>` (`src-tauri/src/commands/rituals.rs:36`). Caller passes a YYYY-MM-DD string in the user's local timezone — Rust deliberately stays timezone-agnostic.
|
||||
|
||||
@@ -9,7 +9,7 @@ last_verified: 2026/05/09
|
||||
|
||||
> **Where you are:** [Architecture map](../../README.md) → [Tauri runtime](../README.md) → [Commands](README.md) → Tasks
|
||||
|
||||
**Plain English summary.** Eleven commands wrapping the `magnotia_storage` task CRUD plus two LLM-driven actions. Standard CRUD: create / list / update / complete / uncomplete / delete a task, plus subtask CRUD (insert / list / complete) and a daily-completion-counts query for the Phase 8 Tasks-page momentum sparkline. The two LLM actions: `decompose_and_store` (break a parent task into subtasks via the local LLM with HITL feedback as few-shot exemplars) and `extract_tasks_from_transcript_cmd` (extract task lines from a recently-finished transcript, again with feedback exemplars).
|
||||
**Plain English summary.** Eleven commands wrapping the `lumotia_storage` task CRUD plus two LLM-driven actions. Standard CRUD: create / list / update / complete / uncomplete / delete a task, plus subtask CRUD (insert / list / complete) and a daily-completion-counts query for the Phase 8 Tasks-page momentum sparkline. The two LLM actions: `decompose_and_store` (break a parent task into subtasks via the local LLM with HITL feedback as few-shot exemplars) and `extract_tasks_from_transcript_cmd` (extract task lines from a recently-finished transcript, again with feedback exemplars).
|
||||
|
||||
## At a glance
|
||||
|
||||
@@ -29,7 +29,7 @@ last_verified: 2026/05/09
|
||||
- `complete_subtask_cmd(state, subtask_id) -> Result<(), String>`.
|
||||
- `list_recent_completions_cmd(state, days) -> Result<Vec<DailyCompletionCount>, String>`.
|
||||
- Events emitted: none.
|
||||
- Depends on: `magnotia_storage::{insert_task, list_tasks, update_task, complete_task, uncomplete_task, delete_task, set_task_energy, get_task_by_id, insert_subtask, list_subtasks, complete_subtask_and_check_parent, list_recent_completions, list_feedback_examples, FeedbackTargetType, TaskRow, DailyCompletionCount, FeedbackRow}`, `magnotia_llm::prompts::FeedbackExample`, `uuid::Uuid`.
|
||||
- Depends on: `lumotia_storage::{insert_task, list_tasks, update_task, complete_task, uncomplete_task, delete_task, set_task_energy, get_task_by_id, insert_subtask, list_subtasks, complete_subtask_and_check_parent, list_recent_completions, list_feedback_examples, FeedbackTargetType, TaskRow, DailyCompletionCount, FeedbackRow}`, `lumotia_llm::prompts::FeedbackExample`, `uuid::Uuid`.
|
||||
- Called from frontend at: Tasks page (CRUD, subtasks, sparkline), dictation result panel ("Extract tasks" button), parent-task expand UI ("Decompose").
|
||||
|
||||
## What's in here
|
||||
@@ -106,7 +106,7 @@ Dictation panel Extract tasks -> extract_tasks_from_transcript_cmd(text, profile
|
||||
## Watch-outs
|
||||
|
||||
- **No `ensure_main_window` guard.** Tasks UI lives in the main window AND in the always-on-top task float (which has the secondary-windows capability). The float can call list / complete / set-energy / list-recent-completions etc. — that's intentional — but it can also fire `decompose_and_store` and `extract_tasks_from_transcript_cmd`, which spend LLM tokens. If you want to lock this down, add the guard to the LLM-spending commands.
|
||||
- **No `PowerAssertion`.** `decompose_and_store` and `extract_tasks_from_transcript_cmd` both run synchronous LLM inference for several seconds. macOS can App Nap them. Add `PowerAssertion::begin("magnotia LLM task decomposition")` and similar.
|
||||
- **No `PowerAssertion`.** `decompose_and_store` and `extract_tasks_from_transcript_cmd` both run synchronous LLM inference for several seconds. macOS can App Nap them. Add `PowerAssertion::begin("lumotia LLM task decomposition")` and similar.
|
||||
- **The patch shape passes `Option<String>` for every column.** Currently the storage layer's `update_task` uses COALESCE: `Some` overwrites, `None` preserves. This means there's no way to clear `notes` or `effort` to empty via `update_task_cmd` — you can only set them to a non-empty string. If a user wants to clear a field, they'd need a fresh task or a dedicated clear command.
|
||||
- **Decompose stores subtasks one-by-one in a loop** (`:329`). Each iteration is a separate DB transaction. Acceptable for typical 3–7-step decompositions; if a future LLM produces 50 steps, batch the inserts.
|
||||
- **`extract_tasks_from_transcript_cmd` returns just `Vec<String>` and does NOT insert.** Frontend chooses what to insert. Confusing because the sibling `decompose_and_store` *does* insert. Convention is dictated by UX (decomposition is one-click, extraction reviews-then-inserts).
|
||||
|
||||
@@ -20,7 +20,7 @@ last_verified: 2026/05/09
|
||||
- `transcribe_file(window, state, path, engine: Option<String>, model_id: Option<String>, language, initial_prompt, remove_fillers, british_english, anti_hallucination, format_mode, profile_id) -> Result<serde_json::Value, String>` — main-window only. Decodes the file, picks engine (default whisper), returns the result inline.
|
||||
- `transcribe_pcm_parakeet(window, state, app, samples, chunk_id, remove_fillers, british_english, anti_hallucination, format_mode, profile_id) -> Result<(), String>` — main-window only. Parakeet PCM. Emits `transcription-result`.
|
||||
- Events emitted: `transcription-result` (payload: `{ status: "transcription", segments, language, duration, chunk_id, inference_ms, raw_text }`) — fires from `transcribe_pcm` (`src-tauri/src/commands/transcription.rs:208`) and `transcribe_pcm_parakeet` (`:398`).
|
||||
- Depends on: `magnotia_audio::{decode_audio_file_limited, resample_to_16khz, probe_audio_duration_secs}`, `magnotia_core::types::{AudioSamples, Segment, Transcript, TranscriptionOptions}`, `magnotia_transcription::{LocalEngine, TimedTranscript}`, `magnotia_ai_formatting::{post_process_segments, FormatMode, PostProcessOptions}`, `magnotia_storage::{database, DEFAULT_PROFILE_ID}`. Plus `commands::build_initial_prompt`, `commands::models::{default_model_id_for_engine, ensure_model_loaded}`, `commands::security::ensure_main_window`.
|
||||
- Depends on: `lumotia_audio::{decode_audio_file_limited, resample_to_16khz, probe_audio_duration_secs}`, `lumotia_core::types::{AudioSamples, Segment, Transcript, TranscriptionOptions}`, `lumotia_transcription::{LocalEngine, TimedTranscript}`, `lumotia_ai_formatting::{post_process_segments, FormatMode, PostProcessOptions}`, `lumotia_storage::{database, DEFAULT_PROFILE_ID}`. Plus `commands::build_initial_prompt`, `commands::models::{default_model_id_for_engine, ensure_model_loaded}`, `commands::security::ensure_main_window`.
|
||||
- Called from frontend at: dictation page (PCM commands when not live), file-import flow (transcribe_file), Settings test page (file command for QA).
|
||||
|
||||
## What's in here
|
||||
@@ -55,7 +55,7 @@ Whisper-specific PCM path (no chunking — frontend is expected to keep the buff
|
||||
|
||||
1. `ensure_main_window`.
|
||||
2. Resolve `profile_id` (default `DEFAULT_PROFILE_ID`).
|
||||
3. Fetch `ProfileRow` and profile term list from `magnotia_storage::database`.
|
||||
3. Fetch `ProfileRow` and profile term list from `lumotia_storage::database`.
|
||||
4. Build effective Whisper prompt via `build_initial_prompt(&caller_prompt, &profile.initial_prompt, &profile_terms)`.
|
||||
5. `spawn_blocking` runs `engine.transcribe_sync` on the samples.
|
||||
6. Run `post_process_segments` (filler removal, British English conversion, anti-hallucination, format mode, dictionary terms, optional LLM cleanup via `state.llm_engine`).
|
||||
@@ -67,7 +67,7 @@ Whisper-specific PCM path (no chunking — frontend is expected to keep the buff
|
||||
2. Resolve profile + terms (same as PCM path).
|
||||
3. Default engine to `"whisper"`, default model id to `default_model_id_for_engine(&engine_name)`.
|
||||
4. `ensure_model_loaded(state, engine, model_id, None)` — None = no sequential-GPU guard.
|
||||
5. Probe audio duration via `magnotia_audio::probe_audio_duration_secs`. If > 2 hours, return a friendly error.
|
||||
5. Probe audio duration via `lumotia_audio::probe_audio_duration_secs`. If > 2 hours, return a friendly error.
|
||||
6. `spawn_blocking` decodes the file (`decode_audio_file_limited(path, Some(MAX_FILE_TRANSCRIPTION_SECS))`), resamples to 16 kHz mono, then runs `transcribe_samples_sync`.
|
||||
7. Run `post_process_segments`.
|
||||
8. Return a JSON value with `engine`, `modelId`, `segments`, `language`, `duration`, `inference_ms`, `raw_text`.
|
||||
|
||||
@@ -25,7 +25,7 @@ last_verified: 2026/05/09
|
||||
- `delete_transcript(state, id) -> Result<(), String>`.
|
||||
- `search_transcripts(state, query) -> Result<Vec<TranscriptDto>, String>` — FTS5; up to 50 best-rank.
|
||||
- Events emitted: none.
|
||||
- Depends on: `magnotia_storage::{insert_transcript, list_transcripts_paged, count_transcripts, get_transcript, update_transcript, update_transcript_meta, delete_transcript, search_transcripts, InsertTranscriptParams, TranscriptRow, DEFAULT_PROFILE_ID}`.
|
||||
- Depends on: `lumotia_storage::{insert_transcript, list_transcripts_paged, count_transcripts, get_transcript, update_transcript, update_transcript_meta, delete_transcript, search_transcripts, InsertTranscriptParams, TranscriptRow, DEFAULT_PROFILE_ID}`.
|
||||
- Called from frontend at: History page (list / search / get / update / delete), dictation result panel (`add_transcript`), Settings → About (`count_transcripts_command`), History viewer window (`update_transcript_meta_cmd` for star / template / language / llm_tags).
|
||||
|
||||
## What's in here
|
||||
@@ -44,7 +44,7 @@ Patch shape for Task 2.5 / Phase 9 metadata: each field is `Option`. `None` pres
|
||||
|
||||
### Commands
|
||||
|
||||
- `add_transcript` (`:103`) builds an `InsertTranscriptParams` from the wide request and calls `magnotia_storage::insert_transcript`. The FTS5 index is updated automatically by an SQLite trigger inside the storage crate.
|
||||
- `add_transcript` (`:103`) builds an `InsertTranscriptParams` from the wide request and calls `lumotia_storage::insert_transcript`. The FTS5 index is updated automatically by an SQLite trigger inside the storage crate.
|
||||
- `list_transcripts` (`:135`) — paginated with sane defaults.
|
||||
- `count_transcripts_command` (`:150`).
|
||||
- `get_transcript` (`:157`).
|
||||
|
||||
@@ -85,8 +85,8 @@ frontend invoke('tts_stop')
|
||||
- **Linux `spd-say` is non-blocking** — `tts_stop` cannot kill its synthesis once it has handed off to speech-dispatcher. The `stop_linux` extra call asks speech-dispatcher to flush its own queue, but that's a soft-stop, not a hard kill.
|
||||
- **Windows path is heavy.** Every speak-call spawns a PowerShell. Acceptable for one-off use; for a streaming TTS pattern you'd want to keep a long-lived child or use the `windows` crate's SAPI bindings directly.
|
||||
- **Voice id semantics differ per platform.** macOS uses the voice name; Linux uses an spd-say `-t` token; Windows uses the SAPI registered voice token. Frontend treats them as opaque strings, but a saved-voice in Settings will not survive a platform switch.
|
||||
- **Brand consistency.** `Magnotia` is being renamed to `Lumenote` (per personal memory `project_lumenote_naming.md`). The TTS module currently embeds the string `"magnotia LLM cleanup"` and `"magnotia"`-prefixed temp filenames; rebrand sweep follow-up.
|
||||
- **No power assertion.** Long read-aloud sessions on macOS could be idled by App Nap. Add `PowerAssertion::begin("magnotia TTS")` to `tts_speak` if longer transcripts ever become a primary use case.
|
||||
- **Brand consistency.** `Lumotia` is being renamed to `Lumenote` (per personal memory `project_lumenote_naming.md`). The TTS module currently embeds the string `"lumotia LLM cleanup"` and `"lumotia"`-prefixed temp filenames; rebrand sweep follow-up.
|
||||
- **No power assertion.** Long read-aloud sessions on macOS could be idled by App Nap. Add `PowerAssertion::begin("lumotia TTS")` to `tts_speak` if longer transcripts ever become a primary use case.
|
||||
|
||||
## See also
|
||||
|
||||
|
||||
Reference in New Issue
Block a user