fix(chat): rebuild auto-follow on a single instant writer to kill scroll twitch
Replace the RAF easing follow loop + settle burst with an always-on instant-follow model: while pinned, the content ResizeObserver re-pins to the bottom synchronously (scrollTop = scrollHeight, before paint) and is the only writer of scrollTop. A position+TTL auto marker distinguishes our own programmatic writes from genuine user scrolling, so a scroll event that lands at the just-written bottom never trips a false release. This removes the feedback loop where the easing animation, growing content, and the user's own scroll all fought for scrollTop in the same frame -- the infinite twitch when scrolling down during streaming, and the jiggle on send / from the queue. The public hook interface is unchanged; all consumers keep working untouched.
This commit is contained in:
@@ -382,12 +382,13 @@ export const useChatTimelineController = ({
|
||||
// through goToBottom — the single programmatic writer.
|
||||
//
|
||||
// A normal bottom APPEND (a sent message, a streaming part) must NOT
|
||||
// re-pin here. Auto-follow's own follow loop — kicked by the content
|
||||
// ResizeObserver and the streaming chunk handlers — already eases to the
|
||||
// new bottom. Calling goToBottom on every append layered its settle burst
|
||||
// on top of that loop: two writers aiming at different positions, which
|
||||
// is exactly the up/down jiggle reported on send / from the queue / while
|
||||
// streaming. So for an append we do nothing and let the follow loop own it.
|
||||
// re-pin here. Auto-follow already owns the bottom: its content
|
||||
// ResizeObserver re-pins instantly (scrollTop = scrollHeight, before
|
||||
// paint) on every append. Re-pinning again from here would just be a
|
||||
// second writer chasing the same target a frame later — redundant at
|
||||
// best, and the source of the old up/down jiggle on send / from the
|
||||
// queue / while streaming. So for an append we do nothing and let
|
||||
// auto-follow own it.
|
||||
if (snap || isPrepend) {
|
||||
prePrependScrollRef.current = null;
|
||||
goToBottom('instant');
|
||||
|
||||
Reference in New Issue
Block a user