Files
nexu-io-open-design/apps/web/tests/analytics
lefarcen dab92a8cf2 fix(analytics): stamp frame platform + drop fetch-failure noise in exception transport (#4661)
* fix(analytics): stamp frame platform + drop fetch-failure noise in exception transport

The hand-rolled browser-exception transport in error-tracking.ts (the sole
$exception source since client.ts sets capture_exceptions:false) had two bugs
that PostHog's weekly digest surfaced as "512K exceptions / 511K failed to
ingest":

1. Frames never carried `platform`. PostHog's exception ingestion requires it
   per frame to pick the symbolication/grouping strategy; without it every
   event failed processing with "missing field platform" and was dropped
   server-side. 100% of our ingested $exception events had platform=(missing),
   all from capture_source=web/error-tracking. Now stamped 'web:javascript' on
   every frame, matching posthog-js.

2. `TypeError: Failed to fetch` from the packaged app's daemon connection
   dropping (restart, boot race, navigation/unmount abort, offline) was ~90% of
   all captured exceptions — environmental noise, not bugs, one polling loop can
   emit thousands. Dropped at the capture chokepoint along with AbortError and
   the WebKit/Firefox network-error wordings.

Also hardens the telemetry beacon: `void fetch(...)` had only a synchronous
try/catch, so a failed beacon rejected unhandled and got re-captured as its own
Failed to fetch — a self-amplifying loop. Now `.catch()`-ed.

Red specs in error-tracking.test.ts go red on main (frames lack platform; noise
is dispatched) and green here.

* fix(analytics): scope fetch-noise drop to packaged runtime, stop dropping AbortError

Address review: captureException is the single chokepoint for window.error /
unhandledrejection / reportHandledException, so a blanket drop of
`Failed to fetch` would also suppress a real broken-/api/* or CORS/TLS signal
from the normal web app — and blanket-dropping AbortError was never justified
by the data.

- Gate the fetch-failure drop on the exception originating in packaged app
  code (od:// stack origin), which is where the ~90% daemon-connection churn
  lives. The same TypeError from a web (http/https) context stays captured.
- Remove the AbortError drop entirely (not a measured noise source, and not
  necessarily fetch-lifecycle cancellation).

Test now asserts the invariant directly: packaged od:// fetch noise dropped,
the identical web-origin error kept, AbortError kept.

* test(analytics): cover beacon-failure self-amplification backstop

Address review: add a regression that a failed telemetry beacon, surfacing as
an unhandledrejection from od:// transport code, does not re-enter capture as a
second $exception/beacon. Exercises the packaged-noise backstop (the
loop-breaking layer observable under jsdom; dispatch()'s .catch() is a
process-level promise rejection that jsdom does not route to
window.onunhandledrejection, so it is not unit-observable here).

* fix(analytics): recognize file:// app-bundle frames as packaged origin

Address blocking review: packaged exceptions don't only arrive as od:// frames
— source-mapped frames surface as file:///.../<Channel>.app/Contents/Resources/...
(the shape scrub.ts rewrites). The od://-only check let those packaged
fetch-failures leak through. Broaden originatesInPackagedApp to also match the
.app/Contents/Resources bundle marker (channel-agnostic, so Beta/Preview builds
are covered). Regression added: file:// app-bundle Failed-to-fetch stacks are
dropped too; red against the od://-only check, green here.
2026-06-23 12:41:56 +00:00
..