Skip to content

feat: scroll to latest button - #845

Open
grubwithu wants to merge 2 commits into
agegr:mainfrom
grubwithu:feat/scroll-to-latest-button
Open

grubwithu wants to merge 2 commits into
agegr:mainfrom
grubwithu:feat/scroll-to-latest-button

Conversation

@grubwithu

Copy link
Copy Markdown

Add a jump-to-latest button to the chat

Problem: The right-side minimap can navigate around a session, but once you scroll up there is no quick way back to the live tail — you have to drag the minimap or scroll manually.

Change: A circular down-arrow button floats just above the composer whenever the chat viewport is detached from the bottom, and hides again once you are at the tail. Clicking it smooth-scrolls to the latest message.

图片

Details

  • Visibility is a pure predicate, shouldShowScrollToLatest(scrollTop, clientHeight, scrollHeight) in lib/chat-lazy-load.ts. It is intentionally separate from the existing live-follow attachment heuristic, which has its own reattach tolerance semantics — reusing it would have made the button appear and disappear at slightly the wrong moments.
  • Placement is centered on the chat column and stops short of the minimap (right: CHAT_MINIMAP_WIDTH), so it never covers it.
  • Transition is deliberately understated: a 160ms fade with a 4px rise and a slight scale. The button stays mounted and toggles an is-visible class rather than being conditionally rendered, so the exit animates too. A delayed visibility switch removes it from the tab order once faded. Under prefers-reduced-motion: reduce, the fade is kept and the transform is dropped.

Testing

  • New unit coverage for the visibility predicate (lib/chat-lazy-load.test.mjs) and for the component wiring/CSS (components/ChatWindow.scroll-to-latest.test.mjs).
  • Verified in a real browser against a 400-message session: hidden at the tail, appears on scroll-up without overlapping the composer or minimap, and returns to the bottom (then hides) on click. The transition was checked mid-flight to confirm the opacity is genuinely interpolating rather than snapping.
  • tsc --noEmit, eslint ., and the full unit suite (1026 tests) all pass.

No behavior change when the chat is already at the bottom, for empty new sessions, or on mobile.

The minimap can navigate up and down a session, but there was no one-click
way back to the live tail once the viewport was detached. Add a circular
down-arrow button that floats just above the composer whenever the chat is
scrolled away from the bottom, and hides again at the tail.

Visibility is a pure `shouldShowScrollToLatest()` predicate over
scrollTop/clientHeight/scrollHeight so it stays independent of the
live-follow attachment heuristic, which has its own reattach semantics.
Keep the button mounted and toggle an `is-visible` class instead of
mount/unmounting it, so the exit can animate too: a 160ms fade with a
4px rise and slight scale, plus a delayed `visibility` switch that takes
it out of the tab order once faded. Reduced-motion keeps the fade but
drops the transform.
@grubwithu grubwithu changed the title Feat/scroll to latest button feat: scroll to latest button Sep 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant