Back to Developer Logs

Build Log — March 12, 2026

Build Log — March 12, 2026

What We Shipped

  • Google Calendar sync: skip completed workouts — When Garmin filled in actual duration and HR data after a workout was finished, the calendar sync would re-push the workout event — sending it back to Google Calendar (and Apple Calendar via subscription) as if it were still upcoming. Added guards in both the single-event pushWorkoutToCalendar and the bulk syncWorkoutsToCalendar paths: any workout with completed_at set is now skipped entirely. No more ghost re-writes of sessions that already happened.

  • allDayRespiration zero-batch guard — Garmin occasionally fires allDayRespiration webhook batches where unavailable intervals are all zeros. Previously, the upsert did a full payload replace — so a zero batch arriving after real data would silently wipe out the good numbers. The fix is two-part: filter out zero-value entries before storing, then read-then-merge with the existing row so each successive webhook call accumulates samples rather than clobbering them. This closes the last known edge case from yesterday's allDayRespiration overhaul.

What We Learned

Garmin webhooks are eventual-consistent and occasionally noisy — you can't assume the last event has the best data. Read-then-merge (rather than blind upsert) is the right pattern any time multiple webhook deliveries might cover the same time window with partial data.

The Google Calendar issue is a good reminder that post-workout data enrichment and calendar sync need to be aware of each other. Completed workouts belong in history, not in someone's upcoming events feed.

What's Next

Data layer is in solid shape across Garmin and Google Calendar. Next focus is likely surfacing respiration and HRV data more prominently in the training load dashboard — now that the underlying data is trustworthy, it's worth putting it in front of athletes.