From 80291ccf6e44c93a8f644d09f154b473d3a0bf4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ullrich=20Sch=C3=A4fer?= Date: Sun, 5 Apr 2026 19:59:54 +0200 Subject: [PATCH 1/2] Pass workout stats (distance, duration, startedAt) through on import ActivityInput now accepts optional distance, duration, and startedAt. GPX-derived stats take precedence when available, but provider stats are used as fallback for workouts without GPS data. The import form and Import All now pass these fields from the Wahoo workout data so activities show correct metadata even without a FIT file. Co-Authored-By: Claude Opus 4.6 (1M context) --- apps/journal/app/lib/activities.server.ts | 14 ++++-- .../app/routes/sync.import.$provider.tsx | 46 ++++++++++--------- 2 files changed, 33 insertions(+), 27 deletions(-) diff --git a/apps/journal/app/lib/activities.server.ts b/apps/journal/app/lib/activities.server.ts index ca7523c..7c32f38 100644 --- a/apps/journal/app/lib/activities.server.ts +++ b/apps/journal/app/lib/activities.server.ts @@ -10,25 +10,28 @@ export interface ActivityInput { description?: string; gpx?: string; routeId?: string; + distance?: number | null; + duration?: number | null; + startedAt?: Date | null; } export async function createActivity(ownerId: string, input: ActivityInput) { const db = getDb(); const id = randomUUID(); - let distance: number | null = null; + let distance: number | null = input.distance ?? null; let elevationGain: number | null = null; let elevationLoss: number | null = null; - let startedAt: Date | null = null; + let startedAt: Date | null = input.startedAt ?? null; + let duration: number | null = input.duration ?? null; if (input.gpx) { try { const gpxData = await parseGpxAsync(input.gpx); - distance = gpxData.distance || null; + distance = gpxData.distance || distance; elevationGain = gpxData.elevation.gain; elevationLoss = gpxData.elevation.loss; - // Try to extract start time from first track point - if (gpxData.tracks[0]?.[0]?.time) { + if (!startedAt && gpxData.tracks[0]?.[0]?.time) { startedAt = new Date(gpxData.tracks[0][0].time); } } catch { @@ -44,6 +47,7 @@ export async function createActivity(ownerId: string, input: ActivityInput) { description: input.description ?? "", gpx: input.gpx, distance, + duration, elevationGain, elevationLoss, startedAt, diff --git a/apps/journal/app/routes/sync.import.$provider.tsx b/apps/journal/app/routes/sync.import.$provider.tsx index 472b294..a59f2c7 100644 --- a/apps/journal/app/routes/sync.import.$provider.tsx +++ b/apps/journal/app/routes/sync.import.$provider.tsx @@ -65,6 +65,9 @@ export async function action({ params, request }: Route.ActionArgs) { const workoutId = formData.get("workoutId") as string; const workoutName = formData.get("workoutName") as string; const fileUrl = formData.get("fileUrl") as string; + const startedAt = formData.get("startedAt") as string; + const distance = formData.get("distance") as string; + const duration = formData.get("duration") as string; // Refresh token if needed let tokens = { @@ -77,18 +80,9 @@ export async function action({ params, request }: Route.ActionArgs) { await updateTokens(connection.id, tokens); } - const workout = { - id: workoutId, - name: workoutName, - type: "", - startedAt: "", - duration: null, - distance: null, - fileUrl: fileUrl || undefined, - }; - let gpx: string | null = null; - if (workout.fileUrl) { + if (fileUrl) { + const workout = { id: workoutId, name: workoutName, type: "", startedAt: "", duration: null, distance: null, fileUrl }; const fileBuffer = await provider.downloadFile(tokens, workout); gpx = await provider.convertToGpx(fileBuffer); } @@ -96,6 +90,9 @@ export async function action({ params, request }: Route.ActionArgs) { const activityId = await createActivity(user.id, { name: workoutName || `${provider.name} workout`, gpx: gpx ?? undefined, + distance: distance ? parseFloat(distance) : null, + duration: duration ? parseInt(duration) : null, + startedAt: startedAt ? new Date(startedAt) : null, }); await recordImport(user.id, provider.id, workoutId, activityId); @@ -151,6 +148,9 @@ function WorkoutRow({ + + +