Add the watch coverage policy and snapshot trimming
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
cbec421367
commit
19454379e5
@@ -0,0 +1,67 @@
|
||||
import Foundation
|
||||
|
||||
/// How much of the calendar travels to the watch.
|
||||
///
|
||||
/// Deliberately narrower than `SnapshotCoverage`: the payload crosses
|
||||
/// WatchConnectivity on every publish, and a watch complication never needs six
|
||||
/// weeks of history to answer "what is next".
|
||||
public enum WatchCoverage {
|
||||
/// Days before today. One is enough for "still running" all-day events.
|
||||
public static let daysBehind = 1
|
||||
/// Days after today.
|
||||
public static let daysAhead = 14
|
||||
/// Hard cap on events sent, applied after sorting by start date.
|
||||
public static let maxEvents = 120
|
||||
}
|
||||
|
||||
extension CalendarrSnapshot {
|
||||
/// A narrower copy of this snapshot, suitable for sending to the watch.
|
||||
///
|
||||
/// The coverage window shrinks together with the events, and never widens
|
||||
/// past what the writer already claimed. If `WatchCoverage.maxEvents`
|
||||
/// truncates the list, `coverageEnd` is pulled back to the first event we
|
||||
/// had to drop: everything before that point is provably complete, and
|
||||
/// beyond it the watch must not pretend to know anything.
|
||||
public func trimmedForWatch(now: Date = Date(),
|
||||
calendar: Calendar = Calendar(identifier: .gregorian)) -> CalendarrSnapshot {
|
||||
let dayStart = calendar.startOfDay(for: now)
|
||||
let windowStart = max(coverageStart,
|
||||
calendar.date(byAdding: .day, value: -WatchCoverage.daysBehind, to: dayStart) ?? dayStart)
|
||||
let windowEnd = min(coverageEnd,
|
||||
calendar.date(byAdding: .day, value: WatchCoverage.daysAhead, to: dayStart) ?? dayStart)
|
||||
|
||||
guard windowEnd > windowStart else {
|
||||
return Self.rebuilt(from: self, start: windowStart, end: windowStart, events: [])
|
||||
}
|
||||
|
||||
let inWindow = events
|
||||
.filter { $0.start < windowEnd && $0.end > windowStart }
|
||||
.sorted { $0.start < $1.start }
|
||||
|
||||
guard inWindow.count > WatchCoverage.maxEvents else {
|
||||
return Self.rebuilt(from: self, start: windowStart, end: windowEnd, events: inWindow)
|
||||
}
|
||||
|
||||
let kept = Array(inWindow.prefix(WatchCoverage.maxEvents))
|
||||
// The first dropped event marks the edge of what we can prove. Its start
|
||||
// becomes the new end of coverage; the last kept event is dropped too
|
||||
// when it starts at that same instant, which keeps the invariant
|
||||
// "every event we carry starts before coverageEnd" exactly true.
|
||||
let provenEnd = max(windowStart, min(windowEnd, inWindow[WatchCoverage.maxEvents].start))
|
||||
return Self.rebuilt(from: self, start: windowStart, end: provenEnd,
|
||||
events: kept.filter { $0.start < provenEnd && $0.end > windowStart })
|
||||
}
|
||||
|
||||
private static func rebuilt(from source: CalendarrSnapshot,
|
||||
start: Date, end: Date,
|
||||
events: [SnapshotEvent]) -> CalendarrSnapshot {
|
||||
CalendarrSnapshot(writtenAt: source.writtenAt,
|
||||
coverageStart: start,
|
||||
coverageEnd: end,
|
||||
isLoggedIn: source.isLoggedIn,
|
||||
writerVersion: source.writerVersion,
|
||||
events: events,
|
||||
theme: source.theme,
|
||||
language: source.language)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user