← Back to updates

One skill, one story.

Until this release, ZenMotion told two different stories about the same skill. Open the Programs tab, tap Planche, and you saw a rung-based roadmap: "You're on Straddle Planche (Light Assist), 12 of 23 cleared." Open the Stats tab, tap the same Planche row, and you saw a level-based timeline: "Level 2 of 9 · Tuck Planche Lean." Two systems, two answers, one confused user.

v1.11.0 collapses that. Stats-tab skill progress now reads from the exact same rung model Programs uses, sorts by how high you've climbed, and shows every skill you've calibrated — not a top-5 window. Same story, both tabs.

Stats now speaks rung

The "Skill progress" section on Stats Overview is fully rewritten. Each row shows:

Elbow Planche
Tuck Elbow Planche Hold
────────────────
Rung 6 / 21

The current rung name, the rung number, and a progress bar based on how many rungs you've actually cleared. Tap a row and you land in the same vertical roadmap view Programs uses — every rung listed, achieved ones marked with a checkmark, current one highlighted with your value vs the target, upcoming ones dimmed.

No more Lv 2 · Tuck Planche Lean if you're actually on Straddle. Both tabs pull from the same rung state — the single source of truth we've been building toward since Plan C.

Sorted by how high you've climbed

Old sort: recency of activity. Newest calibration/bump on top. Problem: users who broadly calibrated across all 11 skills saw a chaotic top-5 dictated by which test they took last, not what they're actually training.

New sort: rung height desc, then rungs cleared, then recency. Your most progressed skill leads the list. The skill you crushed 6 rungs into shows up before the one you just calibrated at rung 1. Ties on rung number break by clear-count (an organically-climbed rung 6 outranks a calibration-seeded rung 6). Same-rung + same-clear ties fall back to recency.

Also: shows up to 20 rows instead of 5. There are only 11 skill families total, so this effectively means "every skill you've touched, in the order that reflects your identity as a trainer."

Everything else in this build

Under the "one rung story" theme, but small enough that they'd normally be patch notes:

The Save banner that wouldn't die

Tap Save on a workout summary. Sheets close. Cold-start the app 10 minutes later. See "Continue your workout" banner on Home for the session you just saved. Baffling.

Root cause: iOS closes the summary sheets synchronously, but the /complete PATCH is async — and if it silently fails (bad signal, expired token, whatever), the local draft never gets cleared. The banner logic couldn't tell "user saved but network flaked" apart from "user force-quit mid-workout."

Fix: the workout draft got a new endedByUser flag. Tapping Save (or Discard) sets it before the sheets close. The banner filters on it, so pending-save drafts stay hidden. If save actually succeeded, the whole draft is cleared anyway and the flag is moot. If save failed silently, the banner still hides — and error tracking gets a warning log so we can see how often it happens.

Calibration polish

Two annoyances on calibration programs:

  1. "+ Add set" button was showing on max-effort tests. But calibration is one max-effort set per movement, on purpose — the point is to capture a clean strength signal, not a fatigued third-set number. Now hidden on any calibration program. Same fix on "+ Add round" for future circuit-style tests.
  2. Cold-body max-effort testing under-reports capability by 1-2 reps and risks strain. All 11 calibration programs now lead with a 3-exercise warmup block: Wrist Circles, Scapular Pulls, Scapular Push-Ups. Covers wrist prep + scap activation for every push and pull test.

Experience level picker retired

The Profile screen had a picker: 🌱 Beginner / 🔥 Intermediate / ⚡️ Advanced. Users would tap it, get a "Profile saved" toast, and… nothing would change. Because since v1.10.0, the backend derives experience level from your rung distribution — averaging your push/pull/squat/hinge/core rungs — and ignored the stored profile column for anyone with rung data.

The picker was a no-op for 99% of users. Misleading UX. Removed. Onboarding still writes an initial value (backend still reads it as fallback for zero-rung accounts), so nothing breaks; the control is gone because it didn't control anything.

Coming later this year: Athlete Profile as a read-only stats view — per-family + per-skill tier derived live from your rungs. The right way to answer "how strong am I really." Under design.

The crash you almost saw

Between "Stats now speaks rung" and this ship, there was a stretch where tapping a skill row from Stats would crash the app on longer ladders (23-rung planche was reliable) with a SwiftUI renderer signal. Reason: the roadmap view was designed as a section-inside-a-scroll-view, and pushing it directly onto navigation left its stretch-to-fit connector lines in an unbounded parent — infinite layout loop.

Fixed with a thin screen wrapper that provides the scroll container and background, matching the pattern the movement-detail view already uses. Same view, correctly framed for standalone use.

For the record

  • App Store version: 1.11.0 (up from 1.10.0)
  • Backend deploy: shipping the Stats swap + calibration warmups + achievement slug cleanup together with the iOS binary
  • No user action required — Stats will re-populate itself on next launch

If you were mid-workout when 1.10.0 was live and the save banner haunted you, apologies. It's really fixed now.

Feedback goes to support@zenmotion.app or Instagram.

Train well.