Skip to Content
Living documentation — last reviewed 2026-05-28
FeaturesWorkout ResultsWorkout results

Workout results

What this is

A workout result is a single logged performance of a workout by a single member: the canonical numeric score (seconds for time, kg for weight, count for reps/rounds, rounds + reps for AMRAP encoded as R*1000+r), Rx/scaled flags, optional set-by-set breakdown (weight kg / distance m / duration seconds / RPE / reps), and a back-reference to the snapshot workout the member performed. The domain also owns personal records (PRs) — auto-detected per (user, library workout) and per (user, exercise) for single-movement weight workouts, plus standalone manual PRs.

Who uses it

PersonaRole
MemberLogs results, owns the row, can update or delete.
CoachReads any member’s results for compliance / programming feedback (findMemberResults).
OwnerSame as coach.
PlatformRead-only for analytics rollups.

Persona impact

  • Member gets a permanent history anchored to the exact snapshot they performed. Edits the coach makes to the library Fran after Mia’s completion do not retroactively change Mia’s logged data.
  • Coach sees a leaderboard per workout, sorted with Rx rows first, then by scoreNumeric (lower-is-better for time scoring).
  • Owner sees aggregated PR + result trends per member at /dashboard/members/[id].

Capabilities

  1. Log a resultPOST /workouts/:workoutId/results with optional assignmentId (auto-completes the assignment + forks the snapshot). Optional setResults[] for per-set detail.
  2. Update / delete own resultPATCH / DELETE /workouts/:workoutId/results/:id.
  3. Workout leaderboardGET /workouts/:workoutId/results — all results for the canonical library row (joined via forkedFromId). Rx-first, then score-sorted. Ranks are assigned post-sort.
  4. My latest result on a workoutGET /workouts/:workoutId/results/me/latest (with sets).
  5. My result feedGET /results/me — all my results across all workouts, most recent first.
  6. PRs
    • POST /personal-records/me — manual PR (standalone, by exercise or library workout).
    • GET /personal-records/me — my PRs.
    • GET /members/:membershipId/personal-records — a member’s PRs (any caller).
    • GET /members/:membershipId/results — a member’s full result history (coach role required).
  7. PR auto-detection — every new result triggers checkIsPR(libraryWorkoutId, userId). First-ever result is a PR; subsequent results compare numerically against all prior. Lower-is-better for time scoring; higher-is-better otherwise. Single-exercise weight workouts also write an exercise-level PR (“Back Squat 100kg”).
  8. Canonical numeric storage with display-unit retention — weight_kg, distance_m, duration_seconds are canonical; weight_display_unit, distance_display_unit retain what the athlete entered so the UI can format back to lb/mi without drift.

Relationship to other features

  • workoutssnapshot_workout_id anchors the result; library_workout_id denormalizes forkedFromId ?? id for “everyone’s Fran scores” queries.
  • workout-assignments — result creation calls forkSnapshotIfNeeded(assignmentId) if assignmentId is supplied, and auto-flips the assignment to completed.
  • exercises — per-set rows reference an exercise_id; manual PRs and single-exercise-weight workout PRs also key on exercise_id.
  • community feed — TODO: verify whether workout_logged feed item is emitted on result creation.

Current status

Shipped. Result CRUD, PR auto-detection, manual PRs, leaderboard ordering, and per-set storage are all live. The lazy-fork integration (FIT-152) is in place — every new result forks the assignment’s snapshot if it hasn’t already.

Known gaps / open Linear issues

  • PR detection per-exercise only fires for single-movement weight workouts. Multi-movement strength workouts do not generate per-exercise PRs from result data — only manual PR entry. TODO: verify scope.
  • No “PR achieved” push notification on auto-detection. TODO: verify.
  • Set-result update endpoint not present — PATCH /results/:id updates the header (score, notes, rx, scaled, performedAt) but does not let the member edit individual sets after the fact. TODO: verify.
  • findByUser / findMyResults returns results without joining set details — for “my latest with sets” the member must call /workouts/:workoutId/results/me/latest.