You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
HeartRateRecovery.calculate requires 120 seconds of sustained Zone-3-or-higher effort near workout end, but both Swift and Kotlin currently add eligible durations across gaps longer than the 10-second continuity cap. Several short disconnected segments can therefore satisfy the gate even though no continuous segment reaches 120 seconds.
Deterministic reproduction
Use three 40-second high-HR segments, each sampled every 10 seconds and separated by an 11-second gap:
offsets from workout end: -300,-290,-280,-270,-260
-249,-239,-229,-219,-209
-198,-188,-178,-168,-158
all at 145 bpm with maxHR = 200 (70% threshold = 140)
cessation samples at -30,-15,0, 130 bpm
1-minute samples at +59,+60,+61, 110 bpm
workout range end-300...end
No continuous high-intensity segment exceeds 40 seconds. The expected result is null.
Observed on exact fork meta base 06e3ee7868d18a0af447b10eef712dbc92f52cfd:
Swift focused test: XCTAssertNil failed with Result(endHR: 130, after1Minute: Optional(20), after2Minutes: nil, after5Minutes: nil).
Kotlin focused test: assertNull failed with Result(endHr=130, after1Minute=20, after2Minutes=null, after5Minutes=null).
Cause
Both sustainedSeconds helpers skip an interval when gap > maximumContinuousGapSeconds, but retain the accumulated total. Later valid intervals resume adding to that same total instead of starting a new continuous segment.
Expected behavior
Eligibility should require at least one continuous qualifying high-intensity segment of 120 seconds. A disqualifying gap or below-threshold interval should break/reset the current segment; disconnected segments must not accumulate.
This is shared cross-platform behavior, not a Swift/Kotlin parity difference. The parity rollout should characterize it explicitly until a separately authorized production fix lands.
Fixed upstream by ryanbr#1901 (merged), which requires continuous effort for heart-rate
recovery eligibility: the tracker now resets on a gap over cap or a drop below threshold, so
disconnected segments no longer add up to an eligible bout. Both platforms, with a differential
case set. Closing.
Activity
bhelm commentedon Sep 7, 2026
Fixed upstream by ryanbr#1901 (merged), which requires continuous effort for heart-rate
recovery eligibility: the tracker now resets on a gap over cap or a drop below threshold, so
disconnected segments no longer add up to an eligible bout. Both platforms, with a differential
case set. Closing.