Building Snapbak: A Mac Screen Recorder That Fits in 10 MB
Summary
Snapbak is a Mac app for screenshots, screen recordings, GIFs and dictation. The installer is about 10 MB. It stays small because of three choices: a Rust core in Tauri instead of a bundled browser, native Swift for anything that's always on screen, and speech models that download on first use instead of shipping in the installer.
Why build another screen recorder
I record a lot of short demos and bug reports, and I dictate a lot of text. The apps I used for that installed at anywhere from 400 MB to over 1 GB, sat in memory all day, and most of the dictation tools sent my voice to someone's server.
So I set myself budgets before writing any code:
| Goal | Hard limit | |
|---|---|---|
| Installer | ≤ 20 MB | ≤ 40 MB |
| Memory when idle | ≤ 40 MB | ≤ 100 MB |
| Cold start | ≤ 300 ms | ≤ 1 s |
Every stack decision after that had to answer to one of these numbers.
What it does
- Capture. One hotkey, then pick an area, a window or a screen. Screenshot, scrolling capture (press S), text grab (press T), GIF or recording.
- Polished recordings. The zoom follows your clicks, the cursor glides, keystrokes show up as overlays, and captions are burned in.
- Edit by text. Every recording comes with a transcript. Delete words in the transcript and the video gets cut. Removing every "um" is one click.
- Dictation. Hold a key, talk, let go. Clean text lands in whatever app has focus.
- Smart Redact. Emails, card numbers and API keys are found and blurred on the device before you share a screenshot.
Meeting notes without a bot joining the call are planned for a later release.
The 96 MB problem
Tauri is a framework that pairs a Rust program with the operating system's own web view (WebKit on macOS) for its HTML screens. Electron ships a full copy of Chromium in every app. Tauri doesn't, which is why the installer can be tiny.
Memory is another story. An empty Tauri app on macOS already sits at about 96 MB of RAM. My idle hard limit is 100 MB. If I kept a web view alive all day, the budget would be gone before Snapbak did anything.
The fix was to split the UI by how often it's on screen:
- Always-on surfaces are native. The capture overlay, the hotkeys and the island in the notch are a Swift package (SwiftUI and AppKit) linked into the Rust binary. No web view.
- Heavy screens are web UI, opened on demand. The editor, the library, settings and onboarding are React. They open when you need them and are destroyed when you close them, which hands their memory back.
The process layout follows the same idea. One small main process stays up and holds the island, the hotkeys and the native surfaces. Speech-to-text, LLM calls, export and the web view host each run in a worker process that starts when needed and exits when the work is done.
main process (always on) worker processes (on demand)
├─ Rust core ├─ speech-to-text
├─ hotkeys ├─ LLM calls
├─ capture overlay (Swift) ├─ video export
└─ notch island (Swift) └─ web view host (editor, library, settings)Keeping the models out of the installer
Dictation runs NVIDIA's Parakeet TDT 0.6B v2 speech model, converted to Core ML and run on the Neural Engine through FluidAudio. That model alone is about 460 MB. A second, smaller Parakeet model (110M parameters, weights cut to 4 bits, about 75 MB) helps spell names and jargon from your personal dictionary.
Neither ships in the installer. They download on first use, get checked against a SHA-256 hash, and unload when idle. That's how a 10 MB app can run a half-gigabyte model.
On the shipped engine, dictation measures about 2.3% word error rate on LibriSpeech test-clean, with a median of half a second from key-up to text on an M4. There's no network traffic for the speech itself.
Where on-device stopped working
My first build also cleaned up the text on the Mac with a small language model: punctuation, grammar, and backtracking ("send it Tuesday, no, Wednesday"). I ran a bake-off on a 2,500-pair test set. Models between 1.7B and 4B parameters scored 45 to 64% on punctuation and 66 to 77% on grammar, and only one of them fit the one-second latency budget.
That wasn't good enough. So the polish step moved to a server with larger hosted models, with targets of 500 ms p95 added latency and at most $0.50 a month in model fees for a heavy user. The audio still never leaves the Mac. Only the text goes up, and there's an offline fallback.
The rule I ended up with: keep the private part (your voice, your screen) on the device, and send the small part (text) out only when it makes the result clearly better.
Small things that weren't small
Your files stay files. Recordings and screenshots are plain files in ~/Pictures/Snapbak and ~/Movies/Snapbak. A SQLite database (with FTS5 for search) indexes them, and the app can rebuild that index from the folders if it ever breaks.
Video froze on keyframes. Tauri's built-in asset protocol cuts byte-range responses at 1000 KiB. Any keyframe bigger than that froze playback in the editor. Snapbak now serves media through its own media:// scheme, which only reads from those two folders and returns each requested range whole.
Try it
Snapbak needs macOS 15 or later on Apple Silicon. Version 0.1.2 is out, and it's free for the first 100 users: snapbak.site. The project page is here.