Skip to Content
Living documentation — last reviewed 2026-05-28
FeaturesWorkoutsWorkouts — code map

Workouts — code map

API (NestJS)

Base path: /organizations/:orgId

RouteFileMethod
POST /workoutsapps/api/src/workouts/workouts.controller.tsWorkoutsService.create
GET /workoutssamefindAll (filters: programId, q)
GET /workouts/:idsamefindById
PATCH /workouts/:idsameupdate (accepts ?assignmentId for lazy-fork redirect)
DELETE /workouts/:idsameremove (soft delete; rejects snapshots)
PUT /workouts/:id/sectionssamesetSections (accepts ?assignmentId)
PATCH /workouts/:id/movements/:movementId/prescriptionsameupdateMovementPrescription (accepts ?assignmentId)
GET /workouts/:id/movements/:movementId/commentssamelistMovementComments
POST /workouts/:id/movements/:movementId/commentssamecreateMovementComment

Module: apps/api/src/workouts/workouts.module.ts registers the BullMQ workouts-enrichment queue, plus dependencies on MembershipsModule, UsersModule, UploadsModule, R2Module, PushNotificationsModule, and WorkoutAssignmentsModule (for forkSnapshotIfNeeded). It provides and exports WorkoutsService and SectionFidelityService.

DTOs

  • apps/api/src/workouts/dto/create-workout.dto.ts — CreateWorkoutDto
  • apps/api/src/workouts/dto/update-workout.dto.ts — UpdateWorkoutDto
  • apps/api/src/workouts/dto/set-sections.dto.ts — SetSectionsDto, SectionDto (incl. part, validated by sectionPartSchema in the service), MovementDto
  • apps/api/src/workouts/dto/update-movement-prescription.dto.ts — UpdateMovementPrescriptionDto (field-for-field whitelist of PrescriptionSchema, incl. calories/bySex, percent_reference + reference, height, levels, group_load)
  • apps/api/src/workouts/dto/create-exercise-comment.dto.ts — CreateExerciseCommentDto

Key service methods (apps/api/src/workouts/workouts.service.ts)

  • resolveEditTarget(workoutId, assignmentId?) — delegates to WorkoutAssignmentsService.forkSnapshotIfNeeded; returns the snapshot id when assignmentId is supplied.
  • requireWorkoutBuilder(orgId) — tier gate for structured-mode writes.
  • mapMovementToSnapshot / resolveSnapshotMovement — locate the snapshot’s mirror of a library movement after fork.
  • validateStructuredSections — every section write: sectionInputSchema, then the part invariant (findSectionPartIssues → 400 invalid_section_parts), the group-load rule (findGroupLoadIssues → 400 invalid_prescription), shape config and progression bounds.
  • insertSections — the one section/movement insert (create and replaceSections): normalized sourceMarks, validated scores, validated part.
  • toDetailResponse — coach GET /workouts/:id section mapper (incl. part); member reads use toWorkoutSectionResponse in apps/api/src/common/workout-section.ts.
  • deepCopyWorkout — lives in WorkoutAssignmentsService.deepCopyWorkout (apps/api/src/workout-assignments/workout-assignments.service.ts). Copies sections (incl. source_text/source_marks/scores/part), movements, and comments. Program-release staleness compares the same columns in ProgramSessionsService.contentOf.
  • WorkoutsService.computeDisplayName (static) — title or first line of description, ≤60 chars.

Section fidelity (apps/api/src/workouts/section-fidelity.service.ts)

  • SectionFidelityService.fill(orgId, sections, { scoring }) — loads every name of the sections’ exercises (name, aliases, the org’s override name/aliases) and runs fillSectionFidelity.
  • fillSectionFidelity (pure) — fills omitted scores (deriveSectionScoreSlots, apps/api/src/ai/parse/derive-score-slots.ts), omitted sourceMarks (anchorMovementMarks) and omitted workout scoring (deriveScoring); returns FidelityWarning[] (missing_prescription, percent_base_mismatch, text_only_section, invented_score_slot). Called by the MCP upsert and the parse tool.

