fix(derive): cap the live-workout derive hold so a forgotten session cannot blank Home - #281
Conversation
…cannot blank Home Starting a workout holds all derivation (DeriveScheduler.setWorkoutActive) on the stated assumption that "a workout is minutes long and its own results are derived at the end anyway". Forget to finish the session and that assumption inverts: capture keeps landing records, every queued derive job stays parked, today's day_result is never built, and Home spends the day on "Nothing recorded for today — Sync the band" while the strap is connected and syncing fine. The sync CTA is a false answer — the sync completes and its derive jobs are parked too. Two changes, one per half of the symptom: - DeriveScheduler gains a workoutHoldCap (6 h, matching AppState._kMaxLiveWorkoutAgeMs, the ceiling past which a live session row from a previous run is already judged "almost certainly not something the user is still in"). Past the cap the session is treated as forgotten and held work drains even though it is still live; ending the workout re-arms the cap for the next session. Inside the cap the hold is unchanged. - Home's bare-day card branches on the live workout: while a session holds derivation it says "A workout is still running" and points at the session bar pinned below, instead of claiming nothing was recorded and offering a sync that cannot help. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015TxisrChNFxtVMXFFNRGtV
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (2)
📒 Files selected for processing (2)
Included review availability: Your plan provides up to 2 included reviews per hour; 1 remains after this review. 📝 WalkthroughWalkthroughChangesWorkout hold handling
Estimated code review effort: 3 (Moderate) | ~20 minutes Merge Risk: ⚪ Minimal · up to The change caps forgotten workout holds and replaces the misleading Home empty state with a live-workout message; no actionable merge-blocking risk remains after normal checks and review. Sequence Diagram(s)sequenceDiagram
participant WorkoutState
participant DeriveScheduler
participant HomeScreen
participant StatusCard
WorkoutState->>DeriveScheduler: setWorkoutActive(true)
DeriveScheduler->>DeriveScheduler: start hold-cap timer
DeriveScheduler->>DeriveScheduler: expire hold and re-arm derivation
WorkoutState->>HomeScreen: expose active workout state
HomeScreen->>StatusCard: render workoutHoldCard()
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Full details: Docstring CoverageExplanation No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 0 files. (2 skipped: 2 unsupported.) ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
The bug
Start a workout and forget to finish it, and Home degrades into the bare-day empty state: no stats, and "Nothing recorded for today — Sync the band" — while the strap is connected and syncing fine. Tapping the sync CTA completes a real sync and changes nothing.
Root cause
startWorkout()callsDeriveScheduler.setWorkoutActive(true), which parks all derive work — deliberately, so a heavy isolate spawn can't land mid-ride against GPS, the live map and the BLE drain. The hold's own justification is "a workout is minutes long and its own results are derived at the end anyway, so deferring costs nothing."That assumption inverts the moment the session is forgotten:
day_resultis never built, so Home'sbarebranch renders the first-run/no-data card,Even a relaunch doesn't clear it:
_reconcileOrphanedLiveWorkoutrehydrates a live session up to 6 h old and re-arms the hold.The fix
Two changes, one per half of the symptom:
DeriveSchedulergains aworkoutHoldCap(default 6 h). Past the cap the session is treated as forgotten and held work drains even though it is still live; jobs arriving after expiry run too, so the pipeline is unwedged for the rest of the session. Ending the workout clears the expiry, so the next session holds again from scratch. Inside the cap nothing changes. Six hours matchesAppState._kMaxLiveWorkoutAgeMs, the existing ceiling past which a live session row from a previous run is already judged "almost certainly not something the user is still in".Home's bare-day card branches on the live workout. While a session holds derivation, the card says "A workout is still running" and points at the session bar pinned directly below it (the door back into the session), instead of claiming nothing was recorded and offering a sync that cannot help. The card clears itself: finishing the workout releases the hold, the derive lands,
insightsRevisionticks, andRevisionReloadre-reads. Read viacontext.select(notwatch) so the 1 Hz live tick doesn't rebuild Home; in goldens (noAppState) it resolves to false, so existing goldens are byte-identical.Tests
workout_reliability_test.dart: the hold is time-capped (a parked job runs past the cap with the workout still live, and later jobs run too), and ending a workout re-arms the cap for the next session. Existing gate tests unchanged and green.ui2_wiring_r2_test.dart: a bare day during a live workout renders the workout card and neither "Nothing recorded for today" nor "Sync the band".flutter analyzeclean; full suite green (2971 passed,--concurrency=1) against the pinned protocol SHA.Summary by CodeRabbit
New Features
Bug Fixes