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:
@@ -50,3 +50,26 @@ test("hides Resume for an archived session", async () => {
|
||||
await screen.findByText("Original question");
|
||||
expect(screen.queryByRole("button", { name: /resume/i })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("a malformed document renders a fallback without taking down its siblings", async () => {
|
||||
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
||||
// Valid JSON but missing `candidates` — SchemaLinkingViewer crashes on this shape.
|
||||
// Without a per-document boundary this crash blanks the whole panel (React 18
|
||||
// unmounts the tree); the good sibling document must still render.
|
||||
// resetHandlers(...) replaces the beforeEach handler so this response is used.
|
||||
server.resetHandlers(
|
||||
http.get("http://localhost:8787/sessions/s1/documents", () =>
|
||||
HttpResponse.json([
|
||||
{ phase: "F4", key: "schema", title: "Schema linking", format: "schema-linking", content: '{"joins":[]}' },
|
||||
{ phase: "—", key: "question", title: "Original question", format: "text", content: "SIBLING-SURVIVES" },
|
||||
]),
|
||||
),
|
||||
);
|
||||
wrap(<SessionDocumentsPanel session={base} onClose={vi.fn()} onResume={vi.fn()} />);
|
||||
// The healthy sibling rendered — the panel did not white-screen.
|
||||
expect(await screen.findByText("SIBLING-SURVIVES")).toBeInTheDocument();
|
||||
// The broken document degraded to a fallback in place.
|
||||
expect(screen.getByRole("alert")).toBeInTheDocument();
|
||||
expect(screen.getByText(/couldn't be displayed/i)).toBeInTheDocument();
|
||||
errorSpy.mockRestore();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user