Add multiple fixes

This commit is contained in:
Jared Miller 2026-01-31 10:14:25 -05:00
parent 0ac5eec30d
commit 116847ab58
Signed by: shmup
GPG key ID: 22B5C6D66A38B06C

View file

@ -220,6 +220,13 @@
align-items: center; align-items: center;
justify-content: center; justify-content: center;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
opacity: 0;
pointer-events: none;
}
.session-output.scrolled-up .scroll-to-bottom-btn {
opacity: 1;
pointer-events: auto;
} }
.scroll-to-bottom-btn:hover { .scroll-to-bottom-btn:hover {
@ -235,9 +242,10 @@
.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);
line-height: 1.5; line-height: 1.3;
white-space: var(--wrap-mode); white-space: var(--wrap-mode);
word-wrap: break-word; word-wrap: normal;
overflow-wrap: normal;
color: #e0e0e0; color: #e0e0e0;
} }
@ -1012,7 +1020,7 @@
columns: 1, // 1, 2, 3, or 'fit' columns: 1, // 1, 2, 3, or 'fit'
fontSize: 12, // px value fontSize: 12, // px value
terminalHeight: 400, // px value terminalHeight: 400, // px value
wrapText: true, // true = pre-wrap, false = pre (horizontal scroll) wrapText: false, // true = pre-wrap, false = pre (horizontal scroll)
}, },
}; };
@ -1043,26 +1051,30 @@
} }
const sessions = await res.json(); const sessions = await res.json();
sessions.forEach(session => { sessions.forEach(session => {
state.sessions.set(session.id, { const existing = state.sessions.get(session.id);
const merged = {
id: session.id, id: session.id,
cwd: session.cwd, cwd: session.cwd,
command: session.command, command: session.command,
output: '', // Preserve any output already received via SSE before fetch completed
outputRenderedLength: 0, output: existing?.output ?? '',
expanded: false, outputRenderedLength: existing?.outputRenderedLength ?? 0,
state: 'ready', // Preserve expansion state
prompts: 0, expanded: existing?.expanded ?? false,
completions: 0, state: existing?.state ?? 'ready',
tools: 0, prompts: existing?.prompts ?? 0,
compressions: 0, completions: existing?.completions ?? 0,
thinking_seconds: 0, tools: existing?.tools ?? 0,
work_seconds: 0, compressions: existing?.compressions ?? 0,
mode: 'normal', thinking_seconds: existing?.thinking_seconds ?? 0,
model: null, work_seconds: existing?.work_seconds ?? 0,
idle_since: null, mode: existing?.mode ?? 'normal',
git_branch: null, model: existing?.model ?? null,
git_files_json: null, idle_since: existing?.idle_since ?? null,
}); git_branch: existing?.git_branch ?? null,
git_files_json: existing?.git_files_json ?? null,
};
state.sessions.set(session.id, merged);
}); });
renderSessions(); renderSessions();
} catch (error) { } catch (error) {
@ -1135,13 +1147,36 @@
es.addEventListener('initial_state', (e) => { es.addEventListener('initial_state', (e) => {
const data = JSON.parse(e.data); const data = JSON.parse(e.data);
const session = state.sessions.get(data.session_id); let session = state.sessions.get(data.session_id);
if (session) { if (!session) {
// Create a placeholder session so we don't drop the state
session = {
id: data.session_id,
cwd: '',
command: 'claude',
output: '',
outputRenderedLength: 0,
expanded: false,
state: 'ready',
prompts: 0,
completions: 0,
tools: 0,
compressions: 0,
thinking_seconds: 0,
work_seconds: 0,
mode: 'normal',
model: null,
idle_since: null,
git_branch: null,
git_files_json: null,
};
state.sessions.set(data.session_id, session);
renderSessions();
}
// Replace output with current terminal state // Replace output with current terminal state
session.output = data.html; session.output = data.html;
session.outputRenderedLength = 0; // Force re-render session.outputRenderedLength = 0; // Force re-render
renderSessionOutput(data.session_id); renderSessionOutput(data.session_id);
}
}); });
es.addEventListener('output', (e) => { es.addEventListener('output', (e) => {
@ -1226,6 +1261,10 @@
// Wait for DOM to update before measuring // Wait for DOM to update before measuring
setTimeout(() => { setTimeout(() => {
handleSessionResize(Number(sessionId)); handleSessionResize(Number(sessionId));
// After mount, ensure we start at bottom and initialize UI state
scrollToBottom(Number(sessionId));
updateScrollButton(Number(sessionId));
initSessionOutputUI(Number(sessionId));
}, 0); }, 0);
} }
} }
@ -1426,6 +1465,12 @@
</div> </div>
</div> </div>
`).join(''); `).join('');
// Initialize scroll handlers and button visibility
sessionsToRender.forEach(s => {
initSessionOutputUI(s.id);
updateScrollButton(s.id);
});
} }
function renderSessionOutput(sessionId) { function renderSessionOutput(sessionId) {
@ -1434,17 +1479,47 @@
const $output = document.getElementById(`output-${sessionId}`); const $output = document.getElementById(`output-${sessionId}`);
if ($output) { if ($output) {
// Always full replace since serializeAsHTML() returns full screen state const $outputContainer = document.getElementById(`session-output-${sessionId}`);
const shouldStickToBottom =
!!($outputContainer && session.expanded && isAtBottom($outputContainer));
// Only mutate DOM when expanded to reduce work
if (session.expanded) {
$output.innerHTML = session.output; $output.innerHTML = session.output;
}
session.outputRenderedLength = session.output.length; session.outputRenderedLength = session.output.length;
const $outputContainer = document.getElementById(`session-output-${sessionId}`); if ($outputContainer && session.expanded && shouldStickToBottom) {
if ($outputContainer && session.expanded) {
$outputContainer.scrollTop = $outputContainer.scrollHeight; $outputContainer.scrollTop = $outputContainer.scrollHeight;
} }
// Update scroll-to-bottom button visibility
updateScrollButton(sessionId);
} }
} }
function isAtBottom(el, threshold = 8) {
return el.scrollTop + el.clientHeight >= el.scrollHeight - threshold;
}
function updateScrollButton(sessionId) {
const $outputContainer = document.getElementById(`session-output-${sessionId}`);
if (!$outputContainer) return;
const nearBottom = isAtBottom($outputContainer);
if (nearBottom) {
$outputContainer.classList.remove('scrolled-up');
} else {
$outputContainer.classList.add('scrolled-up');
}
}
function initSessionOutputUI(sessionId) {
const $outputContainer = document.getElementById(`session-output-${sessionId}`);
if (!$outputContainer) return;
// Attach scroll listener to toggle the button visibility
$outputContainer.addEventListener('scroll', () => updateScrollButton(sessionId));
}
function updateSessionCard(sessionId) { function updateSessionCard(sessionId) {
const session = state.sessions.get(sessionId); const session = state.sessions.get(sessionId);
if (!session) return; if (!session) return;