Live Activity Launch Consistency
Nucleic-Session: 9D2EE9AB-2E44-485B-A196-54D250F9D89B Co-authored-by: Nucleic <[email protected]>
This commit is contained in:
@@ -148,22 +148,15 @@ final class LiveActivityManager {
|
||||
/// totals on every transcript edit but no status) doesn't spend ActivityKit's update budget. The
|
||||
/// fresh churn still rides along on the next status-driven push.
|
||||
private func push(_ state: NucleicSessionAttributes.ContentState, hostName: String) {
|
||||
// Apply immediately when the attention signature shifts (start/finish/approval/input — the
|
||||
// alert-worthy changes). Otherwise the only difference is mid-turn churn (diff totals); let
|
||||
// that refresh on a slow cadence so it stays roughly current without spending ActivityKit's
|
||||
// budget on every delta. Elapsed-time gate, not a sleeping timer — a status change is never
|
||||
// held behind it.
|
||||
let signatureChanged = state.attentionSignature != lastState?.attentionSignature
|
||||
let churnRefreshDue = lastPushAt.map {
|
||||
Date().timeIntervalSince($0) >= Self.churnRefreshInterval
|
||||
} ?? true
|
||||
guard signatureChanged || (state != lastState && churnRefreshDue) else { return }
|
||||
lastState = state
|
||||
lastPushAt = Date()
|
||||
|
||||
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.
|
||||
// No Activity yet — recover one that survived an app relaunch, or start a fresh one. The
|
||||
// dedup gate below only guards *updates* to a live Activity; creation must never sit
|
||||
// behind it. ActivityKit refuses a start unless the app has a foreground/background-
|
||||
// assertion window, and committing the dedup state *before* the request meant a refused
|
||||
// start left `lastState` matching the aggregate — so the next `sync` deduped the glance
|
||||
// away and it never activated until the session's state changed. Record the pushed state
|
||||
// only once an Activity actually exists; a refused start leaves `lastState` untouched so
|
||||
// the next sync simply retries creation.
|
||||
if let existing = Activity<NucleicSessionAttributes>.activities.first {
|
||||
activity = existing
|
||||
observePushToken(existing)
|
||||
@@ -173,19 +166,35 @@ final class LiveActivityManager {
|
||||
// expanding reveals the fresh one, and `end()` on completion would leave it lingering
|
||||
// on the last "needs attention" glance. Keep exactly one.
|
||||
endStrays(keeping: existing.id)
|
||||
lastState = state
|
||||
lastPushAt = Date()
|
||||
enqueue(state)
|
||||
} else {
|
||||
} else if let started = 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),
|
||||
pushType: .token)
|
||||
pushType: .token) {
|
||||
activity = started
|
||||
if let started { observePushToken(started) }
|
||||
observePushToken(started)
|
||||
lastState = state
|
||||
lastPushAt = Date()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// A live Activity exists — spend ActivityKit's update budget only when the glance actually
|
||||
// moved: immediately when the attention signature shifts (start/finish/approval/input — the
|
||||
// alert-worthy changes), otherwise on a slow cadence for mid-turn churn (diff totals ticking
|
||||
// on every transcript delta). Elapsed-time gate, not a sleeping timer — a status change is
|
||||
// never held behind it.
|
||||
let signatureChanged = state.attentionSignature != lastState?.attentionSignature
|
||||
let churnRefreshDue = lastPushAt.map {
|
||||
Date().timeIntervalSince($0) >= Self.churnRefreshInterval
|
||||
} ?? true
|
||||
guard signatureChanged || (state != lastState && churnRefreshDue) else { return }
|
||||
lastState = state
|
||||
lastPushAt = Date()
|
||||
enqueue(state)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user