Web (Next.js)

RouteFile path
Library listapps/web/src/app/[lang]/(protected)/dashboard/workouts/page.tsx
Unified freeform + translation creatorapps/web/src/app/[lang]/(protected)/dashboard/workouts/new/page.tsx — freeform by default; structured translation via ?mode=translate or ?jobId=
Structured builder (new)apps/web/src/app/[lang]/(protected)/dashboard/workouts/new/builder/page.tsx
Workout detailapps/web/src/app/[lang]/(protected)/dashboard/workouts/[id]/page.tsx
Editapps/web/src/app/[lang]/(protected)/dashboard/workouts/[id]/edit/page.tsx
Member whiteboardapps/web/src/app/[lang]/(protected)/(member)/whiteboard/page.tsx

Key components

  • apps/web/src/components/overview/workouts/workout-list-view.tsx — coach library list
  • apps/web/src/components/overview/workouts/workout-card.tsx — list item
  • apps/web/src/components/overview/workouts/workout-builder.tsx — top-level builder shell
  • apps/web/src/components/overview/workouts/workout-builder/ — builder internals:
    • use-builder-state.ts — central state machine (draft sections, movements, undo/redo); updatePart(sectionKey, change) applies a part edit across every block of the part (applyPartChange), and a section reorder re-normalizes parts
    • use-builder-save.ts — POST/PUT orchestration
    • section-payload.ts, prescription-codec.ts, superset.ts — re-export shims; the implementation lives in libs/shared/src/lib/workout-builder/ because the API’s parse eval and unit specs run the same serializer, and an app may not import another app’s source
    • shape.ts — section shape (linear/amrap/emom/…) registry helpers
    • volume.ts — total-volume computation for the right-rail summary
    • keys.ts — stable React keys across reorders; nextPartKey() for a new part
    • components/section-editor.tsx, part-fieldset.tsx (a block’s part: start a new part here / same part as the previous block / leave; part title, class-clock window, note, optional — every edit goes through updatePart), prescription-panel.tsx (calories as a rep kind with an optional ♀/♂ split; percent-of-reference loads, height, levels and group loads pass through unedited), builder-right-rail.tsx, builder-header.tsx, builder-action-bar.tsx, ribbon-action.tsx, shape-renderers.tsx (EMOM interval window max), sortable-section.tsx, section-type-combobox.tsx, num-input.tsx, movement-demo-dialog.tsx, member-preview-panel.tsx (the live “what members see” board in the right rail, structured mode only)
  • apps/web/src/components/overview/workouts/workout-builder/components/builder-text-editor.tsx — the freeform (text-only) editor: title, dir="auto" text, scoring, time cap, and the optional “Convert to structured workout” card. Rendered by the builder’s freeform mode and by the schedule sheets’ quick-create (apps/web/src/components/overview/schedule/quick-create-workout-dialog.tsx, a height-bounded scrolling dialog whose untitled placeholder is the name the API saves: dailyWorkoutTitle(session day, class type))
  • apps/web/src/components/overview/workouts/paste-workout/ — AI paste-to-structure flow; documented in workout-parse
  • apps/web/src/components/overview/workouts/workout-execution-view.tsx — read-only display for non-edit contexts; renders the workout through VerbatimWorkout
  • apps/web/src/components/overview/workouts/workout-mode.ts — effectiveWorkoutMode + formatWorkoutSectionsText (the flat-text workout peek; a part opens with its own heading block — title, clock, optional, note)
  • apps/web/src/components/overview/workouts/exercise-autocomplete.tsx, exercise-library-browser.tsx, exercise-video-dialog.tsx — exercise pickers
  • apps/web/src/components/member/workouts/workout-detail-drawer.tsx — member’s read view of the snapshot (Plan tab: the day brief — workout.description — above the parts)
  • apps/web/src/components/shared/verbatim/ — the coach-facing preview of the member app’s whiteboard, used by the workout page, the builder’s right rail and the paste review: verbatim-workout.tsx (the coach’s text line for line, movement marks → inline-demo-card.tsx, score lines in the org accent), from-builder-sections.ts (builder SectionState[] → the same input the stored workout gives it). Mirrors the app’s src/components/workout/verbatim-sections.tsx; the member web views keep shared/prescription/whiteboard-section.tsx
  • apps/web/src/components/shared/workout-parts/ — section-parts.tsx (SectionParts: groups a workout’s blocks by groupSectionsIntoParts and puts each part under part-header.tsx’s PartHeader — title, class clock “0:00–11:00”, optional badge, part note; blocks outside a part render with no wrapper). Every section renderer goes through it: the member drawer, workout-execution-view.tsx, verbatim-workout.tsx (so the paste preview and the builder’s member preview too), shared/workout-section.tsx (feed / session drawer / program page / program-session sheet card), the course player and the paste preview’s whiteboard list
  • apps/web/src/components/shared/prescription/superset-groups.ts — groupBySuperset for the whiteboard, prescribed and card renderers; a group whose movement carries group_load shows that load (and its progression) on the superset header instead of the movement row

