Blog

Context-aware dictation on Mac: how the app knows where you type

TalkTalkType reads the focused app and field with deterministic classification, then adapts your dictation during voice refinement. No mode selection required.

By tsuvicPublished 9 min read

You are halfway through a reply in Mail. You hold Option-Space, speak for ten seconds, and release. The cleaned sentence lands in the body of the email, not in a separate editor and not on the clipboard waiting for you to carry it over.

Nothing about that required you to say where the text should go.

Most voice tools handle destination the other way around. They hand you a picker before you speak: a raw mode, a punctuate mode, a prompt mode, a chat style, a mail style. You announce the destination, and the tool trusts your announcement. TalkTalkType has no such picker. It reads the destination instead. It looks at the application you were in and the specific field under the cursor, classifies both with a fixed lookup table, and adapts the result during a voice-refinement step you trigger yourself.

The rest of this article is about how that reading works, what it changes, and where it stops.

The destination is captured the moment you press the key

Recording starts on key-down. At that instant the Mac app captures the focused application as a delivery target: the process ID, the bundle identifier, and the localized name of the app. When the result is ready, delivery returns the text to that exact app.

Two delivery paths exist. In Paste mode the app uses the macOS Accessibility API to insert the text into the field you were in, saving the clipboard first and restoring it afterward. In Copy mode the result simply waits on the clipboard for you to paste manually. Either way, the target is the app you were using when you pressed the key, captured before you said a word.

Capturing at key-down is the detail that makes the whole thing feel automatic. If the app waited until the text was ready, you might have switched windows, and the result would land in the wrong place. Instead the field you start in is the field you finish in. The mechanics of that delivery, and the macOS permissions behind it, are covered in dictation that works in any app on Mac.

Classification is a lookup, not a guess

Once the target app is known, the classifier decides what kind of surface you are typing into. It does this without asking a language model.

The first step maps the bundle identifier to an app family using a fixed prefix table. Terminal, iTerm2, Warp, WezTerm, and kitty resolve to terminal. VS Code, Xcode, JetBrains IDEs, Sublime Text, and Zed resolve to code editor. Mail and Outlook resolve to email. Slack, Discord, LINE, and Telegram resolve to chat. TextEdit, Notes, Pages, Word, and Obsidian resolve to document. Safari, Chrome, Firefox, and Arc resolve to browser.

The second step refines the surface using the field's Accessibility role and subrole. A search field (AXSearchField) becomes a search query. In Mail, a text area becomes the email body and a text field becomes the subject. In a chat app, a text area or text field becomes a chat message. In a document app, a text area becomes a document and a text field becomes a short form field. In a code editor, a text area becomes the code editor surface.

There is no inference here, only a table and a couple of role checks. The payoff is that every classification carries an auditable reason string. A Mail body classifies as app_family:email role:AXTextArea subrole:-. You can read exactly why the app reached a conclusion, because the conclusion is derived from two visible facts rather than a model's confidence.

When the lookup fails, it fails closed. An app with an unrecognized bundle identifier classifies as unknown, with the reason unknown_app. A known app with an unrecognized field role classifies as unknown too, with the reason unknown_role. Unknown means the app does nothing special. That is the safe default: when TalkTalkType cannot tell what a field is, it treats your words plainly rather than guessing wrong.

What changes per surface

The surface classification drives what the refinement step is allowed to adapt to. The mapping looks like this.

App and field Detected surface What changes
Mail or Outlook, main text area Email body Treated as the body of an email
Mail or Outlook, subject field Email subject Treated as a subject line
Slack, Discord, LINE, Telegram Chat message Treated as a chat message
VS Code, Xcode, JetBrains, Sublime, Zed Code editor Filenames, paths, and slash commands kept as plain text, never run
Terminal, iTerm2, Warp, WezTerm, kitty Terminal Same technical-token handling as code editors
TextEdit, Notes, Pages, Word, Obsidian, text area Document Treated as a document
TextEdit, Notes, Pages, Word, Obsidian, text field Short form field Treated as a single-line field
Any known app, search field Search query Treated as a search query
Safari, Chrome, Firefox, Arc Unknown No adaptation; web fields are not structurally trustworthy
Unrecognized app or field role Unknown No adaptation, the fail-safe default

Two rows deserve a comment. The browser row is always unknown, on purpose. A web page can draw any field it likes, and the structural signals that make a native Mail or Slack field legible do not hold up inside a browser tab, so TalkTalkType does not pretend to classify web fields. It leaves them alone.

The code editor and terminal rows carry a specific guarantee about technical tokens. On those surfaces, a spoken filename, path, or slash command is preserved as ordinary text. It is never treated as something that exists, resolves, or executes. Dictating a command into a terminal produces the literal string, nothing more.

