hevy-shared 1.0.1039 → 1.0.1040

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
- import { WeeklyTrainingFrequency, TrainingGoal, TrainingLevel, SimplifiedMuscleGroup, MuscleGroup, LibraryExercise, ExerciseCategory, GranularEquipment, HevyTrainerProgramEquipment, RestTimerLength, CardioPreference, StrictRepRange, ExerciseType } from '.';
1
+ import { WeeklyTrainingFrequency, TrainingGoal, TrainingLevel, SimplifiedMuscleGroup, MuscleGroup, LibraryExercise, ExerciseCategory, GranularEquipment, HevyTrainerProgramEquipment, RestTimerLength, CardioPreference } from '.';
2
2
  export type HevyTrainerExerciseCategory = (typeof hevyTrainerExerciseCategories)[number];
3
- export type TrainerWorkoutTemplateName = (typeof workoutTemplateNames)[number];
3
+ export type HevyTrainerRoutineName = (typeof routineNames)[number];
4
4
  export declare const workoutDurationOptions: readonly [40, 60, 80];
5
5
  export type WorkoutDurationMinutes = (typeof workoutDurationOptions)[number];
6
6
  export declare const trainerGymTypes: readonly ["home_gym", "garage_gym", "commercial_gym", "full_gym"];
@@ -16,12 +16,12 @@ export declare const trainerEquipmentToGranularEquipments: (equipments: HevyTrai
16
16
  export declare const granularEquipmentsToTrainerEquipments: (granularEquipments: GranularEquipment[]) => HevyTrainerProgramEquipment[];
17
17
  export declare const hevyTrainerExerciseCategories: readonly ["compound", "isolation"];
18
18
  export declare const defaultDurationPerFrequency: Record<WeeklyTrainingFrequency, WorkoutDurationMinutes>;
19
- export declare const workoutTemplateNames: readonly ["full_body_1", "full_body_2_a", "full_body_2_b", "full_body_3_a", "full_body_3_b", "full_body_3_c", "upper_1_a", "lower_1_a", "upper_1_b", "lower_1_b", "push_1", "pull_1", "legs_1", "upper_2", "lower_2", "push_2_a", "pull_2_a", "legs_2_a", "push_2_b", "pull_2_b", "legs_2_b"];
19
+ export declare const routineNames: readonly ["full_body_1", "full_body_2_a", "full_body_2_b", "full_body_3_a", "full_body_3_b", "full_body_3_c", "upper_1_a", "lower_1_a", "upper_1_b", "lower_1_b", "push_1", "pull_1", "legs_1", "upper_2", "lower_2", "push_2_a", "pull_2_a", "legs_2_a", "push_2_b", "pull_2_b", "legs_2_b"];
20
20
  export type exerciseId = string;
21
21
  export interface ExerciseSelectionCriteria {
22
22
  exerciseCategory: HevyTrainerExerciseCategory | 'all';
23
23
  equipments: GranularEquipment[];
24
- workoutBarbellExerciseCount: number;
24
+ routineBarbellExerciseCount: number;
25
25
  level: TrainingLevel;
26
26
  goal: TrainingGoal;
27
27
  muscleGroup: MuscleGroup;
@@ -29,7 +29,7 @@ export interface ExerciseSelectionCriteria {
29
29
  }
30
30
  export interface ExerciseSelectionContext {
31
31
  programUsedExerciseIds?: Set<string>;
32
- workoutUsedExerciseIds?: Set<string>;
32
+ routineUsedExerciseIds?: Set<string>;
33
33
  excludedExerciseIds?: Set<string>;
34
34
  }
35
35
  export interface ExerciseSelectionParams<T extends HevyTrainerLibraryExercise> {
@@ -69,7 +69,7 @@ export interface ProgramGenerationParams<T extends HevyTrainerLibraryExercise> {
69
69
  }
70
70
  export interface TemplateExerciseSelectionTraceRecord {
71
71
  traceKind: 'template';
72
- workoutTemplate: TrainerWorkoutTemplateName;
72
+ routine: HevyTrainerRoutineName;
73
73
  prescriptionIndex: number;
74
74
  prescription: ExercisePrescription;
75
75
  resolvedMuscleGroup: MuscleGroup;
@@ -79,7 +79,7 @@ export interface TemplateExerciseSelectionTraceRecord {
79
79
  }
80
80
  export interface CardioExerciseSelectionTraceRecord {
81
81
  traceKind: 'cardio';
82
- workoutTemplate: TrainerWorkoutTemplateName;
82
+ routine: HevyTrainerRoutineName;
83
83
  prescriptionIndex: number;
84
84
  selectedExerciseId?: string;
85
85
  equipments: GranularEquipment[];
@@ -92,19 +92,19 @@ export type TrainerProgramAttemptWithTraces = TrainerProgramAttempt & {
92
92
  };
93
93
  export type FrequencyString = 'one_day' | 'two_days' | 'three_days' | 'four_days' | 'five_days' | 'six_days';
94
94
  export declare const frequencyMap: Record<WeeklyTrainingFrequency, FrequencyString>;
95
- export declare const programSplits: Record<WeeklyTrainingFrequency, TrainerWorkoutTemplateName[]>;
95
+ export declare const programSplits: Record<WeeklyTrainingFrequency, HevyTrainerRoutineName[]>;
96
96
  export type SetsPerGoal = {
97
97
  [key in TrainingGoal]: number;
98
98
  };
99
99
  export type SetsPerFrequency = {
100
100
  [key in FrequencyString]: SetsPerGoal;
101
101
  };
102
- export interface AlgorithmRepRange {
102
+ export interface RepRange {
103
103
  rep_range_start: number;
104
104
  rep_range_end: number;
105
105
  }
106
106
  export type RepRangesPerCategory = {
107
- [key in HevyTrainerExerciseCategory]: AlgorithmRepRange;
107
+ [key in HevyTrainerExerciseCategory]: RepRange;
108
108
  };
109
109
  export type RepRanges = {
110
110
  [key in TrainingGoal]: RepRangesPerCategory;
@@ -135,11 +135,10 @@ export type ExerciseReplacements = {
135
135
  };
136
136
  export interface WorkoutTemplate {
137
137
  exercises: ExercisePrescription[];
138
- /** @deprecated Because this is english-only and not translatable so we are not using it currently */
139
138
  notes?: string;
140
139
  }
141
140
  export type Templates = {
142
- [key in TrainerWorkoutTemplateName]: WorkoutTemplate;
141
+ [key in HevyTrainerRoutineName]: WorkoutTemplate;
143
142
  };
144
143
  export interface BackofficeTrainerPreset {
145
144
  id?: number;
@@ -158,40 +157,39 @@ export interface TrainerAlgorithmSettings {
158
157
  exercise_notes: ExerciseNotes;
159
158
  exercise_replacements: ExerciseReplacements;
160
159
  }
160
+ /** Resistance prescriptions from templates (sets, reps, rest, notes). */
161
161
  export interface TrainerProgramResistanceExercise {
162
162
  kind: 'resistance';
163
- exercise_template: HevyTrainerLibraryExercise;
164
- set_count: number;
165
- warmup_set_count: number;
166
- rep_range: StrictRepRange;
167
- rest_seconds: number;
163
+ exerciseTemplate: HevyTrainerLibraryExercise;
164
+ muscleGroup: MuscleGroup | 'focus_muscle';
165
+ category: HevyTrainerExerciseCategory;
166
+ sets: number;
167
+ warmupSetCount?: number;
168
+ repRangeStart: number;
169
+ repRangeEnd: number;
170
+ restTimerSeconds: number;
171
+ notes?: string;
168
172
  }
173
+ /** Optional cardio attachment — no prescription semantics beyond exercise choice. */
169
174
  export interface TrainerProgramCardioExercise {
170
175
  kind: 'cardio';
171
- exercise_template: HevyTrainerLibraryExercise;
172
- set_count: 1;
173
- duration_seconds: number;
176
+ exerciseTemplate: HevyTrainerLibraryExercise;
177
+ durationSeconds: number;
174
178
  }
175
- export interface TrainerProgramOtherExercise {
176
- kind: 'other';
177
- exercise_template: HevyTrainerLibraryExercise;
178
- set_count: number;
179
- }
180
- export type TrainerProgramExercise = TrainerProgramResistanceExercise | TrainerProgramCardioExercise | TrainerProgramOtherExercise;
179
+ export type TrainerProgramExercise = TrainerProgramResistanceExercise | TrainerProgramCardioExercise;
181
180
  export declare const isTrainerProgramResistanceExercise: (exercise: TrainerProgramExercise) => exercise is TrainerProgramResistanceExercise;
182
181
  export declare const isTrainerProgramCardioExercise: (exercise: TrainerProgramExercise) => exercise is TrainerProgramCardioExercise;
183
- export declare const isTrainerProgramOtherExercise: (exercise: TrainerProgramExercise) => exercise is TrainerProgramOtherExercise;
184
- export declare const isTrainerResistanceExerciseType: (exerciseType: ExerciseType) => exerciseType is "weight_reps" | "reps_only" | "bodyweight_reps" | "bodyweight_assisted_reps";
185
- export declare const isTrainerCardioExerciseType: (exerciseType: ExerciseType) => exerciseType is "duration" | "distance_duration" | "floors_duration" | "steps_duration";
186
- export interface TrainerProgramWorkoutTemplate {
187
- name: TrainerWorkoutTemplateName;
182
+ export interface TrainerProgramRoutine {
183
+ name: HevyTrainerRoutineName;
188
184
  exercises: TrainerProgramExercise[];
185
+ notes?: string;
189
186
  }
190
- export interface GeneratedTrainerProgram {
191
- workoutTemplates: TrainerProgramWorkoutTemplate[];
187
+ export interface TrainerProgram {
188
+ name: WeeklyTrainingFrequency;
189
+ routines: TrainerProgramRoutine[];
192
190
  }
193
191
  export declare const getTrainerSetCount: (trainerAlgorithmSettings: TrainerAlgorithmSettings, goal: TrainingGoal, frequency: WeeklyTrainingFrequency) => number;
194
- export declare const getTrainerRepRange: (trainerAlgorithmSettings: TrainerAlgorithmSettings, goal: TrainingGoal, exerciseCategory: HevyTrainerExerciseCategory) => AlgorithmRepRange;
192
+ export declare const getTrainerRepRange: (trainerAlgorithmSettings: TrainerAlgorithmSettings, goal: TrainingGoal, exerciseCategory: HevyTrainerExerciseCategory) => RepRange;
195
193
  export declare const getTrainerRestTimerSeconds: (trainerAlgorithmSettings: TrainerAlgorithmSettings, goal: TrainingGoal, length: RestTimerLength, exerciseCategory: HevyTrainerExerciseCategory) => number;
196
194
  /**
197
195
  * Normalizes the exercise category to a HevyTrainerExerciseCategory
@@ -245,14 +243,23 @@ export interface ExercisePrescriptionError {
245
243
  focusMuscle?: SimplifiedMuscleGroup;
246
244
  };
247
245
  }
246
+ export interface TrainerProgramExerciseResult {
247
+ success: true;
248
+ exercise: TrainerProgramExercise;
249
+ }
250
+ export interface TrainerProgramExerciseError {
251
+ success: false;
252
+ error: ExercisePrescriptionError;
253
+ }
254
+ export type TrainerProgramExerciseAttempt = TrainerProgramExerciseResult | TrainerProgramExerciseError;
248
255
  export interface TrainerProgramResult {
249
256
  success: true;
250
- program: GeneratedTrainerProgram;
257
+ program: TrainerProgram;
251
258
  }
252
259
  export interface TrainerProgramError {
253
260
  success: false;
254
261
  errors: ExercisePrescriptionError[];
255
- partialProgram: GeneratedTrainerProgram;
262
+ partialProgram: TrainerProgram;
256
263
  }
257
264
  export type TrainerProgramAttempt = TrainerProgramResult | TrainerProgramError;
258
265
  /**
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DEFAULT_TRAINER_CARDIO_ATTACHMENT_DURATION_SECONDS = exports.getPrioritySortedExercises = exports.isEquipmentCompatible = exports.normalizeExerciseCategory = exports.getTrainerRestTimerSeconds = exports.getTrainerRepRange = exports.getTrainerSetCount = exports.isTrainerCardioExerciseType = exports.isTrainerResistanceExerciseType = exports.isTrainerProgramOtherExercise = exports.isTrainerProgramCardioExercise = exports.isTrainerProgramResistanceExercise = exports.programSplits = exports.frequencyMap = exports.workoutTemplateNames = exports.defaultDurationPerFrequency = exports.hevyTrainerExerciseCategories = exports.granularEquipmentsToTrainerEquipments = exports.trainerEquipmentToGranularEquipments = exports.granularEquipmentDefaults = exports.trainerGymTypes = exports.workoutDurationOptions = void 0;
3
+ exports.DEFAULT_TRAINER_CARDIO_ATTACHMENT_DURATION_SECONDS = exports.getPrioritySortedExercises = exports.isEquipmentCompatible = exports.normalizeExerciseCategory = exports.getTrainerRestTimerSeconds = exports.getTrainerRepRange = exports.getTrainerSetCount = exports.isTrainerProgramCardioExercise = exports.isTrainerProgramResistanceExercise = exports.programSplits = exports.frequencyMap = exports.routineNames = exports.defaultDurationPerFrequency = exports.hevyTrainerExerciseCategories = exports.granularEquipmentsToTrainerEquipments = exports.trainerEquipmentToGranularEquipments = exports.granularEquipmentDefaults = exports.trainerGymTypes = exports.workoutDurationOptions = void 0;
4
4
  exports.pickTrainerExercise = pickTrainerExercise;
5
5
  exports.generateProgram = generateProgram;
6
6
  const _1 = require(".");
@@ -152,7 +152,7 @@ exports.defaultDurationPerFrequency = {
152
152
  5: 40,
153
153
  6: 40,
154
154
  };
155
- exports.workoutTemplateNames = [
155
+ exports.routineNames = [
156
156
  // Full body 1x
157
157
  'full_body_1',
158
158
  // Full body 2x
@@ -201,18 +201,6 @@ const isTrainerProgramResistanceExercise = (exercise) => exercise.kind === 'resi
201
201
  exports.isTrainerProgramResistanceExercise = isTrainerProgramResistanceExercise;
202
202
  const isTrainerProgramCardioExercise = (exercise) => exercise.kind === 'cardio';
203
203
  exports.isTrainerProgramCardioExercise = isTrainerProgramCardioExercise;
204
- const isTrainerProgramOtherExercise = (exercise) => exercise.kind === 'other';
205
- exports.isTrainerProgramOtherExercise = isTrainerProgramOtherExercise;
206
- const isTrainerResistanceExerciseType = (exerciseType) => exerciseType === 'weight_reps' ||
207
- exerciseType === 'bodyweight_reps' ||
208
- exerciseType === 'bodyweight_assisted_reps' ||
209
- exerciseType === 'reps_only';
210
- exports.isTrainerResistanceExerciseType = isTrainerResistanceExerciseType;
211
- const isTrainerCardioExerciseType = (exerciseType) => exerciseType === 'distance_duration' ||
212
- exerciseType === 'duration' ||
213
- exerciseType === 'floors_duration' ||
214
- exerciseType === 'steps_duration';
215
- exports.isTrainerCardioExerciseType = isTrainerCardioExerciseType;
216
204
  const getTrainerSetCount = (trainerAlgorithmSettings, goal, frequency) => {
217
205
  return trainerAlgorithmSettings.sets[exports.frequencyMap[frequency]][goal];
218
206
  };
@@ -303,12 +291,12 @@ const MAX_BARBELL_EXERCISES_FOR_ONCE_PER_WEEK = 3;
303
291
  * - Only enforce the cap when the user has at least one "barbell substitute" equipment available
304
292
  * (otherwise we allow barbell exercises to avoid running out of viable options)
305
293
  */
306
- const isBarbellExerciseAllowed = (exercise, workoutBarbellExerciseCount, userEquipments, frequency) => {
294
+ const isBarbellExerciseAllowed = (exercise, routineBarbellExerciseCount, userEquipments, frequency) => {
307
295
  const isCandidateBarbell = exercise.equipment_category === 'barbell';
308
296
  const isOncePerWeek = frequency === 1;
309
297
  if (!isCandidateBarbell || !isOncePerWeek)
310
298
  return true;
311
- const isAtOrOverLimit = workoutBarbellExerciseCount >= MAX_BARBELL_EXERCISES_FOR_ONCE_PER_WEEK;
299
+ const isAtOrOverLimit = routineBarbellExerciseCount >= MAX_BARBELL_EXERCISES_FOR_ONCE_PER_WEEK;
312
300
  if (!isAtOrOverLimit)
313
301
  return true;
314
302
  const barbellSubstitutes = [
@@ -326,7 +314,7 @@ const isBarbellExerciseAllowed = (exercise, workoutBarbellExerciseCount, userEqu
326
314
  const isExerciseUsed = (exercise, context) => {
327
315
  var _a, _b, _c;
328
316
  return (((_a = context.programUsedExerciseIds) === null || _a === void 0 ? void 0 : _a.has(exercise.id)) ||
329
- ((_b = context.workoutUsedExerciseIds) === null || _b === void 0 ? void 0 : _b.has(exercise.id)) ||
317
+ ((_b = context.routineUsedExerciseIds) === null || _b === void 0 ? void 0 : _b.has(exercise.id)) ||
330
318
  ((_c = context.excludedExerciseIds) === null || _c === void 0 ? void 0 : _c.has(exercise.id)) ||
331
319
  false);
332
320
  };
@@ -405,16 +393,16 @@ const getPrioritySortedExercises = (exercisePriorities, exerciseStore) => {
405
393
  return sortedExercises;
406
394
  };
407
395
  exports.getPrioritySortedExercises = getPrioritySortedExercises;
408
- const getMuscleGroup = ({ muscleGroupPrescription, programFocusMuscleExerciseCount, focusMuscle, workoutTemplateName, }) => {
396
+ const getMuscleGroup = ({ muscleGroupPrescription, programFocusMuscleExerciseCount, focusMuscle, routineName, }) => {
409
397
  if (muscleGroupPrescription === 'focus_muscle') {
410
- // If the user has selected a focus muscle and it is allowed for the workout template,
398
+ // If the user has selected a focus muscle and it is allowed for the routine,
411
399
  // we return the focus muscle extra exercise
412
400
  if (!!focusMuscle &&
413
- isFocusMuscleExtraExerciseAllowed(workoutTemplateName, focusMuscle)) {
401
+ isFocusMuscleExtraExerciseAllowed(routineName, focusMuscle)) {
414
402
  const n = _1.simplifiedMuscleGroupToMuscleGroups[focusMuscle].length;
415
403
  return _1.simplifiedMuscleGroupToMuscleGroups[focusMuscle][programFocusMuscleExerciseCount % n];
416
404
  }
417
- // If the user has not selected a focus muscle or it is not allowed for the workout template
405
+ // If the user has not selected a focus muscle or it is not allowed for the routine
418
406
  // we skip this extra exercise for the focus muscle in the program
419
407
  return undefined;
420
408
  }
@@ -432,7 +420,7 @@ const isExerciseMatch = (exercise, criteria) => {
432
420
  const levelMatch = (_b = (_a = exercise.level) === null || _a === void 0 ? void 0 : _a.includes(criteria.level)) !== null && _b !== void 0 ? _b : false;
433
421
  const goalMatch = (_d = (_c = exercise.goal) === null || _c === void 0 ? void 0 : _c.includes(criteria.goal)) !== null && _d !== void 0 ? _d : false;
434
422
  const equipmentMatch = (0, exports.isEquipmentCompatible)(exercise, criteria.equipments);
435
- const barbellLimitOk = isBarbellExerciseAllowed(exercise, criteria.workoutBarbellExerciseCount, criteria.equipments, criteria.frequency);
423
+ const barbellLimitOk = isBarbellExerciseAllowed(exercise, criteria.routineBarbellExerciseCount, criteria.equipments, criteria.frequency);
436
424
  return (categoryMatch && levelMatch && goalMatch && equipmentMatch && barbellLimitOk);
437
425
  };
438
426
  /**
@@ -477,8 +465,8 @@ const pickTrainerExerciseWithTrace = (params) => {
477
465
  programUsedExerciseIds: context.programUsedExerciseIds,
478
466
  excludedExerciseIds: context.excludedExerciseIds,
479
467
  };
480
- const workoutContext = {
481
- workoutUsedExerciseIds: context.workoutUsedExerciseIds,
468
+ const routineContext = {
469
+ routineUsedExerciseIds: context.routineUsedExerciseIds,
482
470
  excludedExerciseIds: context.excludedExerciseIds,
483
471
  };
484
472
  // Pass 1
@@ -492,10 +480,10 @@ const pickTrainerExerciseWithTrace = (params) => {
492
480
  if (exercise)
493
481
  return { exercise, trace: Object.assign(Object.assign({}, trace), { selectedPass: 1 }) };
494
482
  // Pass 2
495
- exercise = findMatchingExercise(exercises, criteria, workoutContext);
483
+ exercise = findMatchingExercise(exercises, criteria, routineContext);
496
484
  trace.entries.push({
497
485
  pass: 2,
498
- label: 'exact match; not used in the same workout (reuse allowed in program)',
486
+ label: 'exact match; not used in routine (reuse allowed in program)',
499
487
  candidatePoolSize: exercises.length,
500
488
  selectedExerciseId: exercise === null || exercise === void 0 ? void 0 : exercise.id,
501
489
  });
@@ -512,20 +500,20 @@ const pickTrainerExerciseWithTrace = (params) => {
512
500
  if (exercise)
513
501
  return { exercise, trace: Object.assign(Object.assign({}, trace), { selectedPass: 3 }) };
514
502
  // Pass 4
515
- exercise = findAlternativeIsolationExercise(exercises, criteria, workoutContext);
503
+ exercise = findAlternativeIsolationExercise(exercises, criteria, routineContext);
516
504
  trace.entries.push({
517
505
  pass: 4,
518
- label: 'handpicked alternative isolation; not used in the same workout (reuse allowed in program)',
506
+ label: 'handpicked alternative isolation; not used in routine (reuse allowed in program)',
519
507
  candidatePoolSize: exercises.length,
520
508
  selectedExerciseId: exercise === null || exercise === void 0 ? void 0 : exercise.id,
521
509
  });
522
510
  if (exercise)
523
511
  return { exercise, trace: Object.assign(Object.assign({}, trace), { selectedPass: 4 }) };
524
512
  // Pass 5
525
- exercise = findMatchingExercise(exercises, Object.assign(Object.assign({}, criteria), { exerciseCategory: 'all' }), workoutContext);
513
+ exercise = findMatchingExercise(exercises, Object.assign(Object.assign({}, criteria), { exerciseCategory: 'all' }), routineContext);
526
514
  trace.entries.push({
527
515
  pass: 5,
528
- label: 'match regardless of isolation or compound; not used in the same workout (reuse allowed in program)',
516
+ label: 'match regardless of isolation or compound; not used in routine (reuse allowed in program)',
529
517
  candidatePoolSize: exercises.length,
530
518
  selectedExerciseId: exercise === null || exercise === void 0 ? void 0 : exercise.id,
531
519
  });
@@ -535,10 +523,10 @@ const pickTrainerExerciseWithTrace = (params) => {
535
523
  const secondaryMuscleExercises = Object.values(sortedExercises)
536
524
  .flat()
537
525
  .filter((e) => e.other_muscles.includes(criteria.muscleGroup));
538
- exercise = findMatchingExercise(secondaryMuscleExercises, Object.assign(Object.assign({}, criteria), { exerciseCategory: 'all' }), workoutContext);
526
+ exercise = findMatchingExercise(secondaryMuscleExercises, Object.assign(Object.assign({}, criteria), { exerciseCategory: 'all' }), routineContext);
539
527
  trace.entries.push({
540
528
  pass: 6,
541
- label: 'secondary muscle match; regardless of isolation or compound; not used in the same workout (reuse allowed in program)',
529
+ label: 'secondary muscle match; regardless of isolation or compound; not used in routine (reuse allowed in program)',
542
530
  candidatePoolSize: secondaryMuscleExercises.length,
543
531
  selectedExerciseId: exercise === null || exercise === void 0 ? void 0 : exercise.id,
544
532
  });
@@ -554,11 +542,11 @@ function pickTrainerExercise(params) {
554
542
  }
555
543
  const CARDIO_TRACE_INDEX_BEFORE_TEMPLATE = -1;
556
544
  exports.DEFAULT_TRAINER_CARDIO_ATTACHMENT_DURATION_SECONDS = 600;
557
- /** Inserts optional cardio before or after template exercises for one workout template. */
558
- const attachOptionalCardioToWorkoutTemplate = ({ cardioPreference, sortedExercises, criteria, debugExerciseSelectionTrace, exerciseSelectionTraces, workoutTemplate, templatePrescriptionCount, workoutExercises, }) => {
545
+ /** Inserts optional cardio before or after template exercises for one routine. */
546
+ const attachOptionalCardioToRoutine = ({ cardioPreference, sortedExercises, criteria, debugExerciseSelectionTrace, exerciseSelectionTraces, routine, templatePrescriptionCount, routineExercises, }) => {
559
547
  if (cardioPreference === 'no-cardio')
560
548
  return;
561
- // Cardio is reusable across workout templates, so program/workoutTemplate used-id contexts are
549
+ // Cardio is reusable across routines, so program/routine used-id contexts are
562
550
  // intentionally omitted — only the global excluded set applies.
563
551
  const selectionParams = {
564
552
  sortedExercises,
@@ -569,7 +557,7 @@ const attachOptionalCardioToWorkoutTemplate = ({ cardioPreference, sortedExercis
569
557
  equipments: criteria.equipments,
570
558
  muscleGroup: 'cardio',
571
559
  exerciseCategory: 'all',
572
- workoutBarbellExerciseCount: 0,
560
+ routineBarbellExerciseCount: 0,
573
561
  },
574
562
  context: { excludedExerciseIds: criteria.excludedExerciseIds },
575
563
  };
@@ -579,18 +567,17 @@ const attachOptionalCardioToWorkoutTemplate = ({ cardioPreference, sortedExercis
579
567
  return;
580
568
  const slot = {
581
569
  kind: 'cardio',
582
- exercise_template: cardioExercise,
583
- duration_seconds: exports.DEFAULT_TRAINER_CARDIO_ATTACHMENT_DURATION_SECONDS,
584
- set_count: 1,
570
+ exerciseTemplate: cardioExercise,
571
+ durationSeconds: exports.DEFAULT_TRAINER_CARDIO_ATTACHMENT_DURATION_SECONDS,
585
572
  };
586
573
  if (cardioPreference === 'workout-start')
587
- workoutExercises.unshift(slot);
574
+ routineExercises.unshift(slot);
588
575
  else
589
- workoutExercises.push(slot);
576
+ routineExercises.push(slot);
590
577
  if (debugExerciseSelectionTrace) {
591
578
  exerciseSelectionTraces.push({
592
579
  traceKind: 'cardio',
593
- workoutTemplate,
580
+ routine,
594
581
  prescriptionIndex: cardioPreference === 'workout-start'
595
582
  ? CARDIO_TRACE_INDEX_BEFORE_TEMPLATE
596
583
  : templatePrescriptionCount,
@@ -605,27 +592,28 @@ function generateProgram(params) {
605
592
  var _a, _b;
606
593
  const { trainerAlgorithmSettings, frequency, goal, level, equipments, workoutDurationMinutes, restTimerLength, exerciseStore, focusMuscle, excludedExerciseIds, debugExerciseSelectionTrace, cardioPreference, } = params;
607
594
  const exerciseSelectionTraces = [];
608
- const workoutTemplates = exports.programSplits[frequency];
595
+ const routines = exports.programSplits[frequency];
609
596
  const program = {
610
- workoutTemplates: [],
597
+ name: frequency,
598
+ routines: [],
611
599
  };
612
600
  // TODO: Rename sortedExercises to sortedExercisesByMuscleGroup or something similar
613
601
  const sortedExercises = (0, exports.getPrioritySortedExercises)(trainerAlgorithmSettings.exercise_priorities, exerciseStore);
614
602
  const programUsedExerciseIds = new Set();
615
603
  let programFocusMuscleExerciseCount = 0;
616
604
  const allErrors = [];
617
- for (const workout of workoutTemplates) {
618
- const workoutTemplate = trainerAlgorithmSettings.templates[workout];
619
- let workoutBarbellExerciseCount = 0;
620
- const workoutUsedExerciseIds = new Set();
621
- const workoutExercises = [];
622
- for (let prescriptionIndex = 0; prescriptionIndex < workoutTemplate.exercises.length; prescriptionIndex++) {
623
- const exercisePrescription = workoutTemplate.exercises[prescriptionIndex];
605
+ for (const routine of routines) {
606
+ const routineTemplate = trainerAlgorithmSettings.templates[routine];
607
+ let routineBarbellExerciseCount = 0;
608
+ const routineUsedExerciseIds = new Set();
609
+ const routineExercises = [];
610
+ for (let prescriptionIndex = 0; prescriptionIndex < routineTemplate.exercises.length; prescriptionIndex++) {
611
+ const exercisePrescription = routineTemplate.exercises[prescriptionIndex];
624
612
  const muscleGroup = getMuscleGroup({
625
613
  muscleGroupPrescription: exercisePrescription.muscle_group,
626
614
  programFocusMuscleExerciseCount,
627
615
  focusMuscle,
628
- workoutTemplateName: workout,
616
+ routineName: routine,
629
617
  });
630
618
  // If the muscle group is not found, skip the exercise
631
619
  if (!muscleGroup) {
@@ -644,7 +632,7 @@ function generateProgram(params) {
644
632
  exerciseCategory: exercisePrescription.category,
645
633
  equipments,
646
634
  muscleGroup,
647
- workoutBarbellExerciseCount,
635
+ routineBarbellExerciseCount,
648
636
  level,
649
637
  goal,
650
638
  };
@@ -655,7 +643,7 @@ function generateProgram(params) {
655
643
  criteria,
656
644
  context: {
657
645
  programUsedExerciseIds,
658
- workoutUsedExerciseIds,
646
+ routineUsedExerciseIds,
659
647
  excludedExerciseIds,
660
648
  },
661
649
  withTrace: true,
@@ -663,7 +651,7 @@ function generateProgram(params) {
663
651
  exercise = selection.exercise;
664
652
  exerciseSelectionTraces.push({
665
653
  traceKind: 'template',
666
- workoutTemplate: workout,
654
+ routine,
667
655
  prescriptionIndex,
668
656
  prescription: exercisePrescription,
669
657
  resolvedMuscleGroup: muscleGroup,
@@ -678,29 +666,30 @@ function generateProgram(params) {
678
666
  criteria,
679
667
  context: {
680
668
  programUsedExerciseIds,
681
- workoutUsedExerciseIds,
669
+ routineUsedExerciseIds,
682
670
  excludedExerciseIds,
683
671
  },
684
672
  });
685
673
  }
686
674
  if (!!exercise) {
687
675
  programUsedExerciseIds.add(exercise.id);
688
- workoutUsedExerciseIds.add(exercise.id);
676
+ routineUsedExerciseIds.add(exercise.id);
689
677
  if (exercise.equipment_category === 'barbell')
690
- workoutBarbellExerciseCount++;
678
+ routineBarbellExerciseCount++;
691
679
  if (exercisePrescription.muscle_group === 'focus_muscle')
692
680
  programFocusMuscleExerciseCount++;
693
681
  const repRange = (0, exports.getTrainerRepRange)(trainerAlgorithmSettings, goal, exercisePrescription.category);
694
- workoutExercises.push({
682
+ routineExercises.push({
695
683
  kind: 'resistance',
696
- exercise_template: exercise,
697
- set_count: (0, exports.getTrainerSetCount)(trainerAlgorithmSettings, goal, frequency),
698
- warmup_set_count: (_b = exercisePrescription.warmup_set_count) !== null && _b !== void 0 ? _b : 0,
699
- rep_range: {
700
- start: repRange.rep_range_start,
701
- end: repRange.rep_range_end,
702
- },
703
- rest_seconds: (0, exports.getTrainerRestTimerSeconds)(trainerAlgorithmSettings, goal, restTimerLength, exercisePrescription.category),
684
+ exerciseTemplate: exercise,
685
+ muscleGroup: exercisePrescription.muscle_group,
686
+ category: exercisePrescription.category,
687
+ sets: (0, exports.getTrainerSetCount)(trainerAlgorithmSettings, goal, frequency),
688
+ repRangeStart: repRange.rep_range_start,
689
+ repRangeEnd: repRange.rep_range_end,
690
+ restTimerSeconds: (0, exports.getTrainerRestTimerSeconds)(trainerAlgorithmSettings, goal, restTimerLength, exercisePrescription.category),
691
+ warmupSetCount: exercisePrescription.warmup_set_count,
692
+ notes: (_b = trainerAlgorithmSettings.exercise_notes[exercise.id]) !== null && _b !== void 0 ? _b : '',
704
693
  });
705
694
  }
706
695
  else {
@@ -718,21 +707,23 @@ function generateProgram(params) {
718
707
  });
719
708
  }
720
709
  }
721
- attachOptionalCardioToWorkoutTemplate({
710
+ attachOptionalCardioToRoutine({
722
711
  cardioPreference,
723
712
  sortedExercises,
724
713
  criteria: { equipments, level, goal, frequency, excludedExerciseIds },
725
714
  debugExerciseSelectionTrace,
726
715
  exerciseSelectionTraces,
727
- workoutTemplate: workout,
728
- templatePrescriptionCount: workoutTemplate.exercises.length,
729
- workoutExercises,
716
+ routine,
717
+ templatePrescriptionCount: routineTemplate.exercises.length,
718
+ routineExercises,
730
719
  });
731
- program.workoutTemplates.push({
732
- name: workout,
733
- exercises: workoutExercises,
720
+ program.routines.push({
721
+ name: routine,
722
+ notes: routineTemplate.notes,
723
+ exercises: routineExercises,
734
724
  });
735
725
  }
726
+ // Return result based on whether there were errors
736
727
  if (allErrors.length > 0) {
737
728
  const result = {
738
729
  success: false,
package/built/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { InstructionsLanguage } from './exerciseLocaleUtils';
2
- import { TrainerWorkoutTemplateName, WorkoutDurationMinutes } from './hevyTrainer';
2
+ import { WorkoutDurationMinutes } from './hevyTrainer';
3
3
  import { BugReportQuestionSchema, CreateBugReportRequestBodySchema, postEmailBackofficeSchema } from './schemas';
4
4
  import { Language } from './translations';
5
5
  import { Lookup } from './typeUtils';
@@ -302,7 +302,7 @@ export interface BackofficeExistingUserResponse {
302
302
  public_api_key: string | null;
303
303
  limited_discovery: boolean;
304
304
  coach_trial_expire_date?: string;
305
- hevy_trainer_program: TrainerProgramV3 | null;
305
+ hevy_trainer_program: HevyTrainerProgram | null;
306
306
  }
307
307
  export interface BackofficeDeletedUserResponse {
308
308
  state: 'deleted-account';
@@ -747,10 +747,6 @@ export interface RepRange {
747
747
  start: number | null;
748
748
  end: number | null;
749
749
  }
750
- export interface StrictRepRange extends RepRange {
751
- start: number;
752
- end: number;
753
- }
754
750
  export interface WorkoutComment {
755
751
  id: number;
756
752
  username: string;
@@ -879,14 +875,12 @@ export interface Routine extends BaseRoutine {
879
875
  username: string;
880
876
  coach_id: null;
881
877
  }
882
- /** @deprecated Use TrainerWorkoutTemplate instead */
883
878
  export interface HevyTrainerRoutine extends Routine {
884
879
  hevy_trainer_program_id: string;
885
880
  program_id: null;
886
881
  coach_force_rpe_enabled: false;
887
882
  folder_id: null;
888
883
  }
889
- /** @deprecated Use TrainerWorkoutTemplate instead */
890
884
  export declare const isHevyTrainerRoutine: (routine: BaseRoutine) => routine is HevyTrainerRoutine;
891
885
  export type CoachsShallowLibraryRoutine = Omit<BaseRoutine, 'exercises' | 'profile_pic' | 'hevy_trainer_program_id' | 'username'>;
892
886
  export interface CoachesRoutine extends BaseRoutine {
@@ -1055,97 +1049,6 @@ export interface GetTeamInviteResponse {
1055
1049
  export interface OutstandingInvitesForCoachTeamResponse {
1056
1050
  invites: CoachTeamInvite[];
1057
1051
  }
1058
- export interface TrainerWorkoutTemplateResistanceExercise {
1059
- kind: 'resistance';
1060
- id: string;
1061
- exercise_template_id: string;
1062
- index: number;
1063
- set_count: number;
1064
- warmup_set_count: number;
1065
- rep_range: StrictRepRange;
1066
- rest_seconds: number;
1067
- }
1068
- export interface TrainerWorkoutTemplateCardioExercise {
1069
- kind: 'cardio';
1070
- id: string;
1071
- exercise_template_id: string;
1072
- index: number;
1073
- set_count: 1;
1074
- duration_seconds: number;
1075
- }
1076
- export interface TrainerWorkoutTemplateOtherExercise {
1077
- kind: 'other';
1078
- id: string;
1079
- exercise_template_id: string;
1080
- index: number;
1081
- set_count: number;
1082
- }
1083
- export type TrainerWorkoutTemplateExercise = TrainerWorkoutTemplateResistanceExercise | TrainerWorkoutTemplateCardioExercise | TrainerWorkoutTemplateOtherExercise;
1084
- export interface TrainerWorkoutTemplate {
1085
- id: string;
1086
- hevy_trainer_program_id: string;
1087
- index: number;
1088
- name: TrainerWorkoutTemplateName;
1089
- exercises: TrainerWorkoutTemplateExercise[];
1090
- }
1091
- export interface TrainerProgramV3 {
1092
- id: string;
1093
- schema_version: 'v3';
1094
- created_at: string;
1095
- updated_at: string;
1096
- level: TrainingLevel;
1097
- goal: TrainingGoal;
1098
- equipments: GranularEquipment[];
1099
- weekly_frequency: WeeklyTrainingFrequency;
1100
- templates: TrainerWorkoutTemplate[];
1101
- focus_muscle?: SimplifiedMuscleGroup;
1102
- next_workout_index: number;
1103
- workout_duration_minutes: WorkoutDurationMinutes;
1104
- rest_timer_length: RestTimerLength;
1105
- cardio_preference: CardioPreference;
1106
- }
1107
- export type TrainerWorkoutTemplateExerciseInput = Omit<TrainerWorkoutTemplateResistanceExercise, 'id'> | Omit<TrainerWorkoutTemplateCardioExercise, 'id'> | Omit<TrainerWorkoutTemplateOtherExercise, 'id'>;
1108
- export interface PostTrainerWorkoutTemplate {
1109
- name: TrainerWorkoutTemplateName;
1110
- index: number;
1111
- exercises: TrainerWorkoutTemplateExerciseInput[];
1112
- }
1113
- export interface UpdateTrainerWorkoutTemplate extends PostTrainerWorkoutTemplate {
1114
- id: string;
1115
- }
1116
- export interface PostTrainerProgramV3RequestBody {
1117
- program: {
1118
- version: 3;
1119
- title: string;
1120
- level: TrainingLevel;
1121
- goal: TrainingGoal;
1122
- equipments: GranularEquipment[];
1123
- weekly_frequency: WeeklyTrainingFrequency;
1124
- focus_muscle?: SimplifiedMuscleGroup;
1125
- next_workout_index?: number;
1126
- workout_duration_minutes: WorkoutDurationMinutes;
1127
- rest_timer_length: RestTimerLength;
1128
- cardio_preference: CardioPreference;
1129
- templates: PostTrainerWorkoutTemplate[];
1130
- };
1131
- }
1132
- export interface UpdateTrainerProgramV3RequestBody {
1133
- program: {
1134
- version: 3;
1135
- programId: string;
1136
- level: TrainingLevel;
1137
- goal: TrainingGoal;
1138
- equipments: GranularEquipment[];
1139
- weekly_frequency: WeeklyTrainingFrequency;
1140
- focus_muscle?: SimplifiedMuscleGroup;
1141
- next_workout_index?: number;
1142
- workout_duration_minutes: WorkoutDurationMinutes;
1143
- rest_timer_length: RestTimerLength;
1144
- cardio_preference: CardioPreference;
1145
- templates: UpdateTrainerWorkoutTemplate[];
1146
- };
1147
- }
1148
- /** @deprecated Use TrainerProgramV3 instead */
1149
1052
  export interface HevyTrainerProgram {
1150
1053
  id: string;
1151
1054
  created_at: string;
@@ -1162,7 +1065,6 @@ export interface HevyTrainerProgram {
1162
1065
  rest_timer_length: RestTimerLength;
1163
1066
  cardio_preference: CardioPreference;
1164
1067
  }
1165
- /** @deprecated Use PostTrainerProgramV3RequestBody instead */
1166
1068
  export interface PostHevyTrainerProgramRequestBody {
1167
1069
  program: {
1168
1070
  version: 1;
@@ -1179,7 +1081,6 @@ export interface PostHevyTrainerProgramRequestBody {
1179
1081
  cardio_preference: CardioPreference;
1180
1082
  };
1181
1083
  }
1182
- /** @deprecated Use UpdateTrainerProgramV3RequestBody instead */
1183
1084
  export interface UpdateHevyTrainerProgramRequestBody {
1184
1085
  program: {
1185
1086
  version: 1;
@@ -1202,7 +1103,7 @@ export interface UpdateHevyTrainerProgramRequestBody {
1202
1103
  }[];
1203
1104
  };
1204
1105
  }
1205
- /** @deprecated Use TrainerProgramV3 instead */
1106
+ /** @deprecated Use HevyTrainerProgram instead */
1206
1107
  export interface HevyTrainerProgramOld {
1207
1108
  id: string;
1208
1109
  created_at: string;
@@ -1217,7 +1118,7 @@ export interface HevyTrainerProgramOld {
1217
1118
  next_workout_index: number;
1218
1119
  workout_duration_minutes?: WorkoutDurationMinutes;
1219
1120
  }
1220
- /** @deprecated Use PostTrainerProgramV3RequestBody instead */
1121
+ /** @deprecated Use PostHevyTrainerProgramRequestBody instead */
1221
1122
  export interface PostHevyTrainerProgramOldRequestBody {
1222
1123
  program: {
1223
1124
  title: string;
@@ -1231,7 +1132,7 @@ export interface PostHevyTrainerProgramOldRequestBody {
1231
1132
  workout_duration_minutes?: WorkoutDurationMinutes;
1232
1133
  };
1233
1134
  }
1234
- /** @deprecated Use UpdateTrainerProgramV3RequestBody instead */
1135
+ /** @deprecated Use UpdateHevyTrainerProgramRequestBody instead */
1235
1136
  export interface UpdateHevyTrainerProgramOldRequestBody {
1236
1137
  program: {
1237
1138
  programId: string;
@@ -1499,7 +1400,7 @@ export interface UserMetadataResponse {
1499
1400
  export interface NetworkInfoRequest {
1500
1401
  networkType: NetworkType;
1501
1402
  }
1502
- export type CommercialGym = {
1403
+ type CommercialGym = {
1503
1404
  type: 'commercial';
1504
1405
  id: string;
1505
1406
  name: string;
@@ -1507,10 +1408,7 @@ export type CommercialGym = {
1507
1408
  city: string;
1508
1409
  distanceM: number;
1509
1410
  };
1510
- export type HomeGym = {
1511
- type: 'home';
1512
- };
1513
- export type Gym = CommercialGym | HomeGym;
1411
+ export type Gym = CommercialGym;
1514
1412
  export interface StripePrice {
1515
1413
  product_id: string;
1516
1414
  billing_period: 'month' | 'year' | 'pay-once';
package/built/index.js CHANGED
@@ -321,7 +321,6 @@ const isWorkoutBiometrics = (x) => {
321
321
  return caloriesAreValid && heartSamplesAreValid;
322
322
  };
323
323
  exports.isWorkoutBiometrics = isWorkoutBiometrics;
324
- /** @deprecated Use TrainerWorkoutTemplate instead */
325
324
  const isHevyTrainerRoutine = (routine) => routine.hevy_trainer_program_id !== null;
326
325
  exports.isHevyTrainerRoutine = isHevyTrainerRoutine;
327
326
  exports.measurementsList = [
@@ -236,7 +236,7 @@ const makeSettings = (overrides = {}) => {
236
236
  };
237
237
  return acc;
238
238
  }, {});
239
- const templates = hevyTrainer_1.workoutTemplateNames.reduce((acc, name) => {
239
+ const templates = hevyTrainer_1.routineNames.reduce((acc, name) => {
240
240
  acc[name] = { exercises: [] };
241
241
  return acc;
242
242
  }, {});
@@ -372,7 +372,7 @@ describe('getPrioritySortedExercises', () => {
372
372
  expect(result.quadriceps.map((e) => e.id)).toEqual(['leg-ex']);
373
373
  });
374
374
  });
375
- const baseCriteria = (overrides = {}) => (Object.assign({ exerciseCategory: 'compound', equipments: [], workoutBarbellExerciseCount: 0, level: 'beginner', goal: 'strength', muscleGroup: 'chest', frequency: 3 }, overrides));
375
+ const baseCriteria = (overrides = {}) => (Object.assign({ exerciseCategory: 'compound', equipments: [], routineBarbellExerciseCount: 0, level: 'beginner', goal: 'strength', muscleGroup: 'chest', frequency: 3 }, overrides));
376
376
  /** Produces a `sortedExercises` record where only `chest` is populated. */
377
377
  const chestOnlySorted = (exercises) => {
378
378
  const empty = __1.muscleGroups.reduce((acc, mg) => {
@@ -394,7 +394,7 @@ describe('pickTrainerExercise', () => {
394
394
  });
395
395
  expect(result === null || result === void 0 ? void 0 : result.id).toBe('fresh');
396
396
  });
397
- it('pass 2: falls back to exercises not used in the current workout when all are used in the program', () => {
397
+ it('pass 2: falls back to exercises not used in the current routine when all are used in the program', () => {
398
398
  const onlyExercise = makeExercise({ id: 'only' });
399
399
  const result = (0, hevyTrainer_1.pickTrainerExercise)({
400
400
  sortedExercises: chestOnlySorted([onlyExercise]),
@@ -402,8 +402,8 @@ describe('pickTrainerExercise', () => {
402
402
  context: {
403
403
  // Marked as used in the whole program already.
404
404
  programUsedExerciseIds: new Set(['only']),
405
- // But not used in this workout, so pass 2 should succeed.
406
- workoutUsedExerciseIds: new Set(),
405
+ // But not used in this routine, so pass 2 should succeed.
406
+ routineUsedExerciseIds: new Set(),
407
407
  },
408
408
  });
409
409
  expect(result === null || result === void 0 ? void 0 : result.id).toBe('only');
@@ -506,7 +506,7 @@ describe('pickTrainerExercise', () => {
506
506
  sortedExercises: chestOnlySorted([ex]),
507
507
  criteria: baseCriteria({
508
508
  frequency: 3,
509
- workoutBarbellExerciseCount: 10,
509
+ routineBarbellExerciseCount: 10,
510
510
  equipments: ['dumbbell'],
511
511
  }),
512
512
  context: {},
@@ -519,7 +519,7 @@ describe('pickTrainerExercise', () => {
519
519
  sortedExercises: chestOnlySorted([ex]),
520
520
  criteria: baseCriteria({
521
521
  frequency: 1,
522
- workoutBarbellExerciseCount: 2,
522
+ routineBarbellExerciseCount: 2,
523
523
  equipments: ['dumbbell'],
524
524
  }),
525
525
  context: {},
@@ -532,7 +532,7 @@ describe('pickTrainerExercise', () => {
532
532
  sortedExercises: chestOnlySorted([ex]),
533
533
  criteria: baseCriteria({
534
534
  frequency: 1,
535
- workoutBarbellExerciseCount: 3,
535
+ routineBarbellExerciseCount: 3,
536
536
  // Dumbbell is listed as a barbell substitute, so the cap applies.
537
537
  equipments: ['dumbbell'],
538
538
  }),
@@ -546,7 +546,7 @@ describe('pickTrainerExercise', () => {
546
546
  sortedExercises: chestOnlySorted([ex]),
547
547
  criteria: baseCriteria({
548
548
  frequency: 1,
549
- workoutBarbellExerciseCount: 5,
549
+ routineBarbellExerciseCount: 5,
550
550
  // No substitutes → avoid dead-ends by allowing the barbell.
551
551
  equipments: [],
552
552
  }),
@@ -587,7 +587,7 @@ describe('pickTrainerExercise', () => {
587
587
  describe('generateProgram', () => {
588
588
  const buildSettingsForChestProgram = () => {
589
589
  const settings = makeSettings();
590
- // Put a single chest compound prescription into the 1-day full body workout.
590
+ // Put a single chest compound prescription into the 1-day full body routine.
591
591
  settings.templates.full_body_1 = {
592
592
  exercises: [
593
593
  { muscle_group: 'chest', category: 'compound', warmup_set_count: 2 },
@@ -596,7 +596,7 @@ describe('generateProgram', () => {
596
596
  };
597
597
  return settings;
598
598
  };
599
- it('produces one workout per programSplit and populates exercise fields from the settings', () => {
599
+ it('produces one routine per programSplit and populates exercise fields from the settings', () => {
600
600
  const settings = buildSettingsForChestProgram();
601
601
  const chestExercise = makeExercise({ id: 'bench', muscle_group: 'chest' });
602
602
  const result = (0, hevyTrainer_1.generateProgram)({
@@ -613,19 +613,27 @@ describe('generateProgram', () => {
613
613
  expect(result.success).toBe(true);
614
614
  if (!result.success)
615
615
  return;
616
- expect(result.program.workoutTemplates).toHaveLength(hevyTrainer_1.programSplits[1].length);
617
- const [workoutTemplate] = result.program.workoutTemplates;
618
- expect(workoutTemplate.name).toBe('full_body_1');
619
- expect(workoutTemplate.exercises).toHaveLength(1);
620
- const [exercise] = workoutTemplate.exercises;
621
- if (!(0, hevyTrainer_1.isTrainerProgramResistanceExercise)(exercise))
622
- return;
616
+ expect(result.program.name).toBe(1);
617
+ expect(result.program.routines).toHaveLength(hevyTrainer_1.programSplits[1].length);
618
+ const [routine] = result.program.routines;
619
+ expect(routine.name).toBe('full_body_1');
620
+ expect(routine.notes).toBe('Hit the chest hard.');
621
+ expect(routine.exercises).toHaveLength(1);
622
+ const [exercise] = routine.exercises;
623
623
  expect(exercise.kind).toBe('resistance');
624
- expect(exercise.exercise_template.id).toBe('bench');
625
- expect(exercise.warmup_set_count).toBe(2);
626
- expect(exercise.set_count).toBe(1);
627
- expect(exercise.rep_range).toEqual({ start: 1, end: 5 });
628
- expect(exercise.rest_seconds).toBe(60);
624
+ if (exercise.kind !== 'resistance')
625
+ return;
626
+ expect(exercise.exerciseTemplate.id).toBe('bench');
627
+ expect(exercise.category).toBe('compound');
628
+ expect(exercise.muscleGroup).toBe('chest');
629
+ expect(exercise.warmupSetCount).toBe(2);
630
+ // strength + one_day -> 1 (see `makeSettings`)
631
+ expect(exercise.sets).toBe(1);
632
+ // strength + compound -> 1..5
633
+ expect(exercise.repRangeStart).toBe(1);
634
+ expect(exercise.repRangeEnd).toBe(5);
635
+ // strength + medium + compound -> 60
636
+ expect(exercise.restTimerSeconds).toBe(60);
629
637
  });
630
638
  it('returns a partial program and errors when no matching exercise can be found', () => {
631
639
  const settings = buildSettingsForChestProgram();
@@ -648,14 +656,14 @@ describe('generateProgram', () => {
648
656
  type: 'exercise_not_found',
649
657
  muscleGroup: 'chest',
650
658
  });
651
- // The workout template structure still exists in the partial program, just without
659
+ // The routine structure still exists in the partial program, just without
652
660
  // any resolved exercises.
653
- expect(result.partialProgram.workoutTemplates).toHaveLength(1);
654
- expect(result.partialProgram.workoutTemplates[0].exercises).toEqual([]);
661
+ expect(result.partialProgram.routines).toHaveLength(1);
662
+ expect(result.partialProgram.routines[0].exercises).toEqual([]);
655
663
  });
656
664
  describe('min_workout_duration_limit filtering', () => {
657
665
  /**
658
- * Runs a 1-day workout program containing a single chest compound prescription
666
+ * Runs a 1-day program containing a single chest compound prescription
659
667
  * with the provided `min_workout_duration_limit` against the provided
660
668
  * `workoutDurationMinutes`, and returns whether the prescription was
661
669
  * placed.
@@ -682,8 +690,9 @@ describe('generateProgram', () => {
682
690
  exerciseStore: [makeExercise({ id: 'bench' })],
683
691
  cardioPreference: 'no-cardio',
684
692
  });
685
- const workoutTemplate = (result.success ? result.program : result.partialProgram).workoutTemplates[0];
686
- return workoutTemplate.exercises.length === 1;
693
+ const routine = (result.success ? result.program : result.partialProgram)
694
+ .routines[0];
695
+ return routine.exercises.length === 1;
687
696
  };
688
697
  it('skips prescriptions whose limit is strictly greater than the selected duration', () => {
689
698
  expect(runDurationProbe({ limit: 80, workoutDurationMinutes: 40 })).toBe(false);
@@ -755,11 +764,11 @@ describe('generateProgram', () => {
755
764
  return;
756
765
  // Only the 40- and 60-minute prescriptions survive; the 80-minute one
757
766
  // is filtered out. Order is preserved.
758
- const placed = result.program.workoutTemplates[0].exercises;
767
+ const placed = result.program.routines[0].exercises;
759
768
  expect(placed.every((e) => e.kind === 'resistance')).toBe(true);
760
769
  expect(placed
761
770
  .filter(hevyTrainer_1.isTrainerProgramResistanceExercise)
762
- .map((e) => e.warmup_set_count)).toEqual([1, 2]);
771
+ .map((e) => e.warmupSetCount)).toEqual([1, 2]);
763
772
  });
764
773
  });
765
774
  it('skips focus_muscle prescriptions when no focusMuscle is provided', () => {
@@ -783,7 +792,7 @@ describe('generateProgram', () => {
783
792
  expect(result.success).toBe(true);
784
793
  if (!result.success)
785
794
  return;
786
- expect(result.program.workoutTemplates[0].exercises).toEqual([]);
795
+ expect(result.program.routines[0].exercises).toEqual([]);
787
796
  });
788
797
  it('routes focus_muscle prescriptions to the requested SimplifiedMuscleGroup', () => {
789
798
  const settings = makeSettings();
@@ -810,11 +819,12 @@ describe('generateProgram', () => {
810
819
  expect(result.success).toBe(true);
811
820
  if (!result.success)
812
821
  return;
813
- const [exercise] = result.program.workoutTemplates[0].exercises;
822
+ const [exercise] = result.program.routines[0].exercises;
814
823
  expect(exercise.kind).toBe('resistance');
815
824
  if (exercise.kind !== 'resistance')
816
825
  return;
817
- expect(exercise.exercise_template.id).toBe('biceps-curl');
826
+ expect(exercise.exerciseTemplate.id).toBe('biceps-curl');
827
+ expect(exercise.muscleGroup).toBe('focus_muscle');
818
828
  });
819
829
  it('excludes exercises listed in `excludedExerciseIds`', () => {
820
830
  const settings = buildSettingsForChestProgram();
@@ -835,7 +845,7 @@ describe('generateProgram', () => {
835
845
  expect(result.success).toBe(true);
836
846
  if (!result.success)
837
847
  return;
838
- expect(result.program.workoutTemplates[0].exercises[0].exercise_template.id).toBe('alt');
848
+ expect(result.program.routines[0].exercises[0].exerciseTemplate.id).toBe('alt');
839
849
  });
840
850
  it('attaches exercise selection traces when debugExerciseSelectionTrace is true', () => {
841
851
  const settings = buildSettingsForChestProgram();
@@ -856,7 +866,7 @@ describe('generateProgram', () => {
856
866
  expect(result.exerciseSelectionTraces).toHaveLength(1);
857
867
  const [trace] = result.exerciseSelectionTraces;
858
868
  expect(trace.traceKind).toBe('template');
859
- expect(trace.workoutTemplate).toBe('full_body_1');
869
+ expect(trace.routine).toBe('full_body_1');
860
870
  expect(trace.selectedExerciseId).toBe('bench');
861
871
  expect(trace.trace.selectedPass).toBe(1);
862
872
  });
@@ -928,7 +938,7 @@ describe('generateProgram', () => {
928
938
  expect(cardioTrace.equipments).toEqual([]);
929
939
  expect(cardioTrace.level).toBe('beginner');
930
940
  });
931
- it('uses the same prioritized cardio exercise on every workout (not blocked by program reuse)', () => {
941
+ it('uses the same prioritized cardio exercise on every routine (not blocked by program reuse)', () => {
932
942
  const settings = makeSettings();
933
943
  settings.templates.full_body_2_a = {
934
944
  exercises: [{ muscle_group: 'chest', category: 'compound' }],
@@ -961,13 +971,13 @@ describe('generateProgram', () => {
961
971
  expect(result.success).toBe(true);
962
972
  if (!result.success)
963
973
  return;
964
- expect(result.program.workoutTemplates[0].exercises[0]).toMatchObject({
974
+ expect(result.program.routines[0].exercises[0]).toMatchObject({
965
975
  kind: 'cardio',
966
- exercise_template: { id: 'run' },
976
+ exerciseTemplate: { id: 'run' },
967
977
  });
968
- expect(result.program.workoutTemplates[1].exercises[0]).toMatchObject({
978
+ expect(result.program.routines[1].exercises[0]).toMatchObject({
969
979
  kind: 'cardio',
970
- exercise_template: { id: 'run' },
980
+ exerciseTemplate: { id: 'run' },
971
981
  });
972
982
  });
973
983
  it('skips cardio exercises that need equipment the user does not have', () => {
@@ -1006,9 +1016,9 @@ describe('generateProgram', () => {
1006
1016
  expect(result.success).toBe(true);
1007
1017
  if (!result.success)
1008
1018
  return;
1009
- expect(result.program.workoutTemplates[0].exercises[0]).toMatchObject({
1019
+ expect(result.program.routines[0].exercises[0]).toMatchObject({
1010
1020
  kind: 'cardio',
1011
- exercise_template: { id: 'body-run' },
1021
+ exerciseTemplate: { id: 'body-run' },
1012
1022
  });
1013
1023
  });
1014
1024
  it('skips globally excluded cardio and uses the next priority exercise', () => {
@@ -1047,12 +1057,12 @@ describe('generateProgram', () => {
1047
1057
  expect(result.success).toBe(true);
1048
1058
  if (!result.success)
1049
1059
  return;
1050
- expect(result.program.workoutTemplates[0].exercises[0]).toMatchObject({
1060
+ expect(result.program.routines[0].exercises[0]).toMatchObject({
1051
1061
  kind: 'cardio',
1052
- exercise_template: { id: 'run-b' },
1062
+ exerciseTemplate: { id: 'run-b' },
1053
1063
  });
1054
1064
  });
1055
- it('keeps global trace order aligned with workout generation (no cross-workout unshift)', () => {
1065
+ it('keeps global trace order aligned with routine generation (no cross-routine unshift)', () => {
1056
1066
  const settings = makeSettings();
1057
1067
  settings.templates.full_body_2_a = {
1058
1068
  exercises: [{ muscle_group: 'chest', category: 'compound' }],
@@ -1086,15 +1096,15 @@ describe('generateProgram', () => {
1086
1096
  expect(result.success).toBe(true);
1087
1097
  if (!result.success)
1088
1098
  return;
1089
- const idxA = result.exerciseSelectionTraces.findIndex((t) => t.workoutTemplate === 'full_body_2_a');
1090
- const idxB = result.exerciseSelectionTraces.findIndex((t) => t.workoutTemplate === 'full_body_2_b');
1099
+ const idxA = result.exerciseSelectionTraces.findIndex((t) => t.routine === 'full_body_2_a');
1100
+ const idxB = result.exerciseSelectionTraces.findIndex((t) => t.routine === 'full_body_2_b');
1091
1101
  expect(idxA).toBeGreaterThanOrEqual(0);
1092
1102
  expect(idxB).toBeGreaterThan(idxA);
1093
1103
  });
1094
1104
  describe('barbell cap at frequency === 1 (end-to-end)', () => {
1095
1105
  it('drops the 4th+ barbell prescription when the user has a substitute', () => {
1096
1106
  const settings = makeSettings();
1097
- // 4 identical barbell compound prescriptions in the 1-day workout.
1107
+ // 4 identical barbell compound prescriptions in the 1-day routine.
1098
1108
  settings.templates.full_body_1 = {
1099
1109
  exercises: Array.from({ length: 4 }, () => ({
1100
1110
  muscle_group: 'chest',
@@ -1123,7 +1133,7 @@ describe('generateProgram', () => {
1123
1133
  expect(result.success).toBe(false);
1124
1134
  if (result.success)
1125
1135
  return;
1126
- expect(result.partialProgram.workoutTemplates[0].exercises).toHaveLength(3);
1136
+ expect(result.partialProgram.routines[0].exercises).toHaveLength(3);
1127
1137
  expect(result.errors).toHaveLength(1);
1128
1138
  });
1129
1139
  it('allows 4+ barbell prescriptions at frequency 1 when no substitute is present', () => {
@@ -1154,19 +1164,19 @@ describe('generateProgram', () => {
1154
1164
  expect(result.success).toBe(true);
1155
1165
  if (!result.success)
1156
1166
  return;
1157
- expect(result.program.workoutTemplates[0].exercises).toHaveLength(4);
1167
+ expect(result.program.routines[0].exercises).toHaveLength(4);
1158
1168
  });
1159
1169
  });
1160
1170
  describe('focus_muscle cycling', () => {
1161
1171
  it('cycles through simplifiedMuscleGroupToMuscleGroups as focus_muscle prescriptions are placed', () => {
1162
1172
  // Use a 2-day split so we can place 4 focus_muscle exercises across two
1163
- // workouts and confirm the counter survives between workouts.
1173
+ // routines and confirm the counter survives between routines.
1164
1174
  const settings = makeSettings();
1165
1175
  const focusPrescription = {
1166
1176
  muscle_group: 'focus_muscle',
1167
1177
  category: 'isolation',
1168
1178
  };
1169
- // full_body workouts allow any focus muscle. Place 2 per workout so the
1179
+ // full_body routines allow any focus muscle. Place 2 per routine so the
1170
1180
  // combined count across the program is 4.
1171
1181
  settings.templates.full_body_2_a = {
1172
1182
  exercises: [focusPrescription, focusPrescription],
@@ -1236,7 +1246,7 @@ describe('generateProgram', () => {
1236
1246
  settings.templates.upper_2 = { exercises: [focusPrescription] };
1237
1247
  settings.templates.lower_2 = { exercises: [focusPrescription] };
1238
1248
  // `legs` cycles through ['quadriceps', 'hamstrings', 'calves', 'glutes',
1239
- // 'abductors', 'adductors']. The first two allowed workouts (legs_1 and
1249
+ // 'abductors', 'adductors']. The first two allowed routines (legs_1 and
1240
1250
  // lower_2) should land on quadriceps and hamstrings respectively.
1241
1251
  const exercises = [
1242
1252
  makeExercise({
@@ -1269,7 +1279,10 @@ describe('generateProgram', () => {
1269
1279
  // Only legs_1 and lower_2 run the focus_muscle prescription. Both
1270
1280
  // succeed and should have selected quadriceps then hamstrings in order.
1271
1281
  expect(result.exerciseSelectionTraces).toHaveLength(2);
1272
- expect(result.exerciseSelectionTraces.map((t) => t.workoutTemplate)).toEqual(['legs_1', 'lower_2']);
1282
+ expect(result.exerciseSelectionTraces.map((t) => t.routine)).toEqual([
1283
+ 'legs_1',
1284
+ 'lower_2',
1285
+ ]);
1273
1286
  expect(result.exerciseSelectionTraces
1274
1287
  .filter((t) => t.traceKind === 'template')
1275
1288
  .map((t) => t.resolvedMuscleGroup)).toEqual(['quadriceps', 'hamstrings']);
@@ -1277,14 +1290,14 @@ describe('generateProgram', () => {
1277
1290
  });
1278
1291
  describe('isFocusMuscleExtraExerciseAllowed template compatibility', () => {
1279
1292
  /**
1280
- * Runs a single-workout template program that places one focus_muscle
1293
+ * Runs a single-routine program that places one focus_muscle
1281
1294
  * prescription into the requested template and returns whether the
1282
1295
  * prescription produced an exercise or was skipped.
1283
1296
  */
1284
- const runFocusMuscleProbe = (workoutTemplateName, focusMuscle, frequency, candidateExercise) => {
1297
+ const runFocusMuscleProbe = (routineName, focusMuscle, frequency, candidateExercise) => {
1285
1298
  var _a;
1286
1299
  const settings = makeSettings();
1287
- settings.templates[workoutTemplateName] = {
1300
+ settings.templates[routineName] = {
1288
1301
  exercises: [{ muscle_group: 'focus_muscle', category: 'isolation' }],
1289
1302
  };
1290
1303
  const result = (0, hevyTrainer_1.generateProgram)({
@@ -1300,13 +1313,13 @@ describe('generateProgram', () => {
1300
1313
  debugExerciseSelectionTrace: true,
1301
1314
  cardioPreference: 'no-cardio',
1302
1315
  });
1303
- // Find the workout template we targeted (it may not be first in the split).
1304
- const workoutTemplate = (result.success ? result.program : result.partialProgram).workoutTemplates.find((w) => w.name === workoutTemplateName);
1316
+ // Find the routine we targeted (it may not be first in the split).
1317
+ const routine = (result.success ? result.program : result.partialProgram).routines.find((r) => r.name === routineName);
1305
1318
  return {
1306
- placed: ((_a = workoutTemplate === null || workoutTemplate === void 0 ? void 0 : workoutTemplate.exercises.length) !== null && _a !== void 0 ? _a : 0) > 0,
1319
+ placed: ((_a = routine === null || routine === void 0 ? void 0 : routine.exercises.length) !== null && _a !== void 0 ? _a : 0) > 0,
1307
1320
  };
1308
1321
  };
1309
- it('allows every focus muscle on full_body workouts', () => {
1322
+ it('allows every focus muscle on full_body routines', () => {
1310
1323
  const muscles = ['chest', 'shoulders', 'arms', 'legs', 'back'];
1311
1324
  muscles.forEach((focusMuscle) => {
1312
1325
  // Use a muscle that's in the simplified group: biceps for arms, chest
@@ -155,7 +155,6 @@ class WorkoutBuilder {
155
155
  workoutVisibility: 'public',
156
156
  isWorkoutBiometricsPublic: true,
157
157
  trainerProgramId: undefined,
158
- trainerWorkoutTemplateId: undefined,
159
158
  shareTo: {
160
159
  strava: false,
161
160
  appleHealth: false,
@@ -163,6 +162,7 @@ class WorkoutBuilder {
163
162
  },
164
163
  clientId: '',
165
164
  gymId: undefined,
165
+ isHomeGym: false,
166
166
  });
167
167
  this._exercises = [];
168
168
  }
@@ -36,8 +36,8 @@ export interface NormalizedWorkout {
36
36
  clientId: string;
37
37
  biometrics?: WorkoutBiometrics;
38
38
  trainerProgramId: string | undefined;
39
- trainerWorkoutTemplateId: string | undefined;
40
39
  gymId: string | undefined;
40
+ isHomeGym: boolean;
41
41
  }
42
42
  export interface NormalizedSet {
43
43
  index: number;
@@ -39,8 +39,8 @@ export interface PostWorkoutRequestWorkout {
39
39
  biometrics?: WorkoutBiometrics;
40
40
  is_biometrics_public: boolean;
41
41
  trainer_program_id: string | undefined;
42
- trainer_workout_template_id: string | undefined;
43
42
  gym_id: string | undefined;
43
+ is_home_gym: boolean;
44
44
  }
45
45
  export interface PostWorkoutRequestExercise {
46
46
  title: string;
@@ -46,7 +46,6 @@ export interface UserWorkout {
46
46
  biometrics?: WorkoutBiometrics;
47
47
  is_biometrics_public: boolean;
48
48
  trainer_program_id: string | undefined;
49
- trainer_workout_template_id: string | undefined;
50
49
  gym: UserWorkoutGym | undefined;
51
50
  }
52
51
  export interface UserWorkoutExercise {
@@ -124,8 +123,5 @@ type CommercialGym = {
124
123
  name: string;
125
124
  city: string;
126
125
  };
127
- type HomeGym = {
128
- type: 'home';
129
- };
130
- export type UserWorkoutGym = CommercialGym | HomeGym;
126
+ export type UserWorkoutGym = CommercialGym;
131
127
  export {};
@@ -46,8 +46,8 @@ export interface OwnedWorkout {
46
46
  biometrics?: WorkoutBiometrics;
47
47
  is_biometrics_public: boolean;
48
48
  trainer_program_id: string | undefined;
49
- trainer_workout_template_id: string | undefined;
50
49
  gym_id: string | undefined;
50
+ is_home_gym: boolean;
51
51
  }
52
52
  export interface TrainerWorkout extends OwnedWorkout {
53
53
  trainer_program_id: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hevy-shared",
3
- "version": "1.0.1039",
3
+ "version": "1.0.1040",
4
4
  "description": "",
5
5
  "main": "built/index.js",
6
6
  "types": "built/index.d.ts",