Files
suaveplan/openspec/changes/enterprise-react-error-boundary/tasks.md
2026-05-27 23:29:53 +01:00

4.1 KiB

Tasks: enterprise-react-error-boundary

Change ID: enterprise-react-error-boundary Package: @suaveplan/react-error-boundary Target path: react/ui/react-error-boundary Prerequisite packages on Verdaccio: @suaveplan/error, @suaveplan/react-translate


0. Pre-flight (non-negotiable)

  • 0.1 Confirm clean working tree (git status reports nothing) and no in-flight worktrees with uncommitted work (git worktree list then git status in each)
  • 0.2 If any worktrees exist under .claude/worktrees/, account for each one: still-active agent, work-pending-merge, or stale-and-prune. Stale worktrees MUST be removed (git worktree remove) before opening a new one — code that has been lost twice on main is the failure mode this guards against.
  • 0.3 Confirm the package set in MANIFEST.md matches find packages -maxdepth 3 -name package.json count. Drift here means a prior change archived without regenerating the manifest.

1. Scaffold

  • Create directory tree per proposal layout.
  • Write package.json at 0.1.0 with "sideEffects": false, exports map.
  • Dependencies: @suaveplan/error workspace:^, @suaveplan/react-translate workspace:^, @opentelemetry/api catalog:observability, tslib catalog:build.
  • Write tsconfig.json, vitest.config.ts, scripts.

2. Types

  • Define FallbackProps: { error: Error; resetErrorBoundary: () => void }.
  • Define ErrorBoundaryProps: children, fallback?: ReactNode, FallbackComponent?: ComponentType<FallbackProps>, onError?: (error, info) => void, onReset?: () => void, resetKeys?: unknown[].
  • Define ErrorBoundaryState: { hasError: boolean; error: Error | null }.
  • Define UseErrorBoundaryReturn: { showBoundary: (error: Error) => void }.

3. DefaultFallback

  • Renders translated "Something went wrong" heading + error message (dev-only) + "Try again" button calling resetErrorBoundary.
  • Uses useTranslation from @suaveplan/react-translate.
  • Write default-fallback.test.tsx: renders heading, button calls reset. { timeout: 5000 }.
  • Write default-fallback.stories.tsx: default, with-error-message, playground.
  • Write default-fallback.md.

4. ErrorBoundary

  • Class component with static getDerivedStateFromError(error) and componentDidCatch(error, info).
  • componentDidCatch — calls this.props.onError(error, info); emits OTel span event error_boundary.caught with attributes error.message, error.type, and error.code (if error instanceof SuaveplanError).
  • resetKeys watching via componentDidUpdate — if any resetKeys[i] changed value, call reset().
  • Renders FallbackComponent if provided, else fallback prop, else DefaultFallback.
  • Write error-boundary.test.tsx: catch + fallback rendered, reset clears error, onError called, onReset called, resetKeys trigger reset, FallbackComponent receives props. { timeout: 5000 }.
  • Write error-boundary.stories.tsx: default (forced throw), custom FallbackComponent, resetKeys, playground.
  • Write error-boundary.md (≥200 words).

5. useErrorBoundary hook

  • Uses useState to hold a thrown error; throws it in render via useEffect → nearest boundary catches it.
  • Returns { showBoundary: (error: Error) => void }.
  • Write use-error-boundary.test.tsx: calling showBoundary causes boundary to catch. { timeout: 5000 }.
  • Write use-error-boundary.stories.tsx: playground (button calls showBoundary).
  • Write use-error-boundary.md.

6. E2E

  • e2e/error-boundary.spec.ts — Playwright:
    • Render component that throws on click → DefaultFallback shown.
    • Click "Try again" → component re-mounts without error.
    • FallbackComponent renders with correct error.message.

7. Verify and publish

  • bunx turbo lint typecheck test --filter=@suaveplan/react-error-boundary — green.
  • bunx turbo test:coverage --filter=@suaveplan/react-error-boundary — 100%.
  • bun scripts/publish.ts --package @suaveplan/react-error-boundary.
  • Archive change folder.