Skip to content

0.4.0

Latest

Choose a tag to compare

@adamtheturtle adamtheturtle released this 18 Jul 17:51
9c9e2f1

Gesture state-machine fixes. Each carries a regression test verified to fail without it.

Fixed

  • A cancelled pull no longer fires a refresh (#2). gapOpen latched the moment the pull crossed the threshold and was never cleared mid-drag, so pulling past the threshold, dragging back to the top, and releasing still triggered a full refresh — contradicting the documented "release beyond a threshold" contract, and diverging from the iOS gesture, which cancels. The release decision now reads the actual over-scroll.
  • The baseline inset can no longer be corrupted by a scroll during the gap close (#3). closeGap cleared gapOpen immediately but left the enlarged inset in place for the full 300ms animation, so a scroll starting in that window captured the gap inset as the new baseline. The threshold then doubled (44pt → 88pt), the spoke reveal halved, and repeating the race compounded it. The baseline is now captured into the completion handler, and re-capture is suppressed while a close is in flight.
  • The spinner no longer blinks out at the hand-off (#4). Zeroing the pull before the refresh state round-tripped through SwiftUI dropped the indicator to zero opacity for a frame — the exact "waiting a beat" the design set out to avoid. The pull is now held at full reveal until the refresh state lands, with a fallback so a hand-off that never arrives can't strand the indicator.

Upgrading

The cancel fix changes gesture behaviour: releasing at zero over-scroll after crossing the threshold now cancels instead of refreshing. While the gap is open, the arming bar sits at refreshGap rather than zero, so an ordinary release still reads as a refresh rather than a cancel.

Full changelog: 0.3.1...0.4.0