hataa ec44f2ff3d
feat(frontend): pin the sent message to the top of the viewport (#8261)
* feat(frontend): pin the sent message to the top of the viewport

After a send, keep the user's turn at the top of the viewport and let the
streamed reply grow downward below the reading position, instead of the
viewport chasing the bottom (ChatGPT-style send behavior).

MessageList detects a freshly appended trailing human group while a run is
active and hands its key to VirtualMessageList. The list then grows a
flexible end spacer sized to the remaining viewport, so the last row can
actually reach the top (the browser clamps the scroll without it), escapes
the stick-to-bottom lock around the alignment, and re-escapes on a timeout
chain until the resize animation from the optimistic message settles; the
spacer shrinks as the reply grows and collapses when the run finishes.
Scrolling down or the scroll-to-bottom button re-locks the follow. History
pagination, thread switches and regenerates are excluded by construction
and covered by DOM tests; Playwright covers the static and the virtualized
list against a held run stream.

* style(frontend): fix scroll-on-send test lint

* fix(frontend): send-pin review follow-ups

Review follow-ups on top of the send pin:

- Hand scroll control back on the first genuine user input: the pin chain
  arms wheel/touchstart/pointerdown listeners on the scroller, and outline
  navigation plus the scroll-to-bottom button cancel it through
  cancelPinFollow — previously every tick reverted user scrolling for ~2s
  after a send.
- Capture the mount-time tail key during the first render so the mount
  absorption is idempotent under StrictMode's double effect run.
- Export LOCAL_HUMAN_MESSAGE_ID_PREFIX from core/threads/hooks and use it
  at both the mint site and the pin predicate.

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-11-15 20:49:43 +08:00
..