Rebasely Beta: data may be purged

examples/live-notes Public

Markdown · 60 lines · 2.9 KiB · at bd4f325

---id: conflict-strategytype: conceptowner: justinreviewed: 2026-10-14component: opsdescribes:  - src/ops.ts  - src/room.ts  - client/session.ts---# Conflict strategyEdits are operations, not new copies of the text. There are two kinds: insert some text at anoffset, and delete a number of characters at an offset. Offsets are UTF-16 code units, the unita textarea's `selectionStart` uses. Replacing a selection is a delete followed by an insert.## The room decides the orderEvery op carries the version of the note it was written against. The note's room([[note-room]]) is the only place ops are ordered ([[0001-single-sequencer-per-note]]). When anop arrives written against version 41 and the room is at 43, the room takes ops 42 and 43 fromits log and moves the new op past each of them with `transform()`. It applies the result, savesit as version 44, acks the sender and sends the transformed op to everyone else.Because the room handles one message at a time, there is never a question of which op camefirst: the one with the lower version.## Transform`transform(op, other, otherFirst)` rewrites `op` so it applies after `other`, when both werewritten against the same text. The cases are few:- Inserts before an op shift it right by the inserted length; deletes before it shift it left.- Two inserts at the same offset: the one the room sequenced first ends up first. The room  passes `otherFirst = true` for logged ops; an editor passes `false` when it moves a remote op  past its own pending edits. Both sides then put the same text first.- Overlapping deletes only delete what is still there.- An insert strictly inside a range someone else deleted at the same time is dropped, and the  delete grows to cover it ([[0002-drop-inserts-inside-concurrent-deletes]]).The tests in `test/ops.test.ts` check every pair of random concurrent ops converges in eitherorder, and `test/session.test.ts` runs three editors against a room with messages delivered inrandom interleavings.## The editor's side`client/session.ts` keeps at most one op in flight. Edits made while it waits for the ack go ina queue, folded together with `compose()` when they touch the same spot, so a fast typist sendsa word per round trip. When a remote op arrives, the session moves it past the in-flight op andthe queue, and moves those past it, the same way the room will. When the ack arrives, the nextqueued op goes out written against the version in the ack.## When an editor is too far behindThe room keeps the last 200 ops. An op written against an older version, one from a versionthe room has not reached, one that does not fit the text after transforming, or one that wouldtake the note past 100,000 characters gets a snapshot back instead of an ack. The editor shows the snapshot and drops its pending edits.In practice this happens after a long disconnect, and the page disables the textarea while itis offline so nothing is typed into the void.