From fa47dc30abf79a0341d5c112873e4bd28d8c1c87 Mon Sep 17 00:00:00 2001 From: "ilblackdragon@gmail.com" Date: Thu, 26 Mar 2026 19:46:00 -0700 Subject: [PATCH] fix(web): support multiple gateway tabs by reducing SSE connections MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- src/channels/web/static/app.js | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/channels/web/static/app.js b/src/channels/web/static/app.js index 3c588f0e..889ba312 100644 --- a/src/channels/web/static/app.js +++ b/src/channels/web/static/app.js @@ -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 {