Keep Android chat scrollable while agents stream#2338
Conversation
Continuous Android stream updates kept bottom-anchor verification pending, so genuine finger drags were mistaken for programmatic corrections. Preserve explicit drag ownership so user scrolling can detach immediately.
|
| Filename | Overview |
|---|---|
| packages/app/src/agent-stream/bottom-anchor-controller.ts | Adds isUserScrollActive flag, beginUserScroll/endUserScroll methods, and early-exit guards in the four handlers that drive programmatic scroll maintenance — the logic is correct, with endUserScroll cleanly branching between verify-in-place and detach based on final scroll position. |
| packages/app/src/agent-stream/strategy-native.tsx | Wires four new FlatList scroll-lifecycle events (drag begin/end, momentum begin/end) to track user intent; the one-frame defer on drag-end correctly bridges the gap before momentum begins, and the reset paths on agent change are properly handled. |
| packages/app/src/agent-stream/bottom-anchor-controller.test.ts | Adds one integration test covering the main path (prepareForStickyContentChange → beginUserScroll → content/near-bottom change → detach); the hasPendingRequest: true, isUserScroll: true short-circuit path is not covered by any test. |
Sequence Diagram
sequenceDiagram
participant U as User (finger)
participant FL as FlatList events
participant SN as strategy-native
participant BAC as BottomAnchorController
Note over FL,BAC: Drag-then-fling gesture
U->>FL: onScrollBeginDrag
FL->>SN: handleScrollBeginDrag
SN->>BAC: beginUserScroll() — cancels pending request/verification
loop scroll events during drag
FL->>SN: onScroll
SN->>BAC: "handleScrollNearBottomChange (isUserScrollActive=true)"
BAC->>BAC: detachByUser() if not near bottom
end
U->>FL: onScrollEndDrag
FL->>SN: handleScrollEndDrag
SN->>SN: schedule rAF for endUserScroll
alt fling — momentum follows
FL->>SN: onMomentumScrollBegin
SN->>SN: clearPendingUserScrollEnd() — cancels rAF
FL->>SN: onMomentumScrollEnd
SN->>BAC: endUserScroll()
BAC->>BAC: markVerified if nearBottom, else detachByUser
else simple drag — no momentum
Note over SN: rAF fires one frame later
SN->>BAC: endUserScroll()
BAC->>BAC: markVerified if nearBottom, else detachByUser
end
Note over FL,BAC: Programmatic scroll (agent streaming, no user touch)
BAC->>SN: "scrollToBottom(animated=false) — sets budget=3"
FL->>SN: "onScroll (budget>0, y<=8, isUserScrollActive=false)"
SN->>SN: decrement budget — handleScrollNearBottomChange NOT called
Reviews (2): Last reviewed commit: "fix(app): preserve scroll ownership thro..." | Re-trigger Greptile
Upstream PR: getpaseo#2338 Resolved strategy-native.tsx by keeping both clearScrollToIndexRetry (local) and clearPendingUserScrollEnd (PR) helpers.
Linked issue
No matching open issue.
Type of change
What does this PR do
Android users can now drag or fling away from the bottom of a chat while an agent is streaming without new tokens taking the viewport back. The bottom-anchor controller pauses sticky maintenance for the full user gesture, including momentum, and genuine drag events bypass the small event budget reserved for programmatic offset corrections.
How did you verify it
sh.paseo.debug) on the emulator.Scroll to bottomremained visible in both cases.npx vitest run packages/app/src/agent-stream/bottom-anchor-controller.test.ts --bail=1(19 tests)npm run typechecknpm run lintnpm run formatRisk surface: native chat scrolling on Android and iOS. The behavioral smoke covered Android; the shared native event wiring was not manually exercised on iOS.
Checklist
npm run typecheckpassesnpm run lintpassesnpm run formatran (Biome)