Two scroll owners were writing the chat container's scrollTop concurrently
during pinned content growth and on send, fighting frame-to-frame and
producing the reported flicker/jiggle (after a pause, from the queue, on user
interruptions) plus a visible double scroll on a normal user send.
Enforce a single-writer invariant in useChatAutoFollow:
- The easing follow loop and the instant settle burst now mutually exclude:
starting one stops the other, so they can never write scrollTop in the same
frame. The isFollowingProgrammatically flag (which suppresses the overlay
scrollbar) is owned by whichever loop is active and cleared only when both
are idle, including the settle burst's natural 280ms end.
Stop the redundant re-pin storm in useChatTimelineController:
- While pinned, route goToBottom('instant') only for a prepend (history loaded
above), not on every bottom append / streaming part. Normal growth is owned
by the follow loop (kicked by the content ResizeObserver and chunk handlers).
Remove the double movement on send:
- Add scrollToBottomOnSend: when already following, just (re)kick the follow
loop for a single smooth movement instead of also firing an instant
goToBottom that raced the ResizeObserver-driven loop. When released (scrolled
up), keep the instant jump to the just-sent message.