diff --git a/src/client/components/ErrorBoundary.test.tsx b/src/client/components/ErrorBoundary.test.tsx
new file mode 100644
index 0000000..e7f881e
--- /dev/null
+++ b/src/client/components/ErrorBoundary.test.tsx
@@ -0,0 +1,50 @@
+// @vitest-environment jsdom
+import { describe, it, expect, vi, beforeEach } from "vitest";
+import React from "react";
+import { render, screen } from "@testing-library/react";
+import "@testing-library/jest-dom/vitest";
+import { ErrorBoundary } from "./ErrorBoundary";
+
+function ThrowingComponent({ shouldThrow }: { shouldThrow: boolean }) {
+ if (shouldThrow) {
+ throw new Error("Test render error");
+ }
+ return
Child content
;
+}
+
+describe("ErrorBoundary", () => {
+ beforeEach(() => {
+ // Suppress React error boundary console errors in test output
+ vi.spyOn(console, "error").mockImplementation(() => {});
+ });
+
+ it("renders children when no error occurs", () => {
+ render(
+
+
+
+ );
+ expect(screen.getByText("Child content")).toBeInTheDocument();
+ });
+
+ it("renders error UI when child throws", () => {
+ render(
+
+
+
+ );
+ expect(screen.getByText("Something went wrong")).toBeInTheDocument();
+ expect(screen.getByText("Test render error")).toBeInTheDocument();
+ });
+
+ it("shows try again button in error state", () => {
+ render(
+
+
+
+ );
+ expect(screen.getByText("Something went wrong")).toBeInTheDocument();
+ expect(screen.getByText("Try again")).toBeInTheDocument();
+ expect(screen.getByText("An error occurred while rendering this view.")).toBeInTheDocument();
+ });
+});
diff --git a/src/client/components/ErrorBoundary.tsx b/src/client/components/ErrorBoundary.tsx
new file mode 100644
index 0000000..78f5768
--- /dev/null
+++ b/src/client/components/ErrorBoundary.tsx
@@ -0,0 +1,57 @@
+import React from "react";
+
+interface Props {
+ children: React.ReactNode;
+}
+
+interface State {
+ hasError: boolean;
+ error: Error | null;
+}
+
+export class ErrorBoundary extends React.Component {
+ constructor(props: Props) {
+ super(props);
+ this.state = { hasError: false, error: null };
+ }
+
+ static getDerivedStateFromError(error: Error): State {
+ return { hasError: true, error };
+ }
+
+ render() {
+ if (!this.state.hasError) {
+ return this.props.children;
+ }
+
+ return (
+
+
+
+
Something went wrong
+
+ An error occurred while rendering this view.
+
+ {this.state.error && (
+
+ {this.state.error.message}
+
+ )}
+
+
+
+ );
+ }
+}