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

One Strike, One Undo

~10 min · one-undo, single-action, pasteboard, acceptance

Level 0Cold Flint
0 XP0/34 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"The whole transformation should vanish with a single Cmd+Z. If it takes three, you built it wrong."

One Action, One Undo

When Flint replaces a selection, the replacement should register in the target app as exactly one undoable edit — so a single Cmd+Z puts the original text back, cleanly. The native AX replace already behaves this way: setting the selected-text attribute is one edit. For paste-based insertion into opaque targets, the equivalent is a single Cmd+V. The rule is 'one strike, one undo': whatever the transformation did, the user can take it back in a single step, in their own app's undo, without Flint having to implement undo itself.

Why Not Type Character by Character

There's a tempting alternative — synthesize the result as individual keystrokes, typing it in character by character. Avoid it wherever you can, because it shatters the undo stack: every character (or chunk) can become its own undo entry, so taking back a transformation means mashing Cmd+Z dozens of times and probably not landing cleanly. A single paste or a single AX replace collapses the whole change into one reversible action. Character-by-character insertion is a last resort for targets that genuinely need it, and it comes with an undo cost you accept deliberately, not by accident.

SINGLE PASTE / AX REPLACE     CHARACTER-BY-CHARACTER
-------------------------     ----------------------
one edit on the undo stack    N edits on the undo stack
Cmd+Z restores original       Cmd+Z removes one char
clean, predictable            fragmented, frustrating

Prefer the single action. Fall to typing only when forced.

The Paste Also Preserves the Clipboard

Paste-based insertion borrows the clipboard to carry the result, which means it inherits the same obligation the read probe had: preserve what was there. Snapshot the full pasteboard, put the result on it, issue one Cmd+V, then restore the user's original clipboard — every item and type, exactly as in Track 3. So a paste insertion is two disciplines at once: one undoable action for the target, and complete clipboard restoration for the user. The result lands, the undo is clean, and the clipboard the user had is handed back untouched.

Collapse a compound effect into a single undoable unit. When your action is really several steps under the hood, the user should still experience it as one thing they can reverse in one gesture. Leaking your internal steps into their undo stack makes your convenience their cleanup. Design the effect so the smallest unit the user can undo matches the unit of intent — one strike in, one undo out.
The first acceptance check is exactly this. Flint's first end-to-end test is the Correct macro: select intentionally misspelled text, strike its shortcut, run a local model with the vault bypassed, verify one replacement lands — and verify that one Cmd+Z restores the original. That single-undo requirement isn't a nicety bolted on at the end; it's the acceptance bar the whole insertion layer is built to meet.

Code

Paste as one undoable action, clipboard preserved·swift
func pasteReplace(_ result: String) {
    let pb = NSPasteboard.general
    let saved = capturePasteboard(pb)          // Track 3: full snapshot

    pb.clearContents()
    pb.setString(result, forType: .string)
    synthesizePaste()                          // ONE Cmd+V == one undo step

    // Give the user's clipboard back, every item and type.
    restorePasteboard(pb, from: saved)
}

func synthesizePaste() {
    let src = CGEventSource(stateID: .combinedSessionState)
    let vKey: CGKeyCode = 0x09                  // kVK_ANSI_V
    let down = CGEvent(keyboardEventSource: src, virtualKey: vKey, keyDown: true)
    down?.flags = .maskCommand
    let up = CGEvent(keyboardEventSource: src, virtualKey: vKey, keyDown: false)
    up?.flags = .maskCommand
    down?.post(tap: .cghidEventTap); up?.post(tap: .cghidEventTap)
}

External links

Exercise

Two insertion implementations produce the same visible result: (A) one Cmd+V of the whole transformed text, (B) synthesizing each character as a keystroke. A user strikes 'Correct' on a misspelled sentence, sees it fixed, then presses Cmd+Z once. Describe what happens under each implementation, and explain why the single-undo behavior is treated as an acceptance requirement rather than a nice-to-have.
Hint
Under (A) one Cmd+Z restores the entire original misspelled sentence — clean. Under (B) one Cmd+Z removes only the last typed character, and the user must press it many times, likely landing in a messy in-between state. Single-undo is an acceptance requirement because a transformation the user can't cleanly take back is not trustworthy: reversibility in one gesture is what lets them experiment freely, so it's part of what 'the feature works' means, not a bonus.

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.