fix(frontend): contain viewer/widget crashes with error boundaries

A render error in one viewer or gate widget unmounted the whole React root
(white screen). Add a reusable ErrorBoundary (class component, no new dep) with
resetKeys + an on-brand fallback, and wire it at two surfaces:
- WidgetHost: isolates the gate widget (reset on descriptor id) so a malformed
  gate payload no longer blanks the conversation
- SessionDocumentsPanel: wraps each document (reset on doc key/content) so one
  crashing viewer degrades only its section; siblings and the panel survive

The observed crash: a schema-linking doc that parses but lacks `candidates`
makes SchemaLinkingViewer throw. Verified live via Playwright against the mock
backend. TDD throughout; tsc clean, 123/123 vitest (+8 new tests).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-05 13:18:51 +02:00
co-authored by Claude Fable 5
parent 864b7ee29c
commit 2f68b0d109
6 changed files with 274 additions and 2 deletions
+7 -1
View File
@@ -3,6 +3,7 @@ import { X } from "lucide-react";
import { getSessionDocuments } from "../api/sessions";
import type { SessionDocument, SessionSummary } from "../api/types";
import { Button } from "../components/ui/button";
import { ErrorBoundary } from "../components/ErrorBoundary";
import { SqlViewer } from "../viewers/SqlViewer";
import { SchemaLinkingViewer } from "../viewers/SchemaLinkingViewer";
import { MarkdownView } from "../viewers/MarkdownView";
@@ -110,7 +111,12 @@ export function SessionDocumentsPanel({ session, onClose, onResume }: Props) {
</span>
)}
</div>
<DocBody doc={doc} />
{/* Isolate each document: a viewer that crashes on malformed
content degrades to a fallback in place, leaving the rest of
the panel intact instead of blanking the whole tree. */}
<ErrorBoundary resetKeys={[doc.key, doc.content]} label="document">
<DocBody doc={doc} />
</ErrorBoundary>
</section>
))}
</div>