C.W.K.
Stream
Lesson 04 of 04 · published

Three Modes, Kept Apart

~11 min · mode-taxonomy, command-mode, transform, hotkeys

Level 0Unlit
0 XP0/36 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"The bug wasn't in the code. It was in one hotkey doing two jobs."

The Three Modes

Every serious dictation tool converges on the same three modes, because they're the three things you actually want to do with your voice at a cursor:

  • Dictation: "type what I say." Speech becomes text, inserted at the cursor or replacing a selection.
  • Command: "do what I say to this." Speech is an instruction — "make this shorter," "turn this into bullets" — that rewrites the selected text, or generates new text when nothing is selected.
  • Transform: a saved command you run on demand or automatically — "Polish," "Fix Korean register," "No exclamation marks." A named, reusable command mode.

The Hijack Bug

Here's a real Firekeeper war story. In an early build, command mode was welded onto the dictation hotkey: if text happened to be selected when you started dictating, the app treated your speech as a command and rewrote the selection instead of inserting your words. That sounds clever — until you select a paragraph to look at it, start dictating something unrelated, and your paragraph silently gets "rewritten." One hotkey doing two jobs, disambiguated by a fragile signal (is anything selected?), is a bug generator. The fix was blunt: give command mode its own hold hotkey so a stray selection can never hijack normal dictation.

Bad:   [dictate key] + selection present?  -> maybe dictate, maybe rewrite  (guess!)
Good:  [dictate key]                        -> always dictate
       [command key]                        -> always command

Explicit Beats Clever

The lesson generalizes far past voice apps: when one control's behavior depends on hidden state, users can't predict it, and unpredictable destructive behavior is the worst kind. A separate, explicit control for each intent is boring and correct. Firekeeper's dictation key always dictates; its command key always commands. No mode is ever inferred from whether you happened to have something highlighted.

One control, one intent. When a single trigger changes behavior based on ambient state (a selection, a focused app, a timing window), you've built a coin-flip the user can't see. Give each intent its own explicit trigger. Boring and predictable beats clever and surprising, especially when the surprise edits the user's text.
I liked the clever version at first — "if there's a selection, obviously you mean to transform it." Dad found the failure in about a day: he'd highlighted a sentence to re-read it, dictated a new thought, and watched his sentence get replaced. "Obviously" was doing a lot of load-bearing work in my head that the user never agreed to. Splitting the hotkey was the whole fix, and it never came back.

Code

Separate hotkeys make the mode explicit, not inferred·swift
enum DictationMode { case dictate, command }

// Two independent hold hotkeys — the mode is chosen by WHICH key,
// never by whether a selection happens to exist.
hotkeys.bind(.dictateKey) { phase in
    controller.start(mode: .dictate)   // always inserts spoken text
}
hotkeys.bind(.commandKey) { phase in
    controller.start(mode: .command)   // always treats speech as an instruction
}

// A stray selection can no longer flip dictation into a rewrite,
// because dictation never consults the selection to pick its mode.

External links

Exercise

Find a control in software you use whose behavior changes based on invisible state (a button that does different things depending on what's selected or focused). Describe the surprising case where it does the wrong thing. How would splitting it into two explicit controls fix it — and what would you lose?
Hint
You lose a little tidiness (two controls instead of one) and gain predictability (each control has exactly one behavior). For anything that can destroy user data — like rewriting selected text — that trade is almost always worth making.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.