Adaptation is a refinement step, not a silent rewrite

This is the part that is easiest to overstate, so the boundary matters.

The cleaned result first arrives as a draft on the menu-bar HUD. That first paste is faithful Clean output. Clean keeps every spoken word, in the order you said it, at the register and brevity you intended. It removes only unambiguous hesitation fillers, joins everything onto a single line, and never paraphrases, expands, or polishes. Raw mode skips cleanup entirely. The first thing you see is what you said.

The context-aware adaptation happens later, in the Voice Patch flow. From the draft you can speak a refinement before the text is inserted, or commit, copy, or discard it as it stands. When a refinement asks the app to adapt to the surface, the model instruction is literally "Adapt only to the supplied deterministic desktop-app and field classification." The same flow preserves meaning and every protected fact, makes the smallest necessary edit, and treats everything it is given about the screen as data, never as an instruction.

So the app does not quietly rewrite your first paste into "email style" the moment it sees Mail. It shows you a faithful draft, and adaptation is something you ask for by voice, gated on the surface it already detected.

The question this design answers is why other tools still hand you a picker. TalkTalkType has no output-mode selection anywhere in its settings. The only switch is Clean on or off — faithful light cleanup, or nothing at all. Everything destination-specific comes from reading the field, not from a mode you maintain. That is the strength to judge it on: one less thing to set before you speak, and nothing to set wrong.

Reading the field raises an obvious question: what about everything else on the screen?

The context snapshot the app can assemble includes the app identity, the window title, the field structure (role, subrole, whether it is multiline or a secure password field), the text immediately before and after the caret, the current selection, an optional screenshot of the active window, a capture timestamp, and the permission state. That is a lot of surface area, so the boundaries are enforced on the server, not left to the client's good behavior.

Two rules matter most. First, field labels and placeholders are never sent to the AI provider. Those strings are arbitrary text supplied by another app, and classification needs only structural attributes, so the server rejects any request that contains a label or a placeholder outright. Second, each context channel (text context, screenshot, and preference memory) sits behind a server-owned feature flag. All three default to on in production and can be overridden per deployment. If a channel is not enabled for a request, the server rejects the context rather than silently stripping it. A rejected request tells the client its consent state is stale, which a silent strip would hide.

The full data ledger, covering what the Mac, the service, and the provider each hold, is in private dictation on Mac.

Preferences are learned locally, per app

The last piece of context is preference memory, and it stays on the Mac.

Preferences live in a local SQLite database at ~/Library/Application Support/TalkTalkType/PreferenceMemory/preferences.sqlite. There is no cloud sync. The stored keys are deliberately narrow: response tone (concise, neutral, formal, or friendly), whether to use the Oxford comma, a detail level from 0 to 2, and a list of preferred terms. Each preference is scoped to all apps or to one app by bundle identifier, and a per-app preference wins over a global one.

Nothing here is used until it is both explicitly approved and enabled. Only abstracted values ride along in a refinement request; raw speech, draft text, and rejected candidates are never written to this store. If you correct the app repeatedly in one app, it may propose a preference, but a proposal does nothing until you approve it.

Where this falls short

The honest limitations are part of the design, not footnotes.

Browsers classify as unknown, so there is no surface adaptation for web forms; the app does not trust web field structure. Adaptation applies during the voice-refinement flow, not as a silent rewrite of the first paste, which stays faithful by design. Adaptation never paraphrases away your words: meaning and protected facts are preserved, and the edit is the smallest one that satisfies the instruction.

The platform is Mac only, and it requires macOS 26 (Tahoe) or later. Transcription runs in the cloud, so audio leaves the Mac for processing; the service does not store audio or transcript text by default. Recognition languages are Japanese and English. Spoken commands such as "copy" or "open Safari" are a related expression of the same idea: they run as Mac actions against the captured app instead of being typed as text.

Judging it for your own workflow

The case for this design is that the destination is a fact the app can read, not a setting you should have to maintain. A mode picker outsources that job to you, and a picker is a promise the tool can break the moment you forget to switch it. A lookup table over bundle identifiers and field roles is duller and more honest: it knows a small set of apps precisely, and it admits when it does not.

TalkTalkType counts usage in dictations per week plus a weekly voice-time pool, not words. As of 2026-07-30, the plans are Free at $0 (30 dictations and 15 minutes per week), Plus at $1 (80 and 30 minutes), Pro at $7 (500 and 250 minutes, with two-minute takes), and Max at $18 (1,200 and 600 minutes). Current numbers and plan availability are on the pricing page.

You can download TalkTalkType and try the detection on the fields you actually use. Hold the hotkey in Mail, then in a terminal, then in a browser form, and watch where the result lands and what it declines to touch. The app already knows where you are typing. The interesting part is how plainly it tells you when it does not.

Blog