Repository: RocketChat/Rocket.Chat
Pull request #35818: refactor: dont use `useEffect` anymore to control thread scroll
Author: login: ggazzo
Labels: name: stat: ready to merge
description: PR tested and approved waiting for merge
name: stat: QA assured
description: Means it has been tested and approved by a company insider
Created: 2025-04-16T03:41:11Z | Merged: 2025-04-16T21:52:52Z

<!-- This is a pull request template, you do not need to uncomment or remove the comments, they won't show up in the PR text. -->
https://rocketchat.atlassian.net/browse/ARCH-1580


Everything started with a flaky test and some reports that the scrolling was behaving strangely. Then it was realized that the control of the scrolls was erratic and not reliable in terms of repetitive testing. Therefore, a refactor was necessary.
<!-- Your Pull Request name should start with one of the following tags
  feat: Adding a new feature
  refactor: A code change that doesn't change behavior (it doesn't add anything and doesn't fix anything)
  fix: For bug fixes that affect the end-user
  chore: For small tasks
  docs: For documentation
  ci: For updating CI configuration
  test: For adding tests
  i18n: For updating any translations
  regression: Issues created/reported/fixed during the development phase. kind of problem that never existed in production and that we don't need to list in a changelog for the end user
-->

<!-- Checklist!!! If you're unsure about any of them, don't hesitate to ask. We're here to help! This is simply a reminder of what we are going to look for before merging your code. 
  - I have read the Contributing Guide - https://github.com/RocketChat/Rocket.Chat/blob/develop/.github/CONTRIBUTING.md#contributing-to-rocketchat doc
  - I have signed the CLA - https://cla-assistant.io/RocketChat/Rocket.Chat
  - Lint and unit tests pass locally with my changes
  - I have added tests that prove my fix is effective or that my feature works (if applicable)
  - I have added necessary documentation (if applicable)
  - Any dependent changes have been merged and published in downstream modules
-->

## Proposed changes (including videos or screenshots)
<!--
  Describe the big picture of your changes here to communicate to the maintainers why we should accept this pull request.
  If it fixes a bug or resolves a feature request, be sure to link to that issue below.
  This description won't be displayed to our end users in the release notes, so feel free to add as much technical context as needed.
  If the changes introduced in this pull request must be presented in the release notes, make sure to add a changeset file. Check our guidelines for adding a changeset to your pull request: https://developer.rocket.chat/contribute-to-rocket.chat/modes-of-contribution/participate-in-rocket.chat-development/development-workflow#4.-adding-changeset-to-your-pull-request 
-->

## Issue(s)
<!-- Link the issues being closed by or related to this PR. For example, you can use #594 if this PR closes issue number 594 -->

## Steps to test or reproduce
<!-- Mention how you would reproduce the bug if not mentioned on the issue page already. Also mention which screens are going to have the changes if applicable -->

## Further comments
<!-- If this is a relatively large or complex change, kick off the discussion by explaining why you chose the solution you did and what alternatives you considered, etc... -->



This pull request refactors the thread scrolling mechanism in the Rocket.Chat application. The changes include:

- Updating `MessageListContext` to use `MutableRefObject` instead of `RefObject` for `messageListRef`, enhancing reference management flexibility.
- Refactoring the `useJumpToMessage` hook to incorporate a `useSafeRefCallback` and an `IntersectionObserver` for improved message scrolling and highlighting.
- Refining type imports and the `messageListRef` type definition in the `MessageListProvider` component.
- Modifying the type of `innerBoxRef` from `HTMLDivElement` to `HTMLElement` in `RoomBody.tsx` and `RoomBodyV2.tsx`, initializing it with `undefined` instead of `null`.
- Simplifying the `ThreadMessageList` component by removing unused imports and optimizing the use of refs for scrolling and navigation.
- Introducing a new hook, `useLegacyThreadMessageListScrolling`, to manage legacy thread message list scrolling with added functionality for throttling scroll actions and using safe ref callbacks.

These changes aim to improve the efficiency and reliability of thread navigation and scrolling within the application.


