Progress becomes phase-aware ({Phase, Done, Total} instead of a flat
{Done, Total}), with FullSync reporting an indeterminate "discovering"
phase during backfill/incremental sync (there's no meaningful total
before those calls have already happened), then FillPendingDetails
reporting a real Done/Total for its activities pass, then its new,
independent workouts pass.
alignWorkoutTargets now takes a lap count instead of a []garmin.Lap
slice, since it never used lap content, only length -- this lets the
new workouts pass call it against laps read back from the DB rather
than needing the original Garmin lap data again.
Splitting the passes also fixes a latent bug: an activity whose
details were fetched successfully but whose workout fetch failed in
that same run previously had no way to ever retry the workout fetch,
since ActivitiesMissingDetails stops returning it once
details_fetched_at/splits_fetched_at are set. ActivitiesMissingWorkout
queries workout_id/workout_raw_json independently, so it keeps
surfacing that activity until its workout is actually fetched.
- Remove duplicated Garmin fields from storage; decode display-only fields
(activity name/type, lap duration/HR, structured workout raw JSON) from
RawJSON at API-response time instead of storing redundant columns
- Add a fully configurable chart color system (pace/HR main-line colors, 4
effort-kind colors, tint/darken/brighten intensity knobs) under Profile >
Chart colors
- Rename training types and fix their display order (Easy, Long, 60'/30'
Threshold, Tempo, Intervals, MAS Test, Race) everywhere they're listed
- Add an Efficiency Factor progression metric; fix Progression chart axes to
use tight non-zero-based domains, m:ss/km pace formatting, and rounded
ticks instead of raw floating-point labels
- Expose the raw get_workout_by_id() payload in the raw-data viewer
alongside activity/lap/detail JSON; enlarge the modal and shrink array
indentation for readability
- Fix "last sync" reporting a meaningless activity count: record one
combined sync run per manual "Sync now" and count genuinely new
activities instead of re-listing whatever Garmin returned for the queried
window
- Let a Review Queue activity be manually cleared back to Unclassified, and
make "Reset all" available even while disconnected from Garmin
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Confirmed via Garmin Connect against a real activity ("Auriol - W3-3-Double
Barrel"): the workout's step count didn't match its 18 recorded laps
because the athlete kept running 6:46 past the prescribed 5-minute
cool-down, logged as an 18th lap the workout never defined. The strict
equality check meant this one extra lap discarded every other lap's real
target too, not just its own.
alignWorkoutTargets now tolerates exactly one extra recorded lap beyond the
step count: the steps that do exist still zip to their laps normally, and
only the trailing extra lap is left without a target. Any larger mismatch
still falls back to nil for every lap, since that can't be trusted at all.
Activities recorded from a structured Garmin workout carry a workoutId;
when its steps line up 1:1 with the recorded laps, the per-step target
pace/HR zone is resolved (via a Karvonen lookup for named zones) and stored
on each lap. The Review Queue plots it against the actual per-lap pace/HR
so the user can eyeball whether a run matches its plan while sorting it.
Race is the 8th fixed workout kind, seeded with a real (not placeholder)
rule since Garmin Connect's eventType.typeKey reports "race" for
manually-tagged race activities. Non-running activity types (padel,
cycling, strength training, ...) are now dropped at sync time instead of
being stored. The Review Queue's type filter is now clickable exclusive
pill buttons instead of a dropdown.