Releases: DX23876/noop
Release list
NOOP AI 10.1.0
NOOP AI 10.1.0
The first release of this fork on the 10.x line, and it carries two of them: everything upstream
shipped in 10.0.0 and 10.1.0 since the 9.3.3 beta, plus this fork's own work on heart-rate zones and
the coach.
The release identity changes with it. Builds are no longer branded "DX Beta" — the tag is
v10.1.0-dx, the title is plain, and the version the app reports is what it always was: a number.
The -dx suffix stays because upstream tags vX.Y.Z in the same namespace this fork fetches into;
it separates the two, and nothing more is meant by it.
Added — your own heart-rate zones
- Three modes, one resolver. The conventional 50/60/70/80/90 % of maximum, your own percentages,
or absolute BPM boundaries from a threshold or lactate test. Percentages move when your maximum is
re-estimated; absolute beats stay put, which is the whole reason both exist. Both sets of numbers
survive a mode switch, so trying one costs nothing. Editor under Profile › Heart-rate zones. - Display-only, deliberately. Custom bands change the live readout, a workout's zone split and
what the coach prescribes in. They never reach your Effort score, which is measured against your
heart-rate reserve on a published method with fixed thresholds — re-basing it on a band you edited
yesterday would silently rewrite every Effort you have ever been shown. Old workouts show the new
bands immediately; there is no migration. - A backup written by upstream's build still restores its bands. Their single custom-zone key is
read and adopted into this fork's absolute mode when you have set none of your own.
Added — the coach
- A prescribed session's Effort is computed, not asserted. "20 minutes in Zone 2" is evaluated
against your own bands and the shipped Edwards maths before the coach may quote a figure. A value
it supplies survives only within five points of that — leaning easy inside a band is judgement; a
15-point target for a session that produces 30 is arithmetic, and it was the reported bug. - The morning brief waits for a night the data can support. When last night's window is still
truncated because your strap hasn't finished offloading, the brief is withheld and the day is not
stamped as briefed. You get a card that says so and takes you to the sleep editor in one tap;
correcting the night re-runs the brief on the corrected numbers. A mere suspicion never withholds —
it travels as a caveat wherever a Charge is quoted.
Added — from upstream
- Polish joins the languages the app ships, and the ~1300 strings that exist only in this fork were
translated for it too, so the app speaks one language throughout rather than two. - Background syncing now shows in the Today header: the battery ring becomes the sync indicator while
your strap is offloading history (@Quaii, ryanbr#837). - Home Screen quick actions (@Quaii, ryanbr#1294), five WHOOP-catalogue sports that were missing, VO₂max
telling you what it needs before it can appear, and a strap switcher plus a two-strap comparison. - Considerable Oura work — hypnogram-scored nights, reversed HRV buckets, a partial night no longer
reading 36 % efficiency, and a reconnect that survives suspension (@pipiche38). - Rest and readiness now use the methodology that was computed but never connected (@vishk23, ryanbr#456),
and the daytime stress timeline masks exertion instead of scoring it as stress (@vishk23, ryanbr#1114). - HRV tuning moved out of the always-visible Strap card into Advanced — the in-app search still finds
it — and a night whose R-R stream was over-counted now says its HRV is unverified rather than
quietly reporting it.
Fixed — numbers that claimed more than they knew
- Skin temperature read as a deviation it wasn't (ryanbr#622, reported by @bartmuskala). The column is
bimodal: the live pipeline stores a signed deviation from your baseline, imports store an absolute
wrist reading. Today's dashboard signed everything, so an imported night showed "+33.4°" — a
deviation from baseline no body produces. It also ignored your °C/°F preference, alone among the
screens. Both are now routed through the shared resolver the other screens already used. - Respiratory rate carried without limit (ryanbr#1312, @pipiche38 upstream; the same fix now on this
fork's classic dashboard). One CSV import's last value could read as today's measurement for a
fortnight. The carry is bounded to a week; past that the card says "—", which is the truthful
answer when nobody measured. SpO₂ and skin temperature keep their unbounded carry on purpose —
those are genuinely sparse, and last-known is the honest answer for them. - A workout's zone split ignored a manual maximum heart rate. It built its bands from age alone,
so the same heart rate read as Zone 2 live and Zone 3 in the workout. - Three screens carried their own hardcoded zone ladder — the Health card, the haptic zone alert
and the workout split — so a buzz could fire at a different boundary than the one displayed. All
five zone consumers now read one resolver. - A band edge could fall outside its own zone. 0.55 × 200 is 110.000000000000014 in binary
floating point, which put exactly 110 bpm below its own Zone 1 floor. Invisible on round tens,
everyday with custom bands. - Apple Health received a mislabelled HRV number. HealthKit's HRV field is SDNN; the export wrote
a whole-night standard deviation, which reads 2–3× high against Apple's own short-window samples.
It now writes the stored 5-minute SDNN index, and never falls back to RMSSD under an SDNN label.
Verification
StrandAnalytics1450 tests,WhoopStore386, app target 2085 — all passing.NOOPiOS(iOS) andStrand(macOS): both BUILD SUCCEEDED.Tools/i18n_audit.pyexits 0 with Polish at zero gaps, held to the same zero-tolerance tier as
German, Spanish, French and Portuguese.- Nothing on the BLE or offload path changed in this release, so no strap behaviour was re-validated.
Thanks
The Oura decode work, the vitals-carry bound and a long run of protocol fixes are @pipiche38's.
@vishk23 connected Wave 0 scoring into Rest and readiness and rebuilt the stress timeline;
@Quaii did the sync indicator, quick actions and a great deal of layout; @AveMonti contributed
the Polish localization this release completes; @liaolo refined the iOS visual system;
@adrianlaureda, @evoveotech, @TAKEOFF69, @tigercraft4, @kavemang, @dnesdan and
@whisp0 contributed fixes, docs and hardening.
Reports drove much of the rest: @bartmuskala (ryanbr#622, the skin-temperature reading that started the
display fix), @flexagoon (ryanbr#1160, ryanbr#1161, light-mode cards), @h-nry (ryanbr#1311, sleep-stage nights),
@adamstefanik, @afonsojramos, @cybermicky0, @lukasito25, @Simple9,
@sudden-break, @UncreativeX and @digitalerdude — and everyone who filed the reports behind
these fixes.
Install
The unsigned IPA installs through AltStore, SideStore or Feather; the macOS build is a universal,
ad-hoc-signed app. Both are attached to this release, and the AltStore source updates itself when it
is published.
NOOP AI 9.3.3 DX Beta
NOOP AI 9.3.3 DX Beta
A packaging release: the sideload IPA carries the iOS widgets again, and — the half that actually
makes them useful — the App Group they read through is provisioned on the way out. No app code
changed.
Fixed
- The AltStore/SideStore IPA ships the widget extension again. It had been stripped alongside the
Watch app on the assumption that generic re-signing could not provision either. That is true of an
embedded watchOS bundle and not true of an app extension: AltStore and SideStore re-sign
NOOPWidgets.appextogether with the host app. The packaging step now removes onlyWatch/, and
the assertion that used to fail if the widget was present is inverted — it now fails if the widget
is missing, because what the bundle contains is the promise made to the person installing it. - The widget can actually read the app's data.
Tools/prepare-ios-sideload-app.sh— added
upstream with ryanbr#887 to embed the ad-hoc App Group + HealthKit capability template that AltSign needs
in order to provision and re-sign the shared container — existed in the tree with no caller in
this fork's release path. It is now invoked on the sideload bundle, after the Watch removal and
before zipping (it code-signs; any later change to the bundle would break that seal). Without it a
widget installs cleanly and then shows dashes forever, which is exactly the failure this avoids.
The resolver that picks the provisioned group at runtime (WidgetSnapshot.resolveSuiteName) was
already correct and is unchanged. - Two repairs in the macOS test target, found by running
StrandTestsfor the first time since
the 9.3.1 and 9.3.2 upstream merges — both were compile failures that no app build could reveal,
because the test target is built only byxcodebuild … test:AICoachEmptyReplyAndUrlTests(which arrived with upstream ryanbr#1087) calledparseChatContentwith
one argument, while this fork keeps the richerparseChatContent(_:isLocalServer:)that tailors
the truncation notice to local servers versus hosted gateways. Adapted to the fork's signature.project.ymlhad lostStrandiOSShared/WidgetSnapshot.swiftfrom theStrandTestssources
during the 9.3.1 merge, soWidgetSnapshotTestscould not see the type and the whole target
failed to build. Restored — and those four tests cover precisely the App Group resolver this
release now depends on.
Unchanged on purpose
- The Apple Watch app is still excluded from the sideload IPA. Sideloaders have installed embedded
watchOS bundles unreliably for years, and the failure mode is not a missing watch app but a failed
installation — the tester gets nothing. A free Apple ID's three-app limit (sideloader + NOOP +
watch app) is the second reason. Nothing about the project changed to achieve this:NOOPWatch
remains a dependency ofNOOPiOS, the canonical build stays complete, and only a staged copy is
thinned. An Xcode source build and the Full IPA are unaffected. Upstream's own release step makes
the same cut. - No analytics, protocol, storage or UI behaviour changed in this release.
Verification
- Packaging run against the real iOS build product: the sideload IPA contains
PlugIns/NOOPWidgets.appexand noWatch/; the Full IPA contains both. codesign -d --entitlementsreports the sameapplication-groupsvalue on the app and on the
widget after the zip round-trip, andcodesign --verify --deep --strictpasses on the extracted
bundle — so the capability template survives packaging rather than only existing on disk.StrandTests: 1815 tests, 0 failures (1 skipped), where the target previously did not compile.xcodebuildofNOOPiOS(iOS) andStrand(macOS): both BUILD SUCCEEDED.python3 Tools/i18n_audit.py --ciexits 0.- Not verified on a device. Whether AltStore/SideStore provision the two App IDs and the shared
group on a given iPhone is the one step that needs your own Apple ID: sideload this build and check
that the widget shows real values rather than dashes.
Thanks
To everyone who pushed back on the previous release's packaging — the widget removal was the wrong
call, and the correction plus the App Group point came directly out of that. The capability template
itself is @Quaii's work (ryanbr#887).
Install
- iOS: add the AltStore/SideStore source, or download
NOOP-ios-unsigned-v9.3.3-dx-beta.ipaand
sign it yourself with your own free Apple ID. This one includes the widgets. Want the Apple Watch
app too? TakeNOOP-ios-full-unsigned-v9.3.3-dx-beta.ipainstead — it needs a signer or paid
Developer team that can provision the watch bundle. - macOS: unzip
NOOP-macos-v9.3.3-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.
NOOP AI 9.3.2 DX Beta
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 inSleepStager,SleepStagerV2,AnalyticsEngine(both deep-sleep HRV
windows),HRVFreqDomainandResonanceEngine. 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
versionCode309).
Verification
swift test: WhoopProtocol 531, WhoopStore 362, OuraProtocol 168, StrandAnalytics 1259 — all green.xcodebuildof both app targets —Strand(macOS) andNOOPiOS(iOS) — both BUILD SUCCEEDED.python3 Tools/i18n_audit.py --ciexits 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:
- @pipiche38 — the entire Oura beat-duplication investigation and its fixes (ryanbr#1071, ryanbr#1072, ryanbr#1070,
ryanbr#1084) plus the SDNN-withholding follow-through (ryanbr#1085). The R-R double-count was found, root-caused
and fixed by them. - @Creanathan — reported and fixed the sleep-edit wake-date bug (ryanbr#970, ryanbr#1059).
- @vishk23 — the coverage-classifier floor (ryanbr#977), unmeasured respiration (ryanbr#847) and the MG ECG
decoding (ryanbr#896). - @SpurgtBror — naps toward sleep debt, reported and fixed (ryanbr#1041, ryanbr#1042).
- @akeschmidi — the iOS widget score rings (ryanbr#1022).
- @whisp0 — foreground-service analytics caching (ryanbr#1051).
- @tigercraft4 — custom AI gateway auth (ryanbr#765, ryanbr#766).
- @theLastOfCats — reported the Coach settings UI and DeepSeek error handling (ryanbr#1074).
- @Quaii — iOS widgets showing only sample data (ryanbr#887).
- @digitalerdude, @TAKEOFF69, @ben-tsk, @rube-de — Android, CI and documentation
fixes carried in this merge.
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.ipaand
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.
NOOP AI 9.3.1 DX Beta
NOOP AI 9.3.1 DX Beta
A coach-memory release with two Today repairs, on top of nine upstream commits landed after RyanBR's
9.3.0 tag.
Added
- A memory receipt in the chat. A reply that wrote to memory now names what it saved, right under
itself, with That's right / Edit / Forget — and "Saved, pending your confirmation" for the facts
that need one. Rows are looked up by id, so an edited fact re-reads its text and a forgotten one
disappears; the transcript never keeps a stale copy. Existing transcripts load unchanged. - Memory settings that show their work. The list is grouped (awaiting confirmation → always-on →
by category → expired) instead of forty flat rows. Each fact gains pin/unpin and a disclosure with
the provenance that was recorded from the first version and displayed nowhere: source, first saved,
observation count, previous wording, expiry — with controls to set or clear that expiry. Facts can
also be typed in by hand, which stores them confirmed on the spot. - An attention badge on the Memory row, the same one the Coaching row has always had, so a fact
waiting on you is visible from the coach's landing page instead of two taps down.
Changed
- Effort reads the same figure everywhere on Today (upstream ryanbr#1001, ryanbr#1006). The hero and the Key
Metrics tile could disagree; both now use the resolved value. - Fresh installs default the HRV window to Overnight only (upstream ryanbr#1008, ryanbr#1019). Existing
installs keep the setting you already have. - Less work between tapping send and the request leaving.
retrieveembedded up to 64 backlog
documents before looking at your question, inside the same 2.5-second budget the question had to
win — on a cold model that race was lost by construction and you waited the full budget for a
keyword fallback. The question goes first now. Alongside it: the index writes only documents whose
text changed (instead of re-upserting up to 50 conversations per message), a second redundant
reconcile per message is gone, document tokens are cached rather than recomputed per question, and
document building moved off the main actor. - Importers, secure prefs and What's New got cheaper (upstream ryanbr#1012, ryanbr#1014, ryanbr#1016, ryanbr#1017) — one
capped stream reader instead of five, one prefs helper opened once per process, a lazily built
changelog, and a range-scan for Health Connect calories. - Version identity is 9.3.1 DX Beta (build 216; Android
versionCode308). The AltStore and
SideStore source moves to this release as it is published.
Fixed
- A health fact the coach saved could never be used.
addstored an injury, goal or physiology
fact as pending confirmation, the always-on block admits only confirmed facts, and nothing outside
a tap in settings ever set that state — the near-duplicate path did not touch it either, so saying
"yes, that's right" out loud could not fix it.remember_factnow carriesconfirmed_by_user,
honoured on the restatement path where that agreement actually arrives. It never downgrades: a
re-inference cannot un-confirm or unpin a fact. valid_untilfinally expires something. The field was decoded and filtered on, but nothing ever
wrote it, so a March calf strain still framed every answer in August. It can now be set by the coach
or by you, and expired facts are evicted before healthy ones when the 40-fact budget is full.- Two quiet bugs for anyone not reading English or German. The semantic index wrote a hardcoded
"Ja"/"Nein" into every journal document in all nine shipped languages — text that is both embedded
and handed to the model, so it embedded a token your own questions could never match. And category
inference matched English and German only, so in six of the nine locales every distilled fact fell
through to "preference", losing both its health sensitivity and its confirmation requirement. - The same fact no longer goes out twice. The keyword ranking and the semantic index hold the same
facts and both run per turn; the keyword block now skips what the context already carries. - "Forget everything" asks first. It fired on the tap, dropping up to 40 facts irreversibly, while
the only other irreversible control on that screen has always asked. Facts have no archive the way
conversations do. - Classic Today's heart rate is the live card, not a static chart. It kept an older chart with its
own pinch-zoom and a live bpm badge you could not tap; it now hosts the same compact live-HR card
Liquid draws, with a "Full day" link into the Deep Timeline. The removed reading all lives there. - Liquid Today showed one workout under a header counting hundreds. The section rendered a single
card while the heading beside it read "1998 total". It renders up to six, like classic Today. - The Weight tile drew no trend. Its sparkline was windowed by date only, so a series measured
weeks apart had too few points inside the 2/7/14-day window and fell through to an empty graph while
the number stayed correct. A sparse series now falls back to your recent readings, which is what the
feature reference has promised all along. Dense metrics are unaffected. The same tile also stopped
drawing a permanently empty progress bar for a value that has no 0–100 scale. - Sleep stages recognise both spellings of wake in segment comparisons (upstream ryanbr#979, ryanbr#1011).
Verification
xcodebuildof both app targets — macOSStrandbuilt universal (ARCHS="x86_64 arm64" ONLY_ACTIVE_ARCH=NO, the way CI builds it, so an arm64-only thinning cannot hide an Intel break)
and iOSNOOPiOS— both succeeded.swift testfor WhoopProtocol (472), WhoopStore (334), StrandAnalytics (1223) and StrandImport
(221) — all green.StrandTestsunderxcodebuild … test— 1763 tests, 0 failures (1 skipped), up from 1731 with
the new coach-memory and spark-window tests.- Not verified on hardware. Nothing in this release touches the CoreBluetooth path, but the live-HR
card on classic Today reads a connected strap — pair one and watch the number tick.
Thanks
- @bartmuskala — reported that Effort on top and the Key Metrics tile disagreed (ryanbr#1001), the fix
this release carries. - @vishk23 — the decoding work behind the sleep-stage and capture cleanups landed here (ryanbr#979).
And everyone who filed the reports behind these fixes.
NOOP AI 9.3.0 DX Beta
NOOP AI 9.3.0 DX Beta
Built on RyanBR's NOOP 9.3.0 (35 upstream commits since 9.2.1), this release takes upstream's health
imports and scoring corrections while keeping every fork surface — the Coach, its memory, and the
Heute prototype — intact.
Added
- Water and caffeine import from Apple Health (upstream ryanbr#949). Read-only in both directions that
matter: imported entries keep their own row so they can never overwrite a manual one, and NOOP
never writes either type back. iPhone asks permission once for the two new scopes, including for
users who granted Health access before this version existed. The Coach'slog_caffeinetool writes
through the same shared store, so chat-logged and imported intakes sit side by side. - One Customize Today editor (upstream ryanbr#940). The separate Arrange, Key Metrics and Your Cards
sheets are replaced by a single Shown/Hidden list with drag-to-reorder, Cancel/Save over a draft,
and "Edit" rows that deep-link to the two child pages. The fork's Tiles per row picker moved
onto the Key Metrics page and is now discarded by Cancel like every other setting there.- The Coach banner is an ordinary row in that list. It can be dragged to any position or moved
to Hidden, exactly as before. The floating Coach button and the Heute header entry are unchanged.
- The Coach banner is an ordinary row in that list. It can be dragged to any position or moved
Changed
- Effort scoring (upstream ryanbr#963, ryanbr#983). Heart-rate samples are weighted by their own gap rather
than the window's first one, and a saved workout is scored against your measured resting heart rate
instead of a hardcoded 60. Expect Effort numbers to move in either direction, including on past days. - Version identity is 9.3.0 DX Beta (build 215; Android
versionCode307). The AltStore and
SideStore source moves to this release as it is published, so "Check for updates" and a sideload
install both resolve to 9.3.0 DX Beta. - This release ships a macOS build too. 9.2.1 and 9.2.2 DX Beta were iOS-only; the release now
carriesNOOP-macos-v9.3.0-dx-beta.zip(universal x86_64 + arm64, ad-hoc signed so macOS 26 can
hold its Bluetooth permission) alongside the unsigned iOS.ipa.
Fixed
- Oura days no longer spike to a 90+ resting heart rate (upstream ryanbr#375). A nap or a short
fragment could outrank the real night and claim the whole day's numbers. Re-import your Oura
history to correct days already stored. - Sleep staging cannot jump from awake to deep or REM (upstream ryanbr#348/ryanbr#987).
- The i18n gate is green again. The watch String Catalogs had accumulated Xcode build churn that
invented three untranslatable keys and pushed the non-focus locales past their ratchet allowance,
and two fork release titles were still raw Kotlin literals. Both are fixed; the audit baselines
were ratcheted down (Android 215 → 213, Apple 64 → 16 tracked entries). - The
tools/tree is normalised intoTools/(upstream ryanbr#979/ryanbr#1009). The fork carried both
spellings; on a case-insensitive macOS checkout they look like one directory, so upstream's rename
did not land here and sixlinux-capturefiles stayed on an older revision — the newspo2_candidate_82
tests arrived without the decoder change they cover, andTools Python CIwas red. The 27 stray
paths moved, the six files match upstream again, and the suite runs 212 tests green. - The Whoop source badge is visible again on Classic Today. A fork commit had suppressed the
per-ring provenance badge whenever the resolved source was "Whoop", which for a WHOOP user hid it
entirely under the Charge and Rest rings. Both Today screens now match upstream exactly.
Verification
xcodebuildbuild of both app targets — macOSStrandbuilt universal
(ARCHS="x86_64 arm64" ONLY_ACTIVE_ARCH=NO, the way CI builds it, so an arm64-only thinning cannot
hide an Intel break) and iOSNOOPiOSfor the simulator — both succeeded.swift testfor WhoopProtocol (472), WhoopStore (334), StrandAnalytics (1209) and StrandImport
(221) — all green.StrandTestsunderxcodebuild … test— 1726 tests, 0 failures (1 skipped). The
CoachMemoryDedupTestsfailure noted while this merge was in flight is resolved.Tools/linux-capture— 212 tests green after theTools/normalisation above (the workflow
requires at least 200 to be discovered).Tools/i18n_audit.py --cigreen.- Not verified on hardware.
Strand/BLE/BLEManager.swiftmerged automatically (upstream +201
lines) and no CI or local test covers the CoreBluetooth path. Pair with a real strap and watch one
offload.
Thanks
Built on upstream work by:
- @vishk23 — the Oura main-sleep selection behind the resting-heart-rate fix (ryanbr#375), the
wake → deep/REM staging guard and the de-contaminated benchmark that justified it (ryanbr#987, ryanbr#935,
ryanbr#991), the reversible R22 deep-data unlock (ryanbr#932), and theHrBroadcastersubscription-leak fix
(ryanbr#933). - @bartmuskala — reported the Effort gaps this release closes (ryanbr#983), the Updates-page problems
(ryanbr#984), and that a computed value was labelled "Whoop" where it should read "on-device" (ryanbr#936). - @NoBrainNoHeadache — asked for the Apple Health hydration integration that became the water and
caffeine import (ryanbr#949), and supplied French translation corrections (ryanbr#884). - @tanarchytan — found that Health Connect records were day-keyed with the phone's current zone
instead of the offset each record carries (ryanbr#1002). - @pipiche38 — the raw Oura history-notification capture (ryanbr#937), the localized Android What's New
title (ryanbr#878), and the evidence that Oura respiration reads low because banked IBI is not
beat-accurate (ryanbr#882). - @tigercraft4 — retention for scheduled debug exports (ryanbr#960, ryanbr#642, ryanbr#650) and the warning that a
.noopbakis a readable, unencrypted archive (ryanbr#962, ryanbr#644). - @longhoang2810 — localized the Android device-actions menu and fixed a timezone-dependent sleep
test (ryanbr#946). - @Malik-HBS — the connection and sync report (ryanbr#964).
And everyone who filed the reports behind these fixes.
NOOP AI 9.2.2 DX Beta
NOOP AI 9.2.2 DX Beta
Built on RyanBR's NOOP 9.2.1, this patch release completes the Apple localization pass for the NOOP AI
fork and fixes the two compact-iPhone chat problems: the composer being covered by the floating
navigation bar and long answers dimming while they were scrolled.
Fixed
- The Coach could start in the wrong language. Replies now follow the active NOOP app
localization rather than the device's formatting locale. The instruction is appended last and
exactly once, so a custom prompt, differently written question or older chat history cannot silently
change the reply language. The same contract covers regular answers, card analysis, check-ins,
briefings and proactive messages. Unsupported app languages fall back to English with the UI. - The composer could sit underneath the floating tab bar on compact iPhones. The app now measures
the bar's rendered height and passes that clearance to a Coach opened through More. The normal
SwiftUI safe-area inset still handles the keyboard and Home indicator; a true full-screen Coach
correctly resets the additional clearance to zero. - Long answers dimmed near the scroll edges. A row-wide scroll transition changed the opacity of
the whole message when either edge crossed the viewport. Removing it keeps every line fully opaque
while retaining the normal arrival transition for new messages.
Localized
- All 915 Apple strings added by this fork now cover the complete upstream language matrix:
English, German, Spanish, French, Italian, Portuguese (Portugal), Russian, Simplified Chinese and
Traditional Chinese. - Coach errors, empty-reply fallbacks, proactive-message prefixes and formatted provider-balance
labels are localizable as well. - Every fork-specific localization has been checked for presence and placeholder parity. The Italian,
Portuguese, Russian and Chinese wording is structurally complete but has not been reviewed by native
speakers yet.
Apple Watch and widgets
The AltStore/SideStore IPA remains the reliable phone-only build: it does not embed the Watch app or
WidgetKit extension. The source targets remain available for a Full Apple build signed under one paid
developer team. docs/IOS.md now documents the difference and the required target relationships
explicitly.
NOOP AI 9.2.1 DX Beta
This beta lands the coach's on-device memory and a simplified privacy model on top of the latest
upstream sync. Everything stays on-device: there is no NOOP account, server, or analytics service.
New
- On-device semantic memory and long-term insights. The coach can now retrieve past conversations
and journal entries by meaning rather than exact keyword match, entirely with a local embedding
model — no request ever leaves the device to do this. Off by default; enabling it, rebuilding the
index, and deleting it again are all explicit, reversible choices in Coach settings. - Three privacy presets instead of nine separate switches. Data access now defaults to one of
three plain-language modes — Essentials (core health, workouts, planning, memory), Personal
(adds stress, ordinary logs, personal patterns), and Deep insights (adds compact long-term
trends). An Expert mode still exposes every individual purpose toggle for anyone who wants it.
Sensitive journal labels (sexual health, relationship, illness, cannabis) are never covered by any
preset — they need their own separate "Sensitive journal" grant even under Deep insights or Expert. - Local alert history. The Today bell now records alerts that actually fired: low battery,
estimated battery runtime, fully charged, an early health warning, an inactivity nudge, and a smart
alarm wake. A denied operating-system notification permission hides only the banner, never the
in-app history. - Sideload-ready iOS beta. The release IPA is deliberately unsigned and contains no personal
signing identity. AltStore or SideStore signs it locally with the Apple ID you choose.
Refined
- Classic Today synthesis stays compact. Opening the first synthesis insight no longer enlarges
the whole card, and the extra WHOOP source label no longer competes with the scores.
Also in this build
This beta also carries the latest upstream sync (v9.2.1): battery saver now pauses the live
gauges and animations instead of ignoring the setting, Android notifications are fully translated,
and Today / the day chart open instantly even on a large history. See the
9.2.1 release notes for the full list.
Coming next
- Android distribution. The Android source version stays aligned; a public Android beta download
follows in a later rollout.
Experimental Build 4
This experimental release is based on the latest state of the ryanbr/noop repository (July 25, 2026, evening).
In addition to the upstream changes, this fork includes a wide range of custom features and improvements, including:
An extensively enhanced AI Coach
Custom UI/UX refinements
iOS-specific features and integrations
Reminders and proactive notifications
Custom activity status
Various additional improvements and quality-of-life enhancements
This build is considered experimental. Most features are functional, but bugs and unfinished areas are still expected.
If you encounter any issues, please report them. Every piece of feedback helps improve the project and speeds up the development of a stable release.