A new 'Workouts Mac' target (same bundle ID as iOS, universal purchase) with a NavigationSplitView shell over the shared data/sync layers: routine management with starter gallery and seed-fork follow, schedule editing (reminders stay iPhone-scheduled), and a browse-only exercise library with the animated figures and reference guides. Multi-writer stance: the Mac writes only routine and schedule documents (whole-document last-writer-wins) and never workout documents, whose per-log merge exists only on the watch ingest path. The Mac reconciles on window activation (throttled) since iCloud syncs while the app is closed, and flushes pending writes on deactivation.
36 lines
1.2 KiB
Swift
36 lines
1.2 KiB
Swift
import Foundation
|
|
import Observation
|
|
import SwiftData
|
|
|
|
/// Composition root for the Mac app. Owns the SwiftData cache container and the
|
|
/// iCloud sync engine only — no watch bridge, HealthKit, ActivityKit, backup, or
|
|
/// reminders/speech. The Mac's MVP is browsing and managing the library
|
|
/// (routines/schedules/exercises); it never runs a workout. Injected into the
|
|
/// view tree via `.environment(...)`.
|
|
@Observable
|
|
@MainActor
|
|
final class MacAppServices {
|
|
let container: ModelContainer
|
|
let syncEngine: SyncEngine
|
|
|
|
private var bootstrapTask: Task<Void, Never>?
|
|
|
|
init() {
|
|
let container = WorkoutsModelContainer.make()
|
|
self.container = container
|
|
self.syncEngine = SyncEngine(container: container)
|
|
}
|
|
|
|
/// 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()
|
|
}
|
|
bootstrapTask = task
|
|
await task.value
|
|
}
|
|
}
|