Merge develop -> main: full session (KC-51 through KC-71) #28

Merged
kutesir merged 64 commits from develop into main 2026-07-12 22:57:14 +00:00
Showing only changes of commit cc0dc02be0 - Show all commits

View File

@@ -1451,3 +1451,85 @@ toggle, a Snooze submenu (15 min / 1 hr / tomorrow), then the shared
Events (no `taskID`) get no menu.
Files: `CalendarView.swift`.
---
## KC-51 — Health & Workout Analytics system (12-month, immutable reports) — PLAN
**Status:** Planning (blocked on 2 decisions + build-verification environment)
**Reported by:** User (/goal)
**Area:** Analytics / Persistence / HealthKit / Notifications
### Reusable existing functionality (survey)
- `WorkoutViewModel`: `history: [WorkoutDayLog]`, `workoutDates`, `schedule`,
`missedDates`, `compensations`; `StatPeriod`/`WorkoutStat`/`StatBucket`
aggregation already exists (`stat(period:offset:)`, `statBuckets`).
- `WorkoutDayLog``LoggedExercise``LoggedSet(weight, reps, done)` with
computed `volume` (Σ weight×reps for done sets), `totalSets`, `doneSets`.
- `HealthKitManager`: steps, active calories, resting HR, workoutsThisWeek,
`fetchWorkoutDates(from:to:)`, `saveWorkout`, `sumQuery`/`latestQuery`.
- `NotificationManager`: categories/actions, foreground `reschedule(...)`, and
**`schedulePeriodMilestones()`** (week/month/year-close nudges) — extend this.
- Lifecycle reconcile pattern already used: `checkPendingMissed` /
`checkPendingHealthConfirm` on `onAppear` + `scenePhase == .active`.
- Persistence: `UserDefaults` mirrored to iCloud KVS (`CloudSyncManager`).
### Persistence evaluation (SwiftData / Core Data / current)
- **Current (JSON blobs in UserDefaults + iCloud KVS):** iCloud KVS has a hard
**1 MB total** ceiling (see KC-39 context). 12 months of daily snapshots +
HealthKit reference cache **will exceed it** → silent sync failure. Not viable
as the analytics store.
- **SwiftData:** cleanest modelling/migration, but requires **iOS 17+**. App
deploymentTarget is **iOS 16.0** → SwiftData is UNAVAILABLE unless the min
target is raised (drops iOS 16 devices — a product call).
- **Core Data:** works at iOS 16, unbounded local storage, supports lightweight
migration; more boilerplate. Local-only (analytics need not sync via KVS;
reports are device-derived and reconstructable).
### Proposed architecture (pending the decision below)
- **Stores:** `WorkoutRecordStore` (app-generated, mirrors `WorkoutDayLog`) +
`HealthReferenceStore` (HealthKit cache; HealthKit = source of truth) +
`ReportStore` (immutable daily snapshots, weekly, monthly). Keep app records
and HealthKit reference separate; analysis reads across both.
- **Immutability:** reports store a frozen snapshot at generation; later edits to
source data never mutate a generated report. Retention: prune raw beyond 12
months; keep report summaries.
- **Analysis engine (`AnalyticsEngine`)**: pure, no SwiftUI/persistence deps,
deterministic. Formulas documented: volume = Σ(weight×reps) of completed sets;
%Δ = (curbase)/|base| guarded for base≈0/new/rest; consistency = sessions ÷
scheduled over window; trend = classify(slope, threshold) → improve / decline /
plateau / insufficient-data. Handles missing/partial HK, deloads, outliers
(winsorize/IQR), unit compatibility, divide-by-zero. Non-medical language +
disclaimer; never diagnoses.
- **Comparisons:** daily vs previous comparable day + same weekday 1 week;
weekly vs previous completed week; monthly vs previous month.
- **Reconciliation:** idempotent generators keyed by period id; on launch/active,
backfill any missing completed week/month; never duplicate reports or
notifications. Do not depend on exact BG execution.
- **Notifications:** extend `schedulePeriodMilestones()`; one permission-aware,
user-scheduled local notification per weekly/monthly report; deep-link via
`userInfo` → route to the specific report (new deep-link cases).
- **UI (`Analytics` area):** Overview · Daily comparison · Weekly reports ·
Monthly reports · 12-month trends · Per-exercise history. Full loading / empty /
denied-permission / partial-data / error states; Dynamic Type, VoiceOver,
light/dark, compact/regular; Wenza design tokens; charts only where they aid
understanding; non-color status indicators.
- **Tests:** date boundaries (daily/weekly/monthly), calendar/locale/firstWeekday/
timezone/DST, volume/%/consistency/trend math, missing/partial HK, migration/
dedup/reconcile/retention, exercise identity + unit compat, report+notification
idempotency, deep-link routing.
### BLOCKERS (must resolve before build-verified completion)
1. **Build/test verification is impossible in this environment.** The iOS 26.5
simulator runtime was removed this session and CLI builds are broken, so
completion conditions #10 (tests pass) and #11 (Xcode build succeeds) CANNOT
be verified here. Per "never claim verification not performed", any code
produced now is inspection-only until a runtime is restored / built on device.
2. **Persistence + min-iOS decision (irreversible):** SwiftData needs iOS 17
(app is iOS 16). Migrating live workout history to a new store is a one-way
data migration. Needs the founder's call before implementation.
### Next action
Awaiting decision on persistence backend / deployment target, then implement the
pure `AnalyticsEngine` + models + tests first (most inspection-verifiable), UI
last. Device/build QA tracked here.