Diff:
diff --git a/apps/meteor/app/ui-utils/client/lib/RoomHistoryManager.ts b/apps/meteor/app/ui-utils/client/lib/RoomHistoryManager.ts
index 67777b346718b..db11d3ece34fc 100644
--- a/apps/meteor/app/ui-utils/client/lib/RoomHistoryManager.ts
+++ b/apps/meteor/app/ui-utils/client/lib/RoomHistoryManager.ts
@@ -56,8 +56,6 @@ export function upsertMessageBulk(
 
 const defaultLimit = parseInt(getConfig('roomListLimit') ?? '50') || 50;
 
-const waitAfterFlush = (fn: () => void) => setTimeout(() => Tracker.afterFlush(fn), 10);
-
 class RoomHistoryManagerClass extends Emitter {
 	private lastRequest?: Date;
 
@@ -156,8 +154,6 @@ class RoomHistoryManagerClass extends Emitter {
 
 		this.unqueue();
 
-		let previousHeight: number | undefined;
-		let scroll: number | undefined;
 		const { messages = [] } = result;
 		room.unreadNotLoaded.set(result.unreadNotLoaded);
 		room.firstUnread.set(result.firstUnread);
@@ -166,12 +162,7 @@ class RoomHistoryManagerClass extends Emitter {
 			room.oldestTs = messages[messages.length - 1].ts;
 		}
 
-		const wrapper = await waitForElement('.messages-box .wrapper [data-overlayscrollbars-viewport]');
-
-		if (wrapper) {
-			previousHeight = wrapper.scrollHeight;
-			scroll = wrapper.scrollTop;
-		}
+		await waitForElement('.messages-box .wrapper [data-overlayscrollbars-viewport]');
 
 		upsertMessageBulk({
 			msgs: messages.filter((msg) => msg.t !== 'command'),
@@ -194,12 +185,6 @@ class RoomHistoryManagerClass extends Emitter {
 			return this.getMore(rid);
 		}
 
-		waitAfterFlush(() => {
-			this.emit('loaded-messages');
-			const heightDiff = wrapper.scrollHeight - (previousHeight ?? NaN);
-			wrapper.scrollTop = (scroll ?? NaN) + heightDiff;
-		});
-
 		room.isLoading.set(false);
 	}
 
diff --git a/apps/meteor/client/components/message/list/MessageListContext.tsx b/apps/meteor/client/components/message/list/MessageListContext.tsx
index 64899357b2c5b..2f3b1e54c5377 100644
--- a/apps/meteor/client/components/message/list/MessageListContext.tsx
+++ b/apps/meteor/client/components/message/list/MessageListContext.tsx
@@ -1,5 +1,5 @@
 import type { IMessage } from '@rocket.chat/core-typings';
-import type { KeyboardEvent, MouseEvent, RefObject } from 'react';
+import type { KeyboardEvent, MouseEvent, MutableRefObject } from 'react';
 import { createContext, useContext } from 'react';
 
 export type MessageListContextValue = {
@@ -25,7 +25,7 @@ export type MessageListContextValue = {
 	showColors: boolean;
 	jumpToMessageParam?: string;
 	username: string | undefined;
-	messageListRef?: RefObject<HTMLElement>;
+	messageListRef?: MutableRefObject<HTMLElement | undefined>;
 };
 
 export const MessageListContext = createContext<MessageListContextValue>({
@@ -43,7 +43,7 @@ export const MessageListContext = createContext<MessageListContextValue>({
 	showUsername: false,
 	showColors: false,
 	username: undefined,
-	messageListRef: { current: null },
+	messageListRef: { current: undefined },
 });
 
 export const useShowTranslated: MessageListContextValue['useShowTranslated'] = (...args) =>
diff --git a/apps/meteor/client/views/room/MessageList/hooks/useJumpToMessage.ts b/apps/meteor/client/views/room/MessageList/hooks/useJumpToMessage.ts
index 2ec888f51d922..096a6cd4f7777 100644
--- a/apps/meteor/client/views/room/MessageList/hooks/useJumpToMessage.ts
+++ b/apps/meteor/client/views/room/MessageList/hooks/useJumpToMessage.ts
@@ -3,48 +3,69 @@ import { useRouter } from '@rocket.chat/ui-contexts';
 import { useCallback } from 'react';
 
 import { useMessageListJumpToMessageParam, useMessageListRef } from '../../../../components/message/list/MessageListContext';
+import { useSafeRefCallback } from '../../../../hooks/useSafeRefCallback';
 import { setHighlightMessage, clearHighlightMessage } from '../providers/messageHighlightSubscription';
 
 // this is an arbitrary value so that there's a gap between the header and the message;
-const SCROLL_EXTRA_OFFSET = 60;
 
 export const useJumpToMessage = (messageId: IMessage['_id']) => {
 	const jumpToMessageParam = useMessageListJumpToMessageParam();
 	const listRef = useMessageListRef();
 	const router = useRouter();
 
-	const ref = useCallback(
-		(node: HTMLElement | null) => {
-			if (!node || !scroll) {
-				return;
-			}
-			setTimeout(() => {
-				if (listRef?.current) {
-					const wrapper = listRef.current;
-					const containerRect = wrapper.getBoundingClientRect();
-					const messageRect = node.getBoundingClientRect();
-
-					const offset = messageRect.top - containerRect.top;
-					const scrollPosition = wrapper.scrollTop;
-					const newScrollPosition = scrollPosition + offset - SCROLL_EXTRA_OFFSET;
-
-					wrapper.scrollTo({ top: newScrollPosition, behavior: 'smooth' });
+	const ref = useSafeRefCallback(
+		useCallback(
+			(node: HTMLElement | null) => {
+				if (!node || !scroll) {
+					return;
 				}
 
-				const { msg: _, ...search } = router.getSearchParameters();
-				router.navigate(
+				if (listRef) {
+					listRef.current = node;
+				}
+
+				node.scrollIntoView({
+					behavior: 'smooth',
+					block: 'center',
+				});
+				const handleScroll = () => {
+					const { msg: _, ...search } = router.getSearchParameters();
+					router.navigate(
+						{
+							pathname: router.getLocationPathname(),
+							search,
+						},
+						{ replace: true },
+					);
+					setTimeout(clearHighlightMessage, 2000);
+				};
+
+				const observer = new IntersectionObserver(
+					(entries) => {
+						entries.forEach((entry) => {
+							if (entry.isIntersecting) {
+								handleScroll();
+							}
+						});
+					},
 					{
-						pathname: router.getLocationPathname(),
-						search,
+						threshold: 0.1,
 					},
-					{ replace: true },
 				);
 
+				observer.observe(node);
+
 				setHighlightMessage(messageId);
-				setTimeout(clearHighlightMessage, 2000);
-			}, 500);
-		},
-		[listRef, messageId, router],
+
+				return () => {
+					observer.disconnect();
+					if (listRef) {
+						listRef.current = undefined;
+					}
+				};
+			},
+			[listRef, messageId, router],
+		),
 	);
 
 	if (jumpToMessageParam !== messageId) {
diff --git a/apps/meteor/client/views/room/MessageList/providers/MessageListProvider.tsx b/apps/meteor/client/views/room/MessageList/providers/MessageListProvider.tsx
index 682b8b5154ae2..2cf9cc5014359 100644
--- a/apps/meteor/client/views/room/MessageList/providers/MessageListProvider.tsx
+++ b/apps/meteor/client/views/room/MessageList/providers/MessageListProvider.tsx
@@ -1,6 +1,6 @@
 import { isThreadMainMessage } from '@rocket.chat/core-typings';
 import { useLayout, useUser, useUserPreference, useSetting, useEndpoint, useSearchParameter } from '@rocket.chat/ui-contexts';
-import type { ReactNode, RefObject } from 'react';
+import type { MutableRefObject, ReactNode } from 'react';
 import { useMemo, memo } from 'react';
 
 import { getRegexHighlight, getRegexHighlightUrl } from '../../../../../app/highlight-words/client/helper';
@@ -15,7 +15,7 @@ import { useLoadSurroundingMessages } from '../hooks/useLoadSurroundingMessages'
 
 type MessageListProviderProps = {
 	children: ReactNode;
-	messageListRef?: RefObject<HTMLElement>;
+	messageListRef?: MutableRefObject<HTMLElement | undefined>;
 	attachmentDimension?: {
 		width?: number;
 		height?: number;
diff --git a/apps/meteor/client/views/room/body/RoomBody.tsx b/apps/meteor/client/views/room/body/RoomBody.tsx
index 2c5e9d3c09a16..e4b64f35c5864 100644
--- a/apps/meteor/client/views/room/body/RoomBody.tsx
+++ b/apps/meteor/client/views/room/body/RoomBody.tsx
@@ -2,7 +2,7 @@ import { Box } from '@rocket.chat/fuselage';
 import { useMergedRefs } from '@rocket.chat/fuselage-hooks';
 import { usePermission, useRole, useSetting, useTranslation, useUser, useUserPreference } from '@rocket.chat/ui-contexts';
 import type { MouseEvent, ReactElement } from 'react';
-import { memo, useCallback, useMemo, useRef } from 'react';
+import { memo, useCallback, useMemo } from 'react';
 
 import DropTargetOverlay from './DropTargetOverlay';
 import JumpToRecentMessageButton from './JumpToRecentMessageButton';
@@ -81,8 +81,6 @@ const RoomBody = (): ReactElement => {
 		return subscribed;
 	}, [allowAnonymousRead, canPreviewChannelRoom, room, subscribed]);
 
-	const innerBoxRef = useRef<HTMLDivElement | null>(null);
-
 	const {
 		wrapperRef: unreadBarWrapperRef,
 		innerRef: unreadBarInnerRef,
@@ -93,7 +91,7 @@ const RoomBody = (): ReactElement => {
 
 	const { innerRef: dateScrollInnerRef, bubbleRef, listStyle, ...bubbleDate } = useDateScroll();
 
-	const { innerRef: isAtBottomInnerRef, atBottomRef, sendToBottom, sendToBottomIfNecessary, isAtBottom } = useListIsAtBottom();
+	const { innerRef: isAtBottomInnerRef, atBottomRef, sendToBottom, sendToBottomIfNecessary, isAtBottom, jumpToRef } = useListIsAtBottom();
 
 	const { innerRef: getMoreInnerRef } = useGetMore(room._id, atBottomRef);
 
@@ -118,7 +116,6 @@ const RoomBody = (): ReactElement => {
 
 	const innerRef = useMergedRefs(
 		dateScrollInnerRef,
-		innerBoxRef,
 		restoreScrollPositionInnerRef,
 		isAtBottomInnerRef,
 		newMessagesScrollRef,
@@ -245,7 +242,7 @@ const RoomBody = (): ReactElement => {
 														)}
 													</>
 												) : null}
-												<MessageList rid={room._id} messageListRef={innerBoxRef} />
+												<MessageList rid={room._id} messageListRef={jumpToRef} />
 												{hasMoreNextMessages ? (
 													<li className='load-more'>{isLoadingMoreMessages ? <LoadingMessagesIndicator /> : null}</li>
 												) : null}
diff --git a/apps/meteor/client/views/room/body/RoomBodyV2.tsx b/apps/meteor/client/views/room/body/RoomBodyV2.tsx
index 04ddc5da463f4..2038163f82637 100644
--- a/apps/meteor/client/views/room/body/RoomBodyV2.tsx
+++ b/apps/meteor/client/views/room/body/RoomBodyV2.tsx
@@ -2,7 +2,7 @@ import { Box } from '@rocket.chat/fuselage';
 import { useMergedRefs } from '@rocket.chat/fuselage-hooks';
 import { usePermission, useRole, useSetting, useTranslation, useUser, useUserPreference } from '@rocket.chat/ui-contexts';
 import type { MouseEvent, ReactElement } from 'react';
-import { memo, useCallback, useMemo, useRef } from 'react';
+import { memo, useCallback, useMemo } from 'react';
 
 import { isTruthy } from '../../../../lib/isTruthy';
 import { CustomScrollbars } from '../../../components/CustomScrollbars';
@@ -81,8 +81,6 @@ const RoomBody = (): ReactElement => {
 		return subscribed;
 	}, [allowAnonymousRead, canPreviewChannelRoom, room, subscribed]);
 
-	const innerBoxRef = useRef<HTMLDivElement | null>(null);
-
 	const {
 		wrapperRef,
 		innerRef: unreadBarInnerRef,
@@ -93,7 +91,7 @@ const RoomBody = (): ReactElement => {
 
 	const { innerRef: dateScrollInnerRef, bubbleRef, listStyle, ...bubbleDate } = useDateScroll();
 
-	const { innerRef: isAtBottomInnerRef, atBottomRef, sendToBottom, sendToBottomIfNecessary, isAtBottom } = useListIsAtBottom();
+	const { innerRef: isAtBottomInnerRef, atBottomRef, sendToBottom, sendToBottomIfNecessary, isAtBottom, jumpToRef } = useListIsAtBottom();
 
 	const { innerRef: getMoreInnerRef } = useGetMore(room._id, atBottomRef);
 
@@ -118,7 +116,6 @@ const RoomBody = (): ReactElement => {
 
 	const innerRef = useMergedRefs(
 		dateScrollInnerRef,
-		innerBoxRef,
 		restoreScrollPositionInnerRef,
 		isAtBottomInnerRef,
 		newMessagesScrollRef,
@@ -248,7 +245,7 @@ const RoomBody = (): ReactElement => {
 														)}
 													</>
 												) : null}
-												<MessageList rid={room._id} messageListRef={innerBoxRef} />
+												<MessageList rid={room._id} messageListRef={jumpToRef} />
 												{hasMoreNextMessages ? (
 													<li className='load-more'>{isLoadingMoreMessages ? <LoadingMessagesIndicator /> : null}</li>
 												) : null}
diff --git a/apps/meteor/client/views/room/body/hooks/useListIsAtBottom.ts b/apps/meteor/client/views/room/body/hooks/useListIsAtBottom.ts
index fc67c50d9fd83..7425a43e53c42 100644
--- a/apps/meteor/client/views/room/body/hooks/useListIsAtBottom.ts
+++ b/apps/meteor/client/views/room/body/hooks/useListIsAtBottom.ts
@@ -9,6 +9,8 @@ import { useSafeRefCallback } from '../../../../hooks/useSafeRefCallback';
 export const useListIsAtBottom = () => {
 	const atBottomRef = useRef(true);
 
+	const jumpToRef = useRef<HTMLElement>(undefined);
+
 	const innerBoxRef = useRef<HTMLDivElement | null>(null);
 
 	const sendToBottom = useCallback(() => {
@@ -16,6 +18,9 @@ export const useListIsAtBottom = () => {
 	}, []);
 
 	const sendToBottomIfNecessary = useCallback(() => {
+		if (jumpToRef.current) {
+			atBottomRef.current = false;
+		}
 		if (atBottomRef.current === true) {
 			sendToBottom();
 		}
@@ -42,6 +47,9 @@ export const useListIsAtBottom = () => {
 				}
 
 				const observer = new ResizeObserver(() => {
+					if (jumpToRef.current) {
+						atBottomRef.current = false;
+					}
 					if (atBottomRef.current === true) {
 						node.scrollTo({ left: 30, top: node.scrollHeight });
 					}
@@ -72,5 +80,6 @@ export const useListIsAtBottom = () => {
 		sendToBottom,
 		sendToBottomIfNecessary,
 		isAtBottom,
+		jumpToRef,
 	};
 };
diff --git a/apps/meteor/client/views/room/body/hooks/useRestoreScrollPosition.spec.ts b/apps/meteor/client/views/room/body/hooks/useRestoreScrollPosition.spec.ts
index 5444ac71e9818..30bfd0322008e 100644
--- a/apps/meteor/client/views/room/body/hooks/useRestoreScrollPosition.spec.ts
+++ b/apps/meteor/client/views/room/body/hooks/useRestoreScrollPosition.spec.ts
@@ -33,13 +33,14 @@ describe('useRestoreScrollPosition', () => {
 		expect(mockElement.removeEventListener).toHaveBeenCalledWith('scroll', expect.any(Function));
 	});
 
-	it('should not restore scroll position if already at bottom', () => {
-		(RoomManager.getStore as jest.Mock).mockReturnValue({ scroll: 100, atBottom: true });
+	it('should do nothing if no previous scroll position is stored', () => {
+		(RoomManager.getStore as jest.Mock).mockReturnValue({ atBottom: true });
 
 		const mockElement = {
 			addEventListener: jest.fn(),
 			removeEventListener: jest.fn(),
 			scrollHeight: 800,
+			scrollTop: 123,
 		};
 
 		const useRefSpy = jest.spyOn(React, 'useRef').mockReturnValueOnce({ current: mockElement });
@@ -47,7 +48,7 @@ describe('useRestoreScrollPosition', () => {
 		const { unmount } = renderHook(() => useRestoreScrollPosition());
 
 		expect(useRefSpy).toHaveBeenCalledWith(null);
-		expect(mockElement).toHaveProperty('scrollTop', 800);
+		expect(mockElement).toHaveProperty('scrollTop', 123);
 		expect(mockElement).not.toHaveProperty('scrollLeft');
 
 		unmount();
diff --git a/apps/meteor/client/views/room/body/hooks/useRestoreScrollPosition.ts b/apps/meteor/client/views/room/body/hooks/useRestoreScrollPosition.ts
index 77c03bbed39c9..f4a54699d3625 100644
--- a/apps/meteor/client/views/room/body/hooks/useRestoreScrollPosition.ts
+++ b/apps/meteor/client/views/room/body/hooks/useRestoreScrollPosition.ts
@@ -16,11 +16,9 @@ export function useRestoreScrollPosition() {
 
 		const store = RoomManager.getStore(roomId);
 
-		if (store?.scroll && !store.atBottom) {
+		if (store?.scroll !== undefined && !store.atBottom) {
 			ref.current.scrollTop = store.scroll;
 			ref.current.scrollLeft = 30;
-		} else {
-			ref.current.scrollTop = ref.current.scrollHeight;
 		}
 	}, [roomId]);
 
diff --git a/apps/meteor/client/views/room/contextualBar/Threads/components/ThreadMessageList.tsx b/apps/meteor/client/views/room/contextualBar/Threads/components/ThreadMessageList.tsx
index ed0ed10a488cc..b52e8c7b0cc57 100644
--- a/apps/meteor/client/views/room/contextualBar/Threads/components/ThreadMessageList.tsx
+++ b/apps/meteor/client/views/room/contextualBar/Threads/components/ThreadMessageList.tsx
@@ -1,10 +1,9 @@
 import type { IMessage, IThreadMainMessage } from '@rocket.chat/core-typings';
 import { Box } from '@rocket.chat/fuselage';
-import { useMergedRefs } from '@rocket.chat/fuselage-hooks';
 import { useSetting, useUserPreference } from '@rocket.chat/ui-contexts';
 import { differenceInSeconds } from 'date-fns';
 import type { ReactElement } from 'react';
-import { Fragment, useRef } from 'react';
+import { Fragment } from 'react';
 import { useTranslation } from 'react-i18next';
 
 import { ThreadMessageItem } from './ThreadMessageItem';
@@ -55,11 +54,7 @@ const ThreadMessageList = ({ mainMessage }: ThreadMessageListProps): ReactElemen
 
 	const { messages, loading } = useLegacyThreadMessages(mainMessage._id);
 
-	const {
-		listWrapperRef: listWrapperScrollRef,
-		listRef: listScrollRef,
-		onScroll: handleScroll,
-	} = useLegacyThreadMessageListScrolling(mainMessage);
+	const { innerRef: listScrollRef, jumpToRef } = useLegacyThreadMessageListScrolling(mainMessage);
 
 	const hideUsernames = useUserPreference<boolean>('hideUsernames');
 	const showUserAvatar = !!useUserPreference<boolean>('displayAvatars');
@@ -68,18 +63,14 @@ const ThreadMessageList = ({ mainMessage }: ThreadMessageListProps): ReactElemen
 
 	const { messageListRef } = useMessageListNavigation();
 
-	const ref = useRef<HTMLElement | null>(null);
-	const listRef = useMergedRefs<HTMLElement | null>(listScrollRef, messageListRef);
-	const scrollRef = useMergedRefs<HTMLElement | null>(innerRef, listWrapperScrollRef, ref);
-
 	return (
 		<div className={['thread-list js-scroll-thread', hideUsernames && 'hide-usernames'].filter(isTruthy).join(' ')}>
 			<BubbleDate ref={bubbleRef} {...bubbleDate} />
-			<CustomScrollbars ref={scrollRef} onScroll={handleScroll} style={{ scrollBehavior: 'smooth', overflowX: 'hidden' }}>
+			<CustomScrollbars ref={listScrollRef} style={{ scrollBehavior: 'smooth', overflowX: 'hidden' }}>
 				<Box
 					is='ul'
 					className={[listStyle, 'thread']}
-					ref={listRef}
+					ref={messageListRef}
 					aria-label={t('Thread_message_list')}
 					style={{ scrollBehavior: 'smooth', overflowX: 'hidden' }}
 				>
@@ -88,7 +79,7 @@ const ThreadMessageList = ({ mainMessage }: ThreadMessageListProps): ReactElemen
 							<LoadingMessagesIndicator />
 						</li>
 					) : (
-						<MessageListProvider messageListRef={ref}>
+						<MessageListProvider messageListRef={jumpToRef}>
 							{[mainMessage, ...messages].map((message, index, { [index - 1]: previous }) => {
 								const sequential = isMessageSequential(message, previous, messageGroupingPeriod);
 								const newDay = isMessageNewDay(message, previous);
diff --git a/apps/meteor/client/views/room/contextualBar/Threads/hooks/useLegacyThreadMessageListScrolling.ts b/apps/meteor/client/views/room/contextualBar/Threads/hooks/useLegacyThreadMessageListScrolling.ts
index e7b6dabfe7d69..51ccda787b542 100644
--- a/apps/meteor/client/views/room/contextualBar/Threads/hooks/useLegacyThreadMessageListScrolling.ts
+++ b/apps/meteor/client/views/room/contextualBar/Threads/hooks/useLegacyThreadMessageListScrolling.ts
@@ -1,36 +1,16 @@
 import type { IMessage } from '@rocket.chat/core-typings';
 import { isEditedMessage } from '@rocket.chat/core-typings';
-import { useUser } from '@rocket.chat/ui-contexts';
-import { useCallback, useEffect, useRef } from 'react';
+import { useUserId } from '@rocket.chat/ui-contexts';
+import { useEffect } from 'react';
 
 import { callbacks } from '../../../../../../lib/callbacks';
-import type { OverlayScrollbars } from '../../../../../components/CustomScrollbars';
+import { useListIsAtBottom } from '../../../body/hooks/useListIsAtBottom';
 import { useRoom } from '../../../contexts/RoomContext';
 
 export const useLegacyThreadMessageListScrolling = (mainMessage: IMessage) => {
-	const listWrapperRef = useRef<HTMLDivElement>(null);
-	const listRef = useRef<HTMLElement>(null);
-
-	const atBottomRef = useRef(true);
-
-	const onScroll = useCallback(({ elements }: OverlayScrollbars) => {
-		const {
-			viewport: { scrollTop, scrollHeight, clientHeight },
-		} = elements();
-		atBottomRef.current = scrollTop >= scrollHeight - clientHeight;
-	}, []);
-
-	const sendToBottomIfNecessary = useCallback(() => {
-		if (atBottomRef.current === true) {
-			const listWrapper = listWrapperRef.current;
-
-			listWrapper?.scrollTo(30, listWrapper.scrollHeight);
-		}
-	}, []);
-
+	const { atBottomRef, innerRef, sendToBottom, sendToBottomIfNecessary, isAtBottom, jumpToRef } = useListIsAtBottom();
 	const room = useRoom();
-	const user = useUser();
-
+	const uid = useUserId();
 	useEffect(() => {
 		callbacks.add(
 			'streamNewMessage',
@@ -39,7 +19,7 @@ export const useLegacyThreadMessageListScrolling = (mainMessage: IMessage) => {
 					return;
 				}
 
-				if (msg.u._id === user?._id) {
+				if (msg.u._id === uid) {
 					atBottomRef.current = true;
 					sendToBottomIfNecessary();
 				}
@@ -51,20 +31,7 @@ export const useLegacyThreadMessageListScrolling = (mainMessage: IMessage) => {
 		return () => {
 			callbacks.remove('streamNewMessage', `thread-scroll-${room._id}`);
 		};
-	}, [room._id, sendToBottomIfNecessary, user?._id, mainMessage._id]);
-
-	useEffect(() => {
-		const observer = new ResizeObserver(() => {
-			sendToBottomIfNecessary();
-		});
-
-		if (listWrapperRef.current) observer.observe(listWrapperRef.current);
-		if (listRef.current) observer.observe(listRef.current);
-
-		return () => {
-			observer.disconnect();
-		};
-	}, [sendToBottomIfNecessary]);
+	}, [room._id, atBottomRef, sendToBottomIfNecessary, uid, mainMessage._id]);
 
-	return { listWrapperRef, listRef, requestScrollToBottom: sendToBottomIfNecessary, onScroll };
+	return { atBottomRef, innerRef, sendToBottom, sendToBottomIfNecessary, isAtBottom, jumpToRef };
 };
diff --git a/apps/meteor/tests/e2e/jump-to-thread-message.spec.ts b/apps/meteor/tests/e2e/jump-to-thread-message.spec.ts
index 5bcb23d76d342..fd92d933ae5a0 100644
--- a/apps/meteor/tests/e2e/jump-to-thread-message.spec.ts
+++ b/apps/meteor/tests/e2e/jump-to-thread-message.spec.ts
@@ -47,6 +47,11 @@ test.describe.serial('Threads', () => {
 		const messageLink = `/channel/${targetChannel.name}?msg=${mainMessage._id}`;
 		await page.goto(messageLink);
 
+		await expect(async () => {
+			await page.waitForSelector('.main-content');
+			await page.waitForSelector(`[data-qa-rc-room="${targetChannel._id}"]`);
+		}).toPass();
+
 		const message = page.locator(`[aria-label=\"Message list\"] [data-id=\"${mainMessage._id}\"]`);
 
 		await expect(message).toBeVisible();
@@ -54,9 +59,29 @@ test.describe.serial('Threads', () => {
 	});
 
 	test('expect to jump scroll to thread message on opening its message link', async ({ page }) => {
+		const threadMessageLink = `/channel/${targetChannel.name}?msg=${threadMessage._id}`;
+		await page.goto(threadMessageLink);
+
+		await expect(async () => {
+			await page.waitForSelector('.main-content');
+			await page.waitForSelector(`[data-qa-rc-room="${targetChannel._id}"]`);
+		}).toPass();
+
+		const message = await page.locator(`[aria-label=\"Thread message list\"] [data-id=\"${threadMessage._id}\"]`);
+
+		await expect(message).toBeVisible();
+		await expect(message).toBeInViewport();
+	});
+
+	test('expect to jump scroll to thread message on opening its message link from a different channel', async ({ page }) => {
 		const threadMessageLink = `/channel/general?msg=${threadMessage._id}`;
 		await page.goto(threadMessageLink);
 
+		await expect(async () => {
+			await page.waitForSelector('.main-content');
+			await page.waitForSelector(`[data-qa-rc-room="${targetChannel._id}"]`);
+		}).toPass();
+
 		const message = await page.locator(`[aria-label=\"Thread message list\"] [data-id=\"${threadMessage._id}\"]`);
 
 		await expect(message).toBeVisible();


Reviews:
user: login: ggazzo
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-16T03:48:17Z
user: login: ggazzo
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-16T03:48:31Z
user: login: kody-ai[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-16T04:01:01Z
user: login: kody-ai[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-16T04:01:04Z
user: login: kody-ai[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-16T04:01:07Z
user: login: kody-ai[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-16T04:01:10Z
user: login: kody-ai[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-16T04:01:13Z
user: login: kody-ai[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-16T04:01:16Z
user: login: kody-ai[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-16T04:01:19Z
user: login: kody-ai[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-16T04:52:10Z
user: login: kody-ai[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-16T04:52:13Z
user: login: kody-ai[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-16T04:52:45Z
user: login: kody-ai[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-16T05:22:25Z
user: login: kody-ai[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-16T05:22:28Z
user: login: kody-ai[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-16T05:22:31Z
user: login: kody-ai[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-16T05:22:34Z
user: login: kody-ai[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-16T12:51:23Z
user: login: kody-ai[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-16T12:51:26Z
user: login: kody-ai[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-16T12:51:29Z
user: login: kody-ai[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-16T15:23:24Z
user: login: aleksandernsilva
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-16T19:20:01Z
user: login: gabriellsh
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-16T21:49:38Z

Review comments:
diff_hunk: @@ -51,20 +63,79 @@ export const useLegacyThreadMessageListScrolling = (mainMessage: IMessage) => {
 		return () => {
 			callbacks.remove('streamNewMessage', `thread-scroll-${room._id}`);
 		};
-	}, [room._id, sendToBottomIfNecessary, user?._id, mainMessage._id]);
+	}, [room._id, sendToBottomIfNecessaryInner, user?._id, mainMessage._id]);
 
-	useEffect(() => {
-		const observer = new ResizeObserver(() => {
-			sendToBottomIfNecessary();
-		});
+	const listWrapperRefCallback = useSafeRefCallback(
+		useCallback((node: HTMLDivElement | null) => {
+			if (node === null) {
+				return;
+			}
 
-		if (listWrapperRef.current) observer.observe(listWrapperRef.current);
-		if (listRef.current) observer.observe(listRef.current);
+			const setScrollToBottom = () => {
+				const { scrollTop, scrollHeight, clientHeight } = node;
+				atBottomRef.current = scrollTop >= scrollHeight - clientHeight;
+				console.log('updating atBottomRef', atBottomRef.current);
path: apps/meteor/client/views/room/contextualBar/Threads/hooks/useLegacyThreadMessageListScrolling.ts
user: login: ggazzo
body: ```suggestion
```
created_at: 2025-04-16T03:48:17Z
author_association: MEMBER
diff_hunk: @@ -1,32 +1,40 @@
 import type { IMessage } from '@rocket.chat/core-typings';
 import { isEditedMessage } from '@rocket.chat/core-typings';
+import { useMergedRefs } from '@rocket.chat/fuselage-hooks';
 import { useUser } from '@rocket.chat/ui-contexts';
 import { useCallback, useEffect, useRef } from 'react';
+import { throttle } from 'underscore';
 
 import { callbacks } from '../../../../../../lib/callbacks';
-import type { OverlayScrollbars } from '../../../../../components/CustomScrollbars';
+import { useSafeRefCallback } from '../../../../../hooks/useSafeRefCallback';
 import { useRoom } from '../../../contexts/RoomContext';
 
 export const useLegacyThreadMessageListScrolling = (mainMessage: IMessage) => {
 	const listWrapperRef = useRef<HTMLDivElement>(null);
 	const listRef = useRef<HTMLElement>(null);
 
-	const atBottomRef = useRef(true);
+	const jumpToRef = useRef<HTMLElement>(undefined);
 
-	const onScroll = useCallback(({ elements }: OverlayScrollbars) => {
-		const {
-			viewport: { scrollTop, scrollHeight, clientHeight },
-		} = elements();
-		atBottomRef.current = scrollTop >= scrollHeight - clientHeight;
-	}, []);
+	const atBottomRef = useRef(true);
+	const sendToBottomIfNecessaryInner = useCallback(
+		throttle((listWrapper: HTMLDivElement) => {
+			if (jumpToRef.current) {
+				atBottomRef.current = false;
+			}
+			if (atBottomRef.current === true) {
+				console.log(`[${Date.now()}]`, 'scrolling to bottom');
path: apps/meteor/client/views/room/contextualBar/Threads/hooks/useLegacyThreadMessageListScrolling.ts
user: login: ggazzo
body: ```suggestion
```
created_at: 2025-04-16T03:48:30Z
author_association: MEMBER
diff_hunk: @@ -1,32 +1,39 @@
 import type { IMessage } from '@rocket.chat/core-typings';
 import { isEditedMessage } from '@rocket.chat/core-typings';
+import { useMergedRefs } from '@rocket.chat/fuselage-hooks';
 import { useUser } from '@rocket.chat/ui-contexts';
 import { useCallback, useEffect, useRef } from 'react';
+import { throttle } from 'underscore';
 
 import { callbacks } from '../../../../../../lib/callbacks';
-import type { OverlayScrollbars } from '../../../../../components/CustomScrollbars';
+import { useSafeRefCallback } from '../../../../../hooks/useSafeRefCallback';
 import { useRoom } from '../../../contexts/RoomContext';
 
 export const useLegacyThreadMessageListScrolling = (mainMessage: IMessage) => {
 	const listWrapperRef = useRef<HTMLDivElement>(null);
 	const listRef = useRef<HTMLElement>(null);
 
-	const atBottomRef = useRef(true);
+	const jumpToRef = useRef<HTMLElement>(undefined);
 
-	const onScroll = useCallback(({ elements }: OverlayScrollbars) => {
-		const {
-			viewport: { scrollTop, scrollHeight, clientHeight },
-		} = elements();
-		atBottomRef.current = scrollTop >= scrollHeight - clientHeight;
-	}, []);
+	const atBottomRef = useRef(true);
+	const sendToBottomIfNecessaryInner = useCallback(
+		throttle((listWrapper: HTMLDivElement) => {
+			if (jumpToRef.current) {
+				atBottomRef.current = false;
+			}
path: apps/meteor/client/views/room/contextualBar/Threads/hooks/useLegacyThreadMessageListScrolling.ts
user: login: kody-ai[bot]
body: ![kody code-review](https://img.shields.io/badge/kody-code--review-312B4B?labelColor=C9BBF2) ![Kody Rules](https://img.shields.io/badge/Kody_Rules-4527A0) ![high](https://img.shields.io/badge/severity_level-high-6B6B92)
```typescript
+			if (jumpToRef.current) {
+				// Use a callback ref or state variable to access the current value
+				atBottomRef.current = false;
+			}
```
The `jumpToRef.current` is accessed inside a `throttle` callback, which can lead to stale closures and unexpected behavior.

This issue appears in multiple locations:
* apps/meteor/client/views/room/contextualBar/Threads/hooks/useLegacyThreadMessageListScrolling.ts: Lines 21-23
Please use a callback ref or a state variable to track the `jumpToRef` value instead of accessing it directly inside the throttled callback.


<sub>Talk to Kody by mentioning @kody</sub>


<sub>Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.</sub>

<!-- kody-codereview -->&#8203;
&#8203;
created_at: 2025-04-16T04:01:01Z
author_association: NONE
diff_hunk: @@ -43,7 +43,7 @@ export const MessageListContext = createContext<MessageListContextValue>({
 	showUsername: false,
 	showColors: false,
 	username: undefined,
-	messageListRef: { current: null },
+	messageListRef: { current: undefined },
path: apps/meteor/client/components/message/list/MessageListContext.tsx
user: login: kody-ai[bot]
body: ![kody code-review](https://img.shields.io/badge/kody-code--review-312B4B?labelColor=C9BBF2) ![Error Handling](https://img.shields.io/badge/Error_Handling-FF6D00) ![high](https://img.shields.io/badge/severity_level-high-6B6B92)
```typescript
messageListRef: { current: undefined },
// Always check messageListRef?.current before usage to prevent runtime errors
```
Null checks are missing when accessing `messageListRef.current`, which could lead to runtime errors.

This issue appears in multiple locations:
* apps/meteor/client/components/message/list/MessageListContext.tsx: Lines 46-46
Please add null checks when accessing `messageListRef.current` to prevent potential runtime errors.


<sub>Talk to Kody by mentioning @kody</sub>


<sub>Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.</sub>

<!-- kody-codereview -->&#8203;
&#8203;
created_at: 2025-04-16T04:01:04Z
author_association: NONE
diff_hunk: @@ -3,48 +3,69 @@ import { useRouter } from '@rocket.chat/ui-contexts';
 import { useCallback } from 'react';
 
 import { useMessageListJumpToMessageParam, useMessageListRef } from '../../../../components/message/list/MessageListContext';
+import { useSafeRefCallback } from '../../../../hooks/useSafeRefCallback';
 import { setHighlightMessage, clearHighlightMessage } from '../providers/messageHighlightSubscription';
 
 // this is an arbitrary value so that there's a gap between the header and the message;
-const SCROLL_EXTRA_OFFSET = 60;
 
 export const useJumpToMessage = (messageId: IMessage['_id']) => {
 	const jumpToMessageParam = useMessageListJumpToMessageParam();
 	const listRef = useMessageListRef();
 	const router = useRouter();
 
-	const ref = useCallback(
-		(node: HTMLElement | null) => {
-			if (!node || !scroll) {
-				return;
-			}
-			setTimeout(() => {
-				if (listRef?.current) {
-					const wrapper = listRef.current;
-					const containerRect = wrapper.getBoundingClientRect();
-					const messageRect = node.getBoundingClientRect();
-
-					const offset = messageRect.top - containerRect.top;
-					const scrollPosition = wrapper.scrollTop;
-					const newScrollPosition = scrollPosition + offset - SCROLL_EXTRA_OFFSET;
-
-					wrapper.scrollTo({ top: newScrollPosition, behavior: 'smooth' });
+	const ref = useSafeRefCallback(
+		useCallback(
+			(node: HTMLElement | null) => {
+				if (!node || !scroll) {
+					return;
 				}
 
-				const { msg: _, ...search } = router.getSearchParameters();
-				router.navigate(
+				if (listRef) {
+					listRef.current = node;
+				}
+
+				node.scrollIntoView({
+					behavior: 'smooth',
+					block: 'center',
+				});
+				const handleScroll = () => {
+					const { msg: _, ...search } = router.getSearchParameters();
+					router.navigate(
+						{
+							pathname: router.getLocationPathname(),
+							search,
+						},
+						{ replace: true },
+					);
+					setTimeout(clearHighlightMessage, 2000);
+				};
+
+				const observer = new IntersectionObserver(
+					(entries) => {
+						entries.forEach((entry) => {
+							if (entry.isIntersecting) {
+								handleScroll();
+							}
+						});
+					},
 					{
-						pathname: router.getLocationPathname(),
-						search,
+						threshold: 0.1,
 					},
-					{ replace: true },
 				);
 
+				observer.observe(node);
+
 				setHighlightMessage(messageId);
-				setTimeout(clearHighlightMessage, 2000);
-			}, 500);
-		},
-		[listRef, messageId, router],
+
+				return () => {
+					observer.disconnect();
+					if (listRef) {
+						listRef.current = undefined;
+					}
+				};
path: apps/meteor/client/views/room/MessageList/hooks/useJumpToMessage.ts
user: login: kody-ai[bot]
body: ![kody code-review](https://img.shields.io/badge/kody-code--review-312B4B?labelColor=C9BBF2) ![Kody Rules](https://img.shields.io/badge/Kody_Rules-4527A0) ![medium](https://img.shields.io/badge/severity_level-medium-1A7BBE)
```typescript
+				observer.observe(node);
+
+				setHighlightMessage(messageId);
+
+				return () => {
+					observer.disconnect();
+					if (listRef) {
+						listRef.current = undefined;
+					}
+				};
```
The `IntersectionObserver` is not disconnected when the component unmounts or when the `messageId` changes, which can lead to memory leaks.

This issue appears in multiple locations:
* apps/meteor/client/views/room/MessageList/hooks/useJumpToMessage.ts: Lines 56-65
Please add a cleanup function to disconnect the `IntersectionObserver` when the component unmounts or when the `messageId` changes.


<sub>Talk to Kody by mentioning @kody</sub>


<sub>Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.</sub>

<!-- kody-codereview -->&#8203;
&#8203;
created_at: 2025-04-16T04:01:07Z
author_association: NONE
diff_hunk: @@ -51,20 +62,78 @@ export const useLegacyThreadMessageListScrolling = (mainMessage: IMessage) => {
 		return () => {
 			callbacks.remove('streamNewMessage', `thread-scroll-${room._id}`);
 		};
-	}, [room._id, sendToBottomIfNecessary, user?._id, mainMessage._id]);
+	}, [room._id, sendToBottomIfNecessaryInner, user?._id, mainMessage._id]);
 
-	useEffect(() => {
-		const observer = new ResizeObserver(() => {
-			sendToBottomIfNecessary();
-		});
+	const listWrapperRefCallback = useSafeRefCallback(
+		useCallback((node: HTMLDivElement | null) => {
+			if (node === null) {
+				return;
+			}
 
-		if (listWrapperRef.current) observer.observe(listWrapperRef.current);
-		if (listRef.current) observer.observe(listRef.current);
+			const setScrollToBottom = () => {
+				const { scrollTop, scrollHeight, clientHeight } = node;
+				atBottomRef.current = scrollTop >= scrollHeight - clientHeight;
+			};
 
-		return () => {
-			observer.disconnect();
-		};
-	}, [sendToBottomIfNecessary]);
+			node.addEventListener('scroll', setScrollToBottom);
+			return () => {
+				node.removeEventListener('scroll', setScrollToBottom);
+			};
+		}, []),
+	);
+
+	const listRefCallback = useSafeRefCallback(
+		useCallback(
+			(node: HTMLDivElement | null) => {
+				if (node === null) {
+					return;
+				}
+
+				// Create an observer instance linked to the callback function
+				const mutation = new MutationObserver((mutationList) => {
+					for (const mutation of mutationList) {
+						if (mutation.type === 'childList') {
+							sendToBottomIfNecessary();
+						}
+					}
+				});
+				mutation.observe(node, {
+					childList: true,
+				});
+
+				return () => {
+					mutation.disconnect();
+				};
+			},
+			[sendToBottomIfNecessary],
+		),
+	);
+
+	return {
+		listWrapperRef: useMergedRefs(
+			useSafeRefCallback(
+				useCallback(
+					(node: HTMLDivElement | null) => {
+						if (node === null) {
+							return;
+						}
 
-	return { listWrapperRef, listRef, requestScrollToBottom: sendToBottomIfNecessary, onScroll };
+						const observer = new ResizeObserver(() => {
+							sendToBottomIfNecessary();
+						});
+						observer.observe(node);
+						return () => {
+							observer.disconnect();
+						};
path: apps/meteor/client/views/room/contextualBar/Threads/hooks/useLegacyThreadMessageListScrolling.ts
user: login: kody-ai[bot]
body: ![kody code-review](https://img.shields.io/badge/kody-code--review-312B4B?labelColor=C9BBF2) ![Kody Rules](https://img.shields.io/badge/Kody_Rules-4527A0) ![medium](https://img.shields.io/badge/severity_level-medium-1A7BBE)
```typescript
+						const observer = new ResizeObserver(() => {
+							sendToBottomIfNecessary();
+						});
+						observer.observe(node);
+						// Ensure observer is disconnected on component unmount
+						return () => {
+							observer.disconnect();
+						};
```
The `ResizeObserver` is not disconnected when the component unmounts, which could lead to memory leaks.

This issue appears in multiple locations:
* apps/meteor/client/views/room/contextualBar/Threads/hooks/useLegacyThreadMessageListScrolling.ts: Lines 121-127
Please ensure the `ResizeObserver` is disconnected in a cleanup function when the component unmounts.


<sub>Talk to Kody by mentioning @kody</sub>


<sub>Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.</sub>

<!-- kody-codereview -->&#8203;
&#8203;
created_at: 2025-04-16T04:01:10Z
author_association: NONE
diff_hunk: @@ -3,48 +3,69 @@ import { useRouter } from '@rocket.chat/ui-contexts';
 import { useCallback } from 'react';
 
 import { useMessageListJumpToMessageParam, useMessageListRef } from '../../../../components/message/list/MessageListContext';
+import { useSafeRefCallback } from '../../../../hooks/useSafeRefCallback';
 import { setHighlightMessage, clearHighlightMessage } from '../providers/messageHighlightSubscription';
 
 // this is an arbitrary value so that there's a gap between the header and the message;
-const SCROLL_EXTRA_OFFSET = 60;
 
 export const useJumpToMessage = (messageId: IMessage['_id']) => {
 	const jumpToMessageParam = useMessageListJumpToMessageParam();
 	const listRef = useMessageListRef();
 	const router = useRouter();
 
-	const ref = useCallback(
-		(node: HTMLElement | null) => {
-			if (!node || !scroll) {
-				return;
-			}
-			setTimeout(() => {
-				if (listRef?.current) {
-					const wrapper = listRef.current;
-					const containerRect = wrapper.getBoundingClientRect();
-					const messageRect = node.getBoundingClientRect();
-
-					const offset = messageRect.top - containerRect.top;
-					const scrollPosition = wrapper.scrollTop;
-					const newScrollPosition = scrollPosition + offset - SCROLL_EXTRA_OFFSET;
-
-					wrapper.scrollTo({ top: newScrollPosition, behavior: 'smooth' });
+	const ref = useSafeRefCallback(
+		useCallback(
+			(node: HTMLElement | null) => {
+				if (!node || !scroll) {
path: apps/meteor/client/views/room/MessageList/hooks/useJumpToMessage.ts
user: login: kody-ai[bot]
body: ![kody code-review](https://img.shields.io/badge/kody-code--review-312B4B?labelColor=C9BBF2) ![Potential Issues](https://img.shields.io/badge/Potential_Issues-B71C1C) ![medium](https://img.shields.io/badge/severity_level-medium-1A7BBE)
```typescript
// Remove undefined scroll check
if (!node) {
```
The variable `scroll` is used in a condition but is not defined within the scope of the `useCallback` function, which will lead to a `ReferenceError` at runtime.

This issue appears in multiple locations:
* apps/meteor/client/views/room/MessageList/hooks/useJumpToMessage.ts: Lines 19-19
Please ensure that `scroll` is defined or remove it if it is not needed.


<sub>Talk to Kody by mentioning @kody</sub>


<sub>Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.</sub>

<!-- kody-codereview -->&#8203;
&#8203;
created_at: 2025-04-16T04:01:13Z
author_association: NONE
diff_hunk: @@ -1,32 +1,39 @@
 import type { IMessage } from '@rocket.chat/core-typings';
 import { isEditedMessage } from '@rocket.chat/core-typings';
+import { useMergedRefs } from '@rocket.chat/fuselage-hooks';
 import { useUser } from '@rocket.chat/ui-contexts';
 import { useCallback, useEffect, useRef } from 'react';
+import { throttle } from 'underscore';
 
 import { callbacks } from '../../../../../../lib/callbacks';
-import type { OverlayScrollbars } from '../../../../../components/CustomScrollbars';
+import { useSafeRefCallback } from '../../../../../hooks/useSafeRefCallback';
 import { useRoom } from '../../../contexts/RoomContext';
 
 export const useLegacyThreadMessageListScrolling = (mainMessage: IMessage) => {
 	const listWrapperRef = useRef<HTMLDivElement>(null);
 	const listRef = useRef<HTMLElement>(null);
 
-	const atBottomRef = useRef(true);
+	const jumpToRef = useRef<HTMLElement>(undefined);
path: apps/meteor/client/views/room/contextualBar/Threads/hooks/useLegacyThreadMessageListScrolling.ts
user: login: kody-ai[bot]
body: ![kody code-review](https://img.shields.io/badge/kody-code--review-312B4B?labelColor=C9BBF2) ![Potential Issues](https://img.shields.io/badge/Potential_Issues-B71C1C) ![medium](https://img.shields.io/badge/severity_level-medium-1A7BBE)
```typescript
const jumpToRef = useRef<HTMLElement>(null);
```
The `jumpToRef` is initialized with `undefined` for an `HTMLElement` type, which can lead to potential runtime errors if not handled properly.

This issue appears in multiple locations:
* apps/meteor/client/views/room/contextualBar/Threads/hooks/useLegacyThreadMessageListScrolling.ts: Lines 16-16
Please initialize `jumpToRef` with `null` instead of `undefined` to align with the typical usage of `useRef` for DOM elements.


<sub>Talk to Kody by mentioning @kody</sub>


<sub>Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.</sub>

<!-- kody-codereview -->&#8203;
&#8203;
created_at: 2025-04-16T04:01:16Z
author_association: NONE
diff_hunk: @@ -43,7 +43,7 @@ export const MessageListContext = createContext<MessageListContextValue>({
 	showUsername: false,
 	showColors: false,
 	username: undefined,
-	messageListRef: { current: null },
+	messageListRef: { current: undefined },
path: apps/meteor/client/components/message/list/MessageListContext.tsx
user: login: kody-ai[bot]
body: ![kody code-review](https://img.shields.io/badge/kody-code--review-312B4B?labelColor=C9BBF2) ![Error Handling](https://img.shields.io/badge/Error_Handling-FF6D00) ![high](https://img.shields.io/badge/severity_level-high-6B6B92)
```typescript
messageListRef: { current: undefined }
// Ensure null checks when accessing current
```
Consider adding a null check when accessing messageListRef.current to prevent potential runtime errors.

<sub>Talk to Kody by mentioning @kody</sub>


<sub>Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.</sub>

<!-- kody-codereview -->&#8203;
&#8203;
created_at: 2025-04-16T04:01:19Z
author_association: NONE
diff_hunk: @@ -1,32 +1,39 @@
 import type { IMessage } from '@rocket.chat/core-typings';
 import { isEditedMessage } from '@rocket.chat/core-typings';
+import { useMergedRefs } from '@rocket.chat/fuselage-hooks';
 import { useUser } from '@rocket.chat/ui-contexts';
 import { useCallback, useEffect, useRef } from 'react';
+import { throttle } from 'underscore';
 
 import { callbacks } from '../../../../../../lib/callbacks';
-import type { OverlayScrollbars } from '../../../../../components/CustomScrollbars';
+import { useSafeRefCallback } from '../../../../../hooks/useSafeRefCallback';
 import { useRoom } from '../../../contexts/RoomContext';
 
 export const useLegacyThreadMessageListScrolling = (mainMessage: IMessage) => {
 	const listWrapperRef = useRef<HTMLDivElement>(null);
 	const listRef = useRef<HTMLElement>(null);
 
-	const atBottomRef = useRef(true);
+	const jumpToRef = useRef<HTMLElement>(undefined);
path: apps/meteor/client/views/room/contextualBar/Threads/hooks/useLegacyThreadMessageListScrolling.ts
user: login: kody-ai[bot]
body: ![kody code-review](https://img.shields.io/badge/kody-code--review-312B4B?labelColor=C9BBF2) ![Potential Issues](https://img.shields.io/badge/Potential_Issues-B71C1C) ![high](https://img.shields.io/badge/severity_level-high-6B6B92)
```typescript
const jumpToRef = useRef<HTMLElement | null>(null);
```
Multiple instances where refs are initialized with `undefined` instead of `null`, leading to potential type safety issues and runtime errors.

This issue appears in multiple locations:
* apps/meteor/client/views/room/contextualBar/Threads/hooks/useLegacyThreadMessageListScrolling.ts: Lines 16-16
* apps/meteor/client/views/room/body/RoomBody.tsx: Lines 84-84
* apps/meteor/client/views/room/body/RoomBodyV2.tsx: Lines 84-84
* apps/meteor/client/views/room/contextualBar/Threads/hooks/useLegacyThreadMessageListScrolling.ts: Lines 16-16
Please initialize all refs with `null` instead of `undefined` to ensure type safety and consistency across the codebase.


<sub>Talk to Kody by mentioning @kody</sub>


<sub>Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.</sub>

<!-- kody-codereview -->&#8203;
&#8203;
created_at: 2025-04-16T04:52:10Z
author_association: NONE
diff_hunk: @@ -81,7 +81,7 @@ const RoomBody = (): ReactElement => {
 		return subscribed;
 	}, [allowAnonymousRead, canPreviewChannelRoom, room, subscribed]);
 
-	const innerBoxRef = useRef<HTMLDivElement | null>(null);
+	const innerBoxRef = useRef<HTMLElement>(undefined);
path: apps/meteor/client/views/room/body/RoomBodyV2.tsx
user: login: kody-ai[bot]
body: ![kody code-review](https://img.shields.io/badge/kody-code--review-312B4B?labelColor=C9BBF2) ![Kody Rules](https://img.shields.io/badge/Kody_Rules-4527A0) ![high](https://img.shields.io/badge/severity_level-high-6B6B92)
```typescript
const innerBoxRef = useCallback((node) => {
  if (node) {
    // Perform operations with the node here
  }
}, []);
```
According to Kody rules, avoid using ref.current inside useEffect. Prefer callback refs to prevent stale references. Consider refactoring the code to use a callback ref or move the logic that uses innerBoxRef.current outside the useEffect hook.

<sub>Talk to Kody by mentioning @kody</sub>


<sub>Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.</sub>

<!-- kody-codereview -->&#8203;
&#8203;
created_at: 2025-04-16T04:52:13Z
author_association: NONE
diff_hunk: @@ -51,20 +62,78 @@ export const useLegacyThreadMessageListScrolling = (mainMessage: IMessage) => {
 		return () => {
 			callbacks.remove('streamNewMessage', `thread-scroll-${room._id}`);
 		};
-	}, [room._id, sendToBottomIfNecessary, user?._id, mainMessage._id]);
+	}, [room._id, sendToBottomIfNecessaryInner, user?._id, mainMessage._id]);
 
-	useEffect(() => {
-		const observer = new ResizeObserver(() => {
-			sendToBottomIfNecessary();
-		});
+	const listWrapperRefCallback = useSafeRefCallback(
+		useCallback((node: HTMLDivElement | null) => {
+			if (node === null) {
+				return;
+			}
 
-		if (listWrapperRef.current) observer.observe(listWrapperRef.current);
-		if (listRef.current) observer.observe(listRef.current);
+			const setScrollToBottom = () => {
+				const { scrollTop, scrollHeight, clientHeight } = node;
+				atBottomRef.current = scrollTop >= scrollHeight - clientHeight;
+			};
 
-		return () => {
-			observer.disconnect();
-		};
-	}, [sendToBottomIfNecessary]);
+			node.addEventListener('scroll', setScrollToBottom);
+			return () => {
+				node.removeEventListener('scroll', setScrollToBottom);
+			};
+		}, []),
+	);
+
+	const listRefCallback = useSafeRefCallback(
+		useCallback(
+			(node: HTMLDivElement | null) => {
+				if (node === null) {
+					return;
+				}
+
+				// Create an observer instance linked to the callback function
+				const mutation = new MutationObserver((mutationList) => {
+					for (const mutation of mutationList) {
+						if (mutation.type === 'childList') {
+							sendToBottomIfNecessary();
+						}
+					}
+				});
+				mutation.observe(node, {
+					childList: true,
+				});
+
+				return () => {
+					mutation.disconnect();
+				};
+			},
+			[sendToBottomIfNecessary],
+		),
+	);
+
+	return {
+		listWrapperRef: useMergedRefs(
+			useSafeRefCallback(
+				useCallback(
+					(node: HTMLDivElement | null) => {
+						if (node === null) {
+							return;
+						}
 
-	return { listWrapperRef, listRef, requestScrollToBottom: sendToBottomIfNecessary, onScroll };
+						const observer = new ResizeObserver(() => {
+							sendToBottomIfNecessary();
+						});
+						observer.observe(node);
path: apps/meteor/client/views/room/contextualBar/Threads/hooks/useLegacyThreadMessageListScrolling.ts
user: login: kody-ai[bot]
body: ![kody code-review](https://img.shields.io/badge/kody-code--review-312B4B?labelColor=C9BBF2) ![Kody Rules](https://img.shields.io/badge/Kody_Rules-4527A0) ![high](https://img.shields.io/badge/severity_level-high-6B6B92)
```typescript
+						const observer = new ResizeObserver(() => {
+							if (listWrapperRef.current) {
+								sendToBottomIfNecessary();
+							}
+						});
+						if (node) {
+							observer.observe(node);
+						}
```
Avoid accessing `listWrapperRef.current` directly inside the `ResizeObserver` callback within `useEffect`. This can lead to stale closures. Instead, use a callback ref or the `useSafeRefCallback` hook to ensure you're working with the latest ref value.

<sub>Talk to Kody by mentioning @kody</sub>


<sub>Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.</sub>

<!-- kody-codereview -->&#8203;
&#8203;
created_at: 2025-04-16T04:52:45Z
author_association: NONE
diff_hunk: @@ -81,7 +81,7 @@ const RoomBody = (): ReactElement => {
 		return subscribed;
 	}, [allowAnonymousRead, canPreviewChannelRoom, room, subscribed]);
 
-	const innerBoxRef = useRef<HTMLDivElement | null>(null);
+	const innerBoxRef = useRef<HTMLElement>(undefined);
path: apps/meteor/client/views/room/body/RoomBody.tsx
user: login: kody-ai[bot]
body: ![kody code-review](https://img.shields.io/badge/kody-code--review-312B4B?labelColor=C9BBF2) ![Potential Issues](https://img.shields.io/badge/Potential_Issues-B71C1C) ![high](https://img.shields.io/badge/severity_level-high-6B6B92)
```typescript
const innerBoxRef = useRef<HTMLElement>(null);
```
The use of `useRef` with `undefined` initialization instead of `null` is a recurring issue across multiple files, leading to potential type safety and reference errors.

This issue appears in multiple locations:
* apps/meteor/client/views/room/body/RoomBody.tsx: Lines 84-84
* apps/meteor/client/views/room/body/RoomBodyV2.tsx: Lines 84-84
* apps/meteor/client/views/room/body/RoomBodyV2.tsx: Lines 84-84
Please initialize `useRef` with `null` instead of `undefined` to explicitly indicate that the ref doesn't currently point to an element, ensuring safer and more predictable behavior.


<sub>Talk to Kody by mentioning @kody</sub>


<sub>Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.</sub>

<!-- kody-codereview -->&#8203;
&#8203;
created_at: 2025-04-16T05:22:25Z
author_association: NONE
diff_hunk: @@ -81,7 +81,7 @@ const RoomBody = (): ReactElement => {
 		return subscribed;
 	}, [allowAnonymousRead, canPreviewChannelRoom, room, subscribed]);
 
-	const innerBoxRef = useRef<HTMLDivElement | null>(null);
+	const innerBoxRef = useRef<HTMLElement>(undefined);
path: apps/meteor/client/views/room/body/RoomBodyV2.tsx
user: login: kody-ai[bot]
body: ![kody code-review](https://img.shields.io/badge/kody-code--review-312B4B?labelColor=C9BBF2) ![Potential Issues](https://img.shields.io/badge/Potential_Issues-B71C1C) ![high](https://img.shields.io/badge/severity_level-high-6B6B92)
```typescript
const innerBoxRef = useCallback<RefCallback<HTMLElement>>((element) => {
  innerBoxRef.current = element;
}, []);
```
The change from HTMLDivElement to HTMLElement might be intentional for broader element type support. However, initializing with undefined instead of null could lead to potential issues. It's safer to initialize with null to explicitly indicate that the ref doesn't currently point to an element, preventing undefined reference errors.  Additionally, consider using a callback ref to avoid potential stale closures inside useEffect hooks, as per Kody Rule `33134abc-005e-4396-9c0c-72163464c4d3`.

<sub>Talk to Kody by mentioning @kody</sub>


<sub>Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.</sub>

<!-- kody-codereview -->&#8203;
&#8203;
created_at: 2025-04-16T05:22:28Z
author_association: NONE
diff_hunk: @@ -1,32 +1,39 @@
 import type { IMessage } from '@rocket.chat/core-typings';
 import { isEditedMessage } from '@rocket.chat/core-typings';
+import { useMergedRefs } from '@rocket.chat/fuselage-hooks';
 import { useUser } from '@rocket.chat/ui-contexts';
 import { useCallback, useEffect, useRef } from 'react';
+import { throttle } from 'underscore';
 
 import { callbacks } from '../../../../../../lib/callbacks';
-import type { OverlayScrollbars } from '../../../../../components/CustomScrollbars';
+import { useSafeRefCallback } from '../../../../../hooks/useSafeRefCallback';
 import { useRoom } from '../../../contexts/RoomContext';
 
 export const useLegacyThreadMessageListScrolling = (mainMessage: IMessage) => {
 	const listWrapperRef = useRef<HTMLDivElement>(null);
 	const listRef = useRef<HTMLElement>(null);
 
-	const atBottomRef = useRef(true);
+	const jumpToRef = useRef<HTMLElement>(undefined);
 
-	const onScroll = useCallback(({ elements }: OverlayScrollbars) => {
-		const {
-			viewport: { scrollTop, scrollHeight, clientHeight },
-		} = elements();
-		atBottomRef.current = scrollTop >= scrollHeight - clientHeight;
-	}, []);
+	const atBottomRef = useRef(true);
+	const sendToBottomIfNecessaryInner = useCallback(
+		throttle((listWrapper: HTMLDivElement) => {
path: apps/meteor/client/views/room/contextualBar/Threads/hooks/useLegacyThreadMessageListScrolling.ts
user: login: kody-ai[bot]
body: ![kody code-review](https://img.shields.io/badge/kody-code--review-312B4B?labelColor=C9BBF2) ![Maintainability](https://img.shields.io/badge/Maintainability-0091EA) ![high](https://img.shields.io/badge/severity_level-high-6B6B92)
```typescript
const throttleDelay = 100; // Make this configurable

const sendToBottomIfNecessaryInner = useSafeRefCallback(
	useCallback(
		throttle((listWrapper: HTMLDivElement | null) => {
			if (!listWrapper) return;

			if (jumpToRef.current) {
				atBottomRef.current = false;
			}
			if (atBottomRef.current === true) {
				listWrapper.scrollTo(30, listWrapper.scrollHeight);
			}
		}, throttleDelay),
		[throttleDelay],
	),
);
```
The `sendToBottomIfNecessaryInner` function uses `throttle` with a fixed delay of 100ms. Consider making this delay configurable to allow for flexibility in different use cases.  Also, avoid using ref.current inside useEffect. Prefer callbackRef

<sub>Talk to Kody by mentioning @kody</sub>


<sub>Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.</sub>

<!-- kody-codereview -->&#8203;
&#8203;
created_at: 2025-04-16T05:22:31Z
author_association: NONE
diff_hunk: @@ -51,20 +62,78 @@ export const useLegacyThreadMessageListScrolling = (mainMessage: IMessage) => {
 		return () => {
 			callbacks.remove('streamNewMessage', `thread-scroll-${room._id}`);
 		};
-	}, [room._id, sendToBottomIfNecessary, user?._id, mainMessage._id]);
+	}, [room._id, sendToBottomIfNecessaryInner, user?._id, mainMessage._id]);
 
-	useEffect(() => {
-		const observer = new ResizeObserver(() => {
-			sendToBottomIfNecessary();
-		});
+	const listWrapperRefCallback = useSafeRefCallback(
+		useCallback((node: HTMLDivElement | null) => {
+			if (node === null) {
+				return;
+			}
 
-		if (listWrapperRef.current) observer.observe(listWrapperRef.current);
-		if (listRef.current) observer.observe(listRef.current);
+			const setScrollToBottom = () => {
+				const { scrollTop, scrollHeight, clientHeight } = node;
+				atBottomRef.current = scrollTop >= scrollHeight - clientHeight;
+			};
 
-		return () => {
-			observer.disconnect();
-		};
-	}, [sendToBottomIfNecessary]);
+			node.addEventListener('scroll', setScrollToBottom);
path: apps/meteor/client/views/room/contextualBar/Threads/hooks/useLegacyThreadMessageListScrolling.ts
user: login: kody-ai[bot]
body: ![kody code-review](https://img.shields.io/badge/kody-code--review-312B4B?labelColor=C9BBF2) ![Kody Rules](https://img.shields.io/badge/Kody_Rules-4527A0) ![medium](https://img.shields.io/badge/severity_level-medium-1A7BBE)
```typescript
const setScrollToBottom = () => {
  const { scrollTop, scrollHeight, clientHeight } = node;
  atBottomRef.current = scrollTop >= scrollHeight - clientHeight;
};

node.addEventListener('scroll', setScrollToBottom);

return () => {
  node.removeEventListener('scroll', setScrollToBottom);
};
```
The scroll event listener attached to the listWrapper node within the `listWrapperRefCallback` is not removed when the component unmounts. This can lead to memory leaks.  Ensure the event listener is removed when the component unmounts or the node is unmounted.

<sub>Talk to Kody by mentioning @kody</sub>


<sub>Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.</sub>

<!-- kody-codereview -->&#8203;
&#8203;
created_at: 2025-04-16T05:22:34Z
author_association: NONE
diff_hunk: @@ -166,12 +164,7 @@ class RoomHistoryManagerClass extends Emitter {
 			room.oldestTs = messages[messages.length - 1].ts;
 		}
 
-		const wrapper = await waitForElement('.messages-box .wrapper [data-overlayscrollbars-viewport]');
-
-		if (wrapper) {
-			previousHeight = wrapper.scrollHeight;
-			scroll = wrapper.scrollTop;
-		}
+		await waitForElement('.messages-box .wrapper [data-overlayscrollbars-viewport]');
path: apps/meteor/app/ui-utils/client/lib/RoomHistoryManager.ts
user: login: kody-ai[bot]
body: ![kody code-review](https://img.shields.io/badge/kody-code--review-312B4B?labelColor=C9BBF2) ![Error Handling](https://img.shields.io/badge/Error_Handling-FF6D00) ![high](https://img.shields.io/badge/severity_level-high-6B6B92)
```typescript
try {
  await waitForElement('.messages-box .wrapper [data-overlayscrollbars-viewport]');
} catch (error) {
  console.error('Failed to load message viewport:', error);
  throw new Error('Message viewport container not found - check DOM structure');
}
```
Missing error handling for the `waitForElement` call can lead to silent failures when the element is not found or takes too long to load.

This issue appears in multiple locations:
* apps/meteor/app/ui-utils/client/lib/RoomHistoryManager.ts: Lines 167-167
Please add error handling for the `waitForElement` call to handle cases where the element might not be found or takes too long to load.


<sub>Talk to Kody by mentioning @kody</sub>


<sub>Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.</sub>

<!-- kody-codereview -->&#8203;
&#8203;
created_at: 2025-04-16T12:51:23Z
author_association: NONE
diff_hunk: @@ -9,13 +9,18 @@ import { useSafeRefCallback } from '../../../../hooks/useSafeRefCallback';
 export const useListIsAtBottom = () => {
 	const atBottomRef = useRef(true);
 
+	const jumpToRef = useRef<HTMLElement>(undefined);
+
 	const innerBoxRef = useRef<HTMLDivElement | null>(null);
 
 	const sendToBottom = useCallback(() => {
 		innerBoxRef.current?.scrollTo({ left: 30, top: innerBoxRef.current?.scrollHeight });
 	}, []);
 
 	const sendToBottomIfNecessary = useCallback(() => {
+		if (jumpToRef.current) {
path: apps/meteor/client/views/room/body/hooks/useListIsAtBottom.ts
user: login: kody-ai[bot]
body: ![kody code-review](https://img.shields.io/badge/kody-code--review-312B4B?labelColor=C9BBF2) ![Potential Issues](https://img.shields.io/badge/Potential_Issues-B71C1C) ![high](https://img.shields.io/badge/severity_level-high-6B6B92)
```typescript
if (jumpTo) {
```
Add null check for jumpTo to prevent potential runtime errors when accessing properties.  Update the check to accommodate the callback ref approach.

<sub>Talk to Kody by mentioning @kody</sub>


<sub>Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.</sub>

<!-- kody-codereview -->&#8203;
&#8203;
created_at: 2025-04-16T12:51:26Z
author_association: NONE
diff_hunk: @@ -9,13 +9,18 @@ import { useSafeRefCallback } from '../../../../hooks/useSafeRefCallback';
 export const useListIsAtBottom = () => {
 	const atBottomRef = useRef(true);
 
+	const jumpToRef = useRef<HTMLElement>(undefined);
path: apps/meteor/client/views/room/body/hooks/useListIsAtBottom.ts
user: login: kody-ai[bot]
body: ![kody code-review](https://img.shields.io/badge/kody-code--review-312B4B?labelColor=C9BBF2) ![Code Style](https://img.shields.io/badge/Code_Style-6A1B9A) ![high](https://img.shields.io/badge/severity_level-high-6B6B92)
```typescript
const [jumpTo, setJumpTo] = useState<HTMLElement | null>(null);

// In your component's JSX:
<div ref={setJumpTo} />
```
Initialize jumpToRef with null instead of undefined for better type safety and consistency with React ref patterns.  However, due to Kody Rule `33134abc-005e-4396-9c0c-72163464c4d3`, avoid using ref.current inside useEffect. Prefer callbackRef.

<sub>Talk to Kody by mentioning @kody</sub>


<sub>Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.</sub>

<!-- kody-codereview -->&#8203;
&#8203;
created_at: 2025-04-16T12:51:29Z
author_association: NONE
diff_hunk: @@ -16,11 +16,9 @@ export function useRestoreScrollPosition() {
 
 		const store = RoomManager.getStore(roomId);
 
-		if (store?.scroll && !store.atBottom) {
+		if (store?.scroll !== undefined && !store.atBottom) {
path: apps/meteor/client/views/room/body/hooks/useRestoreScrollPosition.ts
user: login: kody-ai[bot]
body: ![kody code-review](https://img.shields.io/badge/kody-code--review-312B4B?labelColor=C9BBF2) ![Maintainability](https://img.shields.io/badge/Maintainability-0091EA) ![medium](https://img.shields.io/badge/severity_level-medium-1A7BBE)
```typescript
if (store?.scroll !== undefined && !store.atBottom) {
```
The condition `store?.scroll` is used instead of the more precise `store?.scroll !== undefined` in multiple locations, which could lead to type safety issues.

This issue appears in multiple locations:
* apps/meteor/client/views/room/body/hooks/useRestoreScrollPosition.ts: Lines 19-19
* apps/meteor/client/views/room/body/hooks/useRestoreScrollPosition.ts: Lines 19-19
Please update the condition to `store?.scroll !== undefined` to ensure type safety and logical correctness.


<sub>Talk to Kody by mentioning @kody</sub>


<sub>Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.</sub>

<!-- kody-codereview -->&#8203;
&#8203;
created_at: 2025-04-16T15:23:24Z
author_association: NONE
diff_hunk: @@ -81,7 +81,7 @@ const RoomBody = (): ReactElement => {
 		return subscribed;
 	}, [allowAnonymousRead, canPreviewChannelRoom, room, subscribed]);
 
-	const innerBoxRef = useRef<HTMLDivElement | null>(null);
+	const innerBoxRef = useRef<HTMLElement>(undefined);
path: apps/meteor/client/views/room/body/RoomBodyV2.tsx
user: login: aleksandernsilva
body: Is `innerBoxRef` still needed here? It doesn't seem to be in use besides being merged at L121.
created_at: 2025-04-16T19:11:41Z
author_association: CONTRIBUTOR

PR comments:
user: login: dionisio-bot[bot]
created_at: 2025-04-16T03:41:16Z
body: Looks like this PR is ready to merge! 🎉
If you have any trouble, please check the [PR guidelines](https://handbook.rocket.chat/departments-and-operations/research-and-development/engineering/development/pr-general-instructions-and-handling)
author_association: CONTRIBUTOR
user: login: changeset-bot[bot]
created_at: 2025-04-16T03:41:16Z
body: ###  ⚠️  No Changeset found

Latest commit: 0725e16c56255de44100235d9298f0351d5c71ae

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. **If these changes should result in a version bump, you need to add a changeset.**

<details><summary>This PR includes no changesets</summary>

  When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

</details>

[Click here to learn what changesets are, and how to add one](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add a changeset to this PR](https://github.com/RocketChat/Rocket.Chat/new/fix/thread-jump?filename=.changeset/famous-worms-decide.md&value=---%0A%22%40rocket.chat%2Fmeteor%22%3A%20patch%0A---%0A%0Arefactor%3A%20dont%20use%20useeffect%20anymore%20to%20control%20thread%20scroll%0A)


author_association: NONE
user: login: kody-ai[bot]
created_at: 2025-04-16T03:49:35Z
body: ## Code Review Completed! 🔥

The code review was successfully completed based on your current configurations.



<details>
<summary>Kody Guide: Usage and Configuration</summary>

<details>
<summary>Interacting with Kody</summary>

- **Request a Review:** Ask Kody to review your PR manually by adding a comment with the `@kody start-review` command at the root of your PR.

- **Provide Feedback:** Help Kody learn and improve by reacting to its comments with a 👍 for helpful suggestions or a 👎 if improvements are needed.

</details>

<details>
<summary>Current Kody Configuration</summary>

<details>
<summary>Review Options</summary>

The following review options are enabled or disabled:

| Options                        | Enabled |
|-------------------------------|---------|
| **Security** | ✅ |
| **Code Style** | ✅ |
| **Kody Rules** | ✅ |
| **Refactoring** | ✅ |
| **Error Handling** | ✅ |
| **Maintainability** | ✅ |
| **Potential Issues** | ✅ |
| **Documentation And Comments** | ✅ |
| **Performance And Optimization** | ✅ |
| **Breaking Changes** | ❌ |

</details>

**[Access your configuration settings here.](https://app.kodus.io/automations/AutomationCodeReview/global/general)**

</details>
</details>

<!-- kody-codereview -->
&#8203;
author_association: NONE
user: login: codecov[bot]
created_at: 2025-04-16T04:03:09Z
body: ## [Codecov](https://app.codecov.io/gh/RocketChat/Rocket.Chat/pull/35818?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=RocketChat) Report
Attention: Patch coverage is `90.90909%` with `3 lines` in your changes missing coverage. Please review.
> Project coverage is 60.95%. Comparing base [(`00b75e3`)](https://app.codecov.io/gh/RocketChat/Rocket.Chat/commit/00b75e3e1f99dda78dc5a8da12fb490867c2504c?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=RocketChat) to head [(`0725e16`)](https://app.codecov.io/gh/RocketChat/Rocket.Chat/commit/0725e16c56255de44100235d9298f0351d5c71ae?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=RocketChat).
> Report is 4 commits behind head on develop.

<details><summary>Additional details and impacted files</summary>


[![Impacted file tree graph](https://app.codecov.io/gh/RocketChat/Rocket.Chat/pull/35818/graphs/tree.svg?width=650&height=150&src=pr&token=lEAH159Fca&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=RocketChat)](https://app.codecov.io/gh/RocketChat/Rocket.Chat/pull/35818?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=RocketChat)

```diff
@@             Coverage Diff             @@
##           develop   #35818      +/-   ##
===========================================
- Coverage    60.96%   60.95%   -0.02%     
===========================================
  Files         2952     2952              
  Lines        70308    70286      -22     
  Branches     16137    16135       -2     
===========================================
- Hits         42866    42844      -22     
+ Misses       24495    24493       -2     
- Partials      2947     2949       +2     
```

| [Flag](https://app.codecov.io/gh/RocketChat/Rocket.Chat/pull/35818/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=RocketChat) | Coverage Δ | |
|---|---|---|
| [e2e](https://app.codecov.io/gh/RocketChat/Rocket.Chat/pull/35818/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=RocketChat) | `57.62% <90.90%> (-0.03%)` | :arrow_down: |
| [unit](https://app.codecov.io/gh/RocketChat/Rocket.Chat/pull/35818/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=RocketChat) | `75.49% <ø> (ø)` | |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=RocketChat#carryforward-flags-in-the-pull-request-comment) to find out more.

</details>
<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-16T04:03:18Z
body: [PR Preview Action](https://github.com/rossjrw/pr-preview-action) v1.6.1
:---:
| <p></p> :rocket: View preview at <br> https://RocketChat.github.io/Rocket.Chat/pr-preview/pr-35818/ <br><br>
| <h6>Built to branch [`gh-pages`](https://github.com/RocketChat/Rocket.Chat/tree/gh-pages) at 2025-04-16 19:36 UTC. <br> Preview will be ready when the [GitHub Pages deployment](https://github.com/RocketChat/Rocket.Chat/deployments) is complete. <br><br> </h6>
<!-- Sticky Pull Request Commentpr-preview -->
author_association: CONTRIBUTOR
user: login: kody-ai[bot]
created_at: 2025-04-16T04:35:24Z
body: ## Code Review Completed! 🔥

The code review was successfully completed based on your current configurations.



<details>
<summary>Kody Guide: Usage and Configuration</summary>

<details>
<summary>Interacting with Kody</summary>

- **Request a Review:** Ask Kody to review your PR manually by adding a comment with the `@kody start-review` command at the root of your PR.

- **Provide Feedback:** Help Kody learn and improve by reacting to its comments with a 👍 for helpful suggestions or a 👎 if improvements are needed.

</details>

<details>
<summary>Current Kody Configuration</summary>

<details>
<summary>Review Options</summary>

The following review options are enabled or disabled:

| Options                        | Enabled |
|-------------------------------|---------|
| **Security** | ✅ |
| **Code Style** | ✅ |
| **Kody Rules** | ✅ |
| **Refactoring** | ✅ |
| **Error Handling** | ✅ |
| **Maintainability** | ✅ |
| **Potential Issues** | ✅ |
| **Documentation And Comments** | ✅ |
| **Performance And Optimization** | ✅ |
| **Breaking Changes** | ❌ |

</details>

**[Access your configuration settings here.](https://app.kodus.io/automations/AutomationCodeReview/global/general)**

</details>
</details>

<!-- kody-codereview -->
&#8203;
author_association: NONE
user: login: kody-ai[bot]
created_at: 2025-04-16T05:09:02Z
body: ## Code Review Completed! 🔥

The code review was successfully completed based on your current configurations.



<details>
<summary>Kody Guide: Usage and Configuration</summary>

<details>
<summary>Interacting with Kody</summary>

- **Request a Review:** Ask Kody to review your PR manually by adding a comment with the `@kody start-review` command at the root of your PR.

- **Provide Feedback:** Help Kody learn and improve by reacting to its comments with a 👍 for helpful suggestions or a 👎 if improvements are needed.

</details>

<details>
<summary>Current Kody Configuration</summary>

<details>
<summary>Review Options</summary>

The following review options are enabled or disabled:

| Options                        | Enabled |
|-------------------------------|---------|
| **Security** | ✅ |
| **Code Style** | ✅ |
| **Kody Rules** | ✅ |
| **Refactoring** | ✅ |
| **Error Handling** | ✅ |
| **Maintainability** | ✅ |
| **Potential Issues** | ✅ |
| **Documentation And Comments** | ✅ |
| **Performance And Optimization** | ✅ |
| **Breaking Changes** | ❌ |

</details>

**[Access your configuration settings here.](https://app.kodus.io/automations/AutomationCodeReview/global/general)**

</details>
</details>

<!-- kody-codereview -->
&#8203;
author_association: NONE
user: login: kody-ai[bot]
created_at: 2025-04-16T06:39:46Z
body: # Kody Review Complete
**Great news!** 🎉
No issues were found that match your current review configurations.

Keep up the excellent work! 🚀

<details>
<summary>Kody Guide: Usage and Configuration</summary>

<details>
<summary>Interacting with Kody</summary>

- **Request a Review:** Ask Kody to review your PR manually by adding a comment with the `@kody start-review` command at the root of your PR.

- **Provide Feedback:** Help Kody learn and improve by reacting to its comments with a 👍 for helpful suggestions or a 👎 if improvements are needed.

</details>

<details>
<summary>Current Kody Configuration</summary>

<details>
<summary>Review Options</summary>

The following review options are enabled or disabled:

| Options                        | Enabled |
|-------------------------------|---------|
| **Security** | ✅ |
| **Code Style** | ✅ |
| **Kody Rules** | ✅ |
| **Refactoring** | ✅ |
| **Error Handling** | ✅ |
| **Maintainability** | ✅ |
| **Potential Issues** | ✅ |
| **Documentation And Comments** | ✅ |
| **Performance And Optimization** | ✅ |
| **Breaking Changes** | ❌ |

</details>

**[Access your configuration settings here.](https://app.kodus.io/automations/AutomationCodeReview/global/general)**

</details>
</details>

<!-- kody-codereview -->
&#8203;
author_association: NONE
user: login: kody-ai[bot]
created_at: 2025-04-16T12:50:09Z
body: ## Code Review Completed! 🔥

The code review was successfully completed based on your current configurations.



<details>
<summary>Kody Guide: Usage and Configuration</summary>

<details>
<summary>Interacting with Kody</summary>

- **Request a Review:** Ask Kody to review your PR manually by adding a comment with the `@kody start-review` command at the root of your PR.

- **Provide Feedback:** Help Kody learn and improve by reacting to its comments with a 👍 for helpful suggestions or a 👎 if improvements are needed.

</details>

<details>
<summary>Current Kody Configuration</summary>

<details>
<summary>Review Options</summary>

The following review options are enabled or disabled:

| Options                        | Enabled |
|-------------------------------|---------|
| **Security** | ✅ |
| **Code Style** | ✅ |
| **Kody Rules** | ✅ |
| **Refactoring** | ✅ |
| **Error Handling** | ✅ |
| **Maintainability** | ✅ |
| **Potential Issues** | ✅ |
| **Documentation And Comments** | ✅ |
| **Performance And Optimization** | ✅ |
| **Breaking Changes** | ❌ |

</details>

**[Access your configuration settings here.](https://app.kodus.io/automations/AutomationCodeReview/global/general)**

</details>
</details>

<!-- kody-codereview -->
&#8203;
author_association: NONE
user: login: kody-ai[bot]
created_at: 2025-04-16T13:05:15Z
body: # Kody Review Complete
**Great news!** 🎉
No issues were found that match your current review configurations.

Keep up the excellent work! 🚀

<details>
<summary>Kody Guide: Usage and Configuration</summary>

<details>
<summary>Interacting with Kody</summary>

- **Request a Review:** Ask Kody to review your PR manually by adding a comment with the `@kody start-review` command at the root of your PR.

- **Provide Feedback:** Help Kody learn and improve by reacting to its comments with a 👍 for helpful suggestions or a 👎 if improvements are needed.

</details>

<details>
<summary>Current Kody Configuration</summary>

<details>
<summary>Review Options</summary>

The following review options are enabled or disabled:

| Options                        | Enabled |
|-------------------------------|---------|
| **Security** | ✅ |
| **Code Style** | ✅ |
| **Kody Rules** | ✅ |
| **Refactoring** | ✅ |
| **Error Handling** | ✅ |
| **Maintainability** | ✅ |
| **Potential Issues** | ✅ |
| **Documentation And Comments** | ✅ |
| **Performance And Optimization** | ✅ |
| **Breaking Changes** | ❌ |

</details>

**[Access your configuration settings here.](https://app.kodus.io/automations/AutomationCodeReview/global/general)**

</details>
</details>

<!-- kody-codereview -->
&#8203;
author_association: NONE
user: login: kody-ai[bot]
created_at: 2025-04-16T13:43:08Z
body: # Kody Review Complete
**Great news!** 🎉
No issues were found that match your current review configurations.

Keep up the excellent work! 🚀

<details>
<summary>Kody Guide: Usage and Configuration</summary>

<details>
<summary>Interacting with Kody</summary>

- **Request a Review:** Ask Kody to review your PR manually by adding a comment with the `@kody start-review` command at the root of your PR.

- **Provide Feedback:** Help Kody learn and improve by reacting to its comments with a 👍 for helpful suggestions or a 👎 if improvements are needed.

</details>

<details>
<summary>Current Kody Configuration</summary>

<details>
<summary>Review Options</summary>

The following review options are enabled or disabled:

| Options                        | Enabled |
|-------------------------------|---------|
| **Security** | ✅ |
| **Code Style** | ✅ |
| **Kody Rules** | ✅ |
| **Refactoring** | ✅ |
| **Error Handling** | ✅ |
| **Maintainability** | ✅ |
| **Potential Issues** | ✅ |
| **Documentation And Comments** | ✅ |
| **Performance And Optimization** | ✅ |
| **Breaking Changes** | ❌ |

</details>

**[Access your configuration settings here.](https://app.kodus.io/automations/AutomationCodeReview/global/general)**

</details>
</details>

<!-- kody-codereview -->
&#8203;
author_association: NONE
user: login: kody-ai[bot]
created_at: 2025-04-16T13:52:02Z
body: ## Code Review Completed! 🔥

The code review was successfully completed based on your current configurations.



<details>
<summary>Kody Guide: Usage and Configuration</summary>

<details>
<summary>Interacting with Kody</summary>

- **Request a Review:** Ask Kody to review your PR manually by adding a comment with the `@kody start-review` command at the root of your PR.

- **Provide Feedback:** Help Kody learn and improve by reacting to its comments with a 👍 for helpful suggestions or a 👎 if improvements are needed.

</details>

<details>
<summary>Current Kody Configuration</summary>

<details>
<summary>Review Options</summary>

The following review options are enabled or disabled:

| Options                        | Enabled |
|-------------------------------|---------|
| **Security** | ✅ |
| **Code Style** | ✅ |
| **Kody Rules** | ✅ |
| **Refactoring** | ✅ |
| **Error Handling** | ✅ |
| **Maintainability** | ✅ |
| **Potential Issues** | ✅ |
| **Documentation And Comments** | ✅ |
| **Performance And Optimization** | ✅ |
| **Breaking Changes** | ❌ |

</details>

**[Access your configuration settings here.](https://app.kodus.io/automations/AutomationCodeReview/global/general)**

</details>
</details>

<!-- kody-codereview -->
&#8203;
author_association: NONE
user: login: kody-ai[bot]
created_at: 2025-04-16T15:22:14Z
body: ## Code Review Completed! 🔥

The code review was successfully completed based on your current configurations.



<details>
<summary>Kody Guide: Usage and Configuration</summary>

<details>
<summary>Interacting with Kody</summary>

- **Request a Review:** Ask Kody to review your PR manually by adding a comment with the `@kody start-review` command at the root of your PR.

- **Provide Feedback:** Help Kody learn and improve by reacting to its comments with a 👍 for helpful suggestions or a 👎 if improvements are needed.

</details>

<details>
<summary>Current Kody Configuration</summary>

<details>
<summary>Review Options</summary>

The following review options are enabled or disabled:

| Options                        | Enabled |
|-------------------------------|---------|
| **Security** | ✅ |
| **Code Style** | ✅ |
| **Kody Rules** | ✅ |
| **Refactoring** | ✅ |
| **Error Handling** | ✅ |
| **Maintainability** | ✅ |
| **Potential Issues** | ✅ |
| **Documentation And Comments** | ✅ |
| **Performance And Optimization** | ✅ |
| **Breaking Changes** | ❌ |

</details>

**[Access your configuration settings here.](https://app.kodus.io/automations/AutomationCodeReview/global/general)**

</details>
</details>

<!-- kody-codereview -->
&#8203;
author_association: NONE
user: login: kody-ai[bot]
created_at: 2025-04-16T17:16:47Z
body: ## Code Review Completed! 🔥

The code review was successfully completed based on your current configurations.



<details>
<summary>Kody Guide: Usage and Configuration</summary>

<details>
<summary>Interacting with Kody</summary>

- **Request a Review:** Ask Kody to review your PR manually by adding a comment with the `@kody start-review` command at the root of your PR.

- **Provide Feedback:** Help Kody learn and improve by reacting to its comments with a 👍 for helpful suggestions or a 👎 if improvements are needed.

</details>

<details>
<summary>Current Kody Configuration</summary>

<details>
<summary>Review Options</summary>

The following review options are enabled or disabled:

| Options                        | Enabled |
|-------------------------------|---------|
| **Security** | ✅ |
| **Code Style** | ✅ |
| **Kody Rules** | ✅ |
| **Refactoring** | ✅ |
| **Error Handling** | ✅ |
| **Maintainability** | ✅ |
| **Potential Issues** | ✅ |
| **Documentation And Comments** | ✅ |
| **Performance And Optimization** | ✅ |
| **Breaking Changes** | ❌ |

</details>

**[Access your configuration settings here.](https://app.kodus.io/automations/AutomationCodeReview/global/general)**

</details>
</details>

<!-- kody-codereview -->
&#8203;
author_association: NONE
user: login: kody-ai[bot]
created_at: 2025-04-16T19:23:46Z
body: # Kody Review Complete
**Great news!** 🎉
No issues were found that match your current review configurations.

Keep up the excellent work! 🚀

<details>
<summary>Kody Guide: Usage and Configuration</summary>

<details>
<summary>Interacting with Kody</summary>

- **Request a Review:** Ask Kody to review your PR manually by adding a comment with the `@kody start-review` command at the root of your PR.

- **Provide Feedback:** Help Kody learn and improve by reacting to its comments with a 👍 for helpful suggestions or a 👎 if improvements are needed.

</details>

<details>
<summary>Current Kody Configuration</summary>

<details>
<summary>Review Options</summary>

The following review options are enabled or disabled:

| Options                        | Enabled |
|-------------------------------|---------|
| **Security** | ✅ |
| **Code Style** | ✅ |
| **Kody Rules** | ✅ |
| **Refactoring** | ✅ |
| **Error Handling** | ✅ |
| **Maintainability** | ✅ |
| **Potential Issues** | ✅ |
| **Documentation And Comments** | ✅ |
| **Performance And Optimization** | ✅ |
| **Breaking Changes** | ❌ |

</details>

**[Access your configuration settings here.](https://app.kodus.io/automations/AutomationCodeReview/global/general)**

</details>
</details>

<!-- kody-codereview -->
&#8203;
author_association: NONE
user: login: juliajforesti
created_at: 2025-04-16T22:04:05Z
body: tested and looks all good
author_association: CONTRIBUTOR