Root cause: observeActivityState() prematurely set workoutActivity=nil
when the activity went .stale (e.g. app backgrounded >2 minutes). This
prevented endActivity() from calling .end() on the stale activity,
leaving it visible on the Lock Screen and Dynamic Island indefinitely.
Fixes (all in PlayerViewModel.swift):
1. observeActivityState(): Split the monolithic stale/ended/dismissed
handler. .stale now only stops the sync timer but keeps the
workoutActivity reference so endActivity() can still call .end()
to properly dismiss the stale Live Activity.
2. syncActivity() nil guard: Changed from != .active to explicit
== .ended || == .dismissed so stale activities are not prematurely
discarded when tick() re-enters syncActivity().
3. endActivity(): Added stopActivitySyncTimer() defensive call at top
to prevent orphaned timer from racing in and recreating the activity
during .end(). Also relaxed the guard from == .active to
!= .ended && != .dismissed so stale activities can be ended.
4. abandonWorkout(): Explicitly set isRunning=false + isPaused=false
before cleanup to prevent accidental Live Activity recreation.