Goal
Rewrite Speech from Swift/SwiftUI to Rust + Tauri 2 (Svelte 5 frontend) while keeping all existing features and adding multi-model support (Whisper, Parakeet, Moonshine, SenseVoice) from Handy's transcribe-rs crate. Version 3.0.0.
Tasks
| # |
Task |
Status |
| 1 |
Initialize Tauri 2 + Svelte project scaffold |
⌛ |
| 2 |
Implement system tray + menu bar panel (no dock icon) |
⌛ |
| 3 |
Implement audio recording manager (cpal, 16kHz, WAV) |
⌛ |
| 4 |
Implement transcription manager + Whisper via transcribe-rs |
⌛ |
| 5 |
Implement global hotkey (hold-to-record + escape-cancel) |
⌛ |
| 6 |
Implement paste manager (clipboard + CGEvent Cmd+V) |
⌛ |
| 7 |
Wire up core recording loop (state machine) |
⌛ |
| 8 |
Implement recording overlay with waveform visualization |
⌛ |
| 9 |
Implement settings window - General tab + hotkey recorder |
⌛ |
| 10 |
Implement settings - Model tab with download UI |
⌛ |
| 11 |
Implement settings - Permissions tab |
⌛ |
| 12 |
Implement menu bar panel UI (status, history, actions) |
⌛ |
| 13 |
Implement model profiles + switch overlay |
⌛ |
| 14 |
Add all Handy models + auto-update + release prep v3.0.0 |
⌛ |
Architecture
Frontend: Svelte 5 + Tailwind CSS
Backend: Rust + Tauri 2
IPC: Tauri commands + events
Managers: AudioMgr (cpal) | ModelMgr (transcribe-rs) | TranscriptionMgr
HotkeyMgr (plugin) | PasteMgr (CGEvent) | SettingsMgr (store)
OverlayMgr | UpdateMgr (updater) | PermissionsMgr
Menu bar: TrayIconBuilder + set_activation_policy(Accessory) (= LSUIElement)
Task Details
Task 1: Initialize Tauri 2 + Svelte project scaffold
Files:
| File |
Lines |
Change |
src-tauri/Cargo.toml |
new |
All Rust dependencies (tauri 2, transcribe-rs, cpal, cocoa, core-graphics, etc.) |
src-tauri/tauri.conf.json |
new |
Windows config (main, recording-overlay, switch-overlay), tray, bundle |
package.json |
new |
Svelte 5 + Tailwind + Tauri CLI |
Verification: bun run tauri dev launches without errors
Task 2: Implement system tray + menu bar panel
Files:
| File |
Lines |
Change |
src-tauri/src/tray.rs |
new |
TrayIconBuilder, click handler, dynamic icon swap |
src-tauri/src/lib.rs |
new |
set_activation_policy(Accessory), setup_tray() |
src/components/MenuBarPanel.svelte |
new |
Basic panel with Quit button |
Verification: Icon in menu bar, no dock icon, panel toggles on click
Task 3: Implement audio recording manager
Files:
| File |
Lines |
Change |
src-tauri/src/managers/audio.rs |
new |
cpal stream, 16kHz resampling, hound WAV writer, RMS level calc |
src-tauri/src/commands/audio.rs |
new |
start_recording, stop_recording Tauri commands |
Porting from: Sources/Recording/AudioRecorder.swift:1-150
Verification: Record 3s audio, WAV is valid 16kHz mono 16-bit PCM
Task 4: Implement transcription manager + Whisper via transcribe-rs
Files:
| File |
Lines |
Change |
src-tauri/src/managers/model.rs |
new |
Model registry (6 Whisper sizes), download with progress, storage |
src-tauri/src/managers/transcription.rs |
new |
transcribe-rs WhisperEngine wrapper |
src-tauri/src/text_cleaner.rs |
new |
Filler word/phrase removal, stutter collapse (regex) |
Porting from: Sources/Transcription/WhisperService.swift:1-107, Sources/Transcription/TextCleaner.swift:1-49
Verification: Download whisper-small, transcribe test audio, verify filler removal
Task 5: Implement global hotkey (hold-to-record + escape-cancel)
Files:
| File |
Lines |
Change |
src-tauri/src/managers/hotkey.rs |
new |
tauri-plugin-global-shortcut, Pressed/Released, escape monitor |
Porting from: Sources/Hotkeys/HotkeyManager.swift:1-153
Verification: Hold Alt+Space emits start, release emits stop, Escape cancels
Task 6: Implement paste manager (clipboard + CGEvent Cmd+V)
Files:
| File |
Lines |
Change |
src-tauri/src/managers/paste.rs |
new |
Save focused app, clipboard save/restore, CGEvent Cmd+V simulation, modifier wait |
Porting from: Sources/Injection/TextInjector.swift:1-138
Verification: Text auto-pasted into TextEdit, original clipboard restored
Task 7: Wire up core recording loop (state machine)
Files:
| File |
Lines |
Change |
src-tauri/src/state.rs |
new |
AppState, TranscriptionCoordinator (Idle/Recording/Processing) |
src-tauri/src/lib.rs |
mod |
Initialize managers, register commands, wire hotkey events |
Porting from: Sources/AppState.swift:109-215 (recording flow), Sources/AppDelegate.swift:8-58 (lifecycle)
Verification: End-to-end: hold hotkey, speak, release -> text pasted into active app
Task 8: Implement recording overlay with waveform visualization
Files:
| File |
Lines |
Change |
src-tauri/src/overlay.rs |
new |
Show/hide/position overlay windows, multi-monitor |
src/overlay/RecordingOverlay.svelte |
new |
3 modes: recording/processing/ready |
src/overlay/AudioWaveform.svelte |
new |
5-bar animated waveform (blue-cyan gradient) |
Porting from: Sources/UI/RecordingOverlay.swift:1-185
Verification: Overlay appears with waveform during recording, transitions through all 3 modes
Task 9: Implement settings window - General tab + hotkey recorder
Files:
| File |
Lines |
Change |
src-tauri/src/managers/settings.rs |
new |
tauri-plugin-store JSON persistence |
src/components/SettingsWindow.svelte |
new |
Tabbed layout (450x560) |
src/components/GeneralTab.svelte |
new |
Toggles + hotkey recorder |
Porting from: Sources/UI/SettingsView.swift:1-99
Verification: Settings open, toggles persist across restart, hotkey recorder works
Task 10: Implement settings - Model tab with download UI
Files:
| File |
Lines |
Change |
src/components/ModelTab.svelte |
new |
Model list, download progress, status display |
src-tauri/src/commands/model.rs |
new |
list_models, download_model, set_active, delete_model |
Porting from: Sources/UI/SettingsView.swift:184-265
Verification: Browse models, download with progress bar, status shows Ready
Task 11: Implement settings - Permissions tab
Files:
| File |
Lines |
Change |
src/components/PermissionsTab.svelte |
new |
3 permission rows with status + Grant buttons |
src-tauri/src/managers/permissions.rs |
new |
macOS permission checks via cocoa/objc FFI |
src-tauri/src/commands/permissions.rs |
new |
check_permissions, open_settings, reset |
Porting from: Sources/AppDelegate.swift:62-115, Sources/UI/SettingsView.swift:267-386
Verification: Correct permission status, Grant opens System Settings
Task 12: Implement menu bar panel UI (status, history, actions)
Files:
| File |
Lines |
Change |
src/components/MenuBarPanel.svelte |
mod |
Full panel: status header, history, error, version, actions |
src/components/TranscriptionRow.svelte |
new |
History item with copy/delete/hover |
src/lib/api.ts |
new |
Typed Tauri invoke wrappers |
Porting from: Sources/UI/MenuBarView.swift:1-314
Verification: Panel shows status, history populates, copy/delete work
Task 13: Implement model profiles + switch overlay
Files:
| File |
Lines |
Change |
src/components/ProfileCard.svelte |
new |
Profile editor with model/language pickers |
src-tauri/src/commands/profiles.rs |
new |
CRUD, switch_to_next, auto-migrate |
src/overlay/SwitchOverlay.svelte |
new |
260x80 notification, 1.5s auto-hide |
Porting from: Sources/AppState.swift:413-477, Sources/UI/SwitchOverlayController.swift:1-79
Verification: Create 2 profiles, switch via hotkey, overlay shows
Task 14: Add all Handy models + auto-update + release prep v3.0.0
Files:
| File |
Lines |
Change |
src-tauri/src/managers/model.rs |
mod |
Add Parakeet V2/V3, Moonshine (4 sizes), SenseVoice to registry |
src-tauri/src/managers/update.rs |
new |
tauri-plugin-updater integration |
CLAUDE.md |
mod |
Version 3.0.0, updated architecture + commands |
Verification: All model types downloadable, auto-update works, bun run tauri build produces .app
Model Registry (v3.0.0)
| Engine |
Model |
Size |
Languages |
| Whisper |
tiny/base/small/medium/large-v3/large-v3-turbo |
75MB-1.6GB |
Multilingual |
| Parakeet |
V2 |
~473MB |
English |
| Parakeet |
V3 |
~478MB |
25 European languages |
| Moonshine |
tiny/base/small/medium |
31MB-192MB |
English |
| SenseVoice |
sensevoice |
~160MB |
Chinese, English, Japanese, Korean, Cantonese |
Research Summary
Files Analyzed
| File |
Lines |
Purpose |
Sources/AppState.swift |
524 |
Central state machine, models, profiles, languages, history |
Sources/SpeechApp.swift |
19 |
App entry with MenuBarExtra |
Sources/AppDelegate.swift |
116 |
Lifecycle, permissions, hotkey/whisper setup |
Sources/Recording/AudioRecorder.swift |
150 |
AVAudioEngine 16kHz recording with RMS levels |
Sources/Transcription/WhisperService.swift |
107 |
WhisperKit model download + transcription |
Sources/Transcription/TextCleaner.swift |
49 |
Filler word removal via regex |
Sources/Injection/TextInjector.swift |
138 |
Clipboard + CGEvent Cmd+V auto-paste |
Sources/Hotkeys/HotkeyManager.swift |
153 |
Global hotkey with hold-to-record pattern |
Sources/UI/RecordingOverlay.swift |
185 |
3-mode overlay with 5-bar waveform |
Sources/UI/MenuBarView.swift |
314 |
Menu bar dropdown panel |
Sources/UI/SettingsView.swift |
536 |
Settings with General/Model/Permissions tabs |
Sources/UpdateManager.swift |
166 |
GitHub release checking + auto-download |
Package.swift |
34 |
Swift dependencies (HotKey, WhisperKit) |
build_app.sh |
88 |
Release build + bundle creation |
Key Code References
- Recording flow:
AppState.swift:109-215 — startRecording → stopAndTranscribe → injectText
- CGEvent paste:
TextInjector.swift:98-113 — keycode 0x09, maskCommand, cghidEventTap
- Audio format:
AudioRecorder.swift:38-63 — 16kHz mono 16-bit PCM, 4096 buffer
- Hotkey pattern:
HotkeyManager.swift:80-93 — keyDown/keyUp + flagsChanged monitor
- Waveform:
RecordingOverlay.swift:106-147 — 5 bars, center-weighted, 8-80px height
- Model variants:
AppState.swift:266-275 — WhisperKit variant strings
- Text cleaner:
TextCleaner.swift:4-48 — filler phrases/words, stutter collapse regex
Risks & Edge Cases
Branch: v3
Tasks: View with ctrl+t
Created with /plan-issue
Goal
Rewrite Speech from Swift/SwiftUI to Rust + Tauri 2 (Svelte 5 frontend) while keeping all existing features and adding multi-model support (Whisper, Parakeet, Moonshine, SenseVoice) from Handy's
transcribe-rscrate. Version 3.0.0.Tasks
Architecture
Menu bar:
TrayIconBuilder+set_activation_policy(Accessory)(= LSUIElement)Task Details
Task 1: Initialize Tauri 2 + Svelte project scaffold
Files:
src-tauri/Cargo.tomlsrc-tauri/tauri.conf.jsonpackage.jsonVerification:
bun run tauri devlaunches without errorsTask 2: Implement system tray + menu bar panel
Files:
src-tauri/src/tray.rssrc-tauri/src/lib.rssrc/components/MenuBarPanel.svelteVerification: Icon in menu bar, no dock icon, panel toggles on click
Task 3: Implement audio recording manager
Files:
src-tauri/src/managers/audio.rssrc-tauri/src/commands/audio.rsPorting from:
Sources/Recording/AudioRecorder.swift:1-150Verification: Record 3s audio, WAV is valid 16kHz mono 16-bit PCM
Task 4: Implement transcription manager + Whisper via transcribe-rs
Files:
src-tauri/src/managers/model.rssrc-tauri/src/managers/transcription.rssrc-tauri/src/text_cleaner.rsPorting from:
Sources/Transcription/WhisperService.swift:1-107,Sources/Transcription/TextCleaner.swift:1-49Verification: Download whisper-small, transcribe test audio, verify filler removal
Task 5: Implement global hotkey (hold-to-record + escape-cancel)
Files:
src-tauri/src/managers/hotkey.rsPorting from:
Sources/Hotkeys/HotkeyManager.swift:1-153Verification: Hold Alt+Space emits start, release emits stop, Escape cancels
Task 6: Implement paste manager (clipboard + CGEvent Cmd+V)
Files:
src-tauri/src/managers/paste.rsPorting from:
Sources/Injection/TextInjector.swift:1-138Verification: Text auto-pasted into TextEdit, original clipboard restored
Task 7: Wire up core recording loop (state machine)
Files:
src-tauri/src/state.rssrc-tauri/src/lib.rsPorting from:
Sources/AppState.swift:109-215(recording flow),Sources/AppDelegate.swift:8-58(lifecycle)Verification: End-to-end: hold hotkey, speak, release -> text pasted into active app
Task 8: Implement recording overlay with waveform visualization
Files:
src-tauri/src/overlay.rssrc/overlay/RecordingOverlay.sveltesrc/overlay/AudioWaveform.sveltePorting from:
Sources/UI/RecordingOverlay.swift:1-185Verification: Overlay appears with waveform during recording, transitions through all 3 modes
Task 9: Implement settings window - General tab + hotkey recorder
Files:
src-tauri/src/managers/settings.rssrc/components/SettingsWindow.sveltesrc/components/GeneralTab.sveltePorting from:
Sources/UI/SettingsView.swift:1-99Verification: Settings open, toggles persist across restart, hotkey recorder works
Task 10: Implement settings - Model tab with download UI
Files:
src/components/ModelTab.sveltesrc-tauri/src/commands/model.rsPorting from:
Sources/UI/SettingsView.swift:184-265Verification: Browse models, download with progress bar, status shows Ready
Task 11: Implement settings - Permissions tab
Files:
src/components/PermissionsTab.sveltesrc-tauri/src/managers/permissions.rssrc-tauri/src/commands/permissions.rsPorting from:
Sources/AppDelegate.swift:62-115,Sources/UI/SettingsView.swift:267-386Verification: Correct permission status, Grant opens System Settings
Task 12: Implement menu bar panel UI (status, history, actions)
Files:
src/components/MenuBarPanel.sveltesrc/components/TranscriptionRow.sveltesrc/lib/api.tsPorting from:
Sources/UI/MenuBarView.swift:1-314Verification: Panel shows status, history populates, copy/delete work
Task 13: Implement model profiles + switch overlay
Files:
src/components/ProfileCard.sveltesrc-tauri/src/commands/profiles.rssrc/overlay/SwitchOverlay.sveltePorting from:
Sources/AppState.swift:413-477,Sources/UI/SwitchOverlayController.swift:1-79Verification: Create 2 profiles, switch via hotkey, overlay shows
Task 14: Add all Handy models + auto-update + release prep v3.0.0
Files:
src-tauri/src/managers/model.rssrc-tauri/src/managers/update.rsCLAUDE.mdVerification: All model types downloadable, auto-update works,
bun run tauri buildproduces .appModel Registry (v3.0.0)
Research Summary
Files Analyzed
Sources/AppState.swiftSources/SpeechApp.swiftSources/AppDelegate.swiftSources/Recording/AudioRecorder.swiftSources/Transcription/WhisperService.swiftSources/Transcription/TextCleaner.swiftSources/Injection/TextInjector.swiftSources/Hotkeys/HotkeyManager.swiftSources/UI/RecordingOverlay.swiftSources/UI/MenuBarView.swiftSources/UI/SettingsView.swiftSources/UpdateManager.swiftPackage.swiftbuild_app.shKey Code References
AppState.swift:109-215— startRecording → stopAndTranscribe → injectTextTextInjector.swift:98-113— keycode 0x09, maskCommand, cghidEventTapAudioRecorder.swift:38-63— 16kHz mono 16-bit PCM, 4096 bufferHotkeyManager.swift:80-93— keyDown/keyUp + flagsChanged monitorRecordingOverlay.swift:106-147— 5 bars, center-weighted, 8-80px heightAppState.swift:266-275— WhisperKit variant stringsTextCleaner.swift:4-48— filler phrases/words, stutter collapse regexRisks & Edge Cases
transcribe-rsv0.2.5 maturity — Mitigation: pin version, keep whisper-rs as fallbackset_ignore_cursor_events(true)+ alwaysOnTop, escalate to tauri-nspanelBranch:
v3Tasks: View with
ctrl+tCreated with
/plan-issue