From 9c3d146b89ee56aa6c7bfdc91d2eb52bf24facb1 Mon Sep 17 00:00:00 2001 From: rzen Date: Sat, 20 Jun 2026 16:03:30 -0400 Subject: [PATCH] TEMP: on-screen diagnostic overlay for the watch hang Records recent ExerciseProgressView lifecycle/mutation events (init count, page changes, bridge updates, phase starts) and shows them as a green overlay so they can be read off a frozen screen on-device. To be reverted once the hang is diagnosed. --- .../Views/ExerciseProgressView.swift | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/Workouts Watch App/Views/ExerciseProgressView.swift b/Workouts Watch App/Views/ExerciseProgressView.swift index f14e301..637c0d2 100644 --- a/Workouts Watch App/Views/ExerciseProgressView.swift +++ b/Workouts Watch App/Views/ExerciseProgressView.swift @@ -8,6 +8,18 @@ import SwiftUI import WatchKit +// DIAGNOSTIC (temporary): records recent lifecycle/mutation events so the on-screen +// overlay can show what ran right before a freeze. Remove once the hang is solved. +enum PVDiag { + nonisolated(unsafe) static var initCount = 0 + nonisolated(unsafe) static var lines: [String] = [] + static func add(_ s: String) { + print("⌚️PV \(s)") + lines.append(s) + if lines.count > 7 { lines.removeFirst(lines.count - 7) } + } +} + /// Runs a single exercise as a horizontally-paged flow, mirroring the iPhone's: /// /// [Ready] → Work₁ → Rest₁ → Work₂ → … → Workₙ → Finish @@ -71,6 +83,8 @@ struct ExerciseProgressView: View { let completed = min(max(0, log?.currentStateIndex ?? 0), sets - 1) let resume = ready ? 0 : base + completed * 2 _currentPage = State(initialValue: debugInitialPage ?? resume) + PVDiag.initCount += 1 + PVDiag.add("init: ready=\(ready) page=\(debugInitialPage ?? resume) sets=\(sets) duration=\(LoadType(rawValue: log?.loadType ?? -1) == .duration)") } private var log: WorkoutLogDocument? { @@ -152,6 +166,20 @@ struct ExerciseProgressView: View { .padding(.bottom, 2) } } + // DIAGNOSTIC overlay (temporary): shows recent events; readable off a frozen screen. + .overlay(alignment: .top) { + VStack(alignment: .leading, spacing: 0) { + Text("init#\(PVDiag.initCount)").bold() + ForEach(Array(PVDiag.lines.enumerated()), id: \.offset) { _, line in + Text(line) + } + } + .font(.system(size: 8, design: .monospaced)) + .foregroundStyle(.green) + .frame(maxWidth: .infinity, alignment: .leading) + .background(.black.opacity(0.65)) + .allowsHitTesting(false) + } .toolbar { ToolbarItem(placement: .cancellationAction) { Button { @@ -166,6 +194,7 @@ struct ExerciseProgressView: View { Button("Continue", role: .cancel) { } } .onChange(of: currentPage) { _, newPage in + PVDiag.add("currentPage -> \(newPage)") // Swiping all the way back to the Ready page wipes the run; any other page // records forward progress. if showsReady && newPage == 0 { @@ -264,6 +293,7 @@ struct ExerciseProgressView: View { /// Flip a not-yet-started exercise to in-progress the moment the user taps Start. private func beginExercise() { + PVDiag.add("beginExercise -> onChange (bridge.update)") guard let i = doc.logs.firstIndex(where: { $0.id == logID }) else { return } guard doc.logs[i].status == WorkoutStatus.notStarted.rawValue else { return } doc.logs[i].status = WorkoutStatus.inProgress.rawValue @@ -307,6 +337,7 @@ struct ExerciseProgressView: View { guard let i = doc.logs.firstIndex(where: { $0.id == logID }) else { return } guard reached > doc.logs[i].currentStateIndex else { return } + PVDiag.add("recordProgress reached=\(reached) -> onChange (bridge.update)") doc.logs[i].currentStateIndex = reached doc.logs[i].status = WorkoutStatus.inProgress.rawValue doc.logs[i].completed = false @@ -324,6 +355,7 @@ struct ExerciseProgressView: View { guard log.currentStateIndex != 0 || log.status != WorkoutStatus.notStarted.rawValue || log.completed else { return } + PVDiag.add("resetExercise -> onChange (bridge.update)") doc.logs[i].currentStateIndex = 0 doc.logs[i].status = WorkoutStatus.notStarted.rawValue doc.logs[i].completed = false @@ -548,6 +580,7 @@ private struct WorkPhaseView: View { } private func restart() { + PVDiag.add("work.restart set=\(setNumber) (beep)") startDate = Date() WorkoutHaptic.start.play() } @@ -591,6 +624,7 @@ private struct CountdownPhaseView: View { } private func start() { + PVDiag.add("countdown.start '\(header)' seconds=\(seconds) (beep)") startDate = Date() endDate = startDate.addingTimeInterval(Double(max(1, seconds))) lastPingSecond = Int.max @@ -606,6 +640,7 @@ private struct CountdownPhaseView: View { if remaining <= 0 { // Time's up — final cue and slide on. If the wrist was down the timer may have // stalled; this then fires on the first tick once the app gets runtime again. + PVDiag.add("countdown.finished '\(header)' -> advance") didFinish = true WorkoutHaptic.stop.play() onFinished()