Skip to content

Can't stop a workout after app restart — only option is delete #147

Description

@abdulsaheel

Symptom

Started a workout in the app. After the app got killed/restarted mid-workout, there was no way to stop/finish it from the UI — the only reachable action on it was delete.

Also reported independently in #102's feedback dump: "deleting the manual workout does not leave the app UI in the same state as if I didn't do it. It shows run live (but I already finished and deleted it, so something is bad here)" — same root, different angle (a stale live-looking state surviving past its actual lifetime).

Root cause

AppState.activeWorkout (the in-memory live-session state that drives the mini-player banner and the live session screen's "hold to finish" control) is purely in-memory and was never rehydrated from the persisted sessions row on cold start.

startWorkout() writes a sessions row with status: 'live' immediately (app_state.dart, putSession), and only stopWorkout() (or the live-activity/GPS teardown paths) ever flips it to status: 'done'. If the app is killed before stopWorkout() runs, the row is permanently stuck at status: 'live' and activeWorkout is lost — there was no code anywhere that restored it on the next launch.

Downstream:

  • The Workouts list explicitly skips the swipe-to-delete Dismissible wrapper for status == 'live' rows (workouts_screen.dart).
  • WorkoutDetailScreen's only action, for any status, was an unconditional trash button — no "finish"/"end" affordance existed there at all.

So once the in-memory state was gone, delete really was the only reachable action — the workout (and whatever was legitimately recorded) could only be discarded, never properly closed out.

Fix

  • LocalDb.liveSessions() (new) — queries any session row still status='live'.
  • AppState._reconcileOrphanedLiveWorkout() (new, called once from _init()):
    • Recent (≤ 6h old): rehydrates activeWorkout from the row so the normal mini-player banner / live session screen / "hold to finish" flow becomes reachable again. Live per-second tallies (calories/strain/zone-minutes) can't be reconstructed from a single DB row, so they resume accruing from zero going forward rather than being fabricated.
    • Stale (> 6h, or a malformed row): silently finalized to status: 'done' instead of resurfacing a days-old "still live" banner — duration_min/calories are left unset rather than guessed.

Fixed in the same pass as #129/#130/#123.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions