Keep terminal scrolled to bottom

This commit is contained in:
Jared Miller 2026-01-30 13:55:26 -05:00
parent 9e8a275831
commit 5597502a8c
Signed by: shmup
GPG key ID: 22B5C6D66A38B06C

View file

@ -38,6 +38,7 @@
h1 {
font-size: 20px;
font-weight: 600;
cursor: pointer;
}
.status {
@ -230,11 +231,6 @@
transform: translateY(0);
}
.scroll-to-bottom-btn.hidden {
opacity: 0;
pointer-events: none;
}
.terminal {
font-family: "SF Mono", Monaco, "Cascadia Code", "Courier New", monospace;
font-size: var(--font-size);
@ -1032,7 +1028,6 @@
idle_since: null,
git_branch: null,
git_files_json: null,
autoScroll: true,
});
});
renderSessions();
@ -1088,10 +1083,6 @@
idle_since: null,
git_branch: null,
git_files_json: null,
// Auto-scroll state is per-session and ephemeral - not persisted to localStorage.
// This is intentional: users may want different auto-scroll settings for different
// sessions, and persisting would require tracking state by session ID which is complex.
autoScroll: true,
});
renderSessions();
});
@ -1371,7 +1362,7 @@
</div>
${renderStatsWidget(s)}
<div class="session-output" id="session-output-${s.id}">
<button class="scroll-to-bottom-btn ${s.autoScroll ? 'hidden' : ''}"
<button class="scroll-to-bottom-btn"
onclick="scrollToBottom(${s.id}); event.stopPropagation();"
title="Scroll to bottom">
@ -1381,13 +1372,6 @@
</div>
`).join('');
// Attach scroll listeners after rendering
sessionsToRender.forEach(s => {
const $output = document.getElementById(`session-output-${s.id}`);
if ($output) {
attachScrollListener(s.id, $output);
}
});
}
function renderSessionOutput(sessionId) {
@ -1404,14 +1388,9 @@
}
const $outputContainer = document.getElementById(`session-output-${sessionId}`);
if ($outputContainer) {
// Only auto-scroll if session is expanded and autoScroll is enabled
if (session.expanded && session.autoScroll) {
if ($outputContainer && session.expanded) {
$outputContainer.scrollTop = $outputContainer.scrollHeight;
}
// Re-attach scroll listener after DOM update
attachScrollListener(sessionId, $outputContainer);
}
}
}
@ -2029,9 +2008,6 @@
}).join('');
}
// Auto-scroll control
const scrollListeners = new Map();
// Viewport-based PTY resize
let resizeDebounceTimer = null;
@ -2100,59 +2076,13 @@
}, 300); // Debounce for 300ms
}
function attachScrollListener(sessionId, $outputContainer) {
// Remove existing listener if present
const existing = scrollListeners.get(sessionId);
if (existing) {
$outputContainer.removeEventListener('scroll', existing);
}
const listener = () => {
const session = state.sessions.get(sessionId);
if (!session) return;
const { scrollTop, clientHeight, scrollHeight } = $outputContainer;
const isAtBottom = scrollTop + clientHeight >= scrollHeight - 50;
if (session.autoScroll !== isAtBottom) {
session.autoScroll = isAtBottom;
updateScrollButton(sessionId);
}
};
$outputContainer.addEventListener('scroll', listener);
scrollListeners.set(sessionId, listener);
}
window.scrollToBottom = (sessionId) => {
const session = state.sessions.get(sessionId);
if (!session) return;
// Scroll to bottom
const $outputContainer = document.getElementById(`session-output-${sessionId}`);
if ($outputContainer) {
$outputContainer.scrollTop = $outputContainer.scrollHeight;
}
// Re-enable auto-scroll
session.autoScroll = true;
updateScrollButton(sessionId);
};
function updateScrollButton(sessionId) {
const session = state.sessions.get(sessionId);
if (!session) return;
const $btn = document.querySelector(`#session-output-${sessionId} .scroll-to-bottom-btn`);
if ($btn) {
if (session.autoScroll) {
$btn.classList.add('hidden');
} else {
$btn.classList.remove('hidden');
}
}
}
// Initialize
loadSettings();
applySettings();