Edit the plan like a person: the caret travels, selects, erases, types in place, and cuts and pastes
Merged
CH
19d ago+1193+1193+0123456789012345678901234567890123456789-188-188-012345678901234567890123456789
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
diffArraysover the file's own word, space, and line-break tokens.diff@9.0.0is 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.
- The comparison is jsdiff's
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.
MarkdownMessagenow takes aneditprop (hiddenrange,selection,caretat any offset) in place ofreveal, drawn on the rendered tree as before. The selection is a<mark>holding text nodes, so session replay still masks it.SCRIBE_INSTRUCTIONSgains 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
nearestwhile 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