diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7acc75d..b024eaa 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -131,7 +131,7 @@ jobs: uses: Swatinem/rust-cache@v2 with: workspaces: . - shared-key: kon-build-${{ matrix.os }} + shared-key: magnotia-build-${{ matrix.os }} - name: Install JS deps run: npm ci @@ -165,7 +165,7 @@ jobs: if: always() uses: actions/upload-artifact@v4 with: - name: kon-${{ matrix.os }}-${{ github.sha }} + name: magnotia-${{ matrix.os }}-${{ github.sha }} path: ${{ matrix.artifact_glob }} retention-days: 30 if-no-files-found: warn diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 202aaf0..be55699 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -125,7 +125,7 @@ jobs: uses: Swatinem/rust-cache@v2 with: workspaces: . - shared-key: kon-${{ matrix.os }} + shared-key: magnotia-${{ matrix.os }} - name: cargo check (workspace) run: cargo check --workspace --all-targets diff --git a/Cargo.lock b/Cargo.lock index 368d51c..882a944 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2574,156 +2574,6 @@ dependencies = [ "unicode-segmentation", ] -[[package]] -name = "kon" -version = "0.1.0" -dependencies = [ - "arboard", - "base64 0.22.1", - "gdk", - "gtk", - "kon-ai-formatting", - "kon-audio", - "kon-cloud-providers", - "kon-core", - "kon-hotkey", - "kon-llm", - "kon-storage", - "kon-transcription", - "libloading 0.8.9", - "objc2", - "objc2-foundation", - "serde", - "serde_json", - "sqlx", - "tauri", - "tauri-build", - "tauri-plugin-autostart", - "tauri-plugin-dialog", - "tauri-plugin-global-shortcut", - "tauri-plugin-notification", - "tauri-plugin-opener", - "tauri-plugin-window-state", - "tempfile", - "tokio", - "uuid", - "webkit2gtk", -] - -[[package]] -name = "kon-ai-formatting" -version = "0.1.0" -dependencies = [ - "kon-core", - "kon-llm", - "regex-lite", -] - -[[package]] -name = "kon-audio" -version = "0.1.0" -dependencies = [ - "cpal", - "hound", - "kon-core", - "rubato", - "serde", - "symphonia", - "tokio", -] - -[[package]] -name = "kon-cloud-providers" -version = "0.1.0" -dependencies = [ - "kon-core", -] - -[[package]] -name = "kon-core" -version = "0.1.0" -dependencies = [ - "async-trait", - "serde", - "serde_json", - "sysinfo", - "thiserror 2.0.18", -] - -[[package]] -name = "kon-hotkey" -version = "0.1.0" -dependencies = [ - "evdev", - "kon-core", - "log", - "nix 0.29.0", - "notify", - "serde", - "tokio", -] - -[[package]] -name = "kon-llm" -version = "0.1.0" -dependencies = [ - "encoding_rs", - "futures-util", - "kon-core", - "llama-cpp-2", - "num_cpus", - "reqwest 0.12.28", - "serde", - "serde_json", - "sha2", - "tempfile", - "thiserror 2.0.18", - "tokio", - "tracing", -] - -[[package]] -name = "kon-mcp" -version = "0.1.0" -dependencies = [ - "anyhow", - "kon-storage", - "serde", - "serde_json", - "sqlx", - "tempfile", - "tokio", -] - -[[package]] -name = "kon-storage" -version = "0.1.0" -dependencies = [ - "kon-core", - "log", - "serde", - "sqlx", - "tokio", - "uuid", -] - -[[package]] -name = "kon-transcription" -version = "0.1.0" -dependencies = [ - "futures-util", - "kon-core", - "num_cpus", - "reqwest 0.12.28", - "sha2", - "tempfile", - "thiserror 2.0.18", - "tokio", - "tracing", - "transcribe-rs", - "whisper-rs", -] - [[package]] name = "kqueue" version = "1.1.1" @@ -2935,6 +2785,156 @@ dependencies = [ "libc", ] +[[package]] +name = "magnotia" +version = "0.1.0" +dependencies = [ + "arboard", + "base64 0.22.1", + "gdk", + "gtk", + "libloading 0.8.9", + "magnotia-ai-formatting", + "magnotia-audio", + "magnotia-cloud-providers", + "magnotia-core", + "magnotia-hotkey", + "magnotia-llm", + "magnotia-storage", + "magnotia-transcription", + "objc2", + "objc2-foundation", + "serde", + "serde_json", + "sqlx", + "tauri", + "tauri-build", + "tauri-plugin-autostart", + "tauri-plugin-dialog", + "tauri-plugin-global-shortcut", + "tauri-plugin-notification", + "tauri-plugin-opener", + "tauri-plugin-window-state", + "tempfile", + "tokio", + "uuid", + "webkit2gtk", +] + +[[package]] +name = "magnotia-ai-formatting" +version = "0.1.0" +dependencies = [ + "magnotia-core", + "magnotia-llm", + "regex-lite", +] + +[[package]] +name = "magnotia-audio" +version = "0.1.0" +dependencies = [ + "cpal", + "hound", + "magnotia-core", + "rubato", + "serde", + "symphonia", + "tokio", +] + +[[package]] +name = "magnotia-cloud-providers" +version = "0.1.0" +dependencies = [ + "magnotia-core", +] + +[[package]] +name = "magnotia-core" +version = "0.1.0" +dependencies = [ + "async-trait", + "serde", + "serde_json", + "sysinfo", + "thiserror 2.0.18", +] + +[[package]] +name = "magnotia-hotkey" +version = "0.1.0" +dependencies = [ + "evdev", + "log", + "magnotia-core", + "nix 0.29.0", + "notify", + "serde", + "tokio", +] + +[[package]] +name = "magnotia-llm" +version = "0.1.0" +dependencies = [ + "encoding_rs", + "futures-util", + "llama-cpp-2", + "magnotia-core", + "num_cpus", + "reqwest 0.12.28", + "serde", + "serde_json", + "sha2", + "tempfile", + "thiserror 2.0.18", + "tokio", + "tracing", +] + +[[package]] +name = "magnotia-mcp" +version = "0.1.0" +dependencies = [ + "anyhow", + "magnotia-storage", + "serde", + "serde_json", + "sqlx", + "tempfile", + "tokio", +] + +[[package]] +name = "magnotia-storage" +version = "0.1.0" +dependencies = [ + "log", + "magnotia-core", + "serde", + "sqlx", + "tokio", + "uuid", +] + +[[package]] +name = "magnotia-transcription" +version = "0.1.0" +dependencies = [ + "futures-util", + "magnotia-core", + "num_cpus", + "reqwest 0.12.28", + "sha2", + "tempfile", + "thiserror 2.0.18", + "tokio", + "tracing", + "transcribe-rs", + "whisper-rs", +] + [[package]] name = "markup5ever" version = "0.14.1" diff --git a/HANDOVER-2026-04-17.md b/HANDOVER-2026-04-17.md index 75c4b46..fa08aa7 100644 --- a/HANDOVER-2026-04-17.md +++ b/HANDOVER-2026-04-17.md @@ -1,10 +1,10 @@ -# Kon Session Handover — 2026/04/17 +# Magnotia Session Handover — 2026/04/17 ## Session Summary Six-commit sprint executing the upgrade plan from -`/home/jake/Documents/CORBEL-Furnished-House/output/reports/kon-upgrade-plan-2026-04-17.md`. -Goal: get Kon from "core feature broken" to "ready to dogfood with friends." +`/home/jake/Documents/CORBEL-Furnished-House/output/reports/magnotia-upgrade-plan-2026-04-17.md`. +Goal: get Magnotia from "core feature broken" to "ready to dogfood with friends." ## Commits @@ -93,7 +93,7 @@ index in sync. Dictionary table also added in v2. New collapsible section. Add custom terms (medication names, jargon, people's names) that the LLM cleanup prompt should preserve. Backed by the `dictionary` SQLite table. The LLM client itself is currently a -stub; when wired, it imports `list_dictionary` from kon_storage and +stub; when wired, it imports `list_dictionary` from magnotia_storage and injects terms into the prompt suffix. ### Wayland self-relaunch @@ -109,14 +109,14 @@ HANDOVER env-var prefix is no longer needed. ```bash sudo dnf install cmake clang-devel -cd /home/jake/Documents/CORBEL-Projects/kon +cd /home/jake/Documents/CORBEL-Projects/magnotia npm install # if you have not already ``` ### Launch (no env-var prefix needed any more) ```bash -cd /home/jake/Documents/CORBEL-Projects/kon +cd /home/jake/Documents/CORBEL-Projects/magnotia npm run tauri dev ``` @@ -140,10 +140,10 @@ env GDK_BACKEND=x11 WINIT_UNIX_BACKEND=x11 \ 3. **Auto mode.** Clear the picker (set to "Auto"). Hit dictation. The logs (terminal where you ran `npm run tauri dev`) should show: - - `[kon-audio] start: enumerated N input device(s)` - - `[kon-audio] trying '...'` for each candidate - - `[kon-audio] '...' validation: M samples, rms=...` - - `[kon-audio] selected microphone: '...'` + - `[magnotia-audio] start: enumerated N input device(s)` + - `[magnotia-audio] trying '...'` for each candidate + - `[magnotia-audio] '...' validation: M samples, rms=...` + - `[magnotia-audio] selected microphone: '...'` The selected mic should NOT be a `.monitor` source. 4. **History rename.** Make a recording. In History, rename it to @@ -194,7 +194,7 @@ env GDK_BACKEND=x11 WINIT_UNIX_BACKEND=x11 \ | **Linux x86_64 (Fedora 43, KDE Wayland)** | ✓ | ✓ | The everyday dev target | **HIGH** — this is what the sprint was developed against | | **Linux x86_64 (other distros, X11)** | Should work | Should work | Wayland self-relaunch is no-op on X11 sessions, evdev hotkeys may need user added to `input` group | **MEDIUM** — tested patterns, untested distros | | **Windows 10/11 x86_64** | Untested but should compile (CPAL + Tauri + whisper.cpp all support it) | Untested | Custom evdev hotkeys are no-op; falls back to Tauri's global-shortcut plugin which works on Windows | **LOW** — theoretically supported, has had zero hands-on testing | -| **macOS aarch64 (Apple Silicon)** | Untested | Untested | Path bug fixed this commit (now uses `~/Library/Application Support/Kon/`); Info.plist needs `NSMicrophoneUsageDescription` for the app bundle | **LOW** — theoretically supported, has had zero hands-on testing | +| **macOS aarch64 (Apple Silicon)** | Untested | Untested | Path bug fixed this commit (now uses `~/Library/Application Support/Magnotia/`); Info.plist needs `NSMicrophoneUsageDescription` for the app bundle | **LOW** — theoretically supported, has had zero hands-on testing | | **macOS x86_64 (Intel)** | Same as Apple Silicon | Same | Same | **LOW** | **For the friends beta on Linux only**, this matters not at all. For a future Windows or macOS build, expect to spend a focused day or two debugging: @@ -207,7 +207,7 @@ env GDK_BACKEND=x11 WINIT_UNIX_BACKEND=x11 \ ### What this sprint added on the cross-platform front -- `crates/storage/src/file_storage.rs::app_data_dir()` now correctly handles macOS (`~/Library/Application Support/Kon/`) and Linux (XDG-aware, `~/.local/share/kon`, with legacy `~/.kon` fallback for existing installs). Windows path unchanged. +- `crates/storage/src/file_storage.rs::app_data_dir()` now correctly handles macOS (`~/Library/Application Support/Magnotia/`) and Linux (XDG-aware, `~/.local/share/magnotia`, with legacy `~/.magnotia` fallback for existing installs). Windows path unchanged. - New Tauri command `get_os_info` returns `{os, arch, family, usesCmd, isWayland, customHotkeyBackend, primaryModifierLabel}` so the frontend can adapt UI strings (Cmd vs Ctrl labels, "Open Finder" vs "Open Explorer", etc). - New `src/lib/utils/osInfo.js` helper: async `loadOsInfo()` warms a cache, then `isMac() / isWindows() / isLinux() / modKeyLabel() / isWayland()` are synchronous. Eagerly loaded at app startup in the root layout. - Falls back gracefully in browser-preview mode by reading `navigator.platform`. @@ -240,7 +240,7 @@ src/routes/+layout.svelte (+ ToastViewport mount) 1. Real-user feedback from one to three friends. What confuses them? What feels slow? What did they expect that did not happen? 2. Address the deferred items in priority of feedback signal. -3. Consider opening up the `kon-public-beta` channel — a single +3. Consider opening up the `magnotia-public-beta` channel — a single GitHub release with the auto-updater plumbed. 4. The architecture review's other items (frontend test coverage, monolithic component split, hardcoded hex colours, ARIA gaps) @@ -248,6 +248,6 @@ src/routes/+layout.svelte (+ ToastViewport mount) --- -*Compiled 2026/04/17 by Wren. Kon goes from "live transcription does not +*Compiled 2026/04/17 by Wren. Magnotia goes from "live transcription does not work" to "ready to put in front of one trusted friend." Six commits, no horrors so far.* diff --git a/HANDOVER-2026-04-18.md b/HANDOVER-2026-04-18.md index c24e82c..fab9b0e 100644 --- a/HANDOVER-2026-04-18.md +++ b/HANDOVER-2026-04-18.md @@ -1,11 +1,11 @@ --- name: handover-2026-04-18 type: reference -tags: [handover, session, kon] +tags: [handover, session, magnotia] description: Session handover — 2026/04/18 dogfooding sprint --- -# Kon Handover — 2026/04/18 +# Magnotia Handover — 2026/04/18 ## Current state @@ -13,7 +13,7 @@ Phase 1 brand migration and Phase 2 polish are both **complete and committed**. ## What's working -- **18/18 automated validation checks pass** (Playwright, `python3 /tmp/kon_validation.py`) +- **18/18 automated validation checks pass** (Playwright, `python3 /tmp/magnotia_validation.py`) - **Pre-warm fixed** — `tauri::async_runtime::spawn` instead of `tokio::spawn`; model loads in background before first dictation - **Preferences infinite loop fixed** — `Object.assign` mutation instead of object reassignment; Svelte 5 module state now stable - **DOM hydration fixed** — `applyToDOM` called on store init so `data-theme` is always set, even without Tauri webview injection @@ -32,7 +32,7 @@ sudo dnf install vulkan-headers vulkan-loader-devel glslc Then launch: ```bash -cd /home/jake/Documents/CORBEL-Projects/kon +cd /home/jake/Documents/CORBEL-Projects/magnotia LIBCLANG_PATH=/usr/lib64/llvm21/lib64 npm run tauri dev ``` @@ -49,7 +49,7 @@ Three items from the validation checklist that need real Tauri runtime: ### Pre-release (before any build beyond Jake's machine) - [ ] Updater signing key — `tauri signer generate`, public key → `tauri.conf.json`, private key → CI secrets -- [ ] ggml dedup — plan at `docs/superpowers/plans/2026-04-18-kon-ggml-dedup.md`, Option A (system-ggml shared lib), execute at Phase 3 +- [ ] ggml dedup — plan at `docs/superpowers/plans/2026-04-18-magnotia-ggml-dedup.md`, Option A (system-ggml shared lib), execute at Phase 3 ## Gotchas discovered today @@ -64,7 +64,7 @@ Three items from the validation checklist that need real Tauri runtime: ## Resume prompt ``` -Picking up Kon dogfooding from the 2026/04/18 session. +Picking up Magnotia dogfooding from the 2026/04/18 session. HANDOVER is at HANDOVER.md in the project root. First job: confirm Vulkan GPU build compiles and check startup logs for RTX 4070. Then run the three manual validation items from the handover. diff --git a/HANDOVER-2026-04-19.md b/HANDOVER-2026-04-19.md index 5759210..2e0fbf9 100644 --- a/HANDOVER-2026-04-19.md +++ b/HANDOVER-2026-04-19.md @@ -1,18 +1,18 @@ --- name: handover-2026-04-19 type: reference -tags: [handover, session, kon] +tags: [handover, session, magnotia] description: Session handover — 2026/04/19 dogfood polish + cross-platform window chrome --- -# Kon Handover — 2026/04/19 +# Magnotia Handover — 2026/04/19 Second dogfood sprint. Four phases: (1) fix bugs surfaced on first real use, (2) redesign History for cognitive-load hygiene, (3) resolve broken window resize/drag on Linux Wayland, (4) clean up microphone picker. ## What shipped this session ### Cross-window preferences sync -- `preferences.svelte.js` emits `kon:preferences-changed` Tauri event on update. +- `preferences.svelte.js` emits `magnotia:preferences-changed` Tauri event on update. - Main / viewer / float layouts listen and call `applyExternalPreferences` without re-emit, so theme and font changes propagate live across sibling windows. - Echo suppressed via source window label check. @@ -28,13 +28,13 @@ Second dogfood sprint. Four phases: (1) fix bugs surfaced on first real use, (2) - **Tags**: `$lib/utils/frontmatter.js` exposes `deriveAutoTags` (currently returns `[]`), `buildFrontmatter`, `serialiseFrontmatter`, `buildMarkdown`. Manual tags stored as `item.manualTags`, rendered as removable chips in the expanded row with `+ add tag` input. - Header tag chip bar (cap 7, click to filter, × to clear), plus `tag:xyz` search syntax. - Global **Starred** filter toggle in the History header. -- Research memo found all five previous auto-tag families redundant with existing row UI — kept the derivation hook for the post-Task-7 `topic:*` content tag from kon-llm. +- Research memo found all five previous auto-tag families redundant with existing row UI — kept the derivation hook for the post-Task-7 `topic:*` content tag from magnotia-llm. - Duplicate-transcript render fix: expanded `

` only if compact preview actually truncated. ### Viewer / editor popout -- `/viewer` route now reads `kon_viewer_mode` from localStorage ("view" | "edit"). +- `/viewer` route now reads `magnotia_viewer_mode` from localStorage ("view" | "edit"). - Edit mode renders a plain textarea bound to `item.text`; 400ms debounced save flushes on input, final flush on `onDestroy`. Segment-specific controls (Compact, Starred) hidden in edit mode. -- Native title: **"Kon - Transcription Editor"**. +- Native title: **"Magnotia - Transcription Editor"**. ### Platform-aware window chrome (Linux fix) **Root cause:** Tauri v2 frameless `decorations: false` on KDE Wayland + webkit2gtk does not honour diagonal corner resize (collapses `NorthEast` etc. to a single axis via GTK's `gtk_window_begin_resize_drag`), and `data-tauri-drag-region` adds noticeable drag latency. Setting `setPointerCapture` ahead of `startResizeDragging` does not help once the compositor has taken over the pointer grab. Verified via Context7 docs + Codex diagnosis — Linux frameless is a known-fragile path. @@ -43,7 +43,7 @@ Second dogfood sprint. Four phases: (1) fix bugs surfaced on first real use, (2) - Linux uses **native KWin/Mutter decorations**. `src-tauri/tauri.linux.conf.json` overlays `decorations: true` + full main window config (title, sizes) — overlays **replace** the windows array, so every field must be present, not just the delta. `src-tauri/src/commands/windows.rs` uses `cfg!(target_os = "linux")` to set decorations per window. - macOS / Windows keep custom chrome. `src/lib/utils/osInfo.js` `isLinux()` gates `` and `` via `useCustomChrome = $state(false)`; flips to `!isLinux()` after `loadOsInfo()` resolves. - Dueling drag-region handlers removed across Titlebar, float page, viewer page — everywhere a manual `startDragging()` lives, the `data-tauri-drag-region` attribute was deleted (they're alternatives per Tauri docs, not combinable). -- `ResizeHandles` kept for macOS/Windows frameless: 12 px edges / 20 px corners via CSS vars (`--kon-resize-edge`, `--kon-resize-corner`), `pointerdown` + `setPointerCapture`, corners with explicit higher z-index. Handles rendered as siblings of the animated layout div so `position: fixed` is viewport-relative rather than captured by the transform containing block. +- `ResizeHandles` kept for macOS/Windows frameless: 12 px edges / 20 px corners via CSS vars (`--magnotia-resize-edge`, `--magnotia-resize-corner`), `pointerdown` + `setPointerCapture`, corners with explicit higher z-index. Handles rendered as siblings of the animated layout div so `position: fixed` is viewport-relative rather than captured by the transform containing block. ### Window minimum sizes (evidence-backed) Research pass cited GNOME HIG (1024×600 desktop / 360×294 mobile floors), WCAG 2.2 SC 1.4.10 Reflow (320 CSS px), Raycast 750×474 as a reference for single-pane working width, and consistent A11y principle that nothing should clip in the default configuration. @@ -64,16 +64,16 @@ Research pass cited GNOME HIG (1024×600 desktop / 360×294 mobile floors), WCAG - Settings now shows the Vulkan option instead of the "This build is CPU-only" notice. ### Desktop shortcut -- `~/Desktop/Kon.desktop` launcher with the 128×128 icon, `Terminal=true` so logs are visible and Ctrl+C cleanly stops the run.sh wrapper. +- `~/Desktop/Magnotia.desktop` launcher with the 128×128 icon, `Terminal=true` so logs are visible and Ctrl+C cleanly stops the run.sh wrapper. ## What's deferred - **Transparent windows (`transparent: true`)** — Tauri issue #13270 reports this smooths drag/resize further on Linux, but it's moot now that Linux uses native decorations. - **File-system export (.md save dialog)** — currently clipboard-only. Needs a Rust `write_text_file` command for plugin-less file writes. - **Bulk select + bulk export** in History. -- **LLM-powered content tags** (`topic:*`, `intent:*`) — slots into Task 7 `kon-llm` stub once Phase 3 wires real llama-cpp-2. +- **LLM-powered content tags** (`topic:*`, `intent:*`) — slots into Task 7 `magnotia-llm` stub once Phase 3 wires real llama-cpp-2. - **Settings UX overhaul** — Jake flagged that current settings feel overwhelming. Proposed: bunch high-traffic settings, hide advanced behind a toggle. Brainstorm + plan deferred to a dedicated session. -- **Task 7 (MicroSteps end-to-end)** — storage + Tauri CRUD + kon-llm stub + frontend dual-write all landed in an earlier commit chain. The MicroSteps UI was written as the final task 7 step but not yet dogfooded against the stub LLM. Needs manual walkthrough. +- **Task 7 (MicroSteps end-to-end)** — storage + Tauri CRUD + magnotia-llm stub + frontend dual-write all landed in an earlier commit chain. The MicroSteps UI was written as the final task 7 step but not yet dogfooded against the stub LLM. Needs manual walkthrough. ## Gotchas discovered today @@ -83,15 +83,15 @@ Research pass cited GNOME HIG (1024×600 desktop / 360×294 mobile floors), WCAG | `data-tauri-drag-region` + manual `startDragging()` on the same node caused drag latency | Pick one — we use manual `startDragging` for the button/input early-return logic | | Corner resize collapsed to single axis on KWin Wayland | Native decorations on Linux side-step the whole frameless path | | `animate-float-enter` on the viewer/float layout root created a containing block that broke `position: fixed` on ResizeHandles children | Render ResizeHandles as a sibling of the animated div, not a descendant | -| Kon binary auto-respawned on file-save while a second run.sh was also launching → two visible instances sharing one Vite server | Do not script `./run.sh` while the user has already launched via the desktop icon; rely on HMR | +| Magnotia binary auto-respawned on file-save while a second run.sh was also launching → two visible instances sharing one Vite server | Do not script `./run.sh` while the user has already launched via the desktop icon; rely on HMR | | `run.sh` leaves `"beforeDevCommand": ""` in tauri.conf.json if its cleanup trap is bypassed (e.g. SIGKILL) | Cleanup trap restores `"npm run dev"` on graceful exit; SIGTERM (not SIGKILL) is the right kill signal | | `/proc/asound/cards` header lines have leading whitespace for 2-digit card ID alignment | Parser trims leading whitespace before checking for leading digit | ## How to resume ``` -Picking up Kon dogfooding from 2026/04/19. +Picking up Magnotia dogfooding from 2026/04/19. HANDOVER is at HANDOVER.md in the project root. Active priorities: (1) confirm resize/drag/mic cleanup, (2) Task 7 MicroSteps -dogfood with kon-llm stub, (3) Settings UX brainstorm. +dogfood with magnotia-llm stub, (3) Settings UX brainstorm. ``` diff --git a/HANDOVER-2026-04-24.md b/HANDOVER-2026-04-24.md index 0e7d278..4ca67ba 100644 --- a/HANDOVER-2026-04-24.md +++ b/HANDOVER-2026-04-24.md @@ -1,17 +1,17 @@ --- name: handover-2026-04-24 type: reference -tags: [handover, session, kon, phase-8, gamification] +tags: [handover, session, magnotia, phase-8, gamification] description: Session handover — 2026/04/24 Phase 8 forgiving gamification shipped end-to-end --- -# Corbie Handover — 2026/04/24 +# Magnotia Handover — 2026/04/24 Phase 8 session. Executed the forgiving-gamification spec + plan written at the top of the session against `main`. Shipped 14 commits end-to-end. All automated gates clean; manual dogfood walkthrough still owed when Jake next opens the running app. ## Rebrand note -Product rename **Kon → Corbie** still in flight. Copy in new docs is "Corbie"; codebase paths / package names / repos still carry `kon`. No rebrand work this session. See `~/.claude/projects/-home-jake-Documents-CORBEL-Main/memory/project_corbie_rebrand.md`. +Product rename **Magnotia → Magnotia** still in flight. Copy in new docs is "Magnotia"; codebase paths / package names / repos still carry `magnotia`. No rebrand work this session. See `~/.claude/projects/-home-jake-Documents-CORBEL-Main/memory/project_magnotia_rebrand.md`. ## What shipped this session @@ -33,7 +33,7 @@ Today's header now shows `Tasks · 3 today` alongside a 7-day momentum sparkline | `4ffdae9` | `completionStats.svelte.ts` store | | `54ddd41` | `CompletionSparkline.svelte` component | | `3cadbb0` | badge + sparkline wired into Tasks header (+ `$derived` → getter fix) | -| `c29720e` | emit `kon:task-uncompleted` + `kon:task-deleted` events | +| `c29720e` | emit `magnotia:task-uncompleted` + `magnotia:task-deleted` events | | `fa93033` | settings toggle for momentum sparkline | ### Counting semantics (locked) @@ -46,9 +46,9 @@ Today's header now shows `Tasks · 3 today` alongside a 7-day momentum sparkline ### Architectural notes worth carrying forward -- **`serde` is now a dependency of `kon-storage`.** Added because `DailyCompletionCount` is serialised directly to the frontend via Tauri. The existing `TaskRow` → `TaskDto` split wasn't reused because the struct has no camelCase translation need (`day`, `count` are already frontend-friendly). Simpler, one fewer file to maintain. +- **`serde` is now a dependency of `magnotia-storage`.** Added because `DailyCompletionCount` is serialised directly to the frontend via Tauri. The existing `TaskRow` → `TaskDto` split wasn't reused because the struct has no camelCase translation need (`day`, `count` are already frontend-friendly). Simpler, one fewer file to maintain. - **`$derived` cannot be exported at module scope in `.svelte.ts`.** Svelte 5 errors with `derived_invalid_export`. Originally hit during Task 9 integration; fix landed in the same commit (`3cadbb0`). `svelte-check` misses this; only Vite catches it. Plan/spec both mistakenly prescribed `$derived`; future stores should use `export function fooCount(): number` + `(...)` call sites, or a `$derived` wrapped inside a component script. -- **Tuple `FromRow` in storage.** `kon-storage` strips sqlx's `derive` feature, so `#[derive(sqlx::FromRow)]` is not available. Use tuple `FromRow` `(String, i64)` etc. instead. Noted for future tasks in this crate. +- **Tuple `FromRow` in storage.** `magnotia-storage` strips sqlx's `derive` feature, so `#[derive(sqlx::FromRow)]` is not available. Use tuple `FromRow` `(String, i64)` etc. instead. Noted for future tasks in this crate. ## Verification state at session end @@ -62,7 +62,7 @@ Fresh run on `main` tip `fa93033`: ## Owed to Jake (next session) -1. **Manual dogfood walkthrough.** Cannot be driven by an automated agent. When opening Corbie next: +1. **Manual dogfood walkthrough.** Cannot be driven by an automated agent. When opening Magnotia next: - Fresh state, no completions → header shows only "Tasks" title; no badge, no sparkline. - Complete one top-level task → badge "1 today"; sparkline appears. - Complete two more → badge "3 today". @@ -79,9 +79,9 @@ Fresh run on `main` tip `fa93033`: 3. **Plan quality note for future Phase 9+ plans.** Two patterns I prescribed turned out to be wrong on this codebase and only surfaced during execution: - `$derived` at `.svelte.ts` module scope: not supported. - - `#[derive(sqlx::FromRow)]` in `kon-storage`: feature is stripped. + - `#[derive(sqlx::FromRow)]` in `magnotia-storage`: feature is stripped. - Worth a one-screen "kon-storage gotchas" reference file or at least a note at the top of future plans that touch these areas. + Worth a one-screen "magnotia-storage gotchas" reference file or at least a note at the top of future plans that touch these areas. ## What's left for v0.1 @@ -92,7 +92,7 @@ Unchanged except for Phase 8 now being closed: | Phases 1 to 8 | **All shipped.** | | Phase 9 | Polish debt (file-system .md save dialog, bulk select/export in History, LLM content tags, settings UX pass, visual polish, accessibility sweep). Absorbs backlog above. 1 to 2 days. | | Phase 10a | QC: dogfood walkthrough, Rachmann's RB-08 Mac verification (parallel), cross-platform CI, a11y regression, clean-install test. Half day. | -| Phase 10b | Kon → Corbie rename sweep: package name, all 10 crates, bundle ids, install paths, `kon.db` → `corbie.db`, event names, repo rename on both remotes. Half to 1 day. | +| Phase 10b | Magnotia → Magnotia rename sweep: package name, all 10 crates, bundle ids, install paths, `magnotia.db` → `magnotia.db`, event names, repo rename on both remotes. Half to 1 day. | | Phase 10c | Release: 0.1.0 version sync, CHANGELOG seeded from roadmap phases, release notes, tag + push. Half day. | ### Release-blocker state @@ -115,8 +115,8 @@ Unchanged except for Phase 8 now being closed: - Spec: [docs/superpowers/specs/2026-04-24-phase8-forgiving-gamification-design.md](docs/superpowers/specs/2026-04-24-phase8-forgiving-gamification-design.md) - Plan: [docs/superpowers/plans/2026-04-24-phase8-forgiving-gamification.md](docs/superpowers/plans/2026-04-24-phase8-forgiving-gamification.md) -- Roadmap: [docs/roadmap/2026-04-23-corbie-feature-complete-roadmap.md](docs/roadmap/2026-04-23-corbie-feature-complete-roadmap.md) +- Roadmap: [docs/roadmap/2026-04-23-magnotia-feature-complete-roadmap.md](docs/roadmap/2026-04-23-magnotia-feature-complete-roadmap.md) - Previous handover: [HANDOVER-2026-04-19.md](HANDOVER-2026-04-19.md) - Release-blocker index: [docs/issues/README.md](docs/issues/README.md) -- Rebrand memory: `~/.claude/projects/-home-jake-Documents-CORBEL-Main/memory/project_corbie_rebrand.md` +- Rebrand memory: `~/.claude/projects/-home-jake-Documents-CORBEL-Main/memory/project_magnotia_rebrand.md` - Active-focus upstream: `context/active-focus.md` in CORBEL-Main diff --git a/HANDOVER.md b/HANDOVER.md index ddb2b81..b7b396f 100644 --- a/HANDOVER.md +++ b/HANDOVER.md @@ -1,35 +1,35 @@ --- name: handover-2026-04-25 type: reference -tags: [handover, session, kon, phase-9, polish-debt] +tags: [handover, session, magnotia, phase-9, polish-debt] description: Session handover — 2026/04/24-25 Phase 9 polish debt mostly shipped --- -# Corbie Handover — 2026/04/25 +# Magnotia Handover — 2026/04/25 Phase 9 session. Spec + plan written from scratch and committed; plan corrections layered in after critical review against the actual codebase (Codex was unreachable for cross-model review, three retries failed at the ChatGPT-account-entitlement layer). Sub-phases 9a + 9b + sparkline polish landed end to end. Sub-phase 9c reduced to the Phase 8 carryover bug fix; sub-phase 9d's walkthrough sweeps deferred to Phase 10a QC. ## Rebrand note -Product rename **Kon → Corbie** still in flight. Copy in new docs is "Corbie"; codebase paths / package names / repos still carry `kon`. No rebrand work this session. See `~/.claude/projects/-home-jake-Documents-CORBEL-Main/memory/project_corbie_rebrand.md`. +Product rename **Magnotia → Magnotia** still in flight. Copy in new docs is "Magnotia"; codebase paths / package names / repos still carry `magnotia`. No rebrand work this session. See `~/.claude/projects/-home-jake-Documents-CORBEL-Main/memory/project_magnotia_rebrand.md`. ## What shipped this session ### 9a — Export plumbing -- `write_text_file_cmd` Rust command in new `src-tauri/src/commands/fs.rs`, with two unit tests (UTF-8 round-trip + bad-parent error path). Registered in `invoke_handler!`. `tempfile = "3"` added as `[dev-dependencies]` on the kon crate. +- `write_text_file_cmd` Rust command in new `src-tauri/src/commands/fs.rs`, with two unit tests (UTF-8 round-trip + bad-parent error path). Registered in `invoke_handler!`. `tempfile = "3"` added as `[dev-dependencies]` on the magnotia crate. - `src/lib/utils/saveMarkdown.ts` utility centralises `suggestedFilename`, `saveTranscriptAsMarkdown`, `exportTranscriptsToDir` (directory-mode bulk export with in-batch collision suffixing). - HistoryPage `exportMarkdown` no longer copies to clipboard; it opens the OS save dialog and writes the file. Cancel returns silently. - HistoryPage gained a slim leading checkbox per row, a bulk-action toolbar (select-all / clear / export / delete), `Esc` to clear, `Cmd/Ctrl+A` to select-all-visible when focus is inside the list and not in a text input. ### 9b — LLM content tags -- `kon-llm` exports a new `ContentTags { topic, intent }`, an `INTENT_CLOSED_SET`, an `is_valid_intent` helper, a `CONTENT_TAGS_SYSTEM` prompt and a `CONTENT_TAGS_GRAMMAR` GBNF (recursive style matching the existing `TASK_ARRAY_GRAMMAR`). -- `LlmEngine::extract_content_tags` method follows the same render-chat → generate → JSON-parse shape as the existing `cleanup_text` and `extract_tasks`. Truncates to the trailing 2000 chars on a UTF-8 boundary; max_tokens 96 is enough for the JSON envelope. Smoke test in `crates/llm/tests/content_tags_smoke.rs` is gated on `KON_LLM_TEST_MODEL` matching the Phase 8 pattern. +- `magnotia-llm` exports a new `ContentTags { topic, intent }`, an `INTENT_CLOSED_SET`, an `is_valid_intent` helper, a `CONTENT_TAGS_SYSTEM` prompt and a `CONTENT_TAGS_GRAMMAR` GBNF (recursive style matching the existing `TASK_ARRAY_GRAMMAR`). +- `LlmEngine::extract_content_tags` method follows the same render-chat → generate → JSON-parse shape as the existing `cleanup_text` and `extract_tasks`. Truncates to the trailing 2000 chars on a UTF-8 boundary; max_tokens 96 is enough for the JSON envelope. Smoke test in `crates/llm/tests/content_tags_smoke.rs` is gated on `MAGNOTIA_LLM_TEST_MODEL` matching the Phase 8 pattern. - `extract_content_tags_cmd` Tauri wrapper bridges through `state.llm_engine` with the standard `spawn_blocking` + `PowerAssertion` guard. ### 9b structural — migration v14 + persistence wiring A correction layered in after the critical-review pass discovered the original Task 9 was assuming a writable `saveHistory()` path that turned out to be a no-op stub. - Migration v14 adds `transcripts.llm_tags TEXT NOT NULL DEFAULT ''`. -- `kon-storage` `database.rs` SELECT statements include the column. `TranscriptRow` + `transcript_row_from` carry it. `update_transcript_meta` accepts an `Option<&str>` for `llm_tags` (sixth optional, `#[allow(too_many_arguments)]` keeps clippy happy without inverting the signature into a struct). +- `magnotia-storage` `database.rs` SELECT statements include the column. `TranscriptRow` + `transcript_row_from` carry it. `update_transcript_meta` accepts an `Option<&str>` for `llm_tags` (sixth optional, `#[allow(too_many_arguments)]` keeps clippy happy without inverting the signature into a struct). - `commands/transcripts.rs` `TranscriptDto` + `UpdateTranscriptMetaRequest` add `llm_tags`; `update_transcript_meta_cmd` forwards. - Frontend types: `TranscriptEntry.llmTags: string[]`, `TranscriptRow.llmTags: string`, `ContentTags`, optional `TranscriptMetaPatch.llmTags`. - `mapTranscriptRow` hydrates `llmTags`. `saveTranscriptMeta` now also forwards `llmTags` payloads. `buildFrontmatter` unions auto + manual + LLM tags into the exported markdown frontmatter. @@ -54,7 +54,7 @@ Fresh run on `main` tip `dd45f10`: - `cargo fmt --check`: clean. - `cargo clippy --all-targets -- -D warnings`: clean. -- `cargo test`: **277 tests pass**, 0 failed. Storage gained 1 new test (`update_transcript_meta_writes_llm_tags`), kon-tauri gained 2 (write_text_file). The Phase 8 brittle test fix is in this count. +- `cargo test`: **277 tests pass**, 0 failed. Storage gained 1 new test (`update_transcript_meta_writes_llm_tags`), magnotia-tauri gained 2 (write_text_file). The Phase 8 brittle test fix is in this count. - `npm run check`: 0 errors, 0 warnings across 3957 files. - `npm run build`: clean production build via `@sveltejs/adapter-static`. @@ -62,13 +62,13 @@ Fresh run on `main` tip `dd45f10`: The original Phase 9 spec + plan committed at `49a795f` + `48d3db7` had three mismatches against the actual codebase, surfaced by a critical-review pass before execution. Layered as a corrections appendix in commit `3eb24f2`: -1. `kon-llm` is `LlmEngine::generate(prompt, config)` synchronous, not the speculated `LlamaEngine::generate_chat(messages, config).await`. +1. `magnotia-llm` is `LlmEngine::generate(prompt, config)` synchronous, not the speculated `LlamaEngine::generate_chat(messages, config).await`. 2. `AppState.llm_engine: Arc` is direct, not behind a `RwLock`. 3. **Structural** — `transcripts.llm_tags` requires a real SQLite migration plus Tauri command extension because the frontend `saveHistory()` is a no-op stub. Original plan assumed `manualTags`-mirroring would suffice. Migration v14 + `update_transcript_meta` extension landed as a new task to cover this. Picked up the latent `manualTags` persistence bug for free. ## Owed to Jake (next session) -1. **Manual dogfood walkthrough.** Cannot be driven by an automated agent. When opening Corbie next: +1. **Manual dogfood walkthrough.** Cannot be driven by an automated agent. When opening Magnotia next: - Export one transcript via the History "Export .md" button — save dialog opens, file written to chosen path. Cancel — no toast, no fallback. - Select 3 history rows via checkboxes — toolbar surfaces, "Export selected" writes one .md per row to a chosen folder, collisions suffixed " (2)" etc. - Click "Tag" on one row — within a few seconds, dashed `topic:*` and `intent:*` chips appear. Click a chip — it moves into `manualTags` (solid accent chip). Page refresh — both `manualTags` and `llmTags` survive (this is the persistence-fix outcome). @@ -90,7 +90,7 @@ The original Phase 9 spec + plan committed at `49a795f` + `48d3db7` had three mi | Phases 1-8 | All shipped. | | Phase 9 | **Mostly shipped this session.** Export plumbing, LLM content tags (with persistence), polish on sparkline + badge are live. SettingsPage deeper restructure + walkthrough a11y sweeps deferred. Roadmap entry updated. | | Phase 10a | QC: dogfood walkthrough (above), Rachmann's RB-08 Mac verification (parallel), cross-platform CI, a11y regression, clean-install test. Half day. | -| Phase 10b | Kon → Corbie rename sweep: package name, all 10 crates, bundle ids, install paths, `kon.db` → `corbie.db`, event names, repo rename on both remotes. Half to 1 day. | +| Phase 10b | Magnotia → Magnotia rename sweep: package name, all 10 crates, bundle ids, install paths, `magnotia.db` → `magnotia.db`, event names, repo rename on both remotes. Half to 1 day. | | Phase 10c | Release: 0.1.0 version sync, CHANGELOG seeded from roadmap phases, release notes, tag + push. Half day. | ### Release-blocker state @@ -109,8 +109,8 @@ The original Phase 9 spec + plan committed at `49a795f` + `48d3db7` had three mi - Spec: [docs/superpowers/specs/2026-04-24-phase9-polish-debt-design.md](docs/superpowers/specs/2026-04-24-phase9-polish-debt-design.md) - Plan: [docs/superpowers/plans/2026-04-24-phase9-polish-debt.md](docs/superpowers/plans/2026-04-24-phase9-polish-debt.md) -- Roadmap: [docs/roadmap/2026-04-23-corbie-feature-complete-roadmap.md](docs/roadmap/2026-04-23-corbie-feature-complete-roadmap.md) +- Roadmap: [docs/roadmap/2026-04-23-magnotia-feature-complete-roadmap.md](docs/roadmap/2026-04-23-magnotia-feature-complete-roadmap.md) - Previous handover: [HANDOVER-2026-04-24.md](HANDOVER-2026-04-24.md) (Phase 8) - Release-blocker index: [docs/issues/README.md](docs/issues/README.md) -- Rebrand memory: `~/.claude/projects/-home-jake-Documents-CORBEL-Main/memory/project_corbie_rebrand.md` +- Rebrand memory: `~/.claude/projects/-home-jake-Documents-CORBEL-Main/memory/project_magnotia_rebrand.md` - Active-focus upstream: `context/active-focus.md` in CORBEL-Main diff --git a/README.md b/README.md index 27a810b..931e2b6 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -# Kon +# Magnotia *Think out loud. Keep working.* -Kon is a local-first, cognitive-load-aware dictation and task-capture desktop app. Every transcription, LLM cleanup, and task extraction runs on the user's machine. No telemetry, no analytics, no cloud dependency. The app is designed around a single observation: people who think in bursts lose ideas faster than they can type, and the tool's job is to get out of the way. +Magnotia is a local-first, cognitive-load-aware dictation and task-capture desktop app. Every transcription, LLM cleanup, and task extraction runs on the user's machine. No telemetry, no analytics, no cloud dependency. The app is designed around a single observation: people who think in bursts lose ideas faster than they can type, and the tool's job is to get out of the way. --- @@ -20,15 +20,15 @@ Kon is a local-first, cognitive-load-aware dictation and task-capture desktop ap 1. **Local-first is the floor, not a feature.** No voice, transcript, or task ever leaves the user's machine unless they explicitly send it. No telemetry. 2. **Cognitive load is the limiting resource.** Every new setting must earn its mental real estate. Every interaction should reduce, not add, decisions. -3. **Composable, not monolithic.** Kon is a dictation primitive: via MCP, CLI, and filesystem export, it slots into whatever workflow the user already has (Obsidian, Claude Desktop, Cline, any text field). +3. **Composable, not monolithic.** Magnotia is a dictation primitive: via MCP, CLI, and filesystem export, it slots into whatever workflow the user already has (Obsidian, Claude Desktop, Cline, any text field). 4. **LLM scope is narrow.** The in-app LLM does transcription cleanup and task extraction. It is not a wake-word agent, not a chat UI, not a multi-provider cloud fan-out. 5. **Raw transcript is always recoverable.** Cleanup is additive, never destructive. The user can always see and revert to what Whisper heard. -These are enforced in the codebase (where practical) and in the docs under [`docs/whisper-ecosystem/kon-context.md`](docs/whisper-ecosystem/kon-context.md). +These are enforced in the codebase (where practical) and in the docs under [`docs/whisper-ecosystem/magnotia-context.md`](docs/whisper-ecosystem/magnotia-context.md). --- -## What Kon does today +## What Magnotia does today ### Speech-to-text - Vulkan-accelerated local **Whisper** inference via [whisper-rs](https://github.com/tazz4843/whisper-rs) 0.16 + whisper.cpp. Works on NVIDIA, AMD, Intel Arc, Apple (via MoltenVK), and integrated graphics. @@ -64,7 +64,7 @@ These are enforced in the codebase (where practical) and in the docs under [`doc - Transcript editor window (`/viewer`) with debounced autosave. ### External integration -- **MCP stdio server** (`kon-mcp`) exposing read-only transcripts and tasks to any Model Context Protocol client (Claude Desktop, Cline, Cursor, etc.). No authentication, read-only, local-only. +- **MCP stdio server** (`magnotia-mcp`) exposing read-only transcripts and tasks to any Model Context Protocol client (Claude Desktop, Cline, Cursor, etc.). No authentication, read-only, local-only. ### Accessibility - Dyslexia-friendly fonts bundled: Lexend, Atkinson Hyperlegible Next, OpenDyslexic. @@ -83,7 +83,7 @@ These are enforced in the codebase (where practical) and in the docs under [`doc ## Architecture -Kon is a Tauri 2 desktop app with three layers: +Magnotia is a Tauri 2 desktop app with three layers: ``` ┌─────────────────────────────────────────────────────────────────┐ @@ -99,18 +99,18 @@ Kon is a Tauri 2 desktop app with three layers: │ window-state │ ├─────────────────────────────────────────────────────────────────┤ │ Rust workspace (crates/) │ -│ kon-core, kon-audio, kon-transcription, kon-llm, │ -│ kon-ai-formatting, kon-storage, kon-hotkey, │ -│ kon-cloud-providers, kon-mcp │ +│ magnotia-core, magnotia-audio, magnotia-transcription, magnotia-llm, │ +│ magnotia-ai-formatting, magnotia-storage, magnotia-hotkey, │ +│ magnotia-cloud-providers, magnotia-mcp │ └─────────────────────────────────────────────────────────────────┘ ``` -The Rust workspace is the brain; Tauri is the OS integration surface; Svelte is the UI. The MCP server (`kon-mcp`) is a separate binary that opens Kon's SQLite store read-only — it's Kon-as-primitive for external agents. +The Rust workspace is the brain; Tauri is the OS integration surface; Svelte is the UI. The MCP server (`magnotia-mcp`) is a separate binary that opens Magnotia's SQLite store read-only — it's Magnotia-as-primitive for external agents. ### Repository layout ``` -kon/ +magnotia/ ├── Cargo.toml # workspace root ├── src-tauri/ # Tauri app (main binary + commands) │ ├── src/ @@ -162,15 +162,15 @@ kon/ | Crate | Responsibility | |---|---| -| **`kon-core`** | Shared types (`Segment`, `Transcript`, `Megabytes`, `ModelId`), constants, the `Engine` / `SpeedTier` / `AccuracyTier` enums, hardware probe (`sysinfo`-based), model registry (Whisper + Parakeet + Moonshine entries), hardware-aware recommendation scoring, `process_watch` for meeting detection. | -| **`kon-audio`** | `cpal`-based microphone capture with device hotplug + error forwarding, VAD, `rubato` streaming resampler to 16 kHz mono, `symphonia` file decoding, `hound` WAV I/O. | -| **`kon-transcription`** | `whisper-rs` backend (`WhisperRsBackend`) that owns a `WhisperContext` and supports `set_initial_prompt`. `LocalEngine` wraps both Whisper and Parakeet (via `transcribe-rs` ONNX) behind a common `Transcriber` trait. Streaming primitives (`VadChunker`, `LocalAgreement`, buffer trim) live in the `streaming/` module. Model manager handles downloads, paths, and disk checks. | -| **`kon-llm`** | `llama-cpp-2` engine with Qwen3 model manager. Three high-level surfaces: `cleanup_text` (formatting), `decompose_task` (3–7 micro-steps, GBNF-constrained JSON array), `extract_tasks` (optional-array, GBNF-constrained). Resumable HTTP downloads with SHA-256 verify. | -| **`kon-ai-formatting`** | Post-processing pipeline: filler removal, British English conversion, anti-hallucination filter, smart paragraph breaks on long pauses, optional LLM cleanup. Also hosts the `llm_client::CLEANUP_PROMPT` constant (prompt-injection-hardened). | -| **`kon-storage`** | SQLite via `sqlx` 0.8. Migrations, CRUD for transcripts / tasks / subtasks / profiles / profile terms / settings / error log, FTS5 search, file-storage paths. | -| **`kon-hotkey`** | Linux `evdev` hotkey listener with device hotplug. Parses Tauri-style hotkey strings (`Ctrl+Shift+R`), emits Pressed / Released events. Works natively on Wayland (no X11 dependency). Checks `/dev/input/event*` access on startup; surfaces a clear "add yourself to the `input` group" error when missing. | -| **`kon-cloud-providers`** | BYOK cloud-STT provider stubs. Currently empty scaffolding. When populated: OpenAI-compatible endpoint + Anthropic (ceiling for scope). | -| **`kon-mcp`** | Standalone `kon-mcp` binary implementing the MCP stdio protocol (2024-11-05). Read-only tools: `list_transcripts`, `get_transcript`, `search_transcripts`, `list_tasks`. Opens Kon's SQLite store. | +| **`magnotia-core`** | Shared types (`Segment`, `Transcript`, `Megabytes`, `ModelId`), constants, the `Engine` / `SpeedTier` / `AccuracyTier` enums, hardware probe (`sysinfo`-based), model registry (Whisper + Parakeet + Moonshine entries), hardware-aware recommendation scoring, `process_watch` for meeting detection. | +| **`magnotia-audio`** | `cpal`-based microphone capture with device hotplug + error forwarding, VAD, `rubato` streaming resampler to 16 kHz mono, `symphonia` file decoding, `hound` WAV I/O. | +| **`magnotia-transcription`** | `whisper-rs` backend (`WhisperRsBackend`) that owns a `WhisperContext` and supports `set_initial_prompt`. `LocalEngine` wraps both Whisper and Parakeet (via `transcribe-rs` ONNX) behind a common `Transcriber` trait. Streaming primitives (`VadChunker`, `LocalAgreement`, buffer trim) live in the `streaming/` module. Model manager handles downloads, paths, and disk checks. | +| **`magnotia-llm`** | `llama-cpp-2` engine with Qwen3 model manager. Three high-level surfaces: `cleanup_text` (formatting), `decompose_task` (3–7 micro-steps, GBNF-constrained JSON array), `extract_tasks` (optional-array, GBNF-constrained). Resumable HTTP downloads with SHA-256 verify. | +| **`magnotia-ai-formatting`** | Post-processing pipeline: filler removal, British English conversion, anti-hallucination filter, smart paragraph breaks on long pauses, optional LLM cleanup. Also hosts the `llm_client::CLEANUP_PROMPT` constant (prompt-injection-hardened). | +| **`magnotia-storage`** | SQLite via `sqlx` 0.8. Migrations, CRUD for transcripts / tasks / subtasks / profiles / profile terms / settings / error log, FTS5 search, file-storage paths. | +| **`magnotia-hotkey`** | Linux `evdev` hotkey listener with device hotplug. Parses Tauri-style hotkey strings (`Ctrl+Shift+R`), emits Pressed / Released events. Works natively on Wayland (no X11 dependency). Checks `/dev/input/event*` access on startup; surfaces a clear "add yourself to the `input` group" error when missing. | +| **`magnotia-cloud-providers`** | BYOK cloud-STT provider stubs. Currently empty scaffolding. When populated: OpenAI-compatible endpoint + Anthropic (ceiling for scope). | +| **`magnotia-mcp`** | Standalone `magnotia-mcp` binary implementing the MCP stdio protocol (2024-11-05). Read-only tools: `list_transcripts`, `get_transcript`, `search_transcripts`, `list_tasks`. Opens Magnotia's SQLite store. | ### Tauri commands (src-tauri/src/commands/) @@ -301,24 +301,24 @@ Beyond this README, the repo ships extensive internal documentation: ### Product + strategy — `docs/brief/` Research briefs, competitive analysis, and strategic framing. Start with: -- [`what-kon-is.md`](docs/brief/what-kon-is.md) — product thesis +- [`what-magnotia-is.md`](docs/brief/what-magnotia-is.md) — product thesis - [`why-current-tools-fail.md`](docs/brief/why-current-tools-fail.md) — market gap - [`design-principles.md`](docs/brief/design-principles.md) — full principle list - [`target-audience.md`](docs/brief/target-audience.md), [`market-size-demographics.md`](docs/brief/market-size-demographics.md) - Appendices on cognitive ergonomics, AI body doubling, evolutionary psychology, implementation intentions, HITL scaffolding, voice interfaces ### Brand — `docs/brand/` -- [`kon-brand-guidelines.md`](docs/brand/kon-brand-guidelines.md) -- [`kon-brand-platform.md`](docs/brand/kon-brand-platform.md) +- [`magnotia-brand-guidelines.md`](docs/brand/magnotia-brand-guidelines.md) +- [`magnotia-brand-platform.md`](docs/brand/magnotia-brand-platform.md) ### Technical research — `docs/whisper-ecosystem/` -Cross-repo survey of 10 OSS Whisper projects, the Kon-specific atomic task backlog, and the two Cursor workstream plans. +Cross-repo survey of 10 OSS Whisper projects, the Magnotia-specific atomic task backlog, and the two Cursor workstream plans. - [`brief.md`](docs/whisper-ecosystem/brief.md) — 31-item task backlog (the canonical research spec) -- [`kon-context.md`](docs/whisper-ecosystem/kon-context.md) — ideology, shipped state, file-ownership fence for cloud AI agents +- [`magnotia-context.md`](docs/whisper-ecosystem/magnotia-context.md) — ideology, shipped state, file-ownership fence for cloud AI agents - [`workstream-A.md`](docs/whisper-ecosystem/workstream-A.md), [`workstream-B.md`](docs/whisper-ecosystem/workstream-B.md) — executed workstream plans ### GPU tuning — `docs/gpu-tuning/` -- [`plan.md`](docs/gpu-tuning/plan.md) — MVP plan for GGML env-var panel + `kon-bench` auto-tuner + `kon-configs` community repo +- [`plan.md`](docs/gpu-tuning/plan.md) — MVP plan for GGML env-var panel + `magnotia-bench` auto-tuner + `magnotia-configs` community repo ### Session handovers - [`HANDOVER.md`](HANDOVER.md) — latest session summary @@ -339,7 +339,7 @@ Pinned roadmap items (scoped in docs and session memory): - **Phase 4** — remaining items from [`workstream-A.md`](docs/whisper-ecosystem/workstream-A.md) + [`workstream-B.md`](docs/whisper-ecosystem/workstream-B.md) - **Voice calibration** — three-tier plan replacing the hardcoded speech-gate with per-user baselines - **GPU community tuning** — see [`docs/gpu-tuning/plan.md`](docs/gpu-tuning/plan.md); five-phase roadmap from settings panel to agentic auto-tuner + community config repo -- **Cloud endpoint contract test** — when `kon-cloud-providers` grows a real provider +- **Cloud endpoint contract test** — when `magnotia-cloud-providers` grows a real provider - **`ggml` dedup** — replace the interim `-Wl,--allow-multiple-definition` link flag with a proper shared-lib setup; unblocks custom shader / backend work - **Mobile (iOS / Android)** — long-horizon, gated on the single-binary Rust stack scaling @@ -347,7 +347,7 @@ Explicitly shelved (not coming without specific community signal): - Wake-word / always-listening agent - Chat-style LLM UI - Multi-provider cloud fan-out beyond OpenAI-compatible + Anthropic -- Second notes-editing surface (transcripts leave Kon via frontmatter to Obsidian) +- Second notes-editing surface (transcripts leave Magnotia via frontmatter to Obsidian) - Speaker diarization - Dragon-style passage-based speaker fine-tuning (Whisper has no speaker adaptation) @@ -374,4 +374,4 @@ To be finalised before public beta. Current intent: MIT or similar permissive li ## Contact **Jake Sames** — [jakeadriansames@gmail.com](mailto:jakeadriansames@gmail.com) -Repo: [github.com/jakejars/kon](https://github.com/jakejars/kon) · [git.corbel.consulting/jake/kon](https://git.corbel.consulting/jake/kon) +Repo: [github.com/jakejars/magnotia](https://github.com/jakejars/magnotia) · [git.corbel.consulting/jake/magnotia](https://git.corbel.consulting/jake/magnotia) diff --git a/crates/ai-formatting/Cargo.toml b/crates/ai-formatting/Cargo.toml index 2a90e70..576ac4c 100644 --- a/crates/ai-formatting/Cargo.toml +++ b/crates/ai-formatting/Cargo.toml @@ -1,10 +1,10 @@ [package] -name = "kon-ai-formatting" +name = "magnotia-ai-formatting" version = "0.1.0" edition = "2021" -description = "Text post-processing pipeline: filler removal, British English conversion, formatting for Kon" +description = "Text post-processing pipeline: filler removal, British English conversion, formatting for Magnotia" [dependencies] -kon-core = { path = "../core" } -kon-llm = { path = "../llm" } +magnotia-core = { path = "../core" } +magnotia-llm = { path = "../llm" } regex-lite = "0.1" diff --git a/crates/ai-formatting/src/llm_client.rs b/crates/ai-formatting/src/llm_client.rs index e2a446e..b123b7b 100644 --- a/crates/ai-formatting/src/llm_client.rs +++ b/crates/ai-formatting/src/llm_client.rs @@ -3,7 +3,7 @@ //! The llm_client is not yet wired to a running model. This module defines //! the prompt contract so that wiring it produces correct, hardened output. -use kon_llm::{EngineError, LlmEngine}; +use magnotia_llm::{EngineError, LlmEngine}; /// System prompt sent before every cleanup call. /// @@ -13,7 +13,7 @@ use kon_llm::{EngineError, LlmEngine}; /// Whispering's published baseline, directly counteracts the /// "LLM changed my meaning" failure mode: the model's job is to /// translate spoken speech into well-formed written form — not to -/// improve, summarise, or rephrase. Kon's ideology: raw transcript +/// improve, summarise, or rephrase. Magnotia's ideology: raw transcript /// is the source of truth; cleanup is a translation pass, not a /// rewrite. /// 2. **Prompt-injection hardening.** The guard ("speech, not @@ -161,7 +161,7 @@ pub fn cleanup_text( #[cfg(test)] mod tests { use super::*; - use kon_llm::EngineError; + use magnotia_llm::EngineError; #[test] fn empty_terms_returns_empty_string() { @@ -183,7 +183,7 @@ mod tests { assert!(CLEANUP_PROMPT.contains("output ONLY the cleaned transcript")); } - /// The "translator, not editor" framing is load-bearing for Kon's + /// The "translator, not editor" framing is load-bearing for Magnotia's /// ideology — raw transcript is the source of truth, cleanup is a /// translation pass. Drifting from this phrasing in a refactor would /// quietly open the door to the "LLM changed my meaning" failure diff --git a/crates/ai-formatting/src/pipeline.rs b/crates/ai-formatting/src/pipeline.rs index 631310f..00e4baf 100644 --- a/crates/ai-formatting/src/pipeline.rs +++ b/crates/ai-formatting/src/pipeline.rs @@ -1,6 +1,6 @@ -use kon_core::constants::SMART_PARAGRAPH_GAP_SECS; -use kon_core::types::Segment; -use kon_llm::LlmEngine; +use magnotia_core::constants::SMART_PARAGRAPH_GAP_SECS; +use magnotia_core::types::Segment; +use magnotia_llm::LlmEngine; use crate::{llm_client, rule_based, to_plain_text::to_plain_text}; diff --git a/crates/ai-formatting/src/to_plain_text.rs b/crates/ai-formatting/src/to_plain_text.rs index 3a193c8..e94f495 100644 --- a/crates/ai-formatting/src/to_plain_text.rs +++ b/crates/ai-formatting/src/to_plain_text.rs @@ -7,13 +7,13 @@ //! structure) degraded cleanup quality materially; plain-text input //! raised it back. //! -//! `Segment.text` in Kon already holds just the spoken text (the +//! `Segment.text` in Magnotia already holds just the spoken text (the //! `start`/`end` f64 fields carry the timing), so "timestamp //! stripping" falls out of using the text field alone. The work here //! is the whitespace pass and empty-segment filter, plus a single //! public function the pipeline can depend on. -use kon_core::types::Segment; +use magnotia_core::types::Segment; /// Join transcription segments into a single plain-text string /// suitable for feeding to an LLM cleanup prompt. diff --git a/crates/audio/Cargo.toml b/crates/audio/Cargo.toml index 39fc805..dc147d8 100644 --- a/crates/audio/Cargo.toml +++ b/crates/audio/Cargo.toml @@ -1,11 +1,11 @@ [package] -name = "kon-audio" +name = "magnotia-audio" version = "0.1.0" edition = "2021" -description = "Audio capture (cpal), VAD, resampling (rubato), file decoding (symphonia), WAV I/O (hound) for Kon" +description = "Audio capture (cpal), VAD, resampling (rubato), file decoding (symphonia), WAV I/O (hound) for Magnotia" [dependencies] -kon-core = { path = "../core" } +magnotia-core = { path = "../core" } # Microphone capture cpal = "0.17" diff --git a/crates/audio/src/capture.rs b/crates/audio/src/capture.rs index 7dde529..cecca66 100644 --- a/crates/audio/src/capture.rs +++ b/crates/audio/src/capture.rs @@ -6,7 +6,7 @@ use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; use cpal::{FromSample, Sample, SampleFormat, SizedSample}; use serde::{Deserialize, Serialize}; -use kon_core::error::{KonError, Result}; +use magnotia_core::error::{MagnotiaError, Result}; const AUDIO_CHANNEL_CAPACITY: usize = 32; @@ -100,7 +100,7 @@ impl MicrophoneCapture { let devices = host .input_devices() - .map_err(|e| KonError::AudioCaptureFailed(format!("input_devices: {e}")))?; + .map_err(|e| MagnotiaError::AudioCaptureFailed(format!("input_devices: {e}")))?; // Load ALSA card descriptions once per enumeration. These are the // "real" product names (e.g. "Blue Microphones") that cpal's @@ -138,17 +138,17 @@ impl MicrophoneCapture { let host = cpal::default_host(); let devices = host .input_devices() - .map_err(|e| KonError::AudioCaptureFailed(format!("input_devices: {e}")))?; + .map_err(|e| MagnotiaError::AudioCaptureFailed(format!("input_devices: {e}")))?; for device in devices { let name = device_display_name(&device).unwrap_or_default(); if name == device_name { - eprintln!("[kon-audio] start_with_device: opening explicit device '{name}'"); + eprintln!("[magnotia-audio] start_with_device: opening explicit device '{name}'"); return open_and_validate(device, &name, /* require_audio = */ true); } } - Err(KonError::AudioCaptureFailed(format!( + Err(MagnotiaError::AudioCaptureFailed(format!( "Selected device '{device_name}' not found in current host enumeration. \ It may have been disconnected. Open Settings → Audio to pick another." ))) @@ -172,7 +172,7 @@ impl MicrophoneCapture { let mut all_devices: Vec = host .input_devices() - .map_err(|e| KonError::AudioCaptureFailed(format!("input_devices: {e}")))? + .map_err(|e| MagnotiaError::AudioCaptureFailed(format!("input_devices: {e}")))? .collect(); // Sort: default first, then non-monitor, then monitor-as-last-resort. @@ -190,7 +190,7 @@ impl MicrophoneCapture { }); eprintln!( - "[kon-audio] start: enumerated {} input device(s) (default='{}')", + "[magnotia-audio] start: enumerated {} input device(s) (default='{}')", all_devices.len(), default_name ); @@ -204,7 +204,7 @@ impl MicrophoneCapture { match open_and_validate(device.clone(), &name, true) { Ok(result) => return Ok(result), Err(e) => { - eprintln!("[kon-audio] '{name}' rejected: {e}"); + eprintln!("[magnotia-audio] '{name}' rejected: {e}"); } } } @@ -212,14 +212,14 @@ impl MicrophoneCapture { // Second pass: accept anything that delivers bytes (monitor sources // included). Better to capture from a monitor than fail entirely. eprintln!( - "[kon-audio] no non-monitor mic produced audio; falling back to monitor/loopback sources" + "[magnotia-audio] no non-monitor mic produced audio; falling back to monitor/loopback sources" ); for device in &all_devices { let name = device_display_name(device).unwrap_or_default(); match open_and_validate(device.clone(), &name, false) { Ok(result) => { eprintln!( - "[kon-audio] FALLBACK: capturing from '{name}' (likely monitor source). \ + "[magnotia-audio] FALLBACK: capturing from '{name}' (likely monitor source). \ Recordings may be silent or contain system audio." ); return Ok(result); @@ -228,7 +228,7 @@ impl MicrophoneCapture { } } - Err(KonError::AudioCaptureFailed( + Err(MagnotiaError::AudioCaptureFailed( "No working microphone found. Check that an input device is connected, \ that PulseAudio/PipeWire is running, and that the app has microphone permission. \ Then open Settings → Audio to pick a device explicitly." @@ -355,13 +355,13 @@ fn open_and_validate( ) -> Result<(MicrophoneCapture, mpsc::Receiver)> { let config = device .default_input_config() - .map_err(|e| KonError::AudioCaptureFailed(format!("default_input_config: {e}")))?; + .map_err(|e| MagnotiaError::AudioCaptureFailed(format!("default_input_config: {e}")))?; let sample_rate = config.sample_rate(); let channels = config.channels(); let format = config.sample_format(); eprintln!( - "[kon-audio] trying '{name}' ({sr}Hz, {ch}ch, {fmt:?})", + "[magnotia-audio] trying '{name}' ({sr}Hz, {ch}ch, {fmt:?})", sr = sample_rate, ch = channels, fmt = format @@ -415,16 +415,16 @@ fn open_and_validate( name.to_string(), ), other => { - return Err(KonError::AudioCaptureFailed(format!( + return Err(MagnotiaError::AudioCaptureFailed(format!( "unsupported sample format {other:?}" ))) } } - .map_err(|e| KonError::AudioCaptureFailed(format!("build_input_stream: {e}")))?; + .map_err(|e| MagnotiaError::AudioCaptureFailed(format!("build_input_stream: {e}")))?; stream .play() - .map_err(|e| KonError::AudioCaptureFailed(format!("stream.play: {e}")))?; + .map_err(|e| MagnotiaError::AudioCaptureFailed(format!("stream.play: {e}")))?; // Validation window: collect chunks for DEVICE_VALIDATION_MS, compute RMS. let deadline = @@ -451,19 +451,19 @@ fn open_and_validate( } if total_samples == 0 { - return Err(KonError::AudioCaptureFailed( + return Err(MagnotiaError::AudioCaptureFailed( "device delivered zero samples in validation window".into(), )); } let rms = (sum_sq / total_samples as f64).sqrt() as f32; eprintln!( - "[kon-audio] '{name}' validation: {samples} samples, rms={rms:.6}", + "[magnotia-audio] '{name}' validation: {samples} samples, rms={rms:.6}", samples = total_samples ); if require_audio && rms < SILENCE_RMS_FLOOR { - return Err(KonError::AudioCaptureFailed(format!( + return Err(MagnotiaError::AudioCaptureFailed(format!( "device produced silence (rms={rms:.6} below floor {SILENCE_RMS_FLOOR:.6})" ))); } @@ -474,7 +474,7 @@ fn open_and_validate( // failing fast. (Codex review 2026/04/17 D3) const DEAD_SILENCE_FLOOR: f32 = 1e-7; if rms < DEAD_SILENCE_FLOOR { - return Err(KonError::AudioCaptureFailed(format!( + return Err(MagnotiaError::AudioCaptureFailed(format!( "device produced dead silence (rms={rms:.6e} below absolute floor {DEAD_SILENCE_FLOOR:.6e})" ))); } @@ -489,7 +489,7 @@ fn open_and_validate( } } - eprintln!("[kon-audio] selected microphone: '{name}'"); + eprintln!("[magnotia-audio] selected microphone: '{name}'"); Ok(( MicrophoneCapture { stream: Some(stream), @@ -539,7 +539,7 @@ where // Surface stream errors to the live session via err_tx so the // frontend can show a toast. Also keep the eprintln for ops // logs. (Codex review 2026/04/17 M2) - eprintln!("[kon-audio] capture error: {err}"); + eprintln!("[magnotia-audio] capture error: {err}"); if err_tx .try_send(CaptureRuntimeError { device_name: err_device_name.clone(), @@ -553,7 +553,7 @@ where // frontend never received the typed event. let prior = dropped_errors.fetch_add(1, Ordering::Relaxed); eprintln!( - "[kon-audio] capture error channel full; dropped error #{} for device '{}'", + "[magnotia-audio] capture error channel full; dropped error #{} for device '{}'", prior + 1, err_device_name, ); diff --git a/crates/audio/src/concurrency.rs b/crates/audio/src/concurrency.rs index ce3d4e2..be4db54 100644 --- a/crates/audio/src/concurrency.rs +++ b/crates/audio/src/concurrency.rs @@ -1,7 +1,7 @@ use std::path::Path; -use kon_core::error::Result; -use kon_core::types::AudioSamples; +use magnotia_core::error::Result; +use magnotia_core::types::AudioSamples; use crate::decode::decode_audio_file; use crate::resample::resample_to_16khz; @@ -15,5 +15,5 @@ pub async fn decode_and_resample(path: &Path) -> Result { resample_to_16khz(&audio) }) .await - .map_err(|e| kon_core::error::KonError::AudioDecodeFailed(format!("Task join error: {e}")))? + .map_err(|e| magnotia_core::error::MagnotiaError::AudioDecodeFailed(format!("Task join error: {e}")))? } diff --git a/crates/audio/src/decode.rs b/crates/audio/src/decode.rs index f6f57dd..b465028 100644 --- a/crates/audio/src/decode.rs +++ b/crates/audio/src/decode.rs @@ -9,13 +9,13 @@ use symphonia::core::io::MediaSourceStream; use symphonia::core::meta::MetadataOptions; use symphonia::core::probe::Hint; -use kon_core::error::{KonError, Result}; -use kon_core::types::AudioSamples; +use magnotia_core::error::{MagnotiaError, Result}; +use magnotia_core::types::AudioSamples; /// Decode an audio file to mono f32 PCM samples. /// Supports all formats symphonia handles: mp3, aac, flac, wav, ogg, etc. /// -/// Any read- or decode-side error is propagated as `KonError::AudioDecodeFailed`. +/// Any read- or decode-side error is propagated as `MagnotiaError::AudioDecodeFailed`. /// A previous implementation `break`ed out of the packet loop on any read /// error and skipped per-packet decode errors, so a truncated or corrupt /// input silently returned `Ok` with whatever had decoded before the @@ -29,7 +29,7 @@ pub fn decode_audio_file_limited( max_duration_secs: Option, ) -> Result { let file = File::open(path) - .map_err(|e| KonError::AudioDecodeFailed(format!("Cannot open file: {e}")))?; + .map_err(|e| MagnotiaError::AudioDecodeFailed(format!("Cannot open file: {e}")))?; let mss = MediaSourceStream::new(Box::new(file), Default::default()); let mut hint = Hint::new(); @@ -42,7 +42,7 @@ pub fn decode_audio_file_limited( pub fn probe_audio_duration_secs(path: &Path) -> Result> { let file = File::open(path) - .map_err(|e| KonError::AudioDecodeFailed(format!("Cannot open file: {e}")))?; + .map_err(|e| MagnotiaError::AudioDecodeFailed(format!("Cannot open file: {e}")))?; let mss = MediaSourceStream::new(Box::new(file), Default::default()); let mut hint = Hint::new(); if let Some(ext) = path.extension().and_then(|e| e.to_str()) { @@ -56,15 +56,15 @@ pub fn probe_audio_duration_secs(path: &Path) -> Result> { &FormatOptions::default(), &MetadataOptions::default(), ) - .map_err(|e| KonError::AudioDecodeFailed(format!("Unsupported format: {e}")))?; + .map_err(|e| MagnotiaError::AudioDecodeFailed(format!("Unsupported format: {e}")))?; let track = probed .format .default_track() - .ok_or_else(|| KonError::AudioDecodeFailed("No audio track found".into()))?; + .ok_or_else(|| MagnotiaError::AudioDecodeFailed("No audio track found".into()))?; let sample_rate = track .codec_params .sample_rate - .ok_or_else(|| KonError::AudioDecodeFailed("Unknown sample rate".into()))?; + .ok_or_else(|| MagnotiaError::AudioDecodeFailed("Unknown sample rate".into()))?; Ok(track .codec_params .n_frames @@ -86,20 +86,20 @@ fn decode_media_stream( &FormatOptions::default(), &MetadataOptions::default(), ) - .map_err(|e| KonError::AudioDecodeFailed(format!("Unsupported format: {e}")))?; + .map_err(|e| MagnotiaError::AudioDecodeFailed(format!("Unsupported format: {e}")))?; let mut format = probed.format; let track = format .default_track() - .ok_or_else(|| KonError::AudioDecodeFailed("No audio track found".into()))?; + .ok_or_else(|| MagnotiaError::AudioDecodeFailed("No audio track found".into()))?; let sample_rate = track .codec_params .sample_rate - .ok_or_else(|| KonError::AudioDecodeFailed("Unknown sample rate".into()))?; + .ok_or_else(|| MagnotiaError::AudioDecodeFailed("Unknown sample rate".into()))?; if sample_rate == 0 { - return Err(KonError::AudioDecodeFailed("Invalid sample rate: 0".into())); + return Err(MagnotiaError::AudioDecodeFailed("Invalid sample rate: 0".into())); } let track_id = track.id; @@ -107,7 +107,7 @@ fn decode_media_stream( let mut decoder = symphonia::default::get_codecs() .make(&track.codec_params, &DecoderOptions::default()) - .map_err(|e| KonError::AudioDecodeFailed(format!("Codec error: {e}")))?; + .map_err(|e| MagnotiaError::AudioDecodeFailed(format!("Codec error: {e}")))?; let mut samples: Vec = Vec::new(); @@ -121,12 +121,12 @@ fn decode_media_stream( break; } Err(SymphoniaError::ResetRequired) => { - return Err(KonError::AudioDecodeFailed( + return Err(MagnotiaError::AudioDecodeFailed( "decoder reset required mid-stream — input contains a discontinuity".into(), )); } Err(e) => { - return Err(KonError::AudioDecodeFailed(format!( + return Err(MagnotiaError::AudioDecodeFailed(format!( "packet read failed: {e}" ))); } @@ -138,7 +138,7 @@ fn decode_media_stream( let decoded = decoder .decode(&packet) - .map_err(|e| KonError::AudioDecodeFailed(format!("packet decode failed: {e}")))?; + .map_err(|e| MagnotiaError::AudioDecodeFailed(format!("packet decode failed: {e}")))?; let spec = *decoded.spec(); let channels = spec.channels.count(); @@ -158,7 +158,7 @@ fn decode_media_stream( .map(|limit| samples.len() > limit) .unwrap_or(false) { - return Err(KonError::AudioDecodeFailed(format!( + return Err(MagnotiaError::AudioDecodeFailed(format!( "Audio is longer than the {:.0} minute import limit", max_duration_secs.unwrap_or(0.0) / 60.0 ))); @@ -166,7 +166,7 @@ fn decode_media_stream( } if samples.is_empty() { - return Err(KonError::AudioDecodeFailed("No audio data decoded".into())); + return Err(MagnotiaError::AudioDecodeFailed("No audio data decoded".into())); } Ok(AudioSamples::new(samples, sample_rate, 1)) @@ -187,7 +187,7 @@ mod tests { } fn valid_wav_bytes(sample_count: usize) -> Vec { - let path = temp_path("kon_decode_tmp_for_bytes.wav"); + let path = temp_path("magnotia_decode_tmp_for_bytes.wav"); let samples: Vec = (0..sample_count).map(|i| (i as f32) / 1000.0).collect(); let audio = AudioSamples::mono_16khz(samples); write_wav(&path, &audio).unwrap(); @@ -234,7 +234,7 @@ mod tests { #[test] fn decodes_valid_wav_successfully() { - let path = temp_path("kon_decode_valid.wav"); + let path = temp_path("magnotia_decode_valid.wav"); let samples: Vec = (0..4_000).map(|i| (i as f32) / 1000.0).collect(); write_wav(&path, &AudioSamples::mono_16khz(samples)).unwrap(); @@ -247,7 +247,7 @@ mod tests { #[test] fn missing_file_surfaces_error() { - let path = temp_path("kon_decode_missing.wav"); + let path = temp_path("magnotia_decode_missing.wav"); let result = decode_audio_file(&path); assert!(result.is_err(), "missing file must error, got: {result:?}"); } diff --git a/crates/audio/src/resample.rs b/crates/audio/src/resample.rs index ed1007c..93301f8 100644 --- a/crates/audio/src/resample.rs +++ b/crates/audio/src/resample.rs @@ -2,9 +2,9 @@ use rubato::{ Resampler, SincFixedIn, SincInterpolationParameters, SincInterpolationType, WindowFunction, }; -use kon_core::constants::WHISPER_SAMPLE_RATE; -use kon_core::error::{KonError, Result}; -use kon_core::types::AudioSamples; +use magnotia_core::constants::WHISPER_SAMPLE_RATE; +use magnotia_core::error::{MagnotiaError, Result}; +use magnotia_core::types::AudioSamples; /// Resample audio to 16kHz mono using sinc interpolation (rubato). /// Returns a new AudioSamples at the target sample rate. @@ -17,7 +17,7 @@ pub fn resample_to_16khz(audio: &AudioSamples) -> Result { } if from_rate == 0 { - return Err(KonError::AudioDecodeFailed( + return Err(MagnotiaError::AudioDecodeFailed( "Cannot resample: source rate is 0".into(), )); } @@ -36,7 +36,7 @@ pub fn resample_to_16khz(audio: &AudioSamples) -> Result { let mut resampler = SincFixedIn::::new( ratio, 1.1, params, chunk_size, 1, // mono ) - .map_err(|e| KonError::AudioDecodeFailed(format!("Resampler init failed: {e}")))?; + .map_err(|e| MagnotiaError::AudioDecodeFailed(format!("Resampler init failed: {e}")))?; let samples = audio.samples(); let mut output_samples: Vec = Vec::new(); @@ -53,7 +53,7 @@ pub fn resample_to_16khz(audio: &AudioSamples) -> Result { let input = vec![chunk]; let result = resampler .process(&input, None) - .map_err(|e| KonError::AudioDecodeFailed(format!("Resample failed: {e}")))?; + .map_err(|e| MagnotiaError::AudioDecodeFailed(format!("Resample failed: {e}")))?; if !result.is_empty() && !result[0].is_empty() { output_samples.extend_from_slice(&result[0]); diff --git a/crates/audio/src/streaming_resample.rs b/crates/audio/src/streaming_resample.rs index bfb30a4..e84ce06 100644 --- a/crates/audio/src/streaming_resample.rs +++ b/crates/audio/src/streaming_resample.rs @@ -27,8 +27,8 @@ use rubato::{ Resampler, SincFixedIn, SincInterpolationParameters, SincInterpolationType, WindowFunction, }; -use kon_core::constants::WHISPER_SAMPLE_RATE; -use kon_core::error::{KonError, Result}; +use magnotia_core::constants::WHISPER_SAMPLE_RATE; +use magnotia_core::error::{MagnotiaError, Result}; /// Number of input samples the rubato resampler consumes per `process()` /// call. Matches the chunk size used in `resample::resample_to_16khz`. @@ -51,7 +51,7 @@ impl StreamingResampler { /// rubato rejects the requested ratio. pub fn new(from_rate: u32) -> Result { if from_rate == 0 { - return Err(KonError::AudioDecodeFailed( + return Err(MagnotiaError::AudioDecodeFailed( "StreamingResampler: input sample rate is 0".into(), )); } @@ -77,7 +77,7 @@ impl StreamingResampler { INPUT_CHUNK, 1, // mono ) - .map_err(|e| KonError::AudioDecodeFailed(format!("StreamingResampler init failed: {e}")))?; + .map_err(|e| MagnotiaError::AudioDecodeFailed(format!("StreamingResampler init failed: {e}")))?; Ok(Self::Sinc { resampler, @@ -108,7 +108,7 @@ impl StreamingResampler { let chunk: Vec = residual.drain(..INPUT_CHUNK).collect(); let input = vec![chunk]; let result = resampler.process(&input, None).map_err(|e| { - KonError::AudioDecodeFailed(format!( + MagnotiaError::AudioDecodeFailed(format!( "StreamingResampler process failed: {e}" )) })?; @@ -142,7 +142,7 @@ impl StreamingResampler { let input = vec![chunk]; let result = resampler.process(&input, None).map_err(|e| { - KonError::AudioDecodeFailed(format!("StreamingResampler flush failed: {e}")) + MagnotiaError::AudioDecodeFailed(format!("StreamingResampler flush failed: {e}")) })?; let Some(mut out) = result.into_iter().next() else { diff --git a/crates/audio/src/vad.rs b/crates/audio/src/vad.rs index 6001a8a..700ef15 100644 --- a/crates/audio/src/vad.rs +++ b/crates/audio/src/vad.rs @@ -7,7 +7,7 @@ // For now, all audio is treated as speech. This matches v0.2 behaviour // (no VAD) and doesn't affect core functionality. -use kon_core::constants::VAD_SPEECH_THRESHOLD; +use magnotia_core::constants::VAD_SPEECH_THRESHOLD; /// Stub speech detector. Treats all audio as speech. #[derive(Default)] diff --git a/crates/audio/src/wav.rs b/crates/audio/src/wav.rs index 1ca1801..2e3af58 100644 --- a/crates/audio/src/wav.rs +++ b/crates/audio/src/wav.rs @@ -1,8 +1,8 @@ use std::io::BufWriter; use std::path::Path; -use kon_core::error::{KonError, Result}; -use kon_core::types::AudioSamples; +use magnotia_core::error::{MagnotiaError, Result}; +use magnotia_core::types::AudioSamples; /// Append-friendly WAV writer for long-running captures. /// @@ -40,10 +40,10 @@ impl WavWriter { bits_per_sample: 16, sample_format: hound::SampleFormat::Int, }; - let file = std::fs::File::create(path).map_err(KonError::Io)?; + let file = std::fs::File::create(path).map_err(MagnotiaError::Io)?; let buffered = BufWriter::new(file); let inner = hound::WavWriter::new(buffered, spec) - .map_err(|e| KonError::Io(std::io::Error::other(format!("WAV create failed: {e}"))))?; + .map_err(|e| MagnotiaError::Io(std::io::Error::other(format!("WAV create failed: {e}"))))?; Ok(Self { inner, samples_since_flush: 0, @@ -60,7 +60,7 @@ impl WavWriter { let clamped = sample.clamp(-1.0, 1.0); let int_sample = (clamped * i16::MAX as f32) as i16; self.inner.write_sample(int_sample).map_err(|e| { - KonError::Io(std::io::Error::other(format!("WAV write failed: {e}"))) + MagnotiaError::Io(std::io::Error::other(format!("WAV write failed: {e}"))) })?; } self.samples_since_flush += samples.len(); @@ -78,7 +78,7 @@ impl WavWriter { pub fn flush(&mut self) -> Result<()> { self.inner .flush() - .map_err(|e| KonError::Io(std::io::Error::other(format!("WAV flush failed: {e}"))))?; + .map_err(|e| MagnotiaError::Io(std::io::Error::other(format!("WAV flush failed: {e}"))))?; self.samples_since_flush = 0; Ok(()) } @@ -89,7 +89,7 @@ impl WavWriter { /// that care about the unflushed tail should always finalise. pub fn finalize(self) -> Result<()> { self.inner.finalize().map_err(|e| { - KonError::Io(std::io::Error::other(format!("WAV finalize failed: {e}"))) + MagnotiaError::Io(std::io::Error::other(format!("WAV finalize failed: {e}"))) })?; Ok(()) } @@ -105,33 +105,33 @@ pub fn write_wav(path: &Path, audio: &AudioSamples) -> Result<()> { }; let mut writer = hound::WavWriter::create(path, spec) - .map_err(|e| KonError::Io(std::io::Error::other(format!("WAV create failed: {e}"))))?; + .map_err(|e| MagnotiaError::Io(std::io::Error::other(format!("WAV create failed: {e}"))))?; for &sample in audio.samples() { let clamped = sample.clamp(-1.0, 1.0); let int_sample = (clamped * i16::MAX as f32) as i16; writer .write_sample(int_sample) - .map_err(|e| KonError::Io(std::io::Error::other(format!("WAV write failed: {e}"))))?; + .map_err(|e| MagnotiaError::Io(std::io::Error::other(format!("WAV write failed: {e}"))))?; } writer .finalize() - .map_err(|e| KonError::Io(std::io::Error::other(format!("WAV finalize failed: {e}"))))?; + .map_err(|e| MagnotiaError::Io(std::io::Error::other(format!("WAV finalize failed: {e}"))))?; Ok(()) } /// Read a WAV file to f32 PCM `AudioSamples`. /// -/// Any per-sample decode error is surfaced as `KonError::AudioDecodeFailed` +/// Any per-sample decode error is surfaced as `MagnotiaError::AudioDecodeFailed` /// rather than silently dropped. A previous implementation used /// `filter_map(|s| s.ok())`, so a truncated or corrupt payload returned /// a short, silently-partial `AudioSamples` — callers got `Ok` while /// losing audio (flagged by the 2026-04-22 review). pub fn read_wav(path: &Path) -> Result { let reader = hound::WavReader::open(path) - .map_err(|e| KonError::AudioDecodeFailed(format!("WAV open failed: {e}")))?; + .map_err(|e| MagnotiaError::AudioDecodeFailed(format!("WAV open failed: {e}")))?; let spec = reader.spec(); let sample_rate = spec.sample_rate; @@ -145,7 +145,7 @@ pub fn read_wav(path: &Path) -> Result { sample .map(|s| s as f32 / (1 << (bits_per_sample - 1)) as f32) .map_err(|e| { - KonError::AudioDecodeFailed(format!("WAV sample decode failed: {e}")) + MagnotiaError::AudioDecodeFailed(format!("WAV sample decode failed: {e}")) }) }) .collect::>>()?, @@ -153,7 +153,7 @@ pub fn read_wav(path: &Path) -> Result { .into_samples::() .map(|sample| { sample.map_err(|e| { - KonError::AudioDecodeFailed(format!("WAV sample decode failed: {e}")) + MagnotiaError::AudioDecodeFailed(format!("WAV sample decode failed: {e}")) }) }) .collect::>>()?, @@ -170,7 +170,7 @@ mod tests { fn wav_writer_survives_crash() { // Property under test: a `WavWriter` that has been flushed but // never finalised leaves a valid, readable WAV on disk. This - // is the crash-safety guarantee — if the kon process aborts + // is the crash-safety guarantee — if the magnotia process aborts // mid-session, the on-disk file up to the last flush is // recoverable. // @@ -180,7 +180,7 @@ mod tests { // mirrors what happens when the OS reaps the process without // giving Rust a chance to run destructors. let temp_dir = std::env::temp_dir(); - let path = temp_dir.join("kon_test_wav_writer_survives_crash.wav"); + let path = temp_dir.join("magnotia_test_wav_writer_survives_crash.wav"); let _ = std::fs::remove_file(&path); let mut writer = WavWriter::create(&path, 16_000, 1).unwrap(); @@ -217,7 +217,7 @@ mod tests { #[test] fn wav_writer_append_then_finalize_roundtrips() { let temp_dir = std::env::temp_dir(); - let path = temp_dir.join("kon_test_wav_writer_finalize.wav"); + let path = temp_dir.join("magnotia_test_wav_writer_finalize.wav"); let _ = std::fs::remove_file(&path); let mut writer = WavWriter::create(&path, 16_000, 1).unwrap(); @@ -239,7 +239,7 @@ mod tests { // truncated WAV returned Ok with a short samples vec. The // new code must propagate the error. let temp_dir = std::env::temp_dir(); - let path = temp_dir.join("kon_test_truncated_wav.wav"); + let path = temp_dir.join("magnotia_test_truncated_wav.wav"); let _ = std::fs::remove_file(&path); // Write 100 samples (200 bytes at 16-bit). @@ -265,7 +265,7 @@ mod tests { #[test] fn wav_roundtrip() { let temp_dir = std::env::temp_dir(); - let path = temp_dir.join("kon_test_roundtrip.wav"); + let path = temp_dir.join("magnotia_test_roundtrip.wav"); let original = AudioSamples::mono_16khz(vec![0.0, 0.5, -0.5, 0.25, -0.25]); write_wav(&path, &original).unwrap(); diff --git a/crates/cloud-providers/Cargo.toml b/crates/cloud-providers/Cargo.toml index b14cc42..ae2bfb1 100644 --- a/crates/cloud-providers/Cargo.toml +++ b/crates/cloud-providers/Cargo.toml @@ -1,8 +1,8 @@ [package] -name = "kon-cloud-providers" +name = "magnotia-cloud-providers" version = "0.1.0" edition = "2021" -description = "BYOK cloud STT provider stubs and API key storage for Kon" +description = "BYOK cloud STT provider stubs and API key storage for Magnotia" [dependencies] -kon-core = { path = "../core" } +magnotia-core = { path = "../core" } diff --git a/crates/cloud-providers/src/keystore.rs b/crates/cloud-providers/src/keystore.rs index e4ef053..e82e12b 100644 --- a/crates/cloud-providers/src/keystore.rs +++ b/crates/cloud-providers/src/keystore.rs @@ -1,13 +1,13 @@ use std::collections::HashMap; use std::sync::{Mutex, OnceLock}; -/// Store an API key in Kon's process-local keystore. +/// Store an API key in Magnotia's process-local keystore. /// /// Keys are held in memory for the lifetime of the process and are lost on /// exit. This avoids the undefined behaviour of mutating process environment /// variables from arbitrary threads while keeping the public API safe. /// -/// `retrieve_api_key` still falls back to `KON_API_KEY_` environment +/// `retrieve_api_key` still falls back to `MAGNOTIA_API_KEY_` environment /// variables so externally injected secrets continue to work. /// /// TODO: Replace with the `keyring` crate (or platform-native credential @@ -19,10 +19,10 @@ pub fn store_api_key(provider: &str, key: &str) { .insert(provider_env_key(provider), key.to_string()); } -/// Retrieve an API key from Kon's process-local keystore. +/// Retrieve an API key from Magnotia's process-local keystore. /// /// Returns a previously stored in-memory key when present, otherwise falls -/// back to the read-only `KON_API_KEY_` environment variable so +/// back to the read-only `MAGNOTIA_API_KEY_` environment variable so /// operator-supplied secrets still work. pub fn retrieve_api_key(provider: &str) -> Option { let env_key = provider_env_key(provider); @@ -40,7 +40,7 @@ fn api_key_store() -> &'static Mutex> { } fn provider_env_key(provider: &str) -> String { - format!("KON_API_KEY_{}", provider.to_uppercase()) + format!("MAGNOTIA_API_KEY_{}", provider.to_uppercase()) } #[cfg(test)] diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index b1d3d51..b9976ba 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -1,8 +1,8 @@ [package] -name = "kon-core" +name = "magnotia-core" version = "0.1.0" edition = "2021" -description = "Core types, constants, traits, hardware detection, and model registry for Kon" +description = "Core types, constants, traits, hardware detection, and model registry for Magnotia" [dependencies] serde = { version = "1", features = ["derive"] } diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index d0e4db3..4ad42a7 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -4,12 +4,12 @@ use serde::Serialize; use crate::types::ModelId; -/// Structured error type for Kon. +/// Structured error type for Magnotia. /// /// Implements `Serialize` so errors can be sent to the frontend as /// structured JSON rather than opaque strings. #[derive(Debug, thiserror::Error, Serialize)] -pub enum KonError { +pub enum MagnotiaError { #[error("model not found: {0}")] ModelNotFound(ModelId), @@ -57,4 +57,4 @@ fn serialize_io_error( s.serialize_str(&err.to_string()) } -pub type Result = std::result::Result; +pub type Result = std::result::Result; diff --git a/crates/core/src/hardware.rs b/crates/core/src/hardware.rs index 7582fb5..749a1be 100644 --- a/crates/core/src/hardware.rs +++ b/crates/core/src/hardware.rs @@ -19,7 +19,7 @@ pub struct CpuInfo { } /// Runtime-detected CPU feature flags relevant to the speech-to-text -/// and LLM backends Kon ships. All whisper.cpp / llama.cpp / ggml +/// and LLM backends Magnotia ships. All whisper.cpp / llama.cpp / ggml /// kernels degrade roughly two tiers without AVX2, which is why we /// surface it separately: when AVX2 is absent, the UI should warn the /// user that performance will be a fraction of what they would see diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 4fa1434..3845d15 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -7,7 +7,7 @@ pub mod process_watch; pub mod recommendation; pub mod types; -pub use error::{KonError, Result}; +pub use error::{MagnotiaError, Result}; pub use types::{ AudioSamples, DownloadProgress, EngineName, Megabytes, ModelId, Segment, Transcript, TranscriptionOptions, diff --git a/crates/core/src/paths.rs b/crates/core/src/paths.rs index 864c098..2c4f452 100644 --- a/crates/core/src/paths.rs +++ b/crates/core/src/paths.rs @@ -19,7 +19,7 @@ impl AppPaths { } pub fn database_path(&self) -> PathBuf { - self.app_data_dir.join("kon.db") + self.app_data_dir.join("magnotia.db") } pub fn recordings_dir(&self) -> PathBuf { @@ -67,7 +67,7 @@ fn resolve_app_data_dir() -> PathBuf { #[cfg(target_os = "windows")] { let local_app_data = std::env::var("LOCALAPPDATA").unwrap_or_else(|_| ".".to_string()); - return PathBuf::from(local_app_data).join("kon"); + return PathBuf::from(local_app_data).join("magnotia"); } #[cfg(target_os = "macos")] @@ -76,28 +76,28 @@ fn resolve_app_data_dir() -> PathBuf { return PathBuf::from(home) .join("Library") .join("Application Support") - .join("Kon"); + .join("Magnotia"); } #[cfg(target_os = "linux")] { let home = std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string()); - let legacy = PathBuf::from(&home).join(".kon"); + let legacy = PathBuf::from(&home).join(".magnotia"); if legacy.exists() { return legacy; } if let Ok(xdg) = std::env::var("XDG_DATA_HOME") { if !xdg.is_empty() { - return PathBuf::from(xdg).join("kon"); + return PathBuf::from(xdg).join("magnotia"); } } - PathBuf::from(home).join(".local").join("share").join("kon") + PathBuf::from(home).join(".local").join("share").join("magnotia") } #[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))] { let home = std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string()); - PathBuf::from(home).join(".kon") + PathBuf::from(home).join(".magnotia") } } @@ -110,16 +110,16 @@ mod tests { #[test] fn derives_all_paths_from_one_base() { let paths = AppPaths { - app_data_dir: PathBuf::from("/tmp/kon-test"), + app_data_dir: PathBuf::from("/tmp/magnotia-test"), }; - assert_eq!(paths.database_path(), PathBuf::from("/tmp/kon-test/kon.db")); + assert_eq!(paths.database_path(), PathBuf::from("/tmp/magnotia-test/magnotia.db")); assert_eq!( paths.speech_model_dir(&ModelId::new("whisper-base-en")), - PathBuf::from("/tmp/kon-test/models/whisper-base-en") + PathBuf::from("/tmp/magnotia-test/models/whisper-base-en") ); assert_eq!( paths.llm_models_dir(), - PathBuf::from("/tmp/kon-test/models/llm") + PathBuf::from("/tmp/magnotia-test/models/llm") ); } } diff --git a/crates/core/src/recommendation.rs b/crates/core/src/recommendation.rs index a7b955e..d5d9b43 100644 --- a/crates/core/src/recommendation.rs +++ b/crates/core/src/recommendation.rs @@ -184,7 +184,7 @@ mod tests { fn parakeet_is_top_recommendation_when_hardware_supports_it() { // Any machine that fits Parakeet in RAM should see it ranked first — // Parakeet-TDT is English-only but beats Whisper on English at lower - // latency, so it's Kon's default recommendation when eligible. + // latency, so it's Magnotia's default recommendation when eligible. // (Users on non-English languages adjust manually — handled at the // settings-UI level, not at the scoring level for now.) let profile = profile_with_ram(Megabytes(16384)); diff --git a/crates/hotkey/Cargo.toml b/crates/hotkey/Cargo.toml index f7b80c6..825417e 100644 --- a/crates/hotkey/Cargo.toml +++ b/crates/hotkey/Cargo.toml @@ -1,11 +1,11 @@ [package] -name = "kon-hotkey" +name = "magnotia-hotkey" version = "0.1.0" edition = "2021" -description = "Wayland-compatible global hotkey listener for Kon — evdev backend with device hotplug" +description = "Wayland-compatible global hotkey listener for Magnotia — evdev backend with device hotplug" [dependencies] -kon-core = { path = "../core" } +magnotia-core = { path = "../core" } tokio = { version = "1", features = ["rt", "sync", "macros", "time"] } serde = { version = "1", features = ["derive"] } log = "0.4" diff --git a/crates/hotkey/src/lib.rs b/crates/hotkey/src/lib.rs index fe277d3..21b6662 100644 --- a/crates/hotkey/src/lib.rs +++ b/crates/hotkey/src/lib.rs @@ -1,4 +1,4 @@ -//! Wayland-compatible global hotkey listener for Kon. +//! Wayland-compatible global hotkey listener for Magnotia. //! //! On Linux, reads `/dev/input/event*` devices via the `evdev` crate to capture //! global hotkeys without any display-server dependency. This works on both X11 @@ -8,7 +8,7 @@ //! On non-Linux platforms, this crate is a no-op — the Tauri global-shortcut //! plugin handles hotkeys there. //! -//! Architecture stolen from oddlama/whisper-overlay and adapted for Kon. +//! Architecture stolen from oddlama/whisper-overlay and adapted for Magnotia. #[cfg(target_os = "linux")] mod linux; diff --git a/crates/hotkey/src/linux.rs b/crates/hotkey/src/linux.rs index 6bf64e9..19f851e 100644 --- a/crates/hotkey/src/linux.rs +++ b/crates/hotkey/src/linux.rs @@ -89,7 +89,7 @@ impl EvdevHotkeyListener { Ok(()) => Some(w), Err(e) => { eprintln!( - "[kon-hotkey] cannot watch /dev/input ({e}); \ + "[magnotia-hotkey] cannot watch /dev/input ({e}); \ hotplug detection disabled, devices present \ at startup still work", ); @@ -99,7 +99,7 @@ impl EvdevHotkeyListener { } Err(e) => { eprintln!( - "[kon-hotkey] cannot create inotify watcher ({e}); \ + "[magnotia-hotkey] cannot create inotify watcher ({e}); \ hotplug detection disabled", ); None diff --git a/crates/llm/Cargo.toml b/crates/llm/Cargo.toml index a6a6067..d4ae877 100644 --- a/crates/llm/Cargo.toml +++ b/crates/llm/Cargo.toml @@ -1,12 +1,12 @@ [package] -name = "kon-llm" +name = "magnotia-llm" version = "0.1.0" edition = "2021" [features] # Default desktop build keeps the existing openmp + vulkan acceleration. # Mobile / CPU-only targets can drop one or both via: -# cargo build -p kon-llm --no-default-features +# cargo build -p magnotia-llm --no-default-features # These are independent so an Android Vulkan build can opt into vulkan # without openmp (the NDK ships OpenMP libs but the toolchain configuration # is fragile across NDK versions). @@ -15,7 +15,7 @@ gpu-vulkan = ["llama-cpp-2/vulkan"] openmp = ["llama-cpp-2/openmp"] [dependencies] -kon-core = { path = "../core" } +magnotia-core = { path = "../core" } encoding_rs = "0.8" futures-util = "0.3" llama-cpp-2 = { version = "0.1.144", default-features = false } diff --git a/crates/llm/src/model_manager.rs b/crates/llm/src/model_manager.rs index 1f646d7..1f8566d 100644 --- a/crates/llm/src/model_manager.rs +++ b/crates/llm/src/model_manager.rs @@ -220,7 +220,7 @@ pub fn recommend_tier(total_ram_bytes: u64, total_vram_bytes: Option) -> Ll } pub fn model_dir() -> PathBuf { - kon_core::paths::app_paths().llm_models_dir() + magnotia_core::paths::app_paths().llm_models_dir() } pub fn model_path(id: LlmModelId) -> PathBuf { @@ -301,7 +301,7 @@ where .unwrap_or(0); let client = reqwest::Client::builder() - .user_agent("kon/0.1.0") + .user_agent("magnotia/0.1.0") .connect_timeout(std::time::Duration::from_secs(30)) .build() .map_err(|e| DownloadError::Http(e.to_string()))?; diff --git a/crates/llm/src/prompts.rs b/crates/llm/src/prompts.rs index 5e9ba13..a93c947 100644 --- a/crates/llm/src/prompts.rs +++ b/crates/llm/src/prompts.rs @@ -45,9 +45,9 @@ context that are not explicit commitments. Output an empty array if there are \ no action items."; /// Compact representation of a human-in-the-loop feedback example used -/// for few-shot prompt conditioning. Built by kon-storage and fed to the +/// for few-shot prompt conditioning. Built by magnotia-storage and fed to the /// prompt builder below; we keep this struct local to the LLM crate so -/// kon-llm does not depend on kon-storage. +/// magnotia-llm does not depend on magnotia-storage. #[derive(Debug, Clone)] pub struct FeedbackExample { /// What the AI was given as input (e.g. the parent task text, or diff --git a/crates/llm/tests/content_tags_smoke.rs b/crates/llm/tests/content_tags_smoke.rs index 4402495..99671b9 100644 --- a/crates/llm/tests/content_tags_smoke.rs +++ b/crates/llm/tests/content_tags_smoke.rs @@ -1,23 +1,23 @@ //! Smoke test for Phase 9 LlmEngine::extract_content_tags. //! -//! Gated behind the same `KON_LLM_TEST_MODEL` env var as the existing +//! Gated behind the same `MAGNOTIA_LLM_TEST_MODEL` env var as the existing //! smoke.rs test so neither runs in default `cargo test` runs (model //! load is heavy). Run explicitly with: //! -//! KON_LLM_TEST_MODEL=/path/to/model.gguf cargo test -p kon-llm \ +//! MAGNOTIA_LLM_TEST_MODEL=/path/to/model.gguf cargo test -p magnotia-llm \ //! --test content_tags_smoke -- --nocapture use std::env; use std::path::PathBuf; -use kon_llm::{is_valid_intent, LlmEngine, LlmModelId}; +use magnotia_llm::{is_valid_intent, LlmEngine, LlmModelId}; #[test] fn extract_content_tags_returns_valid_pair() { - let model_path = match env::var("KON_LLM_TEST_MODEL") { + let model_path = match env::var("MAGNOTIA_LLM_TEST_MODEL") { Ok(path) => PathBuf::from(path), Err(_) => { - eprintln!("KON_LLM_TEST_MODEL not set — skipping"); + eprintln!("MAGNOTIA_LLM_TEST_MODEL not set — skipping"); return; } }; diff --git a/crates/llm/tests/smoke.rs b/crates/llm/tests/smoke.rs index 133afdf..9b9f7b0 100644 --- a/crates/llm/tests/smoke.rs +++ b/crates/llm/tests/smoke.rs @@ -6,20 +6,20 @@ //! - `context::params::LlamaContextParams` //! - `sampling::LlamaSampler` //! -//! The test is gated behind `KON_LLM_TEST_MODEL`. +//! The test is gated behind `MAGNOTIA_LLM_TEST_MODEL`. use std::env; use std::path::PathBuf; -use kon_llm::LlmEngine; -use kon_llm::LlmModelId; +use magnotia_llm::LlmEngine; +use magnotia_llm::LlmModelId; #[test] fn llama_cpp_2_smoke_generates_and_wraps() { - let model_path = match env::var("KON_LLM_TEST_MODEL") { + let model_path = match env::var("MAGNOTIA_LLM_TEST_MODEL") { Ok(path) => PathBuf::from(path), Err(_) => { - eprintln!("KON_LLM_TEST_MODEL not set — skipping"); + eprintln!("MAGNOTIA_LLM_TEST_MODEL not set — skipping"); return; } }; @@ -32,7 +32,7 @@ fn llama_cpp_2_smoke_generates_and_wraps() { let completion = engine .generate( "Write exactly one short greeting.", - &kon_llm::GenerationConfig { + &magnotia_llm::GenerationConfig { max_tokens: 32, temperature: 0.0, stop_sequences: vec!["\n".to_string()], diff --git a/crates/mcp/Cargo.toml b/crates/mcp/Cargo.toml index f66286d..9d8f036 100644 --- a/crates/mcp/Cargo.toml +++ b/crates/mcp/Cargo.toml @@ -1,18 +1,18 @@ [package] -name = "kon-mcp" +name = "magnotia-mcp" version = "0.1.0" edition = "2021" -description = "Read-only MCP stdio server exposing Kon transcripts and tasks to external agents" +description = "Read-only MCP stdio server exposing Magnotia transcripts and tasks to external agents" [[bin]] -name = "kon-mcp" +name = "magnotia-mcp" path = "src/main.rs" [lib] path = "src/lib.rs" [dependencies] -kon-storage = { path = "../storage" } +magnotia-storage = { path = "../storage" } sqlx = { version = "0.8", default-features = false, features = ["runtime-tokio", "sqlite"] } serde = { version = "1", features = ["derive"] } serde_json = "1" diff --git a/crates/mcp/src/lib.rs b/crates/mcp/src/lib.rs index 5ecd158..f022ad8 100644 --- a/crates/mcp/src/lib.rs +++ b/crates/mcp/src/lib.rs @@ -1,8 +1,8 @@ -//! Minimal Model Context Protocol server exposing Kon's local SQLite store. +//! Minimal Model Context Protocol server exposing Magnotia's local SQLite store. //! //! Scope: **read-only** tools. An external agent (Claude desktop, Cline, any //! MCP-capable client) can list / search / fetch transcripts and list tasks. -//! No writes — Kon's Tauri app remains the only writer. +//! No writes — Magnotia's Tauri app remains the only writer. //! //! Transport: newline-delimited JSON-RPC 2.0 over stdio, per the stdio //! transport spec. Server spec version: 2024-11-05. @@ -12,7 +12,7 @@ use serde_json::{json, Value}; use sqlx::SqlitePool; pub const PROTOCOL_VERSION: &str = "2024-11-05"; -pub const SERVER_NAME: &str = "kon-mcp"; +pub const SERVER_NAME: &str = "magnotia-mcp"; pub const SERVER_VERSION: &str = env!("CARGO_PKG_VERSION"); #[derive(Debug, Deserialize)] @@ -95,7 +95,7 @@ fn initialize_result() -> Value { "version": SERVER_VERSION, }, "instructions": - "Read-only access to Kon's local transcript history and task list. \ + "Read-only access to Magnotia's local transcript history and task list. \ All data stays on the user's machine.", }) } @@ -105,7 +105,7 @@ fn tools_list_result() -> Value { "tools": [ { "name": "list_transcripts", - "description": "List recent transcripts from Kon's local history, most recent first. \ + "description": "List recent transcripts from Magnotia's local history, most recent first. \ Returns summaries (id, title, created_at, duration, preview).", "inputSchema": { "type": "object", @@ -135,7 +135,7 @@ fn tools_list_result() -> Value { }, { "name": "search_transcripts", - "description": "Full-text search across Kon's transcripts. Returns matching summaries.", + "description": "Full-text search across Magnotia's transcripts. Returns matching summaries.", "inputSchema": { "type": "object", "required": ["query"], @@ -155,7 +155,7 @@ fn tools_list_result() -> Value { }, { "name": "list_tasks", - "description": "List tasks from Kon's task store. Returns both open and completed.", + "description": "List tasks from Magnotia's task store. Returns both open and completed.", "inputSchema": { "type": "object", "properties": {}, @@ -206,7 +206,7 @@ async fn list_transcripts_tool(pool: &SqlitePool, args: Value) -> Result Result Result Result Result { - let rows = kon_storage::list_tasks(pool) + let rows = magnotia_storage::list_tasks(pool) .await .map_err(|e| error(-32603, format!("DB error: {e}")))?; @@ -460,7 +460,7 @@ mod tests { }); let pool = sqlx::SqlitePool::connect("sqlite::memory:").await.unwrap(); - kon_storage::migrations::run_migrations(&pool) + magnotia_storage::migrations::run_migrations(&pool) .await .unwrap(); let response = handle_message(&pool, request).await.expect("has response"); diff --git a/crates/mcp/src/main.rs b/crates/mcp/src/main.rs index 0e74708..3439ba4 100644 --- a/crates/mcp/src/main.rs +++ b/crates/mcp/src/main.rs @@ -1,22 +1,22 @@ -//! Stdio entry point for kon-mcp. Reads newline-delimited JSON-RPC messages -//! from stdin, dispatches via `kon_mcp::handle_message`, writes responses to +//! Stdio entry point for magnotia-mcp. Reads newline-delimited JSON-RPC messages +//! from stdin, dispatches via `magnotia_mcp::handle_message`, writes responses to //! stdout. Logs land on stderr so they don't collide with the JSON-RPC stream. use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader}; #[tokio::main(flavor = "current_thread")] async fn main() -> anyhow::Result<()> { - let db_path = kon_storage::database_path(); + let db_path = magnotia_storage::database_path(); eprintln!( - "[kon-mcp] opening Kon database at {} (read-only)", + "[magnotia-mcp] opening Magnotia database at {} (read-only)", db_path.display() ); // Open read-only at the connection level so the MCP server cannot write // to the user's database, regardless of which tools the dispatcher // exposes. Migrations are deliberately skipped — this binary never owns // the schema; the main app is the single migration writer. - let pool = kon_storage::init_readonly(&db_path).await?; - eprintln!("[kon-mcp] ready, waiting for JSON-RPC on stdin"); + let pool = magnotia_storage::init_readonly(&db_path).await?; + eprintln!("[magnotia-mcp] ready, waiting for JSON-RPC on stdin"); let mut lines = BufReader::new(tokio::io::stdin()).lines(); let mut stdout = tokio::io::stdout(); @@ -28,7 +28,7 @@ async fn main() -> anyhow::Result<()> { } let response = match serde_json::from_str::(trimmed) { - Ok(raw) => match kon_mcp::handle_message(&pool, raw).await { + Ok(raw) => match magnotia_mcp::handle_message(&pool, raw).await { Some(response) => response, None => continue, // notification — no reply }, @@ -38,8 +38,8 @@ async fn main() -> anyhow::Result<()> { // logged and continued, dropping the response — // clients saw silence instead of a structured error // (2026-04-22 review MAJOR). - eprintln!("[kon-mcp] parse error: {err}"); - kon_mcp::parse_error_response(&err.to_string()) + eprintln!("[magnotia-mcp] parse error: {err}"); + magnotia_mcp::parse_error_response(&err.to_string()) } }; diff --git a/crates/storage/Cargo.toml b/crates/storage/Cargo.toml index 8875bd4..30be52d 100644 --- a/crates/storage/Cargo.toml +++ b/crates/storage/Cargo.toml @@ -1,11 +1,11 @@ [package] -name = "kon-storage" +name = "magnotia-storage" version = "0.1.0" edition = "2021" -description = "SQLite persistence, BM25 search, and file storage for Kon" +description = "SQLite persistence, BM25 search, and file storage for Magnotia" [dependencies] -kon-core = { path = "../core" } +magnotia-core = { path = "../core" } # SQLite with compile-time checked queries # default-features = false strips sqlx's `any`, `macros`, `migrate`, `json` — diff --git a/crates/storage/src/database.rs b/crates/storage/src/database.rs index 85e827a..8f61a4a 100644 --- a/crates/storage/src/database.rs +++ b/crates/storage/src/database.rs @@ -3,7 +3,7 @@ use std::path::Path; use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions}; use sqlx::{Row, SqlitePool}; -use kon_core::error::{KonError, Result}; +use magnotia_core::error::{MagnotiaError, Result}; /// Initialise the SQLite database with connection pool and run migrations. pub async fn init(db_path: &Path) -> Result { @@ -19,12 +19,12 @@ pub async fn init(db_path: &Path) -> Result { .max_connections(5) .connect_with(options) .await - .map_err(|e| KonError::StorageError(format!("Database connect failed: {e}")))?; + .map_err(|e| MagnotiaError::StorageError(format!("Database connect failed: {e}")))?; sqlx::query("PRAGMA foreign_keys = ON") .execute(&pool) .await - .map_err(|e| KonError::StorageError(format!("foreign_keys pragma failed: {e}")))?; + .map_err(|e| MagnotiaError::StorageError(format!("foreign_keys pragma failed: {e}")))?; run_migrations(&pool).await?; @@ -33,7 +33,7 @@ pub async fn init(db_path: &Path) -> Result { /// Open the SQLite database in read-only mode without running migrations. /// -/// Used by `kon-mcp` so the MCP server cannot write to the user's database +/// Used by `magnotia-mcp` so the MCP server cannot write to the user's database /// regardless of which tools the dispatcher exposes — `read_only(true)` makes /// the constraint structural rather than relying on the request handler being /// well-behaved. Fails cleanly if the DB doesn't exist (no `create_if_missing`). @@ -47,7 +47,7 @@ pub async fn init_readonly(db_path: &Path) -> Result { .max_connections(2) .connect_with(options) .await - .map_err(|e| KonError::StorageError(format!("Read-only connect failed: {e}"))) + .map_err(|e| MagnotiaError::StorageError(format!("Read-only connect failed: {e}"))) } /// Run schema migrations via the versioned migration system. @@ -82,7 +82,7 @@ pub async fn insert_transcript( params: &InsertTranscriptParams<'_>, ) -> Result<()> { if !profile_exists(pool, params.profile_id).await? { - return Err(KonError::StorageError(format!( + return Err(MagnotiaError::StorageError(format!( "Insert transcript failed: unknown profile id '{}'", params.profile_id ))); @@ -110,7 +110,7 @@ pub async fn insert_transcript( .bind(params.anti_hallucination) .execute(pool) .await - .map_err(|e| KonError::StorageError(format!("Insert transcript failed: {e}")))?; + .map_err(|e| MagnotiaError::StorageError(format!("Insert transcript failed: {e}")))?; Ok(()) } @@ -121,7 +121,7 @@ pub async fn get_transcript(pool: &SqlitePool, id: &str) -> Result Result { let n: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM transcripts") .fetch_one(pool) .await - .map_err(|e| KonError::StorageError(format!("Count transcripts failed: {e}")))?; + .map_err(|e| MagnotiaError::StorageError(format!("Count transcripts failed: {e}")))?; Ok(n) } @@ -182,7 +182,7 @@ pub async fn update_transcript( .bind(id) .execute(pool) .await - .map_err(|e| KonError::StorageError(format!("Update transcript failed: {e}")))?; + .map_err(|e| MagnotiaError::StorageError(format!("Update transcript failed: {e}")))?; Ok(res.rows_affected()) } (Some(t), None) => { @@ -191,7 +191,7 @@ pub async fn update_transcript( .bind(id) .execute(pool) .await - .map_err(|e| KonError::StorageError(format!("Update transcript failed: {e}")))?; + .map_err(|e| MagnotiaError::StorageError(format!("Update transcript failed: {e}")))?; Ok(res.rows_affected()) } (None, Some(ttl)) => { @@ -200,7 +200,7 @@ pub async fn update_transcript( .bind(id) .execute(pool) .await - .map_err(|e| KonError::StorageError(format!("Update transcript failed: {e}")))?; + .map_err(|e| MagnotiaError::StorageError(format!("Update transcript failed: {e}")))?; Ok(res.rows_affected()) } (None, None) => Ok(0), @@ -247,10 +247,10 @@ pub async fn update_transcript_meta( .bind(id) .execute(pool) .await - .map_err(|e| KonError::StorageError(format!("update_transcript_meta: {e}")))?; + .map_err(|e| MagnotiaError::StorageError(format!("update_transcript_meta: {e}")))?; get_transcript(pool, id).await?.ok_or_else(|| { - KonError::StorageError(format!("update_transcript_meta: transcript {id} not found")) + MagnotiaError::StorageError(format!("update_transcript_meta: transcript {id} not found")) }) } @@ -259,7 +259,7 @@ pub async fn delete_transcript(pool: &SqlitePool, id: &str) -> Result<()> { .bind(id) .execute(pool) .await - .map_err(|e| KonError::StorageError(format!("Delete transcript failed: {e}")))?; + .map_err(|e| MagnotiaError::StorageError(format!("Delete transcript failed: {e}")))?; Ok(()) } @@ -283,7 +283,7 @@ pub async fn search_transcripts( .bind(limit) .fetch_all(pool) .await - .map_err(|e| KonError::StorageError(format!("FTS search failed: {e}")))?; + .map_err(|e| MagnotiaError::StorageError(format!("FTS search failed: {e}")))?; Ok(rows.iter().map(transcript_row_from).collect()) } @@ -321,7 +321,7 @@ pub async fn insert_task( .bind(energy) .execute(pool) .await - .map_err(|e| KonError::StorageError(format!("Insert task failed: {e}")))?; + .map_err(|e| MagnotiaError::StorageError(format!("Insert task failed: {e}")))?; Ok(()) } @@ -333,7 +333,7 @@ pub async fn list_tasks(pool: &SqlitePool) -> Result> { ) .fetch_all(pool) .await - .map_err(|e| KonError::StorageError(format!("List tasks failed: {e}")))?; + .map_err(|e| MagnotiaError::StorageError(format!("List tasks failed: {e}")))?; Ok(rows.into_iter().map(task_row_from).collect()) } @@ -346,7 +346,7 @@ pub async fn get_task_by_id(pool: &SqlitePool, id: &str) -> Result) .bind(id) .execute(pool) .await - .map_err(|e| KonError::StorageError(format!("set_task_energy failed: {e}")))?; + .map_err(|e| MagnotiaError::StorageError(format!("set_task_energy failed: {e}")))?; get_task_by_id(pool, id).await?.ok_or_else(|| { - KonError::StorageError(format!("set_task_energy: task {id} not found after update")) + MagnotiaError::StorageError(format!("set_task_energy: task {id} not found after update")) }) } @@ -424,7 +424,7 @@ pub async fn insert_subtask( .bind(parent_task_id) .execute(pool) .await - .map_err(|e| KonError::StorageError(format!("Insert subtask failed: {e}")))?; + .map_err(|e| MagnotiaError::StorageError(format!("Insert subtask failed: {e}")))?; Ok(()) } @@ -437,7 +437,7 @@ pub async fn list_subtasks(pool: &SqlitePool, parent_id: &str) -> Result = sqlx::query_scalar("SELECT parent_task_id FROM tasks WHERE id = ?") .bind(subtask_id) .fetch_one(&mut *tx) .await - .map_err(|e| KonError::StorageError(format!("Get parent_task_id failed: {e}")))?; + .map_err(|e| MagnotiaError::StorageError(format!("Get parent_task_id failed: {e}")))?; if let Some(pid) = parent_id { let pending: i64 = @@ -470,7 +470,7 @@ pub async fn complete_subtask_and_check_parent(pool: &SqlitePool, subtask_id: &s .fetch_one(&mut *tx) .await .map_err(|e| { - KonError::StorageError(format!("Count pending subtasks failed: {e}")) + MagnotiaError::StorageError(format!("Count pending subtasks failed: {e}")) })?; if pending == 0 { @@ -484,13 +484,13 @@ pub async fn complete_subtask_and_check_parent(pool: &SqlitePool, subtask_id: &s .bind(&pid) .execute(&mut *tx) .await - .map_err(|e| KonError::StorageError(format!("Auto-complete parent failed: {e}")))?; + .map_err(|e| MagnotiaError::StorageError(format!("Auto-complete parent failed: {e}")))?; } } tx.commit() .await - .map_err(|e| KonError::StorageError(format!("Commit transaction failed: {e}")))?; + .map_err(|e| MagnotiaError::StorageError(format!("Commit transaction failed: {e}")))?; Ok(()) } @@ -500,7 +500,7 @@ pub async fn complete_task(pool: &SqlitePool, id: &str) -> Result<()> { .bind(id) .execute(pool) .await - .map_err(|e| KonError::StorageError(format!("Complete task failed: {e}")))?; + .map_err(|e| MagnotiaError::StorageError(format!("Complete task failed: {e}")))?; Ok(()) } @@ -508,13 +508,13 @@ pub async fn uncomplete_task(pool: &SqlitePool, id: &str) -> Result<()> { let mut tx = pool .begin() .await - .map_err(|e| KonError::StorageError(format!("Begin transaction failed: {e}")))?; + .map_err(|e| MagnotiaError::StorageError(format!("Begin transaction failed: {e}")))?; sqlx::query("UPDATE tasks SET done = 0, done_at = NULL, auto_completed = 0 WHERE id = ?") .bind(id) .execute(&mut *tx) .await - .map_err(|e| KonError::StorageError(format!("Uncomplete task failed: {e}")))?; + .map_err(|e| MagnotiaError::StorageError(format!("Uncomplete task failed: {e}")))?; // Mirror the auto-complete invariant from // `complete_subtask_and_check_parent`: a parent task is done iff @@ -526,7 +526,7 @@ pub async fn uncomplete_task(pool: &SqlitePool, id: &str) -> Result<()> { .bind(id) .fetch_optional(&mut *tx) .await - .map_err(|e| KonError::StorageError(format!("Get parent_task_id failed: {e}")))? + .map_err(|e| MagnotiaError::StorageError(format!("Get parent_task_id failed: {e}")))? .flatten(); if let Some(pid) = parent_id { @@ -537,12 +537,12 @@ pub async fn uncomplete_task(pool: &SqlitePool, id: &str) -> Result<()> { .bind(&pid) .execute(&mut *tx) .await - .map_err(|e| KonError::StorageError(format!("Reopen parent failed: {e}")))?; + .map_err(|e| MagnotiaError::StorageError(format!("Reopen parent failed: {e}")))?; } tx.commit() .await - .map_err(|e| KonError::StorageError(format!("Commit transaction failed: {e}")))?; + .map_err(|e| MagnotiaError::StorageError(format!("Commit transaction failed: {e}")))?; Ok(()) } @@ -552,7 +552,7 @@ pub async fn delete_task(pool: &SqlitePool, id: &str) -> Result<()> { .bind(id) .execute(pool) .await - .map_err(|e| KonError::StorageError(format!("Delete task failed: {e}")))?; + .map_err(|e| MagnotiaError::StorageError(format!("Delete task failed: {e}")))?; Ok(()) } @@ -599,7 +599,7 @@ pub async fn list_recent_completions( .bind(format!("-{} days", days - 1)) .fetch_all(pool) .await - .map_err(|e| KonError::StorageError(format!("List recent completions failed: {e}")))?; + .map_err(|e| MagnotiaError::StorageError(format!("List recent completions failed: {e}")))?; let lookup: std::collections::HashMap = rows .into_iter() @@ -610,7 +610,7 @@ pub async fn list_recent_completions( let today_row: (String,) = sqlx::query_as("SELECT DATE('now', 'localtime')") .fetch_one(pool) .await - .map_err(|e| KonError::StorageError(format!("Get local today failed: {e}")))?; + .map_err(|e| MagnotiaError::StorageError(format!("Get local today failed: {e}")))?; let today = today_row.0; let mut series = Vec::with_capacity(days as usize); @@ -622,7 +622,7 @@ pub async fn list_recent_completions( .bind(format!("-{offset} days")) .fetch_one(pool) .await - .map_err(|e| KonError::StorageError(format!("Compute spine day failed: {e}")))?; + .map_err(|e| MagnotiaError::StorageError(format!("Compute spine day failed: {e}")))?; let count = lookup.get(&day).copied().unwrap_or(0); series.push(DailyCompletionCount { day, count }); } @@ -655,10 +655,10 @@ pub async fn insert_implementation_rule( .bind(last_fired_key) .execute(pool) .await - .map_err(|e| KonError::StorageError(format!("Insert implementation rule failed: {e}")))?; + .map_err(|e| MagnotiaError::StorageError(format!("Insert implementation rule failed: {e}")))?; get_implementation_rule(pool, id).await?.ok_or_else(|| { - KonError::StorageError(format!( + MagnotiaError::StorageError(format!( "insert_implementation_rule: rule {id} not found after insert" )) }) @@ -672,7 +672,7 @@ pub async fn list_implementation_rules(pool: &SqlitePool) -> Result Result<( .bind(id) .execute(pool) .await - .map_err(|e| KonError::StorageError(format!("Delete implementation rule failed: {e}")))?; + .map_err(|e| MagnotiaError::StorageError(format!("Delete implementation rule failed: {e}")))?; Ok(()) } @@ -757,7 +757,7 @@ pub async fn set_setting(pool: &SqlitePool, key: &str, value: &str) -> Result<() .bind(value) .execute(pool) .await - .map_err(|e| KonError::StorageError(format!("Set setting failed: {e}")))?; + .map_err(|e| MagnotiaError::StorageError(format!("Set setting failed: {e}")))?; Ok(()) } @@ -766,7 +766,7 @@ pub async fn get_setting(pool: &SqlitePool, key: &str) -> Result> .bind(key) .fetch_optional(pool) .await - .map_err(|e| KonError::StorageError(format!("Get setting failed: {e}")))?; + .map_err(|e| MagnotiaError::StorageError(format!("Get setting failed: {e}")))?; Ok(row.map(|r| r.get("value"))) } @@ -809,7 +809,7 @@ pub struct TranscriptRow { pub anti_hallucination: bool, pub created_at: String, // Task 2.5 — transcripts_meta (migration v5). Persists the UI metadata - // that previously lived in the removed localStorage `kon_history` cache. + // that previously lived in the removed localStorage `magnotia_history` cache. pub starred: bool, pub manual_tags: String, pub template: String, @@ -944,7 +944,7 @@ fn implementation_rule_row_from(r: sqlx::sqlite::SqliteRow) -> ImplementationRul // 1. The DB triggers `trg_protect_default_profile_delete` / // `trg_protect_default_profile_rename` from migration v6. // 2. Rust-layer fail-fast checks below that short-circuit BEFORE the -// query hits sqlite, so UI callers get a friendly KonError::StorageError +// query hits sqlite, so UI callers get a friendly MagnotiaError::StorageError // instead of an opaque `SQLITE_CONSTRAINT_TRIGGER` wrapped in text. pub async fn list_profiles(pool: &SqlitePool) -> Result> { @@ -952,7 +952,7 @@ pub async fn list_profiles(pool: &SqlitePool) -> Result> { sqlx::query("SELECT id, name, initial_prompt, created_at FROM profiles ORDER BY name ASC") .fetch_all(pool) .await - .map_err(|e| KonError::StorageError(format!("List profiles failed: {e}")))?; + .map_err(|e| MagnotiaError::StorageError(format!("List profiles failed: {e}")))?; Ok(rows.iter().map(profile_row_from).collect()) } @@ -961,7 +961,7 @@ pub async fn get_profile(pool: &SqlitePool, id: &str) -> Result Result<()> { if id == crate::DEFAULT_PROFILE_ID && name != "Default" { - return Err(KonError::StorageError( + return Err(MagnotiaError::StorageError( "Default profile cannot be renamed".into(), )); } if id != crate::DEFAULT_PROFILE_ID && name == "Default" { - return Err(KonError::StorageError( + return Err(MagnotiaError::StorageError( "Cannot rename another profile to 'Default'".into(), )); } @@ -1014,7 +1014,7 @@ pub async fn update_profile( .bind(id) .execute(pool) .await - .map_err(|e| KonError::StorageError(format!("Update profile failed: {e}")))?; + .map_err(|e| MagnotiaError::StorageError(format!("Update profile failed: {e}")))?; Ok(()) } @@ -1023,13 +1023,13 @@ pub async fn update_profile( /// ON DELETE CASCADE on `profile_terms.profile_id` cleans up children. pub async fn delete_profile(pool: &SqlitePool, id: &str) -> Result<()> { if id == crate::DEFAULT_PROFILE_ID { - return Err(KonError::StorageError( + return Err(MagnotiaError::StorageError( "Default profile cannot be deleted".into(), )); } let transcript_count = transcript_count_for_profile(pool, id).await?; if transcript_count > 0 { - return Err(KonError::StorageError(format!( + return Err(MagnotiaError::StorageError(format!( "Cannot delete profile while {transcript_count} transcript(s) still reference it; reassign transcripts first" ))); } @@ -1037,7 +1037,7 @@ pub async fn delete_profile(pool: &SqlitePool, id: &str) -> Result<()> { .bind(id) .execute(pool) .await - .map_err(|e| KonError::StorageError(format!("Delete profile failed: {e}")))?; + .map_err(|e| MagnotiaError::StorageError(format!("Delete profile failed: {e}")))?; Ok(()) } @@ -1052,7 +1052,7 @@ pub async fn list_profile_terms( .bind(profile_id) .fetch_all(pool) .await - .map_err(|e| KonError::StorageError(format!("List profile terms failed: {e}")))?; + .map_err(|e| MagnotiaError::StorageError(format!("List profile terms failed: {e}")))?; Ok(rows.iter().map(profile_term_row_from).collect()) } @@ -1078,7 +1078,7 @@ pub async fn add_profile_term( .bind(note) .fetch_one(pool) .await - .map_err(|e| KonError::StorageError(format!("Add profile term failed: {e}")))?; + .map_err(|e| MagnotiaError::StorageError(format!("Add profile term failed: {e}")))?; Ok(profile_term_row_from(&row)) } @@ -1087,7 +1087,7 @@ pub async fn delete_profile_term(pool: &SqlitePool, id: &str) -> Result<()> { .bind(id) .execute(pool) .await - .map_err(|e| KonError::StorageError(format!("Delete profile term failed: {e}")))?; + .map_err(|e| MagnotiaError::StorageError(format!("Delete profile term failed: {e}")))?; Ok(()) } @@ -1096,7 +1096,7 @@ async fn profile_exists(pool: &SqlitePool, id: &str) -> Result { .bind(id) .fetch_optional(pool) .await - .map_err(|e| KonError::StorageError(format!("Profile existence check failed: {e}")))?; + .map_err(|e| MagnotiaError::StorageError(format!("Profile existence check failed: {e}")))?; Ok(exists.is_some()) } @@ -1105,7 +1105,7 @@ async fn transcript_count_for_profile(pool: &SqlitePool, id: &str) -> Result Result { .bind(format!("-{keep_days} days")) .execute(pool) .await - .map_err(|e| KonError::StorageError(format!("Prune error_log failed: {e}")))?; + .map_err(|e| MagnotiaError::StorageError(format!("Prune error_log failed: {e}")))?; Ok(result.rows_affected()) } @@ -1183,7 +1183,7 @@ pub async fn list_recent_errors(pool: &SqlitePool, limit: i64) -> Result Result Result { if !matches!(params.rating, -1..=1) { - return Err(KonError::StorageError(format!( + return Err(MagnotiaError::StorageError(format!( "invalid feedback rating {} (must be -1, 0, or 1)", params.rating ))); @@ -1288,7 +1288,7 @@ pub async fn record_feedback(pool: &SqlitePool, params: RecordFeedbackParams) -> .bind(profile_id) .fetch_one(pool) .await - .map_err(|e| KonError::StorageError(format!("record_feedback failed: {e}")))?; + .map_err(|e| MagnotiaError::StorageError(format!("record_feedback failed: {e}")))?; Ok(row.get::("id")) } @@ -1325,7 +1325,7 @@ pub async fn list_feedback_examples( .bind(limit) .fetch_all(pool) .await - .map_err(|e| KonError::StorageError(format!("list_feedback_examples failed: {e}")))?; + .map_err(|e| MagnotiaError::StorageError(format!("list_feedback_examples failed: {e}")))?; Ok(rows .into_iter() @@ -2430,7 +2430,7 @@ mod tests { #[tokio::test] async fn init_readonly_rejects_writes_and_serves_reads() { - let dir = std::env::temp_dir().join(format!("kon-storage-ro-{}", std::process::id())); + let dir = std::env::temp_dir().join(format!("magnotia-storage-ro-{}", std::process::id())); std::fs::create_dir_all(&dir).unwrap(); let path = dir.join("ro.db"); let _ = std::fs::remove_file(&path); @@ -2482,7 +2482,7 @@ mod tests { #[tokio::test] async fn init_readonly_fails_when_db_missing() { let path = std::env::temp_dir().join(format!( - "kon-storage-ro-missing-{}.db", + "magnotia-storage-ro-missing-{}.db", std::process::id() )); let _ = std::fs::remove_file(&path); diff --git a/crates/storage/src/file_storage.rs b/crates/storage/src/file_storage.rs index 43f5160..3238ac9 100644 --- a/crates/storage/src/file_storage.rs +++ b/crates/storage/src/file_storage.rs @@ -1,28 +1,28 @@ use std::path::PathBuf; pub fn app_data_dir() -> PathBuf { - kon_core::paths::app_paths().app_data_dir() + magnotia_core::paths::app_paths().app_data_dir() } /// Path to the SQLite database file. pub fn database_path() -> PathBuf { - kon_core::paths::app_paths().database_path() + magnotia_core::paths::app_paths().database_path() } /// Directory for saved audio recordings. pub fn recordings_dir() -> PathBuf { - kon_core::paths::app_paths().recordings_dir() + magnotia_core::paths::app_paths().recordings_dir() } /// Directory for crash dumps written by the Rust panic hook. /// Each crash is a single text file: `-.crash`. /// Used by the diagnostic-report bundler in Settings → About. pub fn crashes_dir() -> PathBuf { - kon_core::paths::app_paths().crashes_dir() + magnotia_core::paths::app_paths().crashes_dir() } -/// Directory for the rolling Rust log file (kon.log + rotated kon.log.1, etc). +/// Directory for the rolling Rust log file (magnotia.log + rotated magnotia.log.1, etc). /// Subscribers configured in src-tauri/src/lib.rs at startup. pub fn logs_dir() -> PathBuf { - kon_core::paths::app_paths().logs_dir() + magnotia_core::paths::app_paths().logs_dir() } diff --git a/crates/storage/src/migrations.rs b/crates/storage/src/migrations.rs index 20ff74f..5556080 100644 --- a/crates/storage/src/migrations.rs +++ b/crates/storage/src/migrations.rs @@ -1,4 +1,4 @@ -use kon_core::error::{KonError, Result}; +use magnotia_core::error::{MagnotiaError, Result}; use sqlx::SqlitePool; /// Each migration is a (version, description, sql) tuple. @@ -553,19 +553,19 @@ async fn run_migrations_slice(pool: &SqlitePool, migrations: &[(i64, &str, &str) ) .execute(pool) .await - .map_err(|e| KonError::StorageError(format!("Schema version table creation failed: {e}")))?; + .map_err(|e| MagnotiaError::StorageError(format!("Schema version table creation failed: {e}")))?; let current: i64 = sqlx::query_scalar("SELECT COALESCE(MAX(version), 0) FROM schema_version") .fetch_one(pool) .await - .map_err(|e| KonError::StorageError(format!("Schema version query failed: {e}")))?; + .map_err(|e| MagnotiaError::StorageError(format!("Schema version query failed: {e}")))?; for (version, description, sql) in migrations { if *version > current { log::info!("Running migration {}: {}", version, description); let mut tx = pool.begin().await.map_err(|e| { - KonError::StorageError(format!("Migration {} tx begin failed: {e}", version)) + MagnotiaError::StorageError(format!("Migration {} tx begin failed: {e}", version)) })?; for statement in split_statements(sql) { @@ -573,7 +573,7 @@ async fn run_migrations_slice(pool: &SqlitePool, migrations: &[(i64, &str, &str) .execute(&mut *tx) .await .map_err(|e| { - KonError::StorageError(format!("Migration {} failed: {e}", version)) + MagnotiaError::StorageError(format!("Migration {} failed: {e}", version)) })?; } @@ -583,11 +583,11 @@ async fn run_migrations_slice(pool: &SqlitePool, migrations: &[(i64, &str, &str) .execute(&mut *tx) .await .map_err(|e| { - KonError::StorageError(format!("Migration version record failed: {e}")) + MagnotiaError::StorageError(format!("Migration version record failed: {e}")) })?; tx.commit().await.map_err(|e| { - KonError::StorageError(format!("Migration {} commit failed: {e}", version)) + MagnotiaError::StorageError(format!("Migration {} commit failed: {e}", version)) })?; log::info!("Migration {} complete", version); @@ -947,7 +947,7 @@ mod tests { // dictionary.id is INTEGER PK AUTOINCREMENT (see v2); let SQLite assign rowids. sqlx::query( "INSERT INTO dictionary (term, note, created_at) VALUES \ - ('Kon', '', datetime('now')), \ + ('Magnotia', '', datetime('now')), \ ('CORBEL', 'brand', datetime('now')), \ ('Wren', '', datetime('now'))", ) diff --git a/crates/transcription/Cargo.toml b/crates/transcription/Cargo.toml index 1b9e240..4bd1dce 100644 --- a/crates/transcription/Cargo.toml +++ b/crates/transcription/Cargo.toml @@ -1,8 +1,8 @@ [package] -name = "kon-transcription" +name = "magnotia-transcription" version = "0.1.0" edition = "2021" -description = "Speech-to-text engine wrappers, model management, and inference concurrency for Kon" +description = "Speech-to-text engine wrappers, model management, and inference concurrency for Magnotia" build = "build.rs" [features] @@ -15,13 +15,13 @@ build = "build.rs" # `whisper-vulkan` is a separate feature so a non-Vulkan target (Android # without GPU drivers, a CPU-only Windows build) can pull in whisper-rs # but skip the Vulkan backend. Build CPU-only with: -# cargo build -p kon-transcription --no-default-features --features whisper +# cargo build -p magnotia-transcription --no-default-features --features whisper default = ["whisper", "whisper-vulkan"] whisper = ["dep:whisper-rs", "dep:num_cpus"] whisper-vulkan = ["whisper-rs?/vulkan"] [dependencies] -kon-core = { path = "../core" } +magnotia-core = { path = "../core" } # Parakeet via ONNX. Whisper is handled directly via whisper-rs below. transcribe-rs = { version = "0.3", default-features = false, features = ["onnx"] } @@ -53,6 +53,6 @@ thiserror = "2" tracing = "0.1" [dev-dependencies] -# TcpListener fixture for the download resume tests (mirrors kon-llm). +# TcpListener fixture for the download resume tests (mirrors magnotia-llm). tokio = { version = "1", features = ["rt", "sync", "net", "io-util", "macros"] } tempfile = "3" diff --git a/crates/transcription/build.rs b/crates/transcription/build.rs index 5d0d9dd..e821f25 100644 --- a/crates/transcription/build.rs +++ b/crates/transcription/build.rs @@ -11,7 +11,7 @@ //! workspace ever pulls `tokenizers` into the dependency graph on a //! Windows target. If we ever legitimately need it we can reintroduce //! it via a sidecar (isolated process, separate CRT) rather than -//! linking it into `kon_lib`. +//! linking it into `magnotia_lib`. //! //! The check is advisory on non-Windows targets — it still prints a //! cargo:warning if `tokenizers` appears, so the Windows failure isn't @@ -56,7 +56,7 @@ fn main() { if target_os == "windows" { panic!( - "kon-transcription: the `tokenizers` crate appears in Cargo.lock and this is a \ + "magnotia-transcription: the `tokenizers` crate appears in Cargo.lock and this is a \ Windows build. Linking `whisper-rs-sys` + `tokenizers` in the same binary has \ been a persistent MSVC C-runtime conflict (see Whispering v7.11.0). Route any \ tokenizer usage through an out-of-process sidecar instead, or gate it off for \ @@ -65,7 +65,7 @@ fn main() { } println!( - "cargo:warning=kon-transcription: `tokenizers` crate is in the dependency graph. \ + "cargo:warning=magnotia-transcription: `tokenizers` crate is in the dependency graph. \ This build is non-Windows so the link will succeed, but Windows builds will panic \ at build time per docs/whisper-ecosystem/brief.md item #6. Isolate tokenizer usage \ in a sidecar before a Windows ship." diff --git a/crates/transcription/src/concurrency.rs b/crates/transcription/src/concurrency.rs index c74510b..0c4c1d2 100644 --- a/crates/transcription/src/concurrency.rs +++ b/crates/transcription/src/concurrency.rs @@ -1,7 +1,7 @@ use std::sync::Arc; -use kon_core::error::{KonError, Result}; -use kon_core::types::{AudioSamples, TranscriptionOptions}; +use magnotia_core::error::{MagnotiaError, Result}; +use magnotia_core::types::{AudioSamples, TranscriptionOptions}; use crate::local_engine::{LocalEngine, TimedTranscript}; @@ -14,5 +14,5 @@ pub async fn run_inference( ) -> Result { tokio::task::spawn_blocking(move || engine.transcribe_sync(&audio, &options)) .await - .map_err(|e| KonError::TranscriptionFailed(format!("Task join error: {e}")))? + .map_err(|e| MagnotiaError::TranscriptionFailed(format!("Task join error: {e}")))? } diff --git a/crates/transcription/src/local_engine.rs b/crates/transcription/src/local_engine.rs index fc17877..561ef84 100644 --- a/crates/transcription/src/local_engine.rs +++ b/crates/transcription/src/local_engine.rs @@ -4,8 +4,8 @@ use std::time::Instant; use transcribe_rs::{SpeechModel, TranscribeOptions, TranscriptionResult}; -use kon_core::error::{KonError, Result}; -use kon_core::types::{ +use magnotia_core::error::{MagnotiaError, Result}; +use magnotia_core::types::{ AudioSamples, EngineName, ModelId, Segment, Transcript, TranscriptionOptions, }; @@ -28,7 +28,7 @@ pub struct SpeechModelAdapter(pub Box); impl Transcriber for SpeechModelAdapter { fn capabilities(&self) -> TranscriberCapabilities { TranscriberCapabilities { - sample_rate: kon_core::constants::WHISPER_SAMPLE_RATE, + sample_rate: magnotia_core::constants::WHISPER_SAMPLE_RATE, channels: 1, supports_initial_prompt: false, } @@ -48,7 +48,7 @@ impl Transcriber for SpeechModelAdapter { let result: TranscriptionResult = self .0 .transcribe(samples, &opts) - .map_err(|e| KonError::TranscriptionFailed(e.to_string()))?; + .map_err(|e| MagnotiaError::TranscriptionFailed(e.to_string()))?; Ok(result .segments .unwrap_or_default() @@ -140,7 +140,7 @@ impl LocalEngine { options: &TranscriptionOptions, ) -> Result { let mut guard = self.engine.lock().unwrap_or_else(|e| e.into_inner()); - let backend = guard.as_mut().ok_or(KonError::EngineNotLoaded)?; + let backend = guard.as_mut().ok_or(MagnotiaError::EngineNotLoaded)?; let start = Instant::now(); let segments = backend.transcribe_sync(audio.samples(), options)?; @@ -160,7 +160,7 @@ impl LocalEngine { /// Thin wrapper over `ParakeetModel` that overrides `transcribe_raw` to /// request word-granularity segments. `transcribe-rs` 0.3's trait impl for /// `ParakeetModel::transcribe_raw` ignores `TranscribeOptions` and uses -/// `TimestampGranularity::Token` (per-subword) — which surfaces in Kon as +/// `TimestampGranularity::Token` (per-subword) — which surfaces in Magnotia as /// "T Est Ing . One , Two , Three" output. The concrete-type method /// `ParakeetModel::transcribe_with` accepts `ParakeetParams` with an /// explicit granularity; this wrapper exposes that to the trait object. @@ -197,7 +197,7 @@ impl transcribe_rs::SpeechModel for ParakeetWordGranularity { pub fn load_parakeet(model_dir: &Path) -> Result> { use transcribe_rs::onnx::Quantization; let model = transcribe_rs::onnx::parakeet::ParakeetModel::load(model_dir, &Quantization::Int8) - .map_err(|e| KonError::TranscriptionFailed(format!("Failed to load Parakeet: {e}")))?; + .map_err(|e| MagnotiaError::TranscriptionFailed(format!("Failed to load Parakeet: {e}")))?; Ok(Box::new(SpeechModelAdapter(Box::new( ParakeetWordGranularity(model), )))) @@ -207,7 +207,7 @@ pub fn load_parakeet(model_dir: &Path) -> Result> { #[cfg(feature = "whisper")] pub fn load_whisper(model_path: &Path) -> Result> { let backend = WhisperRsBackend::load(model_path) - .map_err(|e| KonError::TranscriptionFailed(format!("Failed to load Whisper: {e}")))?; + .map_err(|e| MagnotiaError::TranscriptionFailed(format!("Failed to load Whisper: {e}")))?; Ok(Box::new(backend)) } diff --git a/crates/transcription/src/model_manager.rs b/crates/transcription/src/model_manager.rs index 5f91a3b..fb3416e 100644 --- a/crates/transcription/src/model_manager.rs +++ b/crates/transcription/src/model_manager.rs @@ -2,9 +2,9 @@ use std::collections::HashSet; use std::path::{Path, PathBuf}; use std::sync::{LazyLock, Mutex}; -use kon_core::error::{KonError, Result}; -use kon_core::model_registry::{find_model, ModelFile}; -use kon_core::types::{DownloadProgress, ModelId}; +use magnotia_core::error::{MagnotiaError, Result}; +use magnotia_core::model_registry::{find_model, ModelFile}; +use magnotia_core::types::{DownloadProgress, ModelId}; static ACTIVE_DOWNLOADS: LazyLock>> = LazyLock::new(|| Mutex::new(HashSet::new())); @@ -18,9 +18,9 @@ impl DownloadReservation { let id = id.as_str().to_string(); let mut active = ACTIVE_DOWNLOADS .lock() - .map_err(|_| KonError::DownloadFailed("download lock poisoned".into()))?; + .map_err(|_| MagnotiaError::DownloadFailed("download lock poisoned".into()))?; if !active.insert(id.clone()) { - return Err(KonError::DownloadFailed(format!( + return Err(MagnotiaError::DownloadFailed(format!( "download already in progress for {id}" ))); } @@ -37,15 +37,15 @@ impl Drop for DownloadReservation { } /// Resolve the models storage directory. -/// Windows: %LOCALAPPDATA%/kon/models -/// Unix: ~/.kon/models +/// Windows: %LOCALAPPDATA%/magnotia/models +/// Unix: ~/.magnotia/models pub fn models_dir() -> PathBuf { - kon_core::paths::app_paths().models_dir() + magnotia_core::paths::app_paths().models_dir() } /// Get the directory path where a specific model's files are stored. pub fn model_dir(id: &ModelId) -> PathBuf { - kon_core::paths::app_paths().speech_model_dir(id) + magnotia_core::paths::app_paths().speech_model_dir(id) } /// Check whether all files for a model have been downloaded. @@ -61,7 +61,7 @@ pub fn is_downloaded(id: &ModelId) -> bool { /// List all downloaded model IDs. pub fn list_downloaded() -> Vec { - kon_core::model_registry::all_models() + magnotia_core::model_registry::all_models() .iter() .filter(|m| is_downloaded(&m.id)) .map(|m| m.id.clone()) @@ -74,13 +74,13 @@ pub fn list_downloaded() -> Vec { /// For files that declare a `sha256` checksum we validate an existing /// complete file before skipping the download — a truncated or /// tampered file gets redownloaded automatically (pattern ported from -/// `kon-llm`'s model_manager, item #8 in the Whisper ecosystem brief). +/// `magnotia-llm`'s model_manager, item #8 in the Whisper ecosystem brief). pub async fn download( id: &ModelId, progress: impl Fn(DownloadProgress) + Send + 'static, ) -> Result<()> { let _reservation = DownloadReservation::acquire(id)?; - let entry = find_model(id).ok_or_else(|| KonError::ModelNotFound(id.clone()))?; + let entry = find_model(id).ok_or_else(|| MagnotiaError::ModelNotFound(id.clone()))?; let dir = model_dir(id); std::fs::create_dir_all(&dir)?; @@ -98,7 +98,7 @@ pub async fn download( let _ = std::fs::remove_file(&dest); } Err(e) => { - return Err(KonError::DownloadFailed(format!( + return Err(MagnotiaError::DownloadFailed(format!( "failed to verify existing {}: {e}", file.filename ))); @@ -113,10 +113,10 @@ pub async fn download( } fn verified_manifest_path(dir: &Path) -> PathBuf { - dir.join(".kon-verified") + dir.join(".magnotia-verified") } -fn verified_manifest_matches(entry: &kon_core::model_registry::ModelEntry, dir: &Path) -> bool { +fn verified_manifest_matches(entry: &magnotia_core::model_registry::ModelEntry, dir: &Path) -> bool { let manifest = match std::fs::read_to_string(verified_manifest_path(dir)) { Ok(contents) => contents, Err(_) => return false, @@ -137,7 +137,7 @@ fn verified_manifest_matches(entry: &kon_core::model_registry::ModelEntry, dir: } fn write_verified_manifest( - entry: &kon_core::model_registry::ModelEntry, + entry: &magnotia_core::model_registry::ModelEntry, dir: &Path, ) -> std::io::Result<()> { let mut lines = Vec::with_capacity(entry.files.len() + 1); @@ -193,7 +193,7 @@ async fn download_file( let client = reqwest::Client::builder() .connect_timeout(std::time::Duration::from_secs(30)) .build() - .map_err(|e| KonError::DownloadFailed(e.to_string()))?; + .map_err(|e| MagnotiaError::DownloadFailed(e.to_string()))?; // Check for existing partial download (resume support) let existing_bytes = if part_path.exists() { @@ -212,12 +212,12 @@ async fn download_file( let response = request .send() .await - .map_err(|e| KonError::DownloadFailed(e.to_string()))?; + .map_err(|e| MagnotiaError::DownloadFailed(e.to_string()))?; // If we requested Range but the server returned 200 (full file), the // server does not support resume. Rather than blindly appending a // full file on top of our partial bytes (which would produce a - // corrupt result), restart cleanly. This mirrors the kon-llm + // corrupt result), restart cleanly. This mirrors the magnotia-llm // ResumeUnsupported branch — item #8 of the brief. // // For the non-resume path, we still have to validate the status: @@ -234,14 +234,14 @@ async fn download_file( false } other => { - return Err(KonError::DownloadFailed(format!( + return Err(MagnotiaError::DownloadFailed(format!( "resume request returned unexpected status {other}" ))); } } } else { if !response.status().is_success() { - return Err(KonError::DownloadFailed(format!( + return Err(MagnotiaError::DownloadFailed(format!( "download returned HTTP {} for {}", response.status(), file.filename @@ -288,7 +288,7 @@ async fn download_file( } while let Some(chunk) = stream.next().await { - let chunk = chunk.map_err(|e| KonError::DownloadFailed(e.to_string()))?; + let chunk = chunk.map_err(|e| MagnotiaError::DownloadFailed(e.to_string()))?; std::io::Write::write_all(&mut out, &chunk)?; hasher.update(&chunk); downloaded += chunk.len() as u64; @@ -316,7 +316,7 @@ async fn download_file( let actual = format!("{:x}", hasher.finalize()); if actual != file.sha256 { let _ = std::fs::remove_file(&part_path); - return Err(KonError::DownloadFailed(format!( + return Err(MagnotiaError::DownloadFailed(format!( "SHA256 mismatch for {}: expected {}, got {}", file.filename, file.sha256, actual ))); @@ -354,7 +354,7 @@ mod tests { let list = list_downloaded(); // In test environment, no models are downloaded // This just verifies the function doesn't panic - assert!(list.len() <= kon_core::model_registry::all_models().len()); + assert!(list.len() <= magnotia_core::model_registry::all_models().len()); } #[test] @@ -478,7 +478,7 @@ mod tests { let file = ModelFile { filename: leak(dest.file_name().unwrap().to_string_lossy().into_owned()), url: leak(format!("http://{addr}/fixture.bin")), - size: kon_core::types::Megabytes(0), + size: magnotia_core::types::Megabytes(0), sha256: leak(expected_sha.clone()), }; let id = ModelId::new("test-fixture"); @@ -513,7 +513,7 @@ mod tests { let file = ModelFile { filename: leak(dest.file_name().unwrap().to_string_lossy().into_owned()), url: leak(format!("http://{addr}/fixture.bin")), - size: kon_core::types::Megabytes(0), + size: magnotia_core::types::Megabytes(0), sha256: leak(expected_sha), }; let id = ModelId::new("test-fixture"); @@ -568,7 +568,7 @@ mod tests { let file = ModelFile { filename: leak(dest.file_name().unwrap().to_string_lossy().into_owned()), url: leak(format!("http://{addr}/fixture.bin")), - size: kon_core::types::Megabytes(0), + size: magnotia_core::types::Megabytes(0), sha256: leak("0".repeat(64)), }; let id = ModelId::new("test-fixture"); @@ -596,7 +596,7 @@ mod tests { let file = ModelFile { filename: leak(dest.file_name().unwrap().to_string_lossy().into_owned()), url: leak(format!("http://{addr}/fixture.bin")), - size: kon_core::types::Megabytes(0), + size: magnotia_core::types::Megabytes(0), sha256: leak("deadbeef".repeat(8)), }; let id = ModelId::new("test-fixture"); diff --git a/crates/transcription/src/transcriber.rs b/crates/transcription/src/transcriber.rs index 3caa88f..4af1320 100644 --- a/crates/transcription/src/transcriber.rs +++ b/crates/transcription/src/transcriber.rs @@ -9,8 +9,8 @@ //! `whisper` feature — `WhisperRsBackend` (direct whisper-rs, the only //! path that pipes `initial_prompt`). -use kon_core::error::Result; -use kon_core::types::{Segment, TranscriptionOptions}; +use magnotia_core::error::Result; +use magnotia_core::types::{Segment, TranscriptionOptions}; /// Static capabilities a `Transcriber` advertises to callers. /// diff --git a/crates/transcription/src/whisper_rs_backend.rs b/crates/transcription/src/whisper_rs_backend.rs index 4acc38a..a037d10 100644 --- a/crates/transcription/src/whisper_rs_backend.rs +++ b/crates/transcription/src/whisper_rs_backend.rs @@ -10,8 +10,8 @@ use std::path::Path; use whisper_rs::{FullParams, SamplingStrategy, WhisperContext, WhisperContextParameters}; -use kon_core::error::{KonError, Result}; -use kon_core::types::{Segment, TranscriptionOptions}; +use magnotia_core::error::{MagnotiaError, Result}; +use magnotia_core::types::{Segment, TranscriptionOptions}; use crate::transcriber::{Transcriber, TranscriberCapabilities}; @@ -40,7 +40,7 @@ impl WhisperRsBackend { impl Transcriber for WhisperRsBackend { fn capabilities(&self) -> TranscriberCapabilities { TranscriberCapabilities { - sample_rate: kon_core::constants::WHISPER_SAMPLE_RATE, + sample_rate: magnotia_core::constants::WHISPER_SAMPLE_RATE, channels: 1, supports_initial_prompt: true, } @@ -63,7 +63,7 @@ impl Transcriber for WhisperRsBackend { ); let mut state = self.ctx.create_state().map_err(|e| { - KonError::TranscriptionFailed(WhisperBackendError::State(e.to_string()).to_string()) + MagnotiaError::TranscriptionFailed(WhisperBackendError::State(e.to_string()).to_string()) })?; let mut params = FullParams::new(SamplingStrategy::Greedy { best_of: 1 }); @@ -83,7 +83,7 @@ impl Transcriber for WhisperRsBackend { params.set_print_realtime(false); state.full(params, samples).map_err(|e| { - KonError::TranscriptionFailed( + MagnotiaError::TranscriptionFailed( WhisperBackendError::Transcribe(e.to_string()).to_string(), ) })?; @@ -98,7 +98,7 @@ impl Transcriber for WhisperRsBackend { let text = seg .to_str() .map_err(|e| { - KonError::TranscriptionFailed( + MagnotiaError::TranscriptionFailed( WhisperBackendError::Transcribe(e.to_string()).to_string(), ) })? diff --git a/crates/transcription/tests/whisper_rs_smoke.rs b/crates/transcription/tests/whisper_rs_smoke.rs index 366a781..e3ed8b6 100644 --- a/crates/transcription/tests/whisper_rs_smoke.rs +++ b/crates/transcription/tests/whisper_rs_smoke.rs @@ -1,17 +1,17 @@ //! Smoke test: whisper-rs 0.16 loads a GGUF model, transcribes silence, and //! accepts set_initial_prompt without panicking. //! -//! Runs only when `KON_WHISPER_TEST_MODEL` is set to the path of a +//! Runs only when `MAGNOTIA_WHISPER_TEST_MODEL` is set to the path of a //! ggml/gguf whisper model on disk. Otherwise the test exits quiet. use std::env; #[test] fn whisper_rs_smoke_loads_and_transcribes() { - let model_path = match env::var("KON_WHISPER_TEST_MODEL") { + let model_path = match env::var("MAGNOTIA_WHISPER_TEST_MODEL") { Ok(p) => p, Err(_) => { - eprintln!("KON_WHISPER_TEST_MODEL not set — skipping"); + eprintln!("MAGNOTIA_WHISPER_TEST_MODEL not set — skipping"); return; } }; diff --git a/docs/brand/kon-brand-guidelines.md b/docs/brand/magnotia-brand-guidelines.md similarity index 91% rename from docs/brand/kon-brand-guidelines.md rename to docs/brand/magnotia-brand-guidelines.md index 2b25a47..7168986 100644 --- a/docs/brand/kon-brand-guidelines.md +++ b/docs/brand/magnotia-brand-guidelines.md @@ -1,4 +1,4 @@ -# Kon — Brand Guidelines +# Magnotia — Brand Guidelines **Version:** 1.1 **Date:** 2026/03/21 @@ -8,7 +8,7 @@ ## 1. Brand Foundation -**Purpose:** Kon exists because the tools meant to organise your thoughts demand more mental energy than the thoughts themselves. +**Purpose:** Magnotia exists because the tools meant to organise your thoughts demand more mental energy than the thoughts themselves. **Essence:** Clarity without friction. @@ -45,7 +45,7 @@ ### Primary: Wordmark -**"Kon"** set in Instrument Serif Italic, 400 weight, amber (#e8a87c on dark / #b87a4a on light). +**"Magnotia"** set in Instrument Serif Italic, 400 weight, amber (#e8a87c on dark / #b87a4a on light). **Usage:** - The wordmark is the primary brand identifier across all contexts @@ -79,7 +79,7 @@ A minimal abstracted waveform — three vertical bars of asymmetric heights in a **Sizing:** Must remain legible at 16×16px (favicon) and scale cleanly to 512×512px (app store) -**Note:** The CORBEL fox mark is not a Kon asset. Never use the fox on Kon materials. +**Note:** The CORBEL fox mark is not a Magnotia asset. Never use the fox on Magnotia materials. --- @@ -219,7 +219,7 @@ Zone transitions: 300–500ms cross-fade, disabled when `prefers-reduced-motion: ### Why Lexend -Lexend was designed by Bonnie Shaver-Troup specifically to improve reading proficiency for people with reading difficulties. It is a variable font with adjustable width axis, enabling users to dynamically adapt letter spacing to their own fluctuating visual-perceptual thresholds — a direct requirement from the Kon design principles. High x-height, generous spacing, optimised letterforms. +Lexend was designed by Bonnie Shaver-Troup specifically to improve reading proficiency for people with reading difficulties. It is a variable font with adjustable width axis, enabling users to dynamically adapt letter spacing to their own fluctuating visual-perceptual thresholds — a direct requirement from the Magnotia design principles. High x-height, generous spacing, optimised letterforms. User-selectable alternatives in settings: Atkinson Hyperlegible Next, OpenDyslexic. @@ -326,7 +326,7 @@ Off by default. User-controlled toggle in settings. ### Illustration Approach -Kon does not use traditional illustration. Visual communication beyond photography uses: +Magnotia does not use traditional illustration. Visual communication beyond photography uses: - Abstract waveform/sound ripple motifs in amber - Geometric line work — 2px stroke, amber on dark surfaces - Data visualisation-style graphics for explaining features @@ -341,7 +341,7 @@ Empty states are high-emotion moments for neurodivergent users — blank screens |---|---| | First launch | Faint ambient waveform in `--accent-subtle`. Single action: press the record button | | Empty transcript | Waveform motif + "Press record or Ctrl+Shift+R" | -| Empty task list | "Tasks will appear here when Kon finds them in your transcripts" | +| Empty task list | "Tasks will appear here when Magnotia finds them in your transcripts" | | Empty history | "Your transcriptions will be saved here" | | Failed transcription | "Something went wrong with that transcription. Your audio is saved — try again when you're ready." Clear recovery path, never blame the user. This is the highest-emotion failure state in the app | @@ -430,7 +430,7 @@ Empty states are high-emotion moments for neurodivergent users — blank screens r/ADHD, r/productivity, r/neurodiversity, r/selfhosted, r/IndieDev, r/SomebodyMakeThis -**Reddit rule:** "If a post would work without mentioning Kon at all, it's a good post." +**Reddit rule:** "If a post would work without mentioning Magnotia at all, it's a good post." ### Social Templates (Canva Brand Kit) @@ -455,7 +455,7 @@ At pre-launch: Jake's voice, not a brand voice. Direct, honest, no filter. Authe "We sound like peace, not like static." -Kon speaks the way a thoughtful friend listens — calm, direct, never judgmental. The brand voice is astute, concise, and matter-of-fact. It never rambles, never condescends, never performs enthusiasm it doesn't feel. +Magnotia speaks the way a thoughtful friend listens — calm, direct, never judgmental. The brand voice is astute, concise, and matter-of-fact. It never rambles, never condescends, never performs enthusiasm it doesn't feel. ### Catchphrase @@ -469,12 +469,12 @@ Kon speaks the way a thoughtful friend listens — calm, direct, never judgmenta | Error messages | Calm, informative, solution-first. Never blame the user | | Marketing | Direct, occasionally provocative. Anti-subscription, pro-ownership | | Reddit/community | Jake's natural voice. Honest, self-deprecating, never promotional | -| Feature descriptions | Matter-of-fact, benefit-led, no jargon. "Kon does X so you can Y" | +| Feature descriptions | Matter-of-fact, benefit-led, no jargon. "Magnotia does X so you can Y" | | Empty states | Gentle, ambient, patient. "I'm here when you're ready" | ### Tone by Audience -The Brand Platform (`kon-brand-platform.md`, Section 17) contains a full Messaging Architecture with primary/supporting messages, anticipated objections, and persuasive responses for each audience. The voice flexes as follows: +The Brand Platform (`magnotia-brand-platform.md`, Section 17) contains a full Messaging Architecture with primary/supporting messages, anticipated objections, and persuasive responses for each audience. The voice flexes as follows: | Audience | Tone shift | Key emphasis | |---|---|---| @@ -485,19 +485,19 @@ The Brand Platform (`kon-brand-platform.md`, Section 17) contains a full Messagi ### Example Copy **Onboarding:** -> Press the button. Start talking. That's it. Kon handles the rest. +> Press the button. Start talking. That's it. Magnotia handles the rest. **Error message:** > Recording interrupted — looks like the microphone disconnected. Your transcript up to this point is saved. Plug back in and pick up where you left off. **Marketing (social):** -> Your brain had 47 ideas on the drive home. By the time you found a pen, you remembered 3. Kon catches all 47. Locally. No subscription. No cloud. Just you and your thoughts. +> Your brain had 47 ideas on the drive home. By the time you found a pen, you remembered 3. Magnotia catches all 47. Locally. No subscription. No cloud. Just you and your thoughts. **Empty state:** -> Tasks will appear here when Kon finds them in your transcripts. +> Tasks will appear here when Magnotia finds them in your transcripts. **Feature description:** -> Kon transcribes your voice on your device. Nothing leaves your machine. No internet required. +> Magnotia transcribes your voice on your device. Nothing leaves your machine. No internet required. ### Words to Use / Words to Avoid @@ -552,7 +552,7 @@ Impact 8, one shot. Use this structure for the primary launch post (r/ADHD or r/ |---|---|---| | **1. The problem** | 80–100 | Your lived experience. The paralysis, the stasis, the tools that made it worse. First person, specific, emotional. This is the hook — if this doesn't resonate, they stop reading | | **2. The journey** | 80–100 | How you got from frustration to building. The DND transcriber, seeing Whispr's price, realising local transcription was possible. Include a doubt or false start — "I nearly didn't..." | -| **3. What I built** | 100–150 | What Kon actually does, in plain language. Voice capture, local transcription, automatic task extraction. Lead with the mechanism, not the features. Screenshots here (2–3 max, warm dark UI) | +| **3. What I built** | 100–150 | What Magnotia actually does, in plain language. Voice capture, local transcription, automatic task extraction. Lead with the mechanism, not the features. Screenshots here (2–3 max, warm dark UI) | | **4. The principles** | 60–80 | Local-first, lifetime licence, no subscription, no data leaves your device. These are the lines that get upvoted. State them plainly | | **5. What's next** | 40–60 | Where you're headed, what feedback you want. End with a specific question — "What would make this useful for you?" drives comments | @@ -607,7 +607,7 @@ Impact 8, one shot. Use this structure for the primary launch post (r/ADHD or r/ When commissioning external design work, provide: 1. **This document** — the complete brand guidelines -2. **The Brand Platform** (`kon-brand-platform.md`) — strategic context +2. **The Brand Platform** (`magnotia-brand-platform.md`) — strategic context 3. **Specific deliverable** — what you need, in what format, by when 4. **"We Are / We Are Not" table** — from Section 1 5. **Anti-references** — Notion (too much going on), Tiimo (values betrayal), generic SaaS (white/blue/FAANG) diff --git a/docs/brand/kon-brand-platform.md b/docs/brand/magnotia-brand-platform.md similarity index 79% rename from docs/brand/kon-brand-platform.md rename to docs/brand/magnotia-brand-platform.md index 61ab8f5..446a913 100644 --- a/docs/brand/kon-brand-platform.md +++ b/docs/brand/magnotia-brand-platform.md @@ -1,4 +1,4 @@ -# Kon — Brand Platform +# Magnotia — Brand Platform **Version:** 1.0 **Date:** 2026/03/21 @@ -8,11 +8,11 @@ ## 1. Brand Purpose -Kon exists because the tools meant to organise your thoughts demand more mental energy than the thoughts themselves. It was built by someone who spent more time managing systems than getting ideas on paper — and who believes nobody should have to earn a PhD in file structures just to think clearly. +Magnotia exists because the tools meant to organise your thoughts demand more mental energy than the thoughts themselves. It was built by someone who spent more time managing systems than getting ideas on paper — and who believes nobody should have to earn a PhD in file structures just to think clearly. ## 2. Brand Vision -A world where capturing and organising your thoughts costs zero cognitive effort. Where the tools you rely on run on your device, respect your privacy, and never punish you for a missed day. Where neurodivergent people have access to the same frictionless workflows everyone else takes for granted — and where Kon is the first piece of a wider ecosystem that levels that playing field entirely. +A world where capturing and organising your thoughts costs zero cognitive effort. Where the tools you rely on run on your device, respect your privacy, and never punish you for a missed day. Where neurodivergent people have access to the same frictionless workflows everyone else takes for granted — and where Magnotia is the first piece of a wider ecosystem that levels that playing field entirely. ## 3. Brand Enemy @@ -23,19 +23,19 @@ Software that treats your thoughts as its product. The subscription-or-nothing m | Value | What it means in practice | |---|---| | **Ownership** | Your data stays on your device. Your licence doesn't expire. You own the tool, it doesn't own you. Most companies would disagree — their revenue model depends on the opposite. | -| **Honesty** | No dark patterns, no guilt messaging, no streak-shaming. If Kon can't do something, it says so. The brand voice is direct and transparent, even when that's commercially uncomfortable. | +| **Honesty** | No dark patterns, no guilt messaging, no streak-shaming. If Magnotia can't do something, it says so. The brand voice is direct and transparent, even when that's commercially uncomfortable. | | **Cognitive respect** | Every design decision is measured by whether it reduces mental load or adds to it. If a feature requires more than 90 seconds to understand, it doesn't ship. This isn't a nice-to-have — it's the core design constraint. | | **Accessibility as default** | Neurodivergent-first design, not neurodivergent-as-afterthought. The app is built for the people most tools forget, and those design choices make it better for everyone. | ## 5. Brand Tenets -1. **"How can I make this person feel seen and heard?"** — Ask before every customer interaction. Kon is a service animal, not a showpiece. +1. **"How can I make this person feel seen and heard?"** — Ask before every customer interaction. Magnotia is a service animal, not a showpiece. 2. **"Does this add or remove complexity from daily life?"** — Ask before every product decision. If it adds complexity, it doesn't ship. 3. **"Is this scientifically backed? Is it respectful? Is it honest?"** — Ask before every piece of content. No fabricated claims, no condescension, no spin. 4. **"Is the message clear and unambiguous?"** — Ask before every touchpoint. Literal labels always. If it could be misread, rewrite it. 5. **"Integrity, honour, respect."** — The governing principle for all relationships. Customers, partners, yourself. 6. **"Progressive disclosure."** — The creative constraint. Never show the full complexity. Reveal only the next step. This keeps the brand honest about what users actually need in the moment. -7. **"Build the ecosystem."** — The ambition tenet. Kon is the first piece, not the whole picture. Every decision should move toward a frictionless cognitive load reduction stack. +7. **"Build the ecosystem."** — The ambition tenet. Magnotia is the first piece, not the whole picture. Every decision should move toward a frictionless cognitive load reduction stack. ## 6. Target Audience @@ -47,9 +47,9 @@ Their Tuesday: wake up, scroll bad news, feel bad. Go to work, bright lights, he At 3am: everything. Nothing specific. Thoughts blipping in and out of existence, impossible to pin down. -**Emotional precondition:** Frustration. They don't open Kon feeling aspirational — they open it thinking "I need to get this OUT of my head." +**Emotional precondition:** Frustration. They don't open Magnotia feeling aspirational — they open it thinking "I need to get this OUT of my head." -**Identity reinforcement:** They want to be their authentic self and self-actualise. Kon helps them believe that's possible by removing the friction between thought and action. +**Identity reinforcement:** They want to be their authentic self and self-actualise. Magnotia helps them believe that's possible by removing the friction between thought and action. **Trust prerequisite:** They need to believe the founder built this to solve their own problem — not to monetise their attention. @@ -57,7 +57,7 @@ At 3am: everything. Nothing specific. Thoughts blipping in and out of existence, ## 7. Brand Promise -When you speak, Kon listens without judgement, organises without friction, and gives your thoughts back to you in a form you can act on — with nothing leaving your device and nothing expiring at the end of the month. +When you speak, Magnotia listens without judgement, organises without friction, and gives your thoughts back to you in a form you can act on — with nothing leaving your device and nothing expiring at the end of the month. ## 8. Onliness Statement @@ -67,7 +67,7 @@ We are the only **voice-first capture tool** that **runs entirely on your device **Archetype blend:** Sage (primary) + Magician (secondary) -Kon understands your thoughts (Sage) and transforms them into something actionable (Magician). It listens more than it speaks. It matches your energy. It's the straight person who's unknowingly comedic — genuine, not performed. +Magnotia understands your thoughts (Sage) and transforms them into something actionable (Magician). It listens more than it speaks. It matches your energy. It's the straight person who's unknowingly comedic — genuine, not performed. **Tone dimensions:** - Formal (1) ↔ Casual (10): **7** @@ -85,7 +85,7 @@ Kon understands your thoughts (Sage) and transforms them into something actionab | Listening | Judging | | Peace | Static | -**How Kon shows up:** Arrives in thrifted quality clothes — function over form, but with taste. At an event, asks questions, talks about life and experiences, never pitches. Naturally funny without trying. After a few drinks: giddy, keeps the bit going. The filter comes off but the person underneath is the same. +**How Magnotia shows up:** Arrives in thrifted quality clothes — function over form, but with taste. At an event, asks questions, talks about life and experiences, never pitches. Naturally funny without trying. After a few drinks: giddy, keeps the bit going. The filter comes off but the person underneath is the same. ## 10. Brand Voice @@ -96,13 +96,13 @@ Kon understands your thoughts (Sage) and transforms them into something actionab **Rhythm:** Short sentences. Matter-of-fact. Warm but not effusive. **Example — social media post:** -> Your brain had 47 ideas on the drive home. By the time you found a pen, you remembered 3. Kon catches all 47. Locally. No subscription. No cloud. Just you and your thoughts. +> Your brain had 47 ideas on the drive home. By the time you found a pen, you remembered 3. Magnotia catches all 47. Locally. No subscription. No cloud. Just you and your thoughts. **Example — error message:** > Recording interrupted — looks like the microphone disconnected. Your transcript up to this point is saved. Plug back in and pick up where you left off. **Example — onboarding:** -> Press the button. Start talking. That's it. Kon handles the rest. +> Press the button. Start talking. That's it. Magnotia handles the rest. ## 11. Brand Story @@ -112,13 +112,13 @@ Meanwhile, executive dysfunction made the simplest tasks feel impossible. Not la Then he saw Whispr Flow's monthly price tag and thought: I could build this myself. He remembered experimenting with local transcription for his DND game sessions. The technology existed. The only missing piece was software that respected both the user's brain and their data. -Kon was born from that collision — the frustration of systems that serve themselves, and the realisation that local AI had matured enough to serve the user instead. +Magnotia was born from that collision — the frustration of systems that serve themselves, and the realisation that local AI had matured enough to serve the user instead. ## 12. Competitive Position **Positioning axes:** Privacy (cloud → local) × Cognitive accessibility (neurotypical-default → neurodivergent-first) -Kon occupies the quadrant no competitor currently holds: local-first AND neurodivergent-first. +Magnotia occupies the quadrant no competitor currently holds: local-first AND neurodivergent-first. | Competitor | Privacy | Cognitive accessibility | Pricing | |---|---|---|---| @@ -126,7 +126,7 @@ Kon occupies the quadrant no competitor currently holds: local-first AND neurodi | Tiimo | Cloud-based | Neurodivergent-aware | Removed lifetime licence | | Google Recorder | Walled garden (Pixel only) | Neurotypical-default | Free (data cost) | | Otter.ai | Cloud-dependent | Neurotypical-default | Freemium/subscription | -| **Kon** | **Fully local** | **Neurodivergent-first** | **Lifetime licence** | +| **Magnotia** | **Fully local** | **Neurodivergent-first** | **Lifetime licence** | **Key differentiators:** Local processing, lifetime licence, voice-first capture, neurodivergent-first design, zero-friction onboarding (under 90 seconds). @@ -140,7 +140,7 @@ You are not the problem. The tools are wrong. They were built for people who already know how to organise. For brains that activate on command. For users who don't mind handing their thoughts to a server farm and paying monthly for the privilege. -Kon is different. +Magnotia is different. Press a button. Start talking. Your thoughts — all of them, the messy ones, the half-formed ones, the 3am ones that vanish by morning — captured instantly, organised automatically, stored on your device. No internet required. No subscription. No judgement. @@ -152,7 +152,7 @@ Talk now. Think later. The clarity will follow. **Clarity without friction.** -Everything Kon does — voice capture, local processing, automatic organisation, lifetime ownership — serves this single concept. If a decision reinforces frictionless clarity, it's right. If it doesn't, it's wrong. +Everything Magnotia does — voice capture, local processing, automatic organisation, lifetime ownership — serves this single concept. If a decision reinforces frictionless clarity, it's right. If it doesn't, it's wrong. ## 15. Benefits Ladder @@ -161,7 +161,7 @@ Everything Kon does — voice capture, local processing, automatic organisation, | **Functional** | Captures voice, transcribes locally, organises thoughts into actionable tasks — with no internet dependency and no subscription. | | **Emotional** | Relief. The feeling of the blockage being cleared. Permission to be messy, unfocused, and still make progress. | | **Social** | "I finally have a system that works for my brain" — signals self-awareness and agency, not dysfunction. Reframes neurodivergence from limitation to difference. | -| **Self-actualisation** | "I finally wrote that book." Kon clears the path between who you are and who you want to become. | +| **Self-actualisation** | "I finally wrote that book." Magnotia clears the path between who you are and who you want to become. | ## 16. Reasons to Believe @@ -177,7 +177,7 @@ Everything Kon does — voice capture, local processing, automatic organisation, ### Audience 1: Neurodivergent individuals (ADHD, autism, executive dysfunction) -**Primary message:** Kon captures your thoughts the moment they appear — no friction, no cloud, no subscription. Just speak and it's done. +**Primary message:** Magnotia captures your thoughts the moment they appear — no friction, no cloud, no subscription. Just speak and it's done. **Supporting messages:** - Designed for brains that work differently, not adapted as an afterthought @@ -190,9 +190,9 @@ Everything Kon does — voice capture, local processing, automatic organisation, - "It's just one developer — will this still be around in a year?" **Persuasive responses:** -- "Kon isn't a productivity system — it's a capture tool. There's nothing to set up, nothing to maintain, nothing to fail. Press a button and talk." -- "ChatGPT needs internet, sends your data to OpenAI, and costs a subscription. Kon runs locally, keeps your data on your device, and you own it outright." -- "The lifetime licence model means Kon doesn't need exponential growth to survive. It's built to be sustainable, not to scale at all costs." +- "Magnotia isn't a productivity system — it's a capture tool. There's nothing to set up, nothing to maintain, nothing to fail. Press a button and talk." +- "ChatGPT needs internet, sends your data to OpenAI, and costs a subscription. Magnotia runs locally, keeps your data on your device, and you own it outright." +- "The lifetime licence model means Magnotia doesn't need exponential growth to survive. It's built to be sustainable, not to scale at all costs." **Proof points:** Working prototype, founder's lived experience, Roo's validation, research-backed design. @@ -200,7 +200,7 @@ Everything Kon does — voice capture, local processing, automatic organisation, ### Audience 2: Writers, creatives, and power users -**Primary message:** Kon turns brain dumps into structured output — a new tool in your creative workflow that works offline and integrates with what you already use. +**Primary message:** Magnotia turns brain dumps into structured output — a new tool in your creative workflow that works offline and integrates with what you already use. **Supporting messages:** - Voice-first capture for when typing is the bottleneck @@ -212,7 +212,7 @@ Everything Kon does — voice capture, local processing, automatic organisation, - "Can it integrate with Obsidian/Notion/my existing tools?" **Persuasive responses:** -- "Kon doesn't replace your workflow — it adds a capture layer. Speak your thoughts, export to your tool of choice." +- "Magnotia doesn't replace your workflow — it adds a capture layer. Speak your thoughts, export to your tool of choice." - "Export formats cover all major tools. Direct integrations are on the roadmap." **Proof points:** Working export system, template functionality, DND transcription origin story. @@ -233,7 +233,7 @@ Everything Kon does — voice capture, local processing, automatic organisation, - "What about updates and model improvements?" **Persuasive responses:** -- "Kon is open about its architecture. The transcription models run entirely on your hardware. Network monitor confirms zero outbound traffic during transcription." +- "Magnotia is open about its architecture. The transcription models run entirely on your hardware. Network monitor confirms zero outbound traffic during transcription." - "Model updates are downloaded and installed locally — same as any desktop software update." **Proof points:** Technical architecture, no-account-required design, open development approach. @@ -249,8 +249,8 @@ Warm, spacious, unhurried. The sonic reference is Jack Johnson, M83 (Outro), Nuj ### Semiotic Territory **Dominant codes to break:** -- Productivity apps default to clean white/blue, sharp geometric sans-serifs, dashboard-heavy interfaces. Kon should feel nothing like a SaaS dashboard. -- Note-taking tools trend toward complexity pride — graph views, backlink maps, plugin ecosystems. Kon should feel like the opposite of that visual noise. +- Productivity apps default to clean white/blue, sharp geometric sans-serifs, dashboard-heavy interfaces. Magnotia should feel nothing like a SaaS dashboard. +- Note-taking tools trend toward complexity pride — graph views, backlink maps, plugin ecosystems. Magnotia should feel like the opposite of that visual noise. **Emergent codes to explore:** - Warm brutalism — honest materials, structural clarity, but with human warmth. The Barbican metaphor. @@ -286,7 +286,7 @@ Warm, spacious, unhurried. The sonic reference is Jack Johnson, M83 (Outro), Nuj ### Kapferer Brand Identity Prism -| Facet | Kon | +| Facet | Magnotia | |---|---| | **Physique** | Warm amber tones, grain texture, serif/sans-serif typography pairing, clean but not sterile interfaces | | **Personality** | Sage/Magician. Calm, astute, direct. Unknowingly funny. Matches your energy | diff --git a/docs/brief/README.md b/docs/brief/README.md index eb95e2a..482457d 100644 --- a/docs/brief/README.md +++ b/docs/brief/README.md @@ -1,12 +1,12 @@ - + -# Kon — Master Brief Index +# Magnotia — Master Brief Index **Last updated:** 2026/03/20 **Status:** MVP — approaching closed beta **Owner:** Jake (personal project, potential roll-up into CORBEL Ltd if successful) -Modular split of the Kon master brief. Each file is self-contained. The original lives at `input/inbox/kon-master-brief.md`. +Modular split of the Magnotia master brief. Each file is self-contained. The original lives at `input/inbox/magnotia-master-brief.md`. --- @@ -14,7 +14,7 @@ Modular split of the Kon master brief. Each file is self-contained. The original | § | File | Summary | |---|---|---| -| 1 | [what-kon-is.md](what-kon-is.md) | Core thesis — voice-first, local-only, zero-friction productivity for executive dysfunction | +| 1 | [what-magnotia-is.md](what-magnotia-is.md) | Core thesis — voice-first, local-only, zero-friction productivity for executive dysfunction | | 2 | [target-audience.md](target-audience.md) | Beachhead (neurodivergent) and secondary audiences | | 3 | [tech-stack.md](tech-stack.md) | Tauri/Rust/Svelte, Whisper, local LLM, RAG, MCP, sync, dependencies | | 4 | [feature-set.md](feature-set.md) | MVP features, post-MVP, and parked ideas | @@ -30,7 +30,7 @@ Modular split of the Kon master brief. Each file is self-contained. The original | File | Summary | |---|---| -| [micro-saas-playbook.md](micro-saas-playbook.md) | 9 patterns from Starter Story research, each mapped to Kon's position | +| [micro-saas-playbook.md](micro-saas-playbook.md) | 9 patterns from Starter Story research, each mapped to Magnotia's position | ## Part 3: Market Research @@ -38,7 +38,7 @@ Modular split of the Kon master brief. Each file is self-contained. The original |---|---|---| | 11 | [market-size-demographics.md](market-size-demographics.md) | TAM, psychology, economic upside | | 12 | [user-sentiment.md](user-sentiment.md) | Abandon-shame cycle, frustrations, demand signals | -| 13 | [competitive-landscape.md](competitive-landscape.md) | Tiimo, Structured, Goblin.tools, and 5 others — plus Kon's advantages | +| 13 | [competitive-landscape.md](competitive-landscape.md) | Tiimo, Structured, Goblin.tools, and 5 others — plus Magnotia's advantages | | 14 | [why-current-tools-fail.md](why-current-tools-fail.md) | Cognitive overhead, latency, app fatigue | | 15 | [feature-validation.md](feature-validation.md) | Voice input, body doubling, local-first — research backing | | 16 | [lifetime-licence-economics.md](lifetime-licence-economics.md) | Affinity, iA Writer, Sublime Text precedents and risks | diff --git a/docs/brief/appendix-ai-body-doubling.md b/docs/brief/appendix-ai-body-doubling.md index 621acf8..c9c1576 100644 --- a/docs/brief/appendix-ai-body-doubling.md +++ b/docs/brief/appendix-ai-body-doubling.md @@ -1,4 +1,4 @@ - + ## A2. AI Body Doubling — Controlled Studies @@ -15,4 +15,4 @@ **Theoretical basis:** Barkley's (1997) model of ADHD as a disorder of behavioural inhibition prescribes externalisation of executive functions — moving regulatory demands from impaired internal systems into the environment. Body doubling is precisely this: an external source of temporal anchoring, accountability, and arousal regulation. -**Implication for Kon:** The low-fi "Focus Room" (section 4) is strongly validated. Combine ambient AI presence with context-aware nudges for maximum effect. The AI option specifically reduces barriers for autistic users whilst maintaining comparable efficacy. Design should include: simulated progress indicators, rhythmic work pacing cues, and subtle ambient motion for divided attention support. +**Implication for Magnotia:** The low-fi "Focus Room" (section 4) is strongly validated. Combine ambient AI presence with context-aware nudges for maximum effect. The AI option specifically reduces barriers for autistic users whilst maintaining comparable efficacy. Design should include: simulated progress indicators, rhythmic work pacing cues, and subtle ambient motion for divided attention support. diff --git a/docs/brief/appendix-cognitive-ergonomics.md b/docs/brief/appendix-cognitive-ergonomics.md index 5066000..c80e9d9 100644 --- a/docs/brief/appendix-cognitive-ergonomics.md +++ b/docs/brief/appendix-cognitive-ergonomics.md @@ -1,4 +1,4 @@ - + ## A3. Cognitive Ergonomics — Visual Crowding and Typography @@ -22,4 +22,4 @@ **Colour contrast:** - **Rello 2012** (*W3C Symposium*): People with dyslexia read fastest with lower-contrast warm pairs like **black on crème** — not black on white. Only 13.64% of dyslexic readers preferred black-on-white vs. 32.67% of controls. -**Implication for Kon:** Default to a clean sans-serif with large x-height (Atkinson Hyperlegible or Lexend) with coordinated letter, word, and line spacing controls. Offer warm off-white background options (crème, not white). Never use italic for extended reading. OpenDyslexic should be available as an option but not recommended — spacing is the intervention, not letterform. Most importantly: allow full typographic personalisation, because no single configuration is optimal for all neurodivergent users. +**Implication for Magnotia:** Default to a clean sans-serif with large x-height (Atkinson Hyperlegible or Lexend) with coordinated letter, word, and line spacing controls. Offer warm off-white background options (crème, not white). Never use italic for extended reading. OpenDyslexic should be available as an option but not recommended — spacing is the intervention, not letterform. Most importantly: allow full typographic personalisation, because no single configuration is optimal for all neurodivergent users. diff --git a/docs/brief/appendix-evolutionary-psychology.md b/docs/brief/appendix-evolutionary-psychology.md index e052999..12fdad8 100644 --- a/docs/brief/appendix-evolutionary-psychology.md +++ b/docs/brief/appendix-evolutionary-psychology.md @@ -1,4 +1,4 @@ - + ## A7. Evolutionary Psychology and Meta-Insights @@ -6,4 +6,4 @@ **Meta-insight across all domains:** The populations who need these tools most benefit from them the most. Toli et al. found implementation intention effects of d = 0.99 in clinical populations vs. d = 0.65 in general populations. Joo et al. found spacing interventions specifically help those with elevated visual crowding. Kofler et al. found 75–81% of ADHD cases show the WM deficits that make local-first architecture necessary. A well-designed tool's efficacy curve is steepest for the most impaired users. -**Implication for Kon:** The app should feel alive, not static. The convergence of voice-first interaction (reduces navigation complexity), local-first architecture (eliminates latency), and AI presence (provides external regulation) addresses different links in the same causal chain. Each feature amplifies the others. +**Implication for Magnotia:** The app should feel alive, not static. The convergence of voice-first interaction (reduces navigation complexity), local-first architecture (eliminates latency), and AI presence (provides external regulation) addresses different links in the same causal chain. Each feature amplifies the others. diff --git a/docs/brief/appendix-hitl-scaffolding.md b/docs/brief/appendix-hitl-scaffolding.md index 4ecab43..ac60e74 100644 --- a/docs/brief/appendix-hitl-scaffolding.md +++ b/docs/brief/appendix-hitl-scaffolding.md @@ -1,4 +1,4 @@ - + ## A5. HITL AI Scaffolding — Autonomy-Supportive Design @@ -23,4 +23,4 @@ 4. **Keep the human in the loop** — every AI suggestion requires user confirmation, building executive function rather than atrophying it 5. **Design with, not for** — participatory design with neurodivergent users produces fundamentally different and better outcomes -**Implication for Kon:** The AI agent must be visible, conversational, and interactive — but must never override user autonomy. Every suggestion requires confirmation. The human-in-the-loop feedback mechanism builds metacognitive awareness over time. Users should eventually internalise Kon's scaffolding patterns and need them less — that's a feature, not a failure. LLM prompts must be calibrated for neurodivergent cognition, not neurotypical assumptions. +**Implication for Magnotia:** The AI agent must be visible, conversational, and interactive — but must never override user autonomy. Every suggestion requires confirmation. The human-in-the-loop feedback mechanism builds metacognitive awareness over time. Users should eventually internalise Magnotia's scaffolding patterns and need them less — that's a feature, not a failure. LLM prompts must be calibrated for neurodivergent cognition, not neurotypical assumptions. diff --git a/docs/brief/appendix-implementation-intentions.md b/docs/brief/appendix-implementation-intentions.md index 1dfe493..c19392b 100644 --- a/docs/brief/appendix-implementation-intentions.md +++ b/docs/brief/appendix-implementation-intentions.md @@ -1,4 +1,4 @@ - + ## A1. Implementation Intentions — Neurological and Clinical Evidence @@ -18,4 +18,4 @@ - **Gilbert et al. 2009** (*Journal of Experimental Psychology: Learning, Memory, and Cognition*): fMRI shows implementation intentions shift activation from the **lateral rostral prefrontal cortex** (effortful top-down control — impaired in ADHD) to the **medial rostral prefrontal cortex** (automatic stimulus-driven control). Better prospective memory performance with *reduced* overall brain activation. - **Paul et al. 2007** (*NeuroReport*): EEG confirms if-then plans normalised the NoGo-P300 amplitude in ADHD children within the **160–312 millisecond window**, consistent with early automatic processing rather than slow deliberate control. -**Implication for Kon:** The if-then automation feature and voice-activated micro-stepping are neurologically validated mechanisms with a d = 0.99 effect size in the target population. Voice capture must externalise implementation intentions instantaneously, before executive fatigue occurs. The system should prompt users to rehearse plans at least once (amplifies effect) and support varied cue types: time-based, environmental, and emotional. +**Implication for Magnotia:** The if-then automation feature and voice-activated micro-stepping are neurologically validated mechanisms with a d = 0.99 effect size in the target population. Voice capture must externalise implementation intentions instantaneously, before executive fatigue occurs. The system should prompt users to rehearse plans at least once (amplifies effect) and support varied cue types: time-based, environmental, and emotional. diff --git a/docs/brief/appendix-latency-memory.md b/docs/brief/appendix-latency-memory.md index b04d467..c9d8665 100644 --- a/docs/brief/appendix-latency-memory.md +++ b/docs/brief/appendix-latency-memory.md @@ -1,4 +1,4 @@ - + ## A4. Latency, Working Memory Decay, and Software Architecture @@ -25,4 +25,4 @@ **Local-first as cognitive ergonomics:** - **Kleppmann et al. 2019** (*ACM Onward! '19*): Seven ideals of local-first software. Ideal #1 — "No spinners: your work at your fingertips." Primary copy of data on the user's device means read/write operations at local disk speed (sub-millisecond), not network speed (50–500+ ms). Synchronisation happens asynchronously in background. -**Implication for Kon:** Local-first architecture keeps all interactions within Miller's 100ms direct-manipulation threshold, preventing the WM decay → exploration bias → task abandonment cascade. The 90-second setup threshold is a hard design constraint. Voice capture must work in under 3 seconds from app open. +**Implication for Magnotia:** Local-first architecture keeps all interactions within Miller's 100ms direct-manipulation threshold, preventing the WM decay → exploration bias → task abandonment cascade. The 90-second setup threshold is a hard design constraint. Voice capture must work in under 3 seconds from app open. diff --git a/docs/brief/appendix-voice-interfaces.md b/docs/brief/appendix-voice-interfaces.md index 8928658..0b12b80 100644 --- a/docs/brief/appendix-voice-interfaces.md +++ b/docs/brief/appendix-voice-interfaces.md @@ -1,4 +1,4 @@ - + ## A6. Voice User Interfaces as Executive Bypasses @@ -7,6 +7,6 @@ - Voice activation bypasses the visual and mechanical bottlenecks of GUI interaction (typing, mouse navigation, visual scanning, sequential menu navigation) — all of which require sustained top-down executive functioning. - Vocalisation is approximately **3x faster** than manual keyboard entry. - VUI design constraints for cognitive accessibility: engineered pauses between phrases for auditory processing time, options presented in text before requiring selection to avoid overloading verbal working memory. -- Current voice assistants impose their own setup complexity — Kon must minimise this to near-zero. +- Current voice assistants impose their own setup complexity — Magnotia must minimise this to near-zero. -**Implication for Kon:** Voice is not a convenience feature — it is the primary accessibility mechanism. The 3x speed advantage means voice capture preserves working memory traces that would decay during typing. VUI implementation must include processing pauses and visual confirmation of transcribed text before action. The supply-demand gap (47.6% community interest vs. near-zero academic research) represents a significant opportunity for Kon to generate its own evidence through ethically designed measurement. +**Implication for Magnotia:** Voice is not a convenience feature — it is the primary accessibility mechanism. The 3x speed advantage means voice capture preserves working memory traces that would decay during typing. VUI implementation must include processing pauses and visual confirmation of transcribed text before action. The supply-demand gap (47.6% community interest vs. near-zero academic research) represents a significant opportunity for Magnotia to generate its own evidence through ethically designed measurement. diff --git a/docs/brief/b2b-enterprise.md b/docs/brief/b2b-enterprise.md index c841bcc..373d127 100644 --- a/docs/brief/b2b-enterprise.md +++ b/docs/brief/b2b-enterprise.md @@ -1,4 +1,4 @@ - + ## 19. B2B & Enterprise Angle @@ -18,23 +18,23 @@ - Explicitly covers ADHD and other neurodivergent conditions under the Equality Act 2010 - Software subscriptions, planning apps, and coaching are all fundable - Deepwrk already operates as an Access to Work-approved service — employees claim subscriptions through their grant -- **This is the single highest-leverage B2B action Kon can take.** Government effectively subsidises the sale. +- **This is the single highest-leverage B2B action Magnotia can take.** Government effectively subsidises the sale. ### B2B requirements (if/when pursued) - Admin dashboard, SSO (SAML/OAuth), bulk provisioning - Anonymised usage analytics for HR (never individual-level data) -- **Anonymised organisational dashboards.** While Kon processes all personal data locally, the B2B tier must output high-level, anonymised telemetry to satisfy enterprise buyers who need metrics to justify software purchases. Examples: "Your team saved 40 hours in task-planning this month", "Average time-to-capture across your organisation: 6 seconds", "82% of users returned after a gap of 3+ days." Critically, these metrics must be aggregated (minimum cohort size of 10 before any data is surfaced), never traceable to individuals, and opt-in at both the user and organisation level. The local-first architecture makes this possible: anonymised summaries can be generated on-device and transmitted as aggregate statistics only — raw data never leaves the machine. +- **Anonymised organisational dashboards.** While Magnotia processes all personal data locally, the B2B tier must output high-level, anonymised telemetry to satisfy enterprise buyers who need metrics to justify software purchases. Examples: "Your team saved 40 hours in task-planning this month", "Average time-to-capture across your organisation: 6 seconds", "82% of users returned after a gap of 3+ days." Critically, these metrics must be aggregated (minimum cohort size of 10 before any data is surfaced), never traceable to individuals, and opt-in at both the user and organisation level. The local-first architecture makes this possible: anonymised summaries can be generated on-device and transmitted as aggregate statistics only — raw data never leaves the machine. - GDPR compliance documentation, zero-IT-lift deployment - Users must never be identifiable as neurodivergent to their employer - Position under "universal design" framing — beneficial for all employees ### Enterprise IT deployment -Kon's local-first architecture is simultaneously its biggest B2B selling point and its biggest deployment challenge. Key considerations: +Magnotia's local-first architecture is simultaneously its biggest B2B selling point and its biggest deployment challenge. Key considerations: - **Local AI model size.** Whisper models range from ~75MB (tiny) to ~1.5GB (large). Enterprise IT teams may flag large binaries or models downloaded to employee machines. Solution: bundle a smaller model by default (tiny/base) with optional upgrade to larger models. Document the model sizes and what they do for IT review. -- **No cloud = no enterprise compliance headaches.** Because Kon processes everything on-device with no data transmitted externally, it bypasses the cloud security review, vendor risk assessment, and data processing agreements that typically delay enterprise software procurement by 3–6 months. This is a genuine competitive advantage — frame it explicitly in B2B sales materials. -- **Installation permissions.** Enterprise-managed machines often restrict software installation. Kon must be deployable via MDM (Mobile Device Management) tools like Microsoft Intune or Jamf. Tauri's MSIX (Windows) and DMG (macOS) formats are compatible with standard enterprise deployment pipelines. -- **No internet dependency.** Kon does not require network access for core functionality. This makes it deployable in air-gapped, high-security, or restricted-network environments — a strong selling point for defence, legal, and healthcare settings. +- **No cloud = no enterprise compliance headaches.** Because Magnotia processes everything on-device with no data transmitted externally, it bypasses the cloud security review, vendor risk assessment, and data processing agreements that typically delay enterprise software procurement by 3–6 months. This is a genuine competitive advantage — frame it explicitly in B2B sales materials. +- **Installation permissions.** Enterprise-managed machines often restrict software installation. Magnotia must be deployable via MDM (Mobile Device Management) tools like Microsoft Intune or Jamf. Tauri's MSIX (Windows) and DMG (macOS) formats are compatible with standard enterprise deployment pipelines. +- **No internet dependency.** Magnotia does not require network access for core functionality. This makes it deployable in air-gapped, high-security, or restricted-network environments — a strong selling point for defence, legal, and healthcare settings. - **Automatic updates.** Enterprise IT will want to control update rollouts. Provide the option to disable auto-updates and instead distribute updates through enterprise channels. ### Channel partners diff --git a/docs/brief/competitive-landscape.md b/docs/brief/competitive-landscape.md index a14b0e7..181adb0 100644 --- a/docs/brief/competitive-landscape.md +++ b/docs/brief/competitive-landscape.md @@ -1,4 +1,4 @@ - + ## 13. Competitive Landscape (Extended) @@ -44,8 +44,8 @@ - Tasks, habits, calendar, mood tracking, journalling with end-to-end encryption on desktop - Privacy-focused, small user base -### Kon's advantages over the entire field -| Kon | The field | +### Magnotia's advantages over the entire field +| Magnotia | The field | |---|---| | Cross-platform desktop + mobile (Tauri) | Almost all competitors are mobile-first or web-only | | Voice as primary input method | No mature competitor integrates voice into a full planning system | @@ -59,4 +59,4 @@ 3. **Architecture:** Privacy-conscious and offline-first users served only by open-source tools and tiny startups. 4. **Pricing:** Only Structured offers lifetime. Subscription fatigue is extreme in this demographic. -Kon addresses all four simultaneously. No current competitor does. +Magnotia addresses all four simultaneously. No current competitor does. diff --git a/docs/brief/design-principles.md b/docs/brief/design-principles.md index ea5db71..435379f 100644 --- a/docs/brief/design-principles.md +++ b/docs/brief/design-principles.md @@ -1,4 +1,4 @@ - + ### Design principles @@ -21,7 +21,7 @@ #### Interaction & UX - **Low-dopamine design.** Non-judgmental tone throughout. No guilt messaging for missed tasks. No aggressive review prompts. - **WIP limits as a design constraint.** The interface must never present more than 1–3 active tasks simultaneously on the primary view. AI prioritises; the UI constrains. A brain dump can contain 50 items — the "Now" view shows only the next action. This is not a nice-to-have; it is the core mechanism for preventing the freeze response. -- **Automated context restoration.** Working memory traces decay within ~8 seconds of interruption. If a user clicks away, gets distracted, or closes the app mid-task, Kon must perfectly preserve their exact state — cursor position, active timer, active task, scroll position — so they can resume with zero "Where was I?" cognitive latency. This must be seamless and automatic. No "Resume session?" dialogue. Just open the app and be exactly where you left off. +- **Automated context restoration.** Working memory traces decay within ~8 seconds of interruption. If a user clicks away, gets distracted, or closes the app mid-task, Magnotia must perfectly preserve their exact state — cursor position, active timer, active task, scroll position — so they can resume with zero "Where was I?" cognitive latency. This must be seamless and automatic. No "Resume session?" dialogue. Just open the app and be exactly where you left off. - **Literal labels always.** Ambiguous icons (standalone gear, hamburger menu) force literal thinkers to guess function, expending precious mental energy. Always pair icons with literal text labels. - **Progressive disclosure.** Break complex onboarding or tasks down to reveal only the immediate next step, preventing the brain from freezing. - **Motion control.** All non-essential animation and auto-playing media must be off by default or controlled via a prominent "Reduce Motion" / "Calm Mode" toggle. Unexpected animations can cause physical distress and sensory overload. @@ -29,7 +29,7 @@ #### Onboarding - Must be understandable within 30 seconds. If a neurodivergent user can't figure it out immediately, they won't return. -- **90-second hard threshold.** Empirical HCI research (see Appendix A4) shows that tools taking longer than 90 seconds to configure trigger task abandonment cascades in ADHD users, increasing cognitive load by 2.3x. No feature in Kon should require more than 90 seconds of setup. Voice capture must work in under 3 seconds from app open. +- **90-second hard threshold.** Empirical HCI research (see Appendix A4) shows that tools taking longer than 90 seconds to configure trigger task abandonment cascades in ADHD users, increasing cognitive load by 2.3x. No feature in Magnotia should require more than 90 seconds of setup. Voice capture must work in under 3 seconds from app open. - Progressive disclosure applies here especially — show one step at a time, never the full complexity. #### Future consideration: adaptive UI diff --git a/docs/brief/desktop-distribution.md b/docs/brief/desktop-distribution.md index 90e48c8..1d9bec7 100644 --- a/docs/brief/desktop-distribution.md +++ b/docs/brief/desktop-distribution.md @@ -1,4 +1,4 @@ - + ## 17. Desktop Distribution Deep Dive diff --git a/docs/brief/distribution-strategy.md b/docs/brief/distribution-strategy.md index 7d46a1d..2c18546 100644 --- a/docs/brief/distribution-strategy.md +++ b/docs/brief/distribution-strategy.md @@ -1,15 +1,15 @@ - + ## 7. Distribution Strategy ### Marketing positioning -**What Kon is NOT:** A to-do list. A habit tracker. Another productivity app. The market is flooded with generic productivity tools, and ADHD users have severe app fatigue from trying and abandoning dozens of them. Positioning Kon in that category is death. +**What Magnotia is NOT:** A to-do list. A habit tracker. Another productivity app. The market is flooded with generic productivity tools, and ADHD users have severe app fatigue from trying and abandoning dozens of them. Positioning Magnotia in that category is death. -**What Kon IS:** An "external brain." A prosthetic prefrontal cortex designed for cognitive offloading. The app does the heavy cognitive lifting — it takes raw, messy thoughts via voice and automatically decomposes them into verb-led micro-steps (e.g. "Clean the house" → "Pick up one item of clothing from the bedroom floor"). +**What Magnotia IS:** An "external brain." A prosthetic prefrontal cortex designed for cognitive offloading. The app does the heavy cognitive lifting — it takes raw, messy thoughts via voice and automatically decomposes them into verb-led micro-steps (e.g. "Clean the house" → "Pick up one item of clothing from the bedroom floor"). **Key messaging pillars:** -1. **"Your brain moves fast. Kon catches it."** — Voice-first capture, zero friction, thoughts don't get lost. +1. **"Your brain moves fast. Magnotia catches it."** — Voice-first capture, zero friction, thoughts don't get lost. 2. **"Local. Private. Yours forever."** — Nothing leaves your device. No cloud. No subscriptions for core features. Your vulnerabilities are never exposed. 3. **"Built by a neurodivergent brain, for neurodivergent brains."** — Authenticity. Jake has executive dysfunction. This isn't corporate empathy theatre. 4. **"They took away lifetime. We never will."** — Direct competitive positioning against Tiimo's subscription-only model. @@ -19,7 +19,7 @@ ### Distribution channels **Desktop distribution:** -- **Primary:** Direct download from kon.app via Lemon Squeezy or Paddle (5% + 50p per transaction). Signed and notarised builds for macOS (£79/year Apple Developer Programme) and code-signed for Windows (EV certificate, £240–£480/year). +- **Primary:** Direct download from magnotia.app via Lemon Squeezy or Paddle (5% + 50p per transaction). Signed and notarised builds for macOS (£79/year Apple Developer Programme) and code-signed for Windows (EV certificate, £240–£480/year). - **Microsoft Store (supplementary):** Free to list, 250M monthly active users, 0% commission if using own payment system. Good for discovery. - **Mac App Store (evaluate):** 15% commission under Small Business Programme, sandboxing may limit Tauri features. Most successful indie Mac apps distribute directly. - **Linux:** Flathub (1M+ active users, pre-installed on major distros) + AppImage for direct download. @@ -42,7 +42,7 @@ **SEO opportunity:** Long-tail terms like "ADHD app for Windows" and "focus timer desktop app" face lower competition than mobile-focused searches. Obsidian gets 52.9% of traffic from organic search — proof that desktop-first apps can win on SEO. ### Phase 0 — Pre-beta (this week) -- [ ] Register domain (kon.app or getkon.app) +- [ ] Register domain (magnotia.app or getmagnotia.app) - [ ] Build one-page landing page on Carrd (£16/year) or Framer (free tier). Hero must answer three questions in under 5 seconds: what is this, who is it for, what do I do next. Landing page copy written at 5th–7th grade reading level (converts at 11.1% vs. 5.3% for university-level copy). Include 15–30 second silent auto-play GIF showing voice-to-task flow. Single CTA button. - [ ] Set up waitlist with LaunchList (£65 one-time). Includes gamified referral mechanics, anti-spam filtering. Alternative: ConvertKit (free to 1,000 subscribers) + Tally form. - [ ] Set up analytics with Plausible.io (privacy-friendly, no cookie banner needed). @@ -57,8 +57,8 @@ - [ ] Run Van Westendorp pricing survey via Tally (free) to validate £49 price point before committing ### Phase 2 — Community seeding (weeks 2–4) -- [ ] **Reddit (priority 1):** r/ADHD (2.1M members), r/adhdwomen, r/ADHD_Programmers, r/autism, r/neurodiversity, r/executivedysfunction. Spend 4+ weeks genuinely contributing before any mention of Kon (Reddit 10:1 rule). When ready: authentic posts, no sales pitches. Use F5Bot (free) to monitor keywords: "ADHD app", "voice to-do", "ADHD task manager." -- [ ] **Obsidian/PKM communities (priority 2):** Show Kon → Obsidian workflow (voice dump → transcription → tasks → Obsidian vault). Use as amplifiers, not primary sales channel. +- [ ] **Reddit (priority 1):** r/ADHD (2.1M members), r/adhdwomen, r/ADHD_Programmers, r/autism, r/neurodiversity, r/executivedysfunction. Spend 4+ weeks genuinely contributing before any mention of Magnotia (Reddit 10:1 rule). When ready: authentic posts, no sales pitches. Use F5Bot (free) to monitor keywords: "ADHD app", "voice to-do", "ADHD task manager." +- [ ] **Obsidian/PKM communities (priority 2):** Show Magnotia → Obsidian workflow (voice dump → transcription → tasks → Obsidian vault). Use as amplifiers, not primary sales channel. - [ ] **TikTok product seeding (priority 3):** DM 20–50 ADHD micro-influencers (1K–50K followers) with free lifetime licences. Zero obligation to post. Cost per seed: £0 (digital product). Outreach must reference a specific video the creator made. Follow up with affiliate link at 25–30% commission via Lemon Squeezy. - [ ] Submit to ADHD UK discovery platform and ADDitude Magazine tool roundups. diff --git a/docs/brief/feature-set.md b/docs/brief/feature-set.md index 474f6ea..e79ab44 100644 --- a/docs/brief/feature-set.md +++ b/docs/brief/feature-set.md @@ -1,18 +1,18 @@ - + ## 4. Feature Set ### Core MVP (shipping with beta) - Local AI transcription (Whisper, on-device) - Auto-populating to-do lists from transcriptions -- **Visual time representation.** Tasks displayed as visual blocks of time or countdowns, not just text lists. Traditional text-based to-do lists trigger overwhelm — visual timelines directly combat time blindness. This is the #1 community-requested feature and Tiimo's primary strength. Kon must match or exceed it from day one. Time should be externalised using visual countdown timers (e.g. shrinking colour disks, filling progress rings) rather than standard digital clocks — making the passage of time concrete and anchoring focus for users with time agnosia. +- **Visual time representation.** Tasks displayed as visual blocks of time or countdowns, not just text lists. Traditional text-based to-do lists trigger overwhelm — visual timelines directly combat time blindness. This is the #1 community-requested feature and Tiimo's primary strength. Magnotia must match or exceed it from day one. Time should be externalised using visual countdown timers (e.g. shrinking colour disks, filling progress rings) rather than standard digital clocks — making the passage of time concrete and anchoring focus for users with time agnosia. - **WIP limits.** The main screen must mathematically restrict how many active tasks are visible at once. A "Now" column showing only 1–3 items maximum. Auto-generated task lists that dump 30 items onto a screen will instantly trigger the freeze response. The AI can prioritise; the UI must constrain. - History of past voice notes and transcriptions - Light/dark mode - Templates with local AI agent (contextual text under headings with associated metadata) - Vocabulary profiles (custom dictionaries for specialist terms — e.g. DND NPC/location names, technical jargon) - Transcription of uploaded voice notes and media files -- **Open data format.** All transcripts and task lists stored locally in plain text, JSON, or Markdown. Essential for the privacy-first and PKM audience. Enables the Kon → Obsidian workflow promised in the distribution strategy. Users must be able to export, move, and own their data without vendor lock-in. +- **Open data format.** All transcripts and task lists stored locally in plain text, JSON, or Markdown. Essential for the privacy-first and PKM audience. Enables the Magnotia → Obsidian workflow promised in the distribution strategy. Users must be able to export, move, and own their data without vendor lock-in. ### Post-MVP features (validated, designed, not yet prioritised) - **AI-powered micro-stepping with "just start" timer.** Decomposing abstract goals into hyper-specific actionable steps. The local AI agent must generate micro-steps that begin with highly specific, low-friction action verbs. Linguistic rules: every generated step must start with a concrete physical verb, target one single action, and be completable in under 5 minutes. Example: "Clean room" → "Pick up one shirt from the floor." NOT "Organise your bedroom" (still abstract, still paralysing). The goal is to bypass executive dysfunction by removing all ambiguity about what "starting" means. **Paired with a 2-minute or 5-minute "just start" focus timer.** Committing to a task for just five minutes bypasses internal resistance and builds micro-momentum — users frequently work past the timer. The timer should be a single tap from any micro-step, visually prominent, and use a shrinking colour disk or similar visual countdown (not a digital clock) to externalise the passage of time and combat time blindness. @@ -25,5 +25,5 @@ - **Read Page Aloud (text-to-speech).** A simple TTS function that reads transcriptions, task lists, or AI-generated micro-steps aloud. Engages auditory processing alongside visual, which improves retention and comprehension for ADHD users. Particularly valuable during the "Clarify" stage when reviewing a brain dump. Use OS-native TTS engines (available on all target platforms) to avoid additional dependencies. Should be a single-tap action from any text view. ### Parked / future consideration -- **AI body doubling (low-fi implementation).** Research strongly validates the concept (rated #1 ADHD workplace strategy in 2025 ADDitude survey; 12-week study showed focus doubling, 30% anxiety reduction, £37 public value per £1 invested). Body doubling doesn't require high-fidelity interaction — simple ambient presence and shared monitoring work. A "low-fi" version could be a "Focus Room" interface showing abstract statuses ("AI is sorting your tasks…", "3 other Kon users are in deep work right now") to provide the feeling of parallel presence without complex engineering. This sidesteps the need for video, voice, or real-time communication. Potential future subscription feature. Not in MVP scope but worth prototyping early — the implementation cost is low relative to the validated demand. +- **AI body doubling (low-fi implementation).** Research strongly validates the concept (rated #1 ADHD workplace strategy in 2025 ADDitude survey; 12-week study showed focus doubling, 30% anxiety reduction, £37 public value per £1 invested). Body doubling doesn't require high-fidelity interaction — simple ambient presence and shared monitoring work. A "low-fi" version could be a "Focus Room" interface showing abstract statuses ("AI is sorting your tasks…", "3 other Magnotia users are in deep work right now") to provide the feeling of parallel presence without complex engineering. This sidesteps the need for video, voice, or real-time communication. Potential future subscription feature. Not in MVP scope but worth prototyping early — the implementation cost is low relative to the validated demand. - Temptation bundling — cut (OS-level integration nightmare across platforms, essentially impossible on iOS). Replaced by energy-aware task sequencing (see post-MVP features). diff --git a/docs/brief/feature-validation.md b/docs/brief/feature-validation.md index 44957c6..6509272 100644 --- a/docs/brief/feature-validation.md +++ b/docs/brief/feature-validation.md @@ -1,4 +1,4 @@ - + ## 15. Feature Validation from Research diff --git a/docs/brief/influencer-landscape.md b/docs/brief/influencer-landscape.md index 8ca5bfa..787b28d 100644 --- a/docs/brief/influencer-landscape.md +++ b/docs/brief/influencer-landscape.md @@ -1,4 +1,4 @@ - + ## 18. ADHD Content Creator & Influencer Landscape @@ -19,7 +19,7 @@ ### UK advocacy organisations - **ADHD Foundation:** Largest user-led ADHD organisation in Europe -- **ADHD UK:** Launched a discovery platform reviewing tools and strategies — natural fit for Kon +- **ADHD UK:** Launched a discovery platform reviewing tools and strategies — natural fit for Magnotia - **Neurodiversity in Business:** Corporate-facing charity ### Sponsorship costs diff --git a/docs/brief/key-risks.md b/docs/brief/key-risks.md index 17165fb..868bcb6 100644 --- a/docs/brief/key-risks.md +++ b/docs/brief/key-risks.md @@ -1,4 +1,4 @@ - + ## 8. Key Risks @@ -9,9 +9,9 @@ | Zero distribution infrastructure | 90-day calendar above. LaunchList + Reddit + TikTok seeding + Product Hunt. Total budget: £81. | | Lifetime pricing limits long-term revenue | Cloud tier provides recurring revenue. Monitor conversion rate. Launch pricing for first 500 creates urgency. | | Scope creep from secondary audiences (TTRPG, B2B) | Neurodivergent beachhead ONLY until validated. No feature work for secondary audiences until £2K MRR. | -| Nobody has seen Kon yet — zero external validation | Beta this week fixes this. Share embarrassingly early. | +| Nobody has seen Magnotia yet — zero external validation | Beta this week fixes this. Share embarrassingly early. | | ADHD app market high abandonment rate | Design around the shame spiral. Welcome users back without judgement. Never punish inconsistency. Grace day recovery rate is the key metric. | | Lifetime pricing economics break if cloud costs grow | Keep cloud tier strictly optional. Base product must remain sustainable on one-time revenue alone. | -| EAA compliance required as Kon grows beyond microenterprise threshold | Build to WCAG 2.2 AA from day one. Publish VPAT before competitors do. | +| EAA compliance required as Magnotia grows beyond microenterprise threshold | Build to WCAG 2.2 AA from day one. Publish VPAT before competitors do. | | cr-sqlite development pace has slowed since late 2024 | Core CRDT logic is sound and self-contained. Fallback: Automerge + SQLite BLOB storage, reusing entire iroh/mDNS networking stack unchanged. | | Code signing costs are unavoidable | macOS £79/year + Windows £240–£480/year = ~£320–£560/year minimum. Budget from first revenue. | diff --git a/docs/brief/legal-compliance.md b/docs/brief/legal-compliance.md index d92f213..4b91ec1 100644 --- a/docs/brief/legal-compliance.md +++ b/docs/brief/legal-compliance.md @@ -1,4 +1,4 @@ - + ## 6. Legal & Compliance @@ -9,8 +9,8 @@ - **Budget impact:** ~£320–£560/year minimum for macOS + Windows signing. Non-optional cost. ### GDPR position (local-only tier) -- **Jake is NOT a data processor.** Kon runs entirely on-device. No data is transmitted, stored, or visible to the developer. Same legal position as distributing a word processor. -- **Special category data:** Marketing targets neurodivergent users, but the app does not collect, store, or infer diagnosis information. Per ICO guidance, a "possible inference" is not special category data — only "reasonable certainty" triggers Article 9. Kon is on safe ground here. +- **Jake is NOT a data processor.** Magnotia runs entirely on-device. No data is transmitted, stored, or visible to the developer. Same legal position as distributing a word processor. +- **Special category data:** Marketing targets neurodivergent users, but the app does not collect, store, or infer diagnosis information. Per ICO guidance, a "possible inference" is not special category data — only "reasonable certainty" triggers Article 9. Magnotia is on safe ground here. - **Voice data:** Processed locally by Whisper. Never leaves the device. No third-party processor involved. ### GDPR position (cloud tier — when added) @@ -22,10 +22,10 @@ - Enforceable from 28 June 2025. Applies to consumer-facing digital products sold in the EU, including apps. - Technical benchmark: EN 301 549 V3.2.1, incorporating WCAG 2.1 Level AA. - Applies to non-EU companies selling to EU customers (similar extraterritorial reach to GDPR). -- Microenterprises (fewer than 10 employees, under €2M turnover) are currently exempt — Kon qualifies initially. +- Microenterprises (fewer than 10 employees, under €2M turnover) are currently exempt — Magnotia qualifies initially. - **The UK has not adopted the EAA.** UK relies on the Equality Act 2010 ("reasonable adjustments") with no specific technical standards enforced. - **Competitive opportunity:** Neither Tiimo nor Structured publishes a VPAT or formal accessibility conformance report. Publishing one first opens doors to government procurement, educational institutions, and enterprise contracts. -- Build to WCAG 2.2 AA from day one — this aligns with Kon's design philosophy and creates a genuine compliance moat. +- Build to WCAG 2.2 AA from day one — this aligns with Magnotia's design philosophy and creates a genuine compliance moat. ### Required before paid launch - [ ] Privacy policy (no data leaves device, no telemetry, no identifying analytics) diff --git a/docs/brief/lifetime-licence-economics.md b/docs/brief/lifetime-licence-economics.md index cfc88aa..62488ad 100644 --- a/docs/brief/lifetime-licence-economics.md +++ b/docs/brief/lifetime-licence-economics.md @@ -1,4 +1,4 @@ - + ## 16. Lifetime Licence Economics @@ -6,7 +6,7 @@ - **Affinity (Serif):** Perpetual licences (~£40/app, £135 suite) for 23 years. 53% profit margins. Acquired by Canva for ~£410M. - **iA Writer:** £40 Mac, £24 Windows, £16 iOS one-time. Free updates for 7+ years. Profitable with team of 12, entirely bootstrapped. Android experiment showed 50/50 split between one-time (£24) and subscription (£4/year), but purchases generated 2–3x more total revenue with significantly better retention. - **Sublime Text:** £79 perpetual licence with paid major-version upgrades. Sustained a tiny team for over a decade. -- **Obsidian:** Free core + £3.20/month Sync, £6.40/month Publish. Clearest precedent for Kon's hybrid model. +- **Obsidian:** Free core + £3.20/month Sync, £6.40/month Publish. Clearest precedent for Magnotia's hybrid model. ### Risks - Revenue plateaus once addressable market is saturated, while support costs continue indefinitely. diff --git a/docs/brief/market-size-demographics.md b/docs/brief/market-size-demographics.md index b1f9623..c27c3eb 100644 --- a/docs/brief/market-size-demographics.md +++ b/docs/brief/market-size-demographics.md @@ -1,4 +1,4 @@ - + ## 11. Market Size & Demographics diff --git a/docs/brief/micro-saas-playbook.md b/docs/brief/micro-saas-playbook.md index f4fbff6..49187e3 100644 --- a/docs/brief/micro-saas-playbook.md +++ b/docs/brief/micro-saas-playbook.md @@ -1,8 +1,8 @@ - + # PART 2: THE 9-PATTERN MICRO-SAAS PLAYBOOK -**Reference.** Distilled from 30+ Starter Story case studies, founder interviews (Tibo, Mike Hill, Kleo/Lara), and cross-referenced with 4,400+ written case studies. Each pattern is mapped to Kon's current position with specific next actions. +**Reference.** Distilled from 30+ Starter Story case studies, founder interviews (Tibo, Mike Hill, Kleo/Lara), and cross-referenced with 4,400+ written case studies. Each pattern is mapped to Magnotia's current position with specific next actions. --- @@ -10,8 +10,8 @@ **The principle:** The most consistent origin story across successful micro-SaaS. The founder was the customer first. Prerender.io, Kleo, Analyzify, Refiner — all built by people solving their own problem. -**Kon's position: ✅ Strong.** -Jake has executive dysfunction. He searched for an offline-first, voice-driven productivity tool for neurodivergent users, couldn't find one that wasn't cloud-dependent or iOS-exclusive, and started building Kon for himself. This is the textbook origin story. +**Magnotia's position: ✅ Strong.** +Jake has executive dysfunction. He searched for an offline-first, voice-driven productivity tool for neurodivergent users, couldn't find one that wasn't cloud-dependent or iOS-exclusive, and started building Magnotia for himself. This is the textbook origin story. **Next action:** Make this the centrepiece of every piece of marketing. "I'm neurodivergent. I built this because nothing else worked for me." Authenticity is the single most powerful distribution asset in neurodivergent communities. @@ -21,7 +21,7 @@ Jake has executive dysfunction. He searched for an offline-first, voice-driven p **The principle:** Find products already making money despite having terrible UX or obvious gaps. If people pay for something broken, the market is proven — you just build better. Mike Hill's entire philosophy. -**Kon's position: ✅ Strong.** +**Magnotia's position: ✅ Strong.** - **Tiimo:** iPhone App of the Year 2025, $200K/month revenue. iOS-only, no Android, no native desktop, cloud-dependent, no voice transcription, subscription-only (removed lifetime option to community backlash), aggressive review prompts. - **WhisperFlow and similar:** Cloud-dependent, premium pricing, no task management integration. - **Todoist, Notion, etc.:** Not designed for neurodivergent brains, subscription-heavy, cognitively overwhelming. @@ -36,7 +36,7 @@ The market is proven. People are paying. The incumbents have obvious, exploitabl **The principle:** Pick a niche so narrow that big players ignore it, then own it completely. Email signature generators, WhatsApp plugins for Shopify, digital signage for cafes. The narrower the niche, the less competition and the higher the conversion rate. -**Kon's position: ✅ Strong.** +**Magnotia's position: ✅ Strong.** "Voice-first, local-only productivity app for neurodivergent people with executive dysfunction" is extremely narrow. No big player is going to build this. Tiimo is the closest and they're a 40-person VC-funded Copenhagen team that still can't get Android working. **Next action:** Resist the temptation to broaden. "Productivity for everyone" is how you become invisible. Stay locked on neurodivergent users until you hit £2K MRR. The TTRPG and B2B angles can wait. @@ -47,7 +47,7 @@ The market is proven. People are paying. The incumbents have obvious, exploitabl **The principle:** "Shipped in 12 hours and now makes $15K/month." Validation speed matters more than product perfection. Pre-sell first, build second (Gil's model). Revenue before polish. -**Kon's position: ✅ Strong.** +**Magnotia's position: ✅ Strong.** MVP is nearly ready. Jake can rebuild from scratch in a day. Tauri/Svelte/Rust stack enables rapid iteration. Beta testers this weekend. **Next action:** Ship the beta this weekend. Don't polish — test. The goal is not "is it beautiful" but "does the brain dump → task list flow actually work?" If the core loop works, everything else is iteration. @@ -58,11 +58,11 @@ MVP is nearly ready. Jake can rebuild from scratch in a day. Tauri/Svelte/Rust s **The principle:** The loudest message across all 30 videos. Most builders skip distribution because it means doing "the hard thing" — talking to people. A great product with no distribution dies. A decent product with great distribution wins. -**Kon's position: ⚠️ Critical gap.** -Zero distribution infrastructure. No landing page, no waitlist, no domain, no social presence for Kon. Nobody outside Jake's immediate circle has seen it. +**Magnotia's position: ⚠️ Critical gap.** +Zero distribution infrastructure. No landing page, no waitlist, no domain, no social presence for Magnotia. Nobody outside Jake's immediate circle has seen it. **Next actions (in order):** -1. Register domain this week (kon.app or getkon.app). +1. Register domain this week (magnotia.app or getmagnotia.app). 2. One-page landing page with waitlist signup live by Monday. 3. Roo's nonprofit network gets the link first. 4. Reddit posts in r/ADHD, r/adhdwomen, r/ADHD_Programmers, r/autism — authentic, not salesy. @@ -76,7 +76,7 @@ This is the make-or-break pattern. Everything else is in place. Distribution is **The principle:** Kleo's playbook — don't launch publicly. Build a waitlist using content, run mini-launches to waitlist subscribers only, create FOMO through scarcity ("you can't buy this, you need to join the waitlist"), and hit £30K MRR in four days. Lara took info-product launch tactics (webinars, email sequences, urgency) and applied them to SaaS. -**Kon's position: ⚠️ Planned but not yet started.** +**Magnotia's position: ⚠️ Planned but not yet started.** Jake intends to do an invite-only beta to create scarcity and mystique. The instinct is right — this maps directly to Kleo's playbook. **Next actions:** @@ -91,10 +91,10 @@ Jake intends to do an invite-only beta to create scarcity and mystique. The inst **The principle:** Mike Hill is emphatic — every one of his founding teams has a designer. Good design sells. Target incumbents with bad UX. When your product looks and feels better, it becomes self-selling. -**Kon's position: ✅ Strong.** -Tauri/Svelte produces a native, fast UI. The design brief includes research-backed neurodivergent-specific design principles: Lexend/Atkinson Hyperlegible typography, sensory colour zoning, no halation, progressive disclosure, literal labels, motion control, forgiving interaction patterns. This level of design intentionality is a genuine moat — Tiimo is good but Kon's design spec is more deeply grounded in the research. +**Magnotia's position: ✅ Strong.** +Tauri/Svelte produces a native, fast UI. The design brief includes research-backed neurodivergent-specific design principles: Lexend/Atkinson Hyperlegible typography, sensory colour zoning, no halation, progressive disclosure, literal labels, motion control, forgiving interaction patterns. This level of design intentionality is a genuine moat — Tiimo is good but Magnotia's design spec is more deeply grounded in the research. -**Next action:** Make the design visible in marketing. Screenshots, screen recordings, and side-by-side comparisons with competitors. "Here's what Tiimo looks like. Here's what Kon looks like. Notice the difference." Let the design sell itself. +**Next action:** Make the design visible in marketing. Screenshots, screen recordings, and side-by-side comparisons with competitors. "Here's what Tiimo looks like. Here's what Magnotia looks like. Notice the difference." Let the design sell itself. --- @@ -102,7 +102,7 @@ Tauri/Svelte produces a native, fast UI. The design brief includes research-back **The principle:** Almost universally, successful micro-SaaS founders are bootstrapped. Mike Hill's model: 4 co-founders, 25% equity each, grow to £10K MRR to cover costs, then split profits as salary. No VC, no bloated teams. His explicit quote: "these businesses are about bigger salaries, not big exits." -**Kon's position: ✅ Strong.** +**Magnotia's position: ✅ Strong.** Solo founder. No VC. No team overhead. Near-zero infrastructure costs (local-first means no servers for the base product). Lifetime pricing + optional cloud subscription. Revenue goes directly to Jake. **Next action:** Set a clear personal revenue target. What number makes this worth maintaining? £500/month covers costs and proves viability. £2K/month funds CORBEL growth. £5K/month is a genuine second income stream. Know your number so you can measure against it. @@ -113,14 +113,14 @@ Solo founder. No VC. No team overhead. Near-zero infrastructure costs (local-fir **The principle:** The highest earners aren't running one product — they're running five or six. Tibo has five apps (combined £700K/month). Mike Hill has five (combined £200K/month). Risk distribution: if one stalls, others keep growing. Each new product follows the same repeatable playbook. -**Kon's position: ⏳ Not relevant yet.** -This is product #1. The playbook only applies once Kon is generating revenue and the system is proven. Then Jake can ask: "What's the next niche I can apply this exact process to?" +**Magnotia's position: ⏳ Not relevant yet.** +This is product #1. The playbook only applies once Magnotia is generating revenue and the system is proven. Then Jake can ask: "What's the next niche I can apply this exact process to?" -**Next action:** None right now. Focus entirely on Kon. But document everything — what worked, what didn't, what you'd do differently. When the time comes for product #2, you'll have a personal playbook to run again. +**Next action:** None right now. Focus entirely on Magnotia. But document everything — what worked, what didn't, what you'd do differently. When the time comes for product #2, you'll have a personal playbook to run again. --- -### Playbook Summary: Where Kon Stands +### Playbook Summary: Where Magnotia Stands | Pattern | Status | Priority | |---|---|---| diff --git a/docs/brief/open-questions.md b/docs/brief/open-questions.md index 85d5c75..5b16a24 100644 --- a/docs/brief/open-questions.md +++ b/docs/brief/open-questions.md @@ -1,4 +1,4 @@ - + ## 10. Open Questions diff --git a/docs/brief/pricing-model.md b/docs/brief/pricing-model.md index 576896f..e56672c 100644 --- a/docs/brief/pricing-model.md +++ b/docs/brief/pricing-model.md @@ -1,11 +1,11 @@ - + ## 5. Pricing Model ### Free tier Basic voice capture + local transcription + simple task list. Limited functionality (e.g. 5 active tasks or 10 stored transcriptions). Top-of-funnel — proves the core value loop. -### Kon Pro — lifetime licence +### Magnotia Pro — lifetime licence | Platform | Price | |---|---| | Desktop (Windows/macOS/Linux) | £49 | @@ -16,7 +16,7 @@ Full feature set, all running locally. Unlimited transcription, templates, profi **Positioning:** "They took away lifetime. We never will." -### Kon Cloud — optional subscription (£4.99/month or £39.99/year) +### Magnotia Cloud — optional subscription (£4.99/month or £39.99/year) Access to frontier AI model (Claude, GPT-4o, or similar) for: - Higher-accuracy transcription of specialist vocabulary - Smarter task decomposition @@ -44,7 +44,7 @@ This is the only recurring revenue stream and is genuinely tied to per-request A ### Pre-launch pricing validation (Van Westendorp) Before committing to £49, send the waitlist a four-question survey via Tally (free): -1. At what price would Kon be so expensive you'd never buy it? +1. At what price would Magnotia be so expensive you'd never buy it? 2. At what price would it seem so cheap you'd doubt its quality? 3. At what price is it getting expensive but you'd still consider it? 4. At what price is it a bargain? diff --git a/docs/brief/research-gaps.md b/docs/brief/research-gaps.md index c1503b2..06619f3 100644 --- a/docs/brief/research-gaps.md +++ b/docs/brief/research-gaps.md @@ -1,4 +1,4 @@ - + ## 20. Research Gaps Still to Investigate diff --git a/docs/brief/success-metrics.md b/docs/brief/success-metrics.md index e835ed8..16d84c8 100644 --- a/docs/brief/success-metrics.md +++ b/docs/brief/success-metrics.md @@ -1,4 +1,4 @@ - + ## 9. Success Metrics @@ -15,12 +15,12 @@ ### Neuro-inclusive product metrics -Standard SaaS metrics like Daily Active Users (DAU) or unbroken streaks must be avoided — they encourage the exact shame spiral Kon is designed to prevent. Track these instead: +Standard SaaS metrics like Daily Active Users (DAU) or unbroken streaks must be avoided — they encourage the exact shame spiral Magnotia is designed to prevent. Track these instead: | Metric | What it measures | Why it matters | |---|---|---| -| **Time-to-capture** | Seconds from app open to completed brain dump | Measures friction. If this exceeds 10 seconds, the thought is gone. The lower this number, the better Kon serves its core purpose. | -| **Grace day recovery rate** | % of users who return and complete a task after 1+ days of inactivity | Proves Kon has beaten the abandon-shame cycle. This is the single most important product metric. If users come back after missing days without guilt, the design is working. | +| **Time-to-capture** | Seconds from app open to completed brain dump | Measures friction. If this exceeds 10 seconds, the thought is gone. The lower this number, the better Magnotia serves its core purpose. | +| **Grace day recovery rate** | % of users who return and complete a task after 1+ days of inactivity | Proves Magnotia has beaten the abandon-shame cycle. This is the single most important product metric. If users come back after missing days without guilt, the design is working. | | **Micro-step completion rate** | Completion rate of AI-decomposed tasks vs. manually entered abstract tasks | Validates that micro-stepping actually works. If AI-generated steps have higher completion rates than user-entered tasks, the feature is earning its keep. | | **Brain dump → task conversion** | % of voice transcription content that converts into actionable tasks | Measures AI quality. Low conversion means the AI isn't parsing well; high conversion means the core loop works. | | **Return after lapse** | Median days between last session and next session for users who go inactive | Measures stickiness without punishing breaks. A user who returns after 2 weeks is a success, not a failure. | diff --git a/docs/brief/target-audience.md b/docs/brief/target-audience.md index 49f7665..f7b41a1 100644 --- a/docs/brief/target-audience.md +++ b/docs/brief/target-audience.md @@ -1,4 +1,4 @@ - + ## 2. Target Audience diff --git a/docs/brief/tech-stack.md b/docs/brief/tech-stack.md index 1fd1f56..56dcacb 100644 --- a/docs/brief/tech-stack.md +++ b/docs/brief/tech-stack.md @@ -1,4 +1,4 @@ - + ## 3. Tech Stack @@ -25,7 +25,7 @@ | Optimal | 32GB | Llama 3.3 8B | Q5_K_M | ~5.5GB | 10–20 tok/s | | Mobile | 4–6GB | Llama 3.2 1B | Q4_K_M | ~0.8GB | 30–50 tok/s | -- **Benchmarks:** Ryzen 5700G (DDR4) achieves ~11 tok/s on 7B Q4_K_M. Apple M3 base achieves ~26 tok/s. For Kon's use case (50–200 token responses for task decomposition), 10–15 tok/s is perfectly usable (1–10 seconds per response). +- **Benchmarks:** Ryzen 5700G (DDR4) achieves ~11 tok/s on 7B Q4_K_M. Apple M3 base achieves ~26 tok/s. For Magnotia's use case (50–200 token responses for task decomposition), 10–15 tok/s is perfectly usable (1–10 seconds per response). - **Minimum published spec:** 8GB RAM, any CPU from 2020+. Below 8GB is not supported. ### Local RAG pipeline @@ -46,7 +46,7 @@ ### Cross-device sync (post-MVP) - **CRDT layer:** cr-sqlite (vlcn.io, ~3,500 GitHub stars, core Rust). Operates at the SQL level — `SELECT crsql_as_crr('tasks')` converts any table to a Conflict-free Replicated Relation. Normal SQL continues working. Metadata overhead: ~50–100 bytes per modified cell. - **Networking:** iroh (n0-computer/iroh, ~7,900 GitHub stars, pure Rust, v0.96+). Dials peers by Ed25519 public key. Auto-selects best path: direct QUIC on LAN, NAT hole-punching on WAN, or encrypted relay fallback. QUIC with TLS 1.3. Relays are zero-knowledge. -- **Local discovery:** mdns-sd crate v0.13.11. Registers `_kon-sync._tcp.local.` via multicast DNS. +- **Local discovery:** mdns-sd crate v0.13.11. Registers `_magnotia-sync._tcp.local.` via multicast DNS. - **Device pairing:** QR code + Noise XX handshake (snow crate v0.9.x) with OTP pre-shared key. No server required. - **Relay fallback:** Self-host with `cargo install iroh-relay` on a £4/month VPS. n0 also operates free public relays (rate-limited). - **Conflict resolution:** Last-Writer-Wins per field (highest lamport timestamp, site_id tiebreaker). Edits to different fields merge cleanly. Extended offline: changeset size proportional to number of changes, not duration. diff --git a/docs/brief/technology-map.md b/docs/brief/technology-map.md index 6c98a39..ebe446b 100644 --- a/docs/brief/technology-map.md +++ b/docs/brief/technology-map.md @@ -1,9 +1,9 @@ -# Building Kon: a complete technology map for local-first, voice-first desktop AI +# Building Magnotia: a complete technology map for local-first, voice-first desktop AI -**Kon's entire stack -- from audio capture through LLM inference to neurodivergent-friendly UI -- can be built from actively maintained, production-tested open-source components.** The Rust + Tauri v2 + Svelte 5 ecosystem has matured dramatically through 2024-2026, with reference applications like Handy (13.8k stars, Tauri + Whisper + real-time audio) and Whispering (Svelte 5 + Tauri transcription) proving the core architecture viable. The most critical finding: **no existing app combines all of Kon's pieces**, making this a genuinely novel integration -- but every individual subsystem has battle-tested implementations to learn from. +**Magnotia's entire stack -- from audio capture through LLM inference to neurodivergent-friendly UI -- can be built from actively maintained, production-tested open-source components.** The Rust + Tauri v2 + Svelte 5 ecosystem has matured dramatically through 2024-2026, with reference applications like Handy (13.8k stars, Tauri + Whisper + real-time audio) and Whispering (Svelte 5 + Tauri transcription) proving the core architecture viable. The most critical finding: **no existing app combines all of Magnotia's pieces**, making this a genuinely novel integration -- but every individual subsystem has battle-tested implementations to learn from. **Ingested from:** `input/inbox/backlinksforfree` on 2026/03/20 -**Used in:** `docs/superpowers/specs/2026-03-20-kon-mvp-design.md` +**Used in:** `docs/superpowers/specs/2026-03-20-magnotia-mvp-design.md` --- @@ -43,7 +43,7 @@ Model lifecycle: load at first inference, keep during session, unload on backgro Hybrid search: FTS5 + sqlite-vec with **Reciprocal Rank Fusion** (documented by Alex Garcia). <3ms total retrieval on Raspberry Pi Zero 2 W. -**No published project combines sqlite-vec + fastembed-rs** -- Kon's implementation is novel. +**No published project combines sqlite-vec + fastembed-rs** -- Magnotia's implementation is novel. ### 5. Time-block visualisation diff --git a/docs/brief/tiimo-competitive-intel.md b/docs/brief/tiimo-competitive-intel.md index d03609b..93283dc 100644 --- a/docs/brief/tiimo-competitive-intel.md +++ b/docs/brief/tiimo-competitive-intel.md @@ -1,10 +1,10 @@ # Tiimo Competitive Intelligence Report (2026) -## Executive Summary: Kon's Key Advantages -Based on current intelligence, **Kon** has several immediate strategic openings against Tiimo: -1. **The "Lifetime" Opening:** Tiimo recently removed their highly popular lifetime license, causing massive frustration in the neurodivergent community (who often struggle with recurring subscriptions). Kon can win significant goodwill by offering a clear, sustainable lifetime tier or a radically different neuro-friendly pricing model. -2. **The Android/Platform Gap:** In September 2025, Tiimo completely removed its Android app, leaving a massive portion of the market unserved. They also lack a true native desktop application (relying on a web wrapper). Kon's native desktop-first approach fills a vital gap for users who need deep workflow integration rather than just a mobile companion. -3. **The Complexity Friction:** While Tiimo's AI Co-planner is popular, users report a steep learning curve and heavy setup time. Kon's voice-transcription premise—allowing users to simply speak to create structure—offers a dramatically lower barrier to entry for users with executive dysfunction. +## Executive Summary: Magnotia's Key Advantages +Based on current intelligence, **Magnotia** has several immediate strategic openings against Tiimo: +1. **The "Lifetime" Opening:** Tiimo recently removed their highly popular lifetime license, causing massive frustration in the neurodivergent community (who often struggle with recurring subscriptions). Magnotia can win significant goodwill by offering a clear, sustainable lifetime tier or a radically different neuro-friendly pricing model. +2. **The Android/Platform Gap:** In September 2025, Tiimo completely removed its Android app, leaving a massive portion of the market unserved. They also lack a true native desktop application (relying on a web wrapper). Magnotia's native desktop-first approach fills a vital gap for users who need deep workflow integration rather than just a mobile companion. +3. **The Complexity Friction:** While Tiimo's AI Co-planner is popular, users report a steep learning curve and heavy setup time. Magnotia's voice-transcription premise—allowing users to simply speak to create structure—offers a dramatically lower barrier to entry for users with executive dysfunction. 4. **B2B / Teams Vacuum:** Tiimo has virtually no enterprise or team-based pricing, focusing entirely on solo consumers (and a 5-person "family" sharing plan). This leaves the B2B neurodiversity-inclusion workspace wide open. --- diff --git a/docs/brief/user-sentiment.md b/docs/brief/user-sentiment.md index 6eb630a..784fc84 100644 --- a/docs/brief/user-sentiment.md +++ b/docs/brief/user-sentiment.md @@ -1,4 +1,4 @@ - + ## 12. Live User Sentiment — What Neurodivergent Users Actually Say @@ -17,7 +17,7 @@ The dominant emotional narrative across every neurodivergent community: download ### Emotional intensity Language consistently involves shame ("another thing I'm failing at"), resignation ("I've lost count"), and liberation when users find the right framing ("I wasn't broken — I was working with tools designed for someone else's operating system"). Anger directed specifically at subscription billing: one Effecto review reads "Pretty ironic that it's an app supposed to be ADHD-friendly yet charges you for a service you don't use." A Wisey Trustpilot review states: "They are unscrupulous and taking advantage of people with ADHD who may be less organised." -### Demand signals for Kon's specific features +### Demand signals for Magnotia's specific features - **Voice-first capture** receives consistent praise wherever it appears — one user who deleted 47 apps kept a voice memo tool as one of three survivors. - **Offline/local-first** positioning is an emerging differentiator; community responds positively to "your data stays with you." - **One-time purchase preference** is acute: a Goblin Tools App Store reviewer wrote "The fact it isn't subscription-based is incredibly helpful — I know it's mine and can use it whenever I need, without having to worry about whether it's 'worth it' each month or if I'm going to forget to cancel." diff --git a/docs/brief/what-kon-is.md b/docs/brief/what-magnotia-is.md similarity index 64% rename from docs/brief/what-kon-is.md rename to docs/brief/what-magnotia-is.md index eb52a7b..4731401 100644 --- a/docs/brief/what-kon-is.md +++ b/docs/brief/what-magnotia-is.md @@ -1,7 +1,7 @@ - + -## 1. What Kon Is +## 1. What Magnotia Is -A voice-first productivity app for people with executive dysfunction, neurodivergence, and task paralysis. Users brain dump via voice, Kon transcribes locally using AI, and automatically organises thoughts into actionable task lists. +A voice-first productivity app for people with executive dysfunction, neurodivergence, and task paralysis. Users brain dump via voice, Magnotia transcribes locally using AI, and automatically organises thoughts into actionable task lists. **Core thesis:** Capture thoughts the instant they appear, with zero friction, zero latency, and total privacy. Everything runs on-device. No cloud dependency, no subscriptions for core features, no data leaves the user's machine. diff --git a/docs/brief/why-current-tools-fail.md b/docs/brief/why-current-tools-fail.md index c42fda2..3a9a133 100644 --- a/docs/brief/why-current-tools-fail.md +++ b/docs/brief/why-current-tools-fail.md @@ -1,4 +1,4 @@ - + ## 14. Why Current Tools Fail diff --git a/docs/code-review-2026-04-22.md b/docs/code-review-2026-04-22.md index c26a0ea..1e1d72d 100644 --- a/docs/code-review-2026-04-22.md +++ b/docs/code-review-2026-04-22.md @@ -1,14 +1,14 @@ --- name: Code Review — 2026/04/22 -description: Full-sweep audit findings across all Kon crates + src-tauri, with triage buckets for quick wins vs release-blockers +description: Full-sweep audit findings across all Magnotia crates + src-tauri, with triage buckets for quick wins vs release-blockers type: reference -tags: [code-review, audit, bugs, kon, release-blockers] +tags: [code-review, audit, bugs, magnotia, release-blockers] date: 2026/04/22 --- -# Kon Code Review — 2026/04/22 +# Magnotia Code Review — 2026/04/22 -Full-sweep read-only audit of every `.rs` file across the Kon workspace. Four parallel Codex agents scanned: +Full-sweep read-only audit of every `.rs` file across the Magnotia workspace. Four parallel Codex agents scanned: - **Agent A** — `crates/transcription/`, `crates/audio/` - **Agent B** — `crates/ai-formatting/`, `crates/llm/`, `crates/storage/` - **Agent C** — `src-tauri/src/` (commands layer + lib.rs + main.rs + types.rs) diff --git a/docs/dev-setup.md b/docs/dev-setup.md index 2a96396..b566939 100644 --- a/docs/dev-setup.md +++ b/docs/dev-setup.md @@ -2,10 +2,10 @@ name: dev-setup type: reference tags: [setup, dependencies, build, linux, fedora] -description: Authoritative build dependencies and launch instructions for Kon on Fedora Linux +description: Authoritative build dependencies and launch instructions for Magnotia on Fedora Linux --- -# Kon — Developer Setup +# Magnotia — Developer Setup Last updated: 2026/04/18. Primary dev target: Fedora 43, x86_64, KDE Wayland, NVIDIA RTX 4070. @@ -67,7 +67,7 @@ Rust toolchain managed by `rustup`. No extra steps needed beyond what Tauri requ ### CPU build (default) ```bash -cd /home/jake/Documents/CORBEL-Projects/kon +cd /home/jake/Documents/CORBEL-Projects/magnotia LIBCLANG_PATH=/usr/lib64/llvm21/lib64 npm run tauri dev ``` diff --git a/docs/gpu-tuning/plan.md b/docs/gpu-tuning/plan.md index 22369a4..cd8c5f8 100644 --- a/docs/gpu-tuning/plan.md +++ b/docs/gpu-tuning/plan.md @@ -1,4 +1,4 @@ -# Kon — GPU Tuning & Community Config Plan +# Magnotia — GPU Tuning & Community Config Plan *Implementation spec for the first three phases of the GPU kernel tuning roadmap. The full five-phase roadmap is pinned in memory; this document scopes the MVP subset that ships real value without pulling in `ggml`-dedup or agentic-search prerequisites.* @@ -7,8 +7,8 @@ **IN** (this document): - Phase 1 — Advanced GPU tuning settings panel (exposing GGML env vars) -- Phase 2 — `kon-bench` local autotuning CLI -- Phase 3-lite — `kon-configs` community repo with manual-PR workflow (no CI replay) +- Phase 2 — `magnotia-bench` local autotuning CLI +- Phase 3-lite — `magnotia-configs` community repo with manual-PR workflow (no CI replay) **OUT** (pinned to memory for later): @@ -36,13 +36,13 @@ This subset captures roughly 85% of the perceived value for ~20% of the total ef | Disable integer dot product | `GGML_VK_DISABLE_INTEGER_DOT_PRODUCT` | off | "Random NaN" on RDNA2 with certain drivers | | Enable Vulkan validation | `GGML_VK_VALIDATE` | off | Diagnostic only; impacts performance | -Metal / CUDA counterparts slot in when those backends grow in Kon. Today Kon is Vulkan-only. +Metal / CUDA counterparts slot in when those backends grow in Magnotia. Today Magnotia is Vulkan-only. ### Design - New `SettingsState.gpuTuning: { disableCoopmat: boolean, forceFp32: boolean, disableF16: boolean, disableIntegerDotProduct: boolean, enableValidation: boolean }` in [src/lib/types/app.ts](../../src/lib/types/app.ts) - All defaults `false` in [src/lib/stores/page.svelte.ts](../../src/lib/stores/page.svelte.ts) -- Persistence uses the existing `save_preferences` → SQLite `kon_preferences` path +- Persistence uses the existing `save_preferences` → SQLite `magnotia_preferences` path - Backend reads preferences at the **very top** of `run()` in [src-tauri/src/lib.rs](../../src-tauri/src/lib.rs) — before `tauri::Builder::default()` spawns threads — and writes via `unsafe { std::env::set_var(...) }`. Matches the existing `ensure_x11_on_wayland` pattern - Settings UI shows a sticky "Restart required for changes to take effect" banner when any toggle has drifted from its launch-time value - A "Reset to defaults" button zeroes all toggles @@ -55,18 +55,18 @@ Metal / CUDA counterparts slot in when those backends grow in Kon. Today Kon is --- -## Phase 2 — `kon-bench` local autotuning CLI +## Phase 2 — `magnotia-bench` local autotuning CLI **Effort**: 3–5 days. -**What ships**: New workspace binary `crates/bench/` producing a `kon-bench` executable. User runs it once post-install; output lands at `~/.kon/gpu-profile.toml` with the best-scoring config for their hardware. Settings page gets an "Apply auto-tuned profile" button that consumes the TOML and updates the Phase 1 toggles. +**What ships**: New workspace binary `crates/bench/` producing a `magnotia-bench` executable. User runs it once post-install; output lands at `~/.magnotia/gpu-profile.toml` with the best-scoring config for their hardware. Settings page gets an "Apply auto-tuned profile" button that consumes the TOML and updates the Phase 1 toggles. ### CLI surface ``` -kon-bench --quick # bundled 20s sample + reference transcript -kon-bench --model --audio --transcript -kon-bench --compare # benchmark a specific profile vs default +magnotia-bench --quick # bundled 20s sample + reference transcript +magnotia-bench --model --audio --transcript +magnotia-bench --compare # benchmark a specific profile vs default ``` ### Execution model @@ -102,7 +102,7 @@ A 20-second public-domain speech clip with a known-good reference transcript, co ```toml [benchmarked_at] timestamp = "2026-04-21T14:32:00Z" -kon_version = "0.1.0" +magnotia_version = "0.1.0" model = "whisper-distil-large-v3" [hardware] @@ -137,37 +137,37 @@ crates/bench/ │ └── librivox-sample.txt └── src/ ├── main.rs # CLI + parent process - ├── runner.rs # subprocess harness (child entry gate: KON_BENCH_RUN=1) + ├── runner.rs # subprocess harness (child entry gate: MAGNOTIA_BENCH_RUN=1) ├── matrix.rs # grid-search + top-k logic ├── metrics.rs # RTF + WER + optional VRAM sampling └── profile.rs # TOML serialise ``` -Depends on `kon-transcription` + `kon-llm` + `kon-audio` as path deps so it reuses the existing model-loading code. +Depends on `magnotia-transcription` + `magnotia-llm` + `magnotia-audio` as path deps so it reuses the existing model-loading code. ### Acceptance -- `kon-bench --quick` runs unattended to completion on a fresh install +- `magnotia-bench --quick` runs unattended to completion on a fresh install - Produces a valid `gpu-profile.toml` - "Apply auto-tuned" button in Settings consumes the TOML and updates Phase 1 toggles (restart banner fires as expected) - Re-running with `--compare ` produces reproducible-enough numbers (RTF within 5% run-to-run) --- -## Phase 3-lite — `kon-configs` community repo +## Phase 3-lite — `magnotia-configs` community repo -**Effort**: 3 days (1 for repo + seeds, 2 for Kon-side fetch + apply UI). +**Effort**: 3 days (1 for repo + seeds, 2 for Magnotia-side fetch + apply UI). -**What ships**: A separate public GitHub repo `kon-configs` (not part of the kon main repo) seeded with 2–3 curated configs. Kon's Settings page gets a "Browse community configs" button that fetches matching configs for the user's detected hardware. +**What ships**: A separate public GitHub repo `magnotia-configs` (not part of the magnotia main repo) seeded with 2–3 curated configs. Magnotia's Settings page gets a "Browse community configs" button that fetches matching configs for the user's detected hardware. ### Repo structure ``` -kon-configs/ +magnotia-configs/ ├── README.md # pitch + how to benefit ├── CONTRIBUTING.md # required fields, benchmark protocol, fork/PR flow ├── SCHEMA.md # TOML schema documentation -├── index.json # manifest for Kon to discover configs +├── index.json # manifest for Magnotia to discover configs └── configs/ ├── nvidia/ │ ├── rtx-3060-12gb-linux.toml @@ -190,15 +190,15 @@ notes = "Tested with 1-hour continuous dictation session, no crashes." ### Contribution flow (manual, honour-system MVP) -1. User runs `kon-bench` on their hardware. -2. User runs `kon-bench --compare` against baseline to confirm improvement isn't noise. -3. User forks `kon-configs`, commits their TOML under `configs//`, opens PR. +1. User runs `magnotia-bench` on their hardware. +2. User runs `magnotia-bench --compare` against baseline to confirm improvement isn't noise. +3. User forks `magnotia-configs`, commits their TOML under `configs//`, opens PR. 4. Maintainer reviews format + plausibility, merges. 5. No CI replay — revisit if spam becomes a problem. -### Kon integration +### Magnotia integration -- New Tauri command `fetch_community_configs(gpu_fingerprint)` — HTTPS GET `https://raw.githubusercontent.com//kon-configs/main/index.json` for the manifest, then fetches matching TOMLs +- New Tauri command `fetch_community_configs(gpu_fingerprint)` — HTTPS GET `https://raw.githubusercontent.com//magnotia-configs/main/index.json` for the manifest, then fetches matching TOMLs - Fingerprint match: GPU name substring + VRAM tier (e.g., `"RTX 3060"` + `"12gb"`) - Settings "Browse community configs" button lists matches with submitter, claimed RTF improvement, and a preview of the toggle deltas - Applying a config updates Phase 1 toggles AND stores provenance (source = `"community"`, submitter, fetch date) @@ -206,13 +206,13 @@ notes = "Tested with 1-hour continuous dictation session, no crashes." ### What we explicitly skip at MVP - **No CI replay**. Maintainer eyeballs + honour system. Revisit past ~50 configs or on abuse. -- **No automated upload from `kon-bench`**. User always commits + PRs manually. Zero privacy concerns, zero spam surface. +- **No automated upload from `magnotia-bench`**. User always commits + PRs manually. Zero privacy concerns, zero spam surface. - **No sophisticated fingerprint normalisation**. Substring matching is sufficient. ### Acceptance - Repo exists with README + CONTRIBUTING + 2–3 seed configs -- Kon Settings fetches + lists + applies a community config end-to-end +- Magnotia Settings fetches + lists + applies a community config end-to-end - "Revert to default" path works (Phase 1's reset) --- @@ -223,7 +223,7 @@ This is the UX the three phases together enable. All three are prerequisites; Ph ### First-launch onboarding nudge -After the existing first-run model download, Kon surfaces a non-modal card: +After the existing first-run model download, Magnotia surfaces a non-modal card: ``` 🎛 GPU Optimisation @@ -241,7 +241,7 @@ Two steps, in this order: **Step 1 — Community config check (instant, ~2 s)** -Kon fingerprints the GPU and queries the `kon-configs` manifest for matches. If a match exists, a preview card appears: +Magnotia fingerprints the GPU and queries the `magnotia-configs` manifest for matches. If a match exists, a preview card appears: ``` ┌─────────────────────────────────────────────┐ @@ -271,13 +271,13 @@ If no community match, or the user prefers their own measurement: │ │ │ We can benchmark your machine to find the │ │ best settings. Takes ~8 minutes; runs in │ -│ the background while you keep using Kon. │ +│ the background while you keep using Magnotia. │ │ │ │ [ Benchmark my GPU ] [ Skip ] │ └─────────────────────────────────────────────┘ ``` -Kicks off `kon-bench` as a background process. Kon keeps working during the run. +Kicks off `magnotia-bench` as a background process. Magnotia keeps working during the run. ### Progress UI during benchmark @@ -307,7 +307,7 @@ Plus a "Revert to previous config" button, active for 7 days after any change, i ### Optional — sharing back to the community -After a successful local benchmark that shows meaningful gains, Kon prompts: +After a successful local benchmark that shows meaningful gains, Magnotia prompts: ``` ┌─────────────────────────────────────────────┐ @@ -325,7 +325,7 @@ After a successful local benchmark that shows meaningful gains, Kon prompts: └─────────────────────────────────────────────┘ ``` -"Create PR" opens the user's browser to `github.com/…/kon-configs/new/main` with the TOML prefilled in the PR body. User finishes the submission on GitHub (still honour-system; no automated uploads, no telemetry). +"Create PR" opens the user's browser to `github.com/…/magnotia-configs/new/main` with the TOML prefilled in the PR body. User finishes the submission on GitHub (still honour-system; no automated uploads, no telemetry). ### Non-GPU / integrated-only fallback @@ -333,7 +333,7 @@ If `sysinfo` reports no dedicated GPU or Vulkan isn't available, the card replac ``` 🎛 GPU Optimisation -No dedicated GPU detected — Kon is using CPU inference. +No dedicated GPU detected — Magnotia is using CPU inference. GPU tuning doesn't apply to this setup. ``` @@ -345,7 +345,7 @@ For users whose GPU has a community-contributed config, the experience is **lite For users without a community match, the experience is **two clicks** (trigger bench → apply results on completion), with a passive ~8-minute background wait in between. -For users on integrated graphics / no GPU, the experience is **zero clicks** — Kon tells them GPU tuning doesn't apply and moves on. +For users on integrated graphics / no GPU, the experience is **zero clicks** — Magnotia tells them GPU tuning doesn't apply and moves on. --- @@ -353,7 +353,7 @@ For users on integrated graphics / no GPU, the experience is **zero clicks** — Strict linear: Phase 1 → Phase 2 → Phase 3-lite. Each phase merges to `main` and gets dogfooded before the next starts. -- Phase 1 is a prereq for Phase 2 — `kon-bench`'s output needs the Phase 1 settings schema to be its consumption target. +- Phase 1 is a prereq for Phase 2 — `magnotia-bench`'s output needs the Phase 1 settings schema to be its consumption target. - Phase 2 is a prereq for Phase 3-lite — the community repo's config TOML schema **is** Phase 2's output schema (with an added `[attribution]` section). ## Shelved with rationale @@ -361,4 +361,4 @@ Strict linear: Phase 1 → Phase 2 → Phase 3-lite. Each phase merges to `main` - **Phase 4 — custom SPIR-V shader drops.** Blocked on `ggml`-dedup workstream. Pinned in memory. - **Phase 5 — agentic (Karpathy-style) autotune.** Phase 2's grid search produces schema-compatible results, so Phase 5 can drop in later without a schema break. Pinned. - **Phase 3's CI replay.** Defer until spam / bad-config abuse is a real problem rather than a hypothetical one. Honour-system PR review is sufficient for the MVP community. -- **`kon-bench` automated upload.** Deliberately manual for MVP — removes all privacy / spam / rate-limiting concerns. Revisit when the community volume justifies the infrastructure. +- **`magnotia-bench` automated upload.** Deliberately manual for MVP — removes all privacy / spam / rate-limiting concerns. Revisit when the community volume justifies the infrastructure. diff --git a/docs/issues/README.md b/docs/issues/README.md index 0cfecbf..bf414a9 100644 --- a/docs/issues/README.md +++ b/docs/issues/README.md @@ -7,9 +7,9 @@ tags: [issues, release-blockers] # Release-blockers -Issues here must land before Kon v0.1 ships. Each is sourced from +Issues here must land before Magnotia v0.1 ships. Each is sourced from `docs/code-review-2026-04-22.md`. When `gh` CLI is available, these -should be mirrored as real GitHub issues on `jakejars/kon`. +should be mirrored as real GitHub issues on `jakejars/magnotia`. ## CRITICAL (0 open, 3 resolved) @@ -28,13 +28,13 @@ No open CRITICAL blockers. | RB-01 | [c1-live-session-race.md](c1-live-session-race.md) | `src-tauri/commands/live.rs` | Added `LiveTranscriptionState.lifecycle: tokio::sync::Mutex<()>` and hold it across the async spans of both `start_live_transcription_session` and `stop_live_transcription_session`. The running-slot check/insert and stop/take/join sequence are now serialized, so concurrent starts can no longer both pass the empty-slot check and a start during stop blocks until the previous worker fully joins. Two async regression tests cover both races. | | RB-02 | [c3-migrations-atomicity.md](c3-migrations-atomicity.md) | `crates/storage/migrations.rs` | Each migration now runs inside a `pool.begin()` / `tx.commit()` transaction alongside its `schema_version` insert. Regression test injects a poisoned v9 migration and asserts neither the partial schema change nor the version row persists. DRY'd `run_migrations_up_to` test helper onto the same code path. | | RB-03 | [c4-transcript-profile-fk.md](c4-transcript-profile-fk.md) | `crates/storage/migrations.rs` + `database.rs` | Added a transactional v9 rebuild of `transcripts` that enforces `profile_id REFERENCES profiles(id) ON DELETE RESTRICT`, reassigns any orphaned transcript provenance to `DEFAULT_PROFILE_ID`, rebuilds dependent `segments` / FTS state, and preserves valid profile references. `insert_transcript` now rejects unknown profile ids up front, and `delete_profile` returns a clear reassign-first error when transcripts still reference the profile. Regression tests cover migration reconciliation, invalid inserts, and delete rejection. | -| RB-04 | [run-live-session-monolith.md](run-live-session-monolith.md) | `src-tauri/commands/live.rs` | Replaced the 200+ line `run_live_session` loop with an explicit `LiveSessionRuntime` + `LiveLoopState` structure. Capture startup, runtime mic-error draining, audio chunk processing, overflow handling, stop-tail flush, inference dispatch/drain, and WAV finalisation each live in focused helpers, preserving behaviour while making the lifecycle auditable enough for RB-01 follow-up. Existing live tests and the full `kon` lib suite stay green. | +| RB-04 | [run-live-session-monolith.md](run-live-session-monolith.md) | `src-tauri/commands/live.rs` | Replaced the 200+ line `run_live_session` loop with an explicit `LiveSessionRuntime` + `LiveLoopState` structure. Capture startup, runtime mic-error draining, audio chunk processing, overflow handling, stop-tail flush, inference dispatch/drain, and WAV finalisation each live in focused helpers, preserving behaviour while making the lifecycle auditable enough for RB-01 follow-up. Existing live tests and the full `magnotia` lib suite stay green. | | RB-05 | [poll-inference-channel-fatality.md](poll-inference-channel-fatality.md) | `src-tauri/commands/live.rs` | `poll_inference` now treats result-channel loss as a listener-lifecycle problem rather than a transcription failure. On the first `result_channel.send(...)` error it marks the live result listener as lost, emits a single warning that transcription will continue in the background, and keeps processing later chunks without retrying the dead channel. Regression test simulates a dead result listener and asserts chunk processing continues with only one warning. | | RB-06 | [native-capture-worker-join.md](native-capture-worker-join.md) | `src-tauri/commands/audio.rs` | `NativeCaptureState.stop_tx` replaced by `worker: AsyncMutex>`. `CaptureWorker` bundles the stop sender and the spawned task's `JoinHandle`; `stop_worker(worker)` sends stop then `await`s termination. Both `start_native_capture` (prior-worker stop) and `stop_native_capture` use the helper. Removed the 50ms sleep — the join barrier is exact. Two regression tests cover the lifecycle guarantee and the already-exited case. | | RB-07 | [runtime-capabilities-accelerators.md](runtime-capabilities-accelerators.md) | `src-tauri/commands/models.rs` | Introduced `compose_accelerators(whisper_enabled, loader_available, target)` as a pure helper; `supported_accelerators()` reads `cfg(feature = "whisper")`, `vulkan_loader_available()`, and target OS then delegates. `get_runtime_capabilities` uses it in place of the hard-coded `["cpu", "vulkan"]`. Whisper's `supports_gpu` now follows the feature flag. Five regression tests cover all permutations. | | RB-09 | [decoder-partial-audio-on-error.md](decoder-partial-audio-on-error.md) | `crates/audio/decode.rs` | Packet-loop now propagates all non-EOF `SymphoniaError`s as `AudioDecodeFailed`; per-packet decode errors bubble via `?`. Mock-`MediaSource` regression test confirms mid-stream I/O errors surface instead of returning partial audio. | | RB-10 | [llm-prompt-preflight.md](llm-prompt-preflight.md) | `crates/llm/lib.rs` | Added an explicit prompt-budget preflight before context creation. If `prompt_tokens + max_tokens + reserve` exceeds the 8192-token cap, `generate` now returns a typed `EngineError::PromptTooLong { ... }` instead of failing late inside inference. Regression tests cover both the over-budget and exact-budget boundaries. | -| RB-11 | [keystore-thread-safety.md](keystore-thread-safety.md) | `crates/cloud-providers/keystore.rs` | Replaced the `std::env::set_var` stub with a process-global `OnceLock>>` keystore, keeping the API safe from any thread. Retrieval still falls back to read-only `KON_API_KEY_*` env vars for externally supplied secrets. Two regression tests cover store/retrieve and provider isolation. | +| RB-11 | [keystore-thread-safety.md](keystore-thread-safety.md) | `crates/cloud-providers/keystore.rs` | Replaced the `std::env::set_var` stub with a process-global `OnceLock>>` keystore, keeping the API safe from any thread. Retrieval still falls back to read-only `MAGNOTIA_API_KEY_*` env vars for externally supplied secrets. Two regression tests cover store/retrieve and provider isolation. | | RB-12 | [hotkey-linux-device-filter.md](hotkey-linux-device-filter.md) | `crates/hotkey/linux.rs` | Extracted `device_supports_combo` helper; `try_attach_device` now reads the configured `HotkeyCombo` from the watch channel and checks support for that trigger key. Four regression tests land in `linux::tests`. | ## Remaining blocker @@ -51,7 +51,7 @@ for file in docs/issues/rb-*.md c1-*.md c3-*.md c4-*.md run-*.md poll-*.md \ native-*.md runtime-*.md power-*.md decoder-*.md llm-*.md \ keystore-*.md hotkey-*.md set -l title (head -1 "$file" | sed 's/^# //') - gh issue create --repo jakejars/kon --title "$title" --body-file "$file" \ + gh issue create --repo jakejars/magnotia --title "$title" --body-file "$file" \ --label release-blocker end ``` diff --git a/docs/issues/c3-migrations-atomicity.md b/docs/issues/c3-migrations-atomicity.md index 427e82b..52989d7 100644 --- a/docs/issues/c3-migrations-atomicity.md +++ b/docs/issues/c3-migrations-atomicity.md @@ -27,7 +27,7 @@ call returns `Err`, and post-call `SELECT COUNT(*) FROM poison_marker` fails with "no such table" while `MAX(schema_version)` remains at 8. SQLite DDL participates in transactions, so this is sufficient for the -Kon schema. If any future migration needs a statement that implicitly +Magnotia schema. If any future migration needs a statement that implicitly commits (`VACUUM`, `REINDEX`, `ATTACH`) — none do today — it must be split into its own non-transactional migration. Reviewer's job to flag. @@ -43,7 +43,7 @@ split into its own non-transactional migration. Reviewer's job to flag. ## Fix scope -Medium. Wrap each migration in `pool.begin()` / `tx.commit()`. The version update and the migration statements all execute on the same `Transaction` handle. Needs careful review of any migration that uses implicit commits (SQLite `VACUUM`, `REINDEX`, `ATTACH` — none of which Kon currently uses, but the review pattern should guard against future additions). +Medium. Wrap each migration in `pool.begin()` / `tx.commit()`. The version update and the migration statements all execute on the same `Transaction` handle. Needs careful review of any migration that uses implicit commits (SQLite `VACUUM`, `REINDEX`, `ATTACH` — none of which Magnotia currently uses, but the review pattern should guard against future additions). ## Dependencies diff --git a/docs/issues/decoder-partial-audio-on-error.md b/docs/issues/decoder-partial-audio-on-error.md index cb3c99e..92c50e9 100644 --- a/docs/issues/decoder-partial-audio-on-error.md +++ b/docs/issues/decoder-partial-audio-on-error.md @@ -12,7 +12,7 @@ explicit end-of-stream `UnexpectedEof`: - `SymphoniaError::ResetRequired` → error (mid-stream discontinuity). -- Any other packet-read error → `KonError::AudioDecodeFailed`. +- Any other packet-read error → `MagnotiaError::AudioDecodeFailed`. - `decoder.decode(&packet)` errors → bubble via `?` instead of counter-then-skip. diff --git a/docs/issues/hotkey-linux-device-filter.md b/docs/issues/hotkey-linux-device-filter.md index cd09024..0d2d4a5 100644 --- a/docs/issues/hotkey-linux-device-filter.md +++ b/docs/issues/hotkey-linux-device-filter.md @@ -27,13 +27,13 @@ ship-gate checklist — code path is correct; runtime GUI check is deferred. `try_attach_device` claims to check whether an input device supports the configured hotkey's key, but the implementation tests for hard-coded `KEY_A` or `KEY_R` instead of consulting the actual `HotkeyCombo` that was configured. Hotkeys bound to any other key (which is most of them) can be silently skipped even when the device supports them. -This is a correctness bug in a user-facing feature. A user who binds Kon to `Ctrl+Shift+D` and sees "no hotkey fires" has no obvious path to diagnose it. +This is a correctness bug in a user-facing feature. A user who binds Magnotia to `Ctrl+Shift+D` and sees "no hotkey fires" has no obvious path to diagnose it. ## Acceptance - Device attachment consults the actual configured `HotkeyCombo.trigger` key code. - Regression test: `try_attach_device` called with a mock device that supports `KEY_D` attaches when the configured hotkey's trigger is `D`, does not attach when the trigger is a key the device doesn't support. -- Manual verification: bind `Ctrl+Shift+D` in Settings, confirm it fires in a running Kon. +- Manual verification: bind `Ctrl+Shift+D` in Settings, confirm it fires in a running Magnotia. ## Fix scope diff --git a/docs/issues/keystore-thread-safety.md b/docs/issues/keystore-thread-safety.md index f73c223..e86bb4d 100644 --- a/docs/issues/keystore-thread-safety.md +++ b/docs/issues/keystore-thread-safety.md @@ -17,7 +17,7 @@ Additional details: - Stored keys now live in-memory only for the life of the process. - `retrieve_api_key` checks the in-memory keystore first, then falls - back to read-only `KON_API_KEY_` environment variables so + back to read-only `MAGNOTIA_API_KEY_` environment variables so externally injected secrets still work. - Module docs now describe the real tradeoff clearly: safe from any thread, but non-persistent until a proper OS keychain backend lands. diff --git a/docs/issues/power-assertion-macos-objc2.md b/docs/issues/power-assertion-macos-objc2.md index acec03a..c8fd85e 100644 --- a/docs/issues/power-assertion-macos-objc2.md +++ b/docs/issues/power-assertion-macos-objc2.md @@ -13,7 +13,7 @@ passes for both `x86_64-apple-darwin` and `aarch64-apple-darwin`. Remaining acceptance gap: manual runtime verification on a real macOS machine (`pmset -g assertions`, background live session). Diagnostic reports now also include a `## Power assertions` section that lists any -currently active Kon assertion guards (`reason`, `backend`, `acquired`) +currently active Magnotia assertion guards (`reason`, `backend`, `acquired`) at report time, which gives the tester an in-app breadcrumb alongside `pmset`. @@ -25,25 +25,25 @@ The stub was deliberate (A.1 #9 acceptance concession — untestable on Linux wi ## Acceptance -- `objc2` + `objc2-foundation` deps added to the kon crate, gated `cfg(target_os = "macos")`. +- `objc2` + `objc2-foundation` deps added to the magnotia crate, gated `cfg(target_os = "macos")`. - `begin_activity` calls `[NSProcessInfo processInfo] beginActivityWithOptions:(NSActivityUserInitiated | NSActivityLatencyCritical) reason:reason]` and retains the returned activity handle. - `end_activity` calls `endActivity:` on the retained handle. -- Manual-test on a real macOS box: 10-minute background live session completes without throttling; `pmset -g assertions` shows Kon's activity during capture. +- Manual-test on a real macOS box: 10-minute background live session completes without throttling; `pmset -g assertions` shows Magnotia's activity during capture. ## Manual verification checklist -1. Launch Kon on a real macOS machine and start a live transcription session. +1. Launch Magnotia on a real macOS machine and start a live transcription session. 2. While capture is running, background the app for at least several minutes. -3. In Terminal, run `pmset -g assertions` and confirm Kon appears with a +3. In Terminal, run `pmset -g assertions` and confirm Magnotia appears with a user-initiated / no-idle-style assertion while the session is active. -4. While the session is still running, generate a Kon diagnostic report +4. While the session is still running, generate a Magnotia diagnostic report and confirm the `## Power assertions` section lists an active entry - such as `reason=kon live dictation session`, `backend=macos`, + such as `reason=magnotia live dictation session`, `backend=macos`, `acquired=true`. 5. Stop the session and rerun `pmset -g assertions` or regenerate the diagnostic report to confirm the assertion disappears. 6. Repeat once for the LLM cleanup path if desired - (`reason=kon LLM cleanup`). + (`reason=magnotia LLM cleanup`). ## Fix scope diff --git a/docs/issues/runtime-capabilities-accelerators.md b/docs/issues/runtime-capabilities-accelerators.md index c24727a..4d63c31 100644 --- a/docs/issues/runtime-capabilities-accelerators.md +++ b/docs/issues/runtime-capabilities-accelerators.md @@ -29,9 +29,9 @@ Five regression tests in `commands::models::tests` cover: - `non_macos_with_loader_advertises_vulkan` - `cpu_is_always_first_entry` (contract the frontend relies on) -Both `cargo test -p kon --lib` and `cargo test -p kon --lib ---no-default-features` pass the new suite; both `cargo build -p kon` -and `cargo build -p kon --no-default-features` compile clean. +Both `cargo test -p magnotia --lib` and `cargo test -p magnotia --lib +--no-default-features` pass the new suite; both `cargo build -p magnotia` +and `cargo build -p magnotia --no-default-features` compile clean. Runtime GUI verification on a real macOS box is still on the ship-gate checklist — the detection logic is correct in code; Metal-loader diff --git a/docs/roadmap/2026-04-23-corbie-feature-complete-roadmap.md b/docs/roadmap/2026-04-23-magnotia-feature-complete-roadmap.md similarity index 81% rename from docs/roadmap/2026-04-23-corbie-feature-complete-roadmap.md rename to docs/roadmap/2026-04-23-magnotia-feature-complete-roadmap.md index 2efb0e3..21083ec 100644 --- a/docs/roadmap/2026-04-23-corbie-feature-complete-roadmap.md +++ b/docs/roadmap/2026-04-23-magnotia-feature-complete-roadmap.md @@ -1,18 +1,18 @@ --- -name: Corbie — feature-complete roadmap +name: Magnotia — feature-complete roadmap description: Build plan from 2026-04-23 baseline to full feature-complete v0.1 release type: roadmap -tags: [roadmap, planning, corbie, release] +tags: [roadmap, planning, magnotia, release] created: 2026/04/23 status: active author: Wren (CORBEL's resident agent) on behalf of Jake Sames --- -# Corbie — Feature-Complete Roadmap +# Magnotia — Feature-Complete Roadmap -> **What Corbie is.** A local-first, cognitive-load-aware dictation + task-capture desktop app. Vulkan-accelerated Whisper / Parakeet speech-to-text, a local LLM (Qwen3 tiers) for transcript cleanup and task extraction, an MCP server for integration with Claude Desktop / Cline / Cursor, and a UI designed around ADHD / executive-dysfunction needs. Tauri 2 + Svelte 5 + Rust. Zero telemetry. +> **What Magnotia is.** A local-first, cognitive-load-aware dictation + task-capture desktop app. Vulkan-accelerated Whisper / Parakeet speech-to-text, a local LLM (Qwen3 tiers) for transcript cleanup and task extraction, an MCP server for integration with Claude Desktop / Cline / Cursor, and a UI designed around ADHD / executive-dysfunction needs. Tauri 2 + Svelte 5 + Rust. Zero telemetry. > -> **Formerly known as Kon.** Rebrand in flight; repo names at `jakejars/kon` + `git.corbel.consulting/jake/kon` still carry the Kon name and will rename together with the codebase sweep in the final phase. +> **Formerly known as Magnotia.** Rebrand in flight; repo names at `jakejars/magnotia` + `git.corbel.consulting/jake/magnotia` still carry the Magnotia name and will rename together with the codebase sweep in the final phase. ## Baseline — where we are (2026/04/23) @@ -32,19 +32,19 @@ MicroSteps is shipped; its "just-start" timer button emits an event that current > **Layer 1 first** (per Jake's standing rule): build the features roughly, in series, through Phase 1 – Phase 8. Do polish passes in Phase 9 and QC + release in Phase 10. **Do not mix make-it-work and make-it-neat passes** — every phase ships end-to-end (event wired, UI rendered, store state committed, tests updated) but does not chase aesthetic polish until Phase 9. -> **Ordering rationale.** Phase 1 closes the Core MVP gap and unblocks the already-half-wired just-start timer. Phase 2 enables model improvement by collecting human feedback — useful even while later phases are being built. Phases 3–8 build the differentiating ADHD-specific features from highest-utility-per-effort to lowest. Phase 9 is polish debt. Phase 10 is release prep (including Rachmann's RB-08 verification and the Kon → Corbie codebase rename). +> **Ordering rationale.** Phase 1 closes the Core MVP gap and unblocks the already-half-wired just-start timer. Phase 2 enables model improvement by collecting human feedback — useful even while later phases are being built. Phases 3–8 build the differentiating ADHD-specific features from highest-utility-per-effort to lowest. Phase 9 is polish debt. Phase 10 is release prep (including Rachmann's RB-08 verification and the Magnotia → Magnotia codebase rename). --- ## Phase 1 — Visual countdown + Just-Start timer -**Why now.** Closes the one remaining Core MVP gap. Unblocks the dangling `kon:start-timer` emit from MicroSteps. Directly combats time blindness, which the brief names as the single biggest lever for the target audience. +**Why now.** Closes the one remaining Core MVP gap. Unblocks the dangling `magnotia:start-timer` emit from MicroSteps. Directly combats time blindness, which the brief names as the single biggest lever for the target audience. **Scope.** - `FocusTimer.svelte` — a progress-ring countdown component. Shrinking colour ring (not digital). Subtle colour shift across the last 15%. Remaining time label inside the ring for users who want the number; small enough that the ring dominates the visual field. - `focusTimer.svelte.ts` store — single active timer, running / paused / completed state, elapsed / remaining computed, event dispatch on state transitions. - Mount in `+layout.svelte` so the timer persists across page navigation (dictation → tasks → settings). -- Listener for `window` event `kon:start-timer` with `{ durationSeconds, label }` payload. +- Listener for `window` event `magnotia:start-timer` with `{ durationSeconds, label }` payload. - Trigger from MicroSteps row (already exists). Trigger from task-row context button (new). - Floating position top-right of main content, above everything, not intrusive when idle (hidden until a timer is running). - On completion: gentle chime + 3-second ring flourish + OS notification via `tauri-plugin-notification`. No modal. No guilt copy. @@ -60,7 +60,7 @@ MicroSteps is shipped; its "just-start" timer button emits an event that current ## Phase 2 — Human-in-the-loop feedback -**Why here.** AI output quality is the single biggest determinant of whether Corbie feels useful. Thumbs-up / thumbs-down on AI-generated micro-steps and task extractions costs almost nothing to add and gives us a feedback corpus the moment anyone uses it. Enables later retraining / prompt tuning. +**Why here.** AI output quality is the single biggest determinant of whether Magnotia feels useful. Thumbs-up / thumbs-down on AI-generated micro-steps and task extractions costs almost nothing to add and gives us a feedback corpus the moment anyone uses it. Enables later retraining / prompt tuning. **Scope.** - Thumbs-up / thumbs-down buttons on every AI-generated item (micro-step row, extracted task, cleanup paragraph). @@ -141,9 +141,9 @@ MicroSteps is shipped; its "just-start" timer button emits an event that current **Revised architecture — "nudge bus" hybrid.** Earlier drafts proposed a Rust-side OS-activity watcher (keyboard, active window). Cross-platform review flagged this as fragile: Wayland offers no sanctioned global-keyboard API, macOS needs accessibility permission, Windows needs a message-loop hook, and the signal is low quality everywhere. Deferred to post-v0.1. -Phase 6 instead ships a **frontend-owned nudge bus** that consumes signals Corbie already produces: +Phase 6 instead ships a **frontend-owned nudge bus** that consumes signals Magnotia already produces: - Focus-timer state (running / completed / cancelled) — already on `window` events from Phase 1. -- Task-completed events — adds a `kon:task-completed` window event dispatched when `complete_task_cmd` / `complete_subtask_cmd` resolve. +- Task-completed events — adds a `magnotia:task-completed` window event dispatched when `complete_task_cmd` / `complete_subtask_cmd` resolve. - Micro-step generation — event from the decompose path. - Ritual state — `ritualsMorning`, `lastTriageDate` from settings/storage. - App focus / visibility — `document.visibilitychange` + Tauri window `focus`/`blur` events. @@ -164,7 +164,7 @@ The bus applies suppression rules, then dispatches via Rust commands for platfor - **Trigger set for v1 (all in-app, no OS-wide detection):** - `inactivity_with_active_timer` — timer running + `document.visibilitystate === 'hidden'` OR window `blur` for 90 s continuous. (We know the user has switched away; we don't need to know what they switched to.) - `pending_morning_triage` — past 10:00 local + triage enabled + last-shown ≠ today. Fires once per day; gets suppressed forever if user later skips or completes. - - `micro_step_idle` — micro-step created + no `kon:task-completed` or `kon:step-completed` event for that parent-task-id within 15 min. + - `micro_step_idle` — micro-step created + no `magnotia:task-completed` or `magnotia:step-completed` event for that parent-task-id within 15 min. - **Suppression rules:** - Global mute in settings (on/off). - Hard cap 3 nudges per rolling hour. @@ -190,7 +190,7 @@ The bus applies suppression rules, then dispatches via Rust commands for platfor **Rule idempotency (new explicit requirement).** - Each rule stores `last_fired_at: ISO8601` and, for daily rules, `last_fired_local_date: YYYY-MM-DD`. Without this, a poll-driven "at 09:00" fires on every tick. - On sleep/resume: on app focus after > 10 minutes away, check each time-of-day rule; if today's fire time has passed and `last_fired_local_date` is not today, fire once and update. Configurable per-rule toggle: `catch_up_on_resume` (default ON for time-of-day rules). -- "After a task completes" rule: subscribes to the `kon:task-completed` event from Phase 6. Rule fires once per task id (guarded via `last_fired_task_ids`). +- "After a task completes" rule: subscribes to the `magnotia:task-completed` event from Phase 6. Rule fires once per task id (guarded via `last_fired_task_ids`). - "Morning triage finishes" rule: fires on *either* "Start the day" or "Skip for today" — skip counts as finishing. Fires once per `last_fired_local_date`. **Scope.** @@ -225,7 +225,7 @@ The bus applies suppression rules, then dispatches via Rust commands for platfor **Estimated effort.** Half day. -**Shipped note (2026/04/24).** Landed on `main` across commits `729b82c` to `fa93033` (13 feature commits plus one style-fix commit for a comment em-dash). Migration v13 adds `auto_completed` on `tasks`; cascade path in `complete_subtask_and_check_parent` sets it, `uncomplete_task` clears it on both target and reopened parent. New storage fn `list_recent_completions(pool, days)` + Tauri wrapper `list_recent_completions_cmd` expose the fixed-length, oldest-first 7-day series. Frontend has a dedicated `completionStats.svelte.ts` store (listens on `kon:task-completed` / `kon:step-completed` / `kon:task-uncompleted` / `kon:task-deleted` / `focus`), a `CompletionSparkline.svelte` SVG component, and header wiring in `TasksPage.svelte`. Settings toggle `showMomentumSparkline` added (default `true`) in the Rituals section; Phase 9 polish may resection. Acceptance list verified by full suite: 273 Rust tests pass, `cargo clippy --all-targets -D warnings` clean, `cargo fmt --check` clean, `npm run check` 0/0, `npm run build` clean. Manual dogfood walkthrough (Task 12 Step 6) still owes real-app verification when Jake next opens Corbie. +**Shipped note (2026/04/24).** Landed on `main` across commits `729b82c` to `fa93033` (13 feature commits plus one style-fix commit for a comment em-dash). Migration v13 adds `auto_completed` on `tasks`; cascade path in `complete_subtask_and_check_parent` sets it, `uncomplete_task` clears it on both target and reopened parent. New storage fn `list_recent_completions(pool, days)` + Tauri wrapper `list_recent_completions_cmd` expose the fixed-length, oldest-first 7-day series. Frontend has a dedicated `completionStats.svelte.ts` store (listens on `magnotia:task-completed` / `magnotia:step-completed` / `magnotia:task-uncompleted` / `magnotia:task-deleted` / `focus`), a `CompletionSparkline.svelte` SVG component, and header wiring in `TasksPage.svelte`. Settings toggle `showMomentumSparkline` added (default `true`) in the Rituals section; Phase 9 polish may resection. Acceptance list verified by full suite: 273 Rust tests pass, `cargo clippy --all-targets -D warnings` clean, `cargo fmt --check` clean, `npm run check` 0/0, `npm run build` clean. Manual dogfood walkthrough (Task 12 Step 6) still owes real-app verification when Jake next opens Magnotia. --- @@ -236,7 +236,7 @@ The bus applies suppression rules, then dispatches via Rust commands for platfor **Contents.** - File-system `.md` save dialog (replace clipboard-only export). Rust `write_text_file` command; platform dialog via `tauri-plugin-dialog`. - Bulk select + bulk export in History. -- LLM-powered content tags (`topic:*`, `intent:*`). Slot into the existing `kon-llm` stub. +- LLM-powered content tags (`topic:*`, `intent:*`). Slot into the existing `magnotia-llm` stub. - Settings UX overhaul: bundle high-traffic settings into a "Start here" group; hide advanced behind a disclosure. - Visual polish pass on all Phase 1 – Phase 8 surfaces: spacing, typography, motion curves, colour, dark-mode parity. - Accessibility pass: keyboard navigation, screen reader labels, focus order, colour contrast audit against WCAG AA. @@ -270,7 +270,7 @@ all-targets / fmt clean / svelte-check 0/0 / npm build clean. ## Phase 10 — QC + rename + release — **SPLIT 2026/04/23** -Earlier draft rolled QC, a full codebase rename, an app-data migration shim, and the release ceremony into one day. Review flagged this as unrealistic and split it. The rename sweep in particular crosses every surface in the app; rushing it is how you end up with `kon.db` on half the users' machines and `corbie.db` on the rest. +Earlier draft rolled QC, a full codebase rename, an app-data migration shim, and the release ceremony into one day. Review flagged this as unrealistic and split it. The rename sweep in particular crosses every surface in the app; rushing it is how you end up with `magnotia.db` on half the users' machines and `magnotia.db` on the rest. ### Pre-Phase-10: Cargo.lock policy decision — **RESOLVED 2026/04/24** @@ -283,33 +283,33 @@ Earlier draft rolled QC, a full codebase rename, an app-data migration shim, and **Prerequisite:** Phase 1 – Phase 9 complete. Cargo.lock committed. - Full dogfood walkthrough: record a real brain-dump → clean transcript → task extraction → micro-step one task → run a focus timer → tag energy → complete → open evening wind-down → skip morning triage → re-check no re-prompt. -- RB-08 macOS power-assertion verification: **Rachmann runs this offline** on his Mac. `pmset -g assertions` during a live session; expected `PreventSystemSleep` attributed to Corbie's bundle id. On confirmation, close RB-08 and move `docs/issues/power-assertion-macos-objc2.md` to `docs/issues/resolved/`. +- RB-08 macOS power-assertion verification: **Rachmann runs this offline** on his Mac. `pmset -g assertions` during a live session; expected `PreventSystemSleep` attributed to Magnotia's bundle id. On confirmation, close RB-08 and move `docs/issues/power-assertion-macos-objc2.md` to `docs/issues/resolved/`. - Cross-platform build matrix green across Linux / macOS / Windows. - Accessibility regression check: keyboard-only traversal of every new Phase 5 – Phase 8 surface. - Freshly-clean install test on a spare user account: no stray data leaks from dev. Rachmann's Mac slot runs in parallel; not blocking the rest of 10a. -### Phase 10b — Kon → Corbie rename sweep (estimated half day to 1 day) +### Phase 10b — Magnotia → Magnotia rename sweep (estimated half day to 1 day) Runs **after** Phase 10a QC, **after** Jake has renamed the two repos in GitHub + Gitea web UIs. The rename only starts here, not earlier, so in-flight Phase 1 – Phase 9 work doesn't have to re-learn event names mid-cycle. -- `package.json` → `name: "corbie"`, `description` update. (Version stays at `0.1.0`.) -- Cargo crates: `kon`, `kon-audio`, `kon-storage`, `kon-transcription`, `kon-llm`, `kon-ai-formatting`, `kon-core`, `kon-cloud-providers`, `kon-hotkey`, `kon-mcp` → `corbie-*`. Mass-rename via `Cargo.toml` name field + workspace path references + `use` imports. +- `package.json` → `name: "magnotia"`, `description` update. (Version stays at `0.1.0`.) +- Cargo crates: `magnotia`, `magnotia-audio`, `magnotia-storage`, `magnotia-transcription`, `magnotia-llm`, `magnotia-ai-formatting`, `magnotia-core`, `magnotia-cloud-providers`, `magnotia-hotkey`, `magnotia-mcp` → `magnotia-*`. Mass-rename via `Cargo.toml` name field + workspace path references + `use` imports. - Binary + product names: `src-tauri/tauri.conf.json` (productName, identifier), `.desktop` file, Windows product name, macOS bundle name. -- Install paths: `~/.local/share/kon/` → `~/.local/share/corbie/` (plus the macOS `~/Library/Application Support` and Windows `%APPDATA%` equivalents). **App-data migration shim required** — first-run checks for the old dir, moves contents, writes a sentinel `.migrated-from-kon`. Shim must handle the case where both dirs exist (prefer new, log the duplicate). -- Database filename: `kon.db` → `corbie.db`. Handled by the same shim. -- Window titles, tray tooltip, About-dialog, README body, docs references where they name the product (leave historical brief content talking about "Kon" — it's a historical document). -- Event names: `kon:start-timer`, `kon:task-completed`, `kon:open-wind-down`, `kon:preferences-changed`, `kon:hotkey-pressed`, `kon:llm-download-progress` → `corbie:*`. Single commit; one find-replace; both emitter and listener in the same diff. -- Logs, error messages, user-facing copy (including toast strings that mention "Kon"). -- Settings SQLite key: `kon_preferences` → `corbie_preferences`. Migration reads old key on first launch, writes new key, deletes old. -- Remotes: `ssh://git.corbel.consulting:2222/jake/kon.git` + `github.com:jakejars/kon.git` → `…/corbie.git`. `git remote set-url` locally after web-UI renames. +- Install paths: `~/.local/share/magnotia/` → `~/.local/share/magnotia/` (plus the macOS `~/Library/Application Support` and Windows `%APPDATA%` equivalents). **App-data migration shim required** — first-run checks for the old dir, moves contents, writes a sentinel `.migrated-from-magnotia`. Shim must handle the case where both dirs exist (prefer new, log the duplicate). +- Database filename: `magnotia.db` → `magnotia.db`. Handled by the same shim. +- Window titles, tray tooltip, About-dialog, README body, docs references where they name the product (leave historical brief content talking about "Magnotia" — it's a historical document). +- Event names: `magnotia:start-timer`, `magnotia:task-completed`, `magnotia:open-wind-down`, `magnotia:preferences-changed`, `magnotia:hotkey-pressed`, `magnotia:llm-download-progress` → `magnotia:*`. Single commit; one find-replace; both emitter and listener in the same diff. +- Logs, error messages, user-facing copy (including toast strings that mention "Magnotia"). +- Settings SQLite key: `magnotia_preferences` → `magnotia_preferences`. Migration reads old key on first launch, writes new key, deletes old. +- Remotes: `ssh://git.corbel.consulting:2222/jake/magnotia.git` + `github.com:jakejars/magnotia.git` → `…/magnotia.git`. `git remote set-url` locally after web-UI renames. ### Phase 10c — Release (estimated half day) - Version is already `0.1.0` in `Cargo.toml`, `package.json`, and `tauri.conf.json` — no bump needed. Confirm the three match. - Write `CHANGELOG.md`. Seed from this roadmap's phases. Entries are written to end-users, not engineers — "You can now read transcripts aloud" not "Added tts_speak command". -- Write release notes in plain language: what it does, who it's for, the Kon-data migration note, the Windows notifications caveat (installed app only). +- Write release notes in plain language: what it does, who it's for, the Magnotia-data migration note, the Windows notifications caveat (installed app only). - Tag `v0.1.0` on the head commit. - Push tag to both remotes. GitHub Actions release workflow auto-builds artefacts for Linux / macOS / Windows. - Smoke-test at least one artefact per platform (ideally Rachmann covers macOS) before the release is made public. @@ -328,7 +328,7 @@ Runs **after** Phase 10a QC, **after** Jake has renamed the two repos in GitHub ## Explicit non-goals -- Mobile apps. Corbie is desktop-first; a mobile companion is post-v0.1. +- Mobile apps. Magnotia is desktop-first; a mobile companion is post-v0.1. - Cloud sync. Local-first is the floor, not a feature. Sync is out of scope through v0.1. - Premium voices, paid tiers, subscription. Licensing + monetisation is a separate track tracked in memory `project_marketplace_creem`. - AI body doubling (low-fi focus rooms) — validated but parked to post-v0.1. @@ -339,13 +339,13 @@ Runs **after** Phase 10a QC, **after** Jake has renamed the two repos in GitHub Ideas worth keeping warm once the v0.1 release is out. Not tracked as phases until the core release is done. - **Calendar integration.** Read-only first pass could parse a local ICS file (Thunderbird / Evolution export) and surface day events alongside the tasks list — stays local-first. A cloud-sync pass (Google / iCloud / CalDAV) is a v0.2+ conversation because it re-opens credential handling and refresh-token plumbing that v0.1 deliberately avoids. -- **Right-click highlighted text → capture as task.** Two flavours: (a) *In-Corbie* — context menu on a selected transcript range or viewer segment, routes to `create_task_cmd` with the selection as text. Small — lives naturally in Phase 9 polish or a bolt-on. (b) *System-wide* — highlight anywhere (browser, Slack, IDE) and call Corbie. Platform-painful: macOS Services API, Windows shell-extension, Linux desktop-env-specific context menus. Scope as a separate post-v0.1 phase. +- **Right-click highlighted text → capture as task.** Two flavours: (a) *In-Magnotia* — context menu on a selected transcript range or viewer segment, routes to `create_task_cmd` with the selection as text. Small — lives naturally in Phase 9 polish or a bolt-on. (b) *System-wide* — highlight anywhere (browser, Slack, IDE) and call Magnotia. Platform-painful: macOS Services API, Windows shell-extension, Linux desktop-env-specific context menus. Scope as a separate post-v0.1 phase. ## Anchors - Spec: [docs/brief/feature-set.md](docs/brief/feature-set.md) + [docs/brief/design-principles.md](docs/brief/design-principles.md) - Current baseline: this session's HANDOVER.md -- Rebrand memory: `~/.claude/projects/-home-jake-Documents-CORBEL-Main/memory/project_corbie_rebrand.md` +- Rebrand memory: `~/.claude/projects/-home-jake-Documents-CORBEL-Main/memory/project_magnotia_rebrand.md` - Release-blocker index: [docs/issues/README.md](docs/issues/README.md) --- diff --git a/docs/superpowers/audits/2026-04-25-phase10a-static-slice.md b/docs/superpowers/audits/2026-04-25-phase10a-static-slice.md index 97b9d68..334a799 100644 --- a/docs/superpowers/audits/2026-04-25-phase10a-static-slice.md +++ b/docs/superpowers/audits/2026-04-25-phase10a-static-slice.md @@ -182,34 +182,34 @@ Three workflows in `.github/workflows/`: ## Clean-install test plan -Run on a spare user account or a fresh VM, with no prior Corbie / -Kon data. Three iterations: one per platform. +Run on a spare user account or a fresh VM, with no prior Magnotia / +Magnotia data. Three iterations: one per platform. 1. Install the artefact from the platform's `build.yml` output. -2. Launch from a clean shell (`corbie` from PATH, or the .app / +2. Launch from a clean shell (`magnotia` from PATH, or the .app / Start-menu shortcut). 3. Verify first-run setup flow renders. Walk through the Whisper / LLM model download for the smallest tier. 4. Confirm app data lands at the expected path: - - Linux: `~/.local/share/kon/` (will become `~/.local/share/ - corbie/` after Phase 10b rename). - - macOS: `~/Library/Application Support/com.corbel.kon/`. - - Windows: `%APPDATA%\com.corbel.kon\`. + - Linux: `~/.local/share/magnotia/` (will become `~/.local/share/ + magnotia/` after Phase 10b rename). + - macOS: `~/Library/Application Support/com.corbel.magnotia/`. + - Windows: `%APPDATA%\com.corbel.magnotia\`. 5. Record a 10-second brain-dump → cleanup → task extraction. Confirm no log leakage to stderr that references `target/` or dev-only paths. -6. Quit the app. Open the SQLite db (`kon.db` for now) and verify +6. Quit the app. Open the SQLite db (`magnotia.db` for now) and verify `SELECT version FROM schema_version ORDER BY version DESC LIMIT 1` returns `14`. 7. Re-launch. Confirm settings persist, history shows the test transcript with manual + LLM tags. 8. Optional but recommended: launch with `RUST_LOG=debug` once and archive the log. Anything referencing `/home/jake/Documents/ - CORBEL-Projects/kon/target/` is a dev-leak bug. + CORBEL-Projects/magnotia/target/` is a dev-leak bug. For Phase 10c this gets re-run after the rename sweep to confirm -the migration shim correctly moves `~/.local/share/kon/` → -`~/.local/share/corbie/` and renames `kon.db` → `corbie.db`. +the migration shim correctly moves `~/.local/share/magnotia/` → +`~/.local/share/magnotia/` and renames `magnotia.db` → `magnotia.db`. ## Walkthrough checklist (deferred from Phase 9d) @@ -255,7 +255,7 @@ def contrast(c1, c2): ## Anchors -- Roadmap: [docs/roadmap/2026-04-23-corbie-feature-complete-roadmap.md](../../roadmap/2026-04-23-corbie-feature-complete-roadmap.md) +- Roadmap: [docs/roadmap/2026-04-23-magnotia-feature-complete-roadmap.md](../../roadmap/2026-04-23-magnotia-feature-complete-roadmap.md) - Phase 9 spec: [docs/superpowers/specs/2026-04-24-phase9-polish-debt-design.md](../specs/2026-04-24-phase9-polish-debt-design.md) - Release-blocker index: [docs/issues/README.md](../../issues/README.md) - Latest handover: [HANDOVER.md](../../../HANDOVER.md) diff --git a/docs/superpowers/plans/2026-04-24-phase8-forgiving-gamification.md b/docs/superpowers/plans/2026-04-24-phase8-forgiving-gamification.md index 06631d4..15fbc13 100644 --- a/docs/superpowers/plans/2026-04-24-phase8-forgiving-gamification.md +++ b/docs/superpowers/plans/2026-04-24-phase8-forgiving-gamification.md @@ -16,7 +16,7 @@ - Branch: `main` (Jake's standing rule — phases ship on main, not feature branches). - Commit after each task. Format: `feat(gamification): [what changed]`, or `fix(gamification): …` for a within-phase correction. Each task's final step is the commit. -- Test runner on Rust: `cargo test -p kon-storage` (or crate name in workspace). Frontend has **no test runner** — `npm run check` is the only type/lint gate; correctness is verified by cargo tests on the backend and manual dogfood on the frontend, deferred to Phase 10a QC. +- Test runner on Rust: `cargo test -p magnotia-storage` (or crate name in workspace). Frontend has **no test runner** — `npm run check` is the only type/lint gate; correctness is verified by cargo tests on the backend and manual dogfood on the frontend, deferred to Phase 10a QC. - British English in user-facing copy. - No em / en dashes in prose or strings. @@ -97,7 +97,7 @@ async fn migration_v13_adds_auto_completed_column() { - [ ] **Step 2: Run test to verify it fails** -Run: `cargo test -p kon-storage migration_v13 -- --nocapture` +Run: `cargo test -p magnotia-storage migration_v13 -- --nocapture` Expected: FAIL with "no such column: auto_completed" or "expected auto_completed column" depending on which assertion fires first. - [ ] **Step 3: Add the migration entry** @@ -130,12 +130,12 @@ In `crates/storage/src/migrations.rs`, append to the `MIGRATIONS: &[(i64, &str, - [ ] **Step 4: Run test to verify it passes** -Run: `cargo test -p kon-storage migration_v13 -- --nocapture` +Run: `cargo test -p magnotia-storage migration_v13 -- --nocapture` Expected: PASS. - [ ] **Step 5: Run the full storage test suite to confirm no regression** -Run: `cargo test -p kon-storage` +Run: `cargo test -p magnotia-storage` Expected: all existing tests still pass. - [ ] **Step 6: Commit** @@ -209,7 +209,7 @@ async fn cascade_sets_auto_completed_on_parent_only() { - [ ] **Step 2: Run test to verify it fails** -Run: `cargo test -p kon-storage cascade_sets_auto_completed -- --nocapture` +Run: `cargo test -p magnotia-storage cascade_sets_auto_completed -- --nocapture` Expected: FAIL — parent_auto is 0 because the cascade UPDATE doesn't set the flag yet. - [ ] **Step 3: Modify the cascade UPDATE** @@ -223,7 +223,7 @@ if pending == 0 { .bind(&pid) .execute(&mut *tx) .await - .map_err(|e| KonError::StorageError(format!("Auto-complete parent failed: {e}")))?; + .map_err(|e| MagnotiaError::StorageError(format!("Auto-complete parent failed: {e}")))?; } ``` @@ -240,18 +240,18 @@ if pending == 0 { .bind(&pid) .execute(&mut *tx) .await - .map_err(|e| KonError::StorageError(format!("Auto-complete parent failed: {e}")))?; + .map_err(|e| MagnotiaError::StorageError(format!("Auto-complete parent failed: {e}")))?; } ``` - [ ] **Step 4: Run test to verify it passes** -Run: `cargo test -p kon-storage cascade_sets_auto_completed -- --nocapture` +Run: `cargo test -p magnotia-storage cascade_sets_auto_completed -- --nocapture` Expected: PASS. - [ ] **Step 5: Run the full storage test suite** -Run: `cargo test -p kon-storage` +Run: `cargo test -p magnotia-storage` Expected: all tests pass. - [ ] **Step 6: Commit** @@ -317,7 +317,7 @@ async fn uncomplete_clears_auto_completed_on_parent() { - [ ] **Step 2: Run test to verify it fails** -Run: `cargo test -p kon-storage uncomplete_clears_auto_completed -- --nocapture` +Run: `cargo test -p magnotia-storage uncomplete_clears_auto_completed -- --nocapture` Expected: FAIL on the `parent_auto` assertion (still 1). - [ ] **Step 3: Modify both UPDATE statements in `uncomplete_task`** @@ -357,12 +357,12 @@ sqlx::query( - [ ] **Step 4: Run test to verify it passes** -Run: `cargo test -p kon-storage uncomplete_clears_auto_completed -- --nocapture` +Run: `cargo test -p magnotia-storage uncomplete_clears_auto_completed -- --nocapture` Expected: PASS. - [ ] **Step 5: Run the full storage test suite** -Run: `cargo test -p kon-storage` +Run: `cargo test -p magnotia-storage` Expected: all tests pass. - [ ] **Step 6: Commit** @@ -507,7 +507,7 @@ async fn list_recent_completions_uses_local_day_boundary() { - [ ] **Step 2: Run tests to verify they fail** -Run: `cargo test -p kon-storage list_recent_completions -- --nocapture` +Run: `cargo test -p magnotia-storage list_recent_completions -- --nocapture` Expected: FAIL — function not defined. - [ ] **Step 3: Add the struct and function** @@ -562,7 +562,7 @@ pub async fn list_recent_completions( .bind(format!("-{} days", days - 1)) .fetch_all(pool) .await - .map_err(|e| KonError::StorageError(format!("List recent completions failed: {e}")))?; + .map_err(|e| MagnotiaError::StorageError(format!("List recent completions failed: {e}")))?; let lookup: std::collections::HashMap = rows .into_iter() @@ -574,7 +574,7 @@ pub async fn list_recent_completions( sqlx::query_as("SELECT DATE('now', 'localtime')") .fetch_one(pool) .await - .map_err(|e| KonError::StorageError(format!("Get local today failed: {e}")))?; + .map_err(|e| MagnotiaError::StorageError(format!("Get local today failed: {e}")))?; let today = today_row.0; let mut series = Vec::with_capacity(days as usize); @@ -588,7 +588,7 @@ pub async fn list_recent_completions( .bind(format!("-{offset} days")) .fetch_one(pool) .await - .map_err(|e| KonError::StorageError(format!("Compute spine day failed: {e}")))?; + .map_err(|e| MagnotiaError::StorageError(format!("Compute spine day failed: {e}")))?; let count = lookup.get(&day).copied().unwrap_or(0); series.push(DailyCompletionCount { day, count }); } @@ -607,17 +607,17 @@ pub use database::{DailyCompletionCount, list_recent_completions}; - [ ] **Step 4: Run tests to verify they pass** -Run: `cargo test -p kon-storage list_recent_completions -- --nocapture` +Run: `cargo test -p magnotia-storage list_recent_completions -- --nocapture` Expected: all 4 new tests PASS. - [ ] **Step 5: Run the full storage test suite** -Run: `cargo test -p kon-storage` +Run: `cargo test -p magnotia-storage` Expected: all tests pass. - [ ] **Step 6: Verify clippy + fmt** -Run: `cargo clippy -p kon-storage -- -D warnings && cargo fmt --check` +Run: `cargo clippy -p magnotia-storage -- -D warnings && cargo fmt --check` Expected: clean. - [ ] **Step 7: Commit** @@ -647,7 +647,7 @@ EOF - [ ] **Step 1: Add the import at the top of `src-tauri/src/commands/tasks.rs`** -Update the existing `use kon_storage::{...}` block (currently starting at line 10) to include the new symbols. Add `DailyCompletionCount, list_recent_completions as db_list_recent_completions` alongside the existing entries. +Update the existing `use magnotia_storage::{...}` block (currently starting at line 10) to include the new symbols. Add `DailyCompletionCount, list_recent_completions as db_list_recent_completions` alongside the existing entries. - [ ] **Step 2: Add the command at the end of `src-tauri/src/commands/tasks.rs`** @@ -676,12 +676,12 @@ In `src-tauri/src/lib.rs`, inside the `invoke_handler!` block, below the existin - [ ] **Step 4: Compile** -Run: `cargo build -p kon` +Run: `cargo build -p magnotia` Expected: clean build. - [ ] **Step 5: Verify clippy + fmt** -Run: `cargo clippy -p kon -- -D warnings && cargo fmt --check` +Run: `cargo clippy -p magnotia -- -D warnings && cargo fmt --check` Expected: clean. - [ ] **Step 6: Commit** @@ -691,7 +691,7 @@ git add src-tauri/src/commands/tasks.rs src-tauri/src/lib.rs git commit -m "$(cat <<'EOF' feat(gamification): list_recent_completions_cmd Tauri wrapper -Thin wrapper over kon_storage::list_recent_completions, parameterised +Thin wrapper over magnotia_storage::list_recent_completions, parameterised by day count. Serialises to camelCase JSON (day, count). Co-Authored-By: Claude Opus 4.7 (1M context) @@ -769,10 +769,10 @@ EOF // // Refresh triggers: // - module load (first time) -// - kon:task-completed (from page.svelte.ts completeTask) -// - kon:step-completed (from MicroSteps.svelte) -// - kon:task-uncompleted (new event — emitted by uncompleteTask) -// - kon:task-deleted (new event — emitted by deleteTask) +// - magnotia:task-completed (from page.svelte.ts completeTask) +// - magnotia:step-completed (from MicroSteps.svelte) +// - magnotia:task-uncompleted (new event — emitted by uncompleteTask) +// - magnotia:task-deleted (new event — emitted by deleteTask) // - window focus (for day rollover while the app stayed open // past midnight) // @@ -813,10 +813,10 @@ if (typeof window !== "undefined") { const handler = () => { refresh().catch(() => {}); }; - window.addEventListener("kon:task-completed", handler); - window.addEventListener("kon:step-completed", handler); - window.addEventListener("kon:task-uncompleted", handler); - window.addEventListener("kon:task-deleted", handler); + window.addEventListener("magnotia:task-completed", handler); + window.addEventListener("magnotia:step-completed", handler); + window.addEventListener("magnotia:task-uncompleted", handler); + window.addEventListener("magnotia:task-deleted", handler); window.addEventListener("focus", handler); if (hasTauriRuntime()) { @@ -1034,12 +1034,12 @@ EOF --- -### Task 10: Dispatch `kon:task-uncompleted` + `kon:task-deleted` +### Task 10: Dispatch `magnotia:task-uncompleted` + `magnotia:task-deleted` **Files:** - Modify: `src/lib/stores/page.svelte.ts` — `uncompleteTask` (line 470-479) and `deleteTask` (line 438-451). -- [ ] **Step 1: Emit `kon:task-uncompleted` from `uncompleteTask`** +- [ ] **Step 1: Emit `magnotia:task-uncompleted` from `uncompleteTask`** Current body: ```ts @@ -1060,11 +1060,11 @@ Replace the try-block body with: await invoke("uncomplete_task_cmd", { id }); applyLocalTaskUpdate(id, { done: false, doneAt: null }); if (typeof window !== "undefined") { - window.dispatchEvent(new CustomEvent("kon:task-uncompleted", { detail: { id } })); + window.dispatchEvent(new CustomEvent("magnotia:task-uncompleted", { detail: { id } })); } ``` -- [ ] **Step 2: Emit `kon:task-deleted` from `deleteTask`** +- [ ] **Step 2: Emit `magnotia:task-deleted` from `deleteTask`** Current body: ```ts @@ -1087,7 +1087,7 @@ Add the dispatch inside the success path, right after `broadcastTasks();`: ```ts if (typeof window !== "undefined") { - window.dispatchEvent(new CustomEvent("kon:task-deleted", { detail: { id } })); + window.dispatchEvent(new CustomEvent("magnotia:task-deleted", { detail: { id } })); } ``` @@ -1105,7 +1105,7 @@ feat(gamification): emit task-uncompleted + task-deleted events Phase 8 completionStats store listens on these events to refresh the daily count. Keeps the badge + sparkline accurate after un-tick / delete -paths, which don't currently fire kon:task-completed. +paths, which don't currently fire magnotia:task-completed. Co-Authored-By: Claude Opus 4.7 (1M context) EOF @@ -1222,7 +1222,7 @@ Record any deviations in the handover. Do not mark this step complete on compile ### Task 13: Update roadmap and handover **Files:** -- Modify: `docs/roadmap/2026-04-23-corbie-feature-complete-roadmap.md` (Phase 8 section — mark shipped, fold the revised-dropped-grace-days line into past tense). +- Modify: `docs/roadmap/2026-04-23-magnotia-feature-complete-roadmap.md` (Phase 8 section — mark shipped, fold the revised-dropped-grace-days line into past tense). - Modify: `HANDOVER.md` (end-of-session summary). - [ ] **Step 1: Update roadmap Phase 8 status** @@ -1247,7 +1247,7 @@ Replace the session summary with today's state: - [ ] **Step 3: Commit** ```bash -git add docs/roadmap/2026-04-23-corbie-feature-complete-roadmap.md HANDOVER.md +git add docs/roadmap/2026-04-23-magnotia-feature-complete-roadmap.md HANDOVER.md git commit -m "$(cat <<'EOF' docs(phase8): mark Phase 8 shipped + refresh handover diff --git a/docs/superpowers/plans/2026-04-24-phase9-polish-debt.md b/docs/superpowers/plans/2026-04-24-phase9-polish-debt.md index 01098ba..40b2761 100644 --- a/docs/superpowers/plans/2026-04-24-phase9-polish-debt.md +++ b/docs/superpowers/plans/2026-04-24-phase9-polish-debt.md @@ -6,7 +6,7 @@ **Architecture:** See companion spec for full detail. Four sub-phases: 9a export plumbing, 9b LLM tags, 9c Settings restructure, 9d visual + a11y polish. -**Tech Stack:** Rust + Tauri 2, `tauri-plugin-dialog` (already installed), `kon-llm` (already wired to llama-cpp-2), Svelte 5 runes, TypeScript, Tailwind v4, Lucide icons. +**Tech Stack:** Rust + Tauri 2, `tauri-plugin-dialog` (already installed), `magnotia-llm` (already wired to llama-cpp-2), Svelte 5 runes, TypeScript, Tailwind v4, Lucide icons. **Spec:** [docs/superpowers/specs/2026-04-24-phase9-polish-debt-design.md](../specs/2026-04-24-phase9-polish-debt-design.md) @@ -16,7 +16,7 @@ Critical-review pass against the actual codebase (after Codex was unavailable for a cross-model read) surfaced three mismatches between the plan's prescribed code and what the repo actually contains. Implementations adapt as follows. -**1. `kon-llm` API.** Plan referenced `LlamaEngine::generate_chat(messages, config).await`. Actual: `LlmEngine::generate(prompt: &str, config: &GenerationConfig) -> Result` — *synchronous*, prompt is a single rendered string, no chat-message vec. The engine struct is `LlmEngine` not `LlamaEngine`. Loaded via `engine.load(model_path)` (sync, &self). The closest in-tree analogue to the new `extract_content_tags` is `LlmEngine::cleanup_text` — same crate, same generate-with-prompt shape; copy that pattern. Tasks 7 + 8 code samples are guides, not literals. +**1. `magnotia-llm` API.** Plan referenced `LlamaEngine::generate_chat(messages, config).await`. Actual: `LlmEngine::generate(prompt: &str, config: &GenerationConfig) -> Result` — *synchronous*, prompt is a single rendered string, no chat-message vec. The engine struct is `LlmEngine` not `LlamaEngine`. Loaded via `engine.load(model_path)` (sync, &self). The closest in-tree analogue to the new `extract_content_tags` is `LlmEngine::cleanup_text` — same crate, same generate-with-prompt shape; copy that pattern. Tasks 7 + 8 code samples are guides, not literals. **2. Tauri `AppState`.** Plan referenced `state.llm.read().await.engine.as_ref()`. Actual: `AppState.llm_engine: Arc` — direct, no RwLock, accessed as `state.llm_engine.as_ref()`. Existing LLM commands file is `src-tauri/src/commands/llm.rs` (already there — do not create a new one). @@ -43,7 +43,7 @@ Fix in scope (decided 2026-04-24, "do it properly so we don't need to revisit"): **TDD steps:** 1. Storage test: migration v14 adds `llm_tags TEXT NOT NULL DEFAULT ''` column; pre-existing rows default to empty string. 2. Storage test: `db_update_transcript(.., None, Some("topic:x,intent:y"))` writes only `llm_tags` and leaves `manual_tags` untouched. -3. Tauri command compile-check via `cargo build -p kon`. +3. Tauri command compile-check via `cargo build -p magnotia`. 4. Frontend `npm run check` clean. Commit message: `feat(phase9): migration v14 + tag persistence wiring`. @@ -72,7 +72,7 @@ Tasks 8.5 inserted between 8 and 9. Subsequent task numbers (11..16) unchanged. - Every Phase 9 addition with motion respects `prefers-reduced-motion`. - Phase 8 gotchas carried forward: - `$derived` cannot export at `.svelte.ts` module scope — use `export function` instead. - - `#[derive(sqlx::FromRow)]` is not available in `kon-storage` (not expected to fire in this phase; noted for safety). + - `#[derive(sqlx::FromRow)]` is not available in `magnotia-storage` (not expected to fire in this phase; noted for safety). --- @@ -97,7 +97,7 @@ Tasks 8.5 inserted between 8 and 9. Subsequent task numbers (11..16) unchanged. - `src/lib/stores/page.svelte.ts` — hydrate / persist `llmTags`. - `src/lib/utils/frontmatter.ts` — include `llmTags` in `buildFrontmatter` tag union. - `src/lib/components/CompletionSparkline.svelte` — friendlier aria-label, per-bar ``, `tabindex`, stagger entrance. -- `docs/roadmap/2026-04-23-corbie-feature-complete-roadmap.md` — mark Phase 9 shipped. +- `docs/roadmap/2026-04-23-magnotia-feature-complete-roadmap.md` — mark Phase 9 shipped. - `HANDOVER.md` — end-of-session state. **Files deleted:** None. @@ -144,7 +144,7 @@ mod tests { async fn write_text_file_errors_on_bad_parent() { // Parent dir does not exist → expect Err. 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; @@ -157,7 +157,7 @@ If `tempfile` is not already a workspace dev-dep, add it to `src-tauri/Cargo.tom - [ ] **Step 3: Run test to verify it fails** -Run: `cargo test -p kon write_text_file -- --nocapture` +Run: `cargo test -p magnotia write_text_file -- --nocapture` Expected: FAIL — `write_text_file_cmd` not defined. - [ ] **Step 4: Add the command** @@ -177,7 +177,7 @@ pub async fn write_text_file_cmd(path: String, contents: String) -> Result<(), S - [ ] **Step 5: Tests pass** -Run: `cargo test -p kon write_text_file -- --nocapture` +Run: `cargo test -p magnotia write_text_file -- --nocapture` Expected: PASS. - [ ] **Step 6: Commit** @@ -218,7 +218,7 @@ Add the line inside the block (alphabetical neighbourhood or end — match local - [ ] **Step 3: Compile** -Run: `cargo build -p kon` +Run: `cargo build -p magnotia` Expected: clean build. - [ ] **Step 4: Commit** @@ -627,7 +627,7 @@ pub use prompts::{ContentTags, INTENT_CLOSED_SET, is_valid_intent}; - [ ] **Step 4: Type-check** -Run: `cargo build -p kon-llm && cargo clippy -p kon-llm -- -D warnings` +Run: `cargo build -p magnotia-llm && cargo clippy -p magnotia-llm -- -D warnings` Expected: clean. - [ ] **Step 5: Commit** @@ -659,10 +659,10 @@ EOF ```rust // crates/llm/tests/content_tags_smoke.rs -use kon_llm::{extract_content_tags, is_valid_intent, model_manager::LlmModelId}; +use magnotia_llm::{extract_content_tags, is_valid_intent, model_manager::LlmModelId}; // Marked #[ignore] so it does not run in default cargo test (the model -// load is heavy). Run explicitly: cargo test -p kon-llm --test +// load is heavy). Run explicitly: cargo test -p magnotia-llm --test // content_tags_smoke -- --ignored --nocapture. #[tokio::test] #[ignore] @@ -685,14 +685,14 @@ async fn extract_content_tags_on_short_transcript() { assert!(is_valid_intent(&tags.intent), "intent in closed set: {tags:?}"); } -async fn load_local_tier0_engine() -> Option<kon_llm::LlamaEngine> { +async fn load_local_tier0_engine() -> Option<magnotia_llm::LlamaEngine> { // Use the existing recommend_tier / model_manager path to find a // locally-downloaded model file. Skip the test if none is present. - let tier = kon_llm::recommend_tier(); - let info = kon_llm::LlmModelInfo::from_id(&LlmModelId::from_tier(tier))?; + let tier = magnotia_llm::recommend_tier(); + let info = magnotia_llm::LlmModelInfo::from_id(&LlmModelId::from_tier(tier))?; let path = info.local_path().ok()?; if !path.exists() { return None; } - kon_llm::LlamaEngine::load(&path).await.ok() + magnotia_llm::LlamaEngine::load(&path).await.ok() } ``` @@ -757,9 +757,9 @@ pub async fn extract_content_tags( - [ ] **Step 3: Build + tests** ``` -cargo build -p kon-llm -cargo test -p kon-llm # excludes #[ignore] — should still be green -cargo clippy -p kon-llm -- -D warnings +cargo build -p magnotia-llm +cargo test -p magnotia-llm # excludes #[ignore] — should still be green +cargo clippy -p magnotia-llm -- -D warnings cargo fmt --check ``` @@ -787,13 +787,13 @@ EOF ### Task 8: `extract_content_tags_cmd` Tauri wrapper **Files:** -- Modify: the existing LLM-commands file under `src-tauri/src/commands/` (grep for `kon_llm::` to find it; typically something like `llm.rs` or `ai.rs`). If absent, create `src-tauri/src/commands/llm.rs` and register the module in `commands/mod.rs`. +- Modify: the existing LLM-commands file under `src-tauri/src/commands/` (grep for `magnotia_llm::` to find it; typically something like `llm.rs` or `ai.rs`). If absent, create `src-tauri/src/commands/llm.rs` and register the module in `commands/mod.rs`. - Modify: `src-tauri/src/lib.rs` — register the command. - [ ] **Step 1: Add the command** ```rust -use kon_llm::{extract_content_tags as llm_extract_content_tags, ContentTags}; +use magnotia_llm::{extract_content_tags as llm_extract_content_tags, ContentTags}; // …alongside existing LLM imports. #[tauri::command] @@ -827,7 +827,7 @@ In `src-tauri/src/lib.rs` `invoke_handler!`: - [ ] **Step 3: Build** -Run: `cargo build -p kon && cargo clippy -p kon -- -D warnings && cargo fmt --check` +Run: `cargo build -p magnotia && cargo clippy -p magnotia -- -D warnings && cargo fmt --check` Expected: clean. - [ ] **Step 4: Commit** @@ -837,7 +837,7 @@ git add src-tauri/src/commands/ src-tauri/src/lib.rs git commit -m "$(cat <<'EOF' feat(phase9): extract_content_tags_cmd Tauri wrapper -Bridges kon_llm::extract_content_tags to the frontend. Returns a +Bridges magnotia_llm::extract_content_tags to the frontend. Returns a ContentTags object serialised to the {topic, intent} shape the frontend stores on item.llmTags. @@ -1560,7 +1560,7 @@ EOF ### Task 16: Full verification pass + roadmap + HANDOVER **Files:** -- Modify: `docs/roadmap/2026-04-23-corbie-feature-complete-roadmap.md` — mark Phase 9 shipped. +- Modify: `docs/roadmap/2026-04-23-magnotia-feature-complete-roadmap.md` — mark Phase 9 shipped. - Modify: `HANDOVER.md` — end-of-session state. - [ ] **Step 1: Full Rust gate** @@ -1605,7 +1605,7 @@ Replace the session summary with today's state: - [ ] **Step 6: Commit** ```bash -git add docs/roadmap/2026-04-23-corbie-feature-complete-roadmap.md HANDOVER.md +git add docs/roadmap/2026-04-23-magnotia-feature-complete-roadmap.md HANDOVER.md git commit -m "$(cat <<'EOF' docs(phase9): mark Phase 9 shipped + refresh HANDOVER diff --git a/docs/superpowers/specs/2026-04-24-phase8-forgiving-gamification-design.md b/docs/superpowers/specs/2026-04-24-phase8-forgiving-gamification-design.md index 09cf074..b29fc5a 100644 --- a/docs/superpowers/specs/2026-04-24-phase8-forgiving-gamification-design.md +++ b/docs/superpowers/specs/2026-04-24-phase8-forgiving-gamification-design.md @@ -1,12 +1,12 @@ --- name: Phase 8 — Forgiving gamification (design) -description: Design spec for Corbie Phase 8. Surfaces today's completion count and a 7-day momentum sparkline on the Tasks header. No streaks, no grace days, no loss language. +description: Design spec for Magnotia Phase 8. Surfaces today's completion count and a 7-day momentum sparkline on the Tasks header. No streaks, no grace days, no loss language. type: spec -tags: [spec, phase-8, corbie, tasks, gamification] +tags: [spec, phase-8, magnotia, tasks, gamification] created: 2026/04/24 status: approved phase: 8 -roadmap: docs/roadmap/2026-04-23-corbie-feature-complete-roadmap.md +roadmap: docs/roadmap/2026-04-23-magnotia-feature-complete-roadmap.md author: Wren (CORBEL's resident agent) on behalf of Jake Sames --- @@ -92,7 +92,7 @@ Query groups matching rows by local-day and then left-joins against a generated // - window focus (for day rollover while the app stayed open overnight) ``` -**Why not hook off `kon:task-completed` alone:** that event only fires on completion, not on uncomplete or delete, and relying on it would leave the badge stale after either of those paths. Refreshing at the mutation-helper level captures every path that can change today's count. +**Why not hook off `magnotia:task-completed` alone:** that event only fires on completion, not on uncomplete or delete, and relying on it would leave the badge stale after either of those paths. Refreshing at the mutation-helper level captures every path that can change today's count. **`src/lib/pages/TasksPage.svelte` header:** Stacks to: @@ -150,7 +150,7 @@ Only the sparkline respects the toggle. The badge is always on. - Migration v13: applies cleanly against a DB populated at v12; default of `auto_completed = 0` on pre-existing rows verified. ### Frontend (`npm run check` + vitest) -- `completionStats` store refreshes on `kon:task-completed` event. +- `completionStats` store refreshes on `magnotia:task-completed` event. - `todayCount` derives from the last entry. - Sparkline renders exactly 7 bars for a populated fixture. - Sparkline hidden when `showMomentumSparkline === false`. diff --git a/docs/superpowers/specs/2026-04-24-phase9-polish-debt-design.md b/docs/superpowers/specs/2026-04-24-phase9-polish-debt-design.md index 2a5e71f..5a1da13 100644 --- a/docs/superpowers/specs/2026-04-24-phase9-polish-debt-design.md +++ b/docs/superpowers/specs/2026-04-24-phase9-polish-debt-design.md @@ -1,12 +1,12 @@ --- name: Phase 9 — Polish debt (design) -description: Design spec for Corbie Phase 9. Closes six polish-debt items from the feature-complete roadmap plus the Phase 8 carryover backlog. File-system save dialog, bulk History export, on-demand LLM content tags, progressive-disclosure Settings restructure, visual polish, accessibility sweep. +description: Design spec for Magnotia Phase 9. Closes six polish-debt items from the feature-complete roadmap plus the Phase 8 carryover backlog. File-system save dialog, bulk History export, on-demand LLM content tags, progressive-disclosure Settings restructure, visual polish, accessibility sweep. type: spec -tags: [spec, phase-9, corbie, polish, accessibility, settings, export, llm] +tags: [spec, phase-9, magnotia, polish, accessibility, settings, export, llm] created: 2026/04/24 status: approved phase: 9 -roadmap: docs/roadmap/2026-04-23-corbie-feature-complete-roadmap.md +roadmap: docs/roadmap/2026-04-23-magnotia-feature-complete-roadmap.md author: Wren (CORBEL's resident agent) on behalf of Jake Sames --- @@ -18,7 +18,7 @@ Close the last polish-debt items before Phase 10 (QC + rename + release). Six it 1. **File-system `.md` save dialog** — replaces clipboard-only export on HistoryPage + DictationPage + FilesPage. 2. **Bulk select + bulk export in History** — multi-select rows, export as a directory of `.md` files. -3. **LLM content tags** — on-demand `topic:*` + `intent:*` extraction using `kon-llm`, persisted on history rows. +3. **LLM content tags** — on-demand `topic:*` + `intent:*` extraction using `magnotia-llm`, persisted on history rows. 4. **Settings UX overhaul** — progressive disclosure. High-frequency settings always-visible; advanced behind `<details>` groups. Phase 8 sparkline toggle relocated out of Rituals. 5. **Visual polish pass** — spacing, typography, motion curves, dark-mode parity. Absorbs Phase 8 motion backlog on badge + sparkline. 6. **Accessibility pass** — keyboard navigation, focus order, screen reader labels (friendlier sparkline aria copy), WCAG AA contrast. Absorbs Phase 8 a11y backlog. @@ -298,9 +298,9 @@ Natural split for the plan: 9a (items 1 + 2), 9b (item 3), 9c (item 4), 9d (item ## Anchors -- Roadmap: [docs/roadmap/2026-04-23-corbie-feature-complete-roadmap.md](../../roadmap/2026-04-23-corbie-feature-complete-roadmap.md) +- Roadmap: [docs/roadmap/2026-04-23-magnotia-feature-complete-roadmap.md](../../roadmap/2026-04-23-magnotia-feature-complete-roadmap.md) - Phase 8 plan + spec + handover — for task-format reference and carryover backlog. - Phase 8 gotchas to respect in this phase: - `$derived` does not export at `.svelte.ts` module scope — use `export function` instead. - - `#[derive(sqlx::FromRow)]` is not available in `kon-storage` (no change in Phase 9, but noted if schema work drifts there). + - `#[derive(sqlx::FromRow)]` is not available in `magnotia-storage` (no change in Phase 9, but noted if schema work drifts there). - Progressive-disclosure pattern reference: NN/g on progressive disclosure; Material Design settings patterns; UX Collective on settings redesign. diff --git a/docs/whisper-ecosystem/brief.md b/docs/whisper-ecosystem/brief.md index d319f0f..1098286 100644 --- a/docs/whisper-ecosystem/brief.md +++ b/docs/whisper-ecosystem/brief.md @@ -1,16 +1,16 @@ -# Kon — Whisper Ecosystem Research Brief +# Magnotia — Whisper Ecosystem Research Brief *Spec handover for Claude Code. Mined across 10 open-source Whisper apps, synthesised April 2026. British English. Every claim URL-backed — see Sources.* ## TL;DR -Ten repos surveyed (whisper.cpp, whisper-rs, Handy, Buzz, Whispering/Epicenter, faster-whisper, WhisperLive, whisper_streaming, Scriberr, Vibe, plus OpenWhispr). The dominant pre-emptive patches for Kon cluster around **silent CUDA fallback, global-hotkey edge cases, MSVC C-runtime linking between `whisper-rs-sys` and `tokenizers`, clipboard overwrite on paste, and Tauri HTTP scope blocking localhost LLM calls**. The highest-leverage features to pinch are **an engine-abstraction crate (`transcribe-rs` pattern), a named-prompt transformation pipeline with raw-transcript preservation, Vulkan as the default GPU path on Windows, VAD-gated chunking with a hallucination blocklist, and a warm-up audio file at launch**. +Ten repos surveyed (whisper.cpp, whisper-rs, Handy, Buzz, Whispering/Epicenter, faster-whisper, WhisperLive, whisper_streaming, Scriberr, Vibe, plus OpenWhispr). The dominant pre-emptive patches for Magnotia cluster around **silent CUDA fallback, global-hotkey edge cases, MSVC C-runtime linking between `whisper-rs-sys` and `tokenizers`, clipboard overwrite on paste, and Tauri HTTP scope blocking localhost LLM calls**. The highest-leverage features to pinch are **an engine-abstraction crate (`transcribe-rs` pattern), a named-prompt transformation pipeline with raw-transcript preservation, Vulkan as the default GPU path on Windows, VAD-gated chunking with a hallucination blocklist, and a warm-up audio file at launch**. --- ## Cross-repo pain pattern matrix -| Pain point | Repos where observed | Severity for Kon | Recommended Kon mitigation | +| Pain point | Repos where observed | Severity for Magnotia | Recommended Magnotia mitigation | |---|---|---|---| | Silent CPU fallback when CUDA version mismatches | whisper.cpp #2857, #2214, #2297; faster-whisper #1398; Buzz FAQ; Handy #209 | **H** | Detect actual compute device at runtime, log it, show in UI; prefer Vulkan default on Windows | | Global-hotkey bugs (modifier-only, right-hand mods, F13–F24, single-key on Linux, Fn on macOS) | Handy #1143/#1105/#1019/#966/#956/#917/#47; Whispering #491/#500/#484/#549 | **H** | Dedicated cross-platform hotkey layer; per-OS capability matrix; reject invalid combos in UI | @@ -37,7 +37,7 @@ Ten repos surveyed (whisper.cpp, whisper-rs, Handy, Buzz, Whispering/Epicenter, ## Killer features inventory -| Feature | Source repo(s) | Implementation complexity | Kon priority | +| Feature | Source repo(s) | Implementation complexity | Magnotia priority | |---|---|---|---| | Engine abstraction crate (Whisper / Parakeet / Moonshine behind one trait) | Handy, Whispering (both use `transcribe-rs`) | **M** | MVP — unblocks Windows fallback off whisper-rs | | Vulkan backend as default Windows GPU path | whisper.cpp, Buzz, Vibe | **S** | MVP | @@ -45,7 +45,7 @@ Ten repos surveyed (whisper.cpp, whisper-rs, Handy, Buzz, Whispering/Epicenter, | VAD-gated chunking with Silero | whisper.cpp, Buzz v1.4.4, faster-whisper | **M** | MVP (already in streaming scope) | | Hallucination blocklist + confidence gate | WhisperLive #185, ufal #121 | **S** | MVP | | Warm-up WAV at launch | ufal (warm-up file PR) | **S** | MVP | -| LocalAgreement-n streaming commit policy | ufal | **M** | v1 (if Kon streaming currently naive) | +| LocalAgreement-n streaming commit policy | ufal | **M** | v1 (if Magnotia streaming currently naive) | | Initial prompt / custom-vocab biasing | Whispering PR #1132, Buzz, Handy custom-words | **S** | MVP | | Progressive audio save (crash-safe) | Whispering | **S** | MVP | | Keep audio on disk when transcription fails, offer retry | Handy v0.8.0 | **S** | MVP | @@ -74,16 +74,16 @@ Ten repos surveyed (whisper.cpp, whisper-rs, Handy, Buzz, Whispering/Epicenter, - **LocalAgreement-n is the de-facto streaming policy** for un-fine-tuned Whisper. Emission latency ≈ 2× chunk size, and every chunk is re-transcribed multiple times, which is costly but correct. Newer AlignAtt (ufal/SimulStreaming, IWSLT 2025 winner) is ~5× faster — flag for a later swap. - **Prompt carry-over is a two-edged sword.** Helpful for proper nouns (ufal's `static_init_prompt`), lethal on repetition loops (#161) or when the model wasn't trained with prompt conditioning (#133). Expose `condition_on_previous_text` as a runtime toggle and reset context on repetition detection. - **Cold-start latency is universal (~4–5 s on first chunk).** Run a short silent WAV at app launch and after any GPU/ANE idle. -- **Backend abstraction is mandatory for Kon's five-platform target.** WhisperLive runs faster-whisper / TensorRT-LLM / OpenVINO behind one socket; ufal swaps faster-whisper / whisper-timestamped / MLX / OpenAI API. Bake this in early. +- **Backend abstraction is mandatory for Magnotia's five-platform target.** WhisperLive runs faster-whisper / TensorRT-LLM / OpenVINO behind one socket; ufal swaps faster-whisper / whisper-timestamped / MLX / OpenAI API. Bake this in early. - **Reconnect/resume logic is under-engineered in both reference projects** (WhisperLive #388). For mobile targets, design resumable streaming from day one — no good OSS reference exists. - **First chunk triggers the full Whisper context window to allocate**, causing the latency cliff. Pre-allocate on warm-up. -- **Multi-client fan-out** (WhisperLive PR #174) is a clean pattern if Kon ever wants simultaneous transcribe + translate on one mic. +- **Multi-client fan-out** (WhisperLive PR #174) is a clean pattern if Magnotia ever wants simultaneous transcribe + translate on one mic. --- ## LLM formatting layer findings -- **Auto-apply LLM cleanup is where "LLM changed my meaning" complaints originate.** None of Scriberr/Vibe/OpenWhispr have this class of issue in volume because they keep LLM post-processing explicitly opt-in. Kon auto-applies — make it one-keystroke revertable and always show the raw transcript. +- **Auto-apply LLM cleanup is where "LLM changed my meaning" complaints originate.** None of Scriberr/Vibe/OpenWhispr have this class of issue in volume because they keep LLM post-processing explicitly opt-in. Magnotia auto-applies — make it one-keystroke revertable and always show the raw transcript. - **Ollama connectivity is the #1 source of user-facing LLM bugs** — Scriberr #111/#144, Vibe #438/#487. Solve with auto-detect, "Test connection", pre-approved Tauri HTTP capability for `127.0.0.1:11434` (and whichever port bundled llama.cpp uses), and a visible status chip. - **VRAM contention** (Scriberr #217): bundled LLM + Whisper on one consumer GPU evict each other. Default to sequential execution: finish Whisper, unload, run LLM. - **Feed plain text to the LLM, not raw Whisper JSON with timestamps** — Scriberr PR #288 made this switch and materially improved quality. @@ -91,7 +91,7 @@ Ten repos surveyed (whisper.cpp, whisper-rs, Handy, Buzz, Whispering/Epicenter, - **CUDA/cuDNN fragmentation pushes projects towards Vulkan** — Vibe migrated off CUDA-exe (300 MB) to Vulkan whisper.cpp precisely to escape version hell. Same logic applies to llama.cpp — prefer Vulkan backend on Windows. - **Non-English transcripts are already weaker** (Vibe Vietnamese/Hebrew). LLM "cleanup" will compound errors. Always keep raw-transcript revert; never rewrite history in the paste buffer. - **System-prompt framing matters.** Whispering's published baseline — *"translator from spoken to written form, not an editor trying to improve the content"* — directly mitigates overcorrection. Copy this framing. -- **Settings per task** (tiny model for titles, bigger for summaries) — Scriberr pattern. Kon can extend: faster model for paste-time cleanup, bigger for explicit summarise action. +- **Settings per task** (tiny model for titles, bigger for summaries) — Scriberr pattern. Magnotia can extend: faster model for paste-time cleanup, bigger for explicit summarise action. - **Streaming LLM output with cancellation** — standard Ollama/llama.cpp capability but rarely shipped; users hit X on 30-second summaries. --- @@ -146,15 +146,15 @@ Ten repos surveyed (whisper.cpp, whisper-rs, Handy, Buzz, Whispering/Epicenter, ## What couldn't be verified - **GitHub `/issues?q=...sort=reactions-%2B1-desc` URLs were blocked at the fetch layer for several repos.** Ordering of feature requests by raw reaction count is therefore inferred from comment volume, maintainer engagement, and release-note inclusion rather than numeric reaction tallies. -- **whisper-rs (GitHub) was archived on 30 July 2025; development continues at `codeberg.org/tazz4843/whisper-rs`.** Issue numbers cited post that date will not resolve on GitHub. Kon should track Codeberg or fork. +- **whisper-rs (GitHub) was archived on 30 July 2025; development continues at `codeberg.org/tazz4843/whisper-rs`.** Issue numbers cited post that date will not resolve on GitHub. Magnotia should track Codeberg or fork. - **OpenWhispr issue tracker was not fully mined within budget** — included as a named reference for the bundled-llama.cpp architecture only. Pain-point claims for it are sourced from its README and a third-party dev.to writeup, not from its issues. - **Whispering / EpicenterHQ issue tracker** was not fully mined — the repo was cited via its release notes and README; its issue base may contain further pain patterns worth a follow-up. - **Scriberr, Vibe PR numbers (#288, #294, #438, #487, #217, #111, #144)** were sourced via search-result snippets where direct issue-page fetches were refused; titles and content are quoted but exact current status (open/closed/merged as of 21 Apr 2026) was not re-checked on each. -- **faster-whisper is named MIT** but its heavy Python + CUDA runtime footprint makes it a pattern source, not a recommended Kon dependency. +- **faster-whisper is named MIT** but its heavy Python + CUDA runtime footprint makes it a pattern source, not a recommended Magnotia dependency. - **Scriberr** is **MIT, not AGPL** as the brief had hedged — full lifting is permitted; the "reference only" warning in the original task spec does not apply. - **WhisperLive** is **MIT confirmed** — full lifting permitted. - **`ufal/whisper_streaming` is maintenance-only**; upstream momentum has moved to `ufal/SimulStreaming` (also MIT). Consider tracking that repo for v1+. -- **Mobile (iOS, Android) pain surface** is under-represented in this survey — none of the ten desktop repos have a mature mobile story beyond whisper.cpp's XCFramework and WhisperLive's `ios-client`. Kon's mobile backlog will need a dedicated pass. +- **Mobile (iOS, Android) pain surface** is under-represented in this survey — none of the ten desktop repos have a mature mobile story beyond whisper.cpp's XCFramework and WhisperLive's `ios-client`. Magnotia's mobile backlog will need a dedicated pass. --- diff --git a/docs/whisper-ecosystem/kon-context.md b/docs/whisper-ecosystem/magnotia-context.md similarity index 86% rename from docs/whisper-ecosystem/kon-context.md rename to docs/whisper-ecosystem/magnotia-context.md index 8ddb6ff..13dc1c1 100644 --- a/docs/whisper-ecosystem/kon-context.md +++ b/docs/whisper-ecosystem/magnotia-context.md @@ -1,12 +1,12 @@ -# Kon — Engineering Context for Cursor Agents +# Magnotia — Engineering Context for Cursor Agents -*Canonical project context for Workstream A (Codex) and Workstream B (Opus). If you are a cloud-based AI agent working on Kon via Cursor, read this **before** reading `brief.md` — it tells you what is already shipped, what is intentionally deferred, and the ideology rules you cannot override.* +*Canonical project context for Workstream A (Codex) and Workstream B (Opus). If you are a cloud-based AI agent working on Magnotia via Cursor, read this **before** reading `brief.md` — it tells you what is already shipped, what is intentionally deferred, and the ideology rules you cannot override.* --- -## What Kon is +## What Magnotia is -Kon is a local-first, cognitive-load-aware dictation + task-capture desktop app. Stack: Tauri 2 + Rust workspace + Svelte 5 frontend. Current primary target is Linux (KDE Plasma Wayland); macOS and Windows are in scope. All transcription and LLM inference run locally — no cloud call is ever made implicitly. +Magnotia is a local-first, cognitive-load-aware dictation + task-capture desktop app. Stack: Tauri 2 + Rust workspace + Svelte 5 frontend. Current primary target is Linux (KDE Plasma Wayland); macOS and Windows are in scope. All transcription and LLM inference run locally — no cloud call is ever made implicitly. Key paths: @@ -44,7 +44,7 @@ Cloud provider support exists (`crates/cloud-providers`) but is empty. When it g ### 2. No second notes surface -Kon is not a notes app. The transcript surface is History + YAML-frontmatter markdown export to the user's existing notes tool (Obsidian). Transcript editing (fix recognition errors in the viewer window) is allowed. Tagging, starring, searching transcripts is allowed. Anything that starts to look like "edit long-form prose" or "sync notes to another device/cloud" is out of scope — stop and reconsider. +Magnotia is not a notes app. The transcript surface is History + YAML-frontmatter markdown export to the user's existing notes tool (Obsidian). Transcript editing (fix recognition errors in the viewer window) is allowed. Tagging, starring, searching transcripts is allowed. Anything that starts to look like "edit long-form prose" or "sync notes to another device/cloud" is out of scope — stop and reconsider. **Do not propose or build:** @@ -66,7 +66,7 @@ No feature may assume cloud availability. Offline install is the baseline; cloud ### 6. Low cognitive load -Kon is deliberately ADHD-aware. A new setting must *earn* its mental real estate. A new flow must *reduce* not add steps. When in doubt, pick the option that requires fewer user decisions. +Magnotia is deliberately ADHD-aware. A new setting must *earn* its mental real estate. A new flow must *reduce* not add steps. When in doubt, pick the option that requires fewer user decisions. --- @@ -74,7 +74,7 @@ Kon is deliberately ADHD-aware. A new setting must *earn* its mental real estate ### Whisper runtime: `whisper-rs` + Vulkan -Kon uses `whisper-rs` 0.16 with the `vulkan` feature ([crates/transcription/Cargo.toml](../../crates/transcription/Cargo.toml)), wrapping whisper.cpp. **Do NOT swap to faster-whisper / WhisperX / any PyTorch-based fork.** Both drag a Python runtime into the Tauri binary, which is a far larger tax than any speedup justifies. Runtime-side improvements go into whisper-rs / whisper.cpp. Model-side improvements (Distil-Whisper GGUF, Parakeet-as-English-default) are already shipped. +Magnotia uses `whisper-rs` 0.16 with the `vulkan` feature ([crates/transcription/Cargo.toml](../../crates/transcription/Cargo.toml)), wrapping whisper.cpp. **Do NOT swap to faster-whisper / WhisperX / any PyTorch-based fork.** Both drag a Python runtime into the Tauri binary, which is a far larger tax than any speedup justifies. Runtime-side improvements go into whisper-rs / whisper.cpp. Model-side improvements (Distil-Whisper GGUF, Parakeet-as-English-default) are already shipped. The `brief.md` references faster-whisper as a pattern source — treat it as one. Do not adopt it as a dependency. @@ -148,7 +148,7 @@ A per-user speech-gate + initial_prompt priming flow. Discussed and scoped, but ### Cloud-endpoint contract test -Pinned for when `kon-cloud-providers` actually grows a provider. Not actionable yet. +Pinned for when `magnotia-cloud-providers` actually grows a provider. Not actionable yet. ### ggml system-lib dedup @@ -160,7 +160,7 @@ Deferred. If ever built, use sherpa-onnx (already linked for Parakeet). ### Dragon-style "100-passage training" -Never. Whisper has no speaker adaptation; fine-tuning is out of Kon's shape. +Never. Whisper has no speaker adaptation; fine-tuning is out of Magnotia's shape. --- @@ -190,7 +190,7 @@ Never. Whisper has no speaker adaptation; fine-tuning is out of Kon's shape. - `get_active_compute_device` command — returns `{ engine, backend, device_name, fallback_reason? }` - `list_gpus` command — enumerates GPUs for the Settings dropdown -- Streaming cleanup variant — `cleanup_transcript_stream` emitting `kon:llm-token` events, abortable via `cancel_llm` +- Streaming cleanup variant — `cleanup_transcript_stream` emitting `magnotia:llm-token` events, abortable via `cancel_llm` Document these in `docs/whisper-ecosystem/workstream-A.md` before coding so Opus can stub frontends against the agreed shape. @@ -233,6 +233,6 @@ If Codex hasn't shipped these yet, stub the frontend behind a feature flag with 1. This document (you're in it). 2. [`docs/whisper-ecosystem/brief.md`](./brief.md) — the 31-item atomic task backlog is your source of truth. 3. [`HANDOVER.md`](../../HANDOVER.md) — latest session handover, Linux/Wayland specifics. -4. The specific Kon file pointers referenced under your workstream's "You own" list. +4. The specific Magnotia file pointers referenced under your workstream's "You own" list. Then write your `docs/whisper-ecosystem/workstream-{A,B}.md` execution plan (sequence, dependencies, contract shapes) and commit it before writing any non-doc code. diff --git a/docs/whisper-ecosystem/workstream-A.md b/docs/whisper-ecosystem/workstream-A.md index e4201b6..8bf1725 100644 --- a/docs/whisper-ecosystem/workstream-A.md +++ b/docs/whisper-ecosystem/workstream-A.md @@ -35,7 +35,7 @@ lib tests and moves on; no new code. The backlog groups itself into four natural phases. Each phase lands as a sequence of small, one-concern commits, with `cargo test --workspace ---lib && cargo build -p kon && npm run check` green at every commit +--lib && cargo build -p magnotia && npm run check` green at every commit boundary. At the end of each phase we pause for review before the next. ### Phase A.1 — Pre-emptive patches (no new UX surface) @@ -43,7 +43,7 @@ boundary. At the end of each phase we pause for review before the next. | Seq | Item | Effect | Depends on | |---|---|---|---| | 1 | **#2**: pre-approve `http://127.0.0.1:*` in Tauri capabilities for LLM | Adds `http:default-scope-127.0.0.1` style capability + widens `connect-src` CSP to localhost | — | -| 2 | **#6**: guard `whisper-rs-sys` + `tokenizers` on Windows | Audit: Kon doesn't link `tokenizers` directly; we add a `#[cfg(all(target_os = "windows"))]` compile-time assert in `kon-transcription/build.rs` that fails the build if anything in the tree pulls in `tokenizers` on Windows | — | +| 2 | **#6**: guard `whisper-rs-sys` + `tokenizers` on Windows | Audit: Magnotia doesn't link `tokenizers` directly; we add a `#[cfg(all(target_os = "windows"))]` compile-time assert in `magnotia-transcription/build.rs` that fails the build if anything in the tree pulls in `tokenizers` on Windows | — | | 3 | **#7**: CPU feature detection + non-AVX2 fallback surface | Extend `crates/core/src/hardware.rs` with `CpuFeatures` (avx2, avx512, fma, sse4_2) via `std::is_x86_feature_detected!` at runtime; surface as part of `RuntimeCapabilities`; when AVX2 is absent, emit a `warning` log + `runtime-warning` event so the frontend can show a banner | #1 | | 4 | **#1**: detect and surface active compute device | Extend `RuntimeCapabilities` with an `ActiveComputeDevice` struct (`kind: "cuda" \| "vulkan" \| "metal" \| "cpu"`, `label: String`, `reason: Option<String>`). For Whisper, read the first `whisper_print_system_info` line via a shim in `whisper_rs_backend`; reason is filled on any CPU fallback (e.g., "Vulkan loader not found") | #7 | | 5 | **#8**: checksum + resumable model downloads; retain audio on failure | Port the `crates/llm/src/model_manager.rs` pattern (SHA256 verify, `.part` file resume, `ResumeUnsupported` error) into `crates/transcription/src/model_manager.rs::download_file`. The existing code already does incremental SHA256 and range resume but lacks (a) validation of an **existing full file** before re-downloading, (b) ResumeUnsupported signalling, (c) test coverage parity. Retaining audio on failure is already covered in `commands/live.rs` via `save_audio` — add an explicit retry-friendly error classification so the frontend can render "Retry transcription" (a B surface, but the error enum ships here) | — | @@ -52,14 +52,14 @@ boundary. At the end of each phase we pause for review before the next. **Commit boundary:** `cargo test --workspace --lib` (must include new tests for checksum resume + CPU feature detect + active-device shim), -`cargo build -p kon`, `npm run check`. Then **stop for review.** +`cargo build -p magnotia`, `npm run check`. Then **stop for review.** ### Phase A.2 — Engine abstraction (#13, #19) | Seq | Item | Effect | Depends on | |---|---|---|---| -| 8 | **#13 (engine trait)**: `kon-transcription` already has `LocalEngine` and a `SpeechBackend` enum over `transcribe-rs` adapter + `WhisperRsBackend`. This is a 90% version of Handy's `transcribe-rs` pattern. We lift it one more inch: replace the enum with a public `Transcriber` trait (`load`, `transcribe_sync`, `capabilities`) + blanket impls for the existing two backends, and gate `WhisperRsBackend` behind the `whisper` feature so non-Whisper builds compile without pulling `whisper-rs-sys` | — | -| 9 | **#19**: progressive WAV write during live capture | Extend `commands/live.rs` to stream captured mono-16kHz samples directly into a `.wav` in the session folder via `kon_audio::WavWriter::append`. Replace the in-memory `kept_audio: Vec<f32>` when `save_audio` is on with a disk-backed writer; on crash, the partial file is a playable WAV. Commits the `commands/live.rs` frame-size bookkeeping untouched | #13 (uses `Transcriber::capabilities().sample_rate`) | +| 8 | **#13 (engine trait)**: `magnotia-transcription` already has `LocalEngine` and a `SpeechBackend` enum over `transcribe-rs` adapter + `WhisperRsBackend`. This is a 90% version of Handy's `transcribe-rs` pattern. We lift it one more inch: replace the enum with a public `Transcriber` trait (`load`, `transcribe_sync`, `capabilities`) + blanket impls for the existing two backends, and gate `WhisperRsBackend` behind the `whisper` feature so non-Whisper builds compile without pulling `whisper-rs-sys` | — | +| 9 | **#19**: progressive WAV write during live capture | Extend `commands/live.rs` to stream captured mono-16kHz samples directly into a `.wav` in the session folder via `magnotia_audio::WavWriter::append`. Replace the in-memory `kept_audio: Vec<f32>` when `save_audio` is on with a disk-backed writer; on crash, the partial file is a playable WAV. Commits the `commands/live.rs` frame-size bookkeeping untouched | #13 (uses `Transcriber::capabilities().sample_rate`) | **Commit boundary:** tests (`transcriber_trait_is_object_safe`, `wav_writer_survives_crash`), build, check. **Stop for review.** @@ -105,7 +105,7 @@ repetition detector. **Stop for review.** | `commands::models::get_runtime_capabilities` | **Extend** | Add `active_compute_device`, `cpu_features`, `parallel_mode_available` fields | | `commands::live::evaluate_speech_gate` | **Replace** with VAD-gated chunker, keep RMS version as a cheap fallback when VAD ONNX model missing | | `commands::live::run_live_session` | **Extend** | Switch buffer ownership to `streaming::StreamingSession`, keep the `cpal` plumbing | -| `crates/transcription/src/model_manager::download_file` | **Extend** | Harden to match `kon-llm`'s resume + sha behaviour, add `ResumeUnsupported` arm | +| `crates/transcription/src/model_manager::download_file` | **Extend** | Harden to match `magnotia-llm`'s resume + sha behaviour, add `ResumeUnsupported` arm | | `crates/transcription/src/whisper_rs_backend::transcribe_sync` | **Extend** | Surface `no_speech_prob` + `avg_logprob` per segment | | `crates/transcription/src/local_engine::SpeechBackend` | **Replace** with public `Transcriber` trait | | `crates/transcription/src/concurrency` | **Extend** | Already a stub; add GPU guard wrapper | @@ -225,7 +225,7 @@ behind `runtimeCapabilities.parallelModeAvailable`. ## Test strategy - Every phase ends green on `cargo test --workspace --lib && cargo - build -p kon && npm run check`. + build -p magnotia && npm run check`. - `phase4-systems-f7d0` never regresses the 116 existing lib tests. If a test starts failing and the cause is an A change, it's fixed in-phase, not deferred. @@ -266,5 +266,5 @@ behind `runtimeCapabilities.parallelModeAvailable`. - One concern per commit. Subject line `feat(A.2):`, `fix(A.1):`, etc., referencing the phase so the reviewer can group them. - Commit message body states which brief-item the commit closes. -- `cargo test --workspace --lib && cargo build -p kon && npm run check` +- `cargo test --workspace --lib && cargo build -p magnotia && npm run check` green before every commit, per the rules of engagement. diff --git a/docs/whisper-ecosystem/workstream-B.md b/docs/whisper-ecosystem/workstream-B.md index 2a67e61..414c4aa 100644 --- a/docs/whisper-ecosystem/workstream-B.md +++ b/docs/whisper-ecosystem/workstream-B.md @@ -42,14 +42,14 @@ From the Workstream B prompt: contract. Load-bearing. Any prompt edit that softens it gets rejected at review. 4. **Local-first.** No feature may assume cloud availability. Ollama - (#27) is additive — Kon works fully offline without it. + (#27) is additive — Magnotia works fully offline without it. --- ## Execution order Three phases, each one commit per concern, each ending with a green -`cargo build -p kon && npm run check`. We stop at each phase boundary +`cargo build -p magnotia && npm run check`. We stop at each phase boundary for review. ### Phase B.1 — Pre-emptive UX (items #3, #4 UX, #5, #10, #11) @@ -61,11 +61,11 @@ a bug, which matches the brief's "pre-emptive patches" framing. |---|---|---|---| | 1 | **#3**: clipboard snapshot + restore after paste | `src-tauri/src/commands/paste.rs` already paste-matrix-hardens for Wayland. Extend it: before `Clipboard::new().set_text(text)`, read the existing `Clipboard::get_text()` snapshot (+ `get_image()` if supported by arboard on this target). Schedule a `tokio::spawn` task that sleeps 300ms after paste and restores the snapshot. User's prior clipboard content is therefore recovered automatically. Keep the `hide_preview_overlay_for_paste` dance. | No | | 2 | **#4 (UX side)**: debounce hotkey, add "warming up…" state | A warms the CPAL stream at app start. B owns the `page.status` state machine: the hotkey handler in `DictationPage.svelte` already gates on `page.recording`; add a ~120ms debounce on the hotkey press so a rapid double-tap doesn't double-init. Existing native capture path is untouched. | Stream warm-up lands in A's Phase A.3 (item #23); B's debounce is independent. | -| 3 | **#5**: hotkey capability matrix per OS, UI rejection of invalid combos | `src/lib/components/HotkeyRecorder.svelte` (or equivalent). Add `hotkeyValidity.ts` returning `{ valid: boolean, reason: string \| null }` given a combo + OS. Rules: on X11, reject single-key combos and report "Add a modifier to capture this key outside Kon"; on Windows, reject combos whose only modifier is a right-hand variant (RCtrl/RAlt — per `Handy` #966); on macOS, reject Fn-only combos. Block save button + surface inline "why" copy. | No | +| 3 | **#5**: hotkey capability matrix per OS, UI rejection of invalid combos | `src/lib/components/HotkeyRecorder.svelte` (or equivalent). Add `hotkeyValidity.ts` returning `{ valid: boolean, reason: string \| null }` given a combo + OS. Rules: on X11, reject single-key combos and report "Add a modifier to capture this key outside Magnotia"; on Windows, reject combos whose only modifier is a right-hand variant (RCtrl/RAlt — per `Handy` #966); on macOS, reject Fn-only combos. Block save button + surface inline "why" copy. | No | | 4 | **#10**: detect focused-app class; force clipboard-only paste in terminals | Extend `src-tauri/src/commands/paste.rs::paste_text` to look up the focused window class (GetForegroundWindow → class name on Windows; `xdotool getactivewindow getwindowclassname` on Linux X11; `CGWindowListCopyWindowInfo` → ownerName on macOS). On a known-terminal class (`Alacritty`, `kitty`, `gnome-terminal-server`, `WindowsTerminal`, `Code`, `iTerm2`, `Terminal`, etc.) skip the keystroke and just set the clipboard; return `outcome.pasted = false, outcome.copied = true, message = "Terminal detected — clipboard only"`. UX handles that message already. | No | -| 5 | **#11**: versioned settings schema with forward migration | `src/lib/stores/page.svelte.ts` currently reads `kon_settings` localStorage blob via `parseStoredJson` and spreads it over `defaults`. Add a `SettingsSchemaVersion` integer (start at 1) and a `migrateSettings(raw, toVersion)` helper. Persist `{ version, data }` on save; read both on load; run migrations in order; fall back cleanly on corruption (drop to defaults, toast the user once). Every new field B adds (below) bumps the version. | No | +| 5 | **#11**: versioned settings schema with forward migration | `src/lib/stores/page.svelte.ts` currently reads `magnotia_settings` localStorage blob via `parseStoredJson` and spreads it over `defaults`. Add a `SettingsSchemaVersion` integer (start at 1) and a `migrateSettings(raw, toVersion)` helper. Persist `{ version, data }` on save; read both on load; run migrations in order; fall back cleanly on corruption (drop to defaults, toast the user once). Every new field B adds (below) bumps the version. | No | -**Commit boundary:** `cargo build -p kon && npm run check` green for +**Commit boundary:** `cargo build -p magnotia && npm run check` green for every commit; JS-side vitest isn't set up in this repo, so the migration tests live under `src/lib/utils/__tests__/` as pure TS + `svelte-check`. If tests need a runner we borrow Codex's setup in @@ -76,7 +76,7 @@ Phase B.2. **Stop for review.** | Seq | Item | Effect | Depends on A? | |---|---|---|---| | 6 | **#14 (UI)**: GPU enumeration + explicit device selector in Settings | Adds a `Compute device` row to `SettingsPage.svelte` under the existing `openSection === 'transcription'` section. Reads `runtimeCapabilities.activeComputeDevice` (A ships this in Phase A.1 #1) and `list_gpus()` (A ships in Phase A.2). If `list_gpus` isn't shipped yet, B ships the UI behind `if (runtimeCapabilities.activeComputeDevice)` and a TODO to call `list_gpus` later. `set_preferred_gpu(id)` is wired to a save-on-select handler with a "Restart to apply" toast. No default change until user picks. | Yes — A ships `list_gpus` in Phase A.2. B stubs behind the `activeComputeDevice` check until then. | -| 7 | **#15**: named LLM prompt presets | Preset data lives client-side in `src/lib/stores/promptPresets.svelte.ts` (new). Five presets shipped: `Quick clean` (default), `Email`, `Meeting notes`, `Code`, `Summary`. Each is `{ id, name, systemPrompt, modelTier }`. User-editable list stored in `localStorage['kon_prompt_presets']` with migration hook. Settings gets a new "Prompt presets" sub-card inside `openSection === 'ai'`; DictationPage gets a small preset pill above the Status bar — active preset is applied at cleanup time. Default preset's `systemPrompt` is `CLEANUP_PROMPT` from `llm_client.rs`, so the existing baseline is preserved. | No — uses existing `cleanup_transcript_text_cmd(transcript, profile_id)`. If the backend later needs `system_prompt_override` (not strictly needed for MVP — UI just restricts _when_ cleanup fires), B adds that with A's sign-off. | +| 7 | **#15**: named LLM prompt presets | Preset data lives client-side in `src/lib/stores/promptPresets.svelte.ts` (new). Five presets shipped: `Quick clean` (default), `Email`, `Meeting notes`, `Code`, `Summary`. Each is `{ id, name, systemPrompt, modelTier }`. User-editable list stored in `localStorage['magnotia_prompt_presets']` with migration hook. Settings gets a new "Prompt presets" sub-card inside `openSection === 'ai'`; DictationPage gets a small preset pill above the Status bar — active preset is applied at cleanup time. Default preset's `systemPrompt` is `CLEANUP_PROMPT` from `llm_client.rs`, so the existing baseline is preserved. | No — uses existing `cleanup_transcript_text_cmd(transcript, profile_id)`. If the backend later needs `system_prompt_override` (not strictly needed for MVP — UI just restricts _when_ cleanup fires), B adds that with A's sign-off. | | 8 | **#17**: raw-transcript revert with ⌘/Ctrl+Z within 5 s | `src/lib/pages/DictationPage.svelte::finaliseTranscription` keeps `rawTranscript` alongside `transcript`. After paste, start a 5-second window where Ctrl/⌘+Z caught globally (via `@tauri-apps/plugin-global-shortcut` ephemerally, or via Tauri IPC from the main window) replaces the last paste: clipboard reset → previous-transcript paste. Preview overlay adds a 5s countdown chip. Untouched clipboard snapshot from #3 is reused. The revert hook lives in `src/routes/preview/+page.svelte` phase machine: new `revertable: true` transient on `phase === "final"`. | No — builds on #3 and the existing paste command. | | 9 | **#20**: sound cues for start / stop / complete | Small sound-cue module `src/lib/utils/soundCues.ts`: preloads three short WAV/OGG assets (under `static/sounds/`), uses the WebAudio API, caps volume at the user's mute + slider. Settings adds a new `Sound cues` card under `openSection === 'audio'` with a toggle, three preview buttons, a volume slider. Default: **on**, 50% volume. | No | diff --git a/package-lock.json b/package-lock.json index 11b4dfe..b64eab2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "kon", + "name": "magnotia", "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "kon", + "name": "magnotia", "version": "0.1.0", "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 75d1deb..f35a927 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "kon", + "name": "magnotia", "version": "0.1.0", - "description": "Kon — Think out loud", + "description": "Magnotia — Think out loud", "type": "module", "scripts": { "dev": "npm run dev:frontend", diff --git a/run.sh b/run.sh index 84ea724..593e362 100755 --- a/run.sh +++ b/run.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Kon dev launcher. Starts Vite first, waits for it to bind port 1420, +# Magnotia dev launcher. Starts Vite first, waits for it to bind port 1420, # then runs Tauri without triggering a second Vite instance. # For performance testing use: npm run tauri build set -euo pipefail diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 185bd83..2f457a2 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -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" diff --git a/src-tauri/build.rs b/src-tauri/build.rs index 5ed5e02..bcdbc85 100644 --- a/src-tauri/build.rs +++ b/src-tauri/build.rs @@ -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 diff --git a/src-tauri/resources/windows/README.md b/src-tauri/resources/windows/README.md index b8febfe..21997a9 100644 --- a/src-tauri/resources/windows/README.md +++ b/src-tauri/resources/windows/README.md @@ -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. diff --git a/src-tauri/src/commands/audio.rs b/src-tauri/src/commands/audio.rs index 25f3bda..9c3a785 100644 --- a/src-tauri/src/commands/audio.rs +++ b/src-tauri/src/commands/audio.rs @@ -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())??; diff --git a/src-tauri/src/commands/diagnostics.rs b/src-tauri/src/commands/diagnostics.rs index d7b38f2..fb71de1 100644 --- a/src-tauri/src/commands/diagnostics.rs +++ b/src-tauri/src/commands/diagnostics.rs @@ -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()) diff --git a/src-tauri/src/commands/feedback.rs b/src-tauri/src/commands/feedback.rs index edb6689..483e3d5 100644 --- a/src-tauri/src/commands/feedback.rs +++ b/src-tauri/src/commands/feedback.rs @@ -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, }; diff --git a/src-tauri/src/commands/fs.rs b/src-tauri/src/commands/fs.rs index 17a5b75..2cc3d11 100644 --- a/src-tauri/src/commands/fs.rs +++ b/src-tauri/src/commands/fs.rs @@ -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; diff --git a/src-tauri/src/commands/hardware.rs b/src-tauri/src/commands/hardware.rs index 7c0d7a7..a09d931 100644 --- a/src-tauri/src/commands/hardware.rs +++ b/src-tauri/src/commands/hardware.rs @@ -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, diff --git a/src-tauri/src/commands/hotkey.rs b/src-tauri/src/commands/hotkey.rs index d8a399d..e3f6340 100644 --- a/src-tauri/src/commands/hotkey.rs +++ b/src-tauri/src/commands/hotkey.rs @@ -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", ()); } } } diff --git a/src-tauri/src/commands/intentions.rs b/src-tauri/src/commands/intentions.rs index 64ce69b..b793d9a 100644 --- a/src-tauri/src/commands/intentions.rs +++ b/src-tauri/src/commands/intentions.rs @@ -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, diff --git a/src-tauri/src/commands/live.rs b/src-tauri/src/commands/live.rs index ee5ef04..d910dab 100644 --- a/src-tauri/src/commands/live.rs +++ b/src-tauri/src/commands/live.rs @@ -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, diff --git a/src-tauri/src/commands/llm.rs b/src-tauri/src/commands/llm.rs index e83183a..5883f69 100644 --- a/src-tauri/src/commands/llm.rs +++ b/src-tauri/src/commands/llm.rs @@ -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 diff --git a/src-tauri/src/commands/meeting.rs b/src-tauri/src/commands/meeting.rs index 8d7e5cc..37571ac 100644 --- a/src-tauri/src/commands/meeting.rs +++ b/src-tauri/src/commands/meeting.rs @@ -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` diff --git a/src-tauri/src/commands/models.rs b/src-tauri/src/commands/models.rs index 630f803..bd0b741 100644 --- a/src-tauri/src/commands/models.rs +++ b/src-tauri/src/commands/models.rs @@ -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 => { diff --git a/src-tauri/src/commands/nudges.rs b/src-tauri/src/commands/nudges.rs index 4973cfd..9eb6e8a 100644 --- a/src-tauri/src/commands/nudges.rs +++ b/src-tauri/src/commands/nudges.rs @@ -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}")) diff --git a/src-tauri/src/commands/paste.rs b/src-tauri/src/commands/paste.rs index 57b098f..73646ec 100644 --- a/src-tauri/src/commands/paste.rs +++ b/src-tauri/src/commands/paste.rs @@ -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] diff --git a/src-tauri/src/commands/profiles.rs b/src-tauri/src/commands/profiles.rs index 4160b77..7b4b105 100644 --- a/src-tauri/src/commands/profiles.rs +++ b/src-tauri/src/commands/profiles.rs @@ -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, diff --git a/src-tauri/src/commands/rituals.rs b/src-tauri/src/commands/rituals.rs index b1916bb..aa1e139 100644 --- a/src-tauri/src/commands/rituals.rs +++ b/src-tauri/src/commands/rituals.rs @@ -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 diff --git a/src-tauri/src/commands/tasks.rs b/src-tauri/src/commands/tasks.rs index 00f760c..d0f2ff3 100644 --- a/src-tauri/src/commands/tasks.rs +++ b/src-tauri/src/commands/tasks.rs @@ -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, diff --git a/src-tauri/src/commands/transcription.rs b/src-tauri/src/commands/transcription.rs index 8a48acb..ec7720a 100644 --- a/src-tauri/src/commands/transcription.rs +++ b/src-tauri/src/commands/transcription.rs @@ -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() diff --git a/src-tauri/src/commands/transcripts.rs b/src-tauri/src/commands/transcripts.rs index c712125..f2f61af 100644 --- a/src-tauri/src/commands/transcripts.rs +++ b/src-tauri/src/commands/transcripts.rs @@ -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, diff --git a/src-tauri/src/commands/windows.rs b/src-tauri/src/commands/windows.rs index b7d141f..91477f7 100644 --- a/src-tauri/src/commands/windows.rs +++ b/src-tauri/src/commands/windows.rs @@ -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) diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 0bf9b65..873690e 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -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"); } diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index f61cf83..5a60a52 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -1,5 +1,5 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] fn main() { - kon_lib::run() + magnotia_lib::run() } diff --git a/src-tauri/src/tray.rs b/src-tauri/src/tray.rs index 73c210a..e387cc5 100644 --- a/src-tauri/src/tray.rs +++ b/src-tauri/src/tray.rs @@ -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); diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index d3ffd7a..9e22814 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -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, diff --git a/src-tauri/tauri.linux.conf.json b/src-tauri/tauri.linux.conf.json index fa59fc4..68b544c 100644 --- a/src-tauri/tauri.linux.conf.json +++ b/src-tauri/tauri.linux.conf.json @@ -3,7 +3,7 @@ "app": { "windows": [ { - "title": "Kon", + "title": "Magnotia", "width": 1020, "height": 720, "minWidth": 960, diff --git a/src/app.css b/src/app.css index 425eaf3..7786dd9 100644 --- a/src/app.css +++ b/src/app.css @@ -37,7 +37,7 @@ @import "tailwindcss"; -/* === Kon Design Tokens === */ +/* === Magnotia Design Tokens === */ @theme { /* Surfaces — warm layered depth */ --color-bg: #0f0e0c; @@ -94,9 +94,9 @@ --duration-decorative: 300ms; /* Window resize hit zones — consumed by ResizeHandles.svelte. One source - of truth so every Kon window feels identical. */ - --kon-resize-edge: 12px; - --kon-resize-corner: 20px; + of truth so every Magnotia window feels identical. */ + --magnotia-resize-edge: 12px; + --magnotia-resize-corner: 20px; } /* === Button Component Classes === */ @@ -430,7 +430,7 @@ textarea, input, [data-no-transition] { parse it as a per-component virtual CSS module, which triggers an "Invalid declaration" error on plain CSS inputs in some Tailwind v4 + Svelte 5 combinations. */ -.kon-rh { +.magnotia-rh { position: fixed; z-index: 2147483646; background: transparent; @@ -440,67 +440,67 @@ textarea, input, [data-no-transition] { -webkit-user-select: none; } -.kon-rh-nw, -.kon-rh-ne, -.kon-rh-sw, -.kon-rh-se { +.magnotia-rh-nw, +.magnotia-rh-ne, +.magnotia-rh-sw, +.magnotia-rh-se { z-index: 2147483647; } -.kon-rh-n { +.magnotia-rh-n { top: 0; - left: var(--kon-resize-corner); - right: var(--kon-resize-corner); - height: var(--kon-resize-edge); + left: var(--magnotia-resize-corner); + right: var(--magnotia-resize-corner); + height: var(--magnotia-resize-edge); cursor: n-resize; } -.kon-rh-s { +.magnotia-rh-s { bottom: 0; - left: var(--kon-resize-corner); - right: var(--kon-resize-corner); - height: var(--kon-resize-edge); + left: var(--magnotia-resize-corner); + right: var(--magnotia-resize-corner); + height: var(--magnotia-resize-edge); cursor: s-resize; } -.kon-rh-w { - top: var(--kon-resize-corner); - bottom: var(--kon-resize-corner); +.magnotia-rh-w { + top: var(--magnotia-resize-corner); + bottom: var(--magnotia-resize-corner); left: 0; - width: var(--kon-resize-edge); + width: var(--magnotia-resize-edge); cursor: w-resize; } -.kon-rh-e { - top: var(--kon-resize-corner); - bottom: var(--kon-resize-corner); +.magnotia-rh-e { + top: var(--magnotia-resize-corner); + bottom: var(--magnotia-resize-corner); right: 0; - width: var(--kon-resize-edge); + width: var(--magnotia-resize-edge); cursor: e-resize; } -.kon-rh-nw { +.magnotia-rh-nw { top: 0; left: 0; - width: var(--kon-resize-corner); - height: var(--kon-resize-corner); + width: var(--magnotia-resize-corner); + height: var(--magnotia-resize-corner); cursor: nw-resize; } -.kon-rh-ne { +.magnotia-rh-ne { top: 0; right: 0; - width: var(--kon-resize-corner); - height: var(--kon-resize-corner); + width: var(--magnotia-resize-corner); + height: var(--magnotia-resize-corner); cursor: ne-resize; } -.kon-rh-sw { +.magnotia-rh-sw { bottom: 0; left: 0; - width: var(--kon-resize-corner); - height: var(--kon-resize-corner); + width: var(--magnotia-resize-corner); + height: var(--magnotia-resize-corner); cursor: sw-resize; } -.kon-rh-se { +.magnotia-rh-se { bottom: 0; right: 0; - width: var(--kon-resize-corner); - height: var(--kon-resize-corner); + width: var(--magnotia-resize-corner); + height: var(--magnotia-resize-corner); cursor: se-resize; } diff --git a/src/app.html b/src/app.html index 5dbf136..322eb0d 100644 --- a/src/app.html +++ b/src/app.html @@ -4,7 +4,7 @@ <meta charset="utf-8" /> <link rel="icon" href="%sveltekit.assets%/favicon.png" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> - <title>Kon + Magnotia %sveltekit.head% diff --git a/src/assets/wordmark.svg b/src/assets/wordmark.svg index 350d6c6..d8e2969 100644 --- a/src/assets/wordmark.svg +++ b/src/assets/wordmark.svg @@ -1,5 +1,5 @@ - Kon + Magnotia THINK OUT LOUD \ No newline at end of file diff --git a/src/design-system/README.md b/src/design-system/README.md index eb735bd..4ffc3cb 100644 --- a/src/design-system/README.md +++ b/src/design-system/README.md @@ -1,16 +1,16 @@ -# Kon — Design System +# Magnotia — Design System > **Think out loud.** A voice-first dictation app built for neurodivergent minds, by CORBEL. -## What is Kon? +## What is Magnotia? -Kon is a local-first dictation + thought-capture app. You press a hotkey, speak, and Kon transcribes, cleans, and — optionally — extracts tasks from what you said. Everything runs on your machine (Whisper / Parakeet models); nothing is sent anywhere. +Magnotia is a local-first dictation + thought-capture app. You press a hotkey, speak, and Magnotia transcribes, cleans, and — optionally — extracts tasks from what you said. Everything runs on your machine (Whisper / Parakeet models); nothing is sent anywhere. **Company:** CORBEL — an AI consultancy and technology integrator that removes friction from day-to-day life and work. **Product essence:** *Clarity without friction.* Every design decision is measured against it. If something adds cognitive load, it doesn't ship. -**Archetypes:** Sage (primary) + Magician (secondary). Kon listens more than it speaks. +**Archetypes:** Sage (primary) + Magician (secondary). Magnotia listens more than it speaks. **Visual reference points:** The Barbican, Muji, Amsterdam urban design, Nujabes album art — warm brutalism, honest materials, structural clarity. **Anti-references:** Notion-style feature density, generic blue/white SaaS, FAANG aesthetics. @@ -18,9 +18,9 @@ Kon is a local-first dictation + thought-capture app. You press a hotkey, speak, ## Sources -- **Repo:** `github.com/jakejars/kon` — Tauri + Svelte 5 + Tailwind v4 desktop app. Imported into `kon-source/` (lib components + routes). +- **Repo:** `github.com/jakejars/magnotia` — Tauri + Svelte 5 + Tailwind v4 desktop app. Imported into `magnotia-source/` (lib components + routes). - **Design brief:** Pasted in the initial message — complete token system, type scale, motion guidelines, ideology. -- **Key reads in `kon-source/`:** +- **Key reads in `magnotia-source/`:** - `app.css` — @theme tokens, global base, zone + motion system - `lib/Sidebar.svelte` — navigation shell - `lib/pages/DictationPage.svelte` — record / transcribe (the hero screen) @@ -41,8 +41,8 @@ Kon is a local-first dictation + thought-capture app. You press a hotkey, speak, | `SKILL.md` | Agent-skill manifest (cross-compatible with Claude Code Agent Skills). | | `assets/` | Logos, the sinhala wordmark glyph, grain texture, icon references | | `preview/` | HTML spec cards shown in the Design System tab (swatches, specimens, components) | -| `ui_kits/kon-app/` | UI kit — pixel-level recreation of the Kon desktop app with interactive click-thru | -| `kon-source/` | Imported Svelte source — the ground truth for component structure | +| `ui_kits/magnotia-app/` | UI kit — pixel-level recreation of the Magnotia desktop app with interactive click-thru | +| `magnotia-source/` | Imported Svelte source — the ground truth for component structure | No slide template was attached, so `slides/` is not created. @@ -52,12 +52,12 @@ No slide template was attached, so `slides/` is not created. > **Voice:** A calm friend who has already done the anxious part for you. -**Person.** Addresses the user as *you*; never refers to itself as *I*. Brand name "Kon" appears sparingly — the product speaks, it doesn't narrate itself. *(Settings labels: "Save recordings", "British English" — never "I'll save your recordings".)* +**Person.** Addresses the user as *you*; never refers to itself as *I*. Brand name "Magnotia" appears sparingly — the product speaks, it doesn't narrate itself. *(Settings labels: "Save recordings", "British English" — never "I'll save your recordings".)* **Sentence shape.** Short. Direct. Rarely more than ~12 words per line of UI copy. Never exclaims. Never hypes. Full stops inside UI labels are dropped; in prose (empty states, toasts) they're used naturally. **Tone.** -- **Ambient, not demanding.** Empty states are *"Your words will appear here…"* — not *"Record your first transcript!"* Blank screens trigger freeze; Kon offers presence instead of prompting. +- **Ambient, not demanding.** Empty states are *"Your words will appear here…"* — not *"Record your first transcript!"* Blank screens trigger freeze; Magnotia offers presence instead of prompting. - **Calm on failure.** Failed transcription copy leads with *"Your audio is saved"* — the anxiety reducer, not the apology. Never red all-caps. - **No guilt, no streaks.** No streak-shaming, no *"keep it up!"*, no urgency. - **British English.** "Minimise", "finalising", "customise". `britishEnglish` is a first-class setting. @@ -65,7 +65,7 @@ No slide template was attached, so `slides/` is not created. **Casing.** - Sentence case for all headings and buttons. *"Start recording"*, *"Clear all"*, *"Sensory zone"* — never *Start Recording* or *START RECORDING*. - Eyebrow labels (10px, uppercase, 0.12em tracking) are the **only** place all-caps appears — e.g. *THINK OUT LOUD* under the sidebar wordmark. Never apply uppercase to sentences. -- Brand name is always `Kon` (capital K, one lowercase letter). +- Brand name is always `Magnotia` (capital K, one lowercase letter). - Product tagline — *"Think out loud"* — always sentence case. **Buttons.** Verbs. *Record, Stop, Save, Copy, Clear, Export, Extract Tasks, Open viewer.* The one exception is *Tasks* (noun) as a sidebar toggle — consistent with its icon label. @@ -147,7 +147,7 @@ Lucide icons, 2px stroke, rounded terminals. 16px in nav, 20px in features, 24px ## Iconography -**Library:** [Lucide Icons](https://lucide.dev) (MIT). Every Lucide glyph Kon uses is listed in `kon-source/docs/icon-mapping.md`. +**Library:** [Lucide Icons](https://lucide.dev) (MIT). Every Lucide glyph Magnotia uses is listed in `magnotia-source/docs/icon-mapping.md`. **Stroke weight:** 2px default · 2.5 on active sidebar items · 1 for empty-state illustrations (48px, opacity 40%). @@ -165,7 +165,7 @@ Lucide icons, 2px stroke, rounded terminals. 16px in nav, 20px in features, 24px - `--danger` for destructive and the recording state indicator - `--success` for completion checks -**Core Kon set (from the audit):** `Mic` (Dictation), `FileText` (Files), `SquareCheck` (Tasks), `Clock` (History), `Settings`, `Circle`/`Square` (record/stop), `Play`/`Pause`, `Search`, `Upload`, `Download`, `Copy`, `ChevronDown`, `ChevronLeft`, `ChevronRight`, `X`, `Check`, `Plus`, `Pin`, `User`, `AlignLeft`, `AlertTriangle`, `Loader2`. +**Core Magnotia set (from the audit):** `Mic` (Dictation), `FileText` (Files), `SquareCheck` (Tasks), `Clock` (History), `Settings`, `Circle`/`Square` (record/stop), `Play`/`Pause`, `Search`, `Upload`, `Download`, `Copy`, `ChevronDown`, `ChevronLeft`, `ChevronRight`, `X`, `Check`, `Plus`, `Pin`, `User`, `AlignLeft`, `AlertTriangle`, `Loader2`. **Brand glyph:** The Sinhala letter **ක** (U+0D9A) sits next to the wordmark in `--accent`. Decorative only. During recording it spins slowly (2s linear infinite, `sinhala-spin`). A Unicode codepoint, not an SVG — scales perfectly, no asset needed. diff --git a/src/design-system/SKILL.md b/src/design-system/SKILL.md index c22ff74..a52a96d 100644 --- a/src/design-system/SKILL.md +++ b/src/design-system/SKILL.md @@ -1,6 +1,6 @@ --- -name: kon-design -description: Use this skill to generate well-branded interfaces and assets for Kon (by CORBEL), either for production or throwaway prototypes/mocks/etc. Contains essential design guidelines, colors, type, fonts, assets, and UI kit components for prototyping. +name: magnotia-design +description: Use this skill to generate well-branded interfaces and assets for Magnotia (by CORBEL), either for production or throwaway prototypes/mocks/etc. Contains essential design guidelines, colors, type, fonts, assets, and UI kit components for prototyping. user-invocable: true --- @@ -21,6 +21,6 @@ If the user invokes this skill without any other guidance, ask them what they wa - `colors_and_type.css` — import this for all tokens + semantic type. - `README.md` — full brand + content + visual foundations. - `assets/` — logos, glyph, grain, icon samples. -- `ui_kits/kon-app/` — component recreations of the desktop app. +- `ui_kits/magnotia-app/` — component recreations of the desktop app. - `preview/` — spec cards (swatches, specimens, components). -- `kon-source/` — ground-truth Svelte source from the Kon repo. +- `magnotia-source/` — ground-truth Svelte source from the Magnotia repo. diff --git a/src/design-system/colors_and_type.css b/src/design-system/colors_and_type.css index 221f814..13949e3 100644 --- a/src/design-system/colors_and_type.css +++ b/src/design-system/colors_and_type.css @@ -1,10 +1,10 @@ /* ============================================================ - Kon — Colors & Type + Magnotia — Colors & Type Single source of truth for CSS variables + semantic type. - Import this into any Kon design artefact. + Import this into any Magnotia design artefact. ============================================================ */ -/* --- Brand Webfonts (local woff2, shipped with Kon) --- */ +/* --- Brand Webfonts (local woff2, shipped with Magnotia) --- */ @font-face { font-family: 'Lexend'; src: url('fonts/lexend-variable.woff2') format('woff2-variations'), @@ -240,7 +240,7 @@ a:hover { color: var(--accent-hover); } } :root[data-theme="light"] .grain::after { opacity: 0.015; } -/* Kon sinhala glyph ක (U+0D9A) — wordmark companion */ +/* Magnotia sinhala glyph ක (U+0D9A) — wordmark companion */ .k-glyph::after { content: '\0D9A'; color: var(--accent); margin-left: 0.25em; display: inline-block; } @media (prefers-reduced-motion: reduce) { diff --git a/src/design-system/preview/brand-wordmark.html b/src/design-system/preview/brand-wordmark.html index af3ad6d..9e425a4 100644 --- a/src/design-system/preview/brand-wordmark.html +++ b/src/design-system/preview/brand-wordmark.html @@ -12,7 +12,7 @@

-
Kon
+
Magnotia
Think out loud
diff --git a/src/design-system/preview/components-toasts.html b/src/design-system/preview/components-toasts.html index 209c4f8..e792365 100644 --- a/src/design-system/preview/components-toasts.html +++ b/src/design-system/preview/components-toasts.html @@ -14,6 +14,6 @@
Toasts · bottom-right · sticky on error
Could not start recording
Audio device disconnected. Plug it back in and try again.
-
Dropped 2s of older audio
Kon is keeping up in real time.
+
Dropped 2s of older audio
Magnotia is keeping up in real time.
Saved · 3 tasks extracted
Your audio is saved. Open Tasks to review.
diff --git a/src/design-system/preview/type-body.html b/src/design-system/preview/type-body.html index 224cb6d..67841d8 100644 --- a/src/design-system/preview/type-body.html +++ b/src/design-system/preview/type-body.html @@ -4,7 +4,7 @@ .row{display:flex;align-items:flex-start;gap:18px;margin-bottom:14px} .l{font:400 11px var(--font-mono);color:var(--text-tertiary);min-width:110px;padding-top:4px} -
Body · 16/1.5
Kon listens more than it speaks. Press the hotkey, speak your thought, and your words appear — saved, searchable, left exactly where you put them.
+
Body · 16/1.5
Magnotia listens more than it speaks. Press the hotkey, speak your thought, and your words appear — saved, searchable, left exactly where you put them.
Body large · 18/1.6
A calm friend who has already done the anxious part for you.
Small · 13
Press record or Ctrl+Shift+R
Caption · 12
12 words · 00:47 · live
diff --git a/src/design-system/preview/type-transcript-mono.html b/src/design-system/preview/type-transcript-mono.html index dc71b60..8ccdd14 100644 --- a/src/design-system/preview/type-transcript-mono.html +++ b/src/design-system/preview/type-transcript-mono.html @@ -8,6 +8,6 @@
Transcript · 16–24px · 1.85 · +0.01em · liga + kern
So the thing I wanted to capture is — if we move the onboarding to the second screen instead of the first, the friction around first launch basically disappears. That feels right.
-
~/Library/Application Support/Kon/transcripts.db
+
~/Library/Application Support/Magnotia/transcripts.db
CtrlShiftRGlobal record toggle
diff --git a/src/design-system/ui_kits/DictationPage.jsx b/src/design-system/ui_kits/DictationPage.jsx index af7557e..ae66384 100644 --- a/src/design-system/ui_kits/DictationPage.jsx +++ b/src/design-system/ui_kits/DictationPage.jsx @@ -8,7 +8,7 @@ function RecordButton({ recording, onToggle }) { transition:'all 150ms cubic-bezier(.2,.8,.2,1)', background: recording ? 'var(--danger)' : 'var(--accent)', boxShadow: recording ? '0 0 0 0 rgba(232,113,113,.4)' : '0 4px 20px rgba(232,168,124,.3)', - animation: recording ? 'konPulse 2s ease-in-out infinite' : 'none', + animation: recording ? 'magnotiaPulse 2s ease-in-out infinite' : 'none', }; return (