Add auto-scroll control (pin/unpin) to session output

This commit is contained in:
Jared Miller 2026-01-28 15:14:09 -05:00
parent 68dce06e47
commit 737948c820
Signed by: shmup
GPG key ID: 22B5C6D66A38B06C

View file

@ -194,12 +194,45 @@
border-top: 1px solid #3a3a3a; border-top: 1px solid #3a3a3a;
max-height: var(--terminal-height); max-height: var(--terminal-height);
overflow-y: auto; overflow-y: auto;
position: relative;
} }
.session.expanded .session-output { .session.expanded .session-output {
display: block; display: block;
} }
.pin-scroll-btn {
position: absolute;
bottom: 12px;
right: 12px;
background: rgba(42, 42, 42, 0.9);
border: 1px solid #3a3a3a;
border-radius: 6px;
padding: 8px 12px;
font-size: 16px;
cursor: pointer;
transition: background 0.2s, opacity 0.2s;
z-index: 10;
min-width: 44px;
min-height: 44px;
display: flex;
align-items: center;
justify-content: center;
}
.pin-scroll-btn:hover {
background: rgba(58, 58, 58, 0.95);
}
.pin-scroll-btn:active {
opacity: 0.7;
}
.pin-scroll-btn.pinned {
background: rgba(255, 152, 0, 0.2);
border-color: #ff9800;
}
.terminal { .terminal {
font-family: "SF Mono", Monaco, "Cascadia Code", "Courier New", monospace; font-family: "SF Mono", Monaco, "Cascadia Code", "Courier New", monospace;
font-size: var(--font-size); font-size: var(--font-size);
@ -1009,6 +1042,7 @@
idle_since: null, idle_since: null,
git_branch: null, git_branch: null,
git_files_json: null, git_files_json: null,
autoScroll: true,
}); });
renderSessions(); renderSessions();
}); });
@ -1280,11 +1314,24 @@
<div class="session-toggle"></div> <div class="session-toggle"></div>
</div> </div>
${renderStatsWidget(s)} ${renderStatsWidget(s)}
<div class="session-output"> <div class="session-output" id="session-output-${s.id}">
<button class="pin-scroll-btn ${s.autoScroll ? 'pinned' : ''}"
onclick="toggleAutoScroll(${s.id}); event.stopPropagation();"
title="${s.autoScroll ? 'Auto-scroll enabled' : 'Auto-scroll disabled'}">
${s.autoScroll ? '📌' : '📍'}
</button>
<div class="terminal" id="output-${s.id}">${s.output}</div> <div class="terminal" id="output-${s.id}">${s.output}</div>
</div> </div>
</div> </div>
`).join(''); `).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) { function renderSessionOutput(sessionId) {
@ -1299,7 +1346,8 @@
$output.innerHTML += newChunk; $output.innerHTML += newChunk;
session.outputRenderedLength = session.output.length; session.outputRenderedLength = session.output.length;
} }
if (session.expanded) { // Only auto-scroll if session is expanded and autoScroll is enabled
if (session.expanded && session.autoScroll) {
$output.parentElement.scrollTop = $output.parentElement.scrollHeight; $output.parentElement.scrollTop = $output.parentElement.scrollHeight;
} }
} }
@ -1915,6 +1963,68 @@
}).join(''); }).join('');
} }
// Auto-scroll control
const scrollListeners = new Map();
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;
updatePinButton(sessionId);
}
};
$outputContainer.addEventListener('scroll', listener);
scrollListeners.set(sessionId, listener);
}
window.toggleAutoScroll = (sessionId) => {
const session = state.sessions.get(sessionId);
if (!session) return;
session.autoScroll = !session.autoScroll;
// If enabling auto-scroll, scroll to bottom immediately
if (session.autoScroll) {
const $outputContainer = document.getElementById(`session-output-${sessionId}`);
if ($outputContainer) {
$outputContainer.scrollTop = $outputContainer.scrollHeight;
}
}
updatePinButton(sessionId);
};
function updatePinButton(sessionId) {
const session = state.sessions.get(sessionId);
if (!session) return;
const $btn = document.querySelector(`#session-output-${sessionId} .pin-scroll-btn`);
if ($btn) {
if (session.autoScroll) {
$btn.classList.add('pinned');
$btn.textContent = '📌';
$btn.title = 'Auto-scroll enabled';
} else {
$btn.classList.remove('pinned');
$btn.textContent = '📍';
$btn.title = 'Auto-scroll disabled';
}
}
}
// Initialize // Initialize
loadSettings(); loadSettings();
applySettings(); applySettings();