Tracking tables are client scoped. None of them has a studioId. They all cascade from Client, and any query must first establish that the client belongs to the caller’s studio. Trainee “days” follow the trainee device’s time zone (Client.timezone), not the studio’s. @db.Date columns hold the civil day. Timestamp columns hold instants.

WorkoutLog

Table workout_logs. One recorded workout session. Indexes: clientId, programId, (clientId, performedOn), (clientId, completed, performedOn).

entries

logWorkoutBody.entries accepts an array of free-form records. The app writes this shape from mobile/src/features/workouts/presentation/WorkoutDayScreen.tsx:
Rules worth knowing:
  • A set the trainee never performed keeps its entry and done: false, with reps and weight as null. Targets and placeholders are never written as if performed.
  • A performed set with no weight logs weight: null. That is also how a bodyweight set is logged.
  • rir is a string and is null when the trainee did not touch it.
readWorkoutLog(entries) in apps/core-api/src/modules/trainee/workout-log-view.ts is the tolerant reader. It skips non-objects, coerces numeric strings, counts a set as performed when done is true or, on older logs with no done flag, when it has reps or weight, and computes setCount and volumeKg (weight times reps over performed sets).

CardioLog

Table cardio_logs. A single aerobic session the trainee timed in the app or added by hand. Index: (clientId, performedOn).

DailyMetric

Table daily_metrics. One row per trainee per day. Unique on (clientId, date). Index on clientId.

measurements

Keys accepted by logMeasurementsBody: chest, waist, hips, arm, thigh, neck. Each is a positive number up to 500. At least one is required per write. A form’s body measurement field merges into the same object on submit.

HealthSample

Table health_samples. Raw samples from Apple Health or Health Connect, from the first version of the health sync.
Nothing writes to this table any more. The app now sends one step total per day, which is stored in DailyMetric.steps. The old endpoint POST /v1/trainee/health/samples still answers so older app builds do not fail, but it stores nothing. The only code that touches this table is the trainee account deletion, which clears it.
Unique on (clientId, type, source, externalId). Index on (clientId, type, startedAt). The current sync is covered in Apple Health and Health Connect.

WeightEntry

Table weight_entries. Dedicated weigh-in history, separate from DailyMetric. It allows several entries per day and powers the weight view on the client card. Indexes: clientId, (clientId, recordedAt).

ClientAppDay

Table client_app_days. One row per day the trainee opened the app. Unique on (clientId, date). Client.lastCheckInAt only keeps the latest open, so “opened the app but logged nothing” needs this history.

ClientPhoto

Table client_photos. The trainee’s progress photo gallery. Indexes: clientId, (clientId, takenAt).

MealLog

Table meal_logs. One logged meal. Indexes: clientId, (clientId, consumedAt).

NutritionDayLog

Table nutrition_day_logs. The app’s snapshot of one nutrition day. Unique on (clientId, date).

FavoriteMeal

Table favorite_meals. A meal the trainee saved to log again. Same columns as MealLog minus consumedAt: name (required), source (default MANUAL), note, items (nullable), the four macro totals and photoUrl. Index on clientId.

TechniqueVideo

Table technique_videos. A video the trainee filmed for coach review. Indexes: clientId, status, (clientId, exerciseId), (clientId, createdAt).

TechniqueVideoStatus

Which exercises ask for a video is configured in the training plan. See Training plan JSON.

Derived values that are not stored

  • Personal records, last set history and the workout time estimate are computed from WorkoutLog.entries on read.
  • The movement summary (cardio and steps progress) is computed from CardioLog, DailyMetric.steps and the plan requirement.
  • The water goal comes from the nutrition plan day’s targets.water.