Add generic watch-tracked Cardio exercise and starter split

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
This commit is contained in:
2026-07-09 18:20:05 -04:00
parent e12fe31152
commit ae3aa15019
10 changed files with 235 additions and 7 deletions
+2
View File
@@ -1,5 +1,7 @@
**July 2026** **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. 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. A new Auto-Advance option lets a split flow hands-free from one exercise to the next, resting automatically between them.
+8 -2
View File
@@ -74,8 +74,14 @@ entries) a user logging by that station's name will find.
regression in their Progression sections, not separate picker entries. regression in their Progression sections, not separate picker entries.
- **Dumbbell RDL, dumbbell lunge** — folded into Romanian Deadlift and Lunge - **Dumbbell RDL, dumbbell lunge** — folded into Romanian Deadlift and Lunge
("barbell or dumbbells" in their pages), not separate entries. ("barbell or dumbbells" in their pages), not separate entries.
- **Kettlebells, T-bar row, pullover machine, cardio equipment** — out; not - **Kettlebells, T-bar row, pullover machine** — out; not strength stations
strength stations found in most gyms (cardio is out of the app's scope). 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 - **Morning wake-up mobility & stretching set** — landed. Dynamic warm-up
drills (March in Place, Leg Swings, Neck Rolls, Arm Circles, Torso Twist, 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, Standing Side Bend, Hip Circles) now fill the mobility row alongside Cat-Cow,
+41
View File
@@ -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:** 1530 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.
+52
View File
@@ -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]}
}
]
}
+5 -1
View File
@@ -15,7 +15,7 @@ your own iCloud Drive.
**Bodyweight Core** circuit with its own warm-up) appear automatically on first **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 launch; editing one makes it your own copy, and deleted starters can be restored
from Settings. 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, splits, authored in `Exercise Library/` with per-exercise setup, cues,
mistakes, progressions, and an anatomically-rigged 3D stick-figure visual mistakes, progressions, and an anatomically-rigged 3D stick-figure visual
system — covering every common movement pattern and gym station (see the 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 free-weight canon (deadlifts, squats, benches, rows, curls, raises), the
cable staples, and the bodyweight staples (push-ups, pull-ups, dips, cable staples, and the bodyweight staples (push-ups, pull-ups, dips,
lunges, and the floor core set). 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 - **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 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 a **Finish** page — mirroring the Apple Watch. Swipe a row to mark it complete, or
+6
View File
@@ -177,6 +177,12 @@ let seeds: [Sp] = [
Ex(name: "Cobra Stretch", sets: 1, reps: 8, load: 0), Ex(name: "Cobra Stretch", sets: 1, reps: 8, load: 0),
Ex(name: "Child's Pose", sets: 1, reps: 0, load: 2, dur: 45), 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 // MARK: - Emit
@@ -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:** 1530 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.
@@ -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]}
}
]
}
@@ -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"
}
+5 -4
View File
@@ -11,16 +11,16 @@ import IndieSync
/// both the raw document and a SwiftData cache round-trip. /// both the raw document and a SwiftData cache round-trip.
struct SeedLibraryTests { struct SeedLibraryTests {
@Test func catalogLoadsTwelveSeedsInOrder() { @Test func catalogLoadsThirteenSeedsInOrder() {
let seeds = SeedLibrary.seeds let seeds = SeedLibrary.seeds
#expect(seeds.count == 12) #expect(seeds.count == 13)
#expect(seeds.map(\.doc.order) == Array(0...11)) #expect(seeds.map(\.doc.order) == Array(0...12))
#expect(seeds.map(\.doc.name) == [ #expect(seeds.map(\.doc.name) == [
"Upper Body", "Core", "Lower Body", "Bodyweight Core", "Upper Body", "Core", "Lower Body", "Bodyweight Core",
"Full Body Machines", "Free Weight Basics", "Full Body Machines", "Free Weight Basics",
"Upper Body Warm-Up", "Lower Body Warm-Up", "Upper Body Warm-Up", "Lower Body Warm-Up",
"Morning Wake-Up", "Morning Mobility", "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 "01DXF6DT00QHJ9G4SDMEX64NZD", // Morning Mobility
"01DXF6DT003PRJB3FGYSSGTQ5X", // Full Body Stretch "01DXF6DT003PRJB3FGYSSGTQ5X", // Full Body Stretch
"01DXF6DT00M5YRFPHJQ2FS2B9P", // Evening Stretch "01DXF6DT00M5YRFPHJQ2FS2B9P", // Evening Stretch
"01DXF6DT00BXJ6N63ZBX9M9VNH", // Cardio
]) ])
} }