Seven states, and nothing else may write to them.
Dictation fans out across the bubble, the microphone, the provider, the refiner and the inserter. Left alone that becomes scattered booleans which admit impossible combinations — so one component owns the state.
- one transition per pair
- Every (state, event) pair has exactly one defined transition, or is explicitly ignored. A transition-table test covers it.
- inserting is not cancellable
- Insertion either lands or fails, and the bubble shows which. You cannot back out halfway and leave a field in pieces.
- cancel discards everything
- Cancel during recording, transcribing or refining stops capture, cancels the provider flow, drops the audio, and persists no transcript.
- every step times out
- A suspending step that hangs becomes ErrorRecoverable(Timeout) rather than a dictation that never ends.
- cleanup never eats your words
- A refiner failure degrades to the raw transcript, marked unprocessed. Losing what someone said to a punctuation bug is the worst outcome available.
Eight deterministic stages.
No language model. Every stage is a rule you could implement on a Tuesday, which is the point: the same transcript cleans the same way every time, and you can read exactly what changed.
- NormalizationWhitespace, casing baseline.
- Spoken-command expansion“full stop” becomes a period; new line, new paragraph, quotes, parentheses.
- Backtracking resolution“scratch that…” deletes back to the last sentence boundary. No, I mean and actually are soft markers only — in V1 the next clause does not replace the last one.
- Filler removal“um”, “uh”, “you know”, “like”. Per-language lists, user toggle.
- List detection“number one”, “first” become a numbered list; a line-leading “dash” becomes a bulleted one.
- DictionaryYour wrong→right map. A trigger matches the words you say — case and punctuation don’t decide it, so
signever fires insidesignal. - SnippetsYour shorthand→expansion map, matched the same way, user-toggled.
- App-style formattingOne of Casual, Neutral or Formal — capitalisation and ending punctuation, assigned per app.
raw i'm sorry scratch that friday works too. um like thursday at ten am
stage 3 scratch that friday works too. false start deleted
stage 4 um like fillers removed
stage 8 thursday at 10am → Neutral preset applied
result Thursday at 10am.
Style runs last so it sees the final content. A refiner exception anywhere in the pipeline degrades to the raw transcript marked unprocessed — it never fails the dictation.
Five rules, in a fixed order.
The order is part of the contract, because “which rule excluded this provider” is the explanation you are owed. Consent comes before capability; capability before cost; health last, because it is the most volatile and least authoritative signal.
- 1 · manual preference
- A ranking override among eligible providers. Never a way over a hard exclusion.
- 2 · privacy and offline
local-onlyexcludes cloud.offlineexcludes online. These are your instructions, not the router’s judgement, so they come first.- 3 · capability match
- Does this provider actually support the language you asked for, and work without a network if you said so.
- 4 · cost ceiling
- Compared against the worst case — rate × maximum audio duration — not the amount it turns out to be. A ceiling you can exceed after the router commits is not a ceiling.
- 5 · health
UNAVAILABLEandMODEL_MISSINGskip.DEGRADEDis eligible but ranked last. Health never empties the candidate set: if skipping would leave nothing, degraded providers are admitted and the decision is recorded asDEGRADED_LAST_RESORT.
There is no user-facing cost slider in V1. With no billable provider, a ceiling would control nothing — the field exists so the first cloud adapter is a drop-in rather than a rewrite. Automatic routing by measurement is explicitly out of V1; when it arrives it reads the same decision log.
Getting the text in.
Writing into a field that belongs to another app is the part that can actually break, so it degrades in three steps and re-resolves its target rather than trusting a stale handle.
- SET_TEXTSet the field’s contents directly. Fast, and it replaces a selection rather than inserting into it.
- PASTEWrite to the clipboard and paste, which preserves a caret inside existing text.
- Copy and promptPut it on the clipboard, tell you it is there, and let you paste. The clipboard is preserved beforehand and restored afterwards.
Sensitive fields are refused before the microphone opens. If the focused field turns out to be sensitive partway through, the audio and the transcript are discarded and nothing is written. And if you cancel, there is nothing to find later — no transcript, no audio, no record that a dictation ever started.
Read the decisions
The decisions that shaped it, and the documents behind them, in one index.