From 4e48f8344feee75f11d2138091cf28583d183b3d Mon Sep 17 00:00:00 2001 From: Ben Senescu <44480372+bensenescu@users.noreply.github.com> Date: Tue, 30 Jun 2026 17:41:21 -0400 Subject: [PATCH] Fix production errors: onboarding crash hardening + DataForSEO spend/noise cleanup (#282) --- src/client/lib/posthog.ts | 39 +++++++++++++++++++ src/routes/__root.tsx | 11 +++++- .../features/audit/services/AuditService.ts | 22 ++++++++--- src/server/lib/dataforseo/business.ts | 10 ++++- src/server/lib/dataforseo/envelope.test.ts | 22 +++++++++++ src/server/lib/dataforseo/envelope.ts | 25 +++++++++++- src/server/mcp/schemas.ts | 10 ++++- src/shared/error-codes.test.ts | 4 ++ src/shared/keyword-locations.test.ts | 16 ++++++++ src/shared/keyword-locations.ts | 14 +++++++ 10 files changed, 161 insertions(+), 12 deletions(-) diff --git a/src/client/lib/posthog.ts b/src/client/lib/posthog.ts index bbc2b5a..d569062 100644 --- a/src/client/lib/posthog.ts +++ b/src/client/lib/posthog.ts @@ -9,6 +9,36 @@ let browserPostHogClientPromise: Promise | null = let browserPostHogInitialized = false; let analyticsCaptureEnabled = true; +type ExceptionEntry = { + value?: unknown; + mechanism?: { synthetic?: boolean }; + stacktrace?: { frames?: unknown[] }; +}; + +// Unactionable exceptions we don't want polluting error tracking. They share +// the trait of not being our code: browser extensions inject promise rejections +// and cross-origin scripts surface as a detail-less "Script error.", while the +// global onerror handler synthesizes a stackless "undefined" when it fires +// without a real Error object. Real app errors always carry a stack, so the +// "undefined" rule is gated on synthetic + no frames to avoid false drops. +function isIgnorableException( + properties: Record | undefined, +): boolean { + const list = properties?.["$exception_list"]; + if (!Array.isArray(list) || list.length === 0) return false; + return list.every((entry: ExceptionEntry) => { + const value = typeof entry?.value === "string" ? entry.value : ""; + if (value.includes("Object Not Found Matching Id")) return true; + if (value === "Script error.") return true; + const frames = entry?.stacktrace?.frames; + return ( + value === "undefined" && + entry?.mechanism?.synthetic === true && + (!Array.isArray(frames) || frames.length === 0) + ); + }); +} + function getBrowserPostHogClient(): Promise { if (typeof window === "undefined" || !isHostedClientAuthMode()) { return Promise.resolve(null); @@ -34,6 +64,15 @@ function getBrowserPostHogClient(): Promise { api_host: host, defaults: "2026-01-30", capture_exceptions: true, + before_send(event) { + if ( + event?.event === "$exception" && + isIgnorableException(event.properties) + ) { + return null; + } + return event; + }, capture_pageview: "history_change", respect_dnt: true, session_recording: { diff --git a/src/routes/__root.tsx b/src/routes/__root.tsx index 074adef..a7abfa8 100644 --- a/src/routes/__root.tsx +++ b/src/routes/__root.tsx @@ -48,6 +48,15 @@ export const Route = createRootRoute({ name: "viewport", content: "width=device-width, initial-scale=1, viewport-fit=cover", }, + // Disable browser auto-translate (Google Translate) app-wide. It rewrites + // text nodes into wrappers, which React then can't remove/insert, + // crashing render with NotFoundError ("removeChild"/"insertBefore"). The + // product UI is data-dense (keywords, domains, metrics) and not meaningful + // to machine-translate; the marketing site is a separate app and unaffected. + { + name: "google", + content: "notranslate", + }, { name: "apple-mobile-web-app-capable", content: "yes", @@ -155,7 +164,7 @@ function RootDocument({ children }: { children: React.ReactNode }) { import.meta.env.DEV && import.meta.env.VITE_SHOW_DEVTOOLS !== "false"; return ( - +