fix(web): support multiple gateway tabs by reducing SSE connections

Each browser tab opened 2 SSE connections (chat events + log events).
With the HTTP/1.1 per-origin limit of 6, the 3rd tab exhausted the
pool and couldn't load any data.

Three changes:

1. Lazy log SSE — only connect when the logs tab is active, disconnect
   when switching away. Most users rarely view logs, so this saves a
   connection slot per tab.

2. Visibility API — close SSE when the browser tab goes to background
   (user switches to another tab), reconnect when it becomes visible.
   Background tabs don't need real-time events.

3. Combined with the existing beforeunload cleanup, this means:
   - Active foreground tab: 1 connection (chat SSE only, +1 if logs tab)
   - Background tabs: 0 connections
   - Closed/refreshed tabs: 0 connections (beforeunload cleanup)

This allows many gateway tabs to coexist within the 6-connection limit.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
This commit is contained in:
2026-03-26 19:46:00 -07:00
co-authored by Claude Opus 4.6
parent e508d2083b
commit fa47dc30ab
+17 -2
View File
@@ -184,7 +184,7 @@ function authenticate() {
cleaned.searchParams.delete('log_level');
window.history.replaceState({}, '', cleaned.pathname + cleaned.search);
connectSSE();
connectLogSSE();
// Log SSE is connected lazily when the logs tab is opened (saves a connection slot).
startGatewayStatusPolling();
checkTeeStatus();
loadThreads();
@@ -223,6 +223,20 @@ window.addEventListener('beforeunload', () => {
if (logEventSource) { logEventSource.close(); logEventSource = null; }
});
// Pause SSE when the browser tab is hidden (another tab is focused) and resume
// when it becomes visible again. This frees connection slots for other tabs
// running the gateway — without this, each tab holds 1-2 SSE connections and
// the 3rd tab exhausts the browser's per-origin limit.
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
if (eventSource) { eventSource.close(); eventSource = null; }
if (logEventSource) { logEventSource.close(); logEventSource = null; }
} else if (token) {
connectSSE();
if (currentTab === 'logs') connectLogSSE();
}
});
// Note: main event listener registration is at the bottom of this file (search
// "Event Listener Registration"). Do NOT add duplicate listeners here.
@@ -2222,7 +2236,8 @@ function switchTab(tab) {
if (tab === 'jobs') loadJobs();
if (tab === 'missions') loadMissions();
if (tab === 'routines') loadRoutines();
if (tab === 'logs') applyLogFilters();
if (tab === 'logs') { connectLogSSE(); applyLogFilters(); }
else if (logEventSource) { logEventSource.close(); logEventSource = null; }
if (tab === 'settings') {
loadSettingsSubtab(currentSettingsSubtab);
} else {