trails/openspec/changes/wahoo-import/proposal.md
Ullrich Schäfer b6711d23d6
Add Wahoo activity sync with provider-agnostic framework
Provider-agnostic sync framework + Wahoo as first implementation:

Framework (apps/journal/app/lib/sync/):
- SyncProvider interface for OAuth2, webhooks, import, conversion
- Provider registry for settings UI iteration
- Generic sync_connections + sync_imports tables
- Token storage with auto-refresh

Wahoo provider:
- OAuth2 with workouts_read, user_read, offline_data scopes
- Webhook-based auto-import (workout_summary events)
- Manual import page with pagination
- FIT→GPX conversion via fit-file-parser
- Webhook token verification

Routes:
- /api/sync/connect/:provider — OAuth redirect
- /api/sync/callback/:provider — OAuth callback
- /api/sync/disconnect/:provider — remove connection
- /api/sync/webhook/:provider — webhook receiver
- /sync/import/:provider — manual import page

Settings: Connected Services section with per-provider connect/disconnect

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 11:22:02 +01:00

2.1 KiB

Why

Users with Wahoo cycling computers (ELEMNT, KICKR) record activities that are synced to Wahoo's cloud. Currently there's no way to get those activities into trails.cool without manually exporting GPX files. A direct Wahoo integration lets users connect their account once and have activities sync automatically.

This is the first of several planned device integrations (Garmin, Strava, Coros, etc.). The architecture should be provider-agnostic so adding new integrations is straightforward.

What Changes

  • Provider abstraction: A common interface for activity sync providers (OAuth2, webhook handling, activity listing, file download, format conversion). Wahoo is the first implementation.
  • OAuth2 flow: "Connect Wahoo" button on the journal settings page. Redirects to Wahoo's authorization endpoint, handles callback, stores tokens.
  • Webhook sync: Register for Wahoo's workout_summary webhook. When a new workout completes, automatically download the FIT file, convert to GPX, and create an activity.
  • Manual import: Fallback import page for browsing and selectively importing older workouts.
  • FIT to GPX conversion: Wahoo provides FIT format files. Convert to GPX server-side for storage.
  • Token management: Store and refresh OAuth tokens per provider. Handle the 2-hour expiry with automatic refresh.

Capabilities

New Capabilities

  • activity-sync: Provider-agnostic activity sync framework (OAuth2, webhooks, import, format conversion)
  • wahoo-import: Wahoo-specific provider implementation (OAuth2 scopes, FIT files, webhook payload)

Modified Capabilities

  • account-settings: Add "Connected Services" section for managing provider connections
  • journal-auth: Store OAuth tokens for external providers

Impact

  • apps/journal/app/lib/sync/ — new directory for sync framework + provider implementations
  • apps/journal/app/routes/settings.tsx — Connected Services section
  • apps/journal/app/routes/sync.* — OAuth callback, webhook endpoint, import page
  • packages/db/src/schema/journal.tssync_connections and sync_imports tables
  • infrastructure/secrets.app.env — WAHOO_CLIENT_ID, WAHOO_CLIENT_SECRET