Record per-set actuals and drive the chart and volume from them
Every completed set now writes a SetEntry (reps/weight or seconds), pre-filled from the plan by transition(to:) so the list checkbox, both run flows, and One More all capture for free; reset clears, skip keeps partials. The rest and finish pages show the just-done set as a pill that opens a stepper sheet for correcting reps and weight (2.5 lb / 1.25 kg steps). The Weight Progression chart plots the top-set actual weight and workout volume sums recorded sets, both falling back to the plan for legacy logs via effectiveSetEntries. Storage side of UX #3 rides along: plan weights are Double now. Schema bumps: SplitDocument 2→3, WorkoutDocument 3→4 (a fractional weight fails an older Int decode, and a rewrite would strip the irreplaceable actuals), SwiftData cache 4→5. A per-log updatedAt is reserved for the future cross-device log merge. Claude-Session: https://claude.ai/code/session_01HJDQQDA9QdP8zByg43H5v3
This commit is contained in:
@@ -25,11 +25,11 @@ SwiftData cache entity (`Shared/Model/Entities.swift`) kept in sync by
|
||||
|
||||
---
|
||||
|
||||
## SplitDocument — `currentSchemaVersion: 2`
|
||||
## SplitDocument — `currentSchemaVersion: 3`
|
||||
|
||||
| Property | Type | Optional | Notes |
|
||||
|---|---|---|---|
|
||||
| `schemaVersion` | Int | | Forward-compatibility gate. Bumped 1→2 when the weight-reminder fields and `category` were removed from `ExerciseDocument` and `machineSettings` was added — removing required fields means older apps can't decode new files, so they must quarantine, not rewrite |
|
||||
| `schemaVersion` | Int | | Forward-compatibility gate. Bumped 1→2 when the weight-reminder fields and `category` were removed from `ExerciseDocument` and `machineSettings` was added — removing required fields means older apps can't decode new files, so they must quarantine, not rewrite. Bumped 2→3 when `ExerciseDocument.weight` went Int → Double — an older app decoding a fractional weight into `Int` fails, so it must quarantine |
|
||||
| `id` | String | | ULID |
|
||||
| `name` | String | | |
|
||||
| `color` | String | | Theme color name |
|
||||
@@ -49,16 +49,16 @@ SwiftData cache entity (`Shared/Model/Entities.swift`) kept in sync by
|
||||
| `order` | Int | | Sort position within the split |
|
||||
| `sets` | Int | | |
|
||||
| `reps` | Int | | |
|
||||
| `weight` | Int | | Unit-less integer; `WeightUnit` only relabels display |
|
||||
| `weight` | Double | | Unit-less; `WeightUnit` only relabels display. Int → Double in schema v3 (old integer values decode natively) |
|
||||
| `loadType` | Int | | Raw `LoadType` |
|
||||
| `durationSeconds` | Int | | Total seconds; 0 when not a timed exercise |
|
||||
| `machineSettings` | [MachineSetting] | ✓ | Ordered machine comfort settings; nil → not a machine exercise, empty → machine exercise with nothing recorded. Doubles as the machine-based flag |
|
||||
|
||||
## WorkoutDocument — `currentSchemaVersion: 3`
|
||||
## WorkoutDocument — `currentSchemaVersion: 4`
|
||||
|
||||
| Property | Type | Optional | Notes |
|
||||
|---|---|---|---|
|
||||
| `schemaVersion` | Int | | Bumped 1→2 when `metrics` was added (captured HR data is irreplaceable, so older apps must quarantine, not strip). Bumped 2→3 when the derived `completed` flag was removed from `WorkoutLogDocument` and `machineSettings` was added — dropping a required field means older apps can't decode new files, so they must quarantine |
|
||||
| `schemaVersion` | Int | | Bumped 1→2 when `metrics` was added (captured HR data is irreplaceable, so older apps must quarantine, not strip). Bumped 2→3 when the derived `completed` flag was removed from `WorkoutLogDocument` and `machineSettings` was added — dropping a required field means older apps can't decode new files, so they must quarantine. Bumped 3→4 when `WorkoutLogDocument.weight` went Int → Double and per-set actuals (`setEntries`) were added — a fractional weight fails an older decode, and a rewrite would strip the irreplaceable actuals |
|
||||
| `id` | String | | ULID (chronological — drives month bucketing and sorting) |
|
||||
| `splitID` | String | ✓ | Denormalized reference; no live relationship |
|
||||
| `splitName` | String | ✓ | Denormalized snapshot of the split's name |
|
||||
@@ -79,7 +79,7 @@ SwiftData cache entity (`Shared/Model/Entities.swift`) kept in sync by
|
||||
| `order` | Int | | Sort position within the workout |
|
||||
| `sets` | Int | | Planned sets (snapshot from the exercise) |
|
||||
| `reps` | Int | | Planned reps (snapshot) |
|
||||
| `weight` | Int | | Snapshot; unit-less integer |
|
||||
| `weight` | Double | | Snapshot; unit-less. Int → Double in schema v4 (old integer values decode natively) |
|
||||
| `loadType` | Int | | Raw `LoadType` (snapshot) |
|
||||
| `durationSeconds` | Int | | Total seconds; 0 when not a timed exercise |
|
||||
| `currentStateIndex` | Int | | Progress through the set sequence |
|
||||
@@ -89,6 +89,8 @@ SwiftData cache entity (`Shared/Model/Entities.swift`) kept in sync by
|
||||
| `machineSettings` | [MachineSetting] | ✓ | Snapshot of the exercise's machine settings at plan time; nil → not a machine exercise, empty → nothing recorded |
|
||||
| `startedAt` | Date | ✓ | First move to `.inProgress`. *Not schema-bumped* |
|
||||
| `completedAt` | Date | ✓ | Last move to `.completed`. *Not schema-bumped* |
|
||||
| `setEntries` | [SetEntry] | ✓ | Per-set actuals in set order; nil → legacy file / nothing recorded. Appended as sets complete (pre-filled from the plan); `transition(to:)` fills the missing tail on `.completed` and clears on `.notStarted`; `.skipped` keeps partials. Added in schema v4 |
|
||||
| `updatedAt` | Date | ✓ | Reserved for the future per-log merge (H1); nothing writes it yet. Added in schema v4 |
|
||||
|
||||
## MachineSetting (embedded in ExerciseDocument and WorkoutLogDocument)
|
||||
|
||||
@@ -100,6 +102,18 @@ string, not a number, because machine dials aren't uniformly numeric.
|
||||
| `name` | String | | e.g. "Seat Height", "Back Rest Incline" |
|
||||
| `value` | String | | Free-form, e.g. "4", "3rd hole", "45°" |
|
||||
|
||||
## SetEntry (embedded in WorkoutLogDocument, schema v4)
|
||||
|
||||
One performed set. Which fields are set follows the log's `LoadType`:
|
||||
weight → `reps` + `weight`; none (bodyweight) → `reps`; duration → `seconds`.
|
||||
|
||||
| Property | Type | Optional | Notes |
|
||||
|---|---|---|---|
|
||||
| `reps` | Int | ✓ | |
|
||||
| `weight` | Double | ✓ | Unit-less, like the plan snapshot |
|
||||
| `seconds` | Int | ✓ | Held/worked seconds for duration exercises |
|
||||
| `completedAt` | Date | | When the set finished |
|
||||
|
||||
## WorkoutMetrics (embedded in WorkoutDocument, schema v2)
|
||||
|
||||
| Property | Type | Optional | Notes |
|
||||
@@ -145,4 +159,4 @@ veto seed resurrection forever).
|
||||
`watch`, `phoneEstimate`
|
||||
|
||||
**WeightUnit** (`lb`/`kg`) is a display setting only — it is never persisted in
|
||||
documents, and stored weight integers are never rewritten when it changes.
|
||||
documents, and stored weight values are never rewritten when it changes.
|
||||
|
||||
Reference in New Issue
Block a user