From 416a11dcceda268ed711a4720ae3b49c9e5b6849 Mon Sep 17 00:00:00 2001 From: Isaac Sanchez-Hawkins <266845420+isanchez404@users.noreply.github.com> Date: Tue, 12 May 2026 04:00:58 -0400 Subject: [PATCH] fix(ui): recover from corrupt chunk reload markers (#1230) * fix(ui): recover from corrupt chunk reload markers * test(ui): clarify chunk recovery assertions --------- Co-authored-by: Isaac Sanchez --- packages/ui/src/lib/chunkLoadRecovery.test.ts | 51 +++++++++++++++++++ packages/ui/src/lib/chunkLoadRecovery.ts | 21 ++++++-- packages/ui/src/types/bun-test.d.ts | 2 + 3 files changed, 70 insertions(+), 4 deletions(-) create mode 100644 packages/ui/src/lib/chunkLoadRecovery.test.ts diff --git a/packages/ui/src/lib/chunkLoadRecovery.test.ts b/packages/ui/src/lib/chunkLoadRecovery.test.ts new file mode 100644 index 00000000..5dd0eea7 --- /dev/null +++ b/packages/ui/src/lib/chunkLoadRecovery.test.ts @@ -0,0 +1,51 @@ +import { describe, expect, test } from 'bun:test'; + +import { importWithChunkRecovery } from './chunkLoadRecovery'; + +describe('importWithChunkRecovery', () => { + test('schedules recovery reload when stored reload marker is corrupt', async () => { + const globalWithWindow = globalThis as unknown as { window?: unknown }; + const previousWindow = globalWithWindow.window; + let storedMarker: string | null = null; + let reloadCount = 0; + + globalWithWindow.window = { + sessionStorage: { + getItem: () => '{not json', + setItem: (_key: string, value: string) => { + storedMarker = value; + }, + }, + setTimeout: (callback: () => void) => { + callback(); + return 0; + }, + location: { + reload: () => { + reloadCount += 1; + }, + }, + }; + + try { + let caught: unknown; + try { + await importWithChunkRecovery(async () => { + throw new Error('Failed to fetch dynamically imported module'); + }, { retries: 0 }); + } catch (error) { + caught = error; + } + + expect(caught).toBeInstanceOf(Error); + expect(storedMarker).not.toBeNull(); + expect(reloadCount).toBe(1); + } finally { + if (previousWindow === undefined) { + delete globalWithWindow.window; + } else { + globalWithWindow.window = previousWindow; + } + } + }); +}); diff --git a/packages/ui/src/lib/chunkLoadRecovery.ts b/packages/ui/src/lib/chunkLoadRecovery.ts index 87666eae..6dd0a480 100644 --- a/packages/ui/src/lib/chunkLoadRecovery.ts +++ b/packages/ui/src/lib/chunkLoadRecovery.ts @@ -46,13 +46,26 @@ function scheduleReloadOnce(error: unknown): void { const now = Date.now(); const signature = reloadMarkerSignature(error); + let marker: { signature?: unknown; timestamp?: unknown } | null = null; try { const rawMarker = window.sessionStorage.getItem(RELOAD_STORAGE_KEY); - const marker = rawMarker ? JSON.parse(rawMarker) as { signature?: unknown; timestamp?: unknown } : null; - const markerTimestamp = typeof marker?.timestamp === 'number' ? marker.timestamp : 0; - if (marker?.signature === signature && now - markerTimestamp < RELOAD_GUARD_MS) { - return; + if (rawMarker) { + try { + marker = JSON.parse(rawMarker) as { signature?: unknown; timestamp?: unknown }; + } catch { + marker = null; + } } + } catch { + return; + } + + const markerTimestamp = typeof marker?.timestamp === 'number' ? marker.timestamp : 0; + if (marker?.signature === signature && now - markerTimestamp < RELOAD_GUARD_MS) { + return; + } + + try { window.sessionStorage.setItem(RELOAD_STORAGE_KEY, JSON.stringify({ signature, timestamp: now })); } catch { return; diff --git a/packages/ui/src/types/bun-test.d.ts b/packages/ui/src/types/bun-test.d.ts index de7972cb..da75b83b 100644 --- a/packages/ui/src/types/bun-test.d.ts +++ b/packages/ui/src/types/bun-test.d.ts @@ -15,10 +15,12 @@ declare module "bun:test" { toBeGreaterThan(expected: number): void; toBeLessThan(expected: number): void; toHaveLength(expected: number): void; + toBeInstanceOf(expected: unknown): void; not: { toEqual(expected: unknown): void; toBe(expected: unknown): void; toContain(expected: unknown): void; + toBeNull(): void; }; }; export function beforeEach(fn: () => void | Promise): void;