Let routines rename exercises and repeat them for interval segments

An exercise's name is now a per-routine display name: a new optional
libraryName on ExerciseDocument (snapshotted onto WorkoutLogDocument at
plan time) keeps the link to the bundled library exercise, and every
figure/info/cue lookup resolves libraryName ?? name. Deliberately not
schema-bumped — an older app dropping the key only strands the
figure link, same rationale as activityType. Cache schema bumped to 9
for the new columns.

The picker no longer filters out exercises already in the routine
(an "×N" badge marks them instead), exercise rows gain a leading
Duplicate swipe that clones an entry in place, and the edit sheet gets
a Name field with the library exercise shown read-only above it.

Claude-Session: https://claude.ai/code/session_01H8VxUX4ckjU3vRF5M4L5FV
This commit is contained in:
2026-07-12 18:38:14 -04:00
parent d04526826c
commit df5c77eee1
15 changed files with 203 additions and 38 deletions
+24 -1
View File
@@ -71,6 +71,19 @@ struct ExerciseDocument: Codable, Sendable, Equatable, Identifiable {
/// empty a machine exercise with nothing recorded yet. This optionality
/// doubles as the machine-based flag there is no separate Bool.
var machineSettings: [MachineSetting]? = nil
/// The library exercise `name` was customized from (e.g. "Treadmill" renamed to
/// "Brisk Walk 10 min" for one segment of an interval routine); nil `name` IS
/// the library name. Optional and deliberately NOT schema-bumped, same rationale
/// as `RoutineDocument.activityType`: an older app dropping it on rewrite only
/// strands the figure/info link for the renamed exercise, which is preferable to
/// quarantining the user's whole routine.
var libraryName: String? = nil
}
extension ExerciseDocument {
/// The name to resolve figure/info/cues against `name` itself unless it was
/// customized, in which case the library exercise it derives from.
var libraryExerciseName: String { libraryName ?? name }
}
/// A single free-form comfort setting for a machine-based exercise. `value` is a
@@ -251,9 +264,18 @@ struct WorkoutLogDocument: Codable, Sendable, Equatable, Identifiable {
/// on two devices merges by the newer `updatedAt`). Nothing writes it yet;
/// absent in older files.
var updatedAt: Date? = nil
/// Plan-time snapshot of the exercise's `libraryName` (like sets/reps/weight).
/// Optional and deliberately NOT schema-bumped, same rationale as
/// `RoutineDocument.activityType`: an older app dropping it on rewrite only
/// strands the figure/info link for a renamed exercise, which is preferable to
/// quarantining the whole workout.
var libraryName: String? = nil
}
extension WorkoutLogDocument {
/// The name to resolve figure/info/cues against see `ExerciseDocument.libraryExerciseName`.
var libraryExerciseName: String { libraryName ?? exerciseName }
/// Build a fresh plan-time log from a routine's exercise. Snapshots the plan
/// fields (sets/reps/weight/duration) *and* the machine comfort settings, so a
/// running workout carries the exercise's settings as its own editable copy
@@ -274,7 +296,8 @@ extension WorkoutLogDocument {
status: WorkoutStatus.notStarted.rawValue,
notes: nil,
date: date,
machineSettings: exercise.machineSettings
machineSettings: exercise.machineSettings,
libraryName: exercise.libraryName
)
}