mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-05 21:39:54 +02:00
* feat(analytics): capture JS exceptions to PostHog Turn on posthog-js exception autocapture (window.onerror + unhandled rejections, with stack) and add a buffered captureException() wrapper for boundary-caught React errors those handlers can't see. Wire the web route-level global-error boundary to report through it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(diagnostics): add shared freeze watchdog Long-task observer (>=2s) emits client_unresponsive via captureEvent; client_type super-property tags desktop vs web for free. Installed once in CoreProvider so web and desktop share one in-thread, SSR-safe detector for recoverable freezes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(desktop): report true hangs and crashes via breadcrumb A real hang or crashed renderer can't report itself. The main process now persists a breadcrumb on unresponsive / render-process-gone, and the next renderer boot flushes it to PostHog (client_unresponsive / client_crash). A recovered hang clears its breadcrumb so it isn't double-counted by the in-thread watchdog. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(analytics): scrub PII from $exception before send Error messages can interpolate user input (typed values, URLs with tokens). Add a before_send hook that redacts emails, URL query strings, and long opaque tokens from the exception message and $exception_list values, keeping type + stack frames (code locations, not user data). Addresses the privacy gap from leaving capture_exceptions on with no sanitizer. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test: cover breadcrumb state machine and freeze watchdog The breadcrumb persist/clear orchestration is the correctness-critical part and was untested. Cover: hang->write, recover->clear (no double-count), recover-before-delay->no-op, force-quit->retained, crash->write-and-never- clear, clean-exit->no-write. Add watchdog tests (threshold, idempotent, SSR/PerformanceObserver no-op) via a fake observer. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(desktop): breadcrumb field precedence + document limits Spread the persisted context FIRST so explicit event fields (source, recovered) always win over a future colliding context key. Document why preload-error skips the breadcrumb and the single-slot last-write-wins undercount limitation. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
59 lines
1.6 KiB
TypeScript
59 lines
1.6 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect } from "react";
|
|
import { captureException } from "@multica/core/analytics";
|
|
|
|
/**
|
|
* Route-level error boundary for the web app. Next.js renders this (replacing
|
|
* the root layout) when an error escapes everything below it — the full-page
|
|
* white-screen case. React catches these before they reach window.onerror, so
|
|
* posthog-js's automatic exception capture never sees them; we report them
|
|
* explicitly here. Section-level failures are handled in place by
|
|
* `@multica/ui` ErrorBoundary and don't reach this far.
|
|
*/
|
|
export default function GlobalError({
|
|
error,
|
|
reset,
|
|
}: {
|
|
error: Error & { digest?: string };
|
|
reset: () => void;
|
|
}) {
|
|
useEffect(() => {
|
|
captureException(error, { source: "global-error", digest: error.digest });
|
|
}, [error]);
|
|
|
|
return (
|
|
<html>
|
|
<body
|
|
style={{
|
|
display: "flex",
|
|
minHeight: "100vh",
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
fontFamily: "system-ui, sans-serif",
|
|
}}
|
|
>
|
|
<div style={{ maxWidth: 420, textAlign: "center" }}>
|
|
<h1 style={{ fontSize: 18, fontWeight: 600 }}>Something went wrong</h1>
|
|
<p style={{ marginTop: 8, color: "#666" }}>
|
|
The page hit an unexpected error. Try reloading.
|
|
</p>
|
|
<button
|
|
type="button"
|
|
onClick={reset}
|
|
style={{
|
|
marginTop: 16,
|
|
padding: "8px 16px",
|
|
borderRadius: 6,
|
|
border: "1px solid #ccc",
|
|
cursor: "pointer",
|
|
}}
|
|
>
|
|
Reload
|
|
</button>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
);
|
|
}
|