Guard every persisted read of retained workout models with isLive
A remotely deleted @Model traps on any persisted-property read, including the observed expression of .onChange, which SwiftUI evaluates on every body pass. Guard those expressions, the onAppear takeover read, and the summary sheet's document mapping. Claude-Session: https://claude.ai/code/session_01PKptrgbx74peTwHGRxBojv
This commit is contained in:
@@ -26,19 +26,22 @@ struct WorkoutSummaryView: View {
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
Group {
|
||||
if let workout {
|
||||
// The fromLive guard also covers the query vending a workout whose
|
||||
// cache row was deleted within the same view-graph update (remote
|
||||
// delete / reconcile prune) — reading a dead @Model traps.
|
||||
if let workout, let doc = WorkoutDocument(fromLive: workout) {
|
||||
ScrollView {
|
||||
VStack(spacing: 20) {
|
||||
Image(systemName: "checkmark.seal.fill")
|
||||
.font(.system(size: 48))
|
||||
.foregroundStyle(.green)
|
||||
.padding(.top, 8)
|
||||
Text(workout.routineName ?? Routine.unnamed)
|
||||
Text(doc.routineName ?? Routine.unnamed)
|
||||
.font(.title2.bold())
|
||||
|
||||
WorkoutMetricsView(workout: workout)
|
||||
|
||||
if workout.metrics == nil {
|
||||
if doc.metrics == nil {
|
||||
Label("Saving to Apple Health…", systemImage: "heart.text.square")
|
||||
.font(.footnote)
|
||||
.foregroundStyle(.secondary)
|
||||
@@ -71,15 +74,21 @@ struct WorkoutMetricsView: View {
|
||||
let workout: Workout
|
||||
@AppStorage("weightUnit") private var weightUnit: WeightUnit = .lb
|
||||
|
||||
private var metrics: WorkoutMetrics? { workout.metrics }
|
||||
// Every read goes through this liveness-guarded snapshot: the cache row can be
|
||||
// deleted out from under a retained `Workout` (remote delete, reconcile prune)
|
||||
// while a parent re-evaluates mid-transition, and reading a persisted property
|
||||
// on a dead @Model traps — see `WorkoutDocument.init?(fromLive:)`.
|
||||
private var doc: WorkoutDocument? { WorkoutDocument(fromLive: workout) }
|
||||
|
||||
private var metrics: WorkoutMetrics? { doc?.metrics }
|
||||
|
||||
private var durationSeconds: Int? {
|
||||
guard let end = workout.end else { return nil }
|
||||
return max(0, Int(end.timeIntervalSince(workout.start)))
|
||||
guard let doc, let end = doc.end else { return nil }
|
||||
return max(0, Int(end.timeIntervalSince(doc.start)))
|
||||
}
|
||||
|
||||
private var volume: Double {
|
||||
metrics?.totalVolume ?? WorkoutVolume.total(WorkoutDocument(from: workout).logs)
|
||||
metrics?.totalVolume ?? WorkoutVolume.total(doc?.logs ?? [])
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
|
||||
Reference in New Issue
Block a user