RocketChat / RocketChat/Rocket.Chat
Composer auto-grow: scroll-to-bottom check uses strict equality, breaks on fractional scrollTop (high-DPI/zoom)
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 46.1k
- Forks
- 13.9k
- Avg merge
- 3d 3h
- Merged PRs (30d)
- 130
Description
Description:
The message composer's auto-grow logic decides whether to keep the caret pinned to the bottom using shouldScrollToBottom in apps/meteor/client/views/room/composer/RoomComposer/hooks/useAutoGrow.ts:
function shouldScrollToBottom(textarea: HTMLTextAreaElement) {
const isCursorAtBottom = textarea.selectionEnd === textarea.value.length;
const isScrolledToBottom = textarea.scrollTop + textarea.clientHeight === textarea.scrollHeight;
return isCursorAtBottom || isScrolledToBottom;
}
The isScrolledToBottom check uses strict equality (===) against scrollTop + clientHeight === scrollHeight. scrollTop is a fractional value on high-DPI displays and at non-100% browser zoom, so scrollTop + clientHeight frequently does not exactly equal scrollHeight even when the textarea is visually scrolled all the way down. When that happens isScrolledToBottom is wrongly false.
If the caret is also not at the very end of the text (isCursorAtBottom === false — e.g. the user clicked into the middle of a long draft, or an autocomplete/emoji insert moved it), shouldScrollToBottom returns false and useAutoGrow's resize() skips the node.scrollTop = node.scrollHeight correction — so as the textarea grows, the caret/last line can drift out of view.
The widely-used "scrolled to bottom" idiom accounts for the fractional pixel with a small tolerance:
const isScrolledToBottom = textarea.scrollTop + textarea.clientHeight >= textarea.scrollHeight - 1;
Steps to reproduce:
- Use a high-DPI display or set browser zoom to e.g. 110%/125% (so
scrollTopbecomes fractional). - Open any channel and type a multi-line message until the composer textarea grows a scrollbar.
- Place the caret somewhere before the end of the text (so
selectionEnd !== value.length) while the textarea is scrolled to the bottom. - Keep typing so the textarea keeps growing.
Expected behavior:
While the textarea is scrolled to the bottom, it stays pinned to the bottom (the caret/last line remains visible) as it grows.
Actual behavior:
Because scrollTop + clientHeight === scrollHeight is false for fractional scrollTop, the auto-grow scroll correction is skipped and the content can drift so the caret/last line is no longer visible.
Server Setup Information:
- Version of Rocket.Chat Server:
develop(client-side bug, server-version independent)
Client Setup Information:
- Browser: any (Chrome/Edge/Firefox) on a high-DPI display or with browser zoom ≠ 100%
- Rocket.Chat web client, message composer (
MessageBox→useAutoGrow)
Additional context
- Component path:
apps/meteor/client/views/room/composer/messageBox/MessageBox.tsx(line ~271) →useAutoGrow→shouldScrollToBottom. - Fix is a one-line tolerance change in the scroll comparison.
- I'd like to work on this and will open a PR with a regression test.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in apps/meteor/client/views/room/composer/RoomComposer/hooks/useAutoGrow.ts, following shouldScrollToBottom and resize; use MessageBox.tsx as the entry point. Add the reported fractional-scroll regression coverage and verify that a textarea already at the bottom remains pinned as it grows when the caret is not at the end.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- frontend
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 88/100