From 5cb7e6659ffb08e4385bc2f4eec710ce4fd2a377 Mon Sep 17 00:00:00 2001 From: Andrew Blakeslee Moore Date: Mon, 6 Jul 2026 19:43:58 -0700 Subject: [PATCH] Transcript Incremental Projection Nucleic-Session: 0FFD007B-0696-4517-9429-129C7B0FD5AC Co-authored-by: Nucleic --- .../NucleicRemote/Models/HostConnection.swift | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/NucleicRemote/NucleicRemote/Models/HostConnection.swift b/NucleicRemote/NucleicRemote/Models/HostConnection.swift index 7fa7d94..2771848 100644 --- a/NucleicRemote/NucleicRemote/Models/HostConnection.swift +++ b/NucleicRemote/NucleicRemote/Models/HostConnection.swift @@ -692,6 +692,39 @@ final class HostConnection { send(.fetchTranscript(TranscriptFetch(requestID: requestID, sessionID: sessionID, afterSeq: 0))) } + /// Whether a background transcript prefetch can start now: live, the host can serve + /// transcript fetches, and no prefetch is already in flight (they run one at a time). + var canPrefetchTranscript: Bool { + connectivity.isLive && capabilities.canSyncTranscripts && client != nil && prefetchRequestID == nil + } + + /// Fetch a session's transcript in the background — no subscribe, no open — so the offline + /// cache is warm before the user ever opens the session. `afterSeq` bounds the pull to what + /// the cache is missing. The result (all chunks, merged) arrives via + /// `callbacks.transcriptPrefetched`; returns false when it can't start (caller retries on a + /// later session-list update). + @discardableResult + func prefetchTranscript(_ sessionID: SessionID, afterSeq: UInt64) -> Bool { + guard canPrefetchTranscript else { return false } + let requestID = UUID().uuidString + prefetchRequestID = requestID + prefetchSessionID = sessionID + prefetchEvents = [] + send(.fetchTranscript(TranscriptFetch(requestID: requestID, sessionID: sessionID, afterSeq: afterSeq))) + return true + } + + /// Settle the in-flight prefetch: hand what arrived to RemoteStore (nothing on + /// unavailable/disconnect) and clear the slot so the next queued prefetch can start. + private func finishPrefetch(delivering: Bool) { + guard let sessionID = prefetchSessionID else { return } + let events = delivering ? prefetchEvents : [] + prefetchRequestID = nil + prefetchSessionID = nil + prefetchEvents = [] + callbacks.transcriptPrefetched(sessionID, events) + } + /// The device's network path changed (Wi-Fi ⇄ cellular, joined/left a network). React now rather /// than waiting for a zombie LAN socket to time out or the reconnect backoff to elapse — this is /// what makes the transport switch feel immediate. Only reconnect-managed hosts (a pinned host)