Goal
In the session detail transcript, auto-scroll to the latest message ONLY while the view is pinned to the bottom. When the user scrolls up to read earlier messages, streaming new events must NOT yank the view back down. When the user scrolls back to the bottom (within a small threshold), auto-scroll resumes. While unpinned, a floating "↓ 最新消息" button is shown; clicking it jumps to the bottom and re-pins.
After this:
- A pure helper
atBottom(scrollHeight, scrollTop, clientHeight, threshold) decides the pinned state from scroll metrics.
- The auto-scroll effect in
session-detail.tsx is gated by a stick ref (no scroll-driven re-render).
- Switching sessions resets to pinned, so a newly opened session lands on the latest message.
- A pinned-state React state flips only when crossing the threshold (not every scroll tick), driving the jump button's visibility.
Verification
test -f packages/web/src/features/sessions/session-detail/stick-to-bottom.ts
pnpm --filter @baton/web exec vitest run src/features/sessions/session-detail/stick-to-bottom.test.ts
Refs
- packages/web/src/features/sessions/session-detail.tsx — current unconditional auto-scroll (el.scrollTop = el.scrollHeight on items.length, ~L52-59)
- packages/web/src/features/sessions/session-detail/event-stream.tsx — the overflow-auto scroll container that needs onScroll
Goal
In the session detail transcript, auto-scroll to the latest message ONLY while the view is pinned to the bottom. When the user scrolls up to read earlier messages, streaming new events must NOT yank the view back down. When the user scrolls back to the bottom (within a small threshold), auto-scroll resumes. While unpinned, a floating "↓ 最新消息" button is shown; clicking it jumps to the bottom and re-pins.
After this:
atBottom(scrollHeight, scrollTop, clientHeight, threshold)decides the pinned state from scroll metrics.session-detail.tsxis gated by astickref (no scroll-driven re-render).Verification
Refs