Seed machine-based starter routine from screenshots

Replace the bodyweight-catalog-derived seed (hardcoded 3x10, weight 0) with an
explicit curated machine-based routine: Upper Body, Core, and Lower Body at 4x10
with starting weights taken from real workout logs. Decoupled from the shared
exercise catalog YAML (still used by the exercise picker).
This commit is contained in:
2026-06-19 15:35:15 -04:00
parent c44cdd3f90
commit 3ed7b9272c
2 changed files with 50 additions and 28 deletions
+2 -2
View File
@@ -14,8 +14,8 @@ All notable changes to this project are documented here.
ULIDs (the phone is the sole writer of iCloud Drive). ULIDs (the phone is the sole writer of iCloud Drive).
- Migrated the project to XcodeGen; iOS 26 / watchOS 26, Swift 6 strict - Migrated the project to XcodeGen; iOS 26 / watchOS 26, Swift 6 strict
concurrency. concurrency.
- Splits ship as on-demand starter data generated from the bundled exercise - Splits ship as an on-demand machine-based starter routine (Upper Body, Core,
catalogs. Lower Body) at 4×10 with sensible starting weights.
- Stored exercise/log durations as integer seconds (was a `Date` epoch hack). - Stored exercise/log durations as integer seconds (was a `Date` epoch hack).
- Fixed: workout marked complete on creation, an undismissable delete dialog, - Fixed: workout marked complete on creation, an undismissable delete dialog,
toolbar buttons hidden by nested navigation stacks, and a placeholder toolbar buttons hidden by nested navigation stacks, and a placeholder
+48 -26
View File
@@ -1,45 +1,67 @@
import Foundation import Foundation
import SwiftData import SwiftData
/// Builds starter Splits from the bundled YAML exercise catalog, grouped by the /// Builds the bundled machine-based starter routine (Upper Body / Core / Lower
/// catalog's `split` label (Upper Body / Core / Lower Body). Written on demand /// Body). Written on demand (never auto-seeded) through the SyncEngine an empty
/// (never auto-seeded) through the SyncEngine an empty cache at launch can't be /// cache at launch can't be told apart from an iCloud library that simply hasn't
/// told apart from an iCloud library that simply hasn't downloaded yet. /// downloaded yet.
enum SplitSeeder { enum SplitSeeder {
/// Visual theme per starter split group, in display order. /// One starter exercise: name plus its default starting weight (lbs).
private static let groups: [(name: String, color: String, icon: String)] = [ private struct SeedExercise {
("Upper Body", "blue", "figure.strengthtraining.traditional"), let name: String
("Core", "orange", "figure.core.training"), let weight: Int
("Lower Body", "green", "figure.run"), }
]
/// Default split source catalog (most universally applicable). /// One starter split: visual theme plus its ordered exercises.
private static let sourceFile = "bodyweight-starter.exercises.yaml" private struct SeedSplit {
let name: String
let color: String
let icon: String
let exercises: [SeedExercise]
}
/// Sets/reps shared by every starter exercise.
private static let defaultSets = 4
private static let defaultReps = 10
/// Starter splits in display order, with sensible machine starting weights.
/// Users adjust weights from the exercise screen.
private static let starterSplits: [SeedSplit] = [
SeedSplit(name: "Upper Body", color: "blue", icon: "figure.strengthtraining.traditional", exercises: [
SeedExercise(name: "Lat Pull Down", weight: 110),
SeedExercise(name: "Tricep Press", weight: 100),
SeedExercise(name: "Chest Press", weight: 40),
SeedExercise(name: "Seated Row", weight: 90),
]),
SeedSplit(name: "Core", color: "orange", icon: "figure.core.training", exercises: [
SeedExercise(name: "Abdominal", weight: 0),
SeedExercise(name: "Rotary", weight: 0),
]),
SeedSplit(name: "Lower Body", color: "green", icon: "figure.run", exercises: [
SeedExercise(name: "Abductor", weight: 140),
SeedExercise(name: "Adductor", weight: 140),
SeedExercise(name: "Leg Press", weight: 160),
SeedExercise(name: "Leg Curl", weight: 70),
]),
]
/// Builds the default split documents (fresh ULIDs each call). /// Builds the default split documents (fresh ULIDs each call).
static func defaultSplitDocuments() -> [SplitDocument] { static func defaultSplitDocuments() -> [SplitDocument] {
let lists = ExerciseListLoader.loadExerciseLists() starterSplits.enumerated().map { order, split in
guard let catalog = lists[sourceFile] else { return [] } let exercises = split.exercises.enumerated().map { idx, item in
var docs: [SplitDocument] = []
for (order, group) in groups.enumerated() {
let items = catalog.exercises.filter { $0.split == group.name }
guard !items.isEmpty else { continue }
let exercises = items.enumerated().map { idx, item in
ExerciseDocument( ExerciseDocument(
id: ULID.make(), name: item.name, order: idx, id: ULID.make(), name: item.name, order: idx,
sets: 3, reps: 10, weight: 0, loadType: LoadType.weight.rawValue, sets: defaultSets, reps: defaultReps, weight: item.weight,
loadType: LoadType.weight.rawValue,
durationSeconds: 0, weightLastUpdated: nil, weightReminderWeeks: 2 durationSeconds: 0, weightLastUpdated: nil, weightReminderWeeks: 2
) )
} }
docs.append(SplitDocument( return SplitDocument(
schemaVersion: SplitDocument.currentSchema, id: ULID.make(), schemaVersion: SplitDocument.currentSchema, id: ULID.make(),
name: group.name, color: group.color, systemImage: group.icon, order: order, name: split.name, color: split.color, systemImage: split.icon, order: order,
createdAt: Date(), updatedAt: Date(), exercises: exercises createdAt: Date(), updatedAt: Date(), exercises: exercises
)) )
} }
return docs
} }
/// Writes any starter splits whose name doesn't already exist, appended after /// Writes any starter splits whose name doesn't already exist, appended after