# 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.

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](https://github.com/tantaman/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:

```ts
// 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](https://github.com/tantaman/strut/blob/427664b5aec95c099be5d6b8486f60c2cb748edb/shared/app-def.ts#L616-L622) · 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:

```ts
// 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](https://github.com/tantaman/strut/blob/427664b5aec95c099be5d6b8486f60c2cb748edb/src/editor/useSlideDocEditor.ts#L58-L82) · 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

- [Fine-grained reactivity](/docs/fine-grained-reactivity) — folding a per-frame drag while only the
  dragged row re-renders.
- [The browser client](/docs/client#folded-writes-high-frequency-drags) — the folded-write API in full.

---

[View this page on Rindle](https://rindle.sh/docs/folded-mutations)
