Repository navigation
Today pull-to-sync: iOS/macOS twin of #426 + fix Android stale enabled-gate - #502
Conversation
…not just a UI reload (#334) Android #426 added Today pull-to-sync. iOS's LiquidTodayView already had a pull gesture (handlePull), but it only reloaded UI data (repo.refresh + load) — it never requested a fresh strap history sync. Wire ble.syncNow() into that existing refresh action so a pull kicks a manual offload too, matching Android. syncNow() is internally gated (connected + bonded + not-already-backfilling), so a pull while disconnected or mid-offload safely no-ops — the same guard Android routes through WhoopBleClient.canRequestSync. Observe BLEManager (injected at the app roots beside model), NOT AppModel: AppModel @publishes bpm on the ~1 Hz HR tick, so observing it would re-render all of Today every second (the exact churn the LiveState leaves isolate). BLEManager only publishes connect/discovery state, never HR. App-target Swift (shared iOS + macOS); validated via app-build.
198593b to
0bf08b6
Compare
material3 1.2.1's rememberPullToRefreshState captures the enabled lambda ONCE
(rememberSaveable, no rememberUpdatedState), so { canPullToSync } froze the plain
Boolean from the FIRST composition. Today usually first composes before the strap
has (re)connected, so the gesture stayed disabled for the whole session and
pull-to-sync (#426) silently never worked in the common cold-launch flow.
Read the stable liveSnap State live inside the enabled lambda instead, so each
gesture check sees the current connected/bonded/backfilling. Safe either way
(syncNow is triple-gated); this just lets the gesture enable once the strap is ready.
compileFullDebugKotlin + TodayExplainabilityTest green.
|
Re-review of the merged Android #426 turned up a real bug, folded in here (same feature, both platforms): Android stale pull-to-sync gate. Fix: read the stable |
…enabled-gate (ryanbr#502) * iOS/macOS Today: pull-to-sync — a pull now requests a strap offload, not just a UI reload (ryanbr#334) Android ryanbr#426 added Today pull-to-sync. iOS's LiquidTodayView already had a pull gesture (handlePull), but it only reloaded UI data (repo.refresh + load) — it never requested a fresh strap history sync. Wire ble.syncNow() into that existing refresh action so a pull kicks a manual offload too, matching Android. syncNow() is internally gated (connected + bonded + not-already-backfilling), so a pull while disconnected or mid-offload safely no-ops — the same guard Android routes through WhoopBleClient.canRequestSync. Observe BLEManager (injected at the app roots beside model), NOT AppModel: AppModel @publishes bpm on the ~1 Hz HR tick, so observing it would re-render all of Today every second (the exact churn the LiveState leaves isolate). BLEManager only publishes connect/discovery state, never HR. App-target Swift (shared iOS + macOS); validated via app-build. * Android Today pull-to-sync: fix stale enabled-gate (re-review of ryanbr#426) material3 1.2.1's rememberPullToRefreshState captures the enabled lambda ONCE (rememberSaveable, no rememberUpdatedState), so { canPullToSync } froze the plain Boolean from the FIRST composition. Today usually first composes before the strap has (re)connected, so the gesture stayed disabled for the whole session and pull-to-sync (ryanbr#426) silently never worked in the common cold-launch flow. Read the stable liveSnap State live inside the enabled lambda instead, so each gesture check sees the current connected/bonded/backfilling. Safe either way (syncNow is triple-gated); this just lets the gesture enable once the strap is ready. compileFullDebugKotlin + TodayExplainabilityTest green.
…enabled-gate (ryanbr#502) * iOS/macOS Today: pull-to-sync — a pull now requests a strap offload, not just a UI reload (ryanbr#334) Android ryanbr#426 added Today pull-to-sync. iOS's LiquidTodayView already had a pull gesture (handlePull), but it only reloaded UI data (repo.refresh + load) — it never requested a fresh strap history sync. Wire ble.syncNow() into that existing refresh action so a pull kicks a manual offload too, matching Android. syncNow() is internally gated (connected + bonded + not-already-backfilling), so a pull while disconnected or mid-offload safely no-ops — the same guard Android routes through WhoopBleClient.canRequestSync. Observe BLEManager (injected at the app roots beside model), NOT AppModel: AppModel @publishes bpm on the ~1 Hz HR tick, so observing it would re-render all of Today every second (the exact churn the LiveState leaves isolate). BLEManager only publishes connect/discovery state, never HR. App-target Swift (shared iOS + macOS); validated via app-build. * Android Today pull-to-sync: fix stale enabled-gate (re-review of ryanbr#426) material3 1.2.1's rememberPullToRefreshState captures the enabled lambda ONCE (rememberSaveable, no rememberUpdatedState), so { canPullToSync } froze the plain Boolean from the FIRST composition. Today usually first composes before the strap has (re)connected, so the gesture stayed disabled for the whole session and pull-to-sync (ryanbr#426) silently never worked in the common cold-launch flow. Read the stable liveSnap State live inside the enabled lambda instead, so each gesture check sees the current connected/bonded/backfilling. Safe either way (syncNow is triple-gated); this just lets the gesture enable once the strap is ready. compileFullDebugKotlin + TodayExplainabilityTest green.
iOS twin of #426 (Android Today pull-to-sync), which just merged.
The gap
iOS
LiquidTodayViewalready had a pull gesture (handlePull), but on release it only reloaded UI data (repo.refresh() + load()) — it never requested a fresh strap history sync. So an Apple user pulling on Today got a UI refresh, not the strap offload Android's pull kicks.Fix (minimal — hook the existing gesture)
Wire
model.ble.syncNow()into the existing refresh action inhandlePull. Now a pull requests a manual strap offload and reloads, matching Android.syncNow()is internally gated — it guardsconnected && bondedand no-ops if alreadybackfilling(BLEManager:2535), the exact guard Android routes throughWhoopBleClient.canRequestSync. A pull while disconnected or mid-offload safely does nothing.@EnvironmentObject var model: AppModelto the top view — but AppModel (unlikeLiveState) doesn't publish on the ~1 Hz HR tick, so it doesn't reintroduce the per-tick Today re-render the LiveState leaves deliberately avoid.RootView,RootTabView) are underStrandApp'smodelinjection, so the environment object always resolves.Verification
app-build(below).Fixes #334 for Apple; Android side shipped in #426.