Repository navigation
NOOP 11.0.0
NOOP v11.0.0
A major release, and the version number is about one thing above all: the WHOOP 5.0 and MG finally hold
a connection. Everything else — a body clock on the Sleep screen, nights that read straighter, a Journal
that stops counting silence against you — arrived alongside it.
The WHOOP 5.0 / MG connection
A handshake the strap never answers no longer takes the link with it (#1635, #1642, thanks
@Zebsi235). NOOP wrote a CLIENT_HELLO on every connect and the 5/MG never acknowledged it; roughly
4.8 seconds later the link dropped, and the cycle repeated all day. NOOP now recognises the pattern,
stops writing the handshake for that strap, and keeps the link — the "Live HR, not fully paired" state
the app already modelled, reached deliberately instead of never. A field capture confirmed a link held
for over seven hours where it previously lasted five seconds.
An HCI capture contributed by @Zebsi235 settled what the strap is actually doing: it answers the
pairing request with Pairing Not Supported. That is a refusal, not a timing problem, and it ended
weeks of guessing.
Reconnecting costs what it should (#1778, #1779, #1781). Tapping Connect used to throw away
everything NOOP had learned and re-earn it over five more dropped links. It now keeps that knowledge,
gives the handshake exactly one fresh attempt, and settles after a single reconnect. A timer firing is
no longer treated as you asking to try again.
The strap is asked properly, and says so in the log (#1639, #1646, #1648, #1652, #1653, #1656,
#1668). NOOP now observes the OS pairing flow rather than inferring it, reads the Device Information
Service on a strap that never bonds, enumerates what a strap actually offers once per device, and lets
a 5/MG identify itself instead of being guessed at from its serial.
Clearing a stale phone pairing (#1790). When the phone holds a pairing the strap no longer honours,
NOOP can now clear it for you — off by default, in Test Centre, once per streak, and only after the
guide has already asked you to do it by hand.
Sleep
Your body clock (#1722, #1723, #1726, #1729, #1731, #1733). A 24-hour dial on the Sleep screen
showing your circadian phase, with an absolute chronotype bucketed from it. The diagnostics report the
inputs it used, and a rhythm too small to be real is refused rather than drawn.
A partial night is not a whole one (#1717, #1721, #1734). A hypnogram that only partly downloaded
was scored as though complete, which quietly shortened nights after an interrupted sync. Both readers
of that coverage now agree on what "complete" means.
Fragmented nights are stitched back together (#1755, #1782). When data is sparse, NOOP bridges
short gaps between sleep runs — and now allows a longer interruption when your heart rate stayed in the
sleep band throughout, because on a strap with no motion data heart rate is the better witness.
Sleep debt you can act on (#1741). Debt is now expressed as a target for tonight rather than an
abstract ledger.
Journal, workouts and metrics
Yes days are compared against No days (#322). Previously an insight weighed the days you logged
against every day you did not — so a week you never opened the app counted as a week of No.
Pause and resume a live workout (#1690). The controls are on the active-workout card, and the five
clocks that used to ignore a pause now honour it.
Latest Workouts opens a session (#1701, #1703). Tapping a row goes to its detail, and iOS uses the
same 14-day window Android already did.
Skin temperature leads with the absolute (#1663, #1665, #1671, #1708). The nightly absolute is
stored and shown first, deviation beneath it, in your own units throughout.
VO₂max says why a line breaks (#1740). The chart shows the decimal it draws, and explains a gap
rather than leaving one.
Straps, sync and privacy
Multiple straps are kept apart (#1455, #1634, #1730, #1745). Physiology is read across every
registered strap, firmware is attributed to the strap it came from rather than the last to connect,
last-sync belongs to a strap rather than the install, and a 5/MG carries a stable identity derived from
its serial.
Health Connect permissions by category (#1509, thanks @kavemang). Recovery and wellness, Activity,
and Body composition are picked before Android's prompt. Existing installs are detected and keep every
category they already had.
A crash you can report (#1718, #1720, #1724). Android shows a copyable recovery screen after a
crash, redacted everywhere it leaves the device — and an upgrade no longer greets you with a crash from
the build it replaced.
Update checking, on by default (#1674, #1675). Both platforms notice a new release. The privacy
documentation now describes what actually happens.
Experimental self-hosted push (#1631, #1700). One-way push to your own endpoint, off by default,
with the destination errors localized.
Oura
A bonded ring with no advertised name is listed again (#1776, thanks @pipiche38). A bonded ring
often stops advertising a name, and that was exactly the ring the Add-Device screen could not show.
Skin Temp as a Key Metrics tile (#1678), and protocol corrections (#1629, #1687, #1777).
Under the hood
Raw 100 Hz IMU capture and labeled recording sessions for the 5/MG (#1709). A JSON query CLI for the
local read tools (#1752). Analysis reads each row once instead of 2.25 times (#1695, #1696). Swift
analytics tests run on Linux (#1529). Several diagnostics now say which gate refused a metric, rather
than leaving a blank.
Thanks to @bhelm, @pipiche38, @kvnloo, @Zebsi235, @kavemang, @fadkeprasad, @Meriquium and @dwehrmann
for contributions in this release.
What's Changed
- Stop Blood Oxygen promising a reading a WHOOP 4.0 cannot produce in #1618
- Correct the WHOOP 4.0 blood-oxygen copy: the sensor is not the missing part in #1619
- diagnostics: check other device ids before blaming a fresh re-add in #1620
- ble: pick the battery source from evidence, not from a guess in #1623
- analytics: collapse the per-day sleep-skip spam to one line per pass in #1626
- import: report which metric columns an export actually carried in #1628
- diagnostics: make a nil respiratory rate say which gate refused it in #1632
- diagnostics: name every paired device in the strap log (and redact the header) in #1633
- devices: attribute firmware to the strap it came from, not the last to connect in #1634
- ble: report what became of a 5/MG CLIENT_HELLO write in #1638
- ble: stop declaring a 5/MG bond from a completion that is not the CLIENT_HELLO's in #1641
- ble: let the bond give-up see an unanswered handshake in #1640
- ble: observe the OS pairing flow, which NOOP has never watched in #1639
- ble: stop the CLIENT_HELLO from knocking a working 5/MG link off every 5s in #1642
- ui: stop calling a live-HR-only link "Bonded" in #1645
- ble: ask Android to pair, instead of hoping a write provokes it (opt-in experiment) in #1646
- ble: read the Device Information Service on a 5/MG that never bonds in #1647
- ble: ask the device its bond state, instead of trusting our own receiver in #1648
- ble: let the DIS read happen when the pairing experiment is on, and not lie about refusals in #1649
- docs: BLE lessons from #1635, and correct what app-build actually validates in #1650
- ble: capture 5/MG frames outside a sync window, and stop naming an unreachable remedy in #1651
- ble: enumerate what the strap actually offers, once per device in #1652
- ble: read the rest of the Device Information Service in #1653
- iOS: wire Blood Oxygen and Skin Temp on Liquid Today (#1627) in #1654
- docs(#1617): the WHOOP 4.0 is missing a second channel, not a conversion in #1655
- protocol: let a 5/MG say what it is, instead of guessing from its serial (#520) in #1656
- analytics: start Banister's axis where Edwards' starts (#1624) in #1658
- report: render the trends export in the user's own units in #1660
- store: keep the nightly absolute skin temperature in #1663
- charts: make every scrubbable graph read like the screen around it in #1664
- health: lead the skin-temp tile with the absolute, deviation beneath in #1665
- capture: an empty raw capture is no capture, not a successful share in #1666
- diagnostics: stop calling a second strap's night a read failure in #1667
- ble: opt-in switch to send the CLIENT_HELLO despite the suppression latch in #1669
- ble: the bond poll cannot convict Android, and an HCI capture says it is wrong in #1668
- ble: bound the hello override so it cannot loop the link forever in #1670
- ui: show skin temperature in the reader's unit on the alert banner and the fusion row in #1671
- ble: port the 5/MG CLIENT_HELLO suppression to iOS/macOS (#1635) in #1672
- ble: read the Device Information Service on an unbonded 5/MG (iOS/macOS) in #1673
- updates: notice a new release and say so, on both platforms (#1659) in #1674
- updates: turn the automatic check on by default, and make the privacy docs true in #1675
- fix(android): use the active strap's motion in Steps calibration by @kavemang in #1644
- steps: read the gravity union in the iOS/macOS calibration screen too (#1643) in #1676
- health-connect: retract sleep records the export no longer produces (#1677) in #1679
- rescore: only the pass that owes the debt may settle it (#1681) in #1684
- backfill: say how old the strap's newest record is when a sync banks nothing (#1683) in #1686
- doc(oura): correct stale real_steps gating claim by @pipiche38 in #1629
- doc(oura): carry the real_steps correction to the twins, the spec, and a test in #1687
- feat(oura): add Skin Temp as a Key Metrics tile option by @pipiche38 in #1678
- hrv: multiMs overflows a 32-bit Int, so the R-R delivery census reports a negative percentage by @Zebsi235 in #1685
- hrv: state the real pct overflow bound, and pin that the wrap could land positive in #1688
- workouts: put Pause/Resume on the active-workout cards, and stop three clocks ignoring it in #1690
- diag(hrv): census the two LIVE R-R transports, not just the historical one in #1693
- refactor(analyze): lift the per-day skin/SpO2/wrist-off reads out of analyzeRecentOnCpu in #1696
- perf(analyze): read each pass-1 row once instead of 2.25x in #1695
- diag(ble): Apple decoded the strap's own console log and then dropped it in #1697
- test(protocol): the v18 R-R cross-check asserted on literals and could not fail in #1698
- tooling: doc_comment_lint keys the baseline on OS-native paths, so it fails on a clean tree on Windows by @Zebsi235 in #1691
- ci: gates that never ran on the change they were written to catch in #1699
- Add experimental self-hosted one-way push on Android by @bhelm in #1631
- push: localize the destination errors the settings screen shows in #1700
- today: open a session's detail from the Latest Workouts feed in #1701
- today: window the iOS Latest Workouts feed to the same 14 days as Android in #1703
- analytics: say when a stream read came back at the cap in #1704
- alarm: attribute the readback to a strap before judging it in #1707
- analytics: reduce a skin-temp window to one scale before anything aggregates it in #1708
- alarm: let a strap be asked what alarm it has stored in #1710
- build: order the Room schema snapshot after every KSP round, not just the test ones in #1712
- build: name the KSP predicate the two schema rules share in #1714
- widgets: make the lock-screen accessory read like a lock-screen widget in #1715
- alarm: the 4.0 GET_ALARM_TIME readback carries a stored flag, so its epoch decodes one byte early — a correctly stored alarm reads back as 2045 by @Zebsi235 in #1713
- diagnostics: stop the funnel calling a failed sync expected in #1719
- Show a copyable recovery screen after Android crashes by @bhelm in #1718
- Redact the captured crash everywhere it leaves the device in #1720
- sleep: a partially-received hypnogram is not a complete night by @pipiche38 in #1717
- Make the two hypnogram-coverage readers agree, and state the gate's real scope in #1721
- Bucket an absolute chronotype from the body-clock phase in #1722
- Add the 24 h body-clock dial to the Sleep screen in #1723
- Don't greet an upgrade with a crash from the build it replaced in #1724
- Report the body clock's inputs in the diagnostics in #1726
- Stop a cycle re-evaluate blanking the body clock, and report the rhythm in bpm in #1729
- Accept a rhythm that is large enough in bpm, whatever the mesor in #1731
- Mirror the SOLID rule in the diagnostic verdict in #1732
- Make the body-clock dial readable on a real screen in #1733
- Read WHOOP physiology across all registered straps by @bhelm in #1730
- ecg: 124's argument mapping is inverted on hardware — 2 starts generation, 1 stops it, 0 is refused by @Zebsi235 in #1727
- feat(imu): unlock WHOOP 5/MG 100 Hz capture and labeled recording sessions by @bhelm in #1709
- ble: say why a 5/MG link streams live HR and banks nothing in #1738
- diag: say which workout detector made the rows, and when the pipelines last ran in #1739
- vo2max: show the decimal the chart draws, and say why the line breaks in #1740
- Fix missed sleep after partial history sync by @fadkeprasad in #1734
- Enable StrandAnalytics SwiftPM tests on Linux by @bhelm in #1529
- feat(ble): adopt a stable whoop- identity for 5.0/MG (#1303 phase 1) in #1455
- WHOOP 5/MG: probe whether the historical offload actually needs a bond in #1742
- WHOOP 5/MG: let the offload probe supersede the handshake, or it never runs in #1743
- WHOOP 5/MG: a completed CLIENT_HELLO write is not an encrypted link in #1744
- Last sync belongs to a strap, not to the install in #1745
- Sync controls follow what the strap can actually do in #1748
- The offload probe has to report when the link is what ends it in #1749
- Apple parity: last sync belongs to a strap, and sync controls follow what it can do in #1753
- fix(sleep): make debt an actionable next-night target by @bhelm in #1741
- sync: skip the #689 backlog decode on the GET_DATA_RANGE PENDING ack by @Zebsi235 in #1746
- tooling: i18n_audit keys its baselines on OS-native paths, so it fails on a clean tree on Windows by @Zebsi235 in #1747
- Add a JSON query CLI for local read tools by @kvnloo in #1752
- sleep: let the sparse bridge cross one short active run in #1755
- The unbonded offload probe must not start on top of the DIS chain in #1756
- Coach: retire the transcript on a new local day by @dwehrmann in #1542
- Coach: twin the day-boundary retirement on Apple in #1757
- The unbonded offload probe's silence budget must outlive the process (#1635) in #1761
- Asking Android to pair must not clear the hello give-up every link (#1635) in #1762
- Stop asking Android to pair once the strap has refused the bond (#1635) in #1763
- The Devices card told the truth once, then went green again (#221) in #1766
- A day you never logged is not a day you answered No (#322) in #1768
- protocol(mg): decode the type-43 REALTIME_RAW_DATA record on both platforms by @Zebsi235 in #1765
- Make standard-HR ingestion timing and durability observable by @kvnloo in #1767
- Android standard-HR: stop losing a rejected batch in silence (#1770) in #1771
- sleep: say when a night's hypnogram only covers part of its window by @pipiche38 in #1764
- ios: get StrandiOSApp back under the type-check budget in #1773
- ios: re-land the standard-HR lifecycle flush on the scene delegate (#1770) in #1774
- diag: say why Effort is 0, why a night did not stage, and what the strap provides in #1775
- ble: keep the hello-suppression latch across a user Connect in #1778
- ble: spend fewer links learning what the strap already told us in #1779
- ble: a timer firing is not the user asking to retry the handshake in #1781
- sleep: let the HR band widen the sparse bridge's active bound, not be vetoed by it in #1782
- fix(oura): list a bonded ring that advertises no local name by @pipiche38 in #1776
- doc(oura): record the 0x6A motion_count guard discrepancy vs open_oura by @pipiche38 in #1777
- Add an opt-in pre-sleep heart-rate feedback contract by @kvnloo in #1772
- test(push): pin the clock, so the suite stops asserting the calendar in #1786
- analytics: give the pre-sleep HR feedback its Kotlin twin in #1785
- push: make an omitted clock a compile error, not a green test in #1788
- Preserve sensor-contact state from standard BLE heart-rate measurements by @kvnloo in #1769
- ble: offer to clear a stale phone pairing, once, after asking first in #1790
- ble: record why the stale-pairing clear stays Android-only in #1791
- docs: describe the CI that actually runs in #1793
- Canonicalize signed zero in effective effort by @bhelm in #1792
- Split Health Connect import permissions by category by @kavemang in #1509
- analytics: do not claim a twin with a Swift stdlib method in #1794
New Contributors
- @Zebsi235 made their first contribution in #1685
- @fadkeprasad made their first contribution in #1734
- @dwehrmann made their first contribution in #1542
Full Changelog: v10.6.0...v11.0.0
Downloads — NOOP Staging identity, installs beside the official app (separate data):
- Android —
NOOP-android-v11.0.0.apk(com.noop.whoop.staging) - macOS —
NOOP-macos-v11.0.0.zip(unzip, then right-click → Open) - iOS —
NOOP-ios-unsigned-v11.0.0.ipa(AltStore / Sideloadly)