import Foundation import Observation import SwiftData /// Composition root for the iOS app. Owns the SwiftData cache container and the /// iCloud sync engine, and drives the one-shot launch sequence. Injected into the /// view tree via `.environment(...)`. @Observable @MainActor final class AppServices { let container: ModelContainer let syncEngine: SyncEngine let watchBridge: PhoneConnectivityBridge let workoutLauncher = WorkoutLauncher() let workoutHealthWriter: WorkoutHealthWriter /// Ephemeral live-run state fed by the watch, observed by the mirror UI. Not persisted. let liveRunState: LiveRunState private var bootstrapTask: Task? init() { let container = WorkoutsModelContainer.make() self.container = container self.syncEngine = SyncEngine(container: container) let liveRunState = LiveRunState() self.liveRunState = liveRunState self.watchBridge = PhoneConnectivityBridge(container: container, syncEngine: syncEngine, liveRunState: liveRunState) let writer = WorkoutHealthWriter(container: container, syncEngine: syncEngine) self.workoutHealthWriter = writer syncEngine.onWorkoutCompleted = { [weak writer] doc in writer?.workoutCompleted(doc) } syncEngine.onWatchMetricsArrived = { [weak writer] id in writer?.watchMetricsArrived(workoutID: id) } #if DEBUG if ScreenshotSeed.isActive { ScreenshotSeed.populate(container.mainContext) } #endif } /// Launch step: resolve iCloud and reconcile the cache. Idempotent — repeated /// callers await the same one-shot task. func bootstrap() async { if let bootstrapTask { await bootstrapTask.value; return } let task = Task { @MainActor [weak self] in guard let self else { return } await self.syncEngine.connect() self.watchBridge.activate() // Past the iCloud gate and cache is reconciled: request Health access and // back-fill any recently-completed workout that never reached Health. self.workoutHealthWriter.authorizeIfNeeded() self.workoutHealthWriter.sweepRecentlyCompleted() } bootstrapTask = task await task.value } }