Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/lib/auth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
} from "react"
import { api, setOnUnauthorized } from "@/api/api"
import { resetAnalytics } from "@/lib/analytics"
import { captureException } from "@/lib/sentry"
import {
clearCachedConsents,
fetchConsents,
Expand Down Expand Up @@ -105,7 +106,10 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
}, [clearState])

useEffect(() => {
checkAuth()
// checkAuth handles its own errors internally; this .catch is a guard so a
// future refactor can't leak an unhandled rejection (a bare `void` would
// silence the floating-promise lint but not a real runtime rejection).
checkAuth().catch((error: unknown) => captureException(error))
}, [checkAuth])

useEffect(() => {
Expand Down
29 changes: 29 additions & 0 deletions src/lib/sentry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,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
Expand Down
24 changes: 24 additions & 0 deletions src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { RouterProvider, createRouter } from "@tanstack/react-router"
import { StrictMode } from "react"
import { createRoot } from "react-dom/client"
import { reactErrorHandler } from "@sentry/react"
import { toast } from "sonner"
// @ts-expect-error -- fontsource CSS-only import, no types
import "@fontsource-variable/geist"
import { BrandLockup } from "./components/brand-lockup"
Expand All @@ -16,6 +17,29 @@ import { AuthProvider, useAuth } from "./lib/auth"
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, 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()

const router = createRouter({
Expand Down
Loading