DB (Drizzle, PostgreSQL)

All schema in libs/db/src/lib/schema/workouts.ts:

TablePurpose
workoutsLibrary and snapshot rows. is_snapshot discriminates.
workout_sectionsOrdered sections per workout; carries shape + config JSONB for AMRAP/EMOM/etc., the verbatim source_text/source_marks, the scores slots members log for that part, and the part JSONB grouping consecutive blocks into the coach’s day parts.
workout_movementsOrdered movements per section; carries prescription JSONB + label + superset_group.

Comments: exercise_comments in libs/db/src/lib/schema/comments.ts. Attachments: attachments in libs/db/src/lib/schema/attachments.ts (polymorphic ownerType='exercise_comment').

Shared

FileExports
libs/shared/src/lib/schemas/workout.schema.tsworkoutResponseSchema, workoutSummaryResponseSchema, workoutSectionSchema, workoutMovementSchema, setSectionsInputSchema, sectionScoreSlotSchema/sectionScoreSlotsSchema/sectionScoreScoringValues (per-section score slots), scoring/section-type/mode enum values
libs/shared/src/lib/schemas/prescription.tsPrescriptionSchema + sub-schemas (reps, load)
libs/shared/src/lib/schemas/section-shape.tsSection container shape registry (linear / amrap / emom / for_time / tabata / rep_scheme / rounds / intervals) — TODO: verify exact filename casing
libs/shared/src/lib/source-marks.tsVerbatim whiteboard: sourceMarkSchema/SourceMark, detectScoreMarks, reanchorMovementMarks (follow mentions across a coach’s edit), normalizeSourceMarks (the stored invariant), anchorMovementMarks (link unmarked text: each movement claims its earliest free mention of any of its names, case/plural/joiner-insensitive, never on a score line or inside another movement’s longer name), sectionSourceFromDraft (ParseDraft → per-section text + movement mark positions). Dependency-free apart from the zod schema — mobile copies it like whiteboard-row.ts
libs/shared/src/lib/verbatim-lines.tsThe coach’s text as a renderer lays it out: splitVerbatimLines (lines → marked/unmarked runs), leadingHeading (an opening “WARM UP” is the section’s title, not its first line), toParagraphs, isConnectorLine, synthesizeSectionSource/resolveSectionSource (a section with no sourceText rebuilt into the same shape — height rides on the movement line, each scaling level follows as its own line). Mirrored byte-for-byte by the app at src/lib/verbatim-lines.ts
libs/shared/src/lib/whiteboard-row.tsThe member “poster” formatter: formatWhiteboardLeading (reps / calories / time / distance, sex-labelled counts), formatWhiteboardLoad (incl. percent_reference → ”@ 75% of Back Squat 3RM”), formatWhiteboardHeight, formatWhiteboardLevels, formatWhiteboardProgression, formatWhiteboardDetail. Mirrored byte-for-byte by the app at src/lib/whiteboard-row.ts; the coach’s dense line is apps/web/src/components/shared/prescription/format-prescription.ts (formatPrescription, formatPrescriptionLevels, formatSectionHeader incl. the EMOM window “EMOM 3 × 3:00–3:30”)
libs/shared/src/lib/video-embed.tsparseVideoEmbed — a demo link → youtube / vimeo / file, or nothing embeddable. Mirrored by the app (which adds its own WebView document builder)
libs/shared/src/lib/workout-builder/Pure builder state types (SectionState incl. part, MovementState, PrescriptionState, TimerConfig) and conversion: section-payload.ts (buildSectionsPayload, deriveScoring — canonical serialization/scoring shared by builder save, parse commit, and golden eval), prescription-codec.ts (flat PrescriptionState ⇄ canonical Prescription JSONB; height, levels and percent references are carried whole), parts.ts (applyPartChange — start / join previous / leave / edit a part across all its blocks; normalizeSectionParts — restores the contiguous, identical-copies invariant the API enforces), superset.ts (superset grouping + derived labels), draft-to-builder.ts (ParseDraft → SectionState[])
libs/shared/src/lib/constants/platform-tiers.tstierHasFeature, basic_workouts, workout_builder feature constants

