From 0902a9e173a3e579776d989e4bc7778a486d10c7 Mon Sep 17 00:00:00 2001 From: Levi Neuwirth Date: Thu, 21 May 2026 11:30:29 -0400 Subject: [PATCH] Revert "session 5 fixup: clear styling on CrdtOp ..." MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- pmacs-gpu/src/main.rs | 52 +++++++++++++++++++------------------------ 1 file changed, 23 insertions(+), 29 deletions(-) diff --git a/pmacs-gpu/src/main.rs b/pmacs-gpu/src/main.rs index 7e6fa73..e9f980e 100644 --- a/pmacs-gpu/src/main.rs +++ b/pmacs-gpu/src/main.rs @@ -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 - // (100ms–5s), 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