← Back to updates

Not every week is a push week.

Every fitness app that logs reps eventually runs into the same problem: the progression is monotone. You crush 10 pull-ups, the app asks for 11. You crush 11, it asks for 12. You come back after a two-week trip and the app hands you 14, which you can't get close to, so you skip a session, then another, and by the time you're back the streak is dead.

Real training doesn't work like that. Real training has rhythm — hard weeks, easier weeks, and softer landings after time away. Coaches call it periodization, and until now it lived entirely in your head (or your coach's spreadsheet). ZenMotion v1.7.3 bakes it into the app.

Deload weeks — every 4th week is a step back

Every 4th calendar week since your first completed workout, target numbers scale down by 20%. Sets stay the same, but the reps or hold seconds you're asked to hit are lighter. Then normal progression resumes the following week.

The math looks like this:

  • Week 1: target = 10 pull-ups. You hit it clean.
  • Week 2: target = 11. Clean again.
  • Week 3: target = 12. Clean.
  • Week 4 (deload): target = 10 — the app shows a banner: "Deload week — push less, recover more." You do 10, feel great about it.
  • Week 5: target = 12 again. You crush it, and the ladder keeps climbing.

This is how coached athletes have programmed forever. Three weeks of build, one week of back-off. It gives your central nervous system time to consolidate the gains you actually made, so week 5's PR isn't a fluke — it's the payoff for taking week 4 seriously. The app never forgot your progress; it just gave your body room to catch up.

The banner on Home explains what's happening the moment it starts, so you never have to wonder why your prescription softened. Push less, sleep more, come back stronger.

Easing back in — the softer landing after time away

The other half of the picture: what happens when life gets in the way and you skip a week. Or two. Or a month.

Before v1.7.3, ZenMotion would hand you the exact same brutal target you couldn't finish before you left. You'd fail it, feel worse, and probably not come back the day after. That's the retention story every fitness app quietly loses.

Now: if your last completed workout was 7 or more days ago, the first session back automatically scales targets down 20% with a warm "Easing back in" banner. The message is: welcome back — no pressure — just get the rust off. One session later, normal targets return, but you've re-established the movement, the confidence, and the streak.

You'll see this trigger after a real break — vacation, illness, life. Not after a normal 3-day rest week.

When both would apply, ease wins

The obvious edge case: what if you happen to be in a deload week AND you've been away for 12 days? Compounding both to ×0.64 would over-condition the return. So ease-back-in wins by design — a rested user gets one nudge, not two. The banner shows "Easing back in", the multiplier is a clean 0.8, and life continues.

The workout-save bug — quietly fixed

For a small handful of users with older accounts, PATCH /complete was returning 500 sometimes. The workout summary would close but the "workout in progress" pill would stay, forcing the user to try again. It was intermittent and infuriating for the people affected.

Root cause was a race in the auto-progression bookkeeping: when a UserExerciseTarget row pre-dated our movementId backfill (migration from a previous release), our lookup logic missed the legacy row and tried to create a duplicate. PostgreSQL's unique constraint quite correctly refused, the exception bubbled up, and the whole /complete request tanked with a 500. Users on newer accounts never saw it; users from before the backfill hit it every time they touched an old exercise.

Fixed by adding a fallback lookup (movementId first, exerciseId second) plus wrapping the bookkeeping in try/catch so a max/target update failure can never take down the workout save again. A regression test locks the behaviour in.

If you were one of the users who saw "workout won't save" or "save closes but stays" in the last week — this fixes you specifically. Sorry about that.

HealthKit — permission on a calm screen

The Apple Health permission sheet used to appear during the workout-save flow, timed such that it popped up over Home a beat after the summary dismissed. It felt like an interruption. Sometimes people would tap Deny by mistake because the sheet appeared unexpectedly.

Moved the prompt to post-auth app launch. Now the first time you open the app on a fresh install (after Sign in with Apple + onboarding), Apple's permission sheet lands calmly on Home with nothing else in flight. Existing users who already answered never see it again — the check is idempotent.

Focus picker — clearer switching

Small UX detail that had been bothering us. If you already had "Planche" set as your focus and tapped "Handstand" in the picker, the app would silently switch focuses with no confirmation. Some users read this as "I can have multiple focuses" — actually they were just cycling through choices without realising.

Now:

  • Tapping the row that's already your current focus is a no-op (the row is disabled, the "Active" pill is enough)
  • Tapping a different focus shows a confirmation: "Switch focus from Planche to Handstand? Your strength split's accessory slots will shift to support Handstand instead."
  • The intro copy at the top of the picker explicitly says "only one focus at a time"

The database has always enforced this — max one row per user in the UserFocus table — but the UI now speaks the same language.

Smaller wins

  • Sentry noise reduction. Task cancellations (from tapping back mid-fetch) used to log to Sentry as warnings. They're normal behaviour, not errors. They're filtered out now.
  • HealthKit auth threading fix. The auth prompt was being called from an actor's serial executor, which on iOS 26 sometimes tripped an internal main-thread assertion. Now the auth call explicitly hops to @MainActor before touching HealthKit — belt and braces.
  • iOS 27 beta AsyncRenderer crashes documented. If you're on the iOS 27 developer beta, you may occasionally see a crash entering certain nav destinations. This is an Apple bug in the beta OS's new async SwiftUI renderer, not ZenMotion. Filed a diagnostic ticket in our own backlog to revisit if iOS 27 GA still has it.

This release is a philosophical shift as much as a technical one. The whole point of an app that logs your reps is to make sure they're the right reps. That means sometimes the right reps are fewer than yesterday. It means sometimes coming back gently is the smart move. Fitness apps that don't understand this quietly drop everyone who takes a break — and everyone takes breaks.

If you notice one week where your targets feel softer than usual, don't skip it. That's the week that turns the previous three weeks of grinding into an actual PR. Push less, sleep more.

— Jacob