Found by CodeRabbit during #262 review (flagged as outside that PR's diff — filing separately so it isn't lost). Not a regression from #262; pre-existing behavior.
The gap
Three paths mark a session row done without ever reaching the Health store:
LocalRepositoryImpl.endWorkout — writes status: 'done' but never calls HealthExporter.exportWorkoutId / exportWorkout. Health export only picks sessions up inside exportAll's per-day_result scan.
- Stale-session reconciliation (
AppState._reconcileOrphanedLiveWorkout) — silently finalizes stale live rows (deliberately: no resurfacing a days-old banner), but those rows carry end_ts: null, so even if export scanned them there is no end time to export. They are permanently un-exportable as things stand.
- Workout-only imports — an import that brings sessions but no/unchanged
day_result rows can bypass exportAll's day scan entirely, so imported completed sessions never export.
Why it matters
AGENTS.md §4.7's exact failure class: capability wired into one call path but not all N. A user who finishes a workout offline (app killed mid-session → reconciled on next launch) or imports history gets silent gaps in Apple Health / Health Connect while the app shows the session as saved.
Suggested shape
endWorkout and reconciliation set a real end_ts (reconciliation may need to derive one — e.g. last sample ts or reconcile time, stated honestly) and then route through the existing HealthExporter.exportWorkoutId(...) after the DB write lands.
- Workout-only import completion triggers an explicit session export pass (reuse existing exporter APIs; skip incomplete rows).
- Keep the current single-flight/backoff machinery — this is about adding the missing call sites, not new plumbing.
Verification ideas
- Test: end a workout with health configured → assert one export call with correct start/end.
- Test: reconcile a stale live row → exported once, not re-exported on later passes.
Found by CodeRabbit during #262 review (flagged as outside that PR's diff — filing separately so it isn't lost). Not a regression from #262; pre-existing behavior.
The gap
Three paths mark a session row
donewithout ever reaching the Health store:LocalRepositoryImpl.endWorkout— writesstatus: 'done'but never callsHealthExporter.exportWorkoutId/exportWorkout. Health export only picks sessions up insideexportAll's per-day_resultscan.AppState._reconcileOrphanedLiveWorkout) — silently finalizes stale live rows (deliberately: no resurfacing a days-old banner), but those rows carryend_ts: null, so even if export scanned them there is no end time to export. They are permanently un-exportable as things stand.day_resultrows can bypassexportAll's day scan entirely, so imported completed sessions never export.Why it matters
AGENTS.md §4.7's exact failure class: capability wired into one call path but not all N. A user who finishes a workout offline (app killed mid-session → reconciled on next launch) or imports history gets silent gaps in Apple Health / Health Connect while the app shows the session as saved.
Suggested shape
endWorkoutand reconciliation set a realend_ts(reconciliation may need to derive one — e.g. last sample ts or reconcile time, stated honestly) and then route through the existingHealthExporter.exportWorkoutId(...)after the DB write lands.Verification ideas