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

Goals — Behavior

State machine

create ──> active ──┬── progress crosses target ──> achieved (achieved_at set) ├── user edits target / direction (still active) ├── cancelled (manual) └── soft-delete via DELETE → deleted_at set (UI calls this "archive")

Invariants

InvariantEnforcement
Exactly one of metricType or exerciseId is setDB CHECK goals_type_target_chk
Increase goal: startValue < targetValueService create
Decrease goal: startValue > targetValueService create
startValue == targetValue is invalidService create
Exercise referenced must be canonical or this org’sfindFirst with OR clause on organizationId IS NULL or = orgId
Members can only create / read / update their own goalsfindMyGoals, update, remove resolve via authenticated membership

Golden paths

Member creates a weight-loss goal

  1. Member opens goals screen. Picks “Lose weight to 72kg”.
  2. UI computes direction='decrease' automatically (helper defaultDirectionFor).
  3. POST /goals/me with { type: 'body_metric', metricType: 'weight', targetValue: '72', unit: 'kg', direction: 'decrease' }.
  4. Service: validates target, resolves startValue from latest body_metric (e.g. 78.5kg), inserts row with direction='decrease', start=78.5, target=72.
  5. Progress percentage computed via computeProgress: (start - current) / (start - target) * 100 capped at 100.

Member hits a back squat PR target

  1. Member set a goal: 1RM back squat to 150kg (exercise_pr).
  2. After completing a workout with a 152kg back squat, the personal_records table gets a new row.
  3. Next time goals are read, enrichGoal re-computes progress; 152 ≥ 150 → status flips to achieved, achievedAt = now.

Member archives a stale goal

  1. DELETE /goals/:id → sets deleted_at. Listing excludes.

Edge cases

ScenarioBehavior
Member sets a goal with no current body_metricstartValueNumeric resolves to null (no baseline available); progress falls back to current / target ratio
Member changes the target downward to make the goal already achievedStatus flips to achieved on next enrich
Member edits direction post-creationAllowed; old start/target combination may invalidate — service re-validates
Exercise PR for an org-custom exerciseAllowed; resolution uses personalRecords for that exercise
Member belongs to two orgs and creates “lose 5kg” in bothTwo distinct rows (organization_id differs)
Coach views a member’s goals via member detailAllowed; staff role check

Side effects

ActionWritesReads
Creategoals insert (resolves startValue from body_metrics / personal_records)one of the latter two
Updategoals updatesame
List— (enrich computes progress on the fly)body_metrics / personal_records for current value
Achievement detectiongoals update (status, achievedAt)same
Deletegoals update (deleted_at)

No events emitted today.

Permissions

ActionAuth
Member create / list / update / delete ownSelf-scoped via requireMembership
Staff view member’s goalsStaff role + same org