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;
justify-content: center;
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 {
@ -235,9 +242,10 @@
.terminal {
font-family: "SF Mono", Monaco, "Cascadia Code", "Courier New", monospace;
font-size: var(--font-size);
line-height: 1.5;
line-height: 1.3;
white-space: var(--wrap-mode);
word-wrap: break-word;
word-wrap: normal;
overflow-wrap: normal;
color: #e0e0e0;
}
@ -1012,7 +1020,7 @@
columns: 1, // 1, 2, 3, or 'fit'
fontSize: 12, // 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();
sessions.forEach(session => {
state.sessions.set(session.id, {
const existing = state.sessions.get(session.id);
const merged = {
id: session.id,
cwd: session.cwd,
command: session.command,
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,
});
// Preserve any output already received via SSE before fetch completed
output: existing?.output ?? '',
outputRenderedLength: existing?.outputRenderedLength ?? 0,
// Preserve expansion state
expanded: existing?.expanded ?? false,
state: existing?.state ?? 'ready',
prompts: existing?.prompts ?? 0,
completions: existing?.completions ?? 0,
tools: existing?.tools ?? 0,
compressions: existing?.compressions ?? 0,
thinking_seconds: existing?.thinking_seconds ?? 0,
work_seconds: existing?.work_seconds ?? 0,
mode: existing?.mode ?? 'normal',
model: existing?.model ?? 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();
} catch (error) {
@ -1135,13 +1147,36 @@
es.addEventListener('initial_state', (e) => {
const data = JSON.parse(e.data);
const session = state.sessions.get(data.session_id);
if (session) {
// Replace output with current terminal state
session.output = data.html;
session.outputRenderedLength = 0; // Force re-render
renderSessionOutput(data.session_id);
let session = state.sessions.get(data.session_id);
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
session.output = data.html;
session.outputRenderedLength = 0; // Force re-render
renderSessionOutput(data.session_id);
});
es.addEventListener('output', (e) => {
@ -1226,6 +1261,10 @@
// Wait for DOM to update before measuring
setTimeout(() => {
handleSessionResize(Number(sessionId));
// After mount, ensure we start at bottom and initialize UI state
scrollToBottom(Number(sessionId));
updateScrollButton(Number(sessionId));
initSessionOutputUI(Number(sessionId));
}, 0);
}
}
@ -1426,6 +1465,12 @@
</div>
</div>
`).join('');
// Initialize scroll handlers and button visibility
sessionsToRender.forEach(s => {
initSessionOutputUI(s.id);
updateScrollButton(s.id);
});
}
function renderSessionOutput(sessionId) {
@ -1434,17 +1479,47 @@
const $output = document.getElementById(`output-${sessionId}`);
if ($output) {
// Always full replace since serializeAsHTML() returns full screen state
$output.innerHTML = session.output;
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;
}
session.outputRenderedLength = session.output.length;
const $outputContainer = document.getElementById(`session-output-${sessionId}`);
if ($outputContainer && session.expanded) {
if ($outputContainer && session.expanded && shouldStickToBottom) {
$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) {
const session = state.sessions.get(sessionId);
if (!session) return;