Commit Graph
3 Commits
Author SHA1 Message Date
Bohdan Triapitsyn 6a00d6a830 fix(chat): drive anchored end-follow from the list's total size
Phase 2 of the anchored turn (following the live edge once the reply
outgrows the viewport) never ran: the reveal correction was triggered
only by entries-array changes, and the streaming tail grows inside a
single row without touching the array. The hook now also subscribes to
LegendList's totalSize and re-runs the same guarded correction on every
content growth.
2026-08-25 01:54:52 +03:00
Bohdan Triapitsyn 955b2cc812 fix(chat): anchored-scroll behavior parity with the reference model
Six reported defects, one root theme — our port diverged from the
reference semantics:

- a user gesture no longer collapses the reserved anchored end space
  (that snap-to-bottom was the 'anchoring vanishes on a micro-scroll');
  it only disarms the follow/anchor machinery, and anchor remeasures
  after the user takes over can no longer re-position the viewport
- the anchor arms synchronously BEFORE the send reaches the store, and
  the claim compares against the arm-time baseline — arming a frame
  after the optimistic row committed is why anchoring 'rarely worked'
  and sending from mid-history did nothing
- gestures are selective (wheel up, touch/pointer away from the end,
  PageUp/Home/ArrowUp), so scrolling toward the end or clicking a row
  at the live edge no longer kills follow
- 'at end' is a tight 40px band against the full content length instead
  of the list's half-viewport isNearEnd, so the scroll-to-bottom pill
  appears when the viewport actually leaves the end
- reaching the end re-arms follow without knocking an anchored turn out
  of its mode, and the overlay scrollbar suppression follows scroll
  ownership rather than the anchor's mere existence
- the status/working row moved out of the list footer to a fixed spot
  above the composer: inside the list it walked down with every
  streamed line, and its unmount was part of the end-of-stream jerk
2026-08-25 01:35:45 +03:00
Bohdan Triapitsyn d2d8669564 refactor(chat): replace timeline scroll engine with anchored-turn LegendList
Sending a message now parks that message near the top of the viewport
and streams the reply into reserved end space below it, instead of
jumping to the bottom and chasing it.

- swap @tanstack/react-virtual for @legendapp/list in the chat timeline; the
  streaming tail becomes a normal list row rather than a separately rendered
  block, so one component owns the scroll position
- add timelineScrollAnchoring: pure anchored-turn geometry plus the three
  scroll modes (following-end / anchoring-new-turn / free-scrolling)
- replace useChatAutoFollow with useChatTimelineScroll, which opts out of
  automatic movement on real gestures via a generation counter instead of the
  timer windows the old implementation needed to recognise its own writes
- move the load-older button, question/permission cards, recap, status row and
  bottom spacer into the list header/footer, since the list owns its container
- extract useScrollShadow so the shadows can attach to that container

maintainScrollAtEnd and maintainVisibleContentPosition replace the manual
prepend anchor-hold and the mobile quiet-window prepend deferral.

Validated: workspace type-check, lint, web build, ui tests per file.
Scroll behaviour itself is unverified and needs manual testing on web, desktop
and iOS.
2026-08-25 01:10:00 +03:00