← Back to updates

Runs with signal.

v1.6.4 shipped the new Stats tab — heatmap, recent PRs, per-exercise charts. The Runs list on that tab stayed pretty but quiet: a card per workout that told you how long the session was and which level, then forced you to tap in to learn anything else. This release rebuilds the Runs surface so a glance is enough.

Four threads landed: a denser Runs list with a signal strip per card, a redesigned detail view with a session summary and status tags, real skill-progress rows on the Overview (replaces the "Calibrate a skill" empty when you have at least one skill program in flight), and a foundational fix so rep targets follow you across every program that uses the same movement.

Runs list, redesigned

Each run card used to occupy roughly a third of the screen and showed three things: time, level, date. The date was duplicated from the section header and the other two told you nothing about whether the session went well.

The new card is ~40 % shorter — you see 4 to 5 runs per scroll — and each one carries a signal strip in monospace:

  • Duration (always shown)
  • PRs / Baselines / Ended early — middle cell adapts to what happened: a regular workout shows "3 PRs", a Strength Calibration shows "3 baselines" (a baseline test sets numbers, it doesn't beat them), a partial run shows "ended early"
  • Volume / Completion — strength runs show total volume in tonnes ("5.2 t"), other runs show attempted-vs-prescribed exercises ("5/5")

A thin accent stripe runs down the left edge of each card — teal for calibration runs, dim teal for everything else — and partial runs dim to 0.62 opacity so abandoned sessions step back visually without being hidden. The date now lives only in the section header (with a "1 run" counter), since putting it on every card was a waste of pixels.

Filter chips replace the hamburger

Above the Runs list there's now a horizontal chip row: All · Strength · Skill · Calibration. Chips for categories you've never trained hide automatically — a brand new user only sees the chips they can actually filter to. Tapping a chip narrows the list immediately. The legacy filter sheet stays accessible for power users who want to slice by individual program + level, but the chips cover the 90 % case.

Detail view, calmer and more informative

Open any past workout and the top of the view now shows a 4-cell session summary:

  • Duration
  • PRs (or Baselines for calibration runs)
  • Volume (or Reps logged for calibration)
  • Goals met %

Below it, each exercise card got a serious rework. The old heavy teal-gradient is replaced with a subtle top-only highlight, the ambiguous seal icons (filled-teal vs muted-outline, which most users couldn't decode) are replaced with explicit tags: PR with a diamond, Goal for clean target hits, Partial for short of target, Baseline for calibration sessions. Each card now has a progress bar against its goal — teal when the user matched or beat the prescription, muted grey when they fell short. The trend chip and "vs best" line stay where they were — that signal was already working.

Real skill progress on Stats Overview

The Skill progress section on Stats Overview used to be an inline empty saying "Calibrate a skill". For users who already had skill programs in their history, it stayed empty regardless — there was no roll-up endpoint. v1.6.5 adds one. Now the section lists the user's active skill programs (Front Lever, Planche, Muscle-Up — whatever they've trained at least once), with:

  • Current level + total levels (Lv 3 / 6)
  • Headline name of the current level
  • Mini progress bar of completed levels
  • Best hold seconds at the current level (when applicable)

Tap any row and you land in the SkillTimelineView — the milestones view that's existed since v1.6.4 but lacked an entry point. Users without any active skill programs still see the "Calibrate a skill" empty pointing them at the calibration test.

Targets that travel

This is the quiet but important fix. v1.6.2's auto-progression bumps your prescribed reps when you clean every working set. The intent was always "your bumped Pull-Up target follows you across every program that uses Pull-Ups". The reality was that targets were keyed by the specific ProgramExercise row, not the underlying movement — so a bumped Pull-Up target in your strength split didn't apply in a skill program's pull-up accessory work, even though it's the same movement.

v1.6.5 fixes it. Targets are now keyed on the abstract Movement (the master table that landed in v1.6.4 for Stats drilldowns), and /api/targets expands one target into every catalog exercise that shares the same movement. Practically: clear 10/10/10 on Strict Pull-Ups in Upper Body Strength, and the next time Strict Pull-Ups appear anywhere — Push & Skill warm-up, a custom program, the strength split — they show the 10-rep target.

A backfill script migrates existing target rows so users with bumped targets pre-1.6.5 keep them.

Smaller fixes

  • NetworkRequest URL query items — internal: iOS request structs declared queryItems but the URL builder ignored them. The Stats-tab endpoints sending ?days=84&tz=Europe/Stockholm were silently using server defaults. Now properly attached.
  • Stats heatmap fallback — if the new /heatmap endpoint is unreachable for any reason, the card derives consistency from your existing history list instead of going empty.
  • HistoryDetails tap-routing — exercise rows in a past workout that have a movementId (post-Fas-2a backfill) now route to the per-movement drilldown chart. The chart has been there since v1.6.4 but the entry point was patchier than intended.

The thread tying this release together: every signal in the Stats tab — a card, a tag, a number — should answer a question without forcing you to drill in. A 27-second card with "ended early" reads correctly. A "5.2 t" volume cell sets the apples-to-apples comparison. A "PR + diamond" tag means something the seal icon couldn't.

Email me if anything feels off — support@zenmotion.app. Every message still reaches the person who wrote the code.

— Jacob