Add library exercises mid-workout with plan defaults

Pick any exercise from the full library while a workout is running, not
just the ones in its split. The new exercise's plan is seeded from the
most recent log of that exercise, else the library's authored Defaults
line, else a plain 3x10. Adds a searchable two-section picker sheet and
a **Defaults:** bullet to all 47 library reference pages.

Claude-Session: https://claude.ai/code/session_01HJDQQDA9QdP8zByg43H5v3
This commit is contained in:
2026-07-08 07:51:30 -04:00
parent 29f04a878a
commit a93cbc30b7
98 changed files with 321 additions and 50 deletions
+1
View File
@@ -7,6 +7,7 @@ supported.
- **Type:** Machine-based trunk flexion - **Type:** Machine-based trunk flexion
- **Targets:** Rectus abdominis, deep core - **Targets:** Rectus abdominis, deep core
- **Prescription:** 4 × 10 - **Prescription:** 4 × 10
- **Defaults:** 4 × 10 weighted
## Setup ## Setup
+1
View File
@@ -7,6 +7,7 @@ hips and glutes.
- **Type:** Machine-based hip abduction - **Type:** Machine-based hip abduction
- **Targets:** Gluteus medius, gluteus minimus, outer thighs - **Targets:** Gluteus medius, gluteus minimus, outer thighs
- **Prescription:** 4 × 10 - **Prescription:** 4 × 10
- **Defaults:** 4 × 10 weighted
## Setup ## Setup
+1
View File
@@ -7,6 +7,7 @@ inner thighs.
- **Type:** Machine-based hip adduction - **Type:** Machine-based hip adduction
- **Targets:** Adductors, inner thighs - **Targets:** Adductors, inner thighs
- **Prescription:** 4 × 10 - **Prescription:** 4 × 10
- **Defaults:** 4 × 10 weighted
## Setup ## Setup
+1
View File
@@ -7,6 +7,7 @@ the pad so only the forearms move.
- **Type:** Machine-based elbow flexion (isolation) - **Type:** Machine-based elbow flexion (isolation)
- **Targets:** Biceps brachii, brachialis - **Targets:** Biceps brachii, brachialis
- **Prescription:** 4 × 10 - **Prescription:** 4 × 10
- **Defaults:** 4 × 10 weighted
## Setup ## Setup
+1
View File
@@ -7,6 +7,7 @@ the trunk back in line with the legs.
- **Type:** Machine-based back extension (hip hinge) - **Type:** Machine-based back extension (hip hinge)
- **Targets:** Spinal erectors, glutes, hamstrings - **Targets:** Spinal erectors, glutes, hamstrings
- **Prescription:** 3 × 1015 - **Prescription:** 3 × 1015
- **Defaults:** 3 × 10 weighted
## Setup ## Setup
+1
View File
@@ -7,6 +7,7 @@ heels, stand back up.
- **Type:** Free-weight compound squat - **Type:** Free-weight compound squat
- **Targets:** Quadriceps, glutes, adductors, trunk - **Targets:** Quadriceps, glutes, adductors, trunk
- **Prescription:** 4 × 68 - **Prescription:** 4 × 68
- **Defaults:** 4 × 6 weighted
## Setup ## Setup
+1
View File
@@ -7,6 +7,7 @@ and press it back to straight arms.
- **Type:** Free-weight horizontal press - **Type:** Free-weight horizontal press
- **Targets:** Chest, front delts, triceps - **Targets:** Chest, front delts, triceps
- **Prescription:** 4 × 68 - **Prescription:** 4 × 68
- **Defaults:** 4 × 6 weighted
## Setup ## Setup
+1
View File
@@ -7,6 +7,7 @@ twist.
- **Type:** Anti-rotation - **Type:** Anti-rotation
- **Targets:** Spinal erectors, glutes, deep core, shoulder stability - **Targets:** Spinal erectors, glutes, deep core, shoulder stability
- **Prescription:** Warm-up 8 per side; main circuit 3 × 810 per side, slow - **Prescription:** Warm-up 8 per side; main circuit 3 × 810 per side, slow
- **Defaults:** 3 × 8 bodyweight
## Setup ## Setup
@@ -7,6 +7,7 @@ sit down between the heels. The pattern every loaded squat is built on.
- **Type:** Bodyweight squat - **Type:** Bodyweight squat
- **Targets:** Quadriceps, glutes, mobility - **Targets:** Quadriceps, glutes, mobility
- **Prescription:** 3 × 1520, or 10 slow reps as a warm-up - **Prescription:** 3 × 1520, or 10 slow reps as a warm-up
- **Defaults:** 3 × 15 bodyweight
## Setup ## Setup
+1
View File
@@ -7,6 +7,7 @@ a hard squeeze at the top.
- **Type:** Machine-based calf raise - **Type:** Machine-based calf raise
- **Targets:** Soleus, gastrocnemius - **Targets:** Soleus, gastrocnemius
- **Prescription:** 4 × 10 - **Prescription:** 4 × 10
- **Defaults:** 4 × 10 weighted
## Setup ## Setup
+1
View File
@@ -6,6 +6,7 @@ Gentle spinal mobility drill that wakes up the whole trunk before core work.
- **Type:** Spinal mobility - **Type:** Spinal mobility
- **Targets:** Thoracic and lumbar spine, deep core awareness - **Targets:** Thoracic and lumbar spine, deep core awareness
- **Prescription:** 10 slow reps (one rep = cat + cow) - **Prescription:** 10 slow reps (one rep = cat + cow)
- **Defaults:** 3 × 10 bodyweight
## Setup ## Setup
+1
View File
@@ -6,6 +6,7 @@ forward from the chest.
- **Category:** Main circuit - **Category:** Main circuit
- **Type:** Machine-based horizontal press - **Type:** Machine-based horizontal press
- **Targets:** Pectorals, anterior deltoids, triceps - **Targets:** Pectorals, anterior deltoids, triceps
- **Defaults:** 4 × 10 weighted
## Setup ## Setup
+1
View File
@@ -7,6 +7,7 @@ lower. Short range, all abs.
- **Type:** Spinal flexion (upper) - **Type:** Spinal flexion (upper)
- **Targets:** Rectus abdominis - **Targets:** Rectus abdominis
- **Prescription:** 3 × 1520, slow - **Prescription:** 3 × 1520, slow
- **Defaults:** 3 × 15 bodyweight
## Setup ## Setup
+1
View File
@@ -6,6 +6,7 @@ Anti-extension drill — the limbs move while the trunk stays perfectly still.
- **Type:** Anti-extension - **Type:** Anti-extension
- **Targets:** Deep core (transverse abdominis), hip flexor control - **Targets:** Deep core (transverse abdominis), hip flexor control
- **Prescription:** 8 per side, slow and controlled - **Prescription:** 8 per side, slow and controlled
- **Defaults:** 3 × 8 bodyweight
## Setup ## Setup
+1
View File
@@ -7,6 +7,7 @@ the hips through.
- **Type:** Free-weight compound hinge - **Type:** Free-weight compound hinge
- **Targets:** Glutes, hamstrings, spinal erectors, grip - **Targets:** Glutes, hamstrings, spinal erectors, grip
- **Prescription:** 4 × 68 - **Prescription:** 4 × 68
- **Defaults:** 4 × 6 weighted
## Setup ## Setup
+1
View File
@@ -7,6 +7,7 @@ elbow depth, then press back to straight arms.
- **Type:** Bodyweight elbow extension (chest/triceps press) - **Type:** Bodyweight elbow extension (chest/triceps press)
- **Targets:** Triceps, chest, front delts - **Targets:** Triceps, chest, front delts
- **Prescription:** 3 × 612 - **Prescription:** 3 × 612
- **Defaults:** 3 × 6 bodyweight
## Setup ## Setup
@@ -7,6 +7,7 @@ the bar, one weight in each hand.
- **Type:** Free-weight horizontal press (dumbbells) - **Type:** Free-weight horizontal press (dumbbells)
- **Targets:** Chest, front delts, triceps - **Targets:** Chest, front delts, triceps
- **Prescription:** 3 × 812 - **Prescription:** 3 × 812
- **Defaults:** 3 × 8 weighted
## Setup ## Setup
+1
View File
@@ -7,6 +7,7 @@ dumbbells from a full hang to the shoulders.
- **Type:** Free-weight elbow flexion (dumbbells) - **Type:** Free-weight elbow flexion (dumbbells)
- **Targets:** Biceps, forearms - **Targets:** Biceps, forearms
- **Prescription:** 3 × 1012 - **Prescription:** 3 × 1012
- **Defaults:** 3 × 10 weighted
## Setup ## Setup
+1
View File
@@ -7,6 +7,7 @@ hugging arc, elbows softly fixed.
- **Type:** Free-weight chest fly (dumbbells) - **Type:** Free-weight chest fly (dumbbells)
- **Targets:** Chest, front delts - **Targets:** Chest, front delts
- **Prescription:** 3 × 1015, light and controlled - **Prescription:** 3 × 1015, light and controlled
- **Defaults:** 3 × 10 weighted
## Setup ## Setup
+1
View File
@@ -7,6 +7,7 @@ from a dead hang to the ribs.
- **Type:** Free-weight horizontal pull (dumbbell) - **Type:** Free-weight horizontal pull (dumbbell)
- **Targets:** Lats, mid-back, biceps - **Targets:** Lats, mid-back, biceps
- **Prescription:** 3 × 812 per side - **Prescription:** 3 × 812 per side
- **Defaults:** 3 × 8 weighted
## Setup ## Setup
@@ -7,6 +7,7 @@ press from the shoulders to lockout overhead.
- **Type:** Free-weight vertical press (dumbbells) - **Type:** Free-weight vertical press (dumbbells)
- **Targets:** Shoulders, triceps, upper chest - **Targets:** Shoulders, triceps, upper chest
- **Prescription:** 3 × 812 - **Prescription:** 3 × 812
- **Defaults:** 3 × 8 weighted
## Setup ## Setup
+1
View File
@@ -7,6 +7,7 @@ the nose. Small weight, big shoulders-health payoff.
- **Type:** Machine-based cable pull (rear delts) - **Type:** Machine-based cable pull (rear delts)
- **Targets:** Rear delts, mid-back, rotator cuff - **Targets:** Rear delts, mid-back, rotator cuff
- **Prescription:** 3 × 1215, light - **Prescription:** 3 × 1215, light
- **Defaults:** 3 × 12 weighted
## Setup ## Setup
+1
View File
@@ -7,6 +7,7 @@ body is a straight line from shoulders to knees.
- **Type:** Hip extension (floor) - **Type:** Hip extension (floor)
- **Targets:** Glutes, hamstrings - **Targets:** Glutes, hamstrings
- **Prescription:** 3 × 1215, squeeze at the top - **Prescription:** 3 × 1215, squeeze at the top
- **Defaults:** 3 × 12 bodyweight
## Setup ## Setup
+1
View File
@@ -7,6 +7,7 @@ between the heels. The friendliest way to load a squat.
- **Type:** Free-weight squat (dumbbell) - **Type:** Free-weight squat (dumbbell)
- **Targets:** Quadriceps, glutes, trunk - **Targets:** Quadriceps, glutes, trunk
- **Prescription:** 3 × 1012 - **Prescription:** 3 × 1012
- **Defaults:** 3 × 10 weighted
## Setup ## Setup
+1
View File
@@ -7,6 +7,7 @@ drive the hips up to a straight line.
- **Type:** Free-weight hip extension - **Type:** Free-weight hip extension
- **Targets:** Glutes, hamstrings - **Targets:** Glutes, hamstrings
- **Prescription:** 4 × 810 - **Prescription:** 4 × 810
- **Defaults:** 4 × 8 weighted
## Setup ## Setup
@@ -6,6 +6,7 @@ The gold standard for deep core tension — a shallow, rigid banana shape.
- **Type:** Anti-extension (isometric hold) - **Type:** Anti-extension (isometric hold)
- **Targets:** Rectus abdominis, deep core, hip flexors - **Targets:** Rectus abdominis, deep core, hip flexors
- **Prescription:** 3 × 2040 s holds - **Prescription:** 3 × 2040 s holds
- **Defaults:** 3 × 20 s
## Setup ## Setup
@@ -7,6 +7,7 @@ chest.
- **Type:** Free-weight incline press - **Type:** Free-weight incline press
- **Targets:** Upper chest, front delts, triceps - **Targets:** Upper chest, front delts, triceps
- **Prescription:** 3 × 810 - **Prescription:** 3 × 810
- **Defaults:** 3 × 8 weighted
## Setup ## Setup
+1
View File
@@ -7,6 +7,7 @@ down from overhead.
- **Type:** Machine-based vertical pull - **Type:** Machine-based vertical pull
- **Targets:** Latissimus dorsi, mid-back, rear delts, biceps - **Targets:** Latissimus dorsi, mid-back, rear delts, biceps
- **Prescription:** 4 × 10 - **Prescription:** 4 × 10
- **Defaults:** 4 × 10 weighted
## Setup ## Setup
+1
View File
@@ -7,6 +7,7 @@ the arms are level with the shoulders.
- **Type:** Free-weight lateral raise (dumbbells) - **Type:** Free-weight lateral raise (dumbbells)
- **Targets:** Side delts - **Targets:** Side delts
- **Prescription:** 3 × 1215, light - **Prescription:** 3 × 1215, light
- **Defaults:** 3 × 12 weighted
## Setup ## Setup
+1
View File
@@ -7,6 +7,7 @@ the thighs in place.
- **Type:** Machine-based isolation (knee flexion) - **Type:** Machine-based isolation (knee flexion)
- **Targets:** Hamstrings (biceps femoris, semitendinosus, semimembranosus) - **Targets:** Hamstrings (biceps femoris, semitendinosus, semimembranosus)
- **Prescription:** 4 × 10 - **Prescription:** 4 × 10
- **Defaults:** 4 × 10 weighted
## Setup ## Setup
+1
View File
@@ -7,6 +7,7 @@ stay pinned to the seat.
- **Type:** Machine-based isolation (knee extension) - **Type:** Machine-based isolation (knee extension)
- **Targets:** Quadriceps (rectus femoris, vastus lateralis/medialis) - **Targets:** Quadriceps (rectus femoris, vastus lateralis/medialis)
- **Prescription:** 4 × 10 - **Prescription:** 4 × 10
- **Defaults:** 4 × 10 weighted
## Setup ## Setup
+1
View File
@@ -7,6 +7,7 @@ supported.
- **Type:** Machine-based compound push - **Type:** Machine-based compound push
- **Targets:** Quadriceps, glutes, hamstrings - **Targets:** Quadriceps, glutes, hamstrings
- **Prescription:** 4 × 10 - **Prescription:** 4 × 10
- **Defaults:** 4 × 10 weighted
## Setup ## Setup
+1
View File
@@ -6,6 +6,7 @@ Lower-ab builder — the slow lowering is the exercise.
- **Type:** Anti-extension (dynamic) - **Type:** Anti-extension (dynamic)
- **Targets:** Lower rectus abdominis, hip flexors, deep core - **Targets:** Lower rectus abdominis, hip flexors, deep core
- **Prescription:** 3 × 815 reps, slow negatives - **Prescription:** 3 × 815 reps, slow negatives
- **Defaults:** 3 × 8 bodyweight
## Setup ## Setup
+1
View File
@@ -7,6 +7,7 @@ back up through the front heel. Bodyweight first, dumbbells in hand later.
- **Type:** Split-stance squat (bodyweight or dumbbells) - **Type:** Split-stance squat (bodyweight or dumbbells)
- **Targets:** Quadriceps, glutes, balance - **Targets:** Quadriceps, glutes, balance
- **Prescription:** 3 × 810 per side - **Prescription:** 3 × 810 per side
- **Defaults:** 3 × 8 bodyweight
## Setup ## Setup
+1
View File
@@ -7,6 +7,7 @@ the machine's fixed arc.
- **Type:** Machine-based chest fly - **Type:** Machine-based chest fly
- **Targets:** Chest, front delts - **Targets:** Chest, front delts
- **Prescription:** 4 × 10 - **Prescription:** 4 × 10
- **Defaults:** 4 × 10 weighted
## Setup ## Setup
+1
View File
@@ -6,6 +6,7 @@ The baseline anti-extension hold — a rigid line from head to heels.
- **Type:** Anti-extension (isometric hold) - **Type:** Anti-extension (isometric hold)
- **Targets:** Rectus abdominis, deep core, glutes, shoulders - **Targets:** Rectus abdominis, deep core, glutes, shoulders
- **Prescription:** 3 × 3060 s holds - **Prescription:** 3 × 3060 s holds
- **Defaults:** 3 × 30 s
## Setup ## Setup
+1
View File
@@ -7,6 +7,7 @@ lower all the way back down.
- **Type:** Bodyweight vertical pull - **Type:** Bodyweight vertical pull
- **Targets:** Lats, biceps, mid-back, grip - **Targets:** Lats, biceps, mid-back, grip
- **Prescription:** 3 × as many strict reps as possible - **Prescription:** 3 × as many strict reps as possible
- **Defaults:** 3 × 8 bodyweight
## Setup ## Setup
+1
View File
@@ -7,6 +7,7 @@ press back up without the hips sagging.
- **Type:** Bodyweight horizontal press - **Type:** Bodyweight horizontal press
- **Targets:** Chest, front delts, triceps, trunk - **Targets:** Chest, front delts, triceps, trunk
- **Prescription:** 3 × 815 - **Prescription:** 3 × 815
- **Defaults:** 3 × 8 bodyweight
## Setup ## Setup
+1
View File
@@ -7,6 +7,7 @@ wide and back, hitting what pressing never touches.
- **Type:** Machine-based reverse fly - **Type:** Machine-based reverse fly
- **Targets:** Rear delts, mid-back - **Targets:** Rear delts, mid-back
- **Prescription:** 3 × 1215, light - **Prescription:** 3 × 1215, light
- **Defaults:** 3 × 12 weighted
## Setup ## Setup
+1
View File
@@ -6,6 +6,7 @@ Curl the pelvis toward the ribs — lower-ab flexion without neck strain.
- **Type:** Spinal flexion (lower) - **Type:** Spinal flexion (lower)
- **Targets:** Lower rectus abdominis, deep core - **Targets:** Lower rectus abdominis, deep core
- **Prescription:** 3 × 1215 reps, controlled negative - **Prescription:** 3 × 1215 reps, controlled negative
- **Defaults:** 3 × 12 bodyweight
## Setup ## Setup
@@ -7,6 +7,7 @@ the hamstrings stop you, then stand back up. Barbell or dumbbells.
- **Type:** Free-weight hinge (barbell or dumbbells) - **Type:** Free-weight hinge (barbell or dumbbells)
- **Targets:** Hamstrings, glutes, spinal erectors - **Targets:** Hamstrings, glutes, spinal erectors
- **Prescription:** 3 × 810 - **Prescription:** 3 × 810
- **Defaults:** 3 × 8 weighted
## Setup ## Setup
+1
View File
@@ -7,6 +7,7 @@ obliques.
- **Type:** Machine-based trunk rotation - **Type:** Machine-based trunk rotation
- **Targets:** Obliques, deep core - **Targets:** Obliques, deep core
- **Prescription:** 4 × 10 - **Prescription:** 4 × 10
- **Defaults:** 4 × 10 weighted
## Setup ## Setup
+1
View File
@@ -7,6 +7,7 @@ straight back against the chest pad.
- **Type:** Machine-based horizontal pull - **Type:** Machine-based horizontal pull
- **Targets:** Mid-back, latissimus dorsi, rear delts, biceps - **Targets:** Mid-back, latissimus dorsi, rear delts, biceps
- **Prescription:** 4 × 10 - **Prescription:** 4 × 10
- **Defaults:** 4 × 10 weighted
## Setup ## Setup
+1
View File
@@ -6,6 +6,7 @@ against the pad.
- **Category:** Main circuit - **Category:** Main circuit
- **Type:** Machine-based vertical press - **Type:** Machine-based vertical press
- **Targets:** Deltoids, triceps, upper trapezius - **Targets:** Deltoids, triceps, upper trapezius
- **Defaults:** 4 × 10 weighted
## Setup ## Setup
+1
View File
@@ -6,6 +6,7 @@ Anti-lateral-flexion hold — resist the hips dropping toward the floor.
- **Type:** Anti-lateral flexion (isometric hold) - **Type:** Anti-lateral flexion (isometric hold)
- **Targets:** Obliques, quadratus lumborum, glute medius, shoulder stability - **Targets:** Obliques, quadratus lumborum, glute medius, shoulder stability
- **Prescription:** 3 × 2045 s per side - **Prescription:** 3 × 2045 s per side
- **Defaults:** 3 × 20 s
## Setup ## Setup
+1
View File
@@ -6,6 +6,7 @@ chest to full extension.
- **Category:** Main circuit - **Category:** Main circuit
- **Type:** Machine-based elbow extension - **Type:** Machine-based elbow extension
- **Targets:** Triceps brachii - **Targets:** Triceps brachii
- **Defaults:** 4 × 10 weighted
## Setup ## Setup
@@ -7,6 +7,7 @@ chest height down to straight arms.
- **Type:** Machine-based cable elbow extension - **Type:** Machine-based cable elbow extension
- **Targets:** Triceps - **Targets:** Triceps
- **Prescription:** 3 × 1012 - **Prescription:** 3 × 1012
- **Defaults:** 3 × 10 weighted
## Setup ## Setup
+40 -1
View File
@@ -21,6 +21,10 @@ struct ExerciseInfo {
/// The comma-separated `**Targets:**` values, split for chip rendering. /// The comma-separated `**Targets:**` values, split for chip rendering.
var targets: [String] var targets: [String]
var sections: [Section] var sections: [Section]
/// Parsed `**Defaults:**` bullet the library's suggested starting plan for an
/// exercise added outside a split (no prior log, no split entry to copy from).
/// `nil` when the bullet is absent (not yet authored) or unparseable.
var defaults: Defaults?
/// Whether the authored `Type:` identifies this as a machine exercise the /// Whether the authored `Type:` identifies this as a machine exercise the
/// library convention is that every machine entry's type starts "Machine-based". /// library convention is that every machine entry's type starts "Machine-based".
@@ -34,6 +38,14 @@ struct ExerciseInfo {
var items: [Item] var items: [Item]
} }
/// The library's suggested starting plan, parsed from the `**Defaults:**` bullet.
struct Defaults {
var sets: Int
var reps: Int
var loadType: LoadType
var durationSeconds: Int
}
/// One block within a section, preserving the authored list flavor: numbered /// One block within a section, preserving the authored list flavor: numbered
/// steps render with their ordinal, bullets with a dot, paragraphs plain. /// steps render with their ordinal, bullets with a dot, paragraphs plain.
enum Item { enum Item {
@@ -58,6 +70,7 @@ extension ExerciseInfo {
var category: String? var category: String?
var type: String? var type: String?
var targets: [String] = [] var targets: [String] = []
var defaults: Defaults?
var sections: [Section] = [] var sections: [Section] = []
var currentSection: Section? var currentSection: Section?
@@ -118,6 +131,10 @@ extension ExerciseInfo {
targets = value.components(separatedBy: ",") targets = value.components(separatedBy: ",")
.map { $0.trimmingCharacters(in: .whitespaces) } .map { $0.trimmingCharacters(in: .whitespaces) }
.filter { !$0.isEmpty } .filter { !$0.isEmpty }
} else if currentSection == nil,
let value = metadataValue(line, key: "Defaults") {
flushItem()
defaults = parseDefaults(value)
} else if let range = line.range(of: #"^\d+\.\s+"#, options: .regularExpression) { } else if let range = line.range(of: #"^\d+\.\s+"#, options: .regularExpression) {
flushItem() flushItem()
currentItem = .step(String(line[range.upperBound...])) currentItem = .step(String(line[range.upperBound...]))
@@ -136,9 +153,31 @@ extension ExerciseInfo {
category: category, category: category,
type: type, type: type,
targets: targets, targets: targets,
sections: sections sections: sections,
defaults: defaults
) )
} }
/// Parse a `**Defaults:**` value "4 × 10 weighted", "3 × 12 bodyweight", or
/// "3 × 30 s" into sets/reps/loadType. The trailing unit picks the `LoadType`
/// and which of reps/duration the middle number fills; anything else (missing
/// bullet, unrecognized unit) yields `nil` rather than guessing.
private static func parseDefaults(_ value: String) -> Defaults? {
let parts = value.components(separatedBy: " × ")
guard parts.count == 2, let sets = Int(parts[0]) else { return nil }
let rest = parts[1].components(separatedBy: " ")
guard rest.count == 2, let n = Int(rest[0]) else { return nil }
switch rest[1] {
case "weighted":
return Defaults(sets: sets, reps: n, loadType: .weight, durationSeconds: 0)
case "bodyweight":
return Defaults(sets: sets, reps: n, loadType: .none, durationSeconds: 0)
case "s":
return Defaults(sets: sets, reps: 0, loadType: .duration, durationSeconds: n)
default:
return nil
}
}
} }
/// Finds and parses the bundled `<Exercise Name>.info.md` exported next to the /// Finds and parses the bundled `<Exercise Name>.info.md` exported next to the
@@ -7,6 +7,7 @@ supported.
- **Type:** Machine-based trunk flexion - **Type:** Machine-based trunk flexion
- **Targets:** Rectus abdominis, deep core - **Targets:** Rectus abdominis, deep core
- **Prescription:** 4 × 10 - **Prescription:** 4 × 10
- **Defaults:** 4 × 10 weighted
## Setup ## Setup
@@ -7,6 +7,7 @@ hips and glutes.
- **Type:** Machine-based hip abduction - **Type:** Machine-based hip abduction
- **Targets:** Gluteus medius, gluteus minimus, outer thighs - **Targets:** Gluteus medius, gluteus minimus, outer thighs
- **Prescription:** 4 × 10 - **Prescription:** 4 × 10
- **Defaults:** 4 × 10 weighted
## Setup ## Setup
@@ -7,6 +7,7 @@ inner thighs.
- **Type:** Machine-based hip adduction - **Type:** Machine-based hip adduction
- **Targets:** Adductors, inner thighs - **Targets:** Adductors, inner thighs
- **Prescription:** 4 × 10 - **Prescription:** 4 × 10
- **Defaults:** 4 × 10 weighted
## Setup ## Setup
@@ -7,6 +7,7 @@ the pad so only the forearms move.
- **Type:** Machine-based elbow flexion (isolation) - **Type:** Machine-based elbow flexion (isolation)
- **Targets:** Biceps brachii, brachialis - **Targets:** Biceps brachii, brachialis
- **Prescription:** 4 × 10 - **Prescription:** 4 × 10
- **Defaults:** 4 × 10 weighted
## Setup ## Setup
@@ -7,6 +7,7 @@ the trunk back in line with the legs.
- **Type:** Machine-based back extension (hip hinge) - **Type:** Machine-based back extension (hip hinge)
- **Targets:** Spinal erectors, glutes, hamstrings - **Targets:** Spinal erectors, glutes, hamstrings
- **Prescription:** 3 × 1015 - **Prescription:** 3 × 1015
- **Defaults:** 3 × 10 weighted
## Setup ## Setup
@@ -7,6 +7,7 @@ heels, stand back up.
- **Type:** Free-weight compound squat - **Type:** Free-weight compound squat
- **Targets:** Quadriceps, glutes, adductors, trunk - **Targets:** Quadriceps, glutes, adductors, trunk
- **Prescription:** 4 × 68 - **Prescription:** 4 × 68
- **Defaults:** 4 × 6 weighted
## Setup ## Setup
@@ -7,6 +7,7 @@ and press it back to straight arms.
- **Type:** Free-weight horizontal press - **Type:** Free-weight horizontal press
- **Targets:** Chest, front delts, triceps - **Targets:** Chest, front delts, triceps
- **Prescription:** 4 × 68 - **Prescription:** 4 × 68
- **Defaults:** 4 × 6 weighted
## Setup ## Setup
@@ -7,6 +7,7 @@ twist.
- **Type:** Anti-rotation - **Type:** Anti-rotation
- **Targets:** Spinal erectors, glutes, deep core, shoulder stability - **Targets:** Spinal erectors, glutes, deep core, shoulder stability
- **Prescription:** Warm-up 8 per side; main circuit 3 × 810 per side, slow - **Prescription:** Warm-up 8 per side; main circuit 3 × 810 per side, slow
- **Defaults:** 3 × 8 bodyweight
## Setup ## Setup
@@ -7,6 +7,7 @@ sit down between the heels. The pattern every loaded squat is built on.
- **Type:** Bodyweight squat - **Type:** Bodyweight squat
- **Targets:** Quadriceps, glutes, mobility - **Targets:** Quadriceps, glutes, mobility
- **Prescription:** 3 × 1520, or 10 slow reps as a warm-up - **Prescription:** 3 × 1520, or 10 slow reps as a warm-up
- **Defaults:** 3 × 15 bodyweight
## Setup ## Setup
@@ -7,6 +7,7 @@ a hard squeeze at the top.
- **Type:** Machine-based calf raise - **Type:** Machine-based calf raise
- **Targets:** Soleus, gastrocnemius - **Targets:** Soleus, gastrocnemius
- **Prescription:** 4 × 10 - **Prescription:** 4 × 10
- **Defaults:** 4 × 10 weighted
## Setup ## Setup
@@ -6,6 +6,7 @@ Gentle spinal mobility drill that wakes up the whole trunk before core work.
- **Type:** Spinal mobility - **Type:** Spinal mobility
- **Targets:** Thoracic and lumbar spine, deep core awareness - **Targets:** Thoracic and lumbar spine, deep core awareness
- **Prescription:** 10 slow reps (one rep = cat + cow) - **Prescription:** 10 slow reps (one rep = cat + cow)
- **Defaults:** 3 × 10 bodyweight
## Setup ## Setup
@@ -6,6 +6,7 @@ forward from the chest.
- **Category:** Main circuit - **Category:** Main circuit
- **Type:** Machine-based horizontal press - **Type:** Machine-based horizontal press
- **Targets:** Pectorals, anterior deltoids, triceps - **Targets:** Pectorals, anterior deltoids, triceps
- **Defaults:** 4 × 10 weighted
## Setup ## Setup
@@ -7,6 +7,7 @@ lower. Short range, all abs.
- **Type:** Spinal flexion (upper) - **Type:** Spinal flexion (upper)
- **Targets:** Rectus abdominis - **Targets:** Rectus abdominis
- **Prescription:** 3 × 1520, slow - **Prescription:** 3 × 1520, slow
- **Defaults:** 3 × 15 bodyweight
## Setup ## Setup
@@ -6,6 +6,7 @@ Anti-extension drill — the limbs move while the trunk stays perfectly still.
- **Type:** Anti-extension - **Type:** Anti-extension
- **Targets:** Deep core (transverse abdominis), hip flexor control - **Targets:** Deep core (transverse abdominis), hip flexor control
- **Prescription:** 8 per side, slow and controlled - **Prescription:** 8 per side, slow and controlled
- **Defaults:** 3 × 8 bodyweight
## Setup ## Setup
@@ -7,6 +7,7 @@ the hips through.
- **Type:** Free-weight compound hinge - **Type:** Free-weight compound hinge
- **Targets:** Glutes, hamstrings, spinal erectors, grip - **Targets:** Glutes, hamstrings, spinal erectors, grip
- **Prescription:** 4 × 68 - **Prescription:** 4 × 68
- **Defaults:** 4 × 6 weighted
## Setup ## Setup
@@ -7,6 +7,7 @@ elbow depth, then press back to straight arms.
- **Type:** Bodyweight elbow extension (chest/triceps press) - **Type:** Bodyweight elbow extension (chest/triceps press)
- **Targets:** Triceps, chest, front delts - **Targets:** Triceps, chest, front delts
- **Prescription:** 3 × 612 - **Prescription:** 3 × 612
- **Defaults:** 3 × 6 bodyweight
## Setup ## Setup
@@ -7,6 +7,7 @@ the bar, one weight in each hand.
- **Type:** Free-weight horizontal press (dumbbells) - **Type:** Free-weight horizontal press (dumbbells)
- **Targets:** Chest, front delts, triceps - **Targets:** Chest, front delts, triceps
- **Prescription:** 3 × 812 - **Prescription:** 3 × 812
- **Defaults:** 3 × 8 weighted
## Setup ## Setup
@@ -7,6 +7,7 @@ dumbbells from a full hang to the shoulders.
- **Type:** Free-weight elbow flexion (dumbbells) - **Type:** Free-weight elbow flexion (dumbbells)
- **Targets:** Biceps, forearms - **Targets:** Biceps, forearms
- **Prescription:** 3 × 1012 - **Prescription:** 3 × 1012
- **Defaults:** 3 × 10 weighted
## Setup ## Setup
@@ -7,6 +7,7 @@ hugging arc, elbows softly fixed.
- **Type:** Free-weight chest fly (dumbbells) - **Type:** Free-weight chest fly (dumbbells)
- **Targets:** Chest, front delts - **Targets:** Chest, front delts
- **Prescription:** 3 × 1015, light and controlled - **Prescription:** 3 × 1015, light and controlled
- **Defaults:** 3 × 10 weighted
## Setup ## Setup
@@ -7,6 +7,7 @@ from a dead hang to the ribs.
- **Type:** Free-weight horizontal pull (dumbbell) - **Type:** Free-weight horizontal pull (dumbbell)
- **Targets:** Lats, mid-back, biceps - **Targets:** Lats, mid-back, biceps
- **Prescription:** 3 × 812 per side - **Prescription:** 3 × 812 per side
- **Defaults:** 3 × 8 weighted
## Setup ## Setup
@@ -7,6 +7,7 @@ press from the shoulders to lockout overhead.
- **Type:** Free-weight vertical press (dumbbells) - **Type:** Free-weight vertical press (dumbbells)
- **Targets:** Shoulders, triceps, upper chest - **Targets:** Shoulders, triceps, upper chest
- **Prescription:** 3 × 812 - **Prescription:** 3 × 812
- **Defaults:** 3 × 8 weighted
## Setup ## Setup
@@ -7,6 +7,7 @@ the nose. Small weight, big shoulders-health payoff.
- **Type:** Machine-based cable pull (rear delts) - **Type:** Machine-based cable pull (rear delts)
- **Targets:** Rear delts, mid-back, rotator cuff - **Targets:** Rear delts, mid-back, rotator cuff
- **Prescription:** 3 × 1215, light - **Prescription:** 3 × 1215, light
- **Defaults:** 3 × 12 weighted
## Setup ## Setup
@@ -7,6 +7,7 @@ body is a straight line from shoulders to knees.
- **Type:** Hip extension (floor) - **Type:** Hip extension (floor)
- **Targets:** Glutes, hamstrings - **Targets:** Glutes, hamstrings
- **Prescription:** 3 × 1215, squeeze at the top - **Prescription:** 3 × 1215, squeeze at the top
- **Defaults:** 3 × 12 bodyweight
## Setup ## Setup
@@ -7,6 +7,7 @@ between the heels. The friendliest way to load a squat.
- **Type:** Free-weight squat (dumbbell) - **Type:** Free-weight squat (dumbbell)
- **Targets:** Quadriceps, glutes, trunk - **Targets:** Quadriceps, glutes, trunk
- **Prescription:** 3 × 1012 - **Prescription:** 3 × 1012
- **Defaults:** 3 × 10 weighted
## Setup ## Setup
@@ -7,6 +7,7 @@ drive the hips up to a straight line.
- **Type:** Free-weight hip extension - **Type:** Free-weight hip extension
- **Targets:** Glutes, hamstrings - **Targets:** Glutes, hamstrings
- **Prescription:** 4 × 810 - **Prescription:** 4 × 810
- **Defaults:** 4 × 8 weighted
## Setup ## Setup
@@ -6,6 +6,7 @@ The gold standard for deep core tension — a shallow, rigid banana shape.
- **Type:** Anti-extension (isometric hold) - **Type:** Anti-extension (isometric hold)
- **Targets:** Rectus abdominis, deep core, hip flexors - **Targets:** Rectus abdominis, deep core, hip flexors
- **Prescription:** 3 × 2040 s holds - **Prescription:** 3 × 2040 s holds
- **Defaults:** 3 × 20 s
## Setup ## Setup
@@ -7,6 +7,7 @@ chest.
- **Type:** Free-weight incline press - **Type:** Free-weight incline press
- **Targets:** Upper chest, front delts, triceps - **Targets:** Upper chest, front delts, triceps
- **Prescription:** 3 × 810 - **Prescription:** 3 × 810
- **Defaults:** 3 × 8 weighted
## Setup ## Setup
@@ -7,6 +7,7 @@ down from overhead.
- **Type:** Machine-based vertical pull - **Type:** Machine-based vertical pull
- **Targets:** Latissimus dorsi, mid-back, rear delts, biceps - **Targets:** Latissimus dorsi, mid-back, rear delts, biceps
- **Prescription:** 4 × 10 - **Prescription:** 4 × 10
- **Defaults:** 4 × 10 weighted
## Setup ## Setup
@@ -7,6 +7,7 @@ the arms are level with the shoulders.
- **Type:** Free-weight lateral raise (dumbbells) - **Type:** Free-weight lateral raise (dumbbells)
- **Targets:** Side delts - **Targets:** Side delts
- **Prescription:** 3 × 1215, light - **Prescription:** 3 × 1215, light
- **Defaults:** 3 × 12 weighted
## Setup ## Setup
@@ -7,6 +7,7 @@ the thighs in place.
- **Type:** Machine-based isolation (knee flexion) - **Type:** Machine-based isolation (knee flexion)
- **Targets:** Hamstrings (biceps femoris, semitendinosus, semimembranosus) - **Targets:** Hamstrings (biceps femoris, semitendinosus, semimembranosus)
- **Prescription:** 4 × 10 - **Prescription:** 4 × 10
- **Defaults:** 4 × 10 weighted
## Setup ## Setup
@@ -7,6 +7,7 @@ stay pinned to the seat.
- **Type:** Machine-based isolation (knee extension) - **Type:** Machine-based isolation (knee extension)
- **Targets:** Quadriceps (rectus femoris, vastus lateralis/medialis) - **Targets:** Quadriceps (rectus femoris, vastus lateralis/medialis)
- **Prescription:** 4 × 10 - **Prescription:** 4 × 10
- **Defaults:** 4 × 10 weighted
## Setup ## Setup
@@ -7,6 +7,7 @@ supported.
- **Type:** Machine-based compound push - **Type:** Machine-based compound push
- **Targets:** Quadriceps, glutes, hamstrings - **Targets:** Quadriceps, glutes, hamstrings
- **Prescription:** 4 × 10 - **Prescription:** 4 × 10
- **Defaults:** 4 × 10 weighted
## Setup ## Setup
@@ -6,6 +6,7 @@ Lower-ab builder — the slow lowering is the exercise.
- **Type:** Anti-extension (dynamic) - **Type:** Anti-extension (dynamic)
- **Targets:** Lower rectus abdominis, hip flexors, deep core - **Targets:** Lower rectus abdominis, hip flexors, deep core
- **Prescription:** 3 × 815 reps, slow negatives - **Prescription:** 3 × 815 reps, slow negatives
- **Defaults:** 3 × 8 bodyweight
## Setup ## Setup
@@ -7,6 +7,7 @@ back up through the front heel. Bodyweight first, dumbbells in hand later.
- **Type:** Split-stance squat (bodyweight or dumbbells) - **Type:** Split-stance squat (bodyweight or dumbbells)
- **Targets:** Quadriceps, glutes, balance - **Targets:** Quadriceps, glutes, balance
- **Prescription:** 3 × 810 per side - **Prescription:** 3 × 810 per side
- **Defaults:** 3 × 8 bodyweight
## Setup ## Setup
@@ -7,6 +7,7 @@ the machine's fixed arc.
- **Type:** Machine-based chest fly - **Type:** Machine-based chest fly
- **Targets:** Chest, front delts - **Targets:** Chest, front delts
- **Prescription:** 4 × 10 - **Prescription:** 4 × 10
- **Defaults:** 4 × 10 weighted
## Setup ## Setup
@@ -6,6 +6,7 @@ The baseline anti-extension hold — a rigid line from head to heels.
- **Type:** Anti-extension (isometric hold) - **Type:** Anti-extension (isometric hold)
- **Targets:** Rectus abdominis, deep core, glutes, shoulders - **Targets:** Rectus abdominis, deep core, glutes, shoulders
- **Prescription:** 3 × 3060 s holds - **Prescription:** 3 × 3060 s holds
- **Defaults:** 3 × 30 s
## Setup ## Setup
@@ -7,6 +7,7 @@ lower all the way back down.
- **Type:** Bodyweight vertical pull - **Type:** Bodyweight vertical pull
- **Targets:** Lats, biceps, mid-back, grip - **Targets:** Lats, biceps, mid-back, grip
- **Prescription:** 3 × as many strict reps as possible - **Prescription:** 3 × as many strict reps as possible
- **Defaults:** 3 × 8 bodyweight
## Setup ## Setup
@@ -7,6 +7,7 @@ press back up without the hips sagging.
- **Type:** Bodyweight horizontal press - **Type:** Bodyweight horizontal press
- **Targets:** Chest, front delts, triceps, trunk - **Targets:** Chest, front delts, triceps, trunk
- **Prescription:** 3 × 815 - **Prescription:** 3 × 815
- **Defaults:** 3 × 8 bodyweight
## Setup ## Setup
@@ -7,6 +7,7 @@ wide and back, hitting what pressing never touches.
- **Type:** Machine-based reverse fly - **Type:** Machine-based reverse fly
- **Targets:** Rear delts, mid-back - **Targets:** Rear delts, mid-back
- **Prescription:** 3 × 1215, light - **Prescription:** 3 × 1215, light
- **Defaults:** 3 × 12 weighted
## Setup ## Setup
@@ -6,6 +6,7 @@ Curl the pelvis toward the ribs — lower-ab flexion without neck strain.
- **Type:** Spinal flexion (lower) - **Type:** Spinal flexion (lower)
- **Targets:** Lower rectus abdominis, deep core - **Targets:** Lower rectus abdominis, deep core
- **Prescription:** 3 × 1215 reps, controlled negative - **Prescription:** 3 × 1215 reps, controlled negative
- **Defaults:** 3 × 12 bodyweight
## Setup ## Setup
@@ -7,6 +7,7 @@ the hamstrings stop you, then stand back up. Barbell or dumbbells.
- **Type:** Free-weight hinge (barbell or dumbbells) - **Type:** Free-weight hinge (barbell or dumbbells)
- **Targets:** Hamstrings, glutes, spinal erectors - **Targets:** Hamstrings, glutes, spinal erectors
- **Prescription:** 3 × 810 - **Prescription:** 3 × 810
- **Defaults:** 3 × 8 weighted
## Setup ## Setup
@@ -7,6 +7,7 @@ obliques.
- **Type:** Machine-based trunk rotation - **Type:** Machine-based trunk rotation
- **Targets:** Obliques, deep core - **Targets:** Obliques, deep core
- **Prescription:** 4 × 10 - **Prescription:** 4 × 10
- **Defaults:** 4 × 10 weighted
## Setup ## Setup
@@ -7,6 +7,7 @@ straight back against the chest pad.
- **Type:** Machine-based horizontal pull - **Type:** Machine-based horizontal pull
- **Targets:** Mid-back, latissimus dorsi, rear delts, biceps - **Targets:** Mid-back, latissimus dorsi, rear delts, biceps
- **Prescription:** 4 × 10 - **Prescription:** 4 × 10
- **Defaults:** 4 × 10 weighted
## Setup ## Setup
@@ -6,6 +6,7 @@ against the pad.
- **Category:** Main circuit - **Category:** Main circuit
- **Type:** Machine-based vertical press - **Type:** Machine-based vertical press
- **Targets:** Deltoids, triceps, upper trapezius - **Targets:** Deltoids, triceps, upper trapezius
- **Defaults:** 4 × 10 weighted
## Setup ## Setup
@@ -6,6 +6,7 @@ Anti-lateral-flexion hold — resist the hips dropping toward the floor.
- **Type:** Anti-lateral flexion (isometric hold) - **Type:** Anti-lateral flexion (isometric hold)
- **Targets:** Obliques, quadratus lumborum, glute medius, shoulder stability - **Targets:** Obliques, quadratus lumborum, glute medius, shoulder stability
- **Prescription:** 3 × 2045 s per side - **Prescription:** 3 × 2045 s per side
- **Defaults:** 3 × 20 s
## Setup ## Setup
@@ -6,6 +6,7 @@ chest to full extension.
- **Category:** Main circuit - **Category:** Main circuit
- **Type:** Machine-based elbow extension - **Type:** Machine-based elbow extension
- **Targets:** Triceps brachii - **Targets:** Triceps brachii
- **Defaults:** 4 × 10 weighted
## Setup ## Setup
@@ -7,6 +7,7 @@ chest height down to straight arms.
- **Type:** Machine-based cable elbow extension - **Type:** Machine-based cable elbow extension
- **Targets:** Triceps - **Targets:** Triceps
- **Prescription:** 3 × 1012 - **Prescription:** 3 × 1012
- **Defaults:** 3 × 10 weighted
## Setup ## Setup
@@ -254,12 +254,16 @@ struct ExerciseListView: View {
let newDocs = names let newDocs = names
.filter { !existingNames.contains($0) } .filter { !existingNames.contains($0) }
.enumerated() .enumerated()
.map { i, exName in .map { i, exName -> ExerciseDocument in
ExerciseDocument( // Seed from the library's authored `**Defaults:**` when available,
// falling back to a plain 3×10 weighted guess; weight always starts
// at 0 (there's no prior lift to seed it from).
let defaults = ExerciseInfoLibrary.info(for: exName)?.defaults
return ExerciseDocument(
id: ULID.make(), name: exName, order: base + i, id: ULID.make(), name: exName, order: base + i,
sets: 3, reps: 10, weight: 40, sets: defaults?.sets ?? 3, reps: defaults?.reps ?? 10, weight: 0,
loadType: LoadType.weight.rawValue, loadType: (defaults?.loadType ?? .weight).rawValue,
durationSeconds: 0 durationSeconds: defaults?.durationSeconds ?? 0
) )
} }
doc.exercises.append(contentsOf: newDocs) doc.exercises.append(contentsOf: newDocs)
+9 -5
View File
@@ -175,12 +175,16 @@ struct SplitDetailView: View {
let newDocs = names let newDocs = names
.filter { !existingNames.contains($0) } .filter { !existingNames.contains($0) }
.enumerated() .enumerated()
.map { i, exName in .map { i, exName -> ExerciseDocument in
ExerciseDocument( // Seed from the library's authored `**Defaults:**` when available,
// falling back to a plain 3×10 weighted guess; weight always starts
// at 0 (there's no prior lift to seed it from).
let defaults = ExerciseInfoLibrary.info(for: exName)?.defaults
return ExerciseDocument(
id: ULID.make(), name: exName, order: base + i, id: ULID.make(), name: exName, order: base + i,
sets: 3, reps: 10, weight: 40, sets: defaults?.sets ?? 3, reps: defaults?.reps ?? 10, weight: 0,
loadType: LoadType.weight.rawValue, loadType: (defaults?.loadType ?? .weight).rawValue,
durationSeconds: 0 durationSeconds: defaults?.durationSeconds ?? 0
) )
} }
doc.exercises.append(contentsOf: newDocs) doc.exercises.append(contentsOf: newDocs)
@@ -182,8 +182,11 @@ struct WorkoutLogListView: View {
SplitExercisePickerSheet( SplitExercisePickerSheet(
split: split, split: split,
existingExerciseNames: Set(sortedLogs.map { $0.exerciseName }) existingExerciseNames: Set(sortedLogs.map { $0.exerciseName })
) { exercise in ) { selection in
addExerciseFromSplit(exercise) switch selection {
case .fromSplit(let exercise): addExerciseFromSplit(exercise)
case .fromLibrary(let name): addExerciseFromLibrary(name)
}
} }
} }
.confirmationDialog( .confirmationDialog(
@@ -305,6 +308,21 @@ struct WorkoutLogListView: View {
} }
private func addExerciseFromSplit(_ exercise: Exercise) { private func addExerciseFromSplit(_ exercise: Exercise) {
appendExercise(ExerciseDocument(from: exercise))
}
/// Add an exercise that isn't part of this workout's split, picked from the full
/// exercise library. Seeds a plan from, in priority order: the most recent logged
/// set of this exercise (any workout), the library's authored `**Defaults:**`, or
/// a plain 3×10 fallback when neither exists.
private func addExerciseFromLibrary(_ name: String) {
appendExercise(libraryPlan(for: name))
}
/// Append a freshly-planned exercise to the working doc, persist, and push
/// straight into its progress flow. Shared tail of `addExerciseFromSplit` and
/// `addExerciseFromLibrary`.
private func appendExercise(_ plan: ExerciseDocument) {
let now = Date() let now = Date()
// Reuse the workout's start time only when it's the very first exercise. // Reuse the workout's start time only when it's the very first exercise.
@@ -313,7 +331,7 @@ struct WorkoutLogListView: View {
} }
doc.end = nil doc.end = nil
let newLog = WorkoutLogDocument(planFrom: ExerciseDocument(from: exercise), order: doc.logs.count, date: now) let newLog = WorkoutLogDocument(planFrom: plan, order: doc.logs.count, date: now)
doc.logs.append(newLog) doc.logs.append(newLog)
save() save()
@@ -321,6 +339,46 @@ struct WorkoutLogListView: View {
addedLog = LogRoute(id: newLog.id) addedLog = LogRoute(id: newLog.id)
} }
/// Plan defaults for a library exercise with no split entry: copy the most recent
/// logged set of this exercise (sets/reps/weight/loadType/duration and machine
/// settings) if one exists anywhere; otherwise the library's authored defaults
/// (weight always starts at 0 there's no prior lift to copy); otherwise a plain
/// 3×10 bodyweight-style fallback.
private func libraryPlan(for name: String) -> ExerciseDocument {
if let recent = mostRecentLog(named: name) {
return ExerciseDocument(
id: ULID.make(), name: name, order: doc.logs.count,
sets: recent.sets, reps: recent.reps, weight: recent.weight,
loadType: recent.loadType, durationSeconds: recent.durationTotalSeconds,
machineSettings: recent.machineSettings
)
}
let info = ExerciseInfoLibrary.info(for: name)
if let defaults = info?.defaults {
return ExerciseDocument(
id: ULID.make(), name: name, order: doc.logs.count,
sets: defaults.sets, reps: defaults.reps, weight: 0,
loadType: defaults.loadType.rawValue, durationSeconds: defaults.durationSeconds,
machineSettings: info?.isMachineBased == true ? [] : nil
)
}
return ExerciseDocument(
id: ULID.make(), name: name, order: doc.logs.count,
sets: 3, reps: 10, weight: 0, loadType: LoadType.none.rawValue, durationSeconds: 0
)
}
/// Most recent `WorkoutLog` cache entity logged under `name`, across all
/// workouts the best available signal for "what did I actually do last time."
private func mostRecentLog(named name: String) -> WorkoutLog? {
var descriptor = FetchDescriptor<WorkoutLog>(
predicate: #Predicate { $0.exerciseName == name },
sortBy: [SortDescriptor(\.date, order: .reverse)]
)
descriptor.fetchLimit = 1
return try? modelContext.fetch(descriptor).first
}
/// Apply edited machine settings from the sheet: update the log's plan-time /// Apply edited machine settings from the sheet: update the log's plan-time
/// snapshot (persisted with the workout), then write the same settings back to /// snapshot (persisted with the workout), then write the same settings back to
/// the originating split's exercise as the durable default. Sequenced in one /// the originating split's exercise as the durable default. Sequenced in one
@@ -490,22 +548,77 @@ struct SplitExercisePickerSheet: View {
let split: Split? let split: Split?
let existingExerciseNames: Set<String> let existingExerciseNames: Set<String>
let onExerciseSelected: (Exercise) -> Void let onSelected: (Selection) -> Void
@AppStorage("weightUnit") private var weightUnit: WeightUnit = .lb @AppStorage("weightUnit") private var weightUnit: WeightUnit = .lb
@State private var searchText = ""
/// Parsed once per sheet presentation (`onAppear`), not per row render the
/// library section's name list is stable for the sheet's lifetime.
@State private var libraryInfoByName: [String: ExerciseInfo] = [:]
private var availableExercises: [Exercise] { /// A pick from either source: the split path hands back the live `Exercise`
/// entity (as today), the library path just the exercise name.
enum Selection {
case fromSplit(Exercise)
case fromLibrary(String)
}
/// Section 1 this workout's split, minus exercises already added.
private var splitExercises: [Exercise] {
guard let split else { return [] } guard let split else { return [] }
return split.exercisesArray.filter { !existingExerciseNames.contains($0.name) } return split.exercisesArray.filter { !existingExerciseNames.contains($0.name) }
} }
/// Section 2 every library exercise not already in the workout and not
/// already offered in section 1.
private var libraryExerciseNames: [String] {
let shown = Set(splitExercises.map(\.name))
return ExerciseMotionLibrary.exerciseNames.filter {
!existingExerciseNames.contains($0) && !shown.contains($0)
}
}
private var filteredSplitExercises: [Exercise] {
guard !searchText.isEmpty else { return splitExercises }
return splitExercises.filter { $0.name.localizedCaseInsensitiveContains(searchText) }
}
private var filteredLibraryNames: [String] {
guard !searchText.isEmpty else { return libraryExerciseNames }
return libraryExerciseNames.filter { $0.localizedCaseInsensitiveContains(searchText) }
}
/// Whether there's anything left to add at all independent of the current
/// search, so a no-results search doesn't get confused with a truly empty sheet.
private var hasAnyExercises: Bool {
!splitExercises.isEmpty || !libraryExerciseNames.isEmpty
}
/// "3 × 12" / "3 × 30 s" the library's suggested plan for a row's caption.
private func defaultsCaption(_ defaults: ExerciseInfo.Defaults) -> String {
switch defaults.loadType {
case .duration: "\(defaults.sets) × \(defaults.durationSeconds) s"
case .weight, .none: "\(defaults.sets) × \(defaults.reps)"
}
}
var body: some View { var body: some View {
NavigationStack { NavigationStack {
Group { Group {
if !availableExercises.isEmpty { if !hasAnyExercises {
ContentUnavailableView(
"All Exercises Added",
systemImage: "checkmark.circle",
description: Text("Every exercise is already part of this workout.")
)
} else if filteredSplitExercises.isEmpty && filteredLibraryNames.isEmpty {
ContentUnavailableView.search(text: searchText)
} else {
List { List {
ForEach(availableExercises) { exercise in if !filteredSplitExercises.isEmpty, let split {
Section("From \(split.name)") {
ForEach(filteredSplitExercises) { exercise in
Button { Button {
onExerciseSelected(exercise) onSelected(.fromSplit(exercise))
dismiss() dismiss()
} label: { } label: {
HStack { HStack {
@@ -523,24 +636,41 @@ struct SplitExercisePickerSheet: View {
} }
} }
} }
} else if split == nil { }
ContentUnavailableView(
"No Split Selected", if !filteredLibraryNames.isEmpty {
systemImage: "dumbbell", Section("All Exercises") {
description: Text("This workout has no associated split.") ForEach(filteredLibraryNames, id: \.self) { name in
) Button {
} else if split?.exercisesArray.isEmpty == true { onSelected(.fromLibrary(name))
ContentUnavailableView( dismiss()
"No Exercises in Split", } label: {
systemImage: "dumbbell", HStack {
description: Text("Add exercises to your split first.") VStack(alignment: .leading) {
) Text(name)
} else { .foregroundColor(.primary)
ContentUnavailableView( if let defaults = libraryInfoByName[name]?.defaults {
"All Exercises Added", Text(defaultsCaption(defaults))
systemImage: "checkmark.circle", .font(.caption)
description: Text("You've added all exercises from this split.") .foregroundColor(.secondary)
) }
}
Spacer()
Image(systemName: "plus.circle")
.foregroundColor(.accentColor)
}
}
}
}
}
}
}
}
.searchable(text: $searchText)
.onAppear {
guard libraryInfoByName.isEmpty else { return }
for name in libraryExerciseNames {
libraryInfoByName[name] = ExerciseInfoLibrary.info(for: name)
} }
} }
.navigationTitle("Add Exercise") .navigationTitle("Add Exercise")