"A gigabyte of model weights in your git history is a mistake you commit exactly once."
Weights Live Outside Git
An on-device STT model is hundreds of megabytes to gigabytes. It is data, not source, and it must not live in your repo — commit it once and every clone, every backup, every history walk drags that weight forever. Firekeeper keeps model weights in a cache directory outside the project (under a ~/cwk-models/whisper/ tree), downloaded on first use, never tracked by git. The repo holds the code that manages the model; the model itself is a runtime asset.
The First-Run Tax
The first time a Core ML Whisper model runs, it compiles for the specific machine — a one-time cost of several seconds that looks exactly like a freeze if you don't handle it. Two habits fix this. First, prewarm: kick off model load/compile at app launch, not at the first hotkey, so the model is hot by the time the user actually dictates. Second, show progress: model download and first-run compile both need visible, honest progress (whole-percent download, a 'preparing model' state) so the app never looks hung.
Launch -> prewarm(): start model load/compile in the background
First dictation -> model already hot -> low latency
Missing model -> download with whole-percent progress in the overlay
First compile -> 'preparing model...' state, not a frozen UI
Migrating a Bad Default
One more real detail: Firekeeper once defaulted to a tiny model, which was fast but weak. When the default changed to large-v3-turbo, existing users had the old choice saved in settings. The migration is surgical: bump the old default up once, but keep any model the user deliberately chose. "Upgrade the stale default, respect the explicit choice" is the rule — a blanket overwrite would stomp on a power user's intentional selection.