diff --git a/app/static/js/console.js b/app/static/js/console.js index a12ef47..35025cc 100644 --- a/app/static/js/console.js +++ b/app/static/js/console.js @@ -22,6 +22,7 @@ document.addEventListener('DOMContentLoaded', async function() { setupInputHandlers(); setupHistoryDropdown(); setupClearOutputButton(); + setupScrollToBottom(); }); /** @@ -58,7 +59,8 @@ function connectWebSocket() { isConnected = false; updateStatus('disconnected'); enableInput(false); - addMessage('Disconnected', 'error'); + // Transient session event — show inline but don't persist to transcript + addMessage('Disconnected', 'error', false); // Clear pending command indicator if (pendingCommandDiv) { @@ -102,7 +104,7 @@ function connectWebSocket() { } catch (error) { console.error('Failed to create WebSocket connection:', error); updateStatus('disconnected'); - addMessage('Failed to connect: ' + error.message, 'error'); + addMessage('Failed to connect: ' + error.message, 'error', false); } } @@ -456,6 +458,8 @@ async function loadOutputHistory() { const divider = document.createElement('hr'); divider.className = 'history-divider'; container.appendChild(divider); + // Open at the bottom of the transcript, like a chat window + scrollToBottom(); } catch (error) { console.error('Failed to load output history:', error); } @@ -502,3 +506,28 @@ function setupClearOutputButton() { clearOutputHistory(); }); } + +/** + * Wire the floating scroll-to-bottom button: visible when the user has + * scrolled away from the bottom of the transcript. + */ +function setupScrollToBottom() { + const container = document.getElementById('consoleMessages'); + const btn = document.getElementById('scrollBottomBtn'); + if (!container || !btn) return; + + const SHOW_THRESHOLD = 80; // px from bottom before button appears + + const update = () => { + const distance = container.scrollHeight - container.scrollTop - container.clientHeight; + btn.classList.toggle('show', distance > SHOW_THRESHOLD); + }; + + container.addEventListener('scroll', update, { passive: true }); + // Re-check after content changes (new messages, dropdowns, etc.) + new MutationObserver(update).observe(container, { childList: true, subtree: false }); + btn.addEventListener('click', (e) => { + e.preventDefault(); + container.scrollTop = container.scrollHeight; + }); +} diff --git a/app/templates/console.html b/app/templates/console.html index a7ea2c0..ab0339a 100644 --- a/app/templates/console.html +++ b/app/templates/console.html @@ -44,6 +44,13 @@ font-weight: 500; } + .messages-wrap { + flex: 1; + min-height: 0; + position: relative; + display: flex; + } + .console-messages { flex: 1; overflow-y: auto; @@ -52,6 +59,34 @@ min-height: 0; } + .scroll-bottom-btn { + position: absolute; + right: 1rem; + bottom: 1rem; + width: 36px; + height: 36px; + border-radius: 50%; + border: 1px solid #0f3460; + background-color: #16213e; + color: #4ecdc4; + display: none; + align-items: center; + justify-content: center; + padding: 0; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4); + cursor: pointer; + z-index: 5; + } + + .scroll-bottom-btn.show { + display: flex; + } + + .scroll-bottom-btn:hover { + background-color: #1a1a4e; + color: #00ff88; + } + .console-message { margin-bottom: 1rem; font-family: 'Courier New', Consolas, monospace; @@ -277,8 +312,13 @@ -