pacelore went from a rough Cloudflare Workers prototype to something people actually use. Six months in, it's worth writing down what shipped, what hurt, and where the project is going.

What shipped

The list is longer than expected. In rough chronological order:

  • Strava backfill. OAuth import that pulls your full activity history through the Strava API, respects the 100/15-min and 1000/day rate limits, and is safe to re-run on partial imports. Large histories drain overnight via a queue cron.
  • PMC dashboard. The Performance Management Chart — ATL, CTL, TSB — computed daily from TSS values across power, HR, and pace. The chart that TrainingPeaks charges $19/month for.
  • Segment matching. Dynamic Time Warping against a library of user-created segments. Finds your effort on a segment even when your GPS trace doesn't perfectly overlap the reference line.
  • Clubs. Public and private clubs with role-based access. Public clubs are readable by anyone; private clubs require membership. Feed aggregation across members.
  • Workout library. 60 structured sessions with FIT export for Garmin devices and .zwo export for Zwift. Compliance scoring after the workout completes.
  • Compliance tracking. Compares planned workout targets against actual execution — duration, power zones, HR zones — and produces a 0–100 compliance score.
  • Public API and MCP server. REST endpoints for activities, metrics, and the PMC. An MCP server so the data is accessible from Claude and other AI tools.
  • Dark mode. The --pace-* token system made this straightforward. One CSS variable swap at the root.
  • Notifications. In-app notifications for club invites, activity comments, and segment PRs. Popup with delete, right-aligned in the topbar.

What was harder than expected

Three things stood out as genuinely harder than estimated:

Garmin API partner approval

Garmin's Health API requires partner approval. The application process involves a business justification review, a legal agreement, and a manual approval step that takes weeks. For a pre-alpha solo project, this meant the direct Garmin Connect sync is still waiting. In the meantime, users import via FIT file upload or Strava OAuth.

The approval is in progress. When it clears, live Garmin Connect sync will be the first thing to ship.

GPX distance derivation

GPX files contain latitude, longitude, elevation, and timestamp. They do not contain speed or distance natively. Computing distance from lat/lng pairs using the Haversine formula seems straightforward until you encounter GPS noise — a stationary device jittering ±3 meters accumulates several hundred meters of phantom distance over an hour. The fix is a low-pass filter on position before distance integration, plus a sanity check against the elapsed time and expected average speed.

Activity deduplication across three sources

A Zwift ride arrives as a Garmin FIT file (via Garmin Connect), a Zwift FIT file (via Zwift's own export), and a Strava TCX (via Strava API). All three represent the same 90-minute ride. The external_id fields don't match across platforms. The sport field is cycling in one, virtual_ride in another, and other in the third. The dedup strategy ended up being two passes: exact external ID match first, then a ±5-minute time window with same-sport matching. The cleanup pass removed 71 duplicate activities on the first real test account.

Infrastructure cost reality

The $0.012/athlete/month estimate held up. At around 200 active users during the pre-alpha period, the total Cloudflare bill stayed under $3/month. The Workers free tier absorbs most request volume; D1 and R2 are the variable costs that actually scale with usage. Full breakdown in the cost breakdown post.

What's next

Three things on the immediate roadmap:

  • Garmin Connect live sync. Waiting on partner approval. When it clears, activities will land in pacelore within minutes of upload to Garmin Connect.
  • Coach features. Athlete roster management, plan assignment, compliance review across athletes. The data model already supports multiple athletes; the UI doesn't yet.
  • iOS app concept. A read-only native view of the dashboard and PMC. Not a full-featured app — just enough to check training load without opening a browser.

The project is source-available under PolyForm Noncommercial. Anyone can self-host. The hosted version at pacelore.com is free, will stay free, and the infrastructure math means there's no pressure to monetize.