diff --git a/NucleicRemote/NucleicRemote/Models/RemoteStore.swift b/NucleicRemote/NucleicRemote/Models/RemoteStore.swift index 422f6d0..2445edf 100644 --- a/NucleicRemote/NucleicRemote/Models/RemoteStore.swift +++ b/NucleicRemote/NucleicRemote/Models/RemoteStore.swift @@ -647,6 +647,48 @@ final class RemoteStore: ObservableObject { return bySeq.values.sorted { $0.seq < $1.seq } } + // MARK: - Offline cache (SessionCache) + + /// Debounced write of the live session list to disk, so a read-only history survives a + /// disconnect / relaunch. + private func persistSummaries(_ list: [WireSessionSummary]) { + summaryPersistTask?.cancel() + summaryPersistTask = Task { [list] in + try? await Task.sleep(nanoseconds: 2_000_000_000) + guard !Task.isCancelled else { return } + await SessionCache.saveSummaries(list) + } + } + + /// Seed the open transcript from disk so a cached session's history shows instantly — even fully + /// offline. A live snapshot/backfill merges on top by seq (dedup), so this never double-counts. + private func loadCachedTranscript(_ sessionID: SessionID) { + Task { [weak self] in + let cached = await SessionCache.loadEvents(sessionID) + guard let self, self.openSessionID == sessionID, !cached.isEmpty else { return } + self.openEvents = Self.mergedEvents(cached, self.openEvents) + } + } + + /// Debounced write of the open transcript, called after each batch of events lands. + private func persistOpenTranscript() { + guard !demoMode, let id = openSessionID, !openEvents.isEmpty else { return } + let events = openEvents + transcriptPersistTask?.cancel() + transcriptPersistTask = Task { [events, id] in + try? await Task.sleep(nanoseconds: 1_500_000_000) + guard !Task.isCancelled else { return } + await SessionCache.saveEvents(events, for: id) + } + } + + /// Flush the open transcript to disk immediately (on close), cancelling any pending debounce. + private func flushOpenTranscript(_ id: SessionID, _ events: [AgentEvent]) { + transcriptPersistTask?.cancel() + guard !demoMode, !events.isEmpty else { return } + Task { await SessionCache.saveEvents(events, for: id) } + } + /// The flat sessions list: every connected Mac's sessions, deduped by id (ids are globally /// unique). Views handle sorting/grouping. private func aggregatedSessions() -> [WireSessionSummary] {