From 7c542c236a958c514c5f44ad1b8cbe44c1f58d64 Mon Sep 17 00:00:00 2001 From: Andrew Blakeslee Moore Date: Sun, 5 Jul 2026 00:51:24 -0700 Subject: [PATCH] Live Activity Content Density Nucleic-Session: B3643133-163C-4C5D-BE20-1A6B7FB83DA9 Co-authored-by: Nucleic --- .../NucleicRemote/Models/RemoteStore.swift | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/NucleicRemote/NucleicRemote/Models/RemoteStore.swift b/NucleicRemote/NucleicRemote/Models/RemoteStore.swift index 7d43df5..ba673f6 100644 --- a/NucleicRemote/NucleicRemote/Models/RemoteStore.swift +++ b/NucleicRemote/NucleicRemote/Models/RemoteStore.swift @@ -834,6 +834,49 @@ final class RemoteStore: ObservableObject { activeConnection?.send(msg) } + // MARK: - Live Activity push registration (UX_IOS ยง5.3) + + /// The current Live Activity's APNS update token + id, shipped to every capable Mac so it can + /// refresh the lock-screen glance over APNs while the phone is backgrounded and its socket is + /// suspended. Sent to *all* connected hosts (not just the active one) โ€” while the phone is + /// away, whichever Mac has work to report should be able to push. + private var liveActivityReg: (token: String, activityID: String)? + /// Host ids that already have the current token (re-sent to a host that (re)connects, and + /// re-sent to everyone when the token rotates). + private var liveActivitySentTo: Set = [] + + private func setupLiveActivityBridge() { + LiveActivityManager.shared.onPushToken = { [weak self] token, activityID in + guard let self, self.liveActivityReg?.token != token else { return } + self.liveActivityReg = (token, activityID) + self.liveActivitySentTo.removeAll() // a fresh token must reach every host again + self.syncLiveActivityRegistration() + } + LiveActivityManager.shared.onActivityEnded = { [weak self] activityID in + guard let self else { return } + self.liveActivityReg = nil + self.liveActivitySentTo.removeAll() + for conn in self.connections.values where conn.capabilities.canPushLiveActivity { + conn.send(.endLiveActivity(activityID)) + } + } + } + + /// Send the current Live Activity token to every live, capable host that hasn't got it yet. + /// Called when the token changes and on every connection update, so a host that just connected + /// โ€” or reconnected while the phone was away โ€” learns the token it needs to push. + private func syncLiveActivityRegistration() { + guard let reg = liveActivityReg else { return } + // A host that dropped should re-register when it returns. + liveActivitySentTo = liveActivitySentTo.filter { connections[$0]?.connectivity.isLive == true } + for (id, conn) in connections { + guard conn.connectivity.isLive, conn.capabilities.canPushLiveActivity, + !liveActivitySentTo.contains(id) else { continue } + conn.send(.registerLiveActivity(reg.token, reg.activityID)) + liveActivitySentTo.insert(id) + } + } + // MARK: - Demo simulator (offline, interactive) // // In demo mode there's no host, so writes can't go over the wire. Instead they mutate the