fix(#1249): break sticky-bottom lock when user scrolls up during snap animation
This commit is contained in:
parent
0723737d89
commit
9f8694e3cf
2 changed files with 31 additions and 6 deletions
|
|
@ -121,7 +121,15 @@ the smooth animation eases through positions that are technically
|
||||||
`smoothScrollingUntil` timestamp gates the scroll handler — set to
|
`smoothScrollingUntil` timestamp gates the scroll handler — set to
|
||||||
the animation end + ~80ms headroom, re-armed on each fresh snap.
|
the animation end + ~80ms headroom, re-armed on each fresh snap.
|
||||||
Programmatic `scrollTop` writes (the animation's per-frame update)
|
Programmatic `scrollTop` writes (the animation's per-frame update)
|
||||||
fire scroll events that the gate swallows.
|
fire scroll events that the gate swallows. One caveat: if live events
|
||||||
|
arrive faster than the animation window (< 220ms apart), the
|
||||||
|
MutationObserver keeps re-firing `snapToBottom()` which perpetually
|
||||||
|
re-arms the gate — trapping the operator at the bottom with no way
|
||||||
|
to scroll up or trigger `loadMore()`. Safety valve: even inside the
|
||||||
|
guard the handler checks `isNearBottom()`; if the operator has
|
||||||
|
scrolled away from the bottom, `stickToBottom` is immediately forced
|
||||||
|
false. The MO's `if (stickToBottom)` check then stops re-arming
|
||||||
|
snaps, and the gate expires within ≤220ms.
|
||||||
|
|
||||||
**Post-append `MutationObserver`.** Renderers commonly call
|
**Post-append `MutationObserver`.** Renderers commonly call
|
||||||
`api.row(cls, text)` to create the row shell then append more
|
`api.row(cls, text)` to create the row shell then append more
|
||||||
|
|
|
||||||
|
|
@ -158,12 +158,29 @@ export function create(opts) {
|
||||||
pill.classList.add('visible');
|
pill.classList.add('visible');
|
||||||
}
|
}
|
||||||
log.addEventListener('scroll', () => {
|
log.addEventListener('scroll', () => {
|
||||||
// Swallow scroll events during smooth-snap animations — see
|
// Sticky-bottom intent tracking. Outside an animation this is
|
||||||
// docs/web-ui.md::Shared terminal pane (Mid-animation scroll-event
|
// straightforward — stickToBottom = isNearBottom(). During a
|
||||||
// guard).
|
// smooth-snap animation we swallow most of the event to avoid a
|
||||||
if (Date.now() < smoothScrollingUntil) return;
|
// feedback loop (programmatic scrollTop changes → scroll events →
|
||||||
stickToBottom = isNearBottom();
|
// new snap → cancels current rAF), but we MUST still let the user
|
||||||
|
// break out of sticky mode: if the user scrolls away from the
|
||||||
|
// bottom while a snap animation is in flight, honour that intent
|
||||||
|
// immediately so the MutationObserver stops re-firing snapToBottom()
|
||||||
|
// and the animation-guard window can expire naturally. Without this,
|
||||||
|
// live events arriving < 220ms apart permanently block scroll-to-top
|
||||||
|
// and loadMore() never fires. See docs/web-ui.md::Shared terminal
|
||||||
|
// pane (Mid-animation scroll-event guard).
|
||||||
|
const inAnim = Date.now() < smoothScrollingUntil;
|
||||||
|
const nearBottom = isNearBottom();
|
||||||
|
if (!inAnim) {
|
||||||
|
stickToBottom = nearBottom;
|
||||||
|
} else if (!nearBottom) {
|
||||||
|
// User scrolled up during an animation — break sticky mode so the
|
||||||
|
// MO stops calling snapToBottom() and the gate expires.
|
||||||
|
stickToBottom = false;
|
||||||
|
}
|
||||||
if (stickToBottom) { unseen = 0; updatePill(); }
|
if (stickToBottom) { unseen = 0; updatePill(); }
|
||||||
|
if (inAnim) return;
|
||||||
// Auto-fetch older history when the user scrolls near the top — no
|
// Auto-fetch older history when the user scrolls near the top — no
|
||||||
// click required; the load-more pill stays as a visual indicator.
|
// click required; the load-more pill stays as a visual indicator.
|
||||||
if (rootLog.scrollTop <= LOAD_MORE_SCROLL_PX && histHasMore && !histLoading) {
|
if (rootLog.scrollTop <= LOAD_MORE_SCROLL_PX && histHasMore && !histLoading) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue