Rollback must restore the pre-failure UI, not leave a pending ghost
Aliases: pending ghost · leftover sending · restore snapshot
What it is
An optimistic write fails. The screen has to return to the world of the frame before the press: the heart unlit, the comment absent from the list, the switch on its original side. Rollback’s moment is the beat the authority refuses; the act is peeling the optimistic layer off, not pasting “failing” or “sending” on top of it so it stalls halfway. What remains halfway is a transitional trace — a greyed comment, a half-lit heart, a send arrow that spins forever. The trace is neither success nor pre-failure. Nothing can be done on it.
This leaf is which state the UI is placed in. That the failure must be spoken is another obligation; here the placement itself has to be clean.
Why it happens
Optimistic update inserts an extra state in the UI machine: locally committed, not yet authorised. Success promotes that state to confirmed; failure must erase it so the machine sits in the last legal pre-failure state. If erase is implemented as “patch this state with failure”, the machine gains a legal state the product never designed — a half comment, a half heart. Later interaction (tap again, delete, quote) lands on that illegal combination, and a network failure becomes a long-lived interface ghost.
The timing is also narrow. If rollback waits for an animation to finish or a page to leave before rewinding, people have already planned the next glance on the optimistic state. A late rewind collides with that plan. The frame the authority refuses should complete the erase: optimistic pixels gone, pre-failure pixels present. A notice may overlay that frame; it must not use the halfway object as the notice.
Where it stops holding
A failure that needs handling (enter a code, pick another recipient) opens a new, named dialog on top of the pre-failure state; that is not a trace, it is a new task. A trace is a nameless half-state. “Pending send” in an offline queue is a legal state because it is queryable, cancellable, and promises later honouring; a rollback failure is not a queue and should not borrow pending’s look. A few frames of motion from optimistic back to pre-failure are fine if the terminal frame is pre-failure and intermediate frames are not interactive new objects. List virtualisation that, after rollback, leaves row height or scroll position wrong is also a trace — the content is right, the space still sits in a layout that had been optimistic.
Applying it
- Snapshot each optimistic field at pre-failure; on refusal, replace with the snapshot. Do not merely restyle the optimistic value.
- The terminal rollback state must not retain “sending”, “syncing”, a translucent row, a frozen bar.
- Overlay the notice on a clean pre-failure UI (toast, in-row error). Do not let the halfway object speak for itself.
- How to check: like optimistically, force failure. The terminal frame should be unlit, not a grey heart or a spinner. Post a comment and fail: that row should be absent, scroll as if it had never been inserted. Step the recording; if some frame between optimistic and pre-failure holds more than half a second and is tappable, that is a trace.
Related
- Same group: I3.09.2 Stacked optimistic steps need a defined rollback radius · I3.09.3 A rollback notice must say why it failed and whether retry is possible · I3.09.4 Don't commit later decisions to an unconfirmed optimistic result
- Nearby: I3.02 Optimistic updates · I3.01 Visibility of system status
- Search terms:
clean rollback·optimistic restore·pending ghost