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**
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.
+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.
- **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,
+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
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
+6
View File
@@ -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
@@ -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.
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
])
}