diff --git a/src/frontend/web/von_interface/static/js/chatTab.js b/src/frontend/web/von_interface/static/js/chatTab.js
index 2da77749..47c3c699 100644
--- a/src/frontend/web/von_interface/static/js/chatTab.js
+++ b/src/frontend/web/von_interface/static/js/chatTab.js
@@ -30957,6 +30957,17 @@ function ensureScrollToEndButton(scrollableField = null) {
return null;
}
+ // A loading/empty transcript may still inherit page overflow from the
+ // surrounding composer and navigation. Do not create a latest-message
+ // affordance until there is an actual transcript item to navigate to.
+ const hasTranscriptContent = targetField.querySelector(
+ ':scope > .message-container, :scope > .chat-message'
+ );
+ if (!hasTranscriptContent) {
+ document.getElementById(CHAT_SCROLL_TO_END_BUTTON_ID)?.remove();
+ return null;
+ }
+
const wrapper = targetField.closest('.content-wrapper') || targetField.parentElement;
let controlHost = wrapper?.querySelector('.chat-composer') || wrapper;
if (!isCompactComposer()) {
diff --git a/src/frontend/web/von_interface/static/js/test/chatTab.test.js b/src/frontend/web/von_interface/static/js/test/chatTab.test.js
index 553d8fa0..f325143b 100644
--- a/src/frontend/web/von_interface/static/js/test/chatTab.test.js
+++ b/src/frontend/web/von_interface/static/js/test/chatTab.test.js
@@ -12904,7 +12904,7 @@ describe('scroll to latest message affordance', () => {
-
+
`;
@@ -12933,6 +12933,43 @@ describe('scroll to latest message affordance', () => {
expect(button.getAttribute('aria-hidden')).toBe('true');
});
+ test.each([
+ ['truly empty', ''],
+ ['loading', 'Loading conversations…
']
+ ])('does not render the latest control for an %s conversation', (_label, content) => {
+ const scrollableField = document.getElementById('scrollableField');
+ scrollableField.innerHTML = content;
+
+ __testOnly_updateScrollToEndButtonVisibility(scrollableField);
+
+ expect(document.getElementById('chatScrollToEndButton')).toBeNull();
+ });
+
+ test('renders the control after a streamed message arrives in an empty conversation', () => {
+ const scrollableField = document.getElementById('scrollableField');
+ scrollableField.innerHTML = '';
+ __testOnly_updateScrollToEndButtonVisibility(scrollableField);
+ expect(document.getElementById('chatScrollToEndButton')).toBeNull();
+
+ const message = document.createElement('div');
+ message.className = 'chat-message assistant-message';
+ scrollableField.appendChild(message);
+ __testOnly_updateScrollToEndButtonVisibility(scrollableField);
+
+ expect(document.getElementById('chatScrollToEndButton')).not.toBeNull();
+ });
+
+ test('removes a stale control when pagination or conversation replacement leaves no messages', () => {
+ const scrollableField = document.getElementById('scrollableField');
+ __testOnly_updateScrollToEndButtonVisibility(scrollableField);
+ expect(document.getElementById('chatScrollToEndButton')).not.toBeNull();
+
+ scrollableField.innerHTML = 'Loading older messages…
';
+ __testOnly_updateScrollToEndButtonVisibility(scrollableField);
+
+ expect(document.getElementById('chatScrollToEndButton')).toBeNull();
+ });
+
test('clicking floating control scrolls to the latest message', () => {
jest.useFakeTimers();