v0.20.2 - GPX/TCX pauses, elevation totals and FIT timezones
Backend:
- Fixed recording pauses being counted in GPX and TCX imports. A gap between trackpoints longer than 15 seconds (or three times the track's median sampling interval, so battery-saving recordings are not mistaken for pauses) now splits the track: it adds nothing to timer/moving time, pace or the distance accumulated across it, matching FIT imports. Elapsed time is unchanged (#571, #792).
- Fixed per-lap elevation gain and loss not adding up to the activity total. Elevation is now computed from the same once-smoothed stream that is stored, and each lap uses its slice of it instead of re-smoothing, which clipped gain at every lap boundary. FIT laps whose file omits ascent/descent now get computed values too, and device-reported values (including a genuine 0 m) are never overridden (#791).
- Fixed missing or invalid altitude data being reported as 0 m. A flat activity now shows 0 m gain/loss while an activity with no usable elevation shows nothing, and TCX points without altitude are skipped (#791).
- Fixed FIT imports using the wrong timezone when the file has no GPS fix, such as indoor workouts. The offset is now derived from
activity.local_timestampminusactivity.timestamp, so it works for GPS-less files; it outranks the device-settings offset, and a zero offset (timezone unknown) still yields to a non-zero device setting. Offsets outside the real UTC−12:00 to UTC+14:00 range are ignored (#702, #796). - Fixed FIT sessions with
workout_rpeset to 0 being imported with an invalid RPE; 0 is now treated as unset (#821). - Fixed Strava bulk imports dropping activities stored as
.fit.gzinside the export ZIP. Gzip entries are now accepted instead of being rejected as nested archives, so a Strava export imports completely (#852). - Fixed notifications returning
created_atas a date only (YYYY-MM-DD), which broke relative times in the UI. The API now returns a full UTC ISO 8601 timestamp (#692, #794). - Fixed completing a fasting session returning
500(#824). - A request waiting on an exhausted database pool now fails after 30 seconds instead of 180, so a wedged pool surfaces as an error instead of a long hang. New
GET /api/v1/healthrunsSELECT 1and returns503when the database cannot be reached, unlike/about, which touches no dependency and reports a wedged instance as healthy (#857). - A request without the
X-Client-Typeheader now returns400naming the header, instead of the401previously used for both a missing header and bad credentials (#857).
Frontend:
- The activity summary now shows moving (timer) time as its "Time" stat, matching the activity list and the source app, and falls back to elapsed time when no timer time was recorded. When stops make elapsed time longer, it appears as a separate "Elapsed time" stat — on the pace/speed charts for distance activities, and as its own tile for non-distance sessions. Chart time axes now use the same duration (#788).
Docs:
- Added platform requirements to the bare-metal guide: Python 3.13, glibc 2.27 or newer (musl 1.2 on Alpine).
- Updated the database pool wait in the scaling guide and the module-structure notes to 30 seconds.
- Updated the mobile app privacy policy with remote crash reporting and data retention details.
Maintenance:
- Bumped the app version to 0.20.2.
- Dependencies bump.
- Added the new "Elapsed time" label to all 29 translated locales.
New contributors:
- @SAY-5 in #821
- @ArnyminerZ in #824