Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/components/MessageList/MessageList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ const MessageListWithContext = (props: MessageListWithContextProps) => {
[messages],
);
const isJumpingToLatest = jumpToLatestPhase !== 'idle';
const isHighlightedJumpRequested = !!highlightedMessageId;
// Highlighted jumps temporarily disable prepend pagination so a target
// message rendered near the top does not immediately load the previous page.
const isJumpingToHighlightedMessage = highlightedJumpPhase !== 'idle';
Expand All @@ -152,7 +153,8 @@ const MessageListWithContext = (props: MessageListWithContextProps) => {
scrollToBottom,
wrapperRect,
} = useScrollLocationLogic({
disableAutoScrollToBottom: isJumpingToLatest || justReachedLatestMergedSet,
disableAutoScrollToBottom:
isJumpingToLatest || isHighlightedJumpRequested || justReachedLatestMergedSet,
disableScrollManagement: isJumpingToLatest || isJumpingToHighlightedMessage,
hasMoreNewer,
listElement,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ export const useScrollLocationLogic = (params: UseScrollLocationLogicParams) =>
const closeToTop = useRef(false);
const previousScrollTopRef = useRef(0);
const previousMessagesLengthRef = useRef(messages.length);
const previousDisableAutoScrollToBottomRef = useRef(disableAutoScrollToBottom);
const previousDisableAutoScrollSettleRef = useRef(disableAutoScrollToBottom);
const anchorRestoreCleanupRef = useRef<(() => void) | null>(null);

const captureAnchor = useCallback(() => {
Expand Down Expand Up @@ -249,6 +251,16 @@ export const useScrollLocationLogic = (params: UseScrollLocationLogicParams) =>
* path where existing viewport position must be preserved.
*/
useLayoutEffect(() => {
const disableAutoScrollJustReleased =
previousDisableAutoScrollToBottomRef.current && !disableAutoScrollToBottom;
previousDisableAutoScrollToBottomRef.current = disableAutoScrollToBottom;

// Re-enabling auto-scroll should not immediately force a jump to bottom.
// This avoids snap-back after temporary suppression (e.g. jump-to-message).
if (disableAutoScrollJustReleased) {
return;
}

if (listElement) {
setWrapperRect(listElement.getBoundingClientRect());
}
Expand All @@ -275,6 +287,19 @@ export const useScrollLocationLogic = (params: UseScrollLocationLogicParams) =>
* to catch late layout updates without keeping the list in a prolonged lock loop.
*/
useLayoutEffect(() => {
const disableAutoScrollJustReleased =
previousDisableAutoScrollSettleRef.current && !disableAutoScrollToBottom;
previousDisableAutoScrollSettleRef.current = disableAutoScrollToBottom;

// Skip one settle cycle when auto-scroll suppression is released.
// Without this guard, a jump-to-message flow can scroll to the target and then
// get pulled back down by the delayed "keep pinned to bottom" retries
// (80/260/420/900/1700ms), which looks like a snap-back to the latest message.
// Letting this transition frame pass preserves the jump destination.
if (disableAutoScrollJustReleased) {
return;
}

if (
!listElement ||
disableAutoScrollToBottom ||
Expand Down
Loading