singularity-forge/web/components/sf/error-boundary.tsx
Mikael Hugo 6725a55591 feat(web): add error boundaries, expand test coverage, add README
- Add class-based ErrorBoundary component wrapping all 7 main views
  inside WorkspaceChrome; fallback shows view name, error, reload button
- Add 30 new unit tests (boot null-project path × 9, onboarding
  pure-function logic × 21); all 43 web/lib tests pass
- Add web/README.md: architecture, auth flow, 7 views, dev setup,
  API route pattern, test instructions

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-10 11:24:40 +02:00

57 lines
1.6 KiB
TypeScript

import { Component, type ErrorInfo, type ReactNode } from "react";
interface Props {
children: ReactNode;
viewName: string;
}
interface State {
hasError: boolean;
error: Error | null;
}
export class ErrorBoundary extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error: Error): State {
return { hasError: true, error };
}
componentDidCatch(error: Error, info: ErrorInfo): void {
console.error("[ErrorBoundary]", this.props.viewName, error, info);
}
render(): ReactNode {
if (this.state.hasError) {
return (
<div className="flex h-full w-full items-center justify-center p-8">
<div className="flex max-w-md flex-col gap-4 rounded-md border border-border bg-card p-6 text-center shadow-sm">
<p className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
{this.props.viewName}
</p>
<p className="text-sm font-semibold text-foreground">
Something went wrong in this view
</p>
{this.state.error && (
<p className="rounded bg-accent px-3 py-2 text-left font-mono text-xs text-muted-foreground">
{this.state.error.message}
</p>
)}
<button
type="button"
className="mt-2 rounded-md border border-border bg-background px-4 py-2 text-sm font-medium text-foreground transition-colors hover:bg-accent"
onClick={() => this.setState({ hasError: false, error: null })}
>
Reload view
</button>
</div>
</div>
);
}
return this.props.children;
}
}