Skip to content

steps: stop the calibration card asking for the half the user already has - #1815

Merged
ryanbr merged 2 commits into
mainfrom
fix/steps-calibration-asks-wrong-half
Sep 2, 2026
Merged

ryanbr merged 2 commits into
mainfrom
fix/steps-calibration-asks-wrong-half

Conversation

@ryanbr

@ryanbr ryanbr commented Sep 2, 2026

Copy link
Copy Markdown
Owner

A WHOOP 4.0 user with no synced motion asked whether entering their Apple Health steps by hand would
start NOOP counting and calibrating. It wouldn't — and the sheet is why they thought it might.

The misdirection

With no banked motion, the card still showed:

Need 1 more day where your phone also counted steps

That names phone-step days as the blocker. They aren't:

public static func estimate(motion: Double, calibration: Calibration) -> Int? {
    guard motion >= minMotionForFit, calibration.coefficient > 0 else { return nil }
    let raw = motion * calibration.coefficient

A step estimate is motion * coefficient, and a calibration point is the ratio steps / motion. Both
halves are required, so with the motion half missing neither the estimate nor the fit moves however
many days the phone counts. Setting the coefficient by hand doesn't help either — that sets the
multiplier, and the thing being multiplied is what's absent.

So the countdown pointed at the one input the user could supply, while the input they actually
needed went unmentioned in that card. The no-motion banner at the top of the same sheet explains the
real blocker, and the two were competing — the user believed the countdown.

The fix

The countdown and its explanatory line now render only when motion exists.

Deliberately no new copy. The banner already says the right thing; the fix is to stop
contradicting it rather than add a third message. The comparison card is untouched for the same
reason — "no days yet where both counted" is simply true in this state.

Net diff is two lines of code and a comment explaining why.

Scope

Not a fix for the underlying no-motion case, which on a 4.0 is a history sync that hasn't completed —
#1149 covers the harder version. This only stops the UI sending someone down a road that cannot work.

Verification

  • swiftc -parse clean; doc_comment_lint and i18n_audit --ci clean; no strings added or changed.
  • Not compiled and not seen. App-target Swift with no local build and no default CI job — -parse
    is syntax, not type-checking, and this changes what a card renders. The testing build's iOS leg
    compiles it; the state is reachable on any strap that hasn't synced motion.

🤖 Generated with Claude Code

ryanbr and others added 2 commits September 2, 2026 23:23
… has

A WHOOP 4.0 user with no synced motion asked whether entering their Apple Health steps by hand would
start NOOP counting and calibrating. It would not, and the sheet is why they thought it might.

With no banked motion the card still showed "Need 1 more day where your phone also counted steps",
which names phone-step days as the blocker. They are not. A step estimate is motion * coefficient
(StepsEstimateEngine.estimate returns nil below minMotionForFit), and a calibration point is the
ratio steps / motion - so both halves are required, and with the motion half missing neither the
estimate nor the fit moves however many days the phone counts. Manually setting the coefficient does
not help either: it sets the multiplier, and the thing being multiplied is what is absent.

So the countdown was pointing at the one input the user could supply, while the input they actually
needed went unmentioned in that card. The no-motion banner at the top of the same sheet explains the
real blocker already, and the two were competing.

The countdown and its explanatory line are now shown only when motion exists. Deliberately no new
copy: the banner says the right thing, and the fix is to stop contradicting it rather than to add a
third message. Same reason the comparison card is untouched - "no days yet where both counted" is
true in this state.

Not a fix for the underlying no-motion case, which for a 4.0 is a history sync that has not completed
(#1149 covers the harder version).

NOT COMPILED AND NOT SEEN: app-target Swift, no local build, no default CI job. swiftc -parse is
syntax only, and this changes what a card renders.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RupifTqaTTytpPjfpMvJuj
…own cannot drift

Re-review. The guard tested sampleMotion == nil, which is also true DURING the load - so in that
window the countdown disappeared while the banner had not appeared yet, and the card explained
nothing at all. The banner's own condition is didLoad && sampleMotion == nil; the two must be exactly
complementary, and written as separate expressions they drifted on the first attempt.

Both now read strapHasNoMotion, and the property carries why it exists.

Also found while checking what else depends on this: TodayView.stepsCalibrationCaption has the SAME
defect - it returns "Need N more days where your phone also counted steps" whenever the coefficient
is zero, motion or not - and so does its Kotlin twin, TodayScreen.stepsCalibrationPrompt. That is the
surface where a user actually notices steps missing, so it matters more than this sheet does.

Not folded in here. Neither Today screen has a motion signal to gate on, so fixing it means threading
one through both platforms and choosing between a persisted flag and a per-render query - a different
change from a two-line guard, and filed rather than smuggled into this one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RupifTqaTTytpPjfpMvJuj
@ryanbr
ryanbr merged commit b43b8f0 into main Sep 2, 2026
3 checks passed
@ryanbr
ryanbr deleted the fix/steps-calibration-asks-wrong-half branch September 2, 2026 11:30
ryanbr added a commit that referenced this pull request Sep 2, 2026
Build numbers only: versionCode 414 -> 415, CURRENT_PROJECT_VERSION 295 -> 296. MARKETING_VERSION and
versionName stay 11.1.0, which has only ever been cut as a testing build.

Carries two Apple-only UI fixes that no default CI job compiles - the live-workout capsule timer no
longer sitting under the pause button (#1814) and the steps calibration card no longer asking for
phone-step days when the missing half is strap motion (#1815). This build's iOS and macOS legs are
their first compile, and a screenshot is what confirms either of them actually looks right.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RupifTqaTTytpPjfpMvJuj
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant