Skip to content

Scroll the page under a drag that follows a flick - #933

Merged
Tryanks merged 1 commit into
mainfrom
topic/scroll-bounce-mid-content
Oct 22, 2026
Merged

Tryanks merged 1 commit into
mainfrom
topic/scroll-bounce-mid-content

Conversation

@Tryanks

@Tryanks Tryanks commented Oct 22, 2026

Copy link
Copy Markdown
Owner

In a long conversation, a drag sometimes stops scrolling. The whole viewport slides like a picture instead, its edge clipped and the background showing, and no new rows come into view. It happens most when the drag starts right after a flick, or when you swipe quickly up and down. Reported on a phone and reproduced on a macOS trackpad.

The cause is in gpui-base 0.8's own wheel handling (ScrollBounce with scroll_wheel(true), the default since #922). It stretches the edge away from both ends in two ways:

  • Overscroll::packet gives a release the finger's speed even when nothing is stretched, and Physics::let_go throws the edge from position 0. For about half a second after a flick, the content sits up to 36 px off where it scrolled (with no limit on touch). A gesture that starts in that window grabs the stretch and stretches it further, so the list doesn't move.
  • The amount the content used is computed as an f32 difference, (before + dy) - before. At an offset around 8,800 px this can come out a few ten-thousandths of a pixel below dy, and unused != 0. reads that as reaching the edge. Every later packet in the same direction then goes into that stretch.

Logged in the running app (offset −8082.5 of 8902.5, so far from either end): after a flick, a new gesture's +11, +34, +23, +39 px packets left the list offset at −8082.5 while viewport_top drifted from 70 to 80.

page_viewport now passes scroll_wheel(false). The content then scrolls itself, and ScrollBounce stretches only by what's left over when the list is exactly at an end (at_outward_edge && residual.abs() > 0.01). A release lets go only of a stretch that exists. Edge bounce stays. This override is the one page_viewport owns for every page-level area: timeline, sidebar, settings, palette, diff list and panels. Per principle 6, a comment at the site names the gpui-base gap, and the override goes once gpui-base closes it.

Tests:

  • a_drag_right_after_a_flick_scrolls_the_timeline (new) flicks six 70 px trackpad packets through the middle of the timeline, one per displayed frame. It then puts the fingers back down and drags 200 px. Without the change the drag scrolls 0 px (left: -420px) and the viewport sits 25 px off. With the change it scrolls 200 px and nothing is displaced. Existing coverage didn't reach this: the edge-bounce test covers only the ends, and the paging test settled frames before every gesture.
  • pages_read_far_up_are_released_once_the_reader_stays_at_the_tail gets back its pre-Upgrade to gpui-base 0.8.0 and adopt its scroll, resizable, input and webview defaults #922 driver, which sends one trackpad gesture with no settle_frames() before it. Upgrade to gpui-base 0.8.0 and adopt its scroll, resizable, input and webview defaults #922 added that settling because the test "stalled by starting new gestures against a displaced edge". That stall was this bug: without the change, the restored driver hangs again. With it, the test passes, three runs out of three. A real reader doesn't wait for the edge to settle before the next gesture.

Checks run:

  • cargo fmt --all --check and git diff --check.
  • cargo clippy --workspace --all-targets --locked -- -D warnings.
  • cargo nextest run --workspace --locked.
  • cargo check -p tcode-ios --target aarch64-apple-ios-sim --locked and cargo check -p tcode-web --target wasm32-unknown-unknown --locked, both with RUSTFLAGS='-D warnings'.
  • Manual: on a macOS trackpad, quick swipes up and down in a long conversation no longer slide the viewport. The app log for that run has 736 trackpad events. Every displaced frame starts at an end of the list: the last frame or two of a real edge bounce springing back.

Not run: the Android check (no NDK on this machine; CI runs it), and a device test on a phone. The sidebar, settings and other page areas get the same change, but I only felt it by hand in the timeline.

  • This change alters the wire protocol, so the next release needs a
    PROTOCOL_VERSION bump: a note was added under "Unreleased" above the
    constant in crates/protocol/src/lib.rs (the number itself changes only
    when the release is cut — CONTRIBUTING.md, principle 9).

gpui-base 0.8's own wheel handling stretches the edge away from both
ends: a release while moving throws it, and f32 rounding of the delta
the content used reads as reaching it. A drag in that state moved the
stretch instead of the list, sliding the viewport with its edge clipped.

Page viewports now let the content scroll itself, so ScrollBounce only
stretches by what is left over at an end. The paging test gets back its
one-gesture driver: the frames it settled before each gesture hid this.
@Tryanks
Tryanks merged commit 54b64d6 into main Oct 22, 2026
7 checks passed
@Tryanks
Tryanks deleted the topic/scroll-bounce-mid-content branch October 22, 2026 07:29
Tryanks added a commit that referenced this pull request Oct 29, 2026
…ds it closes removed (#1011)

* Dependencies: gpui-base, gpui-kit and gpui-webview 0.8.1, on gpui-fast 0.1.12

gpui-base 0.8.1 pins gpui-pre 0.3.10 and gpui-fast 0.1.12, so the
reqwest-client snapshot follows to 0.3.10 and every crate's gpui-fast
minimum states 0.1.12.

GPUI renamed `ScrollWheelEvent` to `ScrollEvent` and added the input's
source; the mobile two-finger pan is a touch scroll, and so are the finger
pans the selection tests simulate.

`KeyBinding::new` no longer reads `secondary-`: it panics at startup.
`shortcut::secondary` loads those bindings for the host OS, which GPUI now
reports at run time, so a browser on a Mac binds Command as a native Mac
build does. `Kbd::format` reads the same OS, so a hint names the key its
binding takes.

* Scroll, Sidebar, Chat: drop the workarounds gpui-base 0.8.1 and gpui-fast closed

Page viewports go back to gpui-base's own wheel bounce. #933 turned it off
because a release while moving threw the edge and f32 rounding of the delta
the content used read as reaching it; gpui-base 0.8.1 throws the edge only
when it is stretched and ignores that rounding. The test #933 added, a drag
right after a flick, holds with the default.

gpui-fast fixed a reparented retained child left unmeasured (#68) in 0.1.9,
and since 0.1.11 measures text that reaches prepaint unmeasured instead of
panicking. The sidebar's empty provider-mark slot and the chat row's
content-keyed id existed only for #68: the mark is absent while marks are
off, and rows carry no id; the images inside them are keyed by their
message. The provider-mark toggle test (#452) and the Work Log collapse
test (#898) still pass.

* Markdown: a drag or shift-click from a double or triple click extends by its unit

gpui-base 0.8.1 extends a double- or triple-click drag, and a shift-click
after it, by words or lines, and keeps the clicked unit selected whichever
way they go (#3681). A transcript owns its selection, so it now holds the
unit the same way: until the button is released a drag moves the far end to
the edge of the word or line under the pointer, and a shift-click does so
until a plain click starts afresh. A long press, a touch handle and Select
Entry make their own selection and hold no unit.

* Mobile backends: report their OS to GPUI, and corners GPUI cannot see

gpui-fast 0.1.12 asks every platform for the host OS, which decides what
`secondary-` binds and how a keystroke is written: the iOS backend reports
iOS and the Android one Android, both native, so `secondary` stays Control
there as before. Window insets gained the display's corner radii; neither
host reports them, so they are zero, which GPUI reads as square or unknown.
The palette shortcut no longer imports `KeyBinding` for the macOS-only Quit.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant