Fix dashboard to handle terminal serialization output

The server's serializeAsHTML() returns the full terminal screen state, not incremental chunks. Updated the dashboard to:

1. Handle initial_state event to receive current terminal state on connection
2. Replace output instead of appending (output event now replaces session.output)
3. Simplify renderSessionOutput() to always do full innerHTML replacement

This fixes the issue where output was being duplicated/appended incorrectly.
This commit is contained in:
Jared Miller 2026-01-31 09:58:18 -05:00
parent 130f01a19f
commit 6bd599d47b
Signed by: shmup
GPG key ID: 22B5C6D66A38B06C

View file

@ -1133,11 +1133,23 @@
renderSessions(); renderSessions();
}); });
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);
}
});
es.addEventListener('output', (e) => { es.addEventListener('output', (e) => {
const data = JSON.parse(e.data); const data = JSON.parse(e.data);
const session = state.sessions.get(data.session_id); const session = state.sessions.get(data.session_id);
if (session) { if (session) {
session.output += data.data; session.output = data.data; // Replace, not append
session.outputRenderedLength = 0; // Force full re-render
renderSessionOutput(data.session_id); renderSessionOutput(data.session_id);
} }
}); });
@ -1422,28 +1434,9 @@
const $output = document.getElementById(`output-${sessionId}`); const $output = document.getElementById(`output-${sessionId}`);
if ($output) { if ($output) {
// Append only new content since last render // Always full replace since serializeAsHTML() returns full screen state
const newChunk = session.output.slice(session.outputRenderedLength); $output.innerHTML = session.output;
if (newChunk) { session.outputRenderedLength = session.output.length;
// More efficient than innerHTML += as it avoids reparsing existing content
$output.insertAdjacentHTML('beforeend', newChunk);
session.outputRenderedLength = session.output.length;
}
// Soft cap to prevent unbounded growth in DOM and memory
const MAX_OUTPUT_CHARS = 200000; // ~200KB of HTML per session
if (session.output.length > MAX_OUTPUT_CHARS) {
let start = session.output.length - MAX_OUTPUT_CHARS;
// Find safe cut point - don't cut inside an HTML tag
const firstTagEnd = session.output.indexOf('>', start);
if (firstTagEnd !== -1 && firstTagEnd < start + 100) {
start = firstTagEnd + 1;
}
session.output = session.output.slice(start);
// Reset DOM to trimmed content and sync rendered length
$output.innerHTML = session.output;
session.outputRenderedLength = session.output.length;
}
const $outputContainer = document.getElementById(`session-output-${sessionId}`); const $outputContainer = document.getElementById(`session-output-${sessionId}`);
if ($outputContainer && session.expanded) { if ($outputContainer && session.expanded) {