improvement(chat): smooth streaming — eased stick-to-bottom glide and time-based reveal pacing#5417
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryLow Risk Overview
New util: Reviewed by Cursor Bugbot for commit d43a39c. Bugbot is set up for automated code reviews on this repo. Configure here. |
Greptile SummaryThis PR replaces two "snap" behaviors in the chat view with smooth, time-based alternatives. Auto-scroll now uses an exponential glide (closing 12% of the remaining gap per frame) shared via a new
Confidence Score: 4/5Safe to merge — the scroll and text-reveal changes are self-contained UI improvements with no data-path or state-store impact. The easing and budget logic are correct, detach/re-attach semantics are preserved, and the new
Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Stream as Streaming Content
participant MO as MutationObserver
participant Chase as createSmoothBottomChase
participant rAF as requestAnimationFrame
participant El as scroll container
Stream->>MO: DOM mutation (new text)
MO->>Chase: kick()
Chase->>rAF: schedule step()
loop "Each frame (gap > CHASE_REST_GAP)"
rAF->>Chase: step()
Chase->>El: getTop()
Chase->>El: setTop(top + max(1, gap x 0.12))
Chase->>El: "getTop() -> lastTop"
Chase->>rAF: schedule next step()
end
Note over Chase,El: Parks when gap <= 0.5px
Stream->>MO: DOM mutation (word arrives)
MO->>Chase: kick() no-op raf not null
Note over Chase: User scrolls UP
rAF->>Chase: "step() top < lastTop-1"
Chase->>Chase: park() stops fighting user
Note over Stream: isStreaming false
Stream->>Chase: cancel()
Stream->>El: followToBottom(300ms) via rAF
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant Stream as Streaming Content
participant MO as MutationObserver
participant Chase as createSmoothBottomChase
participant rAF as requestAnimationFrame
participant El as scroll container
Stream->>MO: DOM mutation (new text)
MO->>Chase: kick()
Chase->>rAF: schedule step()
loop "Each frame (gap > CHASE_REST_GAP)"
rAF->>Chase: step()
Chase->>El: getTop()
Chase->>El: setTop(top + max(1, gap x 0.12))
Chase->>El: "getTop() -> lastTop"
Chase->>rAF: schedule next step()
end
Note over Chase,El: Parks when gap <= 0.5px
Stream->>MO: DOM mutation (word arrives)
MO->>Chase: kick() no-op raf not null
Note over Chase: User scrolls UP
rAF->>Chase: "step() top < lastTop-1"
Chase->>Chase: park() stops fighting user
Note over Stream: isStreaming false
Stream->>Chase: cancel()
Stream->>El: followToBottom(300ms) via rAF
|
Summary
smooth-bottom-chaseutil). Detach/re-attach semantics unchanged — chase writes only move down, so they can never read as a user scroll; verified 0px drift after a wheel-up detach mid-stream.smoothScrollinginstead).Type of Change
Testing
Tested manually in the live app: glide follows tightly during streaming (gap decays smoothly, parks at 0), wheel-up detach shows zero fight-back over 1.5s of continued streaming, re-attach at bottom works, and reveal cadence measured word-at-a-time.
vitestsmooth-text + message-content suites pass,bun run lint,bun run check:api-validation:strictpass.Checklist