Tests

TypeFile
Builder state unitapps/web/src/components/overview/workouts/workout-builder/use-builder-state.int.spec.tsx
Builder save unitapps/web/src/components/overview/workouts/workout-builder/use-builder-save.unit.spec.ts
Prescription codecapps/web/src/components/overview/workouts/workout-builder/prescription-codec.unit.spec.ts
Section shapeapps/web/src/components/overview/workouts/workout-builder/shape.unit.spec.ts
Supersetapps/web/src/components/overview/workouts/workout-builder/superset.unit.spec.ts
Volumeapps/web/src/components/overview/workouts/workout-builder/volume.unit.spec.ts
Builder keysapps/web/src/components/overview/workouts/workout-builder/keys.unit.spec.ts
Builder deriveapps/web/src/components/overview/workouts/workout-builder-derive.unit.spec.ts
Member detail drawerapps/web/src/components/member/workouts/workout-detail-drawer.int.spec.tsx
Builder parts (start / join / leave / edit, invariant property)libs/shared/src/lib/workout-builder/parts.unit.spec.ts
Builder part editing → save payload, EMOM interval windowapps/web/src/components/overview/workouts/workout-builder.int.spec.tsx
Member formatters (calories, height, levels, % of reference)libs/shared/src/lib/whiteboard-row.unit.spec.ts, apps/web/src/components/shared/prescription/format-prescription.unit.spec.ts
Comments panelapps/web/src/components/messages/workout-comments-panel.int.spec.tsx
E2E builderapps/web/e2e/specs/workout-builder.spec.ts
E2E member workoutapps/web/e2e/specs/member-workout.spec.ts
E2E commentsapps/web/e2e/specs/workout-comments.spec.ts
E2E lazy-forkapps/web/e2e/specs/lazy-fork.spec.ts
API section writes, parts, group loads, quick editapps/api/src/workouts/workouts.service.unit.spec.ts
API quick-edit DTO whitelistapps/api/src/workouts/dto/update-movement-prescription.dto.unit.spec.ts
API section fidelity fill + warningsapps/api/src/workouts/section-fidelity.service.unit.spec.ts
API verbatim text + parts through every read, release and fork (Postgres)apps/api/src/workouts/workout-source-text.int.spec.ts
API member section serializerapps/api/src/common/workout-section.unit.spec.ts
API program-release stalenessapps/api/src/workout-assignments/program-sessions.int.spec.ts
Source marks (incl. anchoring)libs/shared/src/lib/source-marks.unit.spec.ts