From 70f9f3d51d189d73287064af6c60a6fd52623e20 Mon Sep 17 00:00:00 2001 From: ashim-hq Date: Tue, 21 Apr 2026 00:01:59 +0800 Subject: [PATCH] fix: detect CSS preload errors and keep banner visible during error states - Add "unable to preload" pattern to isChunkError for Vite CSS preload failures - Move ConnectionMonitor and ConnectionBanner outside ErrorBoundary so they remain visible when the error boundary catches a render crash - Add test for CSS preload error retry --- apps/web/src/App.tsx | 63 ++++++++++++++------------ apps/web/src/lib/lazy-with-retry.ts | 3 +- tests/unit/web/lazy-with-retry.test.ts | 13 ++++++ 3 files changed, 49 insertions(+), 30 deletions(-) diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index 20ea42d2..3825071a 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -167,36 +167,41 @@ function ConnectionMonitor() { export function App() { return ( - + <> - - - - - }> - - } /> - } /> - } /> - } /> - } /> - } /> - {/* Redirects: old color tools consolidated into adjust-colors */} - } - /> - } /> - } /> - } /> - } /> - } /> - - - - - - + + + + + + }> + + } /> + } /> + } /> + } /> + } /> + } /> + {/* Redirects: old color tools consolidated into adjust-colors */} + } + /> + } /> + } + /> + } /> + } /> + } /> + + + + + + + ); } diff --git a/apps/web/src/lib/lazy-with-retry.ts b/apps/web/src/lib/lazy-with-retry.ts index cc37950a..54991c61 100644 --- a/apps/web/src/lib/lazy-with-retry.ts +++ b/apps/web/src/lib/lazy-with-retry.ts @@ -7,7 +7,8 @@ export function isChunkError(error: unknown): boolean { msg.includes("dynamically imported module") || msg.includes("loading chunk") || msg.includes("loading css chunk") || - msg.includes("failed to fetch") + msg.includes("failed to fetch") || + msg.includes("unable to preload") ); } diff --git a/tests/unit/web/lazy-with-retry.test.ts b/tests/unit/web/lazy-with-retry.test.ts index 86b4af30..5703b65d 100644 --- a/tests/unit/web/lazy-with-retry.test.ts +++ b/tests/unit/web/lazy-with-retry.test.ts @@ -33,6 +33,19 @@ describe("retryDynamicImport", () => { expect(importFn).toHaveBeenCalledTimes(3); }); + it("retries CSS preload errors", async () => { + const mod = { default: () => null }; + const importFn = vi + .fn() + .mockRejectedValueOnce( + new TypeError("Unable to preload CSS for /assets/tool-page-DDbXBANV.css"), + ) + .mockResolvedValue(mod); + const result = await retryDynamicImport(importFn, 3, 0); + expect(result).toBe(mod); + expect(importFn).toHaveBeenCalledTimes(2); + }); + it("only retries chunk-related errors, not other errors", async () => { const err = new Error("Some other error"); const importFn = vi.fn().mockRejectedValue(err);