Skip to content
C.W.K.
Stream
Lesson 06 of 06 · published

The Courtesy That Ate the Words

~12 min · pasteboard, clipboard, data-loss, uncertainty, persistence

Level 0Unlit
0 XP0/48 lessons0/13 achievements
0/100 XP to next level100 XP to go0% complete
"The clipboard coming back is a convenience. The words he just spoke are the product."

Why Dad Had Turned the Restore Off

The pasteboard etiquette in lesson 5 — save the clipboard, paste, put it back — shipped, and Dad turned it off. Asked why, he gave the reason in one sentence: the transcript itself was disappearing. When focus had drifted out of the text field during the seconds of transcription and cleanup, the synthetic Cmd+V went nowhere. Then the restore ran, as designed, and overwrote the clipboard with what he had copied before. The dictation existed only on the clipboard, and the courtesy erased it. The method still reported "inserted".

The Gap the Guards Missed

The app already guarded a secure field and a different front app. What it never checked was focus moving within the same app: a click into the sidebar, a dialog, the cursor leaving the field. The snapshot taken when dictation started says nothing about where focus is when the paste finally goes out, seconds later. And there is no macOS API that reports "your paste was consumed". So the restore was acting on a fact nobody had established.

Restore Only on the All-Clear

The fix does not refuse the paste — Electron shells, web views and terminals accept pastes while reporting roles a checker would not recognise, and gating the paste would break paths that work. It gates only the restore, which cannot regress anything: the worst new outcome is that the old clipboard does not come back, and Dad can copy that again, while a lost dictation is gone. The previous clipboard is put back only when three things hold, asked at paste time: the front app is unchanged, nothing else wrote to the pasteboard since our write, and the focused element looks like something a paste lands in. With restore on and a complete snapshot, any doubt leaves the clipboard alone, and the status says: "could not confirm the paste — the text is still on the clipboard." One case makes that sentence wrong. If another app wrote to the clipboard during the paste window, its newer copy is kept, as it should be, so the transcript is no longer there; with history on, it is still in Firekeeper's history list.

paste goes out (Cmd+V), wait 0.35 s
restore the old clipboard ONLY IF
    front app unchanged since the key went out
 AND pasteboard untouched since our write
 AND focused AX role looks textual (asked now, not at start)
else
    leave the clipboard alone (the transcript, or another app's newer copy)
    report "could not confirm the paste — the text is still on the clipboard"
    (wrong in the newer-copy case; history keeps the words, if on)
(no complete snapshot: nothing to restore, and the status reads pasted)

All or Nothing, Twice

The same day, a family survey looking for duplicated code found a second flaw. The save had read only the plain-text flavor, so after any dictation a copied image, an RTF fragment, a file promise or an HTML clipping came back as text or not at all. Firekeeper took the shape every other family app already used: PasteboardSnapshot captures every item and every representation, or nothing. An incomplete snapshot refuses to restore at all, because handing back the text of an image looks like the app replaced the clipboard, while not restoring leaves the dictated text there. One cost comes with it: with nothing to restore, the unconfirmed-paste check never runs, so the status reads pasted even if the paste went nowhere. The words are on the clipboard, but the status line does not say so.

The Same Shape in the History File

Turning on disk history that evening exposed the third. A persisted record with a synthesized decoder throws when the file lacks a field the struct just gained; the store read with try?, got an empty list, and the next dictation would have written "empty plus one" over the whole history. Now every field decodes with a fallback, and an unreadable file is moved aside instead of overwritten. Starting empty was survivable; starting empty and writing back is what destroys.

When a courtesy and the user's data conflict, uncertainty resolves toward the data. Restoring a clipboard, compacting a file, cleaning a temp folder — each is polite when you know its precondition holds and destructive when you do not. Act only on the all-clear, and make every unknown fall on the side that keeps what the user just made.
Never report success you did not observe. The old path returned "inserted" after a paste that landed nowhere. If no API can confirm the effect, say what you know ("could not confirm the paste") instead of what you hoped. Firekeeper still misses this in one case, the incomplete snapshot above.

Code

The one decision the restore needs (FirekeeperKit PasteTargetRole)·swift
public enum PasteTargetRole {
  /// nil when nothing is focused at all — an unknown, not a no.
  public static func acceptsPaste(role: String?, subrole: String?) -> Bool? { /* textual roles */ }

  /// Restoring is only safe when the paste plausibly landed.
  /// Every uncertainty resolves the same way: don't restore.
  public static func mayRestoreClipboard(
    frontAppUnchanged: Bool,
    pasteboardUntouchedSinceOurWrite: Bool,
    targetAcceptsPaste: Bool?
  ) -> Bool {
    guard frontAppUnchanged else { return false }
    guard pasteboardUntouchedSinceOurWrite else { return false }
    return targetAcceptsPaste == true
  }
}

External links

Exercise

Find a 'tidy-up' step in something you built: restoring state, deleting a temp file, overwriting a cache. Write its precondition (what must be true for the tidy-up to be harmless). Can your code actually observe that precondition? If not, which way does it fall when it is unsure — toward the user's data, or toward tidiness?
Hint
Most tidy-ups assume their precondition instead of checking it, because it is usually true. The test is the unusual case: focus moved, a write half-failed, a file was newer than expected. Make the unsure case keep the data, and report it.

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 in — Please sign in to comment.

No comments yet — be the first.