# Push-to-talk dictatie-app voor macOS — ontwerp Datum: 2026-10-01 · Status: concept ter review · Werktitel: `Sst` ## Doel Een systeembrede dictatie-app, vergelijkbaar met Wispr Flow. De gebruiker houdt **Fn** ingedrukt, spreekt, laat los, en de transcriptie verschijnt op de cursor in elke app. Alles draait lokaal op de Mac. Nederlands en Engels mogen door elkaar gebruikt worden. ## Buiten scope (v1) - AI-cleanup (vulwoorden, zelfcorrecties, toon per app). Alleen de naad (`Cleaner`) wordt voorbereid. - Streaming-transcriptie tijdens het praten. - Mac App Store-distributie, accounts, sync, cloud. - Branding. Die volgt als aparte stap na de werkende kern. ## Doelomgeving Apple M4 Pro, 24 GB RAM, macOS 27, Xcode 27, Swift 6.4. Alleen Apple Silicon. ## Aannames - Persoonlijk gebruik, niet-gesandboxed. De Mac App Store-sandbox verbiedt globale key-taps en gesimuleerd plakken. - Niets verlaat de Mac. Audio wordt niet bewaard. - Spraakmodel: Whisper `large-v3-turbo` via WhisperKit, met automatische taalherkenning. ## Architectuur Een macOS-menubalk-app (SwiftUI `MenuBarExtra`). De logica staat in een Swift Package (testbaar met `swift test`). De Xcode-app is een dunne schil eromheen. | Unit | Taak | Afhankelijk van | |---|---|---| | `HotkeyMonitor` | Detecteert Fn omlaag/omhoog via `CGEventTap`; stuurt `pressed`/`released` events | Input Monitoring | | `AudioRecorder` | `AVAudioEngine` neemt op naar een 16 kHz mono Float-buffer; start bij `pressed`, stopt bij `released` | Microfoon | | `Transcriber` (protocol) | `transcribe(samples) async throws -> String`; eerste implementatie `WhisperKitTranscriber` | WhisperKit | | `Cleaner` (protocol) | `clean(text, context) async throws -> String`; v1 `PassthroughCleaner` | n.v.t. | | `TextInserter` | Zet tekst op het klembord, simuleert ⌘V, herstelt het klembord | Accessibility | | `DictationController` | Statemachine `idle → recording → transcribing → inserting → idle`; enige component dat de andere kent | bovenstaande | | `OverlayPanel` | Floating `NSPanel` met pilletje: opname, golfvorm, bezig | SwiftUI | | `MenuBarExtra` | Status, instellingen, model-download, rechten-check | n.v.t. | Ontwerpprincipes: elke unit heeft één taak en een duidelijke interface. `Transcriber` en `Cleaner` zijn protocollen, zodat modellen (bijvoorbeeld Parakeet of een LLM) later verwisselbaar zijn. Tests gebruiken fakes voor de protocollen. ## Dataflow 1. Fn omlaag: opname start, overlay verschijnt. 2. Fn omhoog: opname stopt. 3. Samples gaan naar `Transcriber`, daarna naar `Cleaner`. 4. `TextInserter` voegt de tekst in; overlay verdwijnt. Het model wordt bij het opstarten geladen en blijft warm in het geheugen. ## Foutafhandeling - Opname korter dan ~0,3 s: negeren (per ongeluk aangetikt). - Rechten ontbreken: menubalk toont wat ontbreekt, met knop naar de juiste Systeeminstellingen. - Model nog niet gedownload: eerste start toont voortgang; dictatie is dan uitgeschakeld. - Invoegen mislukt: tekst blijft op het klembord, overlay meldt dit. - Stilte of lege transcriptie: niets invoegen. - Transcriptiefout: overlay toont een korte foutmelding; de controller keert terug naar `idle`. ## Vereiste rechten Microfoon, Accessibility (plakken simuleren), Input Monitoring (Fn-toets). ## Testen - Unit-tests voor `DictationController` met fake `Transcriber`, `Cleaner`, `TextInserter` (toestandsovergangen, korte opnames, foutpaden). - Unit-test voor het klembord-herstel in `TextInserter` (injecteerbare pasteboard). - Handmatige rooktest van de hele keten in TextEdit, Notes en een browser: Nederlands, Engels en gemengd. ## Bouwvolgorde (skeleton eerst) 1. Package plus menubalk-app; hotkey logt alleen "recording…". 2. Opnemen naar een buffer. 3. WhisperKit transcriberen. 4. Tekst invoegen. 5. Overlay-pill. 6. Cleanup, instellingen en branding (aparte specs). ## Open punten Geen voor het skeleton. De definitieve naam en branding worden in een latere stap bepaald.