Skip to Content
Living documentation — last reviewed 2026-05-28
FeaturesExercisesExercises

Exercises

What this is

The exercises domain owns the movement library that workouts pull from. It is a two-tier system: a global canonical library of ~600 movements (rows with organization_id IS NULL) that platform admins seed and enrich, plus per-org customs (organization_id = <org>) and overrides (a JSONB diff a coach applies on top of a canonical row to localize its cues, video, name, etc., without forking). A hybrid lexical + semantic search service (/exercises/search) fuses three signals — full-text, pg_trgm similarity, vector cosine on a 1024-dim Voyage embedding — using Reciprocal Rank Fusion. Platform admins drive seeding and alias enrichment via BullMQ jobs.

Who uses it

PersonaRole
Platform adminSeeds canonical movements from R2 JSON; enqueues Hebrew/Russian alias enrichment via Anthropic Haiku.
Owner / CoachBuilds the org’s view: PATCH overrides on canonical rows, create org-customs, delete customs. Consumes the library inside the workout builder.
MemberReads the merged effective view through workouts they’re assigned (exercise name, cues, video).
Spotter agentSearches via /exercises/search to resolve a workout’s movements before persisting.

Persona impact

  • Coach sees one merged list: canonical rows pre-loaded with their cues/videos/translations, plus their org’s customs at the top.
  • Owner can localize a canonical movement (Hebrew name, swap video, tweak primary muscles) without forking the row — improvements to the canonical library still flow through for any field the coach hasn’t overridden.
  • Spotter agent can semantically resolve “barbell hip thrust” against the library even when the gym uses “glute bridge with bar” as the canonical name.

Capabilities

  1. List org-only exercises (GET /exercises, legacy).
  2. List the merged effective library (GET /exercises/library) — paginated canonical + custom + override-merged, with filters: q, category, movementPattern, equipment[], discipline[], difficulty, source (all/canonical/custom/customized).
  3. Get a single exercise (GET /exercises/:id) — accepts canonical or org-owned ids.
  4. Get a single effective exercise (GET /exercises/library/:id) — merged shape.
  5. Create org-custom (POST /exercises), update (PATCH /exercises/:id), soft-delete (DELETE /exercises/:id).
  6. Upsert org-custom by case-insensitive name (POST /exercises/upsert).
  7. Upsert per-org override on a canonical row (PUT /exercises/:id/override) and reset (DELETE /exercises/:id/override).
  8. Hybrid search (GET /exercises/search) — lexical/semantic/hybrid mode with RRF.
  9. Platform-admin: enqueue canonical seed jobs (POST /admin/exercises/canonical/seed) and alias enrichment (POST /admin/exercises/canonical/enrich); inspect jobs (GET /admin/exercises/canonical/jobs).

Relationship to other features

  • workoutsworkout_movements.exerciseId must resolve to canonical (organization_id IS NULL) or this org’s customs. Validated on both create and setSections.
  • workout-resultsworkout_set_results.exerciseId and personal_records.exerciseId reference exercises. Per-movement PR detection runs against the exerciseId.
  • Spotter agent/exercises/search is the primary movement-resolution endpoint. Search results carry a source flag (canonical / org / customized) so the agent can prefer org rows.

Current status

Shipped. M0 (canonical library) is live with ~600 movements seeded from a curated JSON in R2. Slug-based dedup, HNSW vector index, pg_trgm name index, and tsvector full-text index are in place. Override system shipped. Video-moderation columns (video_status, video_candidates, vote counts) exist on the schema but the full feedback loop is “M0+ deferred” per the in-code comment (libs/db/src/lib/schema/workouts.ts:72).

Known gaps / open Linear issues

  • Video feedback loop — schema present (video_status CHECK accepts 'auto'/'verified'/'demoted'/'manual', vote columns, lock-by-user column) but read/write endpoints not exposed yet. TODO: verify Linear ticket.
  • Exercise org fork — schema supports forkedFromId for full forking, but the current implementation prefers the override pattern. forkedFromId is read in search results but no public write endpoint creates a fork.
  • Aliases enrichment — Haiku enrichment runs in canonical-enrich.service.ts for Hebrew + Russian; canonical seeds happen via canonical-seed.processor.ts. Jobs require platform-admin role.
  • Search relevance tuning ongoing — apps/api/src/exercises/exercise-search.int.spec.ts covers RRF fusion; relevance-evaluation work likely tracked elsewhere.