nvrsion: Add: Chat: Chat View Refinement to modernize session chat interface by removing vertical space for selectors, Fix: Chat: Remove Face ID Permission Text, Refactor: Chat: Live Activity Content Density to open app directly into waiting sessions when tapped, Refactor: Chat: Live Activity Content Density to refresh live activity info significantly faster, Fix: Chat: Live Activity Content Density to refresh live activity info significantly faster, Fix: Chat: Live Activity Content Density to refresh live activity info significantly faster, Fix: Chat: Network Type Transition Optimization to reduce delay during network type switches, Feature Enhancement Planning: All devices in a mesh group should see and be aware of each other; “pair” reframed into “join”; scanning, Fix: Chat: Session Transcript Sync to show only current frame’s transcript when opening a session on another Mac, Build: Push-update path to stay fresh while locked.

Nucleic-Promote: 1
Co-authored-by: Nucleic <[email protected]>
This commit is contained in:
2026-07-05 01:00:03 -07:00
co-authored by Nucleic
parent 368978bea8
commit 8b37b8576f
7 changed files with 205 additions and 78 deletions
@@ -28,6 +28,14 @@ final class LiveActivityManager {
private var pendingState: NucleicSessionAttributes.ContentState?
private var updateTask: Task<Void, Never>?
/// Set by `RemoteStore` to ship the activity's APNS push token to the paired Macs (and to tell
/// them it ended). The Macs use the token to keep this glance fresh over APNs while the phone
/// is backgrounded and its sync socket is suspended (UX_IOS §5.3).
var onPushToken: ((_ token: String, _ activityID: String) -> Void)?
var onActivityEnded: ((_ activityID: String) -> Void)?
/// Streams the activity's per-activity APNS update token (it can rotate); cancelled on end.
private var tokenObservation: Task<Void, Never>?
/// Reconcile the Activity with the current session set.
func sync(hostName: String, sessions: [WireSessionSummary]) {
guard ActivityAuthorizationInfo().areActivitiesEnabled else { return }
@@ -82,17 +90,34 @@ final class LiveActivityManager {
// recovered one still needs the fresh state, so fall through to the update pipeline.
if let existing = Activity<NucleicSessionAttributes>.activities.first {
activity = existing
observePushToken(existing)
enqueue(state)
} else {
activity = try? Activity.request(
// `pushType: .token` opts the activity into APNs updates — the Macs push new
// content-state to the token so the glance stays fresh while the phone is locked.
let started = try? Activity.request(
attributes: NucleicSessionAttributes(hostName: hostName),
content: ActivityContent(state: state, staleDate: nil))
content: ActivityContent(state: state, staleDate: nil),
pushType: .token)
activity = started
if let started { observePushToken(started) }
}
return
}
enqueue(state)
}
/// Forward the activity's APNS update token (and its rotations) to `RemoteStore`.
private func observePushToken(_ activity: Activity<NucleicSessionAttributes>) {
tokenObservation?.cancel()
tokenObservation = Task { [weak self] in
for await tokenData in activity.pushTokenUpdates {
let hex = tokenData.map { String(format: "%02x", $0) }.joined()
self?.onPushToken?(hex, activity.id)
}
}
}
/// Hand the newest state to the serial drainer (starting it if idle).
private func enqueue(_ state: NucleicSessionAttributes.ContentState) {
pendingState = state
@@ -111,10 +136,13 @@ final class LiveActivityManager {
func end() {
updateTask?.cancel()
updateTask = nil
tokenObservation?.cancel()
tokenObservation = nil
pendingState = nil
lastState = nil
guard let activity else { return }
self.activity = nil
onActivityEnded?(activity.id) // let the Macs stop pushing to this token
Task {
await activity.end(
ActivityContent(state: activity.content.state, staleDate: nil),