feat(power): "Low refresh" hourly sync, a sub-option of Power saving - #1430
Merged
Conversation
ryanbr
force-pushed
the
feat/low-refresh-sync
branch
from
August 18, 2026 10:28
e838300 to
98e59b3
Compare
Adds a user-elected hourly background-offload cadence under Settings -> Power saving, beside the existing battery threshold and "Pause HRV capture" levers. Unlike the battery lever it is not charge-gated: once chosen, 60 min is the BASE the other levers stretch from, at any strap charge. Fewer periodic offloads means fewer reconnect bursts, which is the measured WHOOP 4.0 drain mechanism (#1120/#1144) - a strap log from a 10.1.1 staging build showed 52 periodic offloads in a session, 26 of them idle-timeout stalls, each holding the link and re-connecting for little or no new data. Composition: every lever now stretches from the low-refresh base with `max`, so a lever can only make the cadence quieter, never restore a faster one the user asked to slow down. The 5/MG empty-history stretch (45 min) and the low-battery stretch (45 min) are both shorter than 60 min, so they become no-ops rather than speed-ups. Default off; with the toggle off every existing path is byte-identical. DELIBERATELY CADENCE-ONLY - the idea comes from @cbedsole03, whose version also stretched the keep-alive 30s -> 300s and released continuous HRV capture. Both are dropped here because they cost data rather than delaying it: - The keep-alive tick is not just a ping: it re-arms the WHOOP 4 realtime burst (R10/R11) every cycle so streaming cannot lapse, and it evaluates the stall fuse, which is 120s on a 4.0. A 300s tick under-samples that fuse, so a stalled 4.0 link would sit stalled up to 5 min - prolonging exactly the reconnect churn this feature exists to reduce. `startKeepAlive` already documents that the battery gain there "rounds to nothing on a 30 s timer". - Releasing continuous HRV capture drops dense beat-to-beat R-R while it is on, INCLUDING overnight, which is where that stream exists to be dense. Density not captured cannot be backfilled later, and it would silently override the user`s own Continuous-HRV-capture preference. "Pause HRV capture" (#477) and the #927 overnight window are the levers for that, and they stay in charge. So the shipped feature delays sync into larger batches and loses nothing: the strap banks to flash and only trims on our ack. PARITY: the Android twin lands in the same PR — NoopPrefs.lowRefresh, the WhoopBleClient LOW_REFRESH_BACKFILL_INTERVAL_MS/baseBackfillIntervalMs pair, the same max() composition inside nextBackfillDelayMs, and the same sub-toggle inside the Settings if(powerSaving) block. Both platforms apply on the NEXT re-arm (matching each side's existing battery lever), so a sync in flight is never cut. New copy localized de/es/fr/pl/pt-PT/zh on Android and de/es/fr/pl/pt-PT in the iOS catalog. Tests: LowRefreshCadenceTests (Swift) + LowRefreshCadenceTest (Kotlin) pin the same five cases — base on/off, and the three composition cases (low-battery lever, 5/MG empty-history stretch, unchanged default path). Validation: full Android suite 4042 tests / 490 classes green (5 new); compileFullDebugKotlin + processFullDebugResources + i18n --ci green. The Swift half is app-target, so it is validated by app-build; note StrandTests do not run in default CI, so the Swift twin of the cadence test is compile-checked there rather than executed.
ryanbr
force-pushed
the
feat/low-refresh-sync
branch
from
August 18, 2026 10:34
98e59b3 to
80c51f3
Compare
ryanbr
added a commit
that referenced
this pull request
Aug 18, 2026
Build numbers only. MARKETING_VERSION / versionName stay 10.1.1 — the staging line is one above the latest release (v10.1.0) and holds there until 10.1.1 actually ships. versionCode 341 -> 342 and CURRENT_PROJECT_VERSION 223 -> 224, both +1 past the previous staging build so a sideloaded update installs in place rather than being refused as a downgrade. Picks up since the last testing build (f2271cd): backup build provenance (#1422), the CTL/ATL/TSB training-load engine (#1423) and its Trends card (#1425), sleep-vs-wake HR contrast (#1424), the v20 optical decoder + CRC gate (#895), the Sleep UI split (#1426), the WHOOP4 clock-correlation fix (#1427), the settings waist-row layout + copy fix (#1428), hourly Apple step import (#1429), Low refresh (#1430), and Power saving moving into More with a 35% ceiling (#1431).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds "Low refresh" as a sub-option of Settings → Power saving, beside the existing battery threshold and "Pause HRV capture" levers. Idea from @cbedsole03.
Unlike the battery lever it is not charge-gated: once chosen, 60 min becomes the BASE cadence the other levers stretch from, at any strap charge.
Why
Fewer periodic offloads means fewer reconnect bursts — the measured WHOOP 4.0 drain mechanism (#1120/#1144). A strap log from a 10.1.1 staging build showed 52 periodic offloads in one session, 26 of them idle-timeout stalls, each holding the link and reconnecting for little or no new data.
Composition
Every lever now stretches from the low-refresh base with
max, so a lever can only make the cadence quieter, never restore a faster one the user asked to slow down. The 5/MG empty-history stretch (45 min) and the low-battery stretch (45 min) are both shorter than 60 min, so they become no-ops rather than speed-ups. Default off — with the toggle off every existing path is byte-identical.Deliberately cadence-only (and why)
@cbedsole03's version also stretched the keep-alive 30s → 300s and released continuous HRV capture. Both are dropped here, because they cost data rather than delaying it:
startKeepAlivealready documents that the battery gain there "rounds to nothing on a 30 s timer."So what ships delays sync into larger batches and loses nothing: the strap banks to flash and only trims on our ack.
Parity
Android twin in the same PR —
NoopPrefs.lowRefresh, theLOW_REFRESH_BACKFILL_INTERVAL_MS/baseBackfillIntervalMspair, the samemax()composition innextBackfillDelayMs, and the same sub-toggle inside the Settingsif (powerSaving)block. Both platforms apply on the next re-arm (matching each side's existing battery lever), so a sync in flight is never cut. New copy localized de/es/fr/pl/pt-PT/zh (Android) and de/es/fr/pl/pt-PT (iOS catalog).Validation
compileFullDebugKotlin,processFullDebugResources(aapt) andi18n_audit.py --cigreen.StrandTestsdon't run in default CI, so the Swift twin of the cadence test is compile-checked there rather than executed; the Kotlin twin pins the same five cases and does execute.