Slowly orbit the camera around prop-free form guides

Bodyweight motions (no equipment layer, no hide list) now turn the
camera a full revolution every 24 seconds while the motion loops, so
the exercise reads from every side; machine exercises keep their
authored view, since scene equipment is a view-locked billboard and a
hide list describes a single viewpoint.

Claude-Session: https://claude.ai/code/session_01LEoff8bXGBS83tK1c55Mf7
This commit is contained in:
2026-07-06 20:43:14 -04:00
parent 3c7a790e9d
commit fd3bcb513f
5 changed files with 52 additions and 6 deletions
@@ -1,4 +1,5 @@
import Foundation
import os
import SwiftData
import WatchConnectivity
@@ -10,6 +11,8 @@ import WatchConnectivity
/// SyncEngine write path (file observer cache push back).
@MainActor
final class PhoneConnectivityBridge: NSObject {
private nonisolated static let log = Logger(subsystem: "dev.rzen.indie.Workouts", category: "phone-bridge")
private let container: ModelContainer
private let syncEngine: SyncEngine
private let liveRunState: LiveRunState
@@ -57,7 +60,10 @@ final class PhoneConnectivityBridge: NSObject {
/// Sends the current splits + most-recent workouts to the watch.
func pushAll() {
guard let session, session.activationState == .activated, session.isPaired,
session.isWatchAppInstalled else { return }
session.isWatchAppInstalled else {
Self.log.info("pushAll skipped: activation=\(self.session?.activationState.rawValue ?? -1) paired=\(self.session?.isPaired ?? false) installed=\(self.session?.isWatchAppInstalled ?? false)")
return
}
let splits = (try? context.fetch(FetchDescriptor<Split>(sortBy: [SortDescriptor(\.order)]))) ?? []
@@ -97,7 +103,16 @@ final class PhoneConnectivityBridge: NSObject {
editingWorkoutID: editingWorkoutID,
editingSplitID: editingSplitID
)
try? session.updateApplicationContext(payload)
do {
try session.updateApplicationContext(payload)
let bytes = ((payload[WCPayload.splitsKey] as? Data)?.count ?? 0)
+ ((payload[WCPayload.workoutsKey] as? Data)?.count ?? 0)
Self.log.info("pushAll: \(splits.count) splits, \(workouts.count) workouts (\(bytes) bytes)")
} catch {
// Payload-too-large / not-activated etc. must be visible, or the watch
// just silently never hears about this state.
Self.log.error("updateApplicationContext failed: \(error, privacy: .public)")
}
}
/// Mark (or clear, with `nil`) the workout currently open in a phone exercise editor.