Files
rzen cbdf02bca7 Add the macOS app: a library-management companion (routines, schedules, exercise browser)
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.
2026-07-16 19:55:06 -04:00

56 lines
2.1 KiB
Swift

//
// WorkoutsMacApp.swift
// Workouts Mac
//
// Copyright 2025 Rouslan Zenetl. All Rights Reserved.
//
import SwiftUI
import SwiftData
@main
struct WorkoutsMacApp: App {
@State private var services = MacAppServices()
@Environment(\.scenePhase) private var scenePhase
@State private var lastActivationReconcile: Date?
/// `.active` also fires on every window focus-in (⌘-Tab back), not just launch —
/// the metadata observer covers live changes while running, so the activation
/// reconcile only needs to catch changes synced while the app was completely
/// inactive for a while.
private static let activationReconcileInterval: TimeInterval = 5 * 60
var body: some Scene {
WindowGroup {
MacRootGateView()
.environment(services)
.environment(services.syncEngine)
.modelContainer(services.container)
.task { await services.bootstrap() }
}
.defaultSize(width: 1000, height: 700)
// The macOS iCloud quirk: the ubiquity container can keep syncing while the
// app isn't running, so re-check on every foreground return rather than
// relying on the (iOS-only) metadata observer alone.
.onChange(of: scenePhase) { _, phase in
switch phase {
case .active:
if services.syncEngine.iCloudStatus == .unavailable {
Task { await services.syncEngine.connect() }
} else if services.syncEngine.iCloudStatus == .available {
let now = Date()
if lastActivationReconcile == nil
|| now.timeIntervalSince(lastActivationReconcile!) > Self.activationReconcileInterval {
lastActivationReconcile = now
Task { await services.syncEngine.rebuildCache() }
}
}
case .background, .inactive:
Task { await services.syncEngine.flushPendingWrites() }
@unknown default:
break
}
}
}
}