diff --git a/src/lib/components/chat/Chat.svelte b/src/lib/components/chat/Chat.svelte index 49e59dd181..751c49bed9 100644 --- a/src/lib/components/chat/Chat.svelte +++ b/src/lib/components/chat/Chat.svelte @@ -1463,6 +1463,29 @@ top: messagesContainerElement.scrollHeight, behavior }); + + // content-visibility: auto causes the initial scrollHeight to be based on + // estimated sizes (contain-intrinsic-size). After we scroll, previously + // off-screen messages become visible and the browser resolves their actual + // heights, which shifts scrollHeight. Re-layouts can cascade across frames + // (new sizes reveal more content, triggering further size resolution), so + // we re-scroll across two animation frames to land at the true bottom. + requestAnimationFrame(() => { + if (messagesContainerElement) { + messagesContainerElement.scrollTo({ + top: messagesContainerElement.scrollHeight, + behavior + }); + requestAnimationFrame(() => { + if (messagesContainerElement) { + messagesContainerElement.scrollTo({ + top: messagesContainerElement.scrollHeight, + behavior + }); + } + }); + } + }); } }; diff --git a/src/lib/components/chat/Messages.svelte b/src/lib/components/chat/Messages.svelte index ea6c89034d..d525e5a8a4 100644 --- a/src/lib/components/chat/Messages.svelte +++ b/src/lib/components/chat/Messages.svelte @@ -140,7 +140,16 @@ const scrollToBottom = () => { const element = document.getElementById('messages-container'); - element.scrollTop = element.scrollHeight; + if (element) { + element.scrollTop = element.scrollHeight; + + // Follow-up scroll to account for content-visibility: auto re-layouts + requestAnimationFrame(() => { + if (element) { + element.scrollTop = element.scrollHeight; + } + }); + } }; export const scrollToTop = async () => {