fix: eliminate SSE padding overhead and debounce subagent window renders

Two performance fixes for browser hanging during active agent work:

1. SSE padding (8KB per event) now only applied when Cloudflare tunnel is
   active — direct/Tailscale connections skip the padding entirely. Previously
   every broadcast event got 8KB of comment padding even on local connections,
   causing 40-160KB/s of wasted bandwidth during active subagent work.

2. Subagent window content renders (tool_call, progress, message, tool_result)
   now debounced at 100ms per agent via scheduleSubagentWindowRender(). Previously
   each SSE event triggered an immediate DOM rewrite, causing 10-30+ rewrites/sec
   that starved the terminal rendering pipeline.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
arkon
2026-03-02 21:48:31 +01:00
co-authored by Claude Opus 4.6
parent f7552c7cb5
commit efa6487361
2 changed files with 54 additions and 18 deletions
+39 -8
View File
@@ -2150,9 +2150,9 @@ class CodemanApp {
this.renderSubagentDetail();
}
this.renderSubagentPanel();
// Update floating window
// Update floating window (debounced — tool_call events fire rapidly)
if (this.subagentWindows.has(data.agentId)) {
this.renderSubagentWindowContent(data.agentId);
this.scheduleSubagentWindowRender(data.agentId);
}
});
@@ -2165,9 +2165,9 @@ class CodemanApp {
if (this.activeSubagentId === data.agentId) {
this.renderSubagentDetail();
}
// Update floating window
// Update floating window (debounced)
if (this.subagentWindows.has(data.agentId)) {
this.renderSubagentWindowContent(data.agentId);
this.scheduleSubagentWindowRender(data.agentId);
}
});
@@ -2180,9 +2180,9 @@ class CodemanApp {
if (this.activeSubagentId === data.agentId) {
this.renderSubagentDetail();
}
// Update floating window
// Update floating window (debounced)
if (this.subagentWindows.has(data.agentId)) {
this.renderSubagentWindowContent(data.agentId);
this.scheduleSubagentWindowRender(data.agentId);
}
});
@@ -2208,9 +2208,9 @@ class CodemanApp {
if (this.activeSubagentId === data.agentId) {
this.renderSubagentDetail();
}
// Update floating window
// Update floating window (debounced)
if (this.subagentWindows.has(data.agentId)) {
this.renderSubagentWindowContent(data.agentId);
this.scheduleSubagentWindowRender(data.agentId);
}
});
@@ -6054,6 +6054,24 @@ class CodemanApp {
display.textContent = '';
display.onclick = null;
}
// Upload URL row
const uploadRow = document.getElementById('tunnelUploadUrlRow');
const uploadDisplay = document.getElementById('tunnelUploadUrlDisplay');
if (!uploadRow || !uploadDisplay) return;
if (url) {
const uploadUrl = url + '/upload.html';
uploadRow.style.display = '';
uploadDisplay.textContent = uploadUrl;
uploadDisplay.onclick = () => {
navigator.clipboard.writeText(uploadUrl).then(() => {
this.showToast('Upload URL copied', 'success');
});
};
} else {
uploadRow.style.display = 'none';
uploadDisplay.textContent = '';
uploadDisplay.onclick = null;
}
}
showTunnelQR() {
@@ -9213,6 +9231,19 @@ class CodemanApp {
}
// Debounced wrapper — coalesces rapid subagent events (tool_call, progress,
// message) into a single DOM update per 100ms per agent window.
scheduleSubagentWindowRender(agentId) {
if (!this._subagentWindowRenderTimeouts) this._subagentWindowRenderTimeouts = new Map();
if (this._subagentWindowRenderTimeouts.has(agentId)) {
clearTimeout(this._subagentWindowRenderTimeouts.get(agentId));
}
this._subagentWindowRenderTimeouts.set(agentId, setTimeout(() => {
this._subagentWindowRenderTimeouts.delete(agentId);
scheduleBackground(() => this.renderSubagentWindowContent(agentId));
}, 100));
}
renderSubagentWindowContent(agentId) {
// Skip if this window has a live terminal (don't overwrite xterm with activity HTML)
if (this.teammateTerminals.has(agentId)) return;