Workout: restore history/missed/compensations from iCloud (KC-39)
save() pushed all 7 workout keys to iCloud KVS, but init only restored 4 — workout history, missed dates, and rest-day compensations were uploaded yet never restored, so they were lost on reinstall/update. Added the three missing restoreIfMissing calls so restore mirrors save. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1215,3 +1215,33 @@ Added DEBUG-only test hooks (`KISANI_AUTOADD`, `KISANI_VOICE_SIM`) to inject a
|
|||||||
transcript on the simulator; `#if DEBUG`, compiled out of Release.
|
transcript on the simulator; `#if DEBUG`, compiled out of Release.
|
||||||
|
|
||||||
Files: `TodayView.swift` (`NLTaskField.updateUIView`), `ContentView.swift`.
|
Files: `TodayView.swift` (`NLTaskField.updateUIView`), `ContentView.swift`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## KC-39 — Workout state lost on reinstall/update (incomplete iCloud restore)
|
||||||
|
|
||||||
|
**Status:** Fixed (build-verified)
|
||||||
|
**Reported by:** User
|
||||||
|
**Area:** Workout / iCloud sync
|
||||||
|
|
||||||
|
### Description
|
||||||
|
Workout state was lost after app updates / fresh installs.
|
||||||
|
|
||||||
|
### Root cause
|
||||||
|
`save()` pushes 7 workout keys to iCloud KVS (programs, schedule, activePid,
|
||||||
|
completedDates, history, missedDates, compensations), but `WorkoutViewModel.init`
|
||||||
|
only `restoreIfMissing(...)` for the first four. So **history, missed dates, and
|
||||||
|
rest-day compensations** were uploaded to iCloud but never restored on a fresh
|
||||||
|
launch — silently lost on reinstall/update.
|
||||||
|
|
||||||
|
### Fix
|
||||||
|
Added the three missing `restoreIfMissing` calls (`history`, `missedDates`,
|
||||||
|
`compensations`) so restore is symmetric with save. (Per-day `lastActiveDay` is
|
||||||
|
transient set-reset state and intentionally not synced.)
|
||||||
|
|
||||||
|
Files: `ExerciseModels.swift`.
|
||||||
|
|
||||||
|
### Note
|
||||||
|
Cross-device *live* refresh while the app is open isn't wired (`WorkoutViewModel`
|
||||||
|
doesn't observe `kisaniCloudDataRefreshed`) — out of scope; the restore-on-launch
|
||||||
|
path is what preserves state across updates.
|
||||||
|
|||||||
@@ -290,11 +290,15 @@ class WorkoutViewModel: ObservableObject {
|
|||||||
self.kMissedDates = "kisani.workout.missedDates.\(uid)"
|
self.kMissedDates = "kisani.workout.missedDates.\(uid)"
|
||||||
self.kCompensations = "kisani.workout.compensations.\(uid)"
|
self.kCompensations = "kisani.workout.compensations.\(uid)"
|
||||||
|
|
||||||
// Restore all workout keys from iCloud if missing locally (fresh install / new build)
|
// Restore all workout keys from iCloud if missing locally (fresh install / new build).
|
||||||
|
// Must cover EVERY key save() pushes, or that state is lost on reinstall/update.
|
||||||
CloudSyncManager.shared.restoreIfMissing(key: "kisani.workout.programs.\(uid)")
|
CloudSyncManager.shared.restoreIfMissing(key: "kisani.workout.programs.\(uid)")
|
||||||
CloudSyncManager.shared.restoreIfMissing(key: "kisani.workout.schedule.\(uid)")
|
CloudSyncManager.shared.restoreIfMissing(key: "kisani.workout.schedule.\(uid)")
|
||||||
CloudSyncManager.shared.restoreIfMissing(key: "kisani.workout.activePid.\(uid)")
|
CloudSyncManager.shared.restoreIfMissing(key: "kisani.workout.activePid.\(uid)")
|
||||||
CloudSyncManager.shared.restoreIfMissing(key: "kisani.workout.completedDates.\(uid)")
|
CloudSyncManager.shared.restoreIfMissing(key: "kisani.workout.completedDates.\(uid)")
|
||||||
|
CloudSyncManager.shared.restoreIfMissing(key: "kisani.workout.history.\(uid)")
|
||||||
|
CloudSyncManager.shared.restoreIfMissing(key: "kisani.workout.missedDates.\(uid)")
|
||||||
|
CloudSyncManager.shared.restoreIfMissing(key: "kisani.workout.compensations.\(uid)")
|
||||||
|
|
||||||
// ── Attempt to restore persisted state ──
|
// ── Attempt to restore persisted state ──
|
||||||
if let data = UserDefaults.kisani.data(forKey: kPrograms),
|
if let data = UserDefaults.kisani.data(forKey: kPrograms),
|
||||||
|
|||||||
Reference in New Issue
Block a user