halkyon/docs/adr/0035-train-strength-plans.md
Tizian 「ティツィアーン」 508994be3a
feat(train): strength plans and sessions (ADR 0035)
Strength plans of exercises from a catalogue or the person's own, in
blocks of one exercise or several done in turn (supersets, circuits),
each with its sets, reps or seconds, weights and rests; each a sealed
`strength_plan` row. Plans are marked with what the place chosen lacks,
and a session offers swaps moving the same way that the place allows.

A session runs on every device, set by set with the rest counted down,
each weight made up from the place's plates, bars and fixed weights with
the plate changes between sets, kept in strength_session.json after each
change so one cut off is carried on, and saved as a workout of the new
activity Strength with its sets, listed on its page; strength training
in Health Connect and FIT, no GPX.

Migration 20261102113104496-strength-plans is purely additive: it
creates the table strength_plan (id uuid v7, spaceId, keyId, the sealed
payload and its nonce), its index on spaceId, and a foreign key to
offline_sync_spaces with ON DELETE CASCADE, so deleting a person's sync
spaces deletes their plans; then the modules' version rows. The
gen_random_uuid_v7 function is created or replaced as in every
migration.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-02 13:31:51 +01:00

6.2 KiB
Raw Permalink Blame History

ADR 0035: Train's strength plans and sessions

  • Status: decided 2 Nov 2026, by the developer (D1–D4, three of them the agent's recommendations, D2 the developer's own choice), the rest proposed by the agent and flagged where it chose.
  • Builds on: ADR 0034 (the places, their equipment and the plate maths), ADR 0030 (Train: D7 sealed rows, D42 interval plans, D60 the tabs).

Context

ADR 0034 built the inventory and the plate maths, and left strength plans as the next step: a plan naming its exercises, sets, repetitions and weights, the plate changes between its sets ordered by the plate maths, and "the training offered adjusting" to the place chosen. Train records cardio alone so far: no model holds an exercise, a set or a repetition.

Decision

  1. Exercises come from a catalogue, with the person's own beside them (D1, the recommendation taken). The catalogue (exercise_catalogue.dart) holds some 45 common exercises (squats, hinges, presses, rows, pull-ups, curls, extensions, raises, core, carries), each with:

    • what it needs (ExerciseNeed: a barbell, an EZ bar, a trap bar, dumbbells, a kettlebell, a bench, a rack, a pull-up bar, dip bars, a cable machine, bands), each met by a place's equipment (dumbbells by fixed dumbbells or by handles with plates);
    • what it is loaded with (ExerciseLoad: a barbell, an EZ bar or a trap bar with plates, a pair of dumbbells, one dumbbell, a kettlebell, a cable's stack, a band, the body), which decides how its weight is made up;
    • its pattern (MovementPattern: squat, lunge, hinge, horizontal and vertical push and pull, curl, extension, raise, calf, core, carry), by which swaps are found (D4);
    • whether it is counted in repetitions or held for a time. Its names are the app's texts, by the exercise's id. The person's own exercise is a name with the needs, load, pattern and measure they pick; it lives in the plans that use it (no table of its own), and the editor offers those of every plan again.
  2. A plan is blocks of exercises, a block of several done in turn (D2, the developer's choice: supersets and circuits too). A block holds one exercise or more, each with its sets (repetitions or seconds, and a weight in grams where it is loaded), and a rest:

    • one exercise: its sets one after the other, the rest between them;
    • two (a superset) or more (a circuit): a round is each exercise's next set in turn, the rest after each round, and a rest between the exercises within a round (none by default). An exercise with fewer sets drops out of the later rounds. What a block is called (exercise, superset, circuit) follows from how many it holds.
  3. This step records a session too (D3, the recommendation taken), on every device, as nothing in it needs the phone's sensors. The session screen leads set by set: the exercise, the set and round, the weight made up at the place chosen (D5) with the plates to change since the exercise's set before, the repetitions or the time, and Done; after it the rest counts down (a tone and a vibration as it ends, where the device has them), skippable. What was actually done is typed over what the plan said. A set can be skipped, an exercise swapped (D4), the session ended at any point. Saved, it is a workout of the new activity strength with what was done (WorkoutStrength: the plan's name, the place's, and each set done: its exercise, repetitions or seconds, weight and time), shown on the workout's page as the exercises with their sets. A session under way is kept in the person's strength_session.json after every change, so one cut off is offered again.

  4. The place marks what it can't do and offers swaps (D4, the recommendation taken). A plan whose exercises the chosen place lacks something for is marked in the list, naming what is missing; a session's exercise the place can't do offers, in its place, the catalogue's exercises of the same pattern the place can, and any exercise can be swapped so. A swap holds for the session alone.

  5. A strength plan is a sealed, synced row of its own (the agent's): the table strength_plan, as plan and training_place are, rather than a kind of plan, whose readers in released apps would show it as locked. The migration only creates the table, its space index and its cascade to the sync engine's spaces.

  6. A strength workout keeps workout schema 1 (the agent's): the activity strength is new, and apps that don't know an activity already skip the workout rather than show it half (decodeWorkout); its sets are a field only strength workouts have. It goes to Health Connect as strength training, to FIT as training/strength training, to TCX as Other; it has no track, so no GPX.

  7. How the weight is made up (the agent's): for a bar, the plate maths of ADR 0034 on the place's bar of that kind, the plates of the exercise's sets ordered by loadSeries so that its plate changes are fewest; for a pair of dumbbells, the place's fixed pair nearest at or below the weight, else two handles with plates; for one dumbbell or a kettlebell, the nearest at or below; a cable's stack, a band and the body take the weight as typed. Where nothing at the place makes the weight up, the session says so and takes it as typed.

  8. Where it shows (the agent's): the Record tab gets a Strength card beside the activities, its Start offering the strength plans, and the strength plans are listed under the interval plans; where the device doesn't record, Profile's Plans tab lists both, a strength plan's Start there too.

Consequences

  • Progression (raising a weight once every set was done) and a one-repetition maximum with weights as a share of it are left for later; the sets done are kept so that both can be worked out from them.
  • A session runs in the app alone, not in a service: on Android a session left in the background keeps its time but rings its rest's end only while the app is open; the next step can hand it to the recorder's service if that matters in use.
  • The catalogue grows by adding to it and its texts; an exercise's id never changes, as plans and workouts keep it.