From f381c373e3d8ab84c9393d24f091bf8dae655dd5 Mon Sep 17 00:00:00 2001 From: Andrew Blakeslee Moore Date: Mon, 6 Jul 2026 18:48:30 -0700 Subject: [PATCH] Transcript Incremental Projection Nucleic-Session: 0FFD007B-0696-4517-9429-129C7B0FD5AC Co-authored-by: Nucleic --- NucleicRemote/NucleicRemote/Models/RemoteStore.swift | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/NucleicRemote/NucleicRemote/Models/RemoteStore.swift b/NucleicRemote/NucleicRemote/Models/RemoteStore.swift index e02ecd0..d041181 100644 --- a/NucleicRemote/NucleicRemote/Models/RemoteStore.swift +++ b/NucleicRemote/NucleicRemote/Models/RemoteStore.swift @@ -639,20 +639,23 @@ final class RemoteStore: ObservableObject { guard let self, hostID == self.openSessionHostID, snap.summary.sessionID == self.openSessionID else { return } // Merge, don't replace: a fresh open merges into an empty transcript (the tail window), // while a reconnect's warm-resubscribe delta appends to the history already on screen - // instead of truncating it to the host's 200-event tail. + // instead of truncating it to the host's 200-event tail. Drain the streaming buffer + // first so the seq-dedup merge sees the full stream (a buffered event the snapshot + // also carries would otherwise be re-appended as a duplicate after the merge). + self.drainPendingOpenEvents() self.openEvents = Self.mergedEvents(self.openEvents, snap.recentEvents) self.openApprovals = snap.pendingApprovals self.persistOpenTranscript() } cb.openEvents = { [weak self] batch in guard let self, hostID == self.openSessionHostID, batch.sessionID == self.openSessionID else { return } - self.openEvents.append(contentsOf: batch.events) - self.persistOpenTranscript() + self.enqueueOpenEvents(batch.events) } cb.openBackfill = { [weak self] batch in guard let self, hostID == self.openSessionHostID, batch.sessionID == self.openSessionID else { return } // Full-history backfill precedes what's on screen — merge by seq so it slots in above the - // tail rather than appending out of order. + // tail rather than appending out of order. Drain first (same reason as openSnapshot). + self.drainPendingOpenEvents() self.openEvents = Self.mergedEvents(self.openEvents, batch.events) self.persistOpenTranscript() }