Skip to content

NOOP AI 9.3.2 DX Beta

Choose a tag to compare

@github-actions github-actions released this 05 Aug 22:14

NOOP AI 9.3.2 DX Beta

A measurement-correctness release. Three separate faults were feeding wrong numbers into HRV — one
introduced by this fork, two found upstream in the Oura path — and all three are fixed here. On top of
that: the fork's own untranslated strings are done, and 60-odd upstream commits land, including the
sleep-edit date fix people have been asking for since ryanbr#970.

Fixed — measurement correctness

  • Same-second heartbeats are read in emission order again (ryanbr#830). Swift's sorted(by:) is not a
    stable sort, so re-sorting R-R intervals by timestamp was free to reorder beats sharing a second
    into value order. RMSSD is the sum of successive differences, so that reordering changes the
    result directly — on the reference case, 34.85 ms read as 12.72 ms. Upstream fixed this in
    34c66b66; this fork reverted it at six call sites during the Today redesign, and the 9.3.1 merge
    resolved the conflict in favour of the reverted side, so it stayed broken here after being correct
    upstream. Restored in SleepStager, SleepStagerV2, AnalyticsEngine (both deep-sleep HRV
    windows), HRVFreqDomain and ResonanceEngine. If you wear a WHOOP 5.0/MG, whose R-R is dense
    enough for several beats to share a second, your nightly HRV and the Charge derived from it were
    affected.
  • An Oura ring's beats are no longer stored twice (ryanbr#1071). The ring reports the same heartbeat on
    more than one optical channel and every one was kept, so an unfiltered read returned ~2.06x the
    beats the measured heart-rate curve allows. Mean rate and resting HR survive that; RMSSD and a
    ~200 ms nocturnal SDNN (against a 40–100 ms physiological range) do not. Scoring now reads one
    channel; the other stays on disk as the cross-check.
  • An Oura record's beats keep their order (ryanbr#1072). The Oura path enqueued one event per beat, so
    the batch-local ordering counter was recomputed as 0 for every row and the emission order was never
    recorded at all — the storage-side root cause of the same problem. Records are handed to the store
    whole.
  • SpO2 samples stop colliding (ryanbr#1070). 12 of every 13 samples shared a primary key and were
    discarded.
  • A capture known to be over-counted no longer reports SDNN (ryanbr#1085). SDNN is a spread over every
    interval, so a double-counted night inflates it directly. The nightly diagnostic now withholds the
    figure and says why, rather than printing a number beside the verdict that it cannot be trusted.
    RMSSD and mean rate are unaffected and still shown.
  • A night missing beats stops reading as clean (ryanbr#977). The coverage classifier had a ceiling but
    no floor.

Fixed — sleep

  • Sleep edits preserve the wake calendar date (ryanbr#970/ryanbr#1059). Correcting a late-tracked night's bed
    time kept the calendar date, so the "corrected" bed could land on the coming evening — a
    future-dated night that staged as all-awake and blanked the Sleep tab. Three layered guards now:
    a cross-midnight bed auto-correct, an explicit confirm when the corrected window has no recorded
    data to stage from, and a persistence clamp that refuses an inverted or future-ending window.
  • Naps count toward sleep debt (ryanbr#1041). A separately-recorded nap repays debt with its actual
    asleep minutes, including for days already in your history. Rest and the sleep headline still
    describe the main night only.
  • Unmeasured respiration is represented explicitly (ryanbr#847) rather than implied by a zero.

Fixed — this fork

  • Coach settings opened from More poisoned the tab. The screen built its own navigation stack
    inside the one More already owns, so SwiftUI updated two navigation authorities in the same frame
    and the outer path stopped accepting pushes: after visiting Coach settings, no other More row
    navigated until you left the tab and came back. It now reuses More's stack; the standalone sheet
    path from the chat is unchanged.
  • The fork's own strings are translated. 25 strings in the design system — the chart colour
    styles (Aurora, Forest, Sunset, Titanium, Apple Health), the sleep-editor labels, the day
    navigation — were extracted but never translated, so German, Spanish, French and Portuguese users
    saw English there. All four locales are now complete across every catalog. Two Watch entries that
    were only punctuation and the product name are marked non-translatable instead of being given fake
    translations.

Changed

  • Today is denser on both shells. Tighter card padding, gutters and corner radii, and a smaller
    header cluster — the visible circles shrink to 34pt while their 36pt hit targets stay exactly as
    they were, so nothing became harder to tap. More of the screen's content sits above the fold.
  • Activity-file steps add up across a day (ryanbr#1058). A second file imported for the same day
    overwrote the day's step count instead of adding to it.
  • iOS widgets show Charge, Effort and Rest as score rings (ryanbr#1022).
  • Low Power Mode stops everything decorative (ryanbr#909), including the tilt sensor.
  • WHOOP MG ECG packets are decoded (ryanbr#896), with a hand-run turn-on probe that is default OFF.
    Unvalidated instrumentation for protocol research — not a measurement, and not a diagnosis.
  • The Live Activity widget anchor and the HR-zone set are memoized across live-HR ticks
    (ryanbr#1091/ryanbr#1092), so a connected strap does less work per beat.
  • Version identity is 9.3.2 DX Beta (build 217; Android versionCode 309).

Verification

  • swift test: WhoopProtocol 531, WhoopStore 362, OuraProtocol 168, StrandAnalytics 1259 — all green.
  • xcodebuild of both app targets — Strand (macOS) and NOOPiOS (iOS) — both BUILD SUCCEEDED.
  • python3 Tools/i18n_audit.py --ci exits 0: all four String Catalogs complete in de/es/fr/pt-PT,
    the non-focus ratchet unchanged.
  • Not verified on hardware. Nothing here touches the CoreBluetooth connection path, but the HRV
    and Oura fixes change numbers derived from a real capture — the honest check is to sync a strap or
    ring and compare a night's HRV against what 9.3.1 reported for the same night.
  • Translation provenance: German is first-hand; 15 of the 25 new strings reuse wording already
    shipping elsewhere in the app. The remaining new terms in es/fr/pt-PT/it/ru/zh are considered but
    not native-speaker reviewed.

Thanks

Nearly everything in the "measurement correctness" section above came from outside this fork:

And to ryanbr/noop, whose 9.3.1 line this builds on.

Install

  • iOS: add the AltStore/SideStore source, or download NOOP-ios-unsigned-v9.3.2-dx-beta.ipa and
    sign it yourself with your own free Apple ID.
  • macOS: unzip NOOP-macos-v9.3.2-dx-beta.zip, then right-click → Open the first time — the
    bundle is ad-hoc signed, not notarised. macOS may ask you to re-approve Bluetooth after an update,
    because the code identity changes with every build.