Loading...
An offline-first capture queue for voice, text, images and video — the tool I built to stop losing my own thinking.
Press a global hotkey, talk, forget about it. The recording is saved and verified on disk immediately; transcription, OCR and AI titling happen afterwards, in the background, and are allowed to fail without ever costing you the capture. It is the same tool I use to feed notes, meeting outcomes and half-formed ideas straight into the repositories my coding agents read.
View the sourceNote-taking tools that transcribe inline make the recording hostage to the network. A flaky connection, a rate-limited API or an expired key does not degrade the note — it loses the thought entirely. The failure is silent and arrives at exactly the moment you have stopped paying attention, because you already said the thing out loud and moved on.
The second problem is what happens after. Captures pile up unlabelled and unsearchable, disconnected from the vault or repository where the work actually happens, so the archive grows while its usefulness falls.
Every capture path — microphone, text note, audio, image, video upload — runs the same seven steps in the same order. This ordering is the reason the app exists.
A .m4a recording, a .txt note, or a copy of the uploaded file.
The write is completed and flushed before anything else is attempted.
The file is confirmed to exist with a size greater than zero.
Written atomically via .tmp → rename, so a crash cannot leave a half-index.
Capture returns here. The user is free; nothing waits on the network.
One job at a time: transcription, OCR, or enrichment.
A failure leaves the source file intact and offers a retry.
It never writes, moves or deletes it. A failed transcription costs you a status, never a recording.
An unreadable index freezes all writes for the session and reports it on screen, instead of quietly writing emptiness over your history.
Stored as .m4a
Processing Transcription
Stored as .txt
Processing Passthrough — no network at all
Stored as Original extension
Processing Transcription
Stored as .jpg / .png
Processing OCR via a vision model
Stored as .mp4 / .mov
Processing Audio track → transcription
Image OCR is LLM-only and deliberately so: one configured endpoint answers for both OCR and enrichment, behaving identically on a laptop and a phone. No local OCR engine, no language packs, no platform where images quietly work better than on another.
The durability contract is the foundation; the acceleration comes from what happens once the file is safe.
A global hotkey records the idea; enrichment gives it a title, a category and tags; one more keypress appends it to inbox.md in the right repository. The coding agent that reads that repo next finds the note already structured. A plain file was chosen over any tracker API on purpose — no token, no network, no new dependency on something that can be down.
Point the app at a vault directory and each capture is written there as markdown with the enriched title, category and tags in YAML front matter. Source files are either copied into the vault or left in place, per preference. An empty directory field is the off switch, so the state "enabled, nowhere to write" cannot be expressed.
Queue search matches on the processor output — the transcript, the OCR result, or the note body — not on filenames. Five status chips partition the list (all / queued / ready / failed / raw) and intersect with an independent desk / off-desk axis, so reviewing what is still open never means rebuilding a filter.
Transcription and enrichment are independent profiles, exactly one active per kind. Presets ship for OpenAI, Anthropic, Google Gemini, Groq, local whisper.cpp and local Ollama. No profile simply disables that stage; the app is fully usable with zero API keys, which is what keeps the durability guarantee honest.
Enrichment asks the model for four fields, and its category is a closed list of routing destinations rather than topics — which is what keeps the vocabulary small enough for a cheap, fast model to classify reliably.
Parsing degrades rather than throws: an unknown category becomes capture, a blank title becomes null, and tags are lowercased, de-duplicated and capped at five. Enrichment writes a title only when it is blank and a category only when it is null — so a retry can never overwrite what you set by hand.
Flutter · Dart ≥ 3.10
One shared path for every capture type. The index (recordings.json) is rewritten whole on each change, and an unreadable index freezes all writes for the session and says so on screen rather than overwriting history with emptiness.
Single-job worker, retryable
A processor only ever reads the source file — never writes, moves, or deletes it. Capture never blocks on the queue: a new recording can start while the previous one is still being transcribed.
OpenAI-compatible HTTP
Anthropic and Gemini are reached through their OpenAI-compatible endpoints, so all providers speak one request shape and there is no per-vendor adapter. The JSON parser degrades field by field instead of throwing, which is why vendors that ignore response_format still work.
AES-256-GCM + OS keyring
Tokens are encrypted under a master key held in Keychain, libsecret, or DPAPI and stored as enc:v1:<base64>. With no keyring available it falls back to plaintext — and says so out loud in the UI rather than failing silently.
LayoutBuilder, three breakpoints
Rows under 600 px, cards with a floating capture dock to 900 px, and a 216 px rail above it. Driven by LayoutBuilder rather than MediaQuery, so dragging a desktop window narrow really does fall through all three layouts.
macOS · Linux · Windows · Android · iOS
All six platform scaffolds are checked in. ffmpeg is the single optional desktop binary, needed for video and long-audio splitting; its absence downgrades an item to a retryable failure instead of a crash.
Separating durability from intelligence is what makes AI note-taking trustworthy. Once the file is verified on disk, every clever stage downstream becomes optional and retryable — which is precisely why the app is fully usable with zero API keys configured, and why adding one later is an upgrade rather than a dependency.
Offline-first pipelines, LLM stages that are allowed to fail, and local-first data handling are the sort of design problems I work on with clients. The source is open if you would rather just read it.