diff --git a/public/index.html b/public/index.html
index 4b706a9..f213924 100644
--- a/public/index.html
+++ b/public/index.html
@@ -194,12 +194,45 @@
border-top: 1px solid #3a3a3a;
max-height: var(--terminal-height);
overflow-y: auto;
+ position: relative;
}
.session.expanded .session-output {
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 {
font-family: "SF Mono", Monaco, "Cascadia Code", "Courier New", monospace;
font-size: var(--font-size);
@@ -1009,6 +1042,7 @@
idle_since: null,
git_branch: null,
git_files_json: null,
+ autoScroll: true,
});
renderSessions();
});
@@ -1280,11 +1314,24 @@
▼
${renderStatsWidget(s)}
-
+
+
${s.output}
`).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) {
@@ -1299,7 +1346,8 @@
$output.innerHTML += newChunk;
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;
}
}
@@ -1915,6 +1963,68 @@
}).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
loadSettings();
applySettings();