Dev Diary

Preserve the work, change the prescription

INCREMNT is an iOS strength-training app that helps athletes follow a plan and decide what to lift next; this week’s central change was making adaptation reversible, so a lighter return session or a delayed Coach check-in never rewrites work the athlete has already done.

  • Added a return-to-training choice after at least 42 days away: use a lighter session or keep the planned one.
  • Lighter sessions change only incomplete prescription rows; completed sets, warm-ups, the saved programme, and future targets stay intact.
  • Old completed drafts now reopen until the athlete explicitly finishes or discards them, instead of being silently cleared.
  • Weekly Coach check-ins now survive access verification and can offer a retry instead of disappearing behind a transient subscription state.

Product: adaptation should not rewrite the evidence

An athlete can be away for six weeks and still have a perfectly good programme waiting. The useful question on return is not “should the app replace my plan?” It is “how much help do I want for this session?”

INCREMNT now makes that choice visible. When the app finds at least 42 local calendar days since the most recent logged workout with completed working sets, it can show Ease back in. The athlete can choose Use lighter session or Keep planned session. The first option reduces the temporary prescription; the second starts the planned workout as it stands.

The boundary matters. A lighter session is not a deload, a programme edit, or a new progression target. It applies once to the current draft. The saved programme remains the saved programme. The next normal workout can still begin from the original plan, which keeps a cautious return from quietly becoming a permanent regression.

The adjustment is also careful about timing. It does not appear while restoring a draft, editing history, entering a retroactive workout, onboarding, or working through a deload. A warm-up-only record does not count as the last real workout, and a future-dated session cannot make the gap look shorter. Those details are product behaviour, not implementation trivia: the prompt should appear when its explanation is true.

There was a sharper version of this problem in the logger. Readiness can arrive after an athlete has already logged a set. Previously, the same array represented both recorded performance and the prescription still to come. Applying a reduction could remove a completed working set or change its weight. That makes the app’s memory of what happened less reliable precisely when the athlete needs it most.

The new rule is simple to use: completed sets—including completed warm-ups—are evidence and stay put. Only incomplete rows can be reduced, and a fully completed exercise is left alone. Accepting the lighter session records the temporary choice in the draft, so reopening the app continues the same session rather than recalculating it from a different plan.

The same instinct now protects abandoned work. A completed draft from an earlier day may be awkward, but it is still unsaved training. INCREMNT restores it until the athlete taps Finish or Discard. Age is a reason to ask a question, not permission to delete a record.

The benefit is less dramatic than a new workout type, but more important for trust. An athlete can return cautiously, correct a bad target, or get interrupted without wondering which parts of the session the app silently rewrote.

Building INCREMNT: make state boundaries executable

The implementation problem was that “what the athlete did” and “what the app suggests next” had been allowed to share mutable state. Once those concepts are separated, the rest of the design gets clearer.

ReadinessLogic.adaptPrescription now operates on the remaining prescription. It leaves completed set IDs, reps, weights, and warm-up flags untouched, removes only an incomplete working set when volume must be reduced, and rounds a temporary weight reduction down to the exercise’s increment. LoggerViewModel applies that transform once, marks the adjustment as temporary, saves the draft, and clears the return offer. The saved programme is never used as a scratchpad for this decision.

The tests describe the boundary in ways a screenshot cannot. ReadinessLogicTests adjusts a workout where sets were completed out of order and confirms every completed row survives. It also confirms that a fully completed exercise is unchanged. LoggerRecommendationTests restores an old completed draft, checks that a temporary prescription does not become progression history, and verifies that the next session returns to the original targets. LoggerPreviewLogicTests pins the six-week boundary and excludes missing, future, and warm-up-only history. The Maestro flow goes one step further: accept the lighter session, restart the app, complete it, and verify that the following workout shows the original plan.

This week’s other fixes follow the same shape. A weighted exercise with an invalid zero target can recover from the most recent valid working load without mutating the saved programme; bodyweight zero remains meaningful. The Coach weekly check-in keeps its pending card while access is refreshed, distinguishes active, inactive, and unavailable states, and offers a retry when a check-in could not start. In both cases, the system holds onto the user’s intent until it has enough evidence to act.

I applied that discipline to evaluation tooling too. The live summary gate now treats a missing required surface as an incomplete evaluation, even when its configured threshold is zero. Nested grades must be explicit booleans; a missing or truthy string cannot pass as a result. The directional check distinguishes “I don’t see a decline” from a current decline followed by a future-monitoring clause. That prevents a partial or malformed run from looking like a clean product signal.

There is a transferable engineering choice here: represent uncertainty as a state with a next action, not as a default mutation. returnToTrainingDaysAway, pendingWeeklyReviewCard, awaitingWeeklyCheckinAccessRefresh, and missingSurfaces are all deliberately boring names for that reason. They make it possible to test what the system knows, what it does not know, and what must remain available while the answer is being resolved.

The trade-off is more state and more tests. That is cheaper than repairing a workout after the app has confused a suggestion with history. It also makes failures legible: a retryable access check is different from an inactive subscription, just as an incomplete eval is different from a failed coaching claim.

Also moved forward

  • Prepared TestFlight 1.6.11 builds 467 and 468 for the weighted-target and weekly-check-in work. The release ledger records successful uploads and validation, while physical-device smoke remains open and the live App Store is still 1.6.10.
  • Added diagnostics and retry handling around Google authentication, transient PostgreSQL connections, and recovered Apple Watch mirror timeouts. The changes make failures easier to explain without claiming that every failure is fixed.
  • Tightened worktree setup, dependency reuse, PR validation, and deployment verification so a fresh checkout can prove its readiness without relying on checkout hooks.

What’s next

The next proof is device-level: run the return flow and weekly check-in retry through the TestFlight build under real access and interruption conditions. The code now preserves the decision boundary; the remaining work is to exercise it where the phone, subscription state, and athlete all have room to surprise us.

This diary was generated and edited from the week’s commits, pull requests, tests, and development notes using AI, evidence-checked, and automatically published, and may describe work not yet publicly released.