Surface phone/watch schema mismatch on the watch; log dropped pulls

The decode-failure freeze (BULLETPROOFING.md L3) is production-reachable
after all - a phone app can update days before the watch app
auto-updates, and every push in between fails to decode, silently
freezing the watch at its last good sync. The bridge now tracks
schemaMismatch (set on a failed decode, cleared on the next good apply)
and ActiveWorkoutGateView shows an "Update both apps to resume sync"
banner while it's set.

Also give requestSync an error handler that logs the dropped pull (L2;
deliberately no retry - the activation/reachability edges re-pull), and
sync DEVICE-COMMUNICATION.md to the post-bulletproofing reality (T1 as
optimization, session recovery, degraded pushes, surfaced mismatch).

Claude-Session: https://claude.ai/code/session_01PVNBVKp5bcq52X722uMjwT
This commit is contained in:
2026-07-09 23:12:39 -04:00
parent 62a2882f0e
commit ccd5194c6c
5 changed files with 41 additions and 9 deletions
@@ -66,6 +66,20 @@ struct ActiveWorkoutGateView: View {
WorkoutLogListView(workout: workout)
}
}
// The phone's pushes aren't decoding (its app updated first; this one hasn't
// yet) everything shown is frozen at the last good sync. Say so rather than
// silently presenting stale workouts until the watch app catches up.
.safeAreaInset(edge: .bottom) {
if bridge.schemaMismatch {
Label("Update both apps to resume sync", systemImage: "exclamationmark.arrow.triangle.2.circlepath")
.font(.caption2)
.foregroundStyle(.orange)
.multilineTextAlignment(.center)
.frame(maxWidth: .infinity)
.padding(.vertical, 2)
.background(.ultraThinMaterial)
}
}
}
.task {
// Nothing to run yet pull fresh state in case the phone just started one.