diff --git a/web/src/app/(app)/workflows/[workflow_id]/@runs/error.tsx b/web/src/app/(app)/workflows/[workflow_id]/@runs/error.tsx
new file mode 100644
index 0000000..9df13d0
--- /dev/null
+++ b/web/src/app/(app)/workflows/[workflow_id]/@runs/error.tsx
@@ -0,0 +1,51 @@
+"use client";
+
+import { Button } from "@/components/ui/button";
+import {
+ Card,
+ CardDescription,
+ CardHeader,
+ CardTitle,
+} from "@/components/ui/card";
+// Error components must be Client Components
+import { useEffect } from "react";
+
+export default function Error({
+ error,
+ reset,
+}: {
+ error?: Error & { digest?: string };
+ reset?: () => void;
+}) {
+ useEffect(() => {
+ // Log the error to an error reporting service
+ console.log(error?.message);
+ }, [error]);
+
+ return (
+
+
+
+
+ Something went wrong!
+
+
+ {error?.message}
+
+
+
+
+
+ );
+}
diff --git a/web/src/app/(app)/workflows/[workflow_id]/layout.tsx b/web/src/app/(app)/workflows/[workflow_id]/layout.tsx
index bf73308..903a3b7 100644
--- a/web/src/app/(app)/workflows/[workflow_id]/layout.tsx
+++ b/web/src/app/(app)/workflows/[workflow_id]/layout.tsx
@@ -1,3 +1,6 @@
+import Error from "@/app/(app)/workflows/[workflow_id]/@runs/error";
+import { ErrorBoundary } from "@/components/ErrorBoundary";
+
export default async function Layout({
children,
deployment,
@@ -10,13 +13,15 @@ export default async function Layout({
workflow: React.ReactNode;
}) {
return (
-
-
- {workflow}
- {deployment}
+
}>
+
+
+ {workflow}
+ {deployment}
+
+ {runs}
+ {children}
- {runs}
- {children}
-
+
);
}
diff --git a/web/src/components/ErrorBoundary.tsx b/web/src/components/ErrorBoundary.tsx
new file mode 100644
index 0000000..12115de
--- /dev/null
+++ b/web/src/components/ErrorBoundary.tsx
@@ -0,0 +1,47 @@
+"use client";
+
+import type { ErrorInfo, ReactNode } from "react";
+import { Component } from "react";
+
+interface ErrorBoundaryProps {
+ fallback: ReactNode;
+ children: ReactNode;
+}
+
+interface ErrorBoundaryState {
+ hasError: boolean;
+}
+
+export class ErrorBoundary extends Component<
+ ErrorBoundaryProps,
+ ErrorBoundaryState
+> {
+ constructor(props: ErrorBoundaryProps) {
+ super(props);
+ this.state = { hasError: false };
+ }
+
+ static getDerivedStateFromError(_: Error): ErrorBoundaryState {
+ // Update state so the next render will show the fallback UI.
+ return { hasError: true };
+ }
+
+ componentDidCatch(error: Error, info: ErrorInfo) {
+ // Example "componentStack":
+ // in ComponentThatThrows (created by App)
+ // in ErrorBoundary (created by App)
+ // in div (created by App)
+ // in App
+ // TODO: Replace this with your own error reporting logic.
+ console.error("Uncaught error:", error, info);
+ }
+
+ render() {
+ if (this.state.hasError) {
+ // You can render any custom fallback UI
+ return this.props.fallback;
+ }
+
+ return this.props.children;
+ }
+}