From 8cc74fec2cb351e6f40a28b909dec3b16f1f799e Mon Sep 17 00:00:00 2001 From: Adam Date: Mon, 31 Aug 2026 12:53:00 +0000 Subject: [PATCH 1/2] fix: preserve drafts during service worker install --- src/register-service-worker.ts | 3 +++ src/test/vercel-deployment-config.test.ts | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/src/register-service-worker.ts b/src/register-service-worker.ts index 5024189eb..156174cc0 100644 --- a/src/register-service-worker.ts +++ b/src/register-service-worker.ts @@ -6,6 +6,8 @@ export function registerServiceWorker() { if (isNativeApp()) return; if (!import.meta.env.PROD || !('serviceWorker' in navigator)) return; + const hadController = navigator.serviceWorker.controller !== null; + window.addEventListener('load', () => { navigator.serviceWorker.register(SERVICE_WORKER_URL) .then((registration) => { @@ -25,6 +27,7 @@ export function registerServiceWorker() { }); navigator.serviceWorker.addEventListener('controllerchange', () => { + if (!hadController) return; if (sessionStorage.getItem('pwa-refreshing') === 'true') return; sessionStorage.setItem('pwa-refreshing', 'true'); window.location.reload(); diff --git a/src/test/vercel-deployment-config.test.ts b/src/test/vercel-deployment-config.test.ts index 5838e05e2..9635a4de8 100644 --- a/src/test/vercel-deployment-config.test.ts +++ b/src/test/vercel-deployment-config.test.ts @@ -91,6 +91,12 @@ describe("Vercel frontend deployment configuration", () => { expect(serviceWorker).toContain("cache.put('/index.html', response.clone())"); }); + it("does not reload the page when the service worker first takes control", () => { + const registration = readProjectFile("src/register-service-worker.ts"); + expect(registration).toContain("const hadController = navigator.serviceWorker.controller !== null"); + expect(registration).toContain("if (!hadController) return"); + }); + it.each(["/app", "/app/(.*)", "/login", "/share/(.*)"])( "keeps private route %s out of search indexes", (source) => { From 1ca2d53c55b661eb261dd65ced449d85b563a032 Mon Sep 17 00:00:00 2001 From: Adam Date: Mon, 31 Aug 2026 13:03:11 +0000 Subject: [PATCH 2/2] test: preserve later service worker refreshes --- src/register-service-worker.ts | 7 +++- src/test/register-service-worker.test.ts | 48 +++++++++++++++++++++++ src/test/vercel-deployment-config.test.ts | 6 --- 3 files changed, 53 insertions(+), 8 deletions(-) create mode 100644 src/test/register-service-worker.test.ts diff --git a/src/register-service-worker.ts b/src/register-service-worker.ts index 156174cc0..a6396d832 100644 --- a/src/register-service-worker.ts +++ b/src/register-service-worker.ts @@ -6,7 +6,7 @@ export function registerServiceWorker() { if (isNativeApp()) return; if (!import.meta.env.PROD || !('serviceWorker' in navigator)) return; - const hadController = navigator.serviceWorker.controller !== null; + let hadController = navigator.serviceWorker.controller !== null; window.addEventListener('load', () => { navigator.serviceWorker.register(SERVICE_WORKER_URL) @@ -27,7 +27,10 @@ export function registerServiceWorker() { }); navigator.serviceWorker.addEventListener('controllerchange', () => { - if (!hadController) return; + if (!hadController) { + hadController = true; + return; + } if (sessionStorage.getItem('pwa-refreshing') === 'true') return; sessionStorage.setItem('pwa-refreshing', 'true'); window.location.reload(); diff --git a/src/test/register-service-worker.test.ts b/src/test/register-service-worker.test.ts new file mode 100644 index 000000000..7c2cc49d4 --- /dev/null +++ b/src/test/register-service-worker.test.ts @@ -0,0 +1,48 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + +vi.mock('@/lib/native', () => ({ isNativeApp: () => false })); + +import { registerServiceWorker } from '@/register-service-worker'; + +describe('registerServiceWorker', () => { + const originalServiceWorker = Object.getOwnPropertyDescriptor(navigator, 'serviceWorker'); + let controllerChange: EventListener | undefined; + + beforeEach(() => { + vi.stubEnv('PROD', true); + sessionStorage.clear(); + controllerChange = undefined; + Object.defineProperty(navigator, 'serviceWorker', { + configurable: true, + value: { + controller: null, + register: vi.fn().mockResolvedValue({ addEventListener: vi.fn() }), + addEventListener: vi.fn((type: string, listener: EventListener) => { + if (type === 'controllerchange') controllerChange = listener; + }), + }, + }); + }); + + afterEach(() => { + vi.unstubAllEnvs(); + vi.restoreAllMocks(); + if (originalServiceWorker) { + Object.defineProperty(navigator, 'serviceWorker', originalServiceWorker); + } else { + Reflect.deleteProperty(navigator, 'serviceWorker'); + } + }); + + it('skips the initial claim but refreshes for a later controller change', () => { + registerServiceWorker(); + + expect(controllerChange).toBeDefined(); + controllerChange?.(new Event('controllerchange')); + expect(sessionStorage.getItem('pwa-refreshing')).toBeNull(); + + vi.spyOn(console, 'error').mockImplementation(() => undefined); + controllerChange?.(new Event('controllerchange')); + expect(sessionStorage.getItem('pwa-refreshing')).toBe('true'); + }); +}); diff --git a/src/test/vercel-deployment-config.test.ts b/src/test/vercel-deployment-config.test.ts index 9635a4de8..5838e05e2 100644 --- a/src/test/vercel-deployment-config.test.ts +++ b/src/test/vercel-deployment-config.test.ts @@ -91,12 +91,6 @@ describe("Vercel frontend deployment configuration", () => { expect(serviceWorker).toContain("cache.put('/index.html', response.clone())"); }); - it("does not reload the page when the service worker first takes control", () => { - const registration = readProjectFile("src/register-service-worker.ts"); - expect(registration).toContain("const hadController = navigator.serviceWorker.controller !== null"); - expect(registration).toContain("if (!hadController) return"); - }); - it.each(["/app", "/app/(.*)", "/login", "/share/(.*)"])( "keeps private route %s out of search indexes", (source) => {