Revert "session 5 fixup: clear styling on CrdtOp ..."

The clear-on-CrdtOp change broke the producer's incremental-update
contract. The producer ships dirty-range spans only on `full=false`
frames; the frontend is expected to retain non-dirty spans across
edits. Emptying both vectors meant the frontend ended up with only
the small dirty-range spans, missing the rest of the viewport — all
colors disappeared after an edit.

Reverting here. The proper fix lives in pmacs core (T M11.7):
producer must force `full=true` on generation transitions so the
frontend gets a complete replacement set on every text edit. Once
that lands, session-5's CrdtOp handler doesn't need to clear
anything — the next frame's `full=true` does it via
`replace_style_spans` / `replace_decorations`.

This reverts commit 49785c4. Returns the consumer behavior to
session-5's original "one-frame stale" artifact pending the core
fix.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Levi Neuwirth 2026-05-21 11:30:29 -04:00
parent abd6f46eee
commit 0902a9e173
1 changed files with 23 additions and 29 deletions

View File

@ -410,13 +410,13 @@ impl State {
/// (avoids the re-shape cost when an unchanged buffer ticks).
///
/// Replaces the rope text and routes through `reshape` so the
/// rich-text rendering uses the current `current_spans` and
/// `current_decorations`. The `CrdtOp` arm of
/// [`Self::apply_attach_message`] clears both vectors before
/// calling `set_text`, so post-edit text never renders with
/// pre-edit-position colors. Brief uncolored window between an
/// edit and the daemon's next styling frame is the accepted
/// tradeoff (see that arm's doc comment for the rationale).
/// rich-text rendering uses the current `current_spans`. When
/// called from the `CrdtOp` path (text shifted under existing
/// spans) the spans are momentarily stale relative to the new
/// byte positions — `reshape` clamps via `range.end.min(text_len)`
/// so rendering is safe, but visual styling may be off until the
/// daemon's next `StyleSpans` frame catches up. A real artifact;
/// classified as a session-4 known limitation rather than a bug.
fn set_text(&mut self, text: &str) {
if self.current_text == text {
return;
@ -498,28 +498,22 @@ impl State {
eprintln!("pmacs-gpu: CrdtOp import failed: {e:?}");
return None;
}
// **Invalidate styling on text-shift.** The current
// spans + decorations index into byte positions of
// the *pre-edit* text. Once `set_text` swaps in the
// post-edit text, those positions no longer
// correspond to the right characters, so painting
// them produces wrong-character-colored fragments
// (the session-5 manual-validation finding: probe
// #3, "edit at a diagnostic boundary leaves stale
// color fragments"). The session-4 PR documented
// this as a "one-frame stale" artifact; in practice
// it stretches to the full LSP re-analysis cycle
// (100ms5s), making the wrong-color period
// visible for human-perceptible durations.
//
// The fix is to drop both vectors here. Tree-sitter
// re-emits StyleSpans almost immediately (one
// frame after CrdtOp lands daemon-side); LSP
// decorations re-emit when clangd republishes.
// Cost: a brief uncolored window after each edit;
// gain: no wrong-position color persists.
self.current_spans.clear();
self.current_decorations.clear();
// NOTE: `current_spans` / `current_decorations` index
// into the *pre-edit* byte positions. The producer's
// next render frame (in pmacs core, post-T M11.7
// generation-transition fix) ships `full=true`
// styling for buffers whose generation advanced, so
// the next message replaces the stale items
// wholesale via `replace_style_spans` /
// `replace_decorations`. The single-frame gap
// between CrdtOp arrival and that next frame paints
// styling at stale byte positions — the session-4
// documented "one-frame stale" artifact. A previous
// attempt to fix it by clearing both vectors here
// (`49785c4`) was reverted because the producer's
// *incremental* updates ship dirty-range spans only,
// and an emptied cache loses the non-dirty viewport
// styling entirely.
let text = doc.get_text(LORO_TEXT_CONTAINER).to_string();
self.set_text(&text);
None