The HKWorkoutSession could only ever be born via the phone's one-shot startWatchApp handoff (BULLETPROOFING.md H1-H3): a dropped handoff, a watch crash/reboot, or a run engaged manually on the wrist left the whole workout sessionless - no heart rate, no Health save, app suspending wrist-down - and "End Current & Start New" swallowed the handoff against the old session's idempotency guard. Now the coordinator self-starts a session on any reconcile that finds an active run with none running (SessionEndPlanner.shouldStart / runToStart, activity type from the run's split), recover() re-adopts a crash-orphaned session at launch, and a system-ended session salvages its Health save instead of dropping it. A .finish decided for a session younger than 30s demotes to .discard so the stale-context races can't save junk workouts attributed to the wrong run; parallel completions now pick the survivor deterministically. Claude-Session: https://claude.ai/code/session_01PVNBVKp5bcq52X722uMjwT
37 lines
1.3 KiB
Swift
37 lines
1.3 KiB
Swift
//
|
|
// WatchAppDelegate.swift
|
|
// Workouts Watch App
|
|
//
|
|
// Copyright 2025 Rouslan Zenetl. All Rights Reserved.
|
|
//
|
|
|
|
import SwiftUI
|
|
import WatchKit
|
|
import HealthKit
|
|
|
|
/// Bridges watchOS lifecycle into the workout session. When the phone starts a workout
|
|
/// it calls `startWatchApp(toHandle:)`; watchOS launches (or foregrounds) this app and
|
|
/// delivers the configuration to `handle(_:)`, where we start a session to claim
|
|
/// foreground runtime. The session manager is shared with the view tree via the
|
|
/// environment (see `WorkoutsWatchApp`).
|
|
@MainActor
|
|
final class WatchAppDelegate: NSObject, WKApplicationDelegate {
|
|
let sessionManager = WorkoutSessionManager()
|
|
|
|
func applicationDidFinishLaunching() {
|
|
#if DEBUG
|
|
// The screenshot harness renders fixed screens — don't pop the Health auth
|
|
// dialog over them (it would also leak into App Store captures).
|
|
if ScreenshotSeed.isActive { return }
|
|
#endif
|
|
sessionManager.requestAuthorization()
|
|
// Re-adopt a session that outlived a crash or reboot — watchOS relaunches us
|
|
// expecting this call; without it the session is orphaned and its workout lost.
|
|
sessionManager.recover()
|
|
}
|
|
|
|
func handle(_ workoutConfiguration: HKWorkoutConfiguration) {
|
|
sessionManager.start(with: workoutConfiguration)
|
|
}
|
|
}
|