Local-first, open-source, cross-platform voice dictation desktop app. Privacy-focused alternative to Wispr Flow and SuperWhisper. Fully offline. Windows 10+, macOS 12+, Linux (X11 + Wayland). License: MIT.
- Backend: Tauri 2, Rust (stable 1.75+),
rusqlite0.31 (bundled),cpal0.15,sherpa-onnx1.12,tokio1.x,serde,enigo0.2 - Frontend: React 19, TypeScript 5, Vite 5, Tailwind CSS 3, shadcn/ui (used minimally), Jotai 2, Recharts 2
- IPC: Tauri command system. Frontend calls Rust via
invoke()from@tauri-apps/api/corethrough typed wrappers insrc/lib/commands/.
- Two Tauri windows:
main(settings) andoverlay(floating pill). src-tauri/src/— all Rust backend code.src/— React/TypeScript frontend.- All commands use
#[tauri::command]and register intauri::generate_handler![]inlib.rs. - SQLite stores app data in
transcription_historyandsettings. That is it. No corrections, dictionary, or training tables. - Nothing leaves the device. No cloud STT, no remote LLM cleanup.
hotkey -> audio/capture.rs -> audio/vad.rs -> stt/whisper.rs -> injection/injector.rs -> text appears in app
Notes:
- Audio capture is 16 kHz mono via
cpal, in memory only. - VAD uses Silero VAD when present, energy fallback otherwise.
- STT is Parakeet via
sherpa-onnx. - Injection is clipboard save -> set -> paste -> restore. The previously focused window is captured before the overlay shows.
- There is no correction pass and no LLM cleanup stage.
- Parakeet and Silero VAD are NOT preloaded at startup. The pipeline lazy-
loads them on the first dictation and
stt::lifecycle::ModelLifecycleschedules them to be dropped fromAppStateafter the idle window (default 60s, override via theidle_unload_secondssetting). - The frontend can listen for
model-stateevents ({ loaded: bool, state: "loaded" | "unloaded" | "loading" }) or call theget_model_stateIPC command to render a loading hint. - The main settings window is destroyed on close (not hidden). It is
rebuilt on demand by
show_or_create_main_window()when the tray icon, tray menu, or open-app hotkey asks for it. The overlay window stays alive so hotkey response is instant. - mimalloc is the global allocator. sherpa-onnx's alloc/free pattern fragments badly under the Windows system allocator.
- STT: Parakeet via
sherpa-onnx(parakeet-110mdefault). - VAD: Silero VAD.
- No local LLM. No Ollama. No BYOK.
- Frontend:
npm run lint && npx tsc --noEmit - Backend:
cargo clippy --manifest-path src-tauri/Cargo.toml --all-targets -- -D warnings - Run relevant tests for changed code.
- NEVER modify
main.rsdirectly. Onlylib.rs. rusqlitemust use the bundled feature.- Text injection must remain clipboard save -> set -> paste -> restore.
- Audio is never written to disk.
- No
unwrap()/expect()in production Rust paths. - No
anyin TypeScript. - Do not reintroduce LLM, Ollama, BYOK, or correction-learning paths unless explicitly requested. They were removed deliberately.
v0.1.0 release candidate. The shipped app is STT-only with Parakeet. See
docs/PRODUCT_AUDIT.md for the remaining tracked work and AGENTS.md for
the longer contributor guide.