Recipes

Folded mutations

Coalesce high-frequency optimistic writes — keystrokes, sliders, drags — with `.folded({ key }, args)`: the local view moves every frame, but the server sees one last-value-wins write per key.

View as Markdown

A mutator called normally is one write per call. For a stream of writes — every keystroke, every frame of a slider or a drag — call .folded({ key }, args) instead. Each call applies to the local engine immediately (the view moves every frame), but only the last args per key are flushed to the server. The plain form stays the committed write your undo/redo replays; the folded form is the live preview.

Folded writes must be absorbing. Replaying only the last args has to equal replaying all of them — a last-value-wins column patch, never an increment()-style delta — and a folding mutator can’t read (yield tx.row / tx.query). Break that and the coalesced result diverges from the full stream.

In the wild — Strut

Strut is a slide-deck editor built on Rindle. It streams every keystroke of a slide’s rich-text body through a folded mutation and only writes the plain, committed form on blur. The mutator itself is an ordinary last-write-wins column patch — nothing about it knows it will be folded:

// shared/app-def.ts — one isomorphic body, driven by both tiers
setSlideDoc: shared(
  setSlideDocArgs,
  function* (tx: IsoTx, a: SetSlideDocArgs): MutationGen {
    yield tx.update('slide', { id: a.id, doc: a.doc, modified: a.now })
  },
),

shared/app-def.ts L616–622 · Strut

The call site picks the form per event — stream on input, plain on commit. Note the fold key: a per-keystroke stream keyed by slide.id collapses to one write; a compound `${slide.id}:${idx}` folds each editable cell independently:

// src/editor/useSlideDocEditor.ts
function writeDoc(doc: string) {
  if (idx <= 0) {
    mutate.setSlideDoc({ id: slide.id, doc, now: Date.now() })
  } else {
    const cells = writeCellDoc(slideRef.current.cells, idx, doc)
    mutate.setSlideCells({ id: slide.id, cells, now: Date.now() })
  }
}
function streamDoc(doc: string) {
  if (idx <= 0) {
    mutate.setSlideDoc.folded(
      { key: slide.id },
      { id: slide.id, doc, now: Date.now() },
    )
  } else {
    const cells = writeCellDoc(slideRef.current.cells, idx, doc)
    mutate.setSlideCells.folded(
      { key: `${slide.id}:${idx}` },
      { id: slide.id, cells, now: Date.now() },
    )
  }
}

src/editor/useSlideDocEditor.ts L58–82 · Strut

Same mechanism drives Strut’s rotation/scale sliders and its slide-card drag (mutate.setSlideTransform.folded({ key: s.id }, …)) — any gesture that fires faster than the network.

See also