hevy-shared 1.0.1045 → 1.0.1047

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.
@@ -13,6 +13,7 @@ export interface RequestConfig {
13
13
  baseURL?: string;
14
14
  params?: unknown;
15
15
  signal?: AbortSignal;
16
+ timeout?: number;
16
17
  }
17
18
  export type HTTPMethod = keyof {
18
19
  [K in keyof HTTPClient as HTTPClient[K] extends (...args: any) => Promise<HTTPResponse<never>> ? K : never]: never;
@@ -1,6 +1,6 @@
1
- import { WeeklyTrainingFrequency, TrainingGoal, TrainingLevel, SimplifiedMuscleGroup, MuscleGroup, LibraryExercise, ExerciseCategory, GranularEquipment, HevyTrainerProgramEquipment, RestTimerLength, CardioPreference, StrictRepRange } 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,38 +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 interface TrainerProgramWorkoutTemplate {
185
- name: TrainerWorkoutTemplateName;
182
+ export interface TrainerProgramRoutine {
183
+ name: HevyTrainerRoutineName;
186
184
  exercises: TrainerProgramExercise[];
185
+ notes?: string;
187
186
  }
188
- export interface GeneratedTrainerProgram {
189
- workoutTemplates: TrainerProgramWorkoutTemplate[];
187
+ export interface TrainerProgram {
188
+ name: WeeklyTrainingFrequency;
189
+ routines: TrainerProgramRoutine[];
190
190
  }
191
191
  export declare const getTrainerSetCount: (trainerAlgorithmSettings: TrainerAlgorithmSettings, goal: TrainingGoal, frequency: WeeklyTrainingFrequency) => number;
192
- export declare const getTrainerRepRange: (trainerAlgorithmSettings: TrainerAlgorithmSettings, goal: TrainingGoal, exerciseCategory: HevyTrainerExerciseCategory) => AlgorithmRepRange;
192
+ export declare const getTrainerRepRange: (trainerAlgorithmSettings: TrainerAlgorithmSettings, goal: TrainingGoal, exerciseCategory: HevyTrainerExerciseCategory) => RepRange;
193
193
  export declare const getTrainerRestTimerSeconds: (trainerAlgorithmSettings: TrainerAlgorithmSettings, goal: TrainingGoal, length: RestTimerLength, exerciseCategory: HevyTrainerExerciseCategory) => number;
194
194
  /**
195
195
  * Normalizes the exercise category to a HevyTrainerExerciseCategory
@@ -243,14 +243,23 @@ export interface ExercisePrescriptionError {
243
243
  focusMuscle?: SimplifiedMuscleGroup;
244
244
  };
245
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;
246
255
  export interface TrainerProgramResult {
247
256
  success: true;
248
- program: GeneratedTrainerProgram;
257
+ program: TrainerProgram;
249
258
  }
250
259
  export interface TrainerProgramError {
251
260
  success: false;
252
261
  errors: ExercisePrescriptionError[];
253
- partialProgram: GeneratedTrainerProgram;
262
+ partialProgram: TrainerProgram;
254
263
  }
255
264
  export type TrainerProgramAttempt = TrainerProgramResult | TrainerProgramError;
256
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.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,8 +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
204
  const getTrainerSetCount = (trainerAlgorithmSettings, goal, frequency) => {
207
205
  return trainerAlgorithmSettings.sets[exports.frequencyMap[frequency]][goal];
208
206
  };
@@ -293,12 +291,12 @@ const MAX_BARBELL_EXERCISES_FOR_ONCE_PER_WEEK = 3;
293
291
  * - Only enforce the cap when the user has at least one "barbell substitute" equipment available
294
292
  * (otherwise we allow barbell exercises to avoid running out of viable options)
295
293
  */
296
- const isBarbellExerciseAllowed = (exercise, workoutBarbellExerciseCount, userEquipments, frequency) => {
294
+ const isBarbellExerciseAllowed = (exercise, routineBarbellExerciseCount, userEquipments, frequency) => {
297
295
  const isCandidateBarbell = exercise.equipment_category === 'barbell';
298
296
  const isOncePerWeek = frequency === 1;
299
297
  if (!isCandidateBarbell || !isOncePerWeek)
300
298
  return true;
301
- const isAtOrOverLimit = workoutBarbellExerciseCount >= MAX_BARBELL_EXERCISES_FOR_ONCE_PER_WEEK;
299
+ const isAtOrOverLimit = routineBarbellExerciseCount >= MAX_BARBELL_EXERCISES_FOR_ONCE_PER_WEEK;
302
300
  if (!isAtOrOverLimit)
303
301
  return true;
304
302
  const barbellSubstitutes = [
@@ -316,7 +314,7 @@ const isBarbellExerciseAllowed = (exercise, workoutBarbellExerciseCount, userEqu
316
314
  const isExerciseUsed = (exercise, context) => {
317
315
  var _a, _b, _c;
318
316
  return (((_a = context.programUsedExerciseIds) === null || _a === void 0 ? void 0 : _a.has(exercise.id)) ||
319
- ((_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)) ||
320
318
  ((_c = context.excludedExerciseIds) === null || _c === void 0 ? void 0 : _c.has(exercise.id)) ||
321
319
  false);
322
320
  };
@@ -395,16 +393,16 @@ const getPrioritySortedExercises = (exercisePriorities, exerciseStore) => {
395
393
  return sortedExercises;
396
394
  };
397
395
  exports.getPrioritySortedExercises = getPrioritySortedExercises;
398
- const getMuscleGroup = ({ muscleGroupPrescription, programFocusMuscleExerciseCount, focusMuscle, workoutTemplateName, }) => {
396
+ const getMuscleGroup = ({ muscleGroupPrescription, programFocusMuscleExerciseCount, focusMuscle, routineName, }) => {
399
397
  if (muscleGroupPrescription === 'focus_muscle') {
400
- // 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,
401
399
  // we return the focus muscle extra exercise
402
400
  if (!!focusMuscle &&
403
- isFocusMuscleExtraExerciseAllowed(workoutTemplateName, focusMuscle)) {
401
+ isFocusMuscleExtraExerciseAllowed(routineName, focusMuscle)) {
404
402
  const n = _1.simplifiedMuscleGroupToMuscleGroups[focusMuscle].length;
405
403
  return _1.simplifiedMuscleGroupToMuscleGroups[focusMuscle][programFocusMuscleExerciseCount % n];
406
404
  }
407
- // 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
408
406
  // we skip this extra exercise for the focus muscle in the program
409
407
  return undefined;
410
408
  }
@@ -422,7 +420,7 @@ const isExerciseMatch = (exercise, criteria) => {
422
420
  const levelMatch = (_b = (_a = exercise.level) === null || _a === void 0 ? void 0 : _a.includes(criteria.level)) !== null && _b !== void 0 ? _b : false;
423
421
  const goalMatch = (_d = (_c = exercise.goal) === null || _c === void 0 ? void 0 : _c.includes(criteria.goal)) !== null && _d !== void 0 ? _d : false;
424
422
  const equipmentMatch = (0, exports.isEquipmentCompatible)(exercise, criteria.equipments);
425
- const barbellLimitOk = isBarbellExerciseAllowed(exercise, criteria.workoutBarbellExerciseCount, criteria.equipments, criteria.frequency);
423
+ const barbellLimitOk = isBarbellExerciseAllowed(exercise, criteria.routineBarbellExerciseCount, criteria.equipments, criteria.frequency);
426
424
  return (categoryMatch && levelMatch && goalMatch && equipmentMatch && barbellLimitOk);
427
425
  };
428
426
  /**
@@ -467,8 +465,8 @@ const pickTrainerExerciseWithTrace = (params) => {
467
465
  programUsedExerciseIds: context.programUsedExerciseIds,
468
466
  excludedExerciseIds: context.excludedExerciseIds,
469
467
  };
470
- const workoutContext = {
471
- workoutUsedExerciseIds: context.workoutUsedExerciseIds,
468
+ const routineContext = {
469
+ routineUsedExerciseIds: context.routineUsedExerciseIds,
472
470
  excludedExerciseIds: context.excludedExerciseIds,
473
471
  };
474
472
  // Pass 1
@@ -482,10 +480,10 @@ const pickTrainerExerciseWithTrace = (params) => {
482
480
  if (exercise)
483
481
  return { exercise, trace: Object.assign(Object.assign({}, trace), { selectedPass: 1 }) };
484
482
  // Pass 2
485
- exercise = findMatchingExercise(exercises, criteria, workoutContext);
483
+ exercise = findMatchingExercise(exercises, criteria, routineContext);
486
484
  trace.entries.push({
487
485
  pass: 2,
488
- 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)',
489
487
  candidatePoolSize: exercises.length,
490
488
  selectedExerciseId: exercise === null || exercise === void 0 ? void 0 : exercise.id,
491
489
  });
@@ -502,20 +500,20 @@ const pickTrainerExerciseWithTrace = (params) => {
502
500
  if (exercise)
503
501
  return { exercise, trace: Object.assign(Object.assign({}, trace), { selectedPass: 3 }) };
504
502
  // Pass 4
505
- exercise = findAlternativeIsolationExercise(exercises, criteria, workoutContext);
503
+ exercise = findAlternativeIsolationExercise(exercises, criteria, routineContext);
506
504
  trace.entries.push({
507
505
  pass: 4,
508
- 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)',
509
507
  candidatePoolSize: exercises.length,
510
508
  selectedExerciseId: exercise === null || exercise === void 0 ? void 0 : exercise.id,
511
509
  });
512
510
  if (exercise)
513
511
  return { exercise, trace: Object.assign(Object.assign({}, trace), { selectedPass: 4 }) };
514
512
  // Pass 5
515
- exercise = findMatchingExercise(exercises, Object.assign(Object.assign({}, criteria), { exerciseCategory: 'all' }), workoutContext);
513
+ exercise = findMatchingExercise(exercises, Object.assign(Object.assign({}, criteria), { exerciseCategory: 'all' }), routineContext);
516
514
  trace.entries.push({
517
515
  pass: 5,
518
- 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)',
519
517
  candidatePoolSize: exercises.length,
520
518
  selectedExerciseId: exercise === null || exercise === void 0 ? void 0 : exercise.id,
521
519
  });
@@ -525,10 +523,10 @@ const pickTrainerExerciseWithTrace = (params) => {
525
523
  const secondaryMuscleExercises = Object.values(sortedExercises)
526
524
  .flat()
527
525
  .filter((e) => e.other_muscles.includes(criteria.muscleGroup));
528
- exercise = findMatchingExercise(secondaryMuscleExercises, Object.assign(Object.assign({}, criteria), { exerciseCategory: 'all' }), workoutContext);
526
+ exercise = findMatchingExercise(secondaryMuscleExercises, Object.assign(Object.assign({}, criteria), { exerciseCategory: 'all' }), routineContext);
529
527
  trace.entries.push({
530
528
  pass: 6,
531
- 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)',
532
530
  candidatePoolSize: secondaryMuscleExercises.length,
533
531
  selectedExerciseId: exercise === null || exercise === void 0 ? void 0 : exercise.id,
534
532
  });
@@ -544,11 +542,11 @@ function pickTrainerExercise(params) {
544
542
  }
545
543
  const CARDIO_TRACE_INDEX_BEFORE_TEMPLATE = -1;
546
544
  exports.DEFAULT_TRAINER_CARDIO_ATTACHMENT_DURATION_SECONDS = 600;
547
- /** Inserts optional cardio before or after template exercises for one workout template. */
548
- 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, }) => {
549
547
  if (cardioPreference === 'no-cardio')
550
548
  return;
551
- // 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
552
550
  // intentionally omitted — only the global excluded set applies.
553
551
  const selectionParams = {
554
552
  sortedExercises,
@@ -559,7 +557,7 @@ const attachOptionalCardioToWorkoutTemplate = ({ cardioPreference, sortedExercis
559
557
  equipments: criteria.equipments,
560
558
  muscleGroup: 'cardio',
561
559
  exerciseCategory: 'all',
562
- workoutBarbellExerciseCount: 0,
560
+ routineBarbellExerciseCount: 0,
563
561
  },
564
562
  context: { excludedExerciseIds: criteria.excludedExerciseIds },
565
563
  };
@@ -569,18 +567,17 @@ const attachOptionalCardioToWorkoutTemplate = ({ cardioPreference, sortedExercis
569
567
  return;
570
568
  const slot = {
571
569
  kind: 'cardio',
572
- exercise_template: cardioExercise,
573
- duration_seconds: exports.DEFAULT_TRAINER_CARDIO_ATTACHMENT_DURATION_SECONDS,
574
- set_count: 1,
570
+ exerciseTemplate: cardioExercise,
571
+ durationSeconds: exports.DEFAULT_TRAINER_CARDIO_ATTACHMENT_DURATION_SECONDS,
575
572
  };
576
573
  if (cardioPreference === 'workout-start')
577
- workoutExercises.unshift(slot);
574
+ routineExercises.unshift(slot);
578
575
  else
579
- workoutExercises.push(slot);
576
+ routineExercises.push(slot);
580
577
  if (debugExerciseSelectionTrace) {
581
578
  exerciseSelectionTraces.push({
582
579
  traceKind: 'cardio',
583
- workoutTemplate,
580
+ routine,
584
581
  prescriptionIndex: cardioPreference === 'workout-start'
585
582
  ? CARDIO_TRACE_INDEX_BEFORE_TEMPLATE
586
583
  : templatePrescriptionCount,
@@ -595,27 +592,28 @@ function generateProgram(params) {
595
592
  var _a, _b;
596
593
  const { trainerAlgorithmSettings, frequency, goal, level, equipments, workoutDurationMinutes, restTimerLength, exerciseStore, focusMuscle, excludedExerciseIds, debugExerciseSelectionTrace, cardioPreference, } = params;
597
594
  const exerciseSelectionTraces = [];
598
- const workoutTemplates = exports.programSplits[frequency];
595
+ const routines = exports.programSplits[frequency];
599
596
  const program = {
600
- workoutTemplates: [],
597
+ name: frequency,
598
+ routines: [],
601
599
  };
602
600
  // TODO: Rename sortedExercises to sortedExercisesByMuscleGroup or something similar
603
601
  const sortedExercises = (0, exports.getPrioritySortedExercises)(trainerAlgorithmSettings.exercise_priorities, exerciseStore);
604
602
  const programUsedExerciseIds = new Set();
605
603
  let programFocusMuscleExerciseCount = 0;
606
604
  const allErrors = [];
607
- for (const workout of workoutTemplates) {
608
- const workoutTemplate = trainerAlgorithmSettings.templates[workout];
609
- let workoutBarbellExerciseCount = 0;
610
- const workoutUsedExerciseIds = new Set();
611
- const workoutExercises = [];
612
- for (let prescriptionIndex = 0; prescriptionIndex < workoutTemplate.exercises.length; prescriptionIndex++) {
613
- 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];
614
612
  const muscleGroup = getMuscleGroup({
615
613
  muscleGroupPrescription: exercisePrescription.muscle_group,
616
614
  programFocusMuscleExerciseCount,
617
615
  focusMuscle,
618
- workoutTemplateName: workout,
616
+ routineName: routine,
619
617
  });
620
618
  // If the muscle group is not found, skip the exercise
621
619
  if (!muscleGroup) {
@@ -634,7 +632,7 @@ function generateProgram(params) {
634
632
  exerciseCategory: exercisePrescription.category,
635
633
  equipments,
636
634
  muscleGroup,
637
- workoutBarbellExerciseCount,
635
+ routineBarbellExerciseCount,
638
636
  level,
639
637
  goal,
640
638
  };
@@ -645,7 +643,7 @@ function generateProgram(params) {
645
643
  criteria,
646
644
  context: {
647
645
  programUsedExerciseIds,
648
- workoutUsedExerciseIds,
646
+ routineUsedExerciseIds,
649
647
  excludedExerciseIds,
650
648
  },
651
649
  withTrace: true,
@@ -653,7 +651,7 @@ function generateProgram(params) {
653
651
  exercise = selection.exercise;
654
652
  exerciseSelectionTraces.push({
655
653
  traceKind: 'template',
656
- workoutTemplate: workout,
654
+ routine,
657
655
  prescriptionIndex,
658
656
  prescription: exercisePrescription,
659
657
  resolvedMuscleGroup: muscleGroup,
@@ -668,29 +666,30 @@ function generateProgram(params) {
668
666
  criteria,
669
667
  context: {
670
668
  programUsedExerciseIds,
671
- workoutUsedExerciseIds,
669
+ routineUsedExerciseIds,
672
670
  excludedExerciseIds,
673
671
  },
674
672
  });
675
673
  }
676
674
  if (!!exercise) {
677
675
  programUsedExerciseIds.add(exercise.id);
678
- workoutUsedExerciseIds.add(exercise.id);
676
+ routineUsedExerciseIds.add(exercise.id);
679
677
  if (exercise.equipment_category === 'barbell')
680
- workoutBarbellExerciseCount++;
678
+ routineBarbellExerciseCount++;
681
679
  if (exercisePrescription.muscle_group === 'focus_muscle')
682
680
  programFocusMuscleExerciseCount++;
683
681
  const repRange = (0, exports.getTrainerRepRange)(trainerAlgorithmSettings, goal, exercisePrescription.category);
684
- workoutExercises.push({
682
+ routineExercises.push({
685
683
  kind: 'resistance',
686
- exercise_template: exercise,
687
- set_count: (0, exports.getTrainerSetCount)(trainerAlgorithmSettings, goal, frequency),
688
- warmup_set_count: (_b = exercisePrescription.warmup_set_count) !== null && _b !== void 0 ? _b : 0,
689
- rep_range: {
690
- start: repRange.rep_range_start,
691
- end: repRange.rep_range_end,
692
- },
693
- 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 : '',
694
693
  });
695
694
  }
696
695
  else {
@@ -708,21 +707,23 @@ function generateProgram(params) {
708
707
  });
709
708
  }
710
709
  }
711
- attachOptionalCardioToWorkoutTemplate({
710
+ attachOptionalCardioToRoutine({
712
711
  cardioPreference,
713
712
  sortedExercises,
714
713
  criteria: { equipments, level, goal, frequency, excludedExerciseIds },
715
714
  debugExerciseSelectionTrace,
716
715
  exerciseSelectionTraces,
717
- workoutTemplate: workout,
718
- templatePrescriptionCount: workoutTemplate.exercises.length,
719
- workoutExercises,
716
+ routine,
717
+ templatePrescriptionCount: routineTemplate.exercises.length,
718
+ routineExercises,
720
719
  });
721
- program.workoutTemplates.push({
722
- name: workout,
723
- exercises: workoutExercises,
720
+ program.routines.push({
721
+ name: routine,
722
+ notes: routineTemplate.notes,
723
+ exercises: routineExercises,
724
724
  });
725
725
  }
726
+ // Return result based on whether there were errors
726
727
  if (allErrors.length > 0) {
727
728
  const result = {
728
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,8 @@ 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
+ email_consent: boolean;
306
307
  }
307
308
  export interface BackofficeDeletedUserResponse {
308
309
  state: 'deleted-account';
@@ -747,10 +748,6 @@ export interface RepRange {
747
748
  start: number | null;
748
749
  end: number | null;
749
750
  }
750
- export interface StrictRepRange extends RepRange {
751
- start: number;
752
- end: number;
753
- }
754
751
  export interface WorkoutComment {
755
752
  id: number;
756
753
  username: string;
@@ -879,14 +876,12 @@ export interface Routine extends BaseRoutine {
879
876
  username: string;
880
877
  coach_id: null;
881
878
  }
882
- /** @deprecated Use TrainerWorkoutTemplate instead */
883
879
  export interface HevyTrainerRoutine extends Routine {
884
880
  hevy_trainer_program_id: string;
885
881
  program_id: null;
886
882
  coach_force_rpe_enabled: false;
887
883
  folder_id: null;
888
884
  }
889
- /** @deprecated Use TrainerWorkoutTemplate instead */
890
885
  export declare const isHevyTrainerRoutine: (routine: BaseRoutine) => routine is HevyTrainerRoutine;
891
886
  export type CoachsShallowLibraryRoutine = Omit<BaseRoutine, 'exercises' | 'profile_pic' | 'hevy_trainer_program_id' | 'username'>;
892
887
  export interface CoachesRoutine extends BaseRoutine {
@@ -1055,105 +1050,6 @@ export interface GetTeamInviteResponse {
1055
1050
  export interface OutstandingInvitesForCoachTeamResponse {
1056
1051
  invites: CoachTeamInvite[];
1057
1052
  }
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
- * Whether to delete (archive) the caller's currently active Hevy Trainer
1133
- * program before creating the new one. Creating a program implicitly
1134
- * replaces any active program, so this must be set explicitly:
1135
- * - `false` — fail with `ActiveProgramExists` (409) if one already exists.
1136
- * - `true` — archive the existing active program, then create the new one.
1137
- */
1138
- deleteActiveProgram: boolean;
1139
- }
1140
- export interface UpdateTrainerProgramV3RequestBody {
1141
- program: {
1142
- version: 3;
1143
- programId: string;
1144
- level: TrainingLevel;
1145
- goal: TrainingGoal;
1146
- equipments: GranularEquipment[];
1147
- weekly_frequency: WeeklyTrainingFrequency;
1148
- focus_muscle?: SimplifiedMuscleGroup;
1149
- next_workout_index?: number;
1150
- workout_duration_minutes: WorkoutDurationMinutes;
1151
- rest_timer_length: RestTimerLength;
1152
- cardio_preference: CardioPreference;
1153
- templates: UpdateTrainerWorkoutTemplate[];
1154
- };
1155
- }
1156
- /** @deprecated Use TrainerProgramV3 instead */
1157
1053
  export interface HevyTrainerProgram {
1158
1054
  id: string;
1159
1055
  created_at: string;
@@ -1170,7 +1066,6 @@ export interface HevyTrainerProgram {
1170
1066
  rest_timer_length: RestTimerLength;
1171
1067
  cardio_preference: CardioPreference;
1172
1068
  }
1173
- /** @deprecated Use PostTrainerProgramV3RequestBody instead */
1174
1069
  export interface PostHevyTrainerProgramRequestBody {
1175
1070
  program: {
1176
1071
  version: 1;
@@ -1187,7 +1082,6 @@ export interface PostHevyTrainerProgramRequestBody {
1187
1082
  cardio_preference: CardioPreference;
1188
1083
  };
1189
1084
  }
1190
- /** @deprecated Use UpdateTrainerProgramV3RequestBody instead */
1191
1085
  export interface UpdateHevyTrainerProgramRequestBody {
1192
1086
  program: {
1193
1087
  version: 1;
@@ -1210,7 +1104,7 @@ export interface UpdateHevyTrainerProgramRequestBody {
1210
1104
  }[];
1211
1105
  };
1212
1106
  }
1213
- /** @deprecated Use TrainerProgramV3 instead */
1107
+ /** @deprecated Use HevyTrainerProgram instead */
1214
1108
  export interface HevyTrainerProgramOld {
1215
1109
  id: string;
1216
1110
  created_at: string;
@@ -1225,7 +1119,7 @@ export interface HevyTrainerProgramOld {
1225
1119
  next_workout_index: number;
1226
1120
  workout_duration_minutes?: WorkoutDurationMinutes;
1227
1121
  }
1228
- /** @deprecated Use PostTrainerProgramV3RequestBody instead */
1122
+ /** @deprecated Use PostHevyTrainerProgramRequestBody instead */
1229
1123
  export interface PostHevyTrainerProgramOldRequestBody {
1230
1124
  program: {
1231
1125
  title: string;
@@ -1239,7 +1133,7 @@ export interface PostHevyTrainerProgramOldRequestBody {
1239
1133
  workout_duration_minutes?: WorkoutDurationMinutes;
1240
1134
  };
1241
1135
  }
1242
- /** @deprecated Use UpdateTrainerProgramV3RequestBody instead */
1136
+ /** @deprecated Use UpdateHevyTrainerProgramRequestBody instead */
1243
1137
  export interface UpdateHevyTrainerProgramOldRequestBody {
1244
1138
  program: {
1245
1139
  programId: string;
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,
@@ -36,7 +36,6 @@ 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;
41
40
  isHomeGym: boolean;
42
41
  }
@@ -39,7 +39,6 @@ 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;
44
43
  is_home_gym: boolean;
45
44
  }
@@ -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 {
@@ -46,7 +46,6 @@ 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;
51
50
  is_home_gym: boolean;
52
51
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hevy-shared",
3
- "version": "1.0.1045",
3
+ "version": "1.0.1047",
4
4
  "description": "",
5
5
  "main": "built/index.js",
6
6
  "types": "built/index.d.ts",