hevy-shared 1.0.994 → 1.0.996

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 } 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[];
@@ -87,24 +87,24 @@ export interface CardioExerciseSelectionTraceRecord {
87
87
  trace: ExerciseSelectionTrace;
88
88
  }
89
89
  export type ProgramExerciseSelectionTraceRecord = TemplateExerciseSelectionTraceRecord | CardioExerciseSelectionTraceRecord;
90
- export type TrainerProgramAttemptWithTraces<T> = TrainerProgramAttempt<T> & {
90
+ export type TrainerProgramAttemptWithTraces = TrainerProgramAttempt & {
91
91
  exerciseSelectionTraces: ProgramExerciseSelectionTraceRecord[];
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;
@@ -138,7 +138,7 @@ export interface WorkoutTemplate {
138
138
  notes?: string;
139
139
  }
140
140
  export type Templates = {
141
- [key in TrainerWorkoutTemplateName]: WorkoutTemplate;
141
+ [key in HevyTrainerRoutineName]: WorkoutTemplate;
142
142
  };
143
143
  export interface BackofficeTrainerPreset {
144
144
  id?: number;
@@ -157,38 +157,39 @@ export interface TrainerAlgorithmSettings {
157
157
  exercise_notes: ExerciseNotes;
158
158
  exercise_replacements: ExerciseReplacements;
159
159
  }
160
- export interface TrainerProgramResistanceExercise<T> {
160
+ /** Resistance prescriptions from templates (sets, reps, rest, notes). */
161
+ export interface TrainerProgramResistanceExercise {
161
162
  kind: 'resistance';
162
- exercise_template: T;
163
- set_count: number;
164
- warmup_set_count: number;
165
- rep_range: StrictRepRange;
166
- 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;
167
172
  }
168
- export interface TrainerProgramCardioExercise<T> {
173
+ /** Optional cardio attachment — no prescription semantics beyond exercise choice. */
174
+ export interface TrainerProgramCardioExercise {
169
175
  kind: 'cardio';
170
- exercise_template: T;
171
- set_count: 1;
172
- duration_seconds: number;
176
+ exerciseTemplate: HevyTrainerLibraryExercise;
177
+ durationSeconds: number;
173
178
  }
174
- export interface TrainerProgramOtherExercise<T> {
175
- kind: 'other';
176
- exercise_template: T;
177
- set_count: number;
178
- }
179
- export type TrainerProgramExercise<T> = TrainerProgramResistanceExercise<T> | TrainerProgramCardioExercise<T> | TrainerProgramOtherExercise<T>;
180
- export declare const isTrainerProgramResistanceExercise: <T>(exercise: TrainerProgramExercise<T>) => exercise is TrainerProgramResistanceExercise<T>;
181
- export declare const isTrainerProgramCardioExercise: <T>(exercise: TrainerProgramExercise<T>) => exercise is TrainerProgramCardioExercise<T>;
182
- export declare const isTrainerProgramOtherExercise: <T>(exercise: TrainerProgramExercise<T>) => exercise is TrainerProgramOtherExercise<T>;
183
- export interface TrainerProgramWorkoutTemplate<T> {
184
- name: TrainerWorkoutTemplateName;
185
- exercises: TrainerProgramExercise<T>[];
179
+ export type TrainerProgramExercise = TrainerProgramResistanceExercise | TrainerProgramCardioExercise;
180
+ export declare const isTrainerProgramResistanceExercise: (exercise: TrainerProgramExercise) => exercise is TrainerProgramResistanceExercise;
181
+ export declare const isTrainerProgramCardioExercise: (exercise: TrainerProgramExercise) => exercise is TrainerProgramCardioExercise;
182
+ export interface TrainerProgramRoutine {
183
+ name: HevyTrainerRoutineName;
184
+ exercises: TrainerProgramExercise[];
185
+ notes?: string;
186
186
  }
187
- export interface GeneratedTrainerProgram<T> {
188
- workoutTemplates: TrainerProgramWorkoutTemplate<T>[];
187
+ export interface TrainerProgram {
188
+ name: WeeklyTrainingFrequency;
189
+ routines: TrainerProgramRoutine[];
189
190
  }
190
191
  export declare const getTrainerSetCount: (trainerAlgorithmSettings: TrainerAlgorithmSettings, goal: TrainingGoal, frequency: WeeklyTrainingFrequency) => number;
191
- export declare const getTrainerRepRange: (trainerAlgorithmSettings: TrainerAlgorithmSettings, goal: TrainingGoal, exerciseCategory: HevyTrainerExerciseCategory) => AlgorithmRepRange;
192
+ export declare const getTrainerRepRange: (trainerAlgorithmSettings: TrainerAlgorithmSettings, goal: TrainingGoal, exerciseCategory: HevyTrainerExerciseCategory) => RepRange;
192
193
  export declare const getTrainerRestTimerSeconds: (trainerAlgorithmSettings: TrainerAlgorithmSettings, goal: TrainingGoal, length: RestTimerLength, exerciseCategory: HevyTrainerExerciseCategory) => number;
193
194
  /**
194
195
  * Normalizes the exercise category to a HevyTrainerExerciseCategory
@@ -242,16 +243,25 @@ export interface ExercisePrescriptionError {
242
243
  focusMuscle?: SimplifiedMuscleGroup;
243
244
  };
244
245
  }
245
- export interface TrainerProgramResult<T> {
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;
255
+ export interface TrainerProgramResult {
246
256
  success: true;
247
- program: GeneratedTrainerProgram<T>;
257
+ program: TrainerProgram;
248
258
  }
249
- export interface TrainerProgramError<T> {
259
+ export interface TrainerProgramError {
250
260
  success: false;
251
261
  errors: ExercisePrescriptionError[];
252
- partialProgram: GeneratedTrainerProgram<T>;
262
+ partialProgram: TrainerProgram;
253
263
  }
254
- export type TrainerProgramAttempt<T> = TrainerProgramResult<T> | TrainerProgramError<T>;
264
+ export type TrainerProgramAttempt = TrainerProgramResult | TrainerProgramError;
255
265
  /**
256
266
  * Generates a complete training program based on the provided parameters
257
267
  * - debugExerciseSelectionTrace: true -> includes exercise selection trace breadcrumbs in the result for debugging.
@@ -259,6 +269,6 @@ export type TrainerProgramAttempt<T> = TrainerProgramResult<T> | TrainerProgramE
259
269
  */
260
270
  export declare function generateProgram<T extends HevyTrainerLibraryExercise>(params: ProgramGenerationParams<T> & {
261
271
  debugExerciseSelectionTrace: true;
262
- }): TrainerProgramAttemptWithTraces<T>;
263
- export declare function generateProgram<T extends HevyTrainerLibraryExercise>(params: ProgramGenerationParams<T>): TrainerProgramAttempt<T>;
272
+ }): TrainerProgramAttemptWithTraces;
273
+ export declare function generateProgram<T extends HevyTrainerLibraryExercise>(params: ProgramGenerationParams<T>): TrainerProgramAttempt;
264
274
  export {};
@@ -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 { postEmailBackofficeSchema } from './schemas';
4
4
  import { Language } from './translations';
5
5
  import { Lookup } from './typeUtils';
@@ -289,6 +289,7 @@ export interface BackofficeExistingUserResponse {
289
289
  last_workout_at: string;
290
290
  coach_client_status?: 'coached-active-plan' | 'coached-inactive-plan';
291
291
  coach_status?: 'coach-active-plan' | 'coach-inactive-plan';
292
+ coach_username?: string;
292
293
  notes: string | null;
293
294
  is_banned: boolean;
294
295
  metadata: UserMetadataResponse | null;
@@ -299,7 +300,7 @@ export interface BackofficeExistingUserResponse {
299
300
  public_api_key: string | null;
300
301
  limited_discovery: boolean;
301
302
  coach_trial_expire_date?: string;
302
- hevy_trainer_program: TrainerProgramV3 | null;
303
+ hevy_trainer_program: HevyTrainerProgram | null;
303
304
  }
304
305
  export interface BackofficeDeletedUserResponse {
305
306
  state: 'deleted-account';
@@ -744,10 +745,6 @@ export interface RepRange {
744
745
  start: number | null;
745
746
  end: number | null;
746
747
  }
747
- export interface StrictRepRange extends RepRange {
748
- start: number;
749
- end: number;
750
- }
751
748
  export interface WorkoutExerciseSet {
752
749
  /**
753
750
  * id can be a string or a number because we used
@@ -891,7 +888,6 @@ export interface Workout {
891
888
  biometrics?: WorkoutBiometrics;
892
889
  is_biometrics_public: boolean;
893
890
  trainer_program_id: string | undefined;
894
- trainer_workout_template_id: string | undefined;
895
891
  }
896
892
  export interface CustomExerciseImage {
897
893
  type: 'image';
@@ -971,7 +967,6 @@ export interface PostWorkoutRequestWorkout {
971
967
  biometrics?: WorkoutBiometrics;
972
968
  is_biometrics_public: boolean;
973
969
  trainer_program_id: string | undefined;
974
- trainer_workout_template_id: string | undefined;
975
970
  }
976
971
  export type WorkoutDataImporterReport = {
977
972
  state: 'idle';
@@ -1099,14 +1094,12 @@ export interface Routine extends BaseRoutine {
1099
1094
  username: string;
1100
1095
  coach_id: null;
1101
1096
  }
1102
- /** @deprecated Use TrainerProgramWorkoutTemplate instead */
1103
1097
  export interface HevyTrainerRoutine extends Routine {
1104
1098
  hevy_trainer_program_id: string;
1105
1099
  program_id: null;
1106
1100
  coach_force_rpe_enabled: false;
1107
1101
  folder_id: null;
1108
1102
  }
1109
- /** @deprecated Use TrainerProgramWorkoutTemplate instead */
1110
1103
  export declare const isHevyTrainerRoutine: (routine: BaseRoutine) => routine is HevyTrainerRoutine;
1111
1104
  export type CoachsShallowLibraryRoutine = Omit<BaseRoutine, 'exercises' | 'profile_pic' | 'hevy_trainer_program_id' | 'username'>;
1112
1105
  export interface CoachesRoutine extends BaseRoutine {
@@ -1275,91 +1268,6 @@ export interface GetTeamInviteResponse {
1275
1268
  export interface OutstandingInvitesForCoachTeamResponse {
1276
1269
  invites: CoachTeamInvite[];
1277
1270
  }
1278
- export interface TrainerWorkoutTemplateResistanceExercise {
1279
- kind: 'resistance';
1280
- id: string;
1281
- exercise_template_id: string;
1282
- index: number;
1283
- set_count: number;
1284
- warmup_set_count: number;
1285
- rep_range: StrictRepRange;
1286
- rest_seconds: number;
1287
- }
1288
- export interface TrainerWorkoutTemplateCardioExercise {
1289
- kind: 'cardio';
1290
- id: string;
1291
- exercise_template_id: string;
1292
- index: number;
1293
- set_count: 1;
1294
- duration_seconds: number;
1295
- }
1296
- export interface TrainerWorkoutTemplateOtherExercise {
1297
- kind: 'other';
1298
- id: string;
1299
- exercise_template_id: string;
1300
- index: number;
1301
- set_count: number;
1302
- }
1303
- export type TrainerWorkoutTemplateExercise = TrainerWorkoutTemplateResistanceExercise | TrainerWorkoutTemplateCardioExercise | TrainerWorkoutTemplateOtherExercise;
1304
- export interface TrainerWorkoutTemplate {
1305
- id: string;
1306
- hevy_trainer_program_id: string;
1307
- index: number;
1308
- name: TrainerWorkoutTemplateName;
1309
- exercises: TrainerWorkoutTemplateExercise[];
1310
- }
1311
- export interface TrainerProgramV3 {
1312
- id: string;
1313
- schema_version: 'v3';
1314
- created_at: string;
1315
- updated_at: string;
1316
- level: TrainingLevel;
1317
- goal: TrainingGoal;
1318
- equipments: GranularEquipment[];
1319
- weekly_frequency: WeeklyTrainingFrequency;
1320
- templates: TrainerWorkoutTemplate[];
1321
- focus_muscle?: SimplifiedMuscleGroup;
1322
- next_workout_index: number;
1323
- workout_duration_minutes: WorkoutDurationMinutes;
1324
- rest_timer_length: RestTimerLength;
1325
- cardio_preference: CardioPreference;
1326
- }
1327
- export interface TrainerWorkoutTemplateUpdate extends Pick<TrainerWorkoutTemplate, 'name' | 'index'> {
1328
- exercises: Omit<TrainerWorkoutTemplateExercise, 'id'>[];
1329
- }
1330
- export interface PostTrainerProgramV3RequestBody {
1331
- program: {
1332
- version: 3;
1333
- title: string;
1334
- level: TrainingLevel;
1335
- goal: TrainingGoal;
1336
- equipments: GranularEquipment[];
1337
- weekly_frequency: WeeklyTrainingFrequency;
1338
- focus_muscle?: SimplifiedMuscleGroup;
1339
- next_workout_index?: number;
1340
- workout_duration_minutes: WorkoutDurationMinutes;
1341
- rest_timer_length: RestTimerLength;
1342
- cardio_preference: CardioPreference;
1343
- templates: TrainerWorkoutTemplateUpdate[];
1344
- };
1345
- }
1346
- export interface UpdateTrainerProgramV3RequestBody {
1347
- program: {
1348
- version: 3;
1349
- programId: string;
1350
- level: TrainingLevel;
1351
- goal: TrainingGoal;
1352
- equipments: GranularEquipment[];
1353
- weekly_frequency: WeeklyTrainingFrequency;
1354
- focus_muscle?: SimplifiedMuscleGroup;
1355
- next_workout_index?: number;
1356
- workout_duration_minutes: WorkoutDurationMinutes;
1357
- rest_timer_length: RestTimerLength;
1358
- cardio_preference: CardioPreference;
1359
- templates: TrainerWorkoutTemplateUpdate[];
1360
- };
1361
- }
1362
- /** @deprecated Use TrainerProgramV3 instead */
1363
1271
  export interface HevyTrainerProgram {
1364
1272
  id: string;
1365
1273
  created_at: string;
@@ -1376,7 +1284,6 @@ export interface HevyTrainerProgram {
1376
1284
  rest_timer_length: RestTimerLength;
1377
1285
  cardio_preference: CardioPreference;
1378
1286
  }
1379
- /** @deprecated Use PostTrainerProgramV3RequestBody instead */
1380
1287
  export interface PostHevyTrainerProgramRequestBody {
1381
1288
  program: {
1382
1289
  version: 1;
@@ -1393,7 +1300,6 @@ export interface PostHevyTrainerProgramRequestBody {
1393
1300
  cardio_preference: CardioPreference;
1394
1301
  };
1395
1302
  }
1396
- /** @deprecated Use UpdateTrainerProgramV3RequestBody instead */
1397
1303
  export interface UpdateHevyTrainerProgramRequestBody {
1398
1304
  program: {
1399
1305
  version: 1;
@@ -1416,7 +1322,7 @@ export interface UpdateHevyTrainerProgramRequestBody {
1416
1322
  }[];
1417
1323
  };
1418
1324
  }
1419
- /** @deprecated Use TrainerProgramV3 instead */
1325
+ /** @deprecated Use HevyTrainerProgram instead */
1420
1326
  export interface HevyTrainerProgramOld {
1421
1327
  id: string;
1422
1328
  created_at: string;
@@ -1431,7 +1337,7 @@ export interface HevyTrainerProgramOld {
1431
1337
  next_workout_index: number;
1432
1338
  workout_duration_minutes?: WorkoutDurationMinutes;
1433
1339
  }
1434
- /** @deprecated Use PostTrainerProgramV3RequestBody instead */
1340
+ /** @deprecated Use PostHevyTrainerProgramRequestBody instead */
1435
1341
  export interface PostHevyTrainerProgramOldRequestBody {
1436
1342
  program: {
1437
1343
  title: string;
@@ -1445,7 +1351,7 @@ export interface PostHevyTrainerProgramOldRequestBody {
1445
1351
  workout_duration_minutes?: WorkoutDurationMinutes;
1446
1352
  };
1447
1353
  }
1448
- /** @deprecated Use UpdateTrainerProgramV3RequestBody instead */
1354
+ /** @deprecated Use UpdateHevyTrainerProgramRequestBody instead */
1449
1355
  export interface UpdateHevyTrainerProgramOldRequestBody {
1450
1356
  program: {
1451
1357
  programId: string;
package/built/index.js CHANGED
@@ -324,7 +324,6 @@ const isWorkoutBiometrics = (x) => {
324
324
  return caloriesAreValid && heartSamplesAreValid;
325
325
  };
326
326
  exports.isWorkoutBiometrics = isWorkoutBiometrics;
327
- /** @deprecated Use TrainerProgramWorkoutTemplate instead */
328
327
  const isHevyTrainerRoutine = (routine) => routine.hevy_trainer_program_id !== null;
329
328
  exports.isHevyTrainerRoutine = isHevyTrainerRoutine;
330
329
  exports.measurementsList = [
@@ -36,7 +36,6 @@ export interface NormalizedWorkout {
36
36
  clientId: string;
37
37
  biometrics?: WorkoutBiometrics;
38
38
  trainerProgramId: string | undefined;
39
- trainerWorkoutTemplateId?: string;
40
39
  }
41
40
  export interface NormalizedSet {
42
41
  index: number;
@@ -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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hevy-shared",
3
- "version": "1.0.994",
3
+ "version": "1.0.996",
4
4
  "description": "",
5
5
  "main": "built/index.js",
6
6
  "types": "built/index.d.ts",