chore: rebrand from Kon/Corbie to Magnotia

Replace all instances of the legacy product names "Kon" and "Corbie" with
"Magnotia" across user-facing copy, code identifiers, package names, bundle
ids, file paths, and documentation. Preserves the unrelated "konsole" (KDE
terminal) reference and the parent CORBEL company name.

- Renames 10 Rust crates (kon-* → magnotia-*) and the tauri binary
- Updates package.json, tauri.conf.json (productName + identifier)
- Renames CSS classes (kon-rh-* → magnotia-rh-*) and animations
- Renames brand and roadmap docs
- Regenerates Cargo.lock and package-lock.json

Verified: svelte-check passes; pure-rust crates compile under new names.
This commit is contained in:
Claude
2026-04-30 13:06:55 +00:00
parent 749403697a
commit 89c63891fa
186 changed files with 1297 additions and 1297 deletions

View File

@@ -1,22 +1,22 @@
[package]
name = "kon"
name = "magnotia"
version = "0.1.0"
description = "Kon — Think out loud"
description = "Magnotia — Think out loud"
authors = ["CORBEL Ltd"]
edition = "2021"
[lib]
name = "kon_lib"
name = "magnotia_lib"
crate-type = ["staticlib", "cdylib", "rlib"]
[features]
# Default build includes the Whisper backend. Disabling this feature
# also drops it from kon-transcription (see Cargo.toml in that crate)
# also drops it from magnotia-transcription (see Cargo.toml in that crate)
# so a --no-default-features workspace build does not pull whisper-rs-sys.
# load_model_from_disk returns a runtime error for Engine::Whisper when
# this feature is off; Parakeet continues to work.
default = ["whisper"]
whisper = ["kon-transcription/whisper"]
whisper = ["magnotia-transcription/whisper"]
[build-dependencies]
tauri-build = { version = "2", features = [] }
@@ -28,14 +28,14 @@ serde_json = "1"
[dependencies]
# Workspace crates
kon-core = { path = "../crates/core" }
kon-audio = { path = "../crates/audio" }
kon-transcription = { path = "../crates/transcription", default-features = false }
kon-ai-formatting = { path = "../crates/ai-formatting" }
kon-storage = { path = "../crates/storage" }
kon-cloud-providers = { path = "../crates/cloud-providers" }
kon-hotkey = { path = "../crates/hotkey" }
kon-llm = { path = "../crates/llm" }
magnotia-core = { path = "../crates/core" }
magnotia-audio = { path = "../crates/audio" }
magnotia-transcription = { path = "../crates/transcription", default-features = false }
magnotia-ai-formatting = { path = "../crates/ai-formatting" }
magnotia-storage = { path = "../crates/storage" }
magnotia-cloud-providers = { path = "../crates/cloud-providers" }
magnotia-hotkey = { path = "../crates/hotkey" }
magnotia-llm = { path = "../crates/llm" }
# Tauri. The `tray-icon` feature, the global-shortcut/window-state/
# autostart plugins are desktop-only — gated below under
@@ -90,7 +90,7 @@ tempfile = "3"
tauri = { version = "2", features = ["tray-icon"] }
tauri-plugin-global-shortcut = "2"
tauri-plugin-window-state = "2"
# Phase 5 rituals: register Corbie as a login-time autostart entry.
# Phase 5 rituals: register Magnotia as a login-time autostart entry.
# Handles platform differences (.desktop file on Linux, LaunchAgents plist
# on macOS, registry Run key on Windows) behind a single API.
tauri-plugin-autostart = "2"

View File

@@ -1,7 +1,7 @@
fn main() {
// INTERIM: both llama-cpp-sys-2 and whisper-rs-sys statically link
// their own copy of ggml, so GNU ld / lld see duplicate symbols when
// linking the kon binary and its lib-tests. --allow-multiple-definition
// linking the magnotia binary and its lib-tests. --allow-multiple-definition
// makes the linker pick the first definition; safe while both crates
// pin compatible ggml revisions. Replace with a system-ggml shared-lib
// setup as a follow-up.
@@ -16,7 +16,7 @@ fn main() {
/// Regression guard for brief item #2 (pre-emptive loopback LLM scope).
///
/// Kon's bundled llama.cpp server and any BYO Ollama install speak HTTP
/// Magnotia's bundled llama.cpp server and any BYO Ollama install speak HTTP
/// on `127.0.0.1:*`. If the `connect-src` CSP ever drops those entries,
/// `fetch()` from the webview to the local LLM silently 404s with an
/// opaque scope error (Vibe #438 / #487). We keep the current permit

View File

@@ -1,6 +1,6 @@
# Windows bundle resources
Files in this directory ship side-by-side with `kon.exe` to avoid the
Files in this directory ship side-by-side with `magnotia.exe` to avoid the
DLL-hell failure modes reported in Whispering #840 / #829 and Buzz
#1459. They are **not** committed to the repo.

View File

@@ -7,9 +7,9 @@ use tokio::sync::{mpsc as tokio_mpsc, Mutex as AsyncMutex};
use tokio::task::JoinHandle;
use crate::commands::security::ensure_main_window;
use kon_audio::{DeviceInfo, MicrophoneCapture, WavWriter};
use kon_core::constants::WHISPER_SAMPLE_RATE;
use kon_core::types::AudioSamples;
use magnotia_audio::{DeviceInfo, MicrophoneCapture, WavWriter};
use magnotia_core::constants::WHISPER_SAMPLE_RATE;
use magnotia_core::types::AudioSamples;
const MAX_NATIVE_CAPTURE_RETURN_SAMPLES: usize = WHISPER_SAMPLE_RATE as usize * 60 * 10;
@@ -360,8 +360,8 @@ pub fn resolve_recording_path(
/// collisions, which `SystemTime::now()` alone cannot guarantee
/// (two calls in the same clock tick can return identical nanos).
///
/// Format: `kon-<secs>-<nanos_in_sec>-<counter>.wav`, e.g.
/// `kon-1776828000-123456789-0000.wav`.
/// Format: `magnotia-<secs>-<nanos_in_sec>-<counter>.wav`, e.g.
/// `magnotia-1776828000-123456789-0000.wav`.
fn recording_filename() -> String {
let duration = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
@@ -369,11 +369,11 @@ fn recording_filename() -> String {
let secs = duration.as_secs();
let nanos = duration.subsec_nanos();
let counter = RECORDING_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
format!("kon-{secs}-{nanos:09}-{counter:04}.wav")
format!("magnotia-{secs}-{nanos:09}-{counter:04}.wav")
}
/// Process-lifetime monotonic counter for `recording_filename`. Starts
/// at 0 on each Kon launch; wall-clock secs/nanos still advance across
/// at 0 on each Magnotia launch; wall-clock secs/nanos still advance across
/// restarts, so cross-launch collisions are already impossible — the
/// counter is the last-mile guarantee against within-launch same-tick
/// collisions.
@@ -408,11 +408,11 @@ mod tests {
#[test]
fn recording_filename_has_expected_shape() {
let name = recording_filename();
assert!(name.starts_with("kon-"));
assert!(name.starts_with("magnotia-"));
assert!(name.ends_with(".wav"));
// Shape: kon-<digits>-<9 digits>-<>=4 digits>.wav
// Shape: magnotia-<digits>-<9 digits>-<>=4 digits>.wav
let rest = name
.strip_prefix("kon-")
.strip_prefix("magnotia-")
.and_then(|s| s.strip_suffix(".wav"))
.expect("shape prefix/suffix");
let parts: Vec<&str> = rest.split('-').collect();
@@ -519,7 +519,7 @@ pub async fn persist_audio_samples(
tokio::task::spawn_blocking(move || {
let audio = AudioSamples::mono_16khz(samples);
kon_audio::write_wav(&path_clone, &audio).map_err(|e| e.to_string())
magnotia_audio::write_wav(&path_clone, &audio).map_err(|e| e.to_string())
})
.await
.map_err(|e| e.to_string())??;

View File

@@ -1,7 +1,7 @@
//! Diagnostics: panic hook, frontend error capture, and the manual
//! diagnostic-report bundler used by Settings → About.
//!
//! Privacy posture (matches Kon's local-first positioning):
//! Privacy posture (matches Magnotia's local-first positioning):
//! - All capture is to disk only. Nothing is transmitted.
//! - The manual report bundler shows the user exactly what would be
//! shared and lets them choose to copy/save/email it.
@@ -15,7 +15,7 @@ use std::time::{SystemTime, UNIX_EPOCH};
use serde::{Deserialize, Serialize};
use kon_storage::{
use magnotia_storage::{
app_data_dir, crashes_dir, list_recent_errors, log_error, logs_dir, ErrorLogRow,
};
@@ -24,7 +24,7 @@ use crate::commands::security::ensure_main_window;
use crate::AppState;
const DEFAULT_RECENT_ERRORS: i64 = 50;
const KON_VERSION: &str = env!("CARGO_PKG_VERSION");
const MAGNOTIA_VERSION: &str = env!("CARGO_PKG_VERSION");
/// Install the Rust panic hook. Writes each panic to a separate file in
/// crashes_dir so the diagnostic-report bundler can attach them. Also
@@ -49,7 +49,7 @@ pub fn install_panic_hook() {
let path = crashes_dir().join(format!("{ts}-{short:04x}.crash"));
let payload = format!(
"Kon crash dump\n\
"Magnotia crash dump\n\
==============\n\
Version: {ver}\n\
Timestamp: {ts} (UTC seconds)\n\
@@ -60,7 +60,7 @@ pub fn install_panic_hook() {
\n\
OS: {os} {arch}\n\
RUST_BACKTRACE: {bt}\n",
ver = KON_VERSION,
ver = MAGNOTIA_VERSION,
ts = ts,
thread = std::thread::current().name().unwrap_or("<unnamed>"),
info = info,
@@ -311,8 +311,8 @@ async fn generate_diagnostic_report_inner(
let opts = options.unwrap_or_default();
let mut out = String::new();
out.push_str("# Kon diagnostic report\n\n");
out.push_str(&format!("- Version: `{}`\n", KON_VERSION));
out.push_str("# Magnotia diagnostic report\n\n");
out.push_str(&format!("- Version: `{}`\n", MAGNOTIA_VERSION));
out.push_str(&format!(
"- OS / arch: `{} / {}`\n",
std::env::consts::OS,
@@ -335,7 +335,7 @@ async fn generate_diagnostic_report_inner(
if opts.include_settings {
out.push_str("## Settings (sanitised)\n\n");
match kon_storage::get_setting(&state.db, "kon_preferences").await {
match magnotia_storage::get_setting(&state.db, "magnotia_preferences").await {
Ok(Some(json)) => {
out.push_str("```json\n");
out.push_str(&sanitise_preferences_json(&json));
@@ -405,7 +405,7 @@ async fn generate_diagnostic_report_inner(
if opts.include_log_tail {
out.push_str("## Log tail\n\n");
let log_path = logs_dir().join("kon.log");
let log_path = logs_dir().join("magnotia.log");
match read_tail(&log_path, 8000) {
Ok(tail) if tail.is_empty() => {
out.push_str("_(log file empty or not yet written)_\n\n");
@@ -421,7 +421,7 @@ async fn generate_diagnostic_report_inner(
out.push_str("---\n\n");
out.push_str(
"Generated by Kon. To share, copy the entire markdown above \
"Generated by Magnotia. To share, copy the entire markdown above \
and paste it into an email or issue. Email: jake@corbel.consulting.\n",
);
@@ -520,14 +520,14 @@ pub async fn save_diagnostic_report(
let _ = app; // reserved for future dialog integration
let report = generate_diagnostic_report_inner(&state, options).await?;
let dir = kon_storage::app_data_dir().join("diagnostic-reports");
let dir = magnotia_storage::app_data_dir().join("diagnostic-reports");
fs::create_dir_all(&dir).map_err(|e| format!("create dir: {e}"))?;
let ts = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
let path = dir.join(format!("kon-diagnostic-{ts}.md"));
let path = dir.join(format!("magnotia-diagnostic-{ts}.md"));
fs::write(&path, &report).map_err(|e| format!("write file: {e}"))?;
Ok(path.to_string_lossy().to_string())

View File

@@ -5,7 +5,7 @@
use serde::{Deserialize, Serialize};
use kon_storage::{
use magnotia_storage::{
list_feedback_examples as db_list_feedback_examples, record_feedback as db_record_feedback,
FeedbackRow, FeedbackTargetType, RecordFeedbackParams,
};

View File

@@ -35,7 +35,7 @@ mod tests {
#[tokio::test]
async fn write_text_file_errors_on_bad_parent() {
let result = write_text_file_cmd(
"/definitely-not-a-real-path-kon-phase9/out.md".into(),
"/definitely-not-a-real-path-magnotia-phase9/out.md".into(),
"x".into(),
)
.await;

View File

@@ -1,7 +1,7 @@
use serde::Serialize;
use kon_core::hardware::{self, Os};
use kon_core::recommendation;
use magnotia_core::hardware::{self, Os};
use magnotia_core::recommendation;
#[derive(Serialize)]
pub struct SystemInfo {
@@ -53,7 +53,7 @@ pub fn rank_models() -> Result<Vec<ModelRecommendation>, String> {
Ok(ranked
.into_iter()
.map(|scored| {
let downloaded = kon_transcription::is_downloaded(&scored.entry.id);
let downloaded = magnotia_transcription::is_downloaded(&scored.entry.id);
ModelRecommendation {
id: scored.entry.id.as_str().to_string(),
display_name: scored.entry.display_name,

View File

@@ -3,7 +3,7 @@ use std::sync::Arc;
use tauri::Emitter;
use tokio::sync::{mpsc, Mutex};
use kon_hotkey::{EvdevHotkeyListener, HotkeyCombo, HotkeyEvent};
use magnotia_hotkey::{EvdevHotkeyListener, HotkeyCombo, HotkeyEvent};
/// Managed state for the evdev hotkey listener.
pub struct HotkeyState {
@@ -30,11 +30,11 @@ pub fn is_wayland_session() -> bool {
/// Check whether evdev hotkey capture is available (user in `input` group, etc.).
#[tauri::command]
pub fn check_hotkey_access() -> Result<(), String> {
kon_hotkey::check_evdev_access()
magnotia_hotkey::check_evdev_access()
}
/// Start the evdev global hotkey listener. Emits "kon:hotkey-pressed" and
/// "kon:hotkey-released" events to the frontend.
/// Start the evdev global hotkey listener. Emits "magnotia:hotkey-pressed" and
/// "magnotia:hotkey-released" events to the frontend.
///
/// If a listener is already running, it is stopped first.
#[tauri::command]
@@ -64,10 +64,10 @@ pub async fn start_evdev_hotkey(
while let Some(event) = event_rx.recv().await {
match event {
HotkeyEvent::Pressed => {
let _ = app_clone.emit("kon:hotkey-pressed", ());
let _ = app_clone.emit("magnotia:hotkey-pressed", ());
}
HotkeyEvent::Released => {
let _ = app_clone.emit("kon:hotkey-released", ());
let _ = app_clone.emit("magnotia:hotkey-released", ());
}
}
}

View File

@@ -7,7 +7,7 @@
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use kon_storage::{
use magnotia_storage::{
delete_implementation_rule as db_delete_rule, get_task_by_id,
insert_implementation_rule as db_insert_rule, list_implementation_rules as db_list_rules,
mark_implementation_rule_fired as db_mark_rule_fired,

View File

@@ -19,13 +19,13 @@ use crate::commands::models::{default_model_id_for_engine, ensure_model_loaded};
use crate::commands::power::PowerAssertion;
use crate::commands::security::ensure_main_window;
use crate::AppState;
use kon_ai_formatting::{post_process_segments, FormatMode, PostProcessOptions};
use kon_audio::{
use magnotia_ai_formatting::{post_process_segments, FormatMode, PostProcessOptions};
use magnotia_audio::{
AudioChunk, CaptureRuntimeError, MicrophoneCapture, StreamingResampler, WavWriter,
};
use kon_core::constants::WHISPER_SAMPLE_RATE;
use kon_core::types::{AudioSamples, Segment, TranscriptionOptions};
use kon_transcription::LocalEngine;
use magnotia_core::constants::WHISPER_SAMPLE_RATE;
use magnotia_core::types::{AudioSamples, Segment, TranscriptionOptions};
use magnotia_transcription::LocalEngine;
const CHUNK_SAMPLES: usize = 32_000; // 2s at 16kHz
const OVERLAP_SAMPLES: usize = 4_000; // 0.25s at 16kHz
@@ -358,7 +358,7 @@ impl LiveSessionRuntime {
let _ = self.status_channel.send(LiveStatusMessage::Overload {
session_id: self.session_id,
dropped_audio_ms: self.state.dropped_audio_ms,
message: "Kon dropped older audio to keep live dictation responsive".into(),
message: "Magnotia dropped older audio to keep live dictation responsive".into(),
});
}
@@ -450,7 +450,7 @@ struct InferenceTask {
chunk_start_sample: u64,
trim_before_secs: f64,
duration_secs: f64,
rx: std::sync::mpsc::Receiver<Result<kon_transcription::TimedTranscript, String>>,
rx: std::sync::mpsc::Receiver<Result<magnotia_transcription::TimedTranscript, String>>,
}
#[derive(Debug, Clone)]
@@ -503,15 +503,15 @@ pub async fn start_live_transcription_session(
let resolved_profile_id = config
.profile_id
.clone()
.unwrap_or_else(|| kon_storage::DEFAULT_PROFILE_ID.to_string());
.unwrap_or_else(|| magnotia_storage::DEFAULT_PROFILE_ID.to_string());
let profile = kon_storage::database::get_profile(&state.db, &resolved_profile_id)
let profile = magnotia_storage::database::get_profile(&state.db, &resolved_profile_id)
.await
.map_err(|e| e.to_string())?
.ok_or_else(|| format!("Profile {resolved_profile_id} not found"))?;
let profile_terms: Vec<String> =
kon_storage::database::list_profile_terms(&state.db, &resolved_profile_id)
magnotia_storage::database::list_profile_terms(&state.db, &resolved_profile_id)
.await
.map_err(|e| e.to_string())?
.into_iter()
@@ -657,7 +657,7 @@ fn run_live_session(
// is a no-op. Keeping the guard in scope ties the assertion's
// lifetime to the session — when the function returns, the Drop
// impl lifts it. Item #9 in docs/whisper-ecosystem/brief.md.
let _power_guard = PowerAssertion::begin("kon live dictation session");
let _power_guard = PowerAssertion::begin("magnotia live dictation session");
LiveSessionRuntime::new(
session_id,
engine,
@@ -1554,8 +1554,8 @@ mod tests {
let stop_flag = Arc::new(AtomicBool::new(false));
let (tx1, rx1) = std::sync::mpsc::channel();
tx1.send(Ok(kon_transcription::TimedTranscript {
transcript: kon_core::types::Transcript::new(
tx1.send(Ok(magnotia_transcription::TimedTranscript {
transcript: magnotia_core::types::Transcript::new(
vec![segment(0.0, 0.8, "first chunk")],
"en".into(),
0.8,
@@ -1596,8 +1596,8 @@ mod tests {
);
let (tx2, rx2) = std::sync::mpsc::channel();
tx2.send(Ok(kon_transcription::TimedTranscript {
transcript: kon_core::types::Transcript::new(
tx2.send(Ok(magnotia_transcription::TimedTranscript {
transcript: magnotia_core::types::Transcript::new(
vec![segment(0.0, 0.9, "second chunk")],
"en".into(),
0.9,

View File

@@ -3,10 +3,10 @@ use tauri::{Emitter, State};
use crate::commands::power::PowerAssertion;
use crate::commands::security::ensure_main_window;
use crate::AppState;
use kon_ai_formatting::{llm_cleanup_text, LlmPromptPreset};
use kon_core::hardware;
use kon_llm::model_manager::{self, model_info};
use kon_llm::{ContentTags, LlmModelId};
use magnotia_ai_formatting::{llm_cleanup_text, LlmPromptPreset};
use magnotia_core::hardware;
use magnotia_llm::model_manager::{self, model_info};
use magnotia_llm::{ContentTags, LlmModelId};
#[derive(Debug, serde::Serialize)]
#[serde(rename_all = "camelCase")]
@@ -74,7 +74,7 @@ pub async fn download_llm_model(
0
};
let _ = app_clone.emit(
"kon:llm-download-progress",
"magnotia:llm-download-progress",
serde_json::json!({
"modelId": id.as_str(),
"done": done,
@@ -182,7 +182,7 @@ pub struct LlmTestResult {
/// The point is that the user sees "Not enough GPU memory — pick a
/// smaller tier" rather than a raw C++ exception bubbled up from
/// llama.cpp. Mirrors OpenWhispr's "Test connection" UX for cloud
/// LLMs, adapted to Kon's local stack.
/// LLMs, adapted to Magnotia's local stack.
#[tauri::command]
pub async fn test_llm_model(
window: tauri::WebviewWindow,
@@ -293,7 +293,7 @@ fn classify_llm_load_error(raw: &str) -> (&'static str, &'static str) {
} else if lower.contains("permission denied") || lower.contains("access is denied") {
(
"load-failed-permission",
"Permission denied reading the model file. Check ownership of ~/.kon/models/llm/.",
"Permission denied reading the model file. Check ownership of ~/.magnotia/models/llm/.",
)
} else {
(
@@ -369,9 +369,9 @@ pub async fn cleanup_transcript_text_cmd(
) -> Result<String, String> {
ensure_main_window(&window)?;
let resolved_profile_id =
profile_id.unwrap_or_else(|| kon_storage::DEFAULT_PROFILE_ID.to_string());
profile_id.unwrap_or_else(|| magnotia_storage::DEFAULT_PROFILE_ID.to_string());
let profile_terms: Vec<String> =
kon_storage::database::list_profile_terms(&state.db, &resolved_profile_id)
magnotia_storage::database::list_profile_terms(&state.db, &resolved_profile_id)
.await
.map_err(|e| e.to_string())?
.into_iter()
@@ -391,7 +391,7 @@ pub async fn cleanup_transcript_text_cmd(
// macOS: pin a power assertion for the duration of the LLM
// generation so App Nap can't decide to throttle us mid-token.
// No-op on every other OS. Item #9.
let _power_guard = PowerAssertion::begin("kon LLM cleanup");
let _power_guard = PowerAssertion::begin("magnotia LLM cleanup");
llm_cleanup_text(&engine, &transcript, &profile_terms, resolved_preset)
})
.await
@@ -414,7 +414,7 @@ pub async fn extract_content_tags_cmd(
}
let engine = state.llm_engine.clone();
tokio::task::spawn_blocking(move || {
let _power_guard = PowerAssertion::begin("kon LLM content-tag extraction");
let _power_guard = PowerAssertion::begin("magnotia LLM content-tag extraction");
engine.extract_content_tags(&transcript)
})
.await

View File

@@ -7,7 +7,7 @@
use std::sync::Mutex;
use kon_core::process_watch::{self, ProcessLister};
use magnotia_core::process_watch::{self, ProcessLister};
/// Tauri-managed state for the meeting poller. Holds a long-lived
/// `ProcessLister` so each poll refreshes the existing `sysinfo::System`

View File

@@ -5,14 +5,14 @@ use tauri::Emitter;
use crate::commands::security::ensure_main_window;
use crate::AppState;
use kon_core::constants::WHISPER_SAMPLE_RATE;
use kon_core::hardware::{self, CpuFeatures};
use kon_core::model_registry::{self, Engine, LanguageSupport, ModelEntry};
use kon_core::types::{AudioSamples, ModelId, TranscriptionOptions};
use magnotia_core::constants::WHISPER_SAMPLE_RATE;
use magnotia_core::hardware::{self, CpuFeatures};
use magnotia_core::model_registry::{self, Engine, LanguageSupport, ModelEntry};
use magnotia_core::types::{AudioSamples, ModelId, TranscriptionOptions};
#[cfg(feature = "whisper")]
use kon_transcription::load_whisper;
use kon_transcription::model_manager;
use kon_transcription::{load_parakeet, LocalEngine, Transcriber};
use magnotia_transcription::load_whisper;
use magnotia_transcription::model_manager;
use magnotia_transcription::{load_parakeet, LocalEngine, Transcriber};
/// Map legacy size strings to ModelId.
fn whisper_model_id(size: &str) -> ModelId {
@@ -94,7 +94,7 @@ pub fn load_model_from_disk(model_id: &ModelId) -> Result<Box<dyn Transcriber +
}
#[cfg(not(feature = "whisper"))]
Engine::Whisper => Err(format!(
"Whisper backend not compiled in this build (kon built without the \"whisper\" feature); \
"Whisper backend not compiled in this build (magnotia built without the \"whisper\" feature); \
cannot load {model_id}"
)),
Engine::Parakeet => {

View File

@@ -56,7 +56,7 @@ pub fn deliver_nudge(
app.notification()
.builder()
.title(if title.is_empty() { "Corbie" } else { title })
.title(if title.is_empty() { "Magnotia" } else { title })
.body(body)
.show()
.map_err(|e| format!("notification delivery failed: {e}"))

View File

@@ -11,7 +11,7 @@
//! ydotool / osascript / SendKeys), so we probe and fall back.
//!
//! NOTE: focus must already be on the target window when the keystroke
//! fires. The global hotkey flow preserves this naturally; clicking Kon's
//! fires. The global hotkey flow preserves this naturally; clicking Magnotia's
//! window does not. The frontend surfaces this caveat next to the toggle.
use std::process::Command;
@@ -57,10 +57,10 @@ pub struct PasteOutcome {
/// focused window. Returns a structured result so the frontend can surface
/// partial success (clipboard set, paste failed).
///
/// Wayland compositor quirk: if Kon's always-on-top preview overlay is
/// Wayland compositor quirk: if Magnotia's always-on-top preview overlay is
/// visible when the keystroke fires, KWin / Mutter may resolve the keystroke
/// against the overlay (even with `focused: false` set at build time) and
/// the paste lands inside Kon instead of the previously-focused app. We hide
/// the paste lands inside Magnotia instead of the previously-focused app. We hide
/// the preview window and give the compositor a beat to re-focus the real
/// target before dispatching. Matches OpenWhispr's PR #246 fix on GNOME.
#[tauri::command]

View File

@@ -1,5 +1,5 @@
// Tauri commands wrapping kon_storage profile + profile_term CRUD.
// Pattern mirrors tasks.rs — flat imports from `kon_storage` with a `db_`
// Tauri commands wrapping magnotia_storage profile + profile_term CRUD.
// Pattern mirrors tasks.rs — flat imports from `magnotia_storage` with a `db_`
// alias prefix to avoid name collisions with the command functions, plain
// snake_case parameters (Tauri 2.x auto-converts camelCase JS keys),
// `.map_err(|e| e.to_string())` for error conversion, and camelCase DTOs
@@ -11,8 +11,8 @@
use serde::Serialize;
use kon_ai_formatting::extract_corrections;
use kon_storage::{
use magnotia_ai_formatting::extract_corrections;
use magnotia_storage::{
add_profile_term as db_add_profile_term, create_profile as db_create_profile,
delete_profile as db_delete_profile, delete_profile_term as db_delete_profile_term,
get_profile as db_get_profile, list_profile_terms as db_list_profile_terms,

View File

@@ -7,14 +7,14 @@
//! filtering rather than adding a second query path.
//!
//! Stored under the existing SQLite settings table via
//! `kon_storage::{get_setting, set_setting}` — same bag as
//! `kon_preferences`.
//! `magnotia_storage::{get_setting, set_setting}` — same bag as
//! `magnotia_preferences`.
use kon_storage::{get_setting, set_setting};
use magnotia_storage::{get_setting, set_setting};
use crate::AppState;
const LAST_TRIAGE_KEY: &str = "kon_morning_triage_last_shown";
const LAST_TRIAGE_KEY: &str = "magnotia_morning_triage_last_shown";
/// Returns the YYYY-MM-DD date string stored on the last successful
/// morning triage dismissal, or `None` if the user has never been

View File

@@ -1,4 +1,4 @@
// Tauri commands wrapping kon_storage task CRUD.
// Tauri commands wrapping magnotia_storage task CRUD.
// Pattern mirrors transcripts.rs — TaskDto is the camelCase frontend shape,
// storage functions are aliased with db_ prefix to avoid name collisions.
@@ -6,8 +6,8 @@ use serde::{Deserialize, Serialize};
use uuid::Uuid;
use kon_llm::prompts::FeedbackExample as LlmFeedbackExample;
use kon_storage::{
use magnotia_llm::prompts::FeedbackExample as LlmFeedbackExample;
use magnotia_storage::{
complete_subtask_and_check_parent as db_complete_subtask, complete_task as db_complete_task,
delete_task as db_delete_task, get_task_by_id as db_get_task,
insert_subtask as db_insert_subtask, insert_task as db_insert_task,

View File

@@ -11,9 +11,9 @@ use crate::commands::build_initial_prompt;
use crate::commands::models::{default_model_id_for_engine, ensure_model_loaded};
use crate::commands::security::ensure_main_window;
use crate::AppState;
use kon_ai_formatting::{post_process_segments, FormatMode, PostProcessOptions};
use kon_core::constants::WHISPER_SAMPLE_RATE;
use kon_core::types::{AudioSamples, Segment, Transcript, TranscriptionOptions};
use magnotia_ai_formatting::{post_process_segments, FormatMode, PostProcessOptions};
use magnotia_core::constants::WHISPER_SAMPLE_RATE;
use magnotia_core::types::{AudioSamples, Segment, Transcript, TranscriptionOptions};
const PARAKEET_CHUNK_THRESHOLD_SECS: usize = 18;
const PARAKEET_CHUNK_SECS: usize = 15;
@@ -31,7 +31,7 @@ struct ChunkingStrategy {
fn pick_engine(
state: &AppState,
engine: &str,
) -> Result<Arc<kon_transcription::LocalEngine>, String> {
) -> Result<Arc<magnotia_transcription::LocalEngine>, String> {
match engine {
"whisper" => Ok(state.whisper_engine.clone()),
"parakeet" => Ok(state.parakeet_engine.clone()),
@@ -72,11 +72,11 @@ fn trim_overlap_segments(segments: &mut Vec<Segment>, trim_before_secs: f64) {
}
fn transcribe_samples_sync(
engine: Arc<kon_transcription::LocalEngine>,
engine: Arc<magnotia_transcription::LocalEngine>,
engine_name: &str,
samples: Vec<f32>,
options: TranscriptionOptions,
) -> Result<kon_transcription::TimedTranscript, String> {
) -> Result<magnotia_transcription::TimedTranscript, String> {
let Some(strategy) = pick_chunking_strategy(engine_name, samples.len()) else {
let audio = AudioSamples::mono_16khz(samples);
return engine
@@ -127,7 +127,7 @@ fn transcribe_samples_sync(
chunk_start = chunk_end.saturating_sub(strategy.overlap_samples);
}
Ok(kon_transcription::TimedTranscript {
Ok(magnotia_transcription::TimedTranscript {
transcript: Transcript::new(
all_segments,
options.language.clone().unwrap_or_else(|| "en".to_string()),
@@ -155,15 +155,15 @@ pub async fn transcribe_pcm(
) -> Result<(), String> {
ensure_main_window(&window)?;
let resolved_profile_id =
profile_id.unwrap_or_else(|| kon_storage::DEFAULT_PROFILE_ID.to_string());
profile_id.unwrap_or_else(|| magnotia_storage::DEFAULT_PROFILE_ID.to_string());
let profile = kon_storage::database::get_profile(&state.db, &resolved_profile_id)
let profile = magnotia_storage::database::get_profile(&state.db, &resolved_profile_id)
.await
.map_err(|e| e.to_string())?
.ok_or_else(|| format!("Profile {resolved_profile_id} not found"))?;
let profile_terms: Vec<String> =
kon_storage::database::list_profile_terms(&state.db, &resolved_profile_id)
magnotia_storage::database::list_profile_terms(&state.db, &resolved_profile_id)
.await
.map_err(|e| e.to_string())?
.into_iter()
@@ -249,15 +249,15 @@ pub async fn transcribe_file(
) -> Result<serde_json::Value, String> {
ensure_main_window(&window)?;
let resolved_profile_id =
profile_id.unwrap_or_else(|| kon_storage::DEFAULT_PROFILE_ID.to_string());
profile_id.unwrap_or_else(|| magnotia_storage::DEFAULT_PROFILE_ID.to_string());
let profile = kon_storage::database::get_profile(&state.db, &resolved_profile_id)
let profile = magnotia_storage::database::get_profile(&state.db, &resolved_profile_id)
.await
.map_err(|e| e.to_string())?
.ok_or_else(|| format!("Profile {resolved_profile_id} not found"))?;
let profile_terms: Vec<String> =
kon_storage::database::list_profile_terms(&state.db, &resolved_profile_id)
magnotia_storage::database::list_profile_terms(&state.db, &resolved_profile_id)
.await
.map_err(|e| e.to_string())?
.into_iter()
@@ -283,23 +283,23 @@ pub async fn transcribe_file(
let engine_name_for_worker = engine_name.clone();
let path_for_probe = Path::new(&path);
if let Some(duration_secs) =
kon_audio::probe_audio_duration_secs(path_for_probe).map_err(|e| e.to_string())?
magnotia_audio::probe_audio_duration_secs(path_for_probe).map_err(|e| e.to_string())?
{
if duration_secs > MAX_FILE_TRANSCRIPTION_SECS {
return Err(format!(
"File is {:.1} hours long. Kon imports up to 2 hours at a time.",
"File is {:.1} hours long. Magnotia imports up to 2 hours at a time.",
duration_secs / 3600.0
));
}
}
let timed = tokio::task::spawn_blocking(move || {
let audio = kon_audio::decode_audio_file_limited(
let audio = magnotia_audio::decode_audio_file_limited(
Path::new(&path),
Some(MAX_FILE_TRANSCRIPTION_SECS),
)
.map_err(|e| e.to_string())?;
let resampled = kon_audio::resample_to_16khz(&audio).map_err(|e| e.to_string())?;
let resampled = magnotia_audio::resample_to_16khz(&audio).map_err(|e| e.to_string())?;
transcribe_samples_sync(
engine,
&engine_name_for_worker,
@@ -353,17 +353,17 @@ pub async fn transcribe_pcm_parakeet(
) -> Result<(), String> {
ensure_main_window(&window)?;
let resolved_profile_id =
profile_id.unwrap_or_else(|| kon_storage::DEFAULT_PROFILE_ID.to_string());
profile_id.unwrap_or_else(|| magnotia_storage::DEFAULT_PROFILE_ID.to_string());
// Validate the profile exists so parakeet and whisper behave identically
// when a bogus id slips through from the frontend.
kon_storage::database::get_profile(&state.db, &resolved_profile_id)
magnotia_storage::database::get_profile(&state.db, &resolved_profile_id)
.await
.map_err(|e| e.to_string())?
.ok_or_else(|| format!("Profile {resolved_profile_id} not found"))?;
let profile_terms: Vec<String> =
kon_storage::database::list_profile_terms(&state.db, &resolved_profile_id)
magnotia_storage::database::list_profile_terms(&state.db, &resolved_profile_id)
.await
.map_err(|e| e.to_string())?
.into_iter()

View File

@@ -1,4 +1,4 @@
// Tauri commands wrapping the kon_storage transcript CRUD.
// Tauri commands wrapping the magnotia_storage transcript CRUD.
// These are the bridge that lets the Svelte frontend treat SQLite as the
// canonical store rather than localStorage.
//
@@ -13,7 +13,7 @@
use serde::{Deserialize, Serialize};
use kon_storage::{
use magnotia_storage::{
count_transcripts, delete_transcript as db_delete_transcript,
get_transcript as db_get_transcript, insert_transcript as db_insert_transcript,
list_transcripts_paged, search_transcripts as db_search_transcripts,
@@ -29,7 +29,7 @@ use crate::AppState;
///
/// Task 2.5 — `starred`, `manualTags`, `template`, `language`, `segmentsJson`
/// were added to back the viewer metadata that previously lived only in the
/// removed `kon_history` localStorage cache.
/// removed `magnotia_history` localStorage cache.
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct TranscriptDto {
@@ -111,7 +111,7 @@ pub async fn add_transcript(
profile_id: transcript
.profile_id
.as_deref()
.unwrap_or(kon_storage::DEFAULT_PROFILE_ID),
.unwrap_or(magnotia_storage::DEFAULT_PROFILE_ID),
title: transcript.title.as_deref(),
audio_path: transcript.audio_path.as_deref(),
duration: transcript.duration,

View File

@@ -60,7 +60,7 @@ pub async fn open_task_window(app: tauri::AppHandle) -> Result<(), String> {
let mut builder =
WebviewWindowBuilder::new(&app, "tasks-float", WebviewUrl::App("/float".into()))
.title("Kon Tasks")
.title("Magnotia Tasks")
.inner_size(480.0, 520.0)
.min_inner_size(360.0, 480.0)
.always_on_top(true)
@@ -113,7 +113,7 @@ pub async fn open_preview_window(app: tauri::AppHandle) -> Result<(), String> {
"transcription-preview",
WebviewUrl::App("/preview".into()),
)
.title("Kon — Preview")
.title("Magnotia — Preview")
.inner_size(420.0, 200.0)
.min_inner_size(360.0, 140.0)
.max_inner_size(520.0, 360.0)
@@ -178,7 +178,7 @@ pub async fn open_viewer_window(app: tauri::AppHandle) -> Result<(), String> {
let mut builder =
WebviewWindowBuilder::new(&app, "transcript-viewer", WebviewUrl::App("/viewer".into()))
.title("Kon - Transcription Editor")
.title("Magnotia - Transcription Editor")
.inner_size(600.0, 700.0)
.min_inner_size(560.0, 520.0)
.decorations(use_native_decorations)

View File

@@ -10,9 +10,9 @@ use std::time::Instant;
use sqlx::SqlitePool;
use tauri::Manager;
use kon_core::types::EngineName;
use kon_llm::LlmEngine;
use kon_storage::{
use magnotia_core::types::EngineName;
use magnotia_llm::LlmEngine;
use magnotia_storage::{
database_path, get_setting, init as init_db, prune_error_log, set_setting,
};
@@ -20,7 +20,7 @@ use kon_storage::{
/// 90 days is long enough to triage "this happened a few weeks ago"
/// reports and short enough that the table stays kilobyte-scale.
const ERROR_LOG_RETENTION_DAYS: i64 = 90;
use kon_transcription::LocalEngine;
use magnotia_transcription::LocalEngine;
/// Shared app state holding the transcription engines and database pool.
pub struct AppState {
@@ -75,7 +75,7 @@ async fn save_preferences(
state: tauri::State<'_, AppState>,
preferences: String,
) -> Result<(), String> {
set_setting(&state.db, "kon_preferences", &preferences)
set_setting(&state.db, "magnotia_preferences", &preferences)
.await
.map_err(|e| e.to_string())
}
@@ -197,7 +197,7 @@ pub fn run() {
// Load saved preferences for webview injection
let t1 = Instant::now();
let prefs_json = tauri::async_runtime::block_on(async {
get_setting(&db, "kon_preferences").await.unwrap_or(None)
get_setting(&db, "magnotia_preferences").await.unwrap_or(None)
});
eprintln!("[startup] Preferences load: {:?}", t1.elapsed());
let init_script = build_preferences_script(prefs_json);
@@ -439,5 +439,5 @@ pub fn run() {
commands::update::install_update,
])
.run(tauri::generate_context!())
.expect("error while running Kon");
.expect("error while running Magnotia");
}

View File

@@ -1,5 +1,5 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
fn main() {
kon_lib::run()
magnotia_lib::run()
}

View File

@@ -4,7 +4,7 @@ use tauri::tray::TrayIconBuilder;
use tauri::{Emitter, Manager};
pub fn setup(app: &tauri::App) -> Result<(), Box<dyn std::error::Error>> {
let show = MenuItemBuilder::with_id("show", "Show Kon").build(app)?;
let show = MenuItemBuilder::with_id("show", "Show Magnotia").build(app)?;
let status = MenuItemBuilder::with_id("status", "Ready")
.enabled(false)
.build(app)?;
@@ -32,7 +32,7 @@ pub fn setup(app: &tauri::App) -> Result<(), Box<dyn std::error::Error>> {
let _tray = TrayIconBuilder::new()
.icon(icon)
.tooltip("Kon — Ready")
.tooltip("Magnotia — Ready")
.menu(&menu)
.on_menu_event(move |app, event| match event.id().as_ref() {
"show" => {
@@ -48,7 +48,7 @@ pub fn setup(app: &tauri::App) -> Result<(), Box<dyn std::error::Error>> {
}
// The frontend layout listens for this event and routes
// to the Phase 5 wind-down page.
let _ = app.emit("kon:open-wind-down", ());
let _ = app.emit("magnotia:open-wind-down", ());
}
"quit" => {
app.exit(0);

View File

@@ -1,8 +1,8 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "Kon",
"productName": "Magnotia",
"version": "0.1.0",
"identifier": "uk.co.corbel.kon",
"identifier": "uk.co.corbel.magnotia",
"build": {
"beforeDevCommand": "npm run dev:frontend",
"devUrl": "http://localhost:1420",
@@ -12,7 +12,7 @@
"app": {
"windows": [
{
"title": "Kon",
"title": "Magnotia",
"width": 1020,
"height": 720,
"minWidth": 960,

View File

@@ -3,7 +3,7 @@
"app": {
"windows": [
{
"title": "Kon",
"title": "Magnotia",
"width": 1020,
"height": 720,
"minWidth": 960,