From bf36c01502668f01b28d648e995ae3e1ad74591a Mon Sep 17 00:00:00 2001 From: Julien Calixte Date: Sun, 17 May 2026 13:00:31 +0200 Subject: [PATCH] docs(notes): trim Ctrl-G essay to its core (question + apocalypse) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The post's job is to land the timing question and the apocalypse reframe — Wi-Fi rationale, optimisation diversions, and generalising reflections were overexpansion. Cut. --- docs/notes/ctrl-g-perceived-latency.md | 151 ++----------------------- 1 file changed, 10 insertions(+), 141 deletions(-) diff --git a/docs/notes/ctrl-g-perceived-latency.md b/docs/notes/ctrl-g-perceived-latency.md index 7e18032..3fcad84 100644 --- a/docs/notes/ctrl-g-perceived-latency.md +++ b/docs/notes/ctrl-g-perceived-latency.md @@ -1,151 +1,20 @@ # Durability before delivery -> Why a 10-second keystroke on a typewriter I'm building doesn't have to -> feel slow. +> Why a 10-second keystroke on a typewriter I'm building doesn't have to feel slow. -## Two keystrokes, very different costs +I'm building a small device called **Typoena**: an e-ink panel, a mechanical keyboard, an ESP32-S3. You open the lid, you write Markdown, you press a key to publish it to GitHub. The whole product surface is two user-facing actions: -I'm building a small device called **Typoena**: an e-ink panel, a -mechanical keyboard, an ESP32-S3, and a single purpose. You open the lid, -you write Markdown, and you press a key to publish it to GitHub. There is -no browser, no notification tray, no second app. The hardware enforces -focus the way software can't. +- **Save** (`Ctrl-S`) — write the buffer to the SD card. ~200 ms. +- **Publish** (`Ctrl-G`) — ship the working copy to the git remote. **5–10 seconds.** -The whole product surface is two user-facing actions: +The same kind of keystroke, but one takes 50× longer than the other. Sitting with that, here's the concern I couldn't shake: -- **Save** (`Ctrl-S`) — write the current buffer to the SD card. Always - available. ~200 ms. -- **Publish** (`Ctrl-G`) — ship the entire tracked working copy to the git - remote. Atomic from the user's view. **5–10 seconds typical.** +> "I'm concerned about the fact that Ctrl-G is a 150ms action to do, but what it triggers can take >5-10s. Compared to the same quick action Ctrl-S for instance that will have a order of magnitude even lower than the pressing key action." -These two keystrokes _look_ symmetric. Both are modifier-letter, both -triggered the same way, both single-shot. But one of them takes 200 ms -and the other takes 5–10 seconds. That's a 50× cost gap, and it matters -because the human perception threshold for "instant" is about 100 ms. +The reframing question: **what is the user actually waiting for?** For `Ctrl-S`, the moment that matters is "my work is saved" — and the SD card completes the write in 50–200 ms. Save = safe. Same instant. -The physical keystroke itself — key down, debounce, USB report, key up — -takes ~150 ms. On `Ctrl-S` that's _most_ of the perceived time. On -`Ctrl-G`, the keystroke is barely the first sliver of a long process. +For `Ctrl-G`, the equivalent moment isn't "push complete." It's "commit landed locally" — which happens at ~0.2 seconds, well before the push even starts. From that moment on, your work is preserved across power loss, SD removal, the apocalypse — everything except remote delivery. The remaining 5–10 seconds is _transport of an already-safe thing_. -## Where the time goes +Surface that moment in the status line at ~0.2 seconds (`✓ committed abc1234 · pushing…`) and the perceived latency of `Ctrl-G` collapses from 10 seconds to roughly 200 milliseconds. The gap with `Ctrl-S` disappears. -Here's a breakdown of `Ctrl-G` on a fresh session, with the Wi-Fi radio -starting cold: - -| Stage | Time | -| --------------------------------- | --------- | -| Save buffer to SD | ~0.1 s | -| `git add` + `git commit` | ~0.2 s | -| Wi-Fi associate + DHCP | 2–5 s | -| TLS handshake | ~2 s | -| `git push` (pack + send + server) | 1–3 s | -| **Total (critical path)** | **5–10 s** | - -Wi-Fi association dominates, and that's deliberate. Typoena's radio is -**off by default** — it only powers up when `Ctrl-G` is pressed, and it -shuts down again after a short grace window. The battery savings are -dramatic: always-on station mode would burn ~410 mAh/day on the radio -alone; on-demand drops that to ~25 mAh/day at ten Publishes per day. On a -single 18650 cell, that's the difference between days and weeks of -standby. - -The price is paid on every cold Publish. A few seconds, every time. For -a device that, by design, doesn't need to be online except when shipping -work, this trade is fine. But it produces the asymmetry above: one -keystroke costs you a fifth of a second; another costs you ten. - -## The question I was sitting with - -After mapping all this out, I asked the question that triggered the rest -of this post: - -> "I'm concerned about the fact that Ctrl-G is a 150ms action to do, but -> what it triggers can take >5-10s. Compared to the same quick action -> Ctrl-S for instance that will have a order of magnitude even lower than -> the pressing key action." - -My first instinct was to optimise. TLS session resumption could shave a -second. A smaller cipher suite, another. Static IP instead of DHCP, a -few hundred milliseconds. With effort, I might cut the cold path to -4–5 seconds. - -But that's still 25× the `Ctrl-S` cost, and every optimisation comes -with friction. TLS resumption requires storing session tickets across -radio power-cycles (more state, more code). Cipher tuning sacrifices -flexibility on networks I haven't tested. Static IPs are fragile when -the user moves between routers. I'd be spending design budget on a -number that, even halved, still feels slow. - -So I asked a different question: **what is the user actually waiting -for?** - -## A different question - -When you press `Ctrl-S`, the moment you care about is "my work is -saved." The SD card writes the bytes in 50–200 ms, and that moment -lines up with the operation completing. Save = safe. Same instant. - -When you press `Ctrl-G`, what's the equivalent? You'd naturally say "my -work is published" — and assume that means the push completed. But this -device authors timestamp commits _before_ it pushes. The local commit -lands at ~0.2 seconds, and from that moment on your work is preserved -across power loss, SD removal, the apocalypse — everything except remote -delivery. The remaining 5–10 seconds is _transport of an already-safe -thing_. The work isn't in flight; it's already committed to disk. The -push is just delivery to a backup location. - -## Durability before delivery - -This is the design principle the question pushed me toward: **the moment -that matters to the user is the moment durability is achieved, not the -moment delivery completes.** Once I named it, the implementation became -obvious. - -The status line surfaces the commit-landed state at ~0.2 seconds, then -shows the push as a secondary state: - -``` -Bad: "publishing… 1 of 3 ▓░░" ← misleading, conflates safe + delivered -Good: "✓ committed abc1234 · pushing" ← says exactly what's done and what's pending -``` - -Two transitions, two messages, partial-refresh on the status line only. -The user sees the `✓` within a fifth of a second of pressing the key. -They know the work is safe. They can keep typing. The radio continues -associating, the TLS handshake completes, the push lands — all of it -happens around them, none of it modal. - -The _perceived_ latency of `Ctrl-G` collapses from 10 seconds to roughly -200 milliseconds. The gap with `Ctrl-S` is no longer 50×; it's barely -distinguishable. - -## Why this generalises - -I think this is a useful lens beyond writing appliances. Any application -that does network I/O in response to a single user action has the same -shape: a fast local operation followed by a slow remote one. The usual -responses are: - -1. **Optimise the slow part until it feels fast.** Often impossible. -2. **Hide the slow part with a spinner.** Admits defeat — and on e-ink, - with ~300 ms refresh and ghosting, you can't even spin. -3. **Quietly do the operation in the background and not tell the user.** - This is the auto-sync trap I explicitly designed Typoena to avoid — - the device is a writing tool, not a sync engine. - -The fourth path — name the durability moment, surface it the instant it -arrives — is almost always available. It shifts the question from "how -fast is the operation" to "when is the user safe." Those are different -questions with different answers, and the second one is almost always -faster. - -## What I'm not optimising - -I'm not chasing a v1.0 target of `Ctrl-G` in ≤10 seconds on the cold -path. With the safety moment landing at ~200 ms, that target is no -longer the load-bearing UX metric. I'd rather spend the engineering -effort on something the user can actually feel: typing latency, -partial-refresh ghosting, keyboard wake time. - -Durability before delivery. Once you see it, you can't unsee it — and -suddenly the slow operations stop feeling slow. +**Durability before delivery.** The moment that matters to the user is the moment durability is achieved, not the moment delivery completes. Once you see that, the slow operations stop feeling slow.