Description
Every InsertRune→followCursor→buildVisual rescans+rewraps all lines; MoveUp/MoveDown call buildVisual then followCursor calls it again (2 full scans/vertical keystroke). Fine for notes, costly for long docs in the writing canvas. Fix: thread one []vrow through move→followCursorWith(rows); longer term cache the visual model, invalidate on edit/resize.
Acceptance Criteria
- #1 at most one buildVisual per keystroke
Implementation Plan
- RED: add buildCount instrumentation + tests asserting MoveUp/MoveDown/MoveToVisual each call buildVisual at most once per keystroke.
- GREEN: extract followCursorWith(rows []vrow); have followCursor() = followCursorWith(buildVisual()). Thread the single rows slice from MoveUp/MoveDown/MoveToVisual into followCursorWith instead of re-scanning.
- Verify suite + vet green; check AC #1.
Implementation Notes
Extracted followCursorWith(rows); MoveUp/MoveDown/MoveToVisual now scan once and reuse the rows for scroll-follow (was 2 scans/vertical keystroke). buildCount instrumentation + 3 perf tests assert <=1 buildVisual per move. Suite + vet green.