From 157358c57e61199eca578f3851d79f749d488a07 Mon Sep 17 00:00:00 2001 From: Levi Neuwirth Date: Tue, 7 Jul 2026 21:11:32 -0400 Subject: [PATCH] fix(daemon): follow-active-buffer snapshots are semantic-sessions only Round-2 regression (TUI could not attach): the follow path fired for every crdt_replica session, and its first-tick send delivered a GUARANTEED duplicate BufferSnapshot right after the attach sweep. The grid TUI's BufferMirror is init-once -- the duplicate errored ('buffer already has a CRDT snapshot applied') on every attach, and every TUI buffer switch would have produced another. Display-follows-snapshot is a grid-less-frontend concept: the GPU rebuilds its replica wholesale on every snapshot and is the only consumer that needs the follow. The send is now gated on negotiated_capabilities.semantic_render -- TUI sessions get zero follow-sends, byte-identical wire behavior to main. Verified: PTY-doubled real-binary attach tests pass with the gate; the one failing PTY test (m10_11_doubled_pty_fixture_propagates_ keystrokes_from_both_sides, 'AXYB' interleaving) fails identically on main 3/3 -- pre-existing, tracked separately, not this branch. Co-Authored-By: Claude Fable 5 --- src/daemon.rs | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/src/daemon.rs b/src/daemon.rs index d58013f..c025d43 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -992,15 +992,29 @@ fn dispatcher_loop( // typing-into-a-buffer-you-can't-see hazard. Ship the // now-active buffer's snapshot to THIS frontend only // (its own view changed; nobody else's did). - let active_now = { - let core = editor.core.borrow(); - core.active_window_for(*fid).map(|w| w.buffer_id) - }; - if let Some(active_now) = active_now - && last_active_buffer_sent.get(fid) != Some(&active_now) + // + // SEMANTIC sessions only: display-follows-snapshot is + // a grid-less-frontend concept, and the GPU rebuilds + // its replica wholesale on every snapshot. The grid + // TUI renders via CellDelta and its `BufferMirror` is + // init-once — a follow send there is a guaranteed + // duplicate that errors ("already has a CRDT snapshot + // applied") on every attach and every buffer switch + // (the PR #94 round-2 startup regression). + if session_registry + .session_state(*fid) + .is_some_and(|s| s.negotiated_capabilities.semantic_render) { - send_buffer_snapshot_to_frontend(editor, active_now, *fid, &mut streams); - last_active_buffer_sent.insert(*fid, active_now); + let active_now = { + let core = editor.core.borrow(); + core.active_window_for(*fid).map(|w| w.buffer_id) + }; + if let Some(active_now) = active_now + && last_active_buffer_sent.get(fid) != Some(&active_now) + { + send_buffer_snapshot_to_frontend(editor, active_now, *fid, &mut streams); + last_active_buffer_sent.insert(*fid, active_now); + } } } #[cfg(not(feature = "crdt"))]