ADR-0090: Undo coalescing by word, decided in the core
- Status: Accepted
- Date: 2026-09-10
- Deciders: jka
- Decision: D90
Context
Every keystroke was its own undo step, so undoing a typed word took one undo per letter. Grouping had been planned from the start. The questions were where the grouping is decided and what the unit is.
Decision
Grouping is decided in the core, in rio::doc::edit, the single path every edit takes.
The undo history belongs to the core (ADR-0003), so every frontend gets the behaviour
with no view change, and undoing a group emits one buffer.changed.
The rule is word granularity:
- A new edit merges into the top undo record if it is a single character that continues it: typing forwards at the end of the run, Backspace immediately before its start, or Delete repeated at the same position.
- A run ends after a blank character joins it, so "the " and "quick " are separate steps.
- A newline never joins a run.
- Anything else (a paste, typing over a selection, Replace All, an agent edit, any multi-character edit) is its own step and closes the run.
The core uses neither a clock nor the caret. A run continues only while edits are adjacent, so moving the caret and typing elsewhere ends it without the frontend reporting cursor moves (ADR-0022).
buffer.replace accepts an optional coalesce flag, default on. A frontend sends 0
for a discrete command, meaning "start a new step here". The flag breaks the run before
the edit, and the new step can still grow. The vi extension sets it on every
normal-mode and visual-mode key, so repeated x or dd are separate undo steps while
i followed by typing is one.
Alternatives considered
- Whole-run grouping as in vim. The maintainer chose word granularity.
- An idle timeout that ends a run. Rejected for now because it makes the model time-dependent; it could be added later without changing the protocol.
Consequences
- Undo behaves as users expect, in every frontend.
- Two identical single-character deletions are indistinguishable to the core; only the
frontend knows whether they were one gesture, hence the
coalesceflag. - A reload of a changed file is always its own undo step (ADR-0094).