From ae3aa1501978f7c43a537380c606bdb2713c1928 Mon Sep 17 00:00:00 2001 From: rzen Date: Thu, 9 Jul 2026 18:20:05 -0400 Subject: [PATCH] Add generic watch-tracked Cardio exercise and starter split MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A single "Cardio" library entry (running-in-place figure, duration-logged) plus a matching Cardio starter split tagged .cardio (HealthKit .mixedCardio), so the Apple Watch records the real aerobic workout while the phone logs the time — no per-machine cardio entries or schema changes. Claude-Session: https://claude.ai/code/session_012qw2itfzKyEJ1HpsFt8Ex4 --- CHANGELOG.md | 2 + Exercise Library/COVERAGE.md | 10 +++- Exercise Library/Cardio/info.md | 41 +++++++++++++++ Exercise Library/Cardio/motion.json | 52 +++++++++++++++++++ README.md | 6 ++- Scripts/generate_starter_splits.swift | 6 +++ .../Resources/ExerciseMotions/Cardio.info.md | 41 +++++++++++++++ .../ExerciseMotions/Cardio.motion.json | 52 +++++++++++++++++++ .../Resources/StarterSplits/Cardio.split.json | 23 ++++++++ WorkoutsTests/SeedLibraryTests.swift | 9 ++-- 10 files changed, 235 insertions(+), 7 deletions(-) create mode 100644 Exercise Library/Cardio/info.md create mode 100644 Exercise Library/Cardio/motion.json create mode 100644 Workouts/Resources/ExerciseMotions/Cardio.info.md create mode 100644 Workouts/Resources/ExerciseMotions/Cardio.motion.json create mode 100644 Workouts/Resources/StarterSplits/Cardio.split.json diff --git a/CHANGELOG.md b/CHANGELOG.md index b1d2deb..75127ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ **July 2026** +A new Cardio exercise and matching starter split let you log aerobic sessions, tracked on your Apple Watch as a cardio workout. + Starting a workout with a split's Start This Split button now brings it up on your Apple Watch too, matching how starting one from the main list already worked. A new Auto-Advance option lets a split flow hands-free from one exercise to the next, resting automatically between them. diff --git a/Exercise Library/COVERAGE.md b/Exercise Library/COVERAGE.md index c80d1a7..d6a4df5 100644 --- a/Exercise Library/COVERAGE.md +++ b/Exercise Library/COVERAGE.md @@ -74,8 +74,14 @@ entries) a user logging by that station's name will find. regression in their Progression sections, not separate picker entries. - **Dumbbell RDL, dumbbell lunge** — folded into Romanian Deadlift and Lunge ("barbell or dumbbells" in their pages), not separate entries. -- **Kettlebells, T-bar row, pullover machine, cardio equipment** — out; not - strength stations found in most gyms (cardio is out of the app's scope). +- **Kettlebells, T-bar row, pullover machine** — out; not strength stations + found in most gyms. +- **Specific cardio machines (treadmill, bike, elliptical, rower, …)** — out as + individual picker entries. Cardio is covered instead by one generic **Cardio** + entry: a single duration-logged block whose split is tagged `.cardio` (→ + HealthKit `.mixedCardio`), so the Apple Watch records the real workout (heart + rate, calories, duration) and the phone just logs the time. It reuses a + running-in-place figure rather than a per-machine rig. - **Morning wake-up mobility & stretching set** — landed. Dynamic warm-up drills (March in Place, Leg Swings, Neck Rolls, Arm Circles, Torso Twist, Standing Side Bend, Hip Circles) now fill the mobility row alongside Cat-Cow, diff --git a/Exercise Library/Cardio/info.md b/Exercise Library/Cardio/info.md new file mode 100644 index 0000000..efa2f10 --- /dev/null +++ b/Exercise Library/Cardio/info.md @@ -0,0 +1,41 @@ +# Cardio + +A steady aerobic session — run in place, ride, row, or use the machine of your +choice and just keep moving. It's logged as a single timed block; your Apple +Watch records the real effort — heart rate, calories, and duration — as a +cardio workout. + +- **Category:** Cardio +- **Type:** Aerobic / conditioning +- **Targets:** Heart, lungs, endurance +- **Prescription:** 15–30 minutes at a conversational-to-brisk pace +- **Defaults:** 1 × 1200 s + +## Setup + +Pick your mode — treadmill, bike, rower, or simply running in place — and let +your watch start the session so the effort is tracked on your wrist. + +## Execution + +1. Ease in for the first minute or two, letting your heart rate climb. +2. Settle into a steady pace you can hold for the whole block. +3. Keep your breathing rhythmic and your shoulders relaxed. +4. Cool down with an easy minute before you stop. + +## Cues + +- Aim for a pace where you could speak in short sentences, not full paragraphs. +- Stay tall and loose — don't grip the rails or hunch over the machine. +- Let the watch handle the numbers; just hold your effort steady. + +## Common Mistakes + +- Starting too hard and fading long before the time is up. +- Leaning on the handrails, which quietly drops the real workload. +- Skipping the warm-up and cool-down minutes at either end. + +## Progression + +Add minutes, lift the pace, or fold in short faster intervals to raise the +challenge over time. diff --git a/Exercise Library/Cardio/motion.json b/Exercise Library/Cardio/motion.json new file mode 100644 index 0000000..2f21547 --- /dev/null +++ b/Exercise Library/Cardio/motion.json @@ -0,0 +1,52 @@ +{ + "name": "Cardio", + "primary": 1, + "camera": {"zoom": 0.7}, + "working": ["leg_r", "leg_l"], + "frames": [ + { + "hold": 0.1, "tween": 0.3, + "root": {"pos": [160, 62], "pitch": 4}, + "spine": [0, 0], + "neck": 2, "head": -6, + "shoulder_r": -28, "elbow_r": 82, + "shoulder_l": 34, "elbow_l": 78, + "hip_r": 68, "knee_r": 88, "ankle_r": -8, + "hip_l": 2, "knee_l": 4, "ankle_l": 0, + "pins": {"foot_l": [162, 150]} + }, + { + "hold": 0.08, "tween": 0.26, + "root": {"pos": [160, 64], "pitch": 4}, + "spine": [0, 0], + "neck": 2, "head": -6, + "shoulder_r": 3, "elbow_r": 64, + "shoulder_l": 3, "elbow_l": 64, + "hip_r": 4, "knee_r": 4, "ankle_r": 0, + "hip_l": 4, "knee_l": 4, "ankle_l": 0, + "pins": {"foot_r": [158, 148], "foot_l": [162, 150]} + }, + { + "hold": 0.1, "tween": 0.3, + "root": {"pos": [160, 62], "pitch": 4}, + "spine": [0, 0], + "neck": 2, "head": -6, + "shoulder_r": 34, "elbow_r": 78, + "shoulder_l": -28, "elbow_l": 82, + "hip_r": 2, "knee_r": 4, "ankle_r": 0, + "hip_l": 68, "knee_l": 88, "ankle_l": -8, + "pins": {"foot_r": [158, 148]} + }, + { + "hold": 0.08, "tween": 0.26, + "root": {"pos": [160, 64], "pitch": 4}, + "spine": [0, 0], + "neck": 2, "head": -6, + "shoulder_r": 3, "elbow_r": 64, + "shoulder_l": 3, "elbow_l": 64, + "hip_r": 4, "knee_r": 4, "ankle_r": 0, + "hip_l": 4, "knee_l": 4, "ankle_l": 0, + "pins": {"foot_r": [158, 148], "foot_l": [162, 150]} + } + ] +} diff --git a/README.md b/README.md index 1d67f79..4f64f2d 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ your own iCloud Drive. **Bodyweight Core** circuit with its own warm-up) appear automatically on first launch; editing one makes it your own copy, and deleted starters can be restored from Settings. -- **Exercise library** — a bundled library of 47 exercises to populate your +- **Exercise library** — a bundled library of 65 exercises to populate your splits, authored in `Exercise Library/` with per-exercise setup, cues, mistakes, progressions, and an anatomically-rigged 3D stick-figure visual system — covering every common movement pattern and gym station (see the @@ -23,6 +23,10 @@ your own iCloud Drive. free-weight canon (deadlifts, squats, benches, rows, curls, raises), the cable staples, and the bodyweight staples (push-ups, pull-ups, dips, lunges, and the floor core set). +- **Cardio** — a generic **Cardio** exercise and matching starter split for + aerobic work. Tag a split's activity type Cardio and your Apple Watch records + the real effort (heart rate, calories, duration) as a `.mixedCardio` workout, + while the phone logs a simple timed block — no per-machine tracking needed. - **Run a workout** — start a session from a split, then tap an exercise to run it as a paged flow: a **Ready?** lead-in, count-up work phases, count-down rests, and a **Finish** page — mirroring the Apple Watch. Swipe a row to mark it complete, or diff --git a/Scripts/generate_starter_splits.swift b/Scripts/generate_starter_splits.swift index a9aa779..78dbd56 100644 --- a/Scripts/generate_starter_splits.swift +++ b/Scripts/generate_starter_splits.swift @@ -177,6 +177,12 @@ let seeds: [Sp] = [ Ex(name: "Cobra Stretch", sets: 1, reps: 8, load: 0), Ex(name: "Child's Pose", sets: 1, reps: 0, load: 2, dur: 45), ]), + // A single generic, watch-tracked cardio block: one duration exercise in a + // `.cardio` (→ HealthKit `.mixedCardio`) split. The phone logs the time; the + // watch records the real workout (heart rate, calories, duration). + Sp(name: "Cardio", color: "red", icon: "figure.run", activity: 4, ex: [ + Ex(name: "Cardio", sets: 1, reps: 0, load: 2, dur: 1200), + ]), ] // MARK: - Emit diff --git a/Workouts/Resources/ExerciseMotions/Cardio.info.md b/Workouts/Resources/ExerciseMotions/Cardio.info.md new file mode 100644 index 0000000..efa2f10 --- /dev/null +++ b/Workouts/Resources/ExerciseMotions/Cardio.info.md @@ -0,0 +1,41 @@ +# Cardio + +A steady aerobic session — run in place, ride, row, or use the machine of your +choice and just keep moving. It's logged as a single timed block; your Apple +Watch records the real effort — heart rate, calories, and duration — as a +cardio workout. + +- **Category:** Cardio +- **Type:** Aerobic / conditioning +- **Targets:** Heart, lungs, endurance +- **Prescription:** 15–30 minutes at a conversational-to-brisk pace +- **Defaults:** 1 × 1200 s + +## Setup + +Pick your mode — treadmill, bike, rower, or simply running in place — and let +your watch start the session so the effort is tracked on your wrist. + +## Execution + +1. Ease in for the first minute or two, letting your heart rate climb. +2. Settle into a steady pace you can hold for the whole block. +3. Keep your breathing rhythmic and your shoulders relaxed. +4. Cool down with an easy minute before you stop. + +## Cues + +- Aim for a pace where you could speak in short sentences, not full paragraphs. +- Stay tall and loose — don't grip the rails or hunch over the machine. +- Let the watch handle the numbers; just hold your effort steady. + +## Common Mistakes + +- Starting too hard and fading long before the time is up. +- Leaning on the handrails, which quietly drops the real workload. +- Skipping the warm-up and cool-down minutes at either end. + +## Progression + +Add minutes, lift the pace, or fold in short faster intervals to raise the +challenge over time. diff --git a/Workouts/Resources/ExerciseMotions/Cardio.motion.json b/Workouts/Resources/ExerciseMotions/Cardio.motion.json new file mode 100644 index 0000000..2f21547 --- /dev/null +++ b/Workouts/Resources/ExerciseMotions/Cardio.motion.json @@ -0,0 +1,52 @@ +{ + "name": "Cardio", + "primary": 1, + "camera": {"zoom": 0.7}, + "working": ["leg_r", "leg_l"], + "frames": [ + { + "hold": 0.1, "tween": 0.3, + "root": {"pos": [160, 62], "pitch": 4}, + "spine": [0, 0], + "neck": 2, "head": -6, + "shoulder_r": -28, "elbow_r": 82, + "shoulder_l": 34, "elbow_l": 78, + "hip_r": 68, "knee_r": 88, "ankle_r": -8, + "hip_l": 2, "knee_l": 4, "ankle_l": 0, + "pins": {"foot_l": [162, 150]} + }, + { + "hold": 0.08, "tween": 0.26, + "root": {"pos": [160, 64], "pitch": 4}, + "spine": [0, 0], + "neck": 2, "head": -6, + "shoulder_r": 3, "elbow_r": 64, + "shoulder_l": 3, "elbow_l": 64, + "hip_r": 4, "knee_r": 4, "ankle_r": 0, + "hip_l": 4, "knee_l": 4, "ankle_l": 0, + "pins": {"foot_r": [158, 148], "foot_l": [162, 150]} + }, + { + "hold": 0.1, "tween": 0.3, + "root": {"pos": [160, 62], "pitch": 4}, + "spine": [0, 0], + "neck": 2, "head": -6, + "shoulder_r": 34, "elbow_r": 78, + "shoulder_l": -28, "elbow_l": 82, + "hip_r": 2, "knee_r": 4, "ankle_r": 0, + "hip_l": 68, "knee_l": 88, "ankle_l": -8, + "pins": {"foot_r": [158, 148]} + }, + { + "hold": 0.08, "tween": 0.26, + "root": {"pos": [160, 64], "pitch": 4}, + "spine": [0, 0], + "neck": 2, "head": -6, + "shoulder_r": 3, "elbow_r": 64, + "shoulder_l": 3, "elbow_l": 64, + "hip_r": 4, "knee_r": 4, "ankle_r": 0, + "hip_l": 4, "knee_l": 4, "ankle_l": 0, + "pins": {"foot_r": [158, 148], "foot_l": [162, 150]} + } + ] +} diff --git a/Workouts/Resources/StarterSplits/Cardio.split.json b/Workouts/Resources/StarterSplits/Cardio.split.json new file mode 100644 index 0000000..fb586d9 --- /dev/null +++ b/Workouts/Resources/StarterSplits/Cardio.split.json @@ -0,0 +1,23 @@ +{ + "activityType" : 4, + "color" : "red", + "createdAt" : "2020-01-01T00:00:00Z", + "exercises" : [ + { + "durationSeconds" : 1200, + "id" : "01DXF6DT0032GKBRB638SQX8M3", + "loadType" : 2, + "name" : "Cardio", + "order" : 0, + "reps" : 0, + "sets" : 1, + "weight" : 0 + } + ], + "id" : "01DXF6DT00BXJ6N63ZBX9M9VNH", + "name" : "Cardio", + "order" : 12, + "schemaVersion" : 3, + "systemImage" : "figure.run", + "updatedAt" : "2020-01-01T00:00:00Z" +} \ No newline at end of file diff --git a/WorkoutsTests/SeedLibraryTests.swift b/WorkoutsTests/SeedLibraryTests.swift index 08b0aaa..ffa112b 100644 --- a/WorkoutsTests/SeedLibraryTests.swift +++ b/WorkoutsTests/SeedLibraryTests.swift @@ -11,16 +11,16 @@ import IndieSync /// both the raw document and a SwiftData cache round-trip. struct SeedLibraryTests { - @Test func catalogLoadsTwelveSeedsInOrder() { + @Test func catalogLoadsThirteenSeedsInOrder() { let seeds = SeedLibrary.seeds - #expect(seeds.count == 12) - #expect(seeds.map(\.doc.order) == Array(0...11)) + #expect(seeds.count == 13) + #expect(seeds.map(\.doc.order) == Array(0...12)) #expect(seeds.map(\.doc.name) == [ "Upper Body", "Core", "Lower Body", "Bodyweight Core", "Full Body Machines", "Free Weight Basics", "Upper Body Warm-Up", "Lower Body Warm-Up", "Morning Wake-Up", "Morning Mobility", - "Full Body Stretch", "Evening Stretch", + "Full Body Stretch", "Evening Stretch", "Cardio", ]) } @@ -44,6 +44,7 @@ struct SeedLibraryTests { "01DXF6DT00QHJ9G4SDMEX64NZD", // Morning Mobility "01DXF6DT003PRJB3FGYSSGTQ5X", // Full Body Stretch "01DXF6DT00M5YRFPHJQ2FS2B9P", // Evening Stretch + "01DXF6DT00BXJ6N63ZBX9M9VNH", // Cardio ]) }