Replace all instances of the legacy product names "Kon" and "Corbie" with
"Magnotia" across user-facing copy, code identifiers, package names, bundle
ids, file paths, and documentation. Preserves the unrelated "konsole" (KDE
terminal) reference and the parent CORBEL company name.
- Renames 10 Rust crates (kon-* → magnotia-*) and the tauri binary
- Updates package.json, tauri.conf.json (productName + identifier)
- Renames CSS classes (kon-rh-* → magnotia-rh-*) and animations
- Renames brand and roadmap docs
- Regenerates Cargo.lock and package-lock.json
Verified: svelte-check passes; pure-rust crates compile under new names.
Two small Phase 1 follow-ups for the Android target:
1. tauri.conf.json: add `bundle.android.minSdkVersion: 24`. Android 7.0
is the floor — gives us Vulkan availability (for the eventual GPU
feature flag), AAudio for cpal, and is what Pixel-class hardware
tests against. Keeps the global `identifier` on `uk.co.corbel.kon`
for now; the Corbie rebrand sweep will land `corbel.technology.corbie`
as a single coherent commit.
2. src/lib/utils/runtime.ts: add `isAndroid()` and `isMobile()` helpers
alongside the existing `hasTauriRuntime()`. Both use UA sniffing —
sufficient for feature-gating UI, never for security decisions.
These are how the Svelte side will hide:
- hotkey config (no global hotkey API on Android)
- paste-mode picker (auto-paste maps to a copy-only flow)
- meeting auto-capture toggle (process list unavailable)
- multi-window buttons (open-viewer, open-float, etc.)
- system-tray-related affordances
Tauri 2 doesn't expose a synchronous platform-detection helper that
works during initial render, so UA sniffing is the pragmatic choice.
https://claude.ai/code/session_0189xUb6ie6t9qHkzatGZ9Rb
The Phase 9 bulk export utility had a single success toast that was emitted
even when zero of N writes succeeded — "Exported 0 of 5 transcripts to
folder/" reading like the user just deliberately exported nothing.
Branch on the result count:
- 0 of N: error toast pointing at the console for write failures.
- N of N: success toast.
- M of N: warn toast — partial export, with the same console pointer.
Single-file save (`saveTranscriptAsMarkdown`) was already correct:
explicit success on save, error on failure, silent on user-cancelled —
left untouched.
https://claude.ai/code/session_0189xUb6ie6t9qHkzatGZ9Rb
TranscriptEntry gains llmTags: string[]; TranscriptRow gains the
storage-shape llmTags: string. ContentTags type added. mapTranscriptRow
hydrates llmTags from the comma-joined column. saveTranscriptMeta
forwards llmTags through to update_transcript_meta_cmd, mirroring the
existing manualTags handling.
buildFrontmatter unions auto + manual + llm tags so exported markdown
surfaces every source in one tags: list.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Centralises the save-dialog plus write-file plumbing. suggestedFilename
slugs the title into "<slug>-<YYYY-MM-DD>.md". saveTranscriptAsMarkdown
opens the system save dialog and writes via write_text_file_cmd; on
cancel returns null with no toast or fallback. exportTranscriptsToDir
writes one .md per item to a chosen folder, in-batch collision suffix
" (2)" etc. Documents the deliberate non-check of pre-existing files
in the chosen directory.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
resolveVolume previously clamped any value >=1 to full blast. If a
future settings change ever leaks a 0–100 scale through (instead of
0–1), the user gets jump-scared at max volume. Treat any v>1 as a
scale-drift bug and play at DEFAULT_VOLUME instead.
Also reject NaN / Infinity explicitly rather than relying on the
<=0 branch catching them.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Hands-off feedback for recording lifecycle: a short C5 pitch at
record start, a falling G4 at record stop, and a staggered C5–E5–G5
major third when the finalise flow completes. Synthesised at runtime
via the Web Audio API (OscillatorNode + linear-ramped GainNode
envelope) rather than shipping WAV assets — keeps the binary size
flat and lets us tweak timbre without touching bundled files.
Off by default. Settings → Output exposes the toggle with a volume
slider (0–100%, default 15%) and a "Test" button that plays the
completion cue so the user can confirm loudness without recording.
Hooked at three call sites in DictationPage:
- playStartCue after page.recording = true in startRecording
- playStopCue at the top of stopRecording
- playCompleteCue just before `saved = true` at the end of
finaliseTranscription's transcript-present branch
All three no-op when settings.soundCues is false. The Web Audio
context is lazily constructed on first cue (most browsers suspend
it until a user gesture — Tauri's webview inherits that). If the
AudioContext can't be built we silently drop the cue rather than
throwing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds src/lib/utils/hotkeyValidity.ts with validateHotkey(combo, os)
and wires it into HotkeyRecorder.svelte. Rules:
- X11/Wayland/Linux: reject single-key combos unless the trigger
is F13..F24 (the conventional global-shortcut escape hatch).
- Windows: reject combos whose only modifier is a right-hand
variant (RCtrl/RAlt) — matches Handy #966's failure mode where
RegisterHotKey silently ignores them.
- macOS: reject Fn-only combos and bare-key combos (non-function
keys).
- unknown OS: pass through — better to ship a flawed combo than
reject one we can't validate.
When validation fails, the recorder leaves the previous known-good
hotkey intact and surfaces an inline warning-coloured sentence
explaining why, with actionable copy ("add a modifier", "use the
left-hand equivalent", etc.). The save button disappears because
settings.globalHotkey is never written to — no state drift.
Matches Handy #917 / #1019 / #966 / #956, brief item #5.
Co-authored-by: jars <jakejars@users.noreply.github.com>
Adds src/lib/utils/settingsMigrations.ts exposing
loadSettingsWithMigration() / saveSettingsWithVersion() around a
{version, data} envelope in localStorage["kon_settings"]. The
migration chain is indexed by destination version so adding a v2
is one MIGRATIONS[2] = (prev) => next entry away from working.
Legacy bare-object settings blobs are treated as v0 and folded into
v1 identically to before — no user-facing reset — but an unreadable
blob now surfaces a single Settings reset toast instead of silently
dropping data.
Covers Handy #602 ('settings reset on update') and unblocks the
other B.2/B.3 SettingsState additions listed in
docs/whisper-ecosystem/workstream-B.md: every subsequent field
lands behind a MIGRATIONS step, so older Kon builds stay readable.
Co-authored-by: jars <jakejars@users.noreply.github.com>
Wholesale JS -> TS migration of the frontend — stores, utils, actions,
and all Svelte component scripts adopt type annotations. Compile-time
surfaces (app.d.ts, lib/types/) added for shared DTO types.
Build plumbing:
- package.json: dev:frontend script that runs svelte-kit sync first
- tauri.conf.json: beforeDevCommand points at dev:frontend
- run.sh: dropped the sed-hack that temporarily blanked beforeDevCommand;
now relies on npm run dev:frontend to avoid double-Vite
- jsconfig.json: allowImportingTsExtensions
Preserves all Group 1 behaviour:
- page.svelte.ts keeps loadHistory / loadTasks Tauri-first, no
localStorage; saveTranscriptMeta + mapTranscriptRow + mapTaskRow
intact; update_task_cmd and update_transcript_meta_cmd invocations
carry the correct payload shape.
- Toasts, preferences stores typed without behaviour change.
- Viewer still routes segment edits through saveTranscriptMeta; the
Task 1.5 TODO markers are gone.
taskExtractor.ts is functionally improved during the migration:
- multi-task matches in the same sentence
- list-style shopping-verb expansion (get bread, milk, and cheese)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Second dogfood sprint. Headline fix: Linux now uses native KWin/Mutter
decorations instead of fragile frameless `startResizeDragging`, which
collapsed diagonal corner resize to a single axis and made drag feel
laggy. macOS / Windows keep custom chrome via `useCustomChrome` gate.
Other changes:
- Cross-window preferences sync via `kon:preferences-changed` Tauri
event — theme and font changes propagate live to float/viewer.
- Hotkey recorder rewritten to use capture-phase document listener
gated by $effect; button focus was unreliable in webkit2gtk.
- History page redesigned for cognitive-load hygiene: title-first
compact row, inline title input, Edit popout opening /viewer in
edit mode, clipboard export as .md with YAML frontmatter, manual
tag chips + + Add tag input, header tag filter (cap 7), global
Starred filter, `tag:xyz` search syntax.
- `deriveAutoTags` kept as empty hook for post-Task-7 LLM topic tags;
research found all previous auto-tag chips redundant with row UI.
- Viewer window adds edit mode with debounced-save textarea; native
title renamed to "Kon - Transcription Editor".
- Window minimums updated per GNOME HIG + WCAG reflow research:
main 960x600, float 360x480, editor 560x520.
- Microphone picker filters raw ALSA strings (hw:, plughw:, front:,
sysdefault:, null) and dedupes by CARD=X. New `description` field
on DeviceInfo reads /proc/asound/cards so Blue Yeti shows as "Blue
Microphones" instead of the short "Microphones" card name.
- GPU reporting fixed: get_runtime_capabilities now returns
accelerators=[cpu,vulkan] and whisper.supports_gpu=true, matching
the transcribe-rs whisper-vulkan feature linked unconditionally.
- ResizeHandles kept for macOS/Windows frameless: 12px edges, 20px
corners via CSS vars, pointerdown + setPointerCapture, corners
above edges in z-order, rendered as sibling (not child) of the
animated layout root so `position: fixed` is viewport-relative.
- Dueling drag-region handlers removed — `data-tauri-drag-region` and
manual `startDragging()` were stacked on the same elements; kept
the manual handler which has the button/input early-return logic.
See HANDOVER.md for the full session log and deferred items.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
textMeasure.js, virtualList.js, and accessibilityTypography.js were
added in the 2026/04/04 session alongside VirtualSegmentList.svelte but
never committed. All four are imported from DictationPage, HistoryPage,
SettingsPage, and the viewer — a fresh clone had unresolvable imports.
textMeasure.js: pretext-backed text measurement with LRU cache.
measureTextHeight, measurePreWrap, clampTextLines, invalidateCache.
virtualList.js: binary-search visible range for variable-height virtual
lists. buildCumulativeOffsets + findVisibleRange.
accessibilityTypography.js: pretext font/line-height helpers that read
the user's accessibility preference store (Lexend / Atkinson /
OpenDyslexic mapping).
VirtualSegmentList.svelte: virtualised transcript segment renderer used
by the viewer page; depends on all three utils above.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Front-to-back QC pass turned up two independent missing-module
showstoppers (workspace did not compile; frontend did not load) plus a
handful of HANDOVER-claimed features that were wired but dead. Fixes:
P0 — gets the app booting again:
- Add the never-committed src/lib/utils/runtime.js (hasTauriRuntime
detection); 5 imports were resolving to nothing.
- Add the never-committed crates/audio/src/streaming_resample.rs
(rubato-backed StreamingResampler with new/push_samples/flush);
declared in lib.rs and used 3x by live.rs but had no impl.
- Drop the duplicate hasTauriRuntime import in routes/+layout.svelte.
- Allow the transcript-viewer window to use the default capability
(was missing from capabilities/default.json:windows, so the viewer
window could open but not invoke any Tauri command).
P1 — features documented as working but actually dead:
- Pump MicrophoneCapture::take_error_rx() into LiveStatusMessage::
Warning each loop iteration in commands/live.rs. The HANDOVER
promised cpal stream errors would surface as toasts; the channel
was created and never read.
- Replace .expect() on the WebKit media-permission setup with a
logged warning. Failure no longer aborts the whole process.
- Toast on save_preferences failure (preferences.svelte.js had a
silent console.error — now warns once per failure run via the
existing toasts store).
P2 — correctness/robustness:
- add_dictionary_entry: switch INSERT OR IGNORE to ON CONFLICT
DO UPDATE ... RETURNING id so duplicate terms get the real row id
instead of a stale auto-increment.
- search_transcripts: qualify ORDER BY fts.rank.
- InsertTranscriptParams + TranscriptRow: bump sample_rate /
audio_channels from i32 to i64 to match the Tauri DTO and avoid
silent truncation at the boundary.
- Drop the unused tauri-plugin-mcp dependency.
- Promote sqlx in src-tauri/Cargo.toml from linux-only to
unconditional (lib.rs names sqlx::SqlitePool unconditionally —
macOS/Windows builds were latently broken).
- hotkey/linux.rs: stop panicking the hotplug task on inotify
failure; degrade to "no hotplug" with a stderr warning.
- layout.svelte: store the global error/unhandledrejection handler
refs and remove them in onDestroy so HMR/window teardown doesn't
leak listeners.
Verified: cargo check -p kon-core -p kon-storage -p kon-cloud-providers
passes. cargo check on src-tauri/kon-audio/kon-hotkey requires alsa +
gtk system libs not present in this sandbox; their changes are
syntactically and type-checked against the rest of the workspace.
svelte-check requires npm install which is not available here.
https://claude.ai/code/session_018ozAs4UcRC8jbJbddqJtEw
CROSS-PLATFORM AUDIT:
- Linux x86_64 (Fedora 43, KDE Wayland): HIGH confidence (the dev target)
- Linux other distros / X11: MEDIUM (tested patterns, untested distros)
- Windows 10/11: LOW — theoretically supported via Tauri + CPAL + whisper.cpp
+ tauri-plugin-global-shortcut (the custom evdev hotkey is no-op on
Windows); has had zero hands-on testing
- macOS Apple Silicon / Intel: LOW — same; macOS Info.plist needs
NSMicrophoneUsageDescription for the bundled app, and the path bug
fixed in this commit
REAL BUG FIXED — macOS app_data_dir was Unix-style:
crates/storage/src/file_storage.rs::app_data_dir() previously fell into
the "Unix" branch on macOS and wrote to ~/.kon/, which violates Apple
guidelines and confuses install/uninstall tooling. Now correctly:
- Windows: %LOCALAPPDATA%/kon (unchanged)
- macOS: ~/Library/Application Support/Kon/
- Linux: $XDG_DATA_HOME/kon or ~/.local/share/kon (XDG Base Directory),
with legacy ~/.kon/ fallback so existing installs keep working
- Other Unix: ~/.kon/ (unchanged)
OS DETECTION LAYER:
- New get_os_info Tauri command in commands/diagnostics.rs returns:
{os, arch, family, usesCmd, isWayland, customHotkeyBackend,
primaryModifierLabel}
- New src/lib/utils/osInfo.js helper:
- async loadOsInfo() warms a cache, called once at root layout mount
- then isMac() / isWindows() / isLinux() / modKeyLabel() / isWayland()
are synchronous
- browser-preview fallback reads navigator.platform
- Lets components localise hotkey labels (Cmd vs Ctrl), file-picker
copy ("Open Finder" vs "Open Explorer"), etc, without recomputing in
every place.
cargo check -p kon-storage clean. Updated HANDOVER-2026-04-17.md with
a per-platform confidence table.