1a9e5e2c18
- Rules abstraction (5e + pf2e) behind one interface, fully tested - Pure combat engine: turn-order safe across add/remove/reorder/init-change - Dexie storage with real cascade deletes + Zod validation on write - Seedable dice engine with notation parser - Lazy SRD compendium (code-split), bestiary -> combat - Strict TS, 54 unit tests, Playwright e2e smoke (all green) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
51 lines
1.6 KiB
TypeScript
51 lines
1.6 KiB
TypeScript
import { Component, type ErrorInfo, type ReactNode } from 'react';
|
|
import { Button } from './Button';
|
|
|
|
interface Props {
|
|
children: ReactNode;
|
|
/** reset key — when it changes, the boundary clears its error */
|
|
resetKey?: string | number;
|
|
}
|
|
interface State {
|
|
error: Error | null;
|
|
}
|
|
|
|
/** Catches render errors so one broken screen never white-screens the whole app. */
|
|
export class ErrorBoundary extends Component<Props, State> {
|
|
override state: State = { error: null };
|
|
|
|
static getDerivedStateFromError(error: Error): State {
|
|
return { error };
|
|
}
|
|
|
|
override componentDidCatch(error: Error, info: ErrorInfo): void {
|
|
console.error('Render error caught by boundary:', error, info.componentStack);
|
|
}
|
|
|
|
override componentDidUpdate(prev: Props): void {
|
|
if (prev.resetKey !== this.props.resetKey && this.state.error) {
|
|
this.setState({ error: null });
|
|
}
|
|
}
|
|
|
|
override render(): ReactNode {
|
|
if (this.state.error) {
|
|
return (
|
|
<div className="m-6 rounded-lg border border-danger/40 bg-danger/10 p-6">
|
|
<h2 className="text-lg font-semibold text-danger">Something went wrong</h2>
|
|
<p className="mt-1 text-sm text-muted">
|
|
This screen hit an error. Your data is safe — try again or reload.
|
|
</p>
|
|
<pre className="mt-3 max-h-40 overflow-auto rounded bg-surface p-3 text-xs text-muted">
|
|
{this.state.error.message}
|
|
</pre>
|
|
<Button className="mt-4" variant="secondary" onClick={() => this.setState({ error: null })}>
|
|
Try again
|
|
</Button>
|
|
</div>
|
|
);
|
|
}
|
|
return this.props.children;
|
|
}
|
|
}
|