fix(chat): do not snap back to the end after a width resize

A slow edge drag settles the resize detector repeatedly, and every settle
replayed the instant return to the live edge — reading as exactly the
jumping the resize suspension removes. Stay where the reader is; geometry
changed, and the next explicit action or stream correction re-engages the
edge as usual.
This commit is contained in:
Bohdan Triapitsyn
2026-08-25 16:01:22 +03:00
parent 5c39db0e36
commit e801afec43
@@ -510,9 +510,11 @@ export const useChatTimelineScroll = ({
// While the list width is resizing, every pinning write fights the // While the list width is resizing, every pinning write fights the
// per-frame row re-measure and the pinned viewport shakes. Corrections // per-frame row re-measure and the pinned viewport shakes. Corrections
// stand down for the whole resize (visible content is held by the list's // stand down for the whole resize and the visible content is held by the
// size compensation instead), and the live edge is re-asserted once with // list's size compensation instead. Deliberately NO snap back to the end
// a single instant write after the resize settles. // afterwards: a slow drag settles repeatedly, and each snap reads as the
// very jump this suspension removes — geometry changed, staying where the
// reader is beats re-asserting the edge.
const widthResizingRef = React.useRef(false); const widthResizingRef = React.useRef(false);
React.useEffect(() => { React.useEffect(() => {
if (!scrollNode || typeof ResizeObserver === 'undefined') return; if (!scrollNode || typeof ResizeObserver === 'undefined') return;
@@ -532,10 +534,6 @@ export const useChatTimelineScroll = ({
quietTimer = setTimeout(() => { quietTimer = setTimeout(() => {
quietTimer = null; quietTimer = null;
widthResizingRef.current = false; widthResizingRef.current = false;
if (modeRef.current !== 'following-end' || !isLiveFollowActive()) return;
const node = listRef.current?.getScrollableNode();
if (!node) return;
node.scrollTop = node.scrollHeight + 4096;
}, 350); }, 350);
}); });
observer.observe(scrollNode); observer.observe(scrollNode);
@@ -543,7 +541,7 @@ export const useChatTimelineScroll = ({
observer.disconnect(); observer.disconnect();
if (quietTimer !== null) clearTimeout(quietTimer); if (quietTimer !== null) clearTimeout(quietTimer);
}; };
}, [isLiveFollowActive, scrollNode]); }, [scrollNode]);
const onTimelineDataChange = React.useCallback(() => { const onTimelineDataChange = React.useCallback(() => {
if (widthResizingRef.current) return; if (widthResizingRef.current) return;