Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
763 commits
Select commit Hold shift + click to select a range
ea5f5bb
ci: find parity authority staleness on a schedule, not on someone PR …
ryanbr Sep 12, 2026
ede4ffb
Sync chip: surface the strap backlog, with both counts inflected (#2138)
digitalerdude Sep 12, 2026
a9e3d39
build: testing build 496 / 377
ryanbr Sep 12, 2026
d6ad093
Say that the day-owner path uses archived candidates, not just inheri…
digitalerdude Sep 12, 2026
a08bcaf
ble: tell a retired probe reader to turn the switch ON, and stop unde…
ryanbr Sep 12, 2026
7b1b2e9
parity tests: pass on macOS, and canonicalise expand_twin_map root (#…
digitalerdude Sep 12, 2026
322f970
parity_ledger: canonicalise the temp checkout root before deriving fr…
digitalerdude Sep 13, 2026
5fc6c7e
fix(diagnostics): keep the night raw Oura sidecar in the export (#2154)
pipiche38 Sep 13, 2026
551b3b8
tools: re-derive the parity authority after the Oura sidecar merge
ryanbr Sep 13, 2026
ba6b95b
doc(oura): correct the macOS pairing limitation, a factory-reset ring…
pipiche38 Sep 13, 2026
1dfc22e
docs(oura): correct the 7.5 heading left stale by 450256a0c (#2150)
pipiche38 Sep 13, 2026
ff86300
oura: score the ring's freshly-offloaded night now, not at the next p…
pipiche38 Sep 13, 2026
8fea1db
oura: refuse the onset rekey when the assembler's own clip never boun…
pipiche38 Sep 13, 2026
1608a65
oura: persist only the SpO2 raw channel, dropping dc_raw from spo2Sam…
pipiche38 Sep 13, 2026
b66f4f0
oura: fetch ring history every 5 min, not 15 (#2153)
pipiche38 Sep 13, 2026
974b490
oura: let the anchor check judge a 10 s gap, not only 30 s (#2155)
pipiche38 Sep 13, 2026
1c04823
health: trust the Android illness baselines per signal (#2157)
kavemang Sep 13, 2026
fe0f5c9
tools: re-derive the parity authority after the Oura and health merges
ryanbr Sep 13, 2026
91e9d4b
build: testing build 497 / 378
ryanbr Sep 13, 2026
9c0b5ee
stress: one spelling for the 0-3 level, on the widget and the card (#…
ryanbr Sep 13, 2026
2a4c157
stress: stop the widget snapshot rounding the level a second time (#2…
ryanbr Sep 13, 2026
b619ead
build: testing build 498 / 379
ryanbr Sep 13, 2026
77480be
stress: give the marker tile's reading the width the chip was holding…
ryanbr Sep 13, 2026
69ece60
build: testing build 499 / 380
ryanbr Sep 13, 2026
948d50c
ci: do not let a failed release delete look like a tag that already e…
ryanbr Sep 13, 2026
becdb6e
health: say when a Fitness Age is sitting on the end of its scale (#2…
ryanbr Sep 13, 2026
4481218
build: testing build 500 / 381
ryanbr Sep 13, 2026
d461520
stress: keep the Today curve as fresh as the screen it links to (#2177)
ryanbr Sep 13, 2026
0514cbf
today: put scan and connect on the Today header (#2178)
ryanbr Sep 13, 2026
03d4a2f
build: testing build 501 / 382
ryanbr Sep 13, 2026
54b4822
today: show the scan control only while the strap is away (#2180)
ryanbr Sep 13, 2026
7854435
build: testing build 502 / 383
ryanbr Sep 13, 2026
d211b40
stress: do not say Calibrating when the day has simply not started ye…
ryanbr Sep 13, 2026
d9afc8d
build: testing build 503 / 384
ryanbr Sep 13, 2026
6b6eb97
widget: refresh the stress curve without waiting for the app to be op…
ryanbr Sep 13, 2026
0ab1d49
build: testing build 504 / 385
ryanbr Sep 13, 2026
0446e09
oura: stop announcing a ring reboot the judge has just rejected, and …
pipiche38 Sep 13, 2026
a3bf780
health: point at the number that still moves when the age stops (#2189)
ryanbr Sep 14, 2026
ebcc18f
stress: draw the screen's line from the sliding read, not the bare ho…
ryanbr Sep 14, 2026
bccd945
stress: get the screen's work off the main thread and the chart out f…
ryanbr Sep 14, 2026
2cc4b6d
today: stop scoring a day of heart rate on the UI thread (#2194)
ryanbr Sep 14, 2026
3f65583
build: testing build 505 / 386
ryanbr Sep 14, 2026
dda33b3
oura: decode the WHOOP 5 console sequence as a wrapping u8 (#2192)
Trillient Sep 14, 2026
a78e2a6
whoop5: read the standard-profile R-R as milliseconds (#2195)
Trillient Sep 14, 2026
656e728
whoop5: decode the Kotlin console sequence as a wrapping u8 (#2196)
ryanbr Sep 14, 2026
077978e
whoop5: pin the Kotlin console sequence wrap, matching Swift (#2197)
ryanbr Sep 14, 2026
e201fa9
test(ios): make the reject-archive protection skip visible on macOS (…
ryanbr Sep 14, 2026
ef0c41c
test(import): assert the Inbox purge no-op off iOS instead of skippin…
ryanbr Sep 14, 2026
4e26623
sleep: date the browsed night, not the newest one, in the nav header …
ryanbr Sep 14, 2026
9e4abcf
perf: let the unprompted stress rescore actually yield to the UI (#2202)
ryanbr Sep 14, 2026
a7c3703
ui: shrink a stat value whose line ellipsized (#2203)
kavemang Sep 14, 2026
bfe9e2f
ui: test the stat-value shrink rule, and guard the line index (#2204)
ryanbr Sep 14, 2026
afb616a
docs: release notes for 11.7.0 (#2205)
ryanbr Sep 14, 2026
1fd9173
Release 11.7.0: bump version (minor) + build numbers + What's New
ryanbr Sep 14, 2026
9e63496
Release 11.7.0: add to AltStore source
ryanbr Sep 14, 2026
8818477
storage: the v46 lift-log schema, with its Room twin (#2098)
UtkuDenizAltiok Sep 14, 2026
168fc59
coach(ios): put Disconnect where iPhone can reach it (#2207)
ryanbr Sep 14, 2026
02969d6
doc(issues): let the bug form name an Oura ring, not only a WHOOP str…
pipiche38 Sep 14, 2026
0b8fb8a
health(ios): delete the workouts Apple Health kept and the store did …
ryanbr Sep 14, 2026
a8e88d7
battery: say whose charge it is on all seven surfaces, not three (#2216)
ryanbr Sep 14, 2026
67149e4
build: testing build 507 / 388
ryanbr Sep 14, 2026
44e4d15
nav(android): give Coach the bottom-bar tab it has on iOS (#2222)
ryanbr Sep 14, 2026
8039ad1
tools: repair the parity governance maintenance flow (#2212)
bhelm Sep 15, 2026
6934170
doc(issues): point an Oura reporter at the ring's known limits (#2215)
pipiche38 Sep 15, 2026
6402b6d
workouts: make auto-detection confirmation-only (#2220)
tigercraft4 Sep 15, 2026
4619a94
whoop5: do not report a failed flag read as a value (#2193)
Trillient Sep 15, 2026
41fbc1d
whoop5: guard the failed flag read on every platform, and on Android …
ryanbr Sep 15, 2026
ac1fc33
docs: expand protocol coverage and clarify WHOOP 4/5 compatibility (#…
bhelm Sep 15, 2026
3c8bfe6
test(sleep): pin the night-caption label and date pairing (#2224)
ryanbr Sep 15, 2026
4453a08
lift log: an on-device gym log book advanced by a double-tap on the s…
UtkuDenizAltiok Sep 15, 2026
de85d2a
build: testing build 508 / 389
ryanbr Sep 15, 2026
ce5121c
i18n(zh): complete the Android locale and carry it to the Apple catal…
ryanbr Sep 15, 2026
a473cf9
i18n(watch): translate the complications into both Chinese scripts, a…
ryanbr Sep 15, 2026
8898129
i18n(apple): complete zh-Hans in the main catalogue, and take zh-Hant…
ryanbr Sep 15, 2026
071f432
test(room): open the last released schema through the production migr…
kavemang Sep 15, 2026
b6dc041
tools(parity): make a one-sided property declarable (#2231)
ryanbr Sep 15, 2026
eb34f3c
analytics(android): Kotlin LiftMetrics twin of the Swift engine (#2232)
ryanbr Sep 15, 2026
36b49db
tools(parity): give the stale-base dead end an exit, then take it (#2…
ryanbr Sep 15, 2026
d823686
parity_ledger: keep trailing hex digits f/d when stripping literal su…
bhelm Sep 15, 2026
858d311
fix(sleep): name a night by the evening it belongs to, not its onset …
ryanbr Sep 15, 2026
c430ca0
build: testing build 509 / 390
ryanbr Sep 15, 2026
87b9f45
i18n(design): complete Chinese, and stop asking anyone to translate p…
ryanbr Sep 15, 2026
a850c5b
i18n(design): repair the French recovery and strain state words (#2247)
ryanbr Sep 15, 2026
830af6c
i18n: make the theme picker say light, not lightweight, on both platf…
ryanbr Sep 15, 2026
33cf678
i18n: drop six dead keys, and gate format specifiers in every locale …
ryanbr Sep 15, 2026
0e38eb6
fix(coach): keep the provider's message when a request is rate-limite…
kavemang Sep 15, 2026
d91a7e9
fix(rescore): let a resumed pass stand down when nothing changed (#2251)
ryanbr Sep 15, 2026
6ce9a0d
fix(oura): settle the 0x13 anchor unit by adjacency so a young ring n…
pipiche38 Sep 15, 2026
b2440ea
diag(oura): report which ring epoch each sidecar row believes in (#2253)
ryanbr Sep 16, 2026
cf895b4
feat(coach): move the coach settings onto their own screen (#2254)
ryanbr Sep 16, 2026
e37cde8
build: testing build 510 / 391
ryanbr Sep 16, 2026
b4556b6
fix(protocol): propagate the full frame integrity verdict to every co…
bhelm Sep 16, 2026
76e31a6
chore(parity): re-derive the twin map after the frame integrity change
ryanbr Sep 16, 2026
04dec60
feat(coach): make the live model catalogue reachable, and keep it cur…
ryanbr Sep 16, 2026
d80c5a2
feat(coach): offer the current OpenAI models (#2255)
ryanbr Sep 16, 2026
9a9a3fd
build: testing build 511 / 392
ryanbr Sep 16, 2026
75a291a
fix(oura): read every packet of a notification that tiles exactly int…
pipiche38 Sep 16, 2026
9632a84
test(oura): name the framing test hex helper so it cannot alias a pro…
ryanbr Sep 16, 2026
7d54594
chore(parity): re-derive the twin map after the packed-notification c…
ryanbr Sep 16, 2026
f376257
feat(steps): improve the steps charts and add an optional 30-day aver…
bhelm Sep 16, 2026
55631a9
build: testing build 512 / 393
ryanbr Sep 16, 2026
bf3c890
doc(readme): say which ring each Oura row was measured on, and update…
pipiche38 Sep 16, 2026
7ceca19
fix(stress): align the Apple stress level rounding with Android (#2258)
kavemang Sep 16, 2026
b6e4e3e
chore(parity): re-derive the twin map after the steps and stress changes
ryanbr Sep 16, 2026
97edae8
tools(parity): stop the scan walking into build output (#2259)
ryanbr Sep 16, 2026
4eda0dc
feat(workouts): offer the WHOOP sports NOOP was missing, and fix the …
ryanbr Sep 16, 2026
f69bd5f
stress: take the Apple screen's work off the main actor too (#2263)
ryanbr Sep 16, 2026
e65cf18
build: testing build 513 / 394
ryanbr Sep 16, 2026
45ee2fb
fix(ble): persist the family the strap actually established, not the …
kavemang Sep 16, 2026
9ed03a0
fix: stop an unparseable argument erasing a callsite in the parity sc…
ryanbr Sep 16, 2026
78402c1
build: testing build 514 / 395
ryanbr Sep 16, 2026
199ec5d
feat(coach): offer a master switch that turns the AI Coach off (#2269)
ryanbr Sep 16, 2026
1a312a9
fix(health): stop a CSV import turning a correct SDNN into RMSSD on e…
ryanbr Sep 16, 2026
4eef299
docs: share agent guidance through AGENTS.md (#2140)
bhelm Sep 16, 2026
acfdc3f
docs: stop the PR template prompting for an auto-closing issue reference
ryanbr Sep 16, 2026
8576a2d
build: testing build 515 / 396
ryanbr Sep 16, 2026
6a2b32b
fix(workouts): delete from every strap namespace the list reads (#2286)
ryanbr Sep 17, 2026
1f223ae
feat(workouts): discard sub-minute sessions and split the list into C…
ryanbr Sep 17, 2026
f332b56
build: testing build 516 / 397
ryanbr Sep 17, 2026
137dc0b
perf(sleep): compute the motion trace peak once instead of per epoch …
ryanbr Sep 17, 2026
81e8cf7
fix(notif): hold the morning recap until 05:00 (#2290)
ryanbr Sep 17, 2026
49cfbc2
feat(health): observe workouts for live delivery (#2291)
ryanbr Sep 17, 2026
2471ee0
fix(timeline): name the active device on the Deep Timeline source row…
pipiche38 Sep 17, 2026
6cd2ba1
fix(ui): stop stat labels running into their neighbour (#2292)
ryanbr Sep 17, 2026
2eafe4b
fix(health): publish to Apple Health once a deferred re-score lands (…
Iskrata Sep 17, 2026
65fde52
fix(rescore): pace a backgrounded pass under iOS CPU limits (#2296)
ryanbr Sep 17, 2026
6c1502f
build: testing build 517 / 398
ryanbr Sep 17, 2026
3b18bf7
iOS: Sync Strap shortcut, keep-screen-on while syncing, and a sync Li…
npapatheodorou Sep 17, 2026
34017a4
perf(charts): stop the 1 Hz live-HR tick re-rendering Workouts and Sl…
Iskrata Sep 17, 2026
6aa1faf
i18n: check the catalog key behind String(localized:) copy, and local…
ryanbr Sep 17, 2026
1f37c4e
build: testing build 518 / 399
ryanbr Sep 17, 2026
4a0dc36
docs(release): the 11.8.0 notes
ryanbr Sep 17, 2026
ef0c0d7
Release 11.8.0: bump version (minor) + build numbers + What's New
ryanbr Sep 17, 2026
f992299
Release 11.8.0: add to AltStore source
ryanbr Sep 17, 2026
de111a9
feat(rescore): log a pass's CPU time and assertion expiries beside it…
Iskrata Sep 18, 2026
ee8b7e3
build: testing build 520 / 401
ryanbr Sep 18, 2026
5157771
fix(oura): watch the auth handshake, so an unanswered get_nonce is re…
pipiche38 Sep 18, 2026
64b5c2d
fix(live): give a ring its own Live console instead of the WHOOP stra…
pipiche38 Sep 18, 2026
6b21429
docs(oura): stop claiming an Apple framework property as a twin (#2316)
ryanbr Sep 18, 2026
43bfe3e
fix(charge): score the breakdown against the baseline the ring rides …
ryanbr Sep 18, 2026
35ebf64
fix(rescore): pace a backgrounded pass in 10-second quanta, not once …
Iskrata Sep 18, 2026
4382c55
feat(today): draw the Today gauges as rings, and honour Reduce Motion…
andigandhi Sep 18, 2026
5d8a204
feat(android): let Today choose ring gauges or the liquid vessels (#2…
ryanbr Sep 18, 2026
e02ed9b
build: testing build 521 / 402
ryanbr Sep 18, 2026
40e63cf
Alarm readback: refuse to judge one that answered an earlier arm (#2322)
ryanbr Sep 18, 2026
f4a6f3f
Sleep: show "May be incomplete" only when the night actually reads sh…
ryanbr Sep 18, 2026
43f90e1
Parity: re-derive the twin-map authority after three in-scope product…
ryanbr Sep 19, 2026
654f451
Parity: declare the OuraAuthWatchdog twin where the ledger reads it (…
ryanbr Sep 19, 2026
d8f2ac4
Changelog: say which platforms the 11.8.0 lift log actually shipped o…
ryanbr Sep 19, 2026
e6bbceb
i18n CI: scope the coverage gate to the diff again, not the whole tre…
digitalerdude Sep 19, 2026
b817102
i18n CI: diff a push against the tip before it, not against itself (#…
ryanbr Sep 19, 2026
8aa8cab
docs: WHOOP 4 protocol parity, contract-to-implementation map and sou…
bhelm Sep 19, 2026
6c84238
Sleep: ask every block whether the night was staged on sparse motion …
ryanbr Sep 19, 2026
897adce
Sleep: give Android the H9 low-confidence note it was promised (#2330)
ryanbr Sep 19, 2026
0c503f4
build: testing build 522 / 403
ryanbr Sep 19, 2026
715ad98
BLE: put the link's signal strength on its epitaph (#2333)
ryanbr Sep 19, 2026
8ea500a
build: testing build 523 / 404
ryanbr Sep 19, 2026
8f7e2bd
Health: say why HRV is blank when the night over-reported R-R (#2336)
ryanbr Sep 19, 2026
3dfce29
Apple: tint the hero Charge ring by recovery score, matching Android …
adamstefanik Sep 19, 2026
a670268
BLE: redact the chosen strap name in the rename log (#2337)
ryanbr Sep 19, 2026
158a05a
Android: show the optimal Effort band on the Today ring (#2323)
andigandhi Sep 19, 2026
2ae4257
iOS: retry an unfinished background re-score after a cooldown (#2334)
Iskrata Sep 19, 2026
7173d93
iOS: drop the unreachable second case .coach in RootTabView (#2349)
Iskrata Sep 19, 2026
78ea2da
Lab Book: show custom markers at their own precision (#2347)
Iskrata Sep 19, 2026
a693306
Lab CSV import: a count row and its % row no longer overwrite each ot…
Iskrata Sep 19, 2026
5c0ca1d
5/MG strap name: a read-only probe, and an honest explainer (#2339)
ryanbr Sep 20, 2026
946e89b
5/MG strap name: allow the rename, behind Test Centre and an in-fligh…
ryanbr Sep 20, 2026
23a0a01
build: testing build 524 / 405
ryanbr Sep 20, 2026
fdaa36b
Alarms screen: say which wake time actually wakes you, and when (#2353)
ryanbr Sep 20, 2026
dd27eef
build: testing build 525 / 406
ryanbr Sep 20, 2026
df3634d
Alarms: count down to the next guaranteed wake on Android too (#2354)
ryanbr Sep 20, 2026
1e229f0
build: testing build 526 / 407
ryanbr Sep 20, 2026
f9a0cd8
AGENTS.md: two readouts of one fact must not be able to disagree
ryanbr Sep 20, 2026
0599d8a
parity: re-derive the authority main has been carrying stale since #2348
ryanbr Sep 20, 2026
dea107f
Body clock dial: give every element its own radius, and label the hou…
ryanbr Sep 20, 2026
88b4355
build: testing build 527 / 408
ryanbr Sep 20, 2026
b97d927
Appearance: let the gauge numerals be softer (#2357)
ryanbr Sep 20, 2026
e29bfe9
build: testing build 528 / 409
ryanbr Sep 20, 2026
1dadf87
Backup picker: report what the sheet did, not why (#2359)
ryanbr Sep 20, 2026
91f926f
build: testing build 529 / 410
ryanbr Sep 20, 2026
01bfccb
sleep-detect: stop claiming no-motion means nothing could stage a nig…
ryanbr Sep 20, 2026
3659bdf
feat(oura): Test Centre toggle for the official app's SetNotification…
pipiche38 Sep 21, 2026
ad1f0b4
fix(ios): load the Liquid Today Weight tile from Apple Health and pro…
rodrigosa7 Sep 21, 2026
f8ada93
parity: migrate the authority main could no longer reproduce
ryanbr Sep 21, 2026
a6c1c01
fix(health): hold a still-open night out of Apple Health until it closes
Iskrata Sep 21, 2026
9590d72
feat(parity): shared parity cases with independent expected-result va…
bhelm Sep 21, 2026
a7f94d3
docs(agents): a gate must be able to fail on the change that caused it
ryanbr Sep 21, 2026
ed7fbcb
docs: add a validation protocol for scoring changes
vishk23 Sep 21, 2026
1015d07
build: testing build 530 / 411
ryanbr Sep 21, 2026
6fbd30c
fix(diagnostics): read last sleep and recovery from the strap actuall…
ryanbr Sep 21, 2026
a56840b
build: testing build 531 / 412
ryanbr Sep 21, 2026
29d90eb
fix(diagnostics): put the R-R transport line in every export, not onl…
ryanbr Sep 22, 2026
e9fe33d
fix(android): keep Deep Timeline zoom and pan from snapping back
andigandhi Sep 22, 2026
a9717ab
Strap log: keep every line on disk, across restarts, within 2 MB
UtkuDenizAltiok Sep 22, 2026
2fd88d1
hrv: a nightly 5-min window needs minBeats clean intervals to count
Iskrata Sep 22, 2026
dab0efe
fix(health): never write RMSSD under the SDNN type in the Apple Healt…
Iskrata Sep 22, 2026
58c41ba
today: swipe the day the same way on both platforms
andremiliano Sep 22, 2026
65ead7d
log: say whether a timed-out offload actually banked anything (#2390)
ryanbr Sep 22, 2026
a4706c0
ble(android): start the keep-alive on a 5/MG that bonds by CLIENT_HEL…
ryanbr Sep 22, 2026
8637ce2
ble: give Android the HR notify line iOS has always printed (#2392)
ryanbr Sep 22, 2026
7065513
ci: ship the macOS app built Release, like the iOS one (#2396)
ryanbr Sep 22, 2026
bca05f0
build: testing build 532 / 413
ryanbr Sep 22, 2026
c8a3f10
macos: stop the decorative frame loops when no window can be seen (#2…
ryanbr Sep 22, 2026
039dd93
design: parse a dynamic colour's hex once per token, not once per fra…
ryanbr Sep 22, 2026
bbd28f5
macos: filter the window list on .titled, so a hidden window still co…
ryanbr Sep 22, 2026
49137ed
sleep: name the night on the HR-only funnel lines (#2398)
ryanbr Sep 22, 2026
d107501
ble: report the link's signal shape on the epitaph, not just its last…
ryanbr Sep 22, 2026
751fa1d
chore(parity): migrate the twin-map authority after the diagnostics m…
ryanbr Sep 22, 2026
3ada90c
Lift Log: follow-ups from seven gym sessions (#2403)
UtkuDenizAltiok Sep 23, 2026
94a71b0
diagnostics: summarise the standard-HR host-received line, keep every…
UtkuDenizAltiok Sep 23, 2026
c9d928c
ui: take the keyboard watcher's recognizer off the window in deinit t…
ryanbr Sep 23, 2026
84bd370
diagnostics: report the stall that crosses a window boundary (#2405)
ryanbr Sep 23, 2026
a4678af
diagnostics: say how long a passive reconnect waited for the OS (#2406)
ryanbr Sep 23, 2026
1eeb1ff
Android: back the Rhythm screen with a real read path (#2381)
andigandhi Sep 23, 2026
971d0d9
today: draw the active ring's own battery in the Liquid header (#2374)
pipiche38 Sep 23, 2026
720187a
sleep: hand the #899 heal's bank-recency witness to the computed id o…
pipiche38 Sep 23, 2026
15c34d4
stress: print the LF/HF ratio through the same tenths arithmetic on b…
ayiskakov Sep 23, 2026
e3f6ef0
live: the Live-screen strap-log save carries the same extras as Setti…
ayiskakov Sep 23, 2026
b736902
diagnostics: the REM funnel names the stager it replays and the one t…
ayiskakov Sep 23, 2026
f25ef40
analytics: take the daily resting HR from the primary session, not th…
moyasserr Sep 23, 2026
266a870
build: testing build 533 / 414
ryanbr Sep 23, 2026
b649152
today: draw the hero ring flat, without the bloom stand-in (#2410)
ryanbr Sep 23, 2026
34211e1
ble: stamp the passive-reconnect wait after the reconnect is schedule…
ryanbr Sep 23, 2026
4b0cc6b
ble: support direct HR broadcast on WHOOP 4.0 (#2400)
don86nl Sep 23, 2026
c611593
today: the 4.0 broadcast row says Partly, not Yes (#2400)
ryanbr Sep 23, 2026
e67c051
oura: do not arm daytime HR on a connect made while live HR is suspen…
pipiche38 Sep 23, 2026
f4600d0
ios: push the Live HR banner only when what it shows changes (#2415)
UtkuDenizAltiok Sep 23, 2026
5783c49
docs: app-build is active, and three comments that said otherwise
ryanbr Sep 23, 2026
09c583b
today: stop the Key Metrics header naming a window it is not drawing …
andremiliano Sep 23, 2026
be060c2
cleanup: remove private code nothing uses (#2417)
UtkuDenizAltiok Sep 23, 2026
38b5685
docs: three comments that outlived what they pointed at (#2417)
ryanbr Sep 23, 2026
d990ef6
workouts: record one heart-rate sample a second in a manual workout (…
UtkuDenizAltiok Sep 23, 2026
44ef71e
chore(parity): re-derive the authority, and drop eight stale debt ent…
ryanbr Sep 23, 2026
8f064df
stress: take each live R-R packet once in the iOS check-in (#2418)
UtkuDenizAltiok Sep 23, 2026
9d85ea8
perf(rescore): stop a post-sync pass re-scoring nights and cycles who…
Iskrata Sep 23, 2026
3ad2588
build: testing build 534 / 415
ryanbr Sep 23, 2026
8fc16bc
logs: say when the crash in a strap log is not from the build that wr…
ryanbr Sep 23, 2026
f15360d
ble: reject a false start-of-frame by its own header checksum (#2429)
ryanbr Sep 24, 2026
2e3d1a2
hrv: do not replace a refused main night's HRV with the day's naps (#…
pipiche38 Sep 24, 2026
f412fa0
log(hrv): describe the main night the gate judged in the hrv diag lin…
pipiche38 Sep 24, 2026
06083a0
today: centre the hero label on its ring, as its own note already spe…
ryanbr Sep 24, 2026
9c99138
live HR: show only a heart rate the strap is measuring, and keep the …
UtkuDenizAltiok Sep 24, 2026
24f2c87
chore(parity): re-derive the authority after nine merges drifted it
ryanbr Sep 24, 2026
f387277
test(i18n): repin the hero label padding assertion to the shape 06083…
ryanbr Sep 24, 2026
4653e2a
log(oura): say WHICH SpO2 channel a first-decoded line is reporting (…
pipiche38 Sep 24, 2026
4fa8a8c
stress: ramp the daytime line down the chart, not across the day (#2432)
ryanbr Sep 24, 2026
141cbd9
ble: ask again for the peripheral once the radio is up, instead of sc…
ryanbr Sep 24, 2026
5762a86
trends: place the date axis on named days, so a label cannot print tw…
ryanbr Sep 25, 2026
88d11ee
oura: tag 0x7B SpO2 stable as its own channel so it cannot enter the …
claude Sep 25, 2026
166db35
health: keep the Oura ring's single channel out of the Raw SpO2 tile
claude Sep 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
14 changes: 10 additions & 4 deletions .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ body:
Many "bugs" are expected behaviour or a known limit. **Please check these before filing — it's the fastest way to an answer:**
- **[FAQ](https://github.com/ryanbr/noop/blob/main/docs/FAQ.md)** — live HR, recovery/sleep, steps, importing, privacy, updates.
- **[WHOOP 5.0 / MG status](https://github.com/ryanbr/noop/wiki/WHOOP-5.0-and-MG-Status)** — SpO₂, steps, recovery/sleep on the newer straps (the most common topic).
- **[Oura ring support](https://github.com/ryanbr/noop/blob/main/README.md#oura-ring-support)** — experimental; what a ring can and cannot give NOOP (Sleep and Strain but no Recovery on a ring-only day, no HRV or respiratory rate, live HR is partial).
- **[Troubleshooting](https://github.com/ryanbr/noop/wiki/Troubleshooting)** — pairing, "syncs but no data", generic HR straps.

---
Expand Down Expand Up @@ -51,12 +52,16 @@ body:
- type: dropdown
id: strap
attributes:
label: WHOOP strap model
label: WHOOP strap / Oura ring model
description: The model NOOP shows on the Devices screen for the device this report is about.
options:
- WHOOP 4.0
- WHOOP 5.0
- WHOOP MG
- No strap — imported data only
- Oura Ring 3 (Gen3)
- Oura Ring 4
- Oura Ring 5
- No strap or ring — imported data only
- Not sure
validations:
required: true
Expand All @@ -67,7 +72,8 @@ body:
multiple: true
description: Tick all that apply.
options:
- Live Bluetooth (strap connected)
- Live Bluetooth (WHOOP strap connected)
- Live Bluetooth (Oura ring connected)
- WHOOP CSV export import
- Apple Health import
- Health Connect import
Expand Down Expand Up @@ -147,7 +153,7 @@ body:
required: true
- label: I checked the [Troubleshooting guide](https://github.com/ryanbr/noop/wiki/Troubleshooting) — especially "history syncs but no data" (most often the strap isn't banking history to flash; charge it to 100% and reconnect).
required: true
- label: I checked the [FAQ](https://github.com/ryanbr/noop/blob/main/docs/FAQ.md) and — if I'm on a WHOOP 5.0 / MG — the [5.0/MG status page](https://github.com/ryanbr/noop/wiki/WHOOP-5.0-and-MG-Status), and this isn't an already-known limit (e.g. SpO₂, or recovery/sleep on the 5/MG).
- label: I checked the [FAQ](https://github.com/ryanbr/noop/blob/main/docs/FAQ.md) and — if I'm on a WHOOP 5.0 / MG — the [5.0/MG status page](https://github.com/ryanbr/noop/wiki/WHOOP-5.0-and-MG-Status), or — if I'm on an Oura ring — the [Oura ring support](https://github.com/ryanbr/noop/blob/main/README.md#oura-ring-support) table, and this isn't an already-known limit (e.g. SpO₂, recovery/sleep on the 5/MG, or no Recovery / HRV on a ring-only day).
required: true
- label: I've left out anything personal (strap serial, account email, raw export).
required: true
2 changes: 1 addition & 1 deletion .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,4 @@ that covers it. "Builds and unit tests pass" alone is not enough for BLE work.

## Related issues

<!-- Closes #N -->
<!-- Refs #N -->
16 changes: 16 additions & 0 deletions .github/workflows/android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,27 @@ on:
paths:
- 'android/**'
- '.github/workflows/android.yml'
# The twin-drift guards read the SWIFT side's fixtures on purpose — DecoderOracleTest compares
# against Packages/.../decoder_oracle.json to catch a one-sided edit, and the schema/protocol
# oracles work the same way. Without these, editing the Swift copy alone leaves the very test
# written to catch that from ever running. Keep this list in step with what the tests read.
- 'Packages/WhoopProtocol/Sources/WhoopProtocol/Resources/**'
- 'Packages/WhoopProtocol/Tests/WhoopProtocolTests/Resources/**'
- 'Packages/WhoopStore/Tests/WhoopStoreTests/Resources/**'
- 'Strand/Resources/Localizable.xcstrings'
push:
branches: [main]
paths:
- 'android/**'
- '.github/workflows/android.yml'
# The twin-drift guards read the SWIFT side's fixtures on purpose — DecoderOracleTest compares
# against Packages/.../decoder_oracle.json to catch a one-sided edit, and the schema/protocol
# oracles work the same way. Without these, editing the Swift copy alone leaves the very test
# written to catch that from ever running. Keep this list in step with what the tests read.
- 'Packages/WhoopProtocol/Sources/WhoopProtocol/Resources/**'
- 'Packages/WhoopProtocol/Tests/WhoopProtocolTests/Resources/**'
- 'Packages/WhoopStore/Tests/WhoopStoreTests/Resources/**'
- 'Strand/Resources/Localizable.xcstrings'
workflow_dispatch:

concurrency:
Expand Down
20 changes: 20 additions & 0 deletions .github/workflows/app-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,29 @@ on:
branches: [main]
paths:
- 'Strand/**'
# The app-target TESTS, not just the sources they cover. The `Test Strand` step below runs
# StrandTests, but a PR touching only those tests matched nothing here, so it built and ran
# nothing: one that failed to compile, or simply failed, merged unverified and went red on the
# next unrelated PR that happened to touch `Strand/**`, mis-attributing the breakage to whoever
# that was.
#
# This is the only test directory that needs naming, and the reason is layout rather than
# oversight: Android's tests sit under `android/**` and the package suites under `Packages/**`,
# both already inside their subject's glob, while StrandTests is a SIBLING of `Strand/` instead
# of part of it. `project.yml` does not cover it either — the target takes
# `sources: [StrandTests]`, a directory, so adding a test file changes no other tracked path.
- 'StrandTests/**'
- 'StrandiOS/**'
- 'StrandiOSShared/**'
- 'StrandiOSWidgets/**'
# The watch app and its complications, for the same reason StrandTests is named above: the
# NOOPiOS scheme BUILDS them (NOOPiOS embeds NOOPWatch, which embeds NOOPWatchComplications),
# but neither directory sits under any glob here, so a PR touching only them matched nothing
# and compiled nothing. A broken watch target merged unverified and surfaced on the next PR
# that happened to touch Strand/**, pointing at the wrong change. #2227 translated 25
# complication strings and got no build at all, which is what surfaced this.
- 'NOOPWatch/**'
- 'NOOPWatchComplications/**'
- 'Packages/**'
- 'project.yml'
- '.github/workflows/app-build.yml'
Expand Down
21 changes: 16 additions & 5 deletions .github/workflows/fork-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -210,12 +210,23 @@ jobs:
run: xcodegen generate
- name: Build NOOP.app (macOS, unsigned, universal)
# #51: 'platform=macOS' on an Apple-Silicon runner resolves to the host arch and thins the binary to
# arm64-only (project.yml's ONLY_ACTIVE_ARCH: NO is set only under configs.Release, so the Debug build
# inherits the default YES). That shipped an arm64-only app that won't launch on Intel Macs. Force a
# true universal build with the generic destination + explicit ARCHS/ONLY_ACTIVE_ARCH; these
# command-line settings override the config, so it stays Debug (unchanged shipping behaviour).
# arm64-only. That shipped an arm64-only app that won't launch on Intel Macs. Force a true universal
# build with the generic destination + explicit ARCHS/ONLY_ACTIVE_ARCH, which override whatever the
# configuration says and therefore keep holding after the switch below.
#
# #2393: Release, matching the iOS leg. macOS shipped Debug for no reason anyone chose — #51 was
# about architecture and said so, and "unchanged shipping behaviour" there meant that PR was not
# also changing the configuration. The artefact showed it: a 120 KB stub plus a 195 MB
# `.debug.dylib` and a `__preview.dylib`, against 53 MB for the same commit built Release. A
# reporter measured the shipped build costing a third to half a CPU core at idle, and unoptimised
# SwiftUI compounds a hot path that already ran every frame.
#
# What Release removes beyond the optimisation: `#if DEBUG` code, which here is previews, sample
# data and the `--demo-sync` screenshot harness (its own doc already says it is stripped from every
# Release build). The iOS artefact has shipped without all of that since it went Release, so this
# brings macOS into line rather than into new territory. Use a local Debug build for the harness.
run: >-
xcodebuild -scheme Strand -configuration Debug
xcodebuild -scheme Strand -configuration Release
-destination 'generic/platform=macOS' ARCHS="x86_64 arm64" ONLY_ACTIVE_ARCH=NO
-derivedDataPath build/dd
CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY=""
Expand Down
56 changes: 54 additions & 2 deletions .github/workflows/fork-testing-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,56 @@ jobs:
echo "ver=$VER" >> "$GITHUB_OUTPUT"
# ROLLING release: recreate the single tag so stale (incl. old-version) assets never linger —
# one stable URL, always the newest build, zero cleanup. (To KEEP a build, cut a one-off tag.)
gh release delete "$TAG" --repo "$GITHUB_REPOSITORY" --cleanup-tag --yes || true
#
# The delete USED to end in `|| true`, which could not tell "the tag is not there yet",
# which is the normal first-run case and fine, from "the delete failed", which makes the
# create below certain to fail (#2174). Build 499 hit a 500 from GitHub here, the create
# then stopped on "Release.tag_name already exists", and since meta gates all four build
# legs a blip in a metadata step threw away the Android, iOS and macOS builds behind it.
# The message a reader got described the symptom and pointed away from the cause.
delete_rolling_release() {
local out status attempt
for attempt in 1 2 3; do
out=$(gh release delete "$TAG" --repo "$GITHUB_REPOSITORY" --cleanup-tag --yes 2>&1) && status=0 || status=$?
[ "$status" -eq 0 ] && return 0
# Nothing there to delete is success, not failure: first build, or an earlier run that
# got as far as deleting. Anything else is worth another go, GitHub 5xx being transient.
case "$(printf '%s' "$out" | tr '[:upper:]' '[:lower:]')" in
*"not found"*|*404*) return 0 ;;
esac
echo "release delete attempt ${attempt} of 3 did not succeed: ${out}"
sleep $((attempt * 5))
done
return 1
}
delete_rolling_release || {
echo "::error::Could not delete $TAG after 3 attempts. Not creating over it, because that fails with a message about the tag existing rather than about this."
exit 1
}
# Assert rather than assume. A delete that reports success but leaves the release standing
# would otherwise be read by the create as a name clash, which is the confusion above.
# Known limit: a 5xx on this view reads the same as a clean 404, so a blip here lands back
# on the create's own error. That is where we were before, not somewhere worse, and failing
# the build on an inconclusive view would stop builds whose release really had gone.
if gh release view "$TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
echo "::error::$TAG still exists after a delete that reported success. Refusing to create over it."
exit 1
fi
# The TAG has to go as well, and its own failure is the quiet one. `create` ignores --target
# when the tag already exists, so a release gone but a tag left behind does not fail: it
# publishes today's assets against a tag still pointing at the previous build's commit, and
# nothing says so. The release title carries $SHA from the workflow, so it would read as
# today's build while `git checkout testing-latest` handed someone yesterday's source.
# Deleting the ref directly is the same thing --cleanup-tag meant to do, so try once more
# before giving up rather than failing a build over a leftover.
if gh api "repos/$GITHUB_REPOSITORY/git/refs/tags/$TAG" >/dev/null 2>&1; then
echo "$TAG survived --cleanup-tag; deleting the ref directly."
gh api -X DELETE "repos/$GITHUB_REPOSITORY/git/refs/tags/$TAG" >/dev/null 2>&1 || true
if gh api "repos/$GITHUB_REPOSITORY/git/refs/tags/$TAG" >/dev/null 2>&1; then
echo "::error::The tag $TAG could not be removed. Creating now would publish this build against the previous build's commit, so stopping instead."
exit 1
fi
fi
gh release create "$TAG" --repo "$GITHUB_REPOSITORY" --prerelease --target "${{ github.sha }}" \
--title "NOOP Staging — base ${VER} · ${DATE} · ${SHA}" \
--notes "Rolling **latest** testing build — refreshed in place, so this tag is always the newest. NOOP main + ryanbr's open PRs + vetted community PRs, built with the **NOOP Staging** identity (installs **beside** the official app, separate data). Filenames carry the base app version (\`v${VER}\`).
Expand Down Expand Up @@ -103,8 +152,11 @@ jobs:
- name: Build NOOP.app (macOS, unsigned, universal)
# #51: force a true universal (x86_64 + arm64) build — 'platform=macOS' on an Apple-Silicon runner
# thins to arm64-only. Mirrors the release workflow so testing builds match what ships.
# #2393: Release, for the reasons spelled out in fork-release.yml. A testing build that is not
# built the way the release is cannot be used to judge how the release performs, which is the one
# thing a testing build is for.
run: >-
xcodebuild -scheme Strand -configuration Debug
xcodebuild -scheme Strand -configuration Release
-destination 'generic/platform=macOS' ARCHS="x86_64 arm64" ONLY_ACTIVE_ARCH=NO
-derivedDataPath build/dd
CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY=""
Expand Down
43 changes: 39 additions & 4 deletions .github/workflows/i18n-coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,43 @@ jobs:
with:
fetch-depth: 0

# `github.base_ref` is set ONLY for pull_request; on a push to main it is empty, which would
# pass a bare `origin/`. Fall back to main. (The ref is retained for workflow compatibility —
# `--ci` is a whole-tree gate, so the value it resolves to does not change the verdict.)
# Which commit the audit diffs against. Since #524 this DECIDES the verdict: `--ci` exempts
# any violation already present at the base, so the gate reports what this change introduced
# rather than auditing the whole tree. That is the point of #524 (a regression on main no
# longer red-flags every open PR), and it makes the base ref load-bearing in two ways.
#
# `fetch-depth: 0` above is one of them. The audit reads the base through `git show`, so a
# shallow clone misses every base lookup. That direction fails safe, since nothing is exempt
# and the gate fails loudly rather than passing, but it fails for a reason nobody would guess.
#
# The other is this resolution. `github.base_ref` is set ONLY for pull_request. On a push it is
# empty, and the obvious fallback of origin/main is the PUSHED COMMIT ITSELF: the run would
# compare the tree against itself and could never fail. Verified by planting a hardcoded
# literal, base=HEAD passes and base=HEAD~1 catches it. That would have quietly retired the
# case the push trigger exists for, described at the top of this file: a release pushes
# straight to main with no PR, and appchangelog-gen.py emits the What's New title as a raw
# Kotlin literal. So a push diffs against `github.event.before`, the tip before the push,
# which spans every commit the push carried rather than just the last one.
- name: Resolve the base to diff against
id: base
run: |
set -euo pipefail
if [ -n "${{ github.base_ref }}" ]; then
base="origin/${{ github.base_ref }}" # pull_request: the branch it targets
else
before="${{ github.event.before }}" # push: the tip before this push
# All-zeros on a branch's first push, and absent for a manual run. Neither can be
# diffed, so fall back to the parent: still a real comparison, just narrower.
if [ -n "$before" ] \
&& [ "$before" != "0000000000000000000000000000000000000000" ] \
&& git cat-file -e "$before^{commit}" 2>/dev/null; then
base="$before"
else
base="HEAD~1"
fi
fi
echo "ref=$base" >> "$GITHUB_OUTPUT"
echo "diffing against $base"

- name: i18n audit (regression gate)
run: python3 Tools/i18n_audit.py --ci origin/${{ github.base_ref || 'main' }}
run: python3 Tools/i18n_audit.py --ci "${{ steps.base.outputs.ref }}"
Loading
Loading