Edit the plan like a person: the caret travels, selects, erases, types in place, and cuts and pastes

Merged
CH
19d ago
+1193-188

The plan used to grow only by typing at its end. A rewrite cut back to the start of the changed line and retyped from there. Now one caret edits the document the way a person would in a Google Doc: it jumps to each change, selects or backspaces the old words, types the new ones in place, and cuts and pastes a bullet that moved. It pauses at sentence ends, line breaks, and before each jump.

How it works

  • planning/plan-diff.ts (new): a word-level diff of each unit.
    • The comparison is jsdiff's diffArrays over the file's own word, space, and line-break tokens. diff@9.0.0 is a new desktop dependency, adopted on purpose over a hand-written table: it adds about 0.8 KB gzipped to the renderer, inside the bundle budget's slack, so the baseline doesn't move.
    • A lone shared word between two changes is folded into a single change.
    • A partly typed word carries on rather than being retyped.
    • An added or removed line slides to whole-line boundaries.
    • A line that moved unchanged is paired as a cut and a paste.
    • A change past 400 tokens is one replacement of the whole unit.
  • planning/plan-reveal.ts (rewritten): one caret and one act at a time (travel, type, backspace up to 12 characters, select then erase or cut, paste). Each act comes from a fresh diff of what's shown against the target, so a draft arriving mid-act is just the next diff.
    • The average pace stays at the streaming model's 200 cps from #1763.
    • Far behind the notetaker, the whole pace speeds up in proportion.
  • Streaming: new text at the end of a field types live as it streams in. A rewrite waits until the field settles, then plays as edits in place. A field settles when:
    • the next draft leaves it unchanged (the notetaker moved on),
    • the plan saves or the call ends,
    • or 1.2 s passes with no change.
      Diffing a cut-off rewrite erased lines the stream hadn't reached yet and showed the old bullet twice.
  • MarkdownMessage now takes an edit prop (hidden range, selection, caret at any offset) in place of reveal, drawn on the rendered tree as before. The selection is a <mark> holding text nodes, so session replay still masks it.
  • SCRIBE_INSTRUCTIONS gains one line: copy unchanged lines and bullets exactly, so diffs stay clean. It has no test, because a test of prompt text would only check that the text is there.
  • The view centers on the caret when it jumps elsewhere, and still follows with nearest while typing.

Known limits

  • Only a bullet that moved word for word is cut and pasted. When the neighbouring bullets changed too, the diff sometimes retypes a bullet in place instead.
  • No fake typos.

CI could not verify this. It's a Mac UI change and CI builds nothing for the Mac. It hasn't had a ./scripts/verify.sh run. I checked the motion by rendering frames from the real animation code against a simulated notetaker stream in headless Chrome. ./scripts/check.sh passes on the final commit.

šŸ¤– Generated with Claude Code