fix(oura): publish the ring's history drain to LiveState.backfilling so the sync indicators light under a ring - #2373
Open
pipiche38 wants to merge 1 commit into
Conversation
…so the sync indicators light under a ring Every sync indicator - the Today header capsule and status light, the sync chip, the Sleep, Live and Health "Syncing..." states, the macOS menu-bar row and the ryanbr#1164 "Pending sync" caption on today's Rest - reads ONE flag, `LiveState.backfilling`, with `syncChunksThisSession` beside it. Only `BLEManager` (the WHOOP offload) ever raised it. The Oura drain stamped `lastSyncedAt` at completion and nothing else, so under a ring all of those surfaces stayed at rest through every drain: a wearer whose ring was handing over a night saw the same header as one whose ring was idle, and the "handing over history now" beat ryanbr#245 added to the header never fired for them. - Swift `OuraLiveSource`: `enterBackfilling()` at fetch start (chunk tally reset, mirroring `BLEManager.startBackfilling`), one chunk per `0x11` batch summary, `exitBackfilling()` in `finishDrain` on every end (caught up, stalled, deadline, no cursor progress) BEFORE the completed-offload stamp, so the re-score that stamp triggers sees the flag at rest and does not defer Today's history-wide reads (ryanbr#755). A `publishedBackfilling` latch means the ring only ever lowers a flag it raised - `LiveState` is one object every source writes into. - The five duplicated `live.connected = false; live.streamingLiveHR = false` link-down writes fold into one `markLinkDown()` that also closes a drain cut by the drop, so a flag cannot be left up holding the header in "Syncing" until the next drain. - Kotlin twin: `WhoopBleClient.publishExternalBackfilling(active, chunks)` beside `publishExternalBattery`, a `syncSink` on `SourceCoordinator` wired in `NoopApplication`, and an `onBackfilling` callback on `OuraLiveSource` raised/ticked/lowered at the same four points (fetch start, batch summary, `finishDrain`, `stop()` + `STATE_DISCONNECTED`). Behaviour to expect: a caught-up ring's periodic drain is one GetEvents round trip at `bytes_left 0`, so the capsule expands and settles within seconds; a drain with a night behind it holds it up for the ~1-2 min a full pull takes, chunk tally ticking. The chained pass after a deadline stop lowers and re-raises it 5 s apart; `ChargeSyncIndicator` already handles a restart mid wind-down. Every action gated on the flag (`onAbortSync`, Health's "Sync now") is additionally WHOOP-gated, so nothing strap-only is exposed under a ring. Verification: `Strand` (macOS) builds; `compileFullDebugKotlin` clean; `doc_comment_lint` green. The drain is BLE-driven end to end, so the flag's timing is only checkable on hardware: to be confirmed on a ring against the strap log's "fetching history from cursor" / "history caught up" bracket with the Today header watched during it. Not yet run on hardware. Refs ryanbr#245, ryanbr#2208. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QrsZczdGcUyQSJVuYZuwUF
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Every sync indicator — the Today header capsule and status light, the sync chip, the Sleep, Live and Health "Syncing…" states, the macOS menu-bar row and the #1164 "Pending sync" caption on today's Rest — reads ONE flag,
LiveState.backfilling, withsyncChunksThisSessionbeside it. OnlyBLEManager(the WHOOP offload) ever raised it. The Oura drain stampedlastSyncedAtat completion and nothing else, so under a ring all of those surfaces stayed at rest through every drain: a wearer whose ring was handing over a night saw the same header as one whose ring was idle, and the "handing over history now" beat #245 added to the header never fired for them.Change
OuraLiveSource:enterBackfilling()at fetch start (chunk tally reset, mirroringBLEManager.startBackfilling), one chunk per0x11batch summary,exitBackfilling()infinishDrainon every end (caught up, stalled, deadline, no cursor progress) before the completed-offload stamp, so the re-score that stamp triggers sees the flag at rest and does not defer Today's history-wide reads (Community and team-chat notification parity: likely out of scope? #755). ApublishedBackfillinglatch means the ring only ever lowers a flag it raised —LiveStateis one object every source writes into.live.connected = false; live.streamingLiveHR = falselink-down writes fold into onemarkLinkDown()that also closes a drain cut by the drop, so a flag cannot be left up holding the header in "Syncing" until the next drain.WhoopBleClient.publishExternalBackfilling(active, chunks)besidepublishExternalBattery, asyncSinkonSourceCoordinatorwired inNoopApplication, and anonBackfillingcallback onOuraLiveSourceraised / ticked / lowered at the same four points (fetch start, batch summary,finishDrain,stop()+STATE_DISCONNECTED).Behaviour to expect
A caught-up ring's periodic drain is one GetEvents round trip at
bytes_left 0, so the capsule expands and settles within seconds; a drain with a night behind it holds it up for the ~1–2 min a full pull takes, chunk tally ticking. The chained pass after a deadline stop lowers and re-raises it 5 s apart;ChargeSyncIndicatoralready handles a restart mid wind-down. Every action gated on the flag (onAbortSync, Health's "Sync now") is additionally WHOOP-gated, so nothing strap-only is exposed under a ring.Not in this PR, noted for parity: the Android Oura drain still stamps no
lastSyncAtat completion (the Swift side has since 2026-08), so Android's sync chip reads ✓ from the WHOOP timestamp only. Separate concern.Verification
Strand(macOS) builds;compileFullDebugKotlinclean;doc_comment_lintgreen.fetching history from cursor/history caught upbracket with the Today header watched during it.Refs #245, #2208.