diff --git a/browse/test/sidebar-ux.test.ts b/browse/test/sidebar-ux.test.ts index 23bab9bb..328c199e 100644 --- a/browse/test/sidebar-ux.test.ts +++ b/browse/test/sidebar-ux.test.ts @@ -1348,6 +1348,30 @@ describe('sidebar auth race prevention', () => { }); }); +describe('startup health check fast-retry', () => { + const bgSrc = fs.readFileSync(path.join(ROOT, '..', 'extension', 'background.js'), 'utf-8'); + + test('initial health check retries every 1s (not 10s)', () => { + // The server may not be listening when the extension starts because + // Chromium launches before Bun.serve(). A 10s gap means the user + // stares at "Connecting..." for 10 seconds. 1s retry fixes this. + expect(bgSrc).toContain('startupAttempts'); + expect(bgSrc).toContain('setInterval(async ()'); + // Fast retry uses 1000ms, not the 10000ms slow poll + expect(bgSrc).toContain('}, 1000);'); + }); + + test('startup retry stops after connection or max attempts', () => { + expect(bgSrc).toContain('isConnected || startupAttempts >= 15'); + expect(bgSrc).toContain('clearInterval(startupCheck)'); + }); + + test('slow 10s polling only starts after startup phase completes', () => { + expect(bgSrc).toContain('if (!healthInterval)'); + expect(bgSrc).toContain('setInterval(checkHealth, 10000)'); + }); +}); + describe('sidebar debug visibility when stuck', () => { const spSrc = fs.readFileSync(path.join(ROOT, '..', 'extension', 'sidepanel.js'), 'utf-8'); diff --git a/extension/background.js b/extension/background.js index 516ddb62..d60a5bbd 100644 --- a/extension/background.js +++ b/extension/background.js @@ -454,10 +454,25 @@ chrome.tabs.onActivated.addListener((activeInfo) => { // ─── Startup ──────────────────────────────────────────────────── -// Load auth token BEFORE first health poll (token no longer in /health response) +// Fast-retry health check on startup. The server may not be listening yet +// (Chromium launches before Bun.serve starts). Retry every 1s for the +// first 15 seconds, then switch to 10s polling. loadAuthToken().then(() => { loadPort().then(() => { - checkHealth(); - healthInterval = setInterval(checkHealth, 10000); + let startupAttempts = 0; + const startupCheck = setInterval(async () => { + startupAttempts++; + await checkHealth(); + if (isConnected || startupAttempts >= 15) { + clearInterval(startupCheck); + // Switch to slow polling now that we're connected (or gave up) + if (!healthInterval) { + healthInterval = setInterval(checkHealth, 10000); + } + if (!isConnected) { + console.log('[gstack] Startup health checks failed after 15 attempts, falling back to 10s polling'); + } + } + }, 1000); }); });