Reads your body โ a morning check-in that meets your HRV halfway
The last three releases taught ZenMotion to notice patterns in your training โ the plateau it can help you break, the week it can reflect back, the session it can synthesise into a story. This release adds the missing piece: it starts listening to your body before the session, not just after.
Every morning now, at the top of Home, one question: How's your body today? Three taps: ๐ด tired, ๐ ok, ๐ช charged. Whatever you tap feeds directly into today's targets.
What each answer does
- ๐ด Tired. Today's targets scale down by 15%. The session still happens โ you still show up โ but ZenMotion softens the numbers so you can get the work in without digging deeper into fatigue. A "Recovery mode" pill appears on the next-workout card so you know why.
- ๐ OK. Nothing changes. One neutral day doesn't move the app's read of your state. But if you tap OK three days running without a Charged day in between, ZenMotion escalates on its own โ a soft "Take it lighter" pill fires with a 10% target scaling. The rule: one data point is noise, three is signal.
- ๐ช Charged. Targets stay put. We deliberately don't scale UP when you feel great, because that's exactly the failure mode that gets people injured chasing a good feeling. Instead the app suggests something specific โ aim for the top of your rep range โ which is the actual tactical move that unlocks a rung bump.
The HRV layer
If you wear an Apple Watch to bed, ZenMotion reads last night's HRV and compares it to your 7-day baseline. If today is 10% or more below baseline, ๐ด shows up pre-selected with a soft outline and a caption: "Based on your HRV โ change if you feel different." If today is 10% or more above baseline, ๐ช gets pre-selected the same way. Within 10% of baseline, the sensor stays out of the way and lets you pick manually.
Two design principles under the hood:
- The sensor suggests. You decide. The user is always the final authority on how they feel. HRV is a good-but-noisy signal โ a glass of wine, a late meal, a room too warm, all of them move HRV without meaningfully changing training readiness. So the pre-selection is a hint, not a decision. Tap the same option and we credit the sensor; tap a different one and we credit you.
- Nothing leaves your device. ZenMotion looks at the trend โ today vs your recent typical โ and derives a single hint. Your HRV samples never get sent to our servers, stored, or shared. The Health data stays on your phone; only the answer you tap makes it to the app.
If you don't have a Watch or haven't granted HealthKit read permission for HRV, the check-in is pure manual. Same tap flow, same recovery tiers โ you just get to pick without a suggestion.
Where the recovery signal lives
Today's readiness feeds into the same computeTrainingModifier service that already runs the deload cycles and ease-back-in logic (v1.7.3-1.7.5). Order of precedence, highest wins:
- Ease-back-in โ first session after 7+ days off (ร0.8, already resting)
- Recovery low โ today = tired (ร0.85, new in v1.20)
- Recovery accumulated โ 3d streak at ok+ (ร0.9, new in v1.20)
- Skill mesocycle โ if a Focus is active
- Deload week โ every 4th week app-wide
- None
Recovery beats deload/mesocycle because a fresh self-report ("I feel tired") is more current than a calendar-based cycle position. It loses to ease-back-in because a user who took a week off is definitionally recovered enough that stacking two "take it lighter" scalings would over-condition.
Under the hood
- New
UserReadinessLogtable stores one row per user per day. Score 1|2|3.sourcefield ismanualorhrvdepending on whether you agreed with the sensor. Migration is a plainCREATE TABLE, no data at risk during deploy. - New
POST/GET /v1/api/readinessendpoints. Backend'sresolveReadinessSignalservice composes the recovery modifier from the last 10 days of logs. - 13 backend unit tests + 10 iOS unit tests pin the streak math and HRV threshold logic. Nothing about "3 days in a row" can silently drift into "2 days" without a red build.
- iOS-side,
ReadinessHRVAnalyzeris a pure enum with one function:HRVSnapshot โ ReadinessScore?. Tests cover elite (baseline 120ms) and sedentary (baseline 25ms) users to make sure the ยฑ10% threshold works relatively, not absolutely.
The arc so far
- v1.17 โ Guided First Workout. Server picks your first session based on tier + focus.
- v1.18 โ Session Intelligence Pane. Three lines synthesising what today said about you.
- v1.19 โ Plateau + Weekly Rollup. The app spots patterns and offers specific answers.
- v1.20 โ Reads your body. Recovery-aware targets, subjective + sensor combined.
- v1.21 โ ??? (open โ depending on what the field data tells us)
The through-line stays the same: the same rung system, adaptive periodization, and rich session state that's been building since v1.7 becomes visible as concrete moments. This one closes the loop with an input the app couldn't previously see โ the day's felt state, before you even start.
Analytics we're watching
Firebase Analytics on v1.20 will give us three numbers:
- Check-in completion rate. How many users actually tap an emoji each morning? If it's under 30%, the card position or copy needs work.
- Score distribution. Are users evenly reporting tired/ok/charged, or is it 80% "ok" out of survey-taking politeness? A skew to "ok" means we need to rethink the design.
- HRV agreement rate. Of users who see an HRV hint, how many tap the pre-selected option? If it's below 40%, HRV is noise โ the sensor is disagreeing with the user's felt state, and we should quiet it down. If it's above 70%, HRV is trustworthy and we can consider adding sleep-integration in v1.22.
Bug reports and feature ideas: support@zenmotion.app.
Train well.