One truth, many views
Multiple clients do not need identical pixels at every instant, but they do need a shared model of what has been committed. The server owns durable state; clients can optimistically show intent while that intent is pending.
Version numbers, ordered events, and explicit acknowledgements help the client distinguish a new value from a late value.
Correctness before smoothness
Optimistic UI is appropriate when rollback is understandable. For high-impact actions, a pending state is more honest than showing success before the server confirms it.
Real-time systems feel trustworthy when they make convergence visible instead of hiding every delay behind animation.
Version the shared state
The server should expose a version or sequence that lets each client compare what it has with what it receives. Clients can optimistically render a local intent, but they need a clear reconciliation path when the authoritative version disagrees.
flowchart LR
A[Client A intent] --> B[Server command]
C[Client B intent] --> B
B --> D[Authoritative state v42]
D --> E[Event v42]
E --> F[Client A reconciliation]
E --> G[Client B reconciliation]
Reconnect and catch up
A reconnecting client should send its last applied sequence. The server can return missed events when the gap is small or a fresh snapshot when it is too large. This is more reliable than assuming the client can infer the missing state from the next live event.
const response = await api.events({ after: lastAppliedSequence });
if (response.kind === "snapshot") replaceLocalState(response.state);
else applyInOrder(response.events);
Choose optimism carefully
Changing a label can usually be optimistic. Sending money, deleting a record, or changing a permission often deserves a pending state. The rule is not “never be optimistic”; it is “make rollback understandable and proportionate to the consequence.”
