Repository navigation
backfill: distinguish no-flash-cursor from writing-but-recording-nothing in the empty-offload banner (#1754) - #1965
Merged
Conversation
…ing in the empty-offload banner (ryanbr#1754) An offload that persists zero sensor records always said the same thing: "your strap had no stored history to hand over... This usually means its clock has lost sync... Fully charge it to 100%..." That copy was written for the no-flash-cursor state (trim=0xFFFFFFFF), which Backfiller detects and reports separately with the same cause. But the banner is gated only on `sessionRowsPersisted == 0` at HISTORY_COMPLETE, so it also fires for a strap whose flash cursor is perfectly healthy — and then names a cause that demonstrably does not apply. A WHOOP 4.0 capture showed exactly this: a valid, advancing write pointer (106970 → 106972), a healthy clock (drift -1s), and every page carrying console output only. The user is told to charge it because its clock lost sync. Charging will not change any of the above, and the message sends them away from the real cause. The fix exposes `Backfiller.sawNoFlashCursor` (was private `loggedNoCursor`) on both platforms and uses it at the banner decision point to choose between two named states: - No flash cursor (trim=0xFFFFFFFF): the existing clock/charge copy, now `Backfiller.noFlashCursorBanner`. - Valid, advancing cursor, zero sensor rows: a new `Backfiller.noSensorRecordsBanner` that says this is NOT a clock problem, points at the sensor front-end or power state, and asks for a strap log rather than promising that charging will fix it. Both banners are byte-identical across Swift and Kotlin, kept as named constants so the caller's branch reads as a choice between two states rather than two inline string literals. The `staleNewest` dated banner still takes precedence over both (it is the most actionable when it applies). Cross-platform parity: Swift `Backfiller.sawNoFlashCursor` + `noFlashCursorBanner` + `noSensorRecordsBanner` mirror the Kotlin twins exactly. Tests on both platforms pin the wording and the distinction. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
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.
Summary
trim=0xFFFFFFFF), but the banner is gated only onsessionRowsPersisted == 0, so it also fires for a strap whose flash cursor is perfectly healthy — and then names a cause that demonstrably does not apply.Backfiller.sawNoFlashCursor(was privateloggedNoCursor) on both platforms and uses it at the banner decision point to choose between two named states:trim=0xFFFFFFFF): the existing clock/charge copy, nowBackfiller.noFlashCursorBanner.Backfiller.noSensorRecordsBannerthat says this is NOT a clock problem, points at the sensor front-end or power state, and asks for a strap log rather than promising that charging will fix it.staleNewestdated banner still takes precedence over both (it is the most actionable when it applies).Cross-platform parity
Backfiller.sawNoFlashCursor+noFlashCursorBanner+noSensorRecordsBannermirror the Kotlin twins exactly.Files
Strand/Collect/Backfiller.swift— exposedsawNoFlashCursor; addednoFlashCursorBanner+noSensorRecordsBannerconstants.Strand/BLE/BLEManager.swift— banner decision now branches onsawNoFlashCursor.StrandTests/BackfillerSessionTallyTests.swift— 4 new tests.android/.../ble/Backfiller.kt— exposedsawNoFlashCursor; addednoFlashCursorBanner+noSensorRecordsBannerconstants.android/.../ble/WhoopBleClient.kt— banner decision now branches onsawNoFlashCursor.android/.../ble/BackfillerSessionTallyTest.kt— 4 new tests.Test plan
cd android && ./gradlew testFullDebugUnitTest --tests "com.noop.ble.BackfillerSessionTallyTest"— all existing + 4 new testscd android && ./gradlew compileFullDebugKotlin— no compile errorsxcodebuild -project Strand.xcodeproj -scheme Strand -destination 'platform=macOS' CODE_SIGNING_ALLOWED=NO build— app-target Swift compilesGenerated with Devin