From eea8cb585c1502ddbdec042dc13a8f2ba2a81aad Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Fri, 21 Aug 2026 23:39:02 -0700 Subject: [PATCH 1/2] Add a test proving the error boundary surfaces a quotable refId (CL-6632) --- apps/web/test/app-error-boundary.test.tsx | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/apps/web/test/app-error-boundary.test.tsx b/apps/web/test/app-error-boundary.test.tsx index b9eae8386..0d5991f8a 100644 --- a/apps/web/test/app-error-boundary.test.tsx +++ b/apps/web/test/app-error-boundary.test.tsx @@ -55,4 +55,20 @@ describe("AppErrorBoundary", () => { expect(el.textContent).toContain("Reload"); expect(el.textContent).not.toContain("Everything is fine"); }); + + // CL-6632: a render crash that never reaches a sink is undiagnosable in + // production — the boundary must report through `reportError` + // (`@corbits/error-sink`) and show the refId that call returns, so a + // person hitting this can quote it back to support. + test("a caught render error surfaces a refId a person can quote", () => { + const originalError = console.error; + console.error = () => undefined; + let el: HTMLDivElement; + try { + el = render(); + } finally { + console.error = originalError; + } + expect(el.textContent).toMatch(/Reference: [0-9a-z]+-[0-9a-z]+/); + }); }); From c2e64bba7302c74a2990b2b624150eec292a58fb Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Fri, 21 Aug 2026 23:39:11 -0700 Subject: [PATCH 2/2] AppErrorBoundary: report caught render errors through reportError (CL-6632) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A render crash the boundary catches never reached a sink and showed no refId — undiagnosable in production. Routes componentDidCatch through @corbits/error-sink instead of the bare client-log call, and shows the returned refId in the fallback so a person can quote it back. --- apps/web/package.json | 1 + apps/web/src/app-error-boundary.tsx | 35 +++++++++++++++++++++-------- apps/web/src/app.css | 7 ++++++ bun.lock | 7 ++++++ 4 files changed, 41 insertions(+), 9 deletions(-) diff --git a/apps/web/package.json b/apps/web/package.json index 987c1a2c4..dcfd638ad 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -26,6 +26,7 @@ "@corbits/client-log": "workspace:*", "@corbits/command-palette": "workspace:*", "@corbits/context-menu": "workspace:*", + "@corbits/error-sink": "workspace:*", "@corbits/inbox": "workspace:*", "@corbits/inference-settings": "workspace:*", "@corbits/insights": "workspace:*", diff --git a/apps/web/src/app-error-boundary.tsx b/apps/web/src/app-error-boundary.tsx index 1462a671e..833733137 100644 --- a/apps/web/src/app-error-boundary.tsx +++ b/apps/web/src/app-error-boundary.tsx @@ -3,28 +3,33 @@ // this as a class component (no hook equivalent exists), so it's the one // class in an otherwise function-component codebase. -import { getLogger } from "@corbits/client-log"; +import { reportError } from "@corbits/error-sink"; import { Button, EmptyState } from "@corbits/react-ui"; import { BoldIconProvider, WarningCircle } from "@corbits/icons"; import { Component, type ErrorInfo, type ReactNode } from "react"; -const log = getLogger("web.app-error-boundary"); - export class AppErrorBoundary extends Component< { readonly children: ReactNode }, - { readonly hasError: boolean } + { readonly hasError: boolean; readonly refId?: string } > { - override state = { hasError: false }; + override state: { readonly hasError: boolean; readonly refId?: string } = { + hasError: false, + }; static getDerivedStateFromError(): { hasError: boolean } { return { hasError: true }; } override componentDidCatch(error: Error, info: ErrorInfo): void { - log.error(error.message, { - stack: error.stack, - componentStack: info.componentStack, + const refId = reportError(error, { + operation: "app_render", + extra: { + ...(info.componentStack !== null + ? { componentStack: info.componentStack } + : {}), + }, }); + this.setState({ hasError: true, refId }); } override render(): ReactNode { @@ -35,7 +40,19 @@ export class AppErrorBoundary extends Component< } title="This screen hit a snag" - description="Something broke while rendering. Reloading usually fixes it." + description={ + this.state.refId === undefined ? ( + "Something broke while rendering. Reloading usually fixes it." + ) : ( + <> + Something broke while rendering. Reloading usually fixes it. +
+ + Reference: {this.state.refId} + + + ) + } action={