diff --git a/NucleicRemote/NucleicRemote/LiveActivityManager.swift b/NucleicRemote/NucleicRemote/LiveActivityManager.swift index 4052afa..00592cc 100644 --- a/NucleicRemote/NucleicRemote/LiveActivityManager.swift +++ b/NucleicRemote/NucleicRemote/LiveActivityManager.swift @@ -68,23 +68,52 @@ final class LiveActivityManager { linesRemoved: removed, lines: Array(lines)) - if let activity { - Task { await activity.update(ActivityContent(state: state, staleDate: nil)) } - } else { - // Recover an Activity that survived an app relaunch before starting a new one. + push(state, hostName: hostName) + } + + /// Apply `state` to the Activity — deduped against the last push and coalesced through one + /// serial task so the newest state always wins. + private func push(_ state: NucleicSessionAttributes.ContentState, hostName: String) { + guard state != lastState else { return } + lastState = state + + guard let activity else { + // Recover an Activity that survived an app relaunch before starting a new one; the + // recovered one still needs the fresh state, so fall through to the update pipeline. if let existing = Activity.activities.first { activity = existing - Task { await existing.update(ActivityContent(state: state, staleDate: nil)) } - return + enqueue(state) + } else { + activity = try? Activity.request( + attributes: NucleicSessionAttributes(hostName: hostName), + content: ActivityContent(state: state, staleDate: nil)) } - activity = try? Activity.request( - attributes: NucleicSessionAttributes(hostName: hostName), - content: ActivityContent(state: state, staleDate: nil)) + return + } + _ = activity + enqueue(state) + } + + /// Hand the newest state to the serial drainer (starting it if idle). + private func enqueue(_ state: NucleicSessionAttributes.ContentState) { + pendingState = state + guard updateTask == nil else { return } // the running drainer will pick this up + updateTask = Task { @MainActor [weak self] in + guard let self else { return } + while let next = self.pendingState { + self.pendingState = nil + await self.activity?.update(ActivityContent(state: next, staleDate: nil)) + } + self.updateTask = nil } } /// End the Activity (all idle, or unpaired). func end() { + updateTask?.cancel() + updateTask = nil + pendingState = nil + lastState = nil guard let activity else { return } self.activity = nil Task {