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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/frontend/web/von_interface/static/js/chatTab.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand Down
39 changes: 38 additions & 1 deletion src/frontend/web/von_interface/static/js/test/chatTab.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12904,7 +12904,7 @@ describe('scroll to latest message affordance', () => {
<div class="chat-session-tabs-row">
<div id="chatSessionTabs"></div>
</div>
<div id="scrollableField"></div><div class="chat-composer"></div>
<div id="scrollableField"><div class="message-container">Existing message</div></div><div class="chat-composer"></div>
</div>
</div>
`;
Expand Down Expand Up @@ -12933,6 +12933,43 @@ describe('scroll to latest message affordance', () => {
expect(button.getAttribute('aria-hidden')).toBe('true');
});

test.each([
['truly empty', ''],
['loading', '<div class="chat-session-loading">Loading conversations…</div>']
])('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 = '<div class="chat-session-loading">Loading older messages…</div>';
__testOnly_updateScrollToEndButtonVisibility(scrollableField);

expect(document.getElementById('chatScrollToEndButton')).toBeNull();
});

test('clicking floating control scrolls to the latest message', () => {
jest.useFakeTimers();

Expand Down
Loading