← Back to updates

In the loop — Announcements, save-recovery, and Home that keeps up

Two kinds of "did something just happen?" have been quietly bugging ZenMotion since Challenges shipped. The first: new features ship and users don't notice — you'd have to scroll through Programs or read the changelog to know Challenges even exists. The second, more painful: you tap Save, sheets close, you think you're done — and the workout never actually saved. A network dropped between your phone and the server, /complete threw, and your session vanished. No history row. No achievement toast. No sign anything went wrong.

v1.28 is the release that fixes both. It also fixes a third quiet bug that turns out to be everywhere in the app: after you save, Home takes a minute to catch up. All three are in the same theme — the app should tell you what's happening, and Home should reflect what just happened. It shouldn't be your job to notice.

Announcements you can actually see

New features that used to hide in Programs or Stats now get a proper on-launch introduction. When something meaningful ships — Challenges last month, this pending-save-recovery banner now — the app opens with a single-screen sheet that explains what it is, why it exists, and where to find it. Tap the CTA to jump to the feature. Tap Later to dismiss.

Two things make it work:

  • Server-driven. Announcements live in a JSON registry on the backend, not baked into the app. That means we can retroactively highlight a feature that shipped three versions ago, run a maintenance heads-up ("scheduled downtime tonight, sorry"), announce a promotion, or bump the version to re-surface an announcement for people who dismissed the first version. All without a client update.
  • Modal window. Each announcement has a 30-day window (tunable per-item). After that the modal stops surfacing but the red-dot navigation hint stays until you visit the target tab.

There's a version field on each entry, so if we improve an announcement's copy or add a new detail, bumping the version re-shows the modal for people who dismissed the previous one. And only one sheet at a time — if there are two announcements queued, you see the first this launch, the second on the next cold-start. No stacking.

Save that never quite saved

The bug: you tap Save after a session, the workout summary closes, and Home appears. That's the last moment the app is on-screen for the /complete network round-trip. If the network drops during those ~5-9 seconds (dropped wifi, backgrounded phone, backend hiccup), the PATCH fails silently. Your workout doesn't make it to the server.

Prior to v1.28 this was invisible. Sentry data over a 30-day window showed 7 unique users hitting this state — small in absolute numbers, catastrophic in retention terms, because from the user's perspective the app "ate their workout."

v1.28 surfaces the failure and offers a retry. If Home detects a workout draft that was voluntarily ended but never saved, a banner appears above the Continue-your-workout slot:

Couldn't save your workout
Planche Focus Split · 6 exercises unsaved
[Discard] [Retry]

Retry re-runs the whole /start + /complete round-trip against the persisted draft. On success, the workout lands in history, achievements retroactively unlock, and rung-bump toasts fire — same as if the original save had worked. On failure, the banner stays and you can try again later. Discard clears the draft; data loss is accepted, banner disappears.

The interesting engineering detail is that a /start doesn't run until Save-tap in ZenMotion, not at workout-begin. So there's no orphaned server-side ProgramRun to reconcile — the retry creates the row fresh, and the persistence side is idempotent all the way down.

Home that catches up

Small but pervasive: after you save a workout, Home would show yesterday's recommendation until you navigated away and back. The cause was a race between the sheet-dismiss animation (which triggers Home's refresh) and the /complete PATCH landing on the server (~5-9 seconds later). Home's refresh fired before the workout was committed, so /api/home returned pre-save state. Then /complete finished quietly, and Home stayed stale.

v1.28 threads a second refresh through the workout-save code path. After /complete lands, the workout view bumps a signal that Home listens to; Home reloads /api/home after the run is in the database. The banner-worthy version of "Home caught up": if you crushed a session and hit a plateau nudge threshold or unlocked a rung, that shows up on Home before you have to go looking for it.

The small stuff

  • The announcement system supports four kinds: feature (default, teal accent), maintenance (orange, wrench glyph), promotion (gold, gift), and info (blue, info-circle). Same shell, different visual language so a maintenance announcement doesn't feel like a feature launch.
  • Dismissed announcements persist per-user across cold-starts. Re-installing the app fetches the current registry fresh and starts from zero — but that's a corner-case worth accepting for the simplicity of the model.
  • Analytics on the recovery banner tracks a five-event funnel: detected, retry-attempted, retry-succeeded, retry-failed, discarded. Ratio-based diagnosis: if retry-succeeded / retry-attempted is <30%, we know the backend endpoint is the problem, not the client; if detected → retry-attempted is <40%, the banner copy is losing people and needs a rewrite.
  • The pending-save banner and the existing "Continue your workout" banner are mutually exclusive by design — regression-tested — so you never see two different "come back to your session" prompts stacked on top of each other.

What's next

More announcements as features ship. Challenges Phase B — the Discover surface of curator-picked public challenges — is next in queue, and it'll ship with an announcement telling you about itself. And a scheduled maintenance announcement will appear the next time the backend needs a planned window, so nobody has to guess whether the app is broken or the server is just resting.

Nothing slips past you now.