What We Shipped
New Features
- Fitbit integration — body metrics sync — Fitbit users now get body composition data (weight, BMI, body fat) synced alongside activity data.
- Compliance badges on mobile calendar — Workout cards now show green/yellow/red badges indicating how closely you hit your planned workout. Badge only appears when a planned workout exists — Garmin syncs without a plan stay clean.
- Calendar overflow days — Previous and next month's days now show greyed out in the calendar grid, and tapping them navigates to that month.
- Imperial/metric unit toggle — Added to the mobile Settings page. Athletes can now switch units without digging into account settings.
- Calendar drag handle — A drag-to-resize handle on the calendar day panel lets you expand it all the way to full screen, collapsing the calendar grid entirely.
- Sleep time range in recovery tile — The total sleep tile now shows bedtime–wake time (e.g. "10:32 PM – 6:14 AM") alongside sleep hours.
- PWA hard reload — Added a hard reload option in the Settings menu for PWA standalone mode. Final resting place after several layout iterations.
Fixes & Polish
- Recovery tile ordering — Standardized the tile order to RHR → HRV → Sleep → REM+Deep across the Home screen, Calendar, and Metrics page. Was inconsistent everywhere.
- Metrics data source sync — ATL/CTL/TSB and recovery tiles on the Metrics page now use the same data source as the Home screen. No more diverging numbers.
- Mobile header padding — Standardized
pt-3+ 22px title size across all mobile screens. Small but it was visibly inconsistent. - InfoTip rendering — Tooltip now renders as a fixed overlay instead of inline, so it doesn't cramp chart layouts or float over screen center.
- Export sheet scroll — Added
flex-1 min-h-0andmax-heightconstraints so the export sheet scrolls internally and doesn't block main content. - iOS export scroll — Applied
overflow-y: scroll+-webkit-overflow-scrolling: touchfor proper momentum scrolling on iOS. - Unread badge suppression — Coach tab unread badge no longer appears until the user has actually visited the tab. No more phantom notifications on first launch.
- Garmin null laps — Filtered out null-only lap rows so the splits chart doesn't render with empty data on Garmin activities.
- Approximate distance prefix — The
~prefix on estimated distances was being stripped. It's back. - Weekly Volume W3 grayed out — A hardcoded
index === 2check was always graying out the third week. Fixed to use actual data state. - Intervals.icu ID truncation — Applied the same calendar ID truncation fix from the integrations modal to
/settings/integrations. - Mobile calendar week start — Calendar now correctly starts weeks on Monday.
- AI coach layout — Used
position: fixedcontainer to track the visual viewport properly on mobile. - Health dashboard performance — Parallelized dashboard queries with
Promise.alland added prefetching inMobileHomeViewto eliminate the recovery widget waterfall delay.
What We Learned
Data consistency across screens is surprisingly hard to maintain. ATL/CTL, recovery tiles, and weekly volume all had subtle divergences because different components were hitting different data sources or applying different filters. The fix is always the same: one source of truth, shared hook, done.
What's Next
The mobile experience is now in solid shape. Next focus: production error visibility (Sentry) and stress-testing the AI coach across different athlete profiles and training phases.