From 465565684ad6930f8856607bdfaefe4b01958d37 Mon Sep 17 00:00:00 2001 From: Amr Gaber Date: Sun, 28 Jun 2026 18:49:31 -0500 Subject: [PATCH] fix: stale-chunk reload prompt + Sentry noise filtering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - main.tsx: add a `vite:preloadError` listener that prompts a one-shot "new version available" toast (sonner) when a deploy replaces a chunk an open tab is importing — prompt rather than force-reload (the router's `defaultPreload: "intent"` fires on hover, and a forced reload would discard a half-filled login/registration form). - lib/sentry.ts: add `beforeSend` + `ignoreErrors`/`denyUrls` to drop never-actionable noise (intentional AbortError cancellation, browser extensions, translating proxies, sandboxed storage). --- src/lib/sentry.ts | 29 +++++++++++++++++++++++++++++ src/main.tsx | 24 ++++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/src/lib/sentry.ts b/src/lib/sentry.ts index ff9a12f..dccca5c 100644 --- a/src/lib/sentry.ts +++ b/src/lib/sentry.ts @@ -47,6 +47,35 @@ export function initSentry(router: SentryRouter) { replaysOnErrorSampleRate: 1.0, enableLogs: true, + + // Drop noise that's never actionable, so it doesn't bury real errors or + // burn quota: intentional fetch cancellation plus errors injected by the + // user's environment rather than our code. + ignoreErrors: [ + // Intentional fetch/query cancellation. + "AbortError", + "The user aborted a request", + "signal is aborted without reason", + // Benign layout-loop notices the browser emits under load. + "ResizeObserver loop limit exceeded", + "ResizeObserver loop completed with undelivered notifications", + // Translating proxies (Yandex/Google Translate) rewrite the DOM and trip the History API. + "Failed to execute 'replaceState' on 'History'", + // Sandboxed-iframe / privacy-mode storage access denied. + "The operation is insecure", + ], + denyUrls: [ + /^chrome-extension:\/\//i, + /^moz-extension:\/\//i, + /^safari-web-extension:\/\//i, + ], + beforeSend(event, hint) { + // Intentional cancellation (a query/fetch aborted mid-flight) is expected + // noise — never report it, however the SDK wrapped it. + const exception = hint.originalException as { name?: string } | undefined + if (exception?.name === "AbortError") return null + return event + }, }) initialized = true diff --git a/src/main.tsx b/src/main.tsx index ff6a496..9d221a4 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -23,6 +23,30 @@ import { FeatureFlagProvider } from "./lib/feature-flags" import { initSentry } from "./lib/sentry" import { routeTree } from "./routeTree.gen" +// A `vite:preloadError` fires when a dynamic import for a code-split chunk +// fails — almost always because a newer build was deployed and this tab is now +// stale (it references content-hashed chunk filenames the deploy has replaced). +// Prompt to reload rather than force-reloading: a forced reload discards unsaved +// work (e.g. a half-filled login/registration form), and because the router uses +// `defaultPreload: "intent"` this event also fires for speculative preloads +// (hovering a stale link), where auto-reloading would be jarring. +// `preventDefault()` stops Vite re-throwing the failed import. Shown once so a +// burst of failures can't stack it. +let updateToastShown = false +window.addEventListener("vite:preloadError", (event) => { + event.preventDefault() + if (updateToastShown) return + updateToastShown = true + toast.info("A new version is available", { + description: "Reload to get the latest update.", + duration: Infinity, + action: { + label: "Reload", + onClick: () => window.location.reload(), + }, + }) +}) + const queryClient = new QueryClient({ defaultOptions: { queries: {