Skip to content

feat: floating scroll toolbar with half-viewport follow-streaming - #372

Open
windli2018 wants to merge 14 commits into
agegr:mainfrom
windli2018:feat/scroll-toolbar
Open

feat: floating scroll toolbar with half-viewport follow-streaming#372
windli2018 wants to merge 14 commits into
agegr:mainfrom
windli2018:feat/scroll-toolbar

Conversation

@windli2018

Copy link
Copy Markdown

Summary

A floating 4-button scroll toolbar (earliest / prev user / next user / latest) over the chat column, auto-hiding 2s after scrolling stops.

Features

  • Navigation: prev/next jump between user messages (lazy-pagination fallback with auto-retry); earliest/latest jump to the ends
  • Follow-streaming: long-press the 'latest' button toggles auto-follow (long-press again or click to unlock). Follow steps half a viewport when new content pushes the last message past a 40px keep-out — the growing output refills the lower half before the next step, so output is always visible and jumps are gentle (no full-screen jumps per message)
  • Pause/resume: manual scroll pauses follow for 6s, then it resumes
  • Smart checks: re-checks on message count changes, streaming chunks (message grows without changing count), and scroll-container resize (queue banner / input area changes)
  • Draggable: long-press + move to reposition; snaps left/right, vertical free; position persisted in localStorage
  • Mobile: no hover-dependent visibility (touch fires mouseenter but never mouseleave), tap-safe pointer handling with capture-free drag via window-level listeners
  • Fixes: agent-running spacer reduced from a full viewport to 96px (no blank screen under the last message); scrollToBottom backs off the spacer so the last message, not the blank spacer, lands at the viewport bottom (~40px keep-out)

A 4-button floating toolbar (earliest / prev user / next user / latest)
over the chat column, auto-hiding 2s after scrolling stops:
- prev/next jump between user messages with lazy-pagination fallback
- long-press 'latest' toggles follow-streaming (second long-press or click
  unlocks); follow steps half a viewport when new content pushes the last
  message past a 40px keep-out, so output stays visible and jumps are gentle
- manual scroll pauses follow for 6s, then it resumes
- drag to reposition (snaps left/right, vertical free, persisted)
- touch: no hover-dependent visibility, tap-safe pointer handling
- agent-running spacer reduced to 96px (was a full viewport of blank space)
- scrollToBottom backs off the spacer so the last message, not the blank
  spacer, sits at the viewport bottom
After the toolbar's prev/next navigation scrolls to a user message, dispatch
a synthetic mouseover on the message container so its action menu (copy /
edit-from-here / new-session) shows — same state a real mouse-over produces.
Dispatch targets the wrapper's first child: messageRefs point at the wrapper
div around the message, and React's onMouseEnter lives on the container
inside it, so the mouseover must originate there to bubble through it.
More room below for the output to grow — fewer steps, longer uninterrupted
streaming before the next jump. (Was 55%.)
Long-press on 'latest' fires a toast on every enable — noise after the user
knows the gesture. Track enable count in localStorage (pi-follow-toast-shown)
and only show the toast for the first 3 enables.
The 'long-press to follow' tooltip and the 'following on' toast are only
useful while the user is learning the gesture. Count enables in a plain ref
(resets on refresh — no localStorage); after 3 enables in one session the
tooltip stops showing entirely and the toast stops appearing.
Code review found:
- smartFollowCheck's step scrolls 2/3 of the viewport smooth; its animation
  can outlast the 700ms programmatic-scroll grace window, so the animation
  tail was treated as a manual scroll and follow paused for 6s mid-stream.
  Bump the grace window to 1200ms for the step.
- stale comments (100px keep-out / 55% landing / clientHeight spacer) now
  match the 40px keep-out, 1/3 landing, 96px spacer.
- stray blank lines in the tap-fallback handlers.
A streaming chunk re-runs the follow check every few hundred ms; a smooth
step animation was cancelled mid-flight by the next chunk's scrollTo, so the
step never completed and the view only crept up a little. Instant scrolling
lands the full step every time — the last message sits at the top 1/4 of the
viewport with 3/4 of blank room below for the output to grow.
The follow step targets the last message at the top 1/4 of the viewport, but
the agent-running spacer was only 96px, so there was not enough content below
the last message to scroll there — the step clamped at the bottom and looked
like a tiny nudge. Restore the spacer to 3/4 of the viewport height so the
step lands (last message at top 1/4, 3/4 blank below for the output to grow)
and sync spacerH in scrollToBottom / scrollToLatest / smartFollowCheck.
@windli2018
windli2018 force-pushed the feat/scroll-toolbar branch from d93832e to d25e222 Compare August 3, 2026 16:07
followHintCountRef was only incremented on long-press enables, so hovering
the button popped the 'long-press to follow' tooltip on every scroll-over.
Count hovers too — after 3 shows this session the tooltip stops entirely.
Until the user long-presses once, the tooltip always shows (still learning
the gesture). After that it never shows again — the tooltip popping on every
toolbar appearance obstructs the view. Drop the show-cycle counter, keep the
follow-enabled flag.
… reset

The 'long-press to follow' tooltip shows for the first 3 toolbar show→hide
cycles of the page session; the 'following on' toast shows at most 3 times.
Nothing resets them mid-session — only a page refresh restarts the counters.
…ther

Switching between user messages now hides the previously revealed menu
(mouseout) before showing the new target's (mouseover), so only one action
menu is ever open after a jump.
All four scroll-toolbar tooltips now show only during the first 3 toolbar
show→hide cycles of the page session, so they teach the buttons up front and
stop obstructing the view later (refresh restarts the counters).
Touch taps leave a sticky hover with no pointer-leave, so a tapped button's
tooltip stayed pinned to the toolbar forever. Tooltips now dismiss
automatically ~1.5s after showing (mouse leave still dismisses instantly),
and the 3-shows-per-page-session cap is unchanged. Drop the pointer:coarse
gate so touch still gets the hint.
@windli2018
windli2018 force-pushed the feat/scroll-toolbar branch from dbd89f5 to f289822 Compare August 3, 2026 16:58
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