@xmachines/play-react 1.0.0-beta.2 → 1.0.0-beta.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,358 +1,306 @@
1
1
  # @xmachines/play-react
2
2
 
3
- **React renderer consuming signals and UI schema with provider pattern**
3
+ **React renderer for XMachines Play Architecture**
4
4
 
5
- Signal-driven React rendering layer observing actor state with zero React state for business logic.
5
+ Bridges TC39 Signal-driven actors to React's render cycle. Business logic stays in the actor; React is purely a rendering target.
6
6
 
7
7
  ## Overview
8
8
 
9
- `@xmachines/play-react` provides `PlayRenderer` and `useSignalEffect` for building React UIs that passively observe actor signals. This package enables framework-swappable architecture where React is just a rendering target subscribing to signal changes — business logic lives entirely in the actor.
9
+ `@xmachines/play-react` provides `PlayRenderer`, a React component that:
10
10
 
11
- Per [RFC Play v1](https://gitlab.com/xmachin-es/rfc/-/blob/main/src/play-v1.md), this package implements:
11
+ - Subscribes to `actor.currentView` (TC39 Signal) and re-renders on every state transition
12
+ - Renders the current view's JSON spec via `@json-render/react`
13
+ - Routes action names from spec elements to `actor.send()` via the `actions` prop
14
+ - Manages per-view UI state in an `@xstate/store` atom (automatic or caller-supplied)
12
15
 
13
- - **Signal-Only Reactivity (INV-05):** No useState/useReducer for business logic, signals only
14
- - **Passive Infrastructure (INV-04):** Components observe signals, send events to actor
16
+ Per [Play RFC](../docs/rfc/play.md):
15
17
 
16
- **Key Principle:** React state is never used for business logic. Signals are the source of truth.
17
-
18
- Renderer receives actor via props (provider pattern), not children.
18
+ - **Actor Authority (INV-01):** Guards in the machine decide all state transitions
19
+ - **Passive Infrastructure (INV-04):** React observes signals and dispatches events — never decides
20
+ - **Signal-Only Reactivity (INV-05):** `actor.currentView` signal is the sole render trigger
19
21
 
20
22
  ## Installation
21
23
 
22
24
  ```bash
23
- npm install react@^18.0.0 react-dom@^18.0.0
24
25
  npm install @xmachines/play-react
26
+ npm install @json-render/react @json-render/core # peer deps
27
+ npm install @json-render/xstate @xstate/store # store integration
25
28
  ```
26
29
 
27
30
  ## Current Exports
28
31
 
29
- - `PlayRenderer`
30
- - `useSignalEffect`
32
+ - `PlayRenderer` — main renderer component
33
+ - `useActor` — hook for accessing the actor inside a `PlayRenderer` tree
34
+ - `useSignalEffect` — React hook for subscribing to TC39 Signals
35
+ - `PlayErrorBoundary` — error boundary wrapping renderer output
36
+ - `defineRegistry` — re-exported from `@json-render/react`
37
+ - `useStateBinding` — re-exported from `@json-render/react`
38
+ - `ComponentFn` (type) — re-exported from `@json-render/react`
39
+ - `ComponentContext` (type) — re-exported from `@json-render/react`
31
40
  - `PlayRendererProps` (type)
32
-
33
- **Peer dependencies:**
34
-
35
- - `react` ^18.0.0 - React runtime
36
- - `react-dom` ^18.0.0 - React DOM renderer
41
+ - `PlayActor` (type)
37
42
 
38
43
  ## Quick Start
39
44
 
40
- ```typescript
41
- import { createRoot } from "react-dom/client";
42
- import { definePlayer } from "@xmachines/play-xstate";
43
- import { defineCatalog } from "@xmachines/play-catalog";
45
+ ```tsx
46
+ import { definePlayer, formatPlayRouteTransitions } from "@xmachines/play-xstate";
44
47
  import { PlayRenderer } from "@xmachines/play-react";
48
+ import { defineCatalog } from "@json-render/core";
49
+ import { defineRegistry } from "@xmachines/play-react";
50
+ import type { ComponentFn } from "@xmachines/play-react";
51
+ import { setup, assign } from "xstate";
45
52
  import { z } from "zod";
46
53
 
47
- // 1. Define catalog (business logic layer)
54
+ // 1. Define catalog the contract between machine spec and UI components
48
55
  const catalog = defineCatalog({
49
- LoginForm: z.object({ error: z.string().optional() }),
50
- Dashboard: z.object({
51
- userId: z.string(),
52
- username: z.string(),
53
- }),
56
+ elements: {
57
+ Login: { props: z.object({ title: z.string() }), description: "Login form" },
58
+ Dashboard: { props: z.object({ username: z.string() }), description: "Dashboard" },
59
+ },
54
60
  });
55
61
 
56
- // 2. Create React components (view layer)
57
- const components = {
58
- LoginForm: ({ error, send }) => (
59
- <form
60
- onSubmit={(e) => {
61
- e.preventDefault();
62
- const data = new FormData(e.currentTarget);
63
- send({
64
- type: "auth.login",
65
- username: data.get("username"),
66
- password: data.get("password"),
67
- });
68
- }}
69
- >
70
- {error && <p style={{ color: "red" }}>{error}</p>}
71
- <input name="username" required placeholder="Username" />
72
- <input name="password" type="password" required placeholder="Password" />
73
- <button type="submit">Log In</button>
74
- </form>
75
- ),
76
- Dashboard: ({ userId, username, send }) => (
77
- <div>
78
- <h1>Welcome, {username}!</h1>
79
- <p>User ID: {userId}</p>
80
- <button onClick={() => send({ type: "auth.logout" })}>Log Out</button>
81
- </div>
82
- ),
83
- };
84
-
85
- // 3. Create player actor (business logic runtime)
86
- const createPlayer = definePlayer({ machine: authMachine, catalog });
62
+ // 2. Implement components using ComponentFn — typed against catalog entries
63
+ const Login: ComponentFn<typeof catalog, "Login"> = ({ props, emit }) => (
64
+ <form
65
+ onSubmit={(e) => {
66
+ e.preventDefault();
67
+ emit("submit");
68
+ }}
69
+ >
70
+ <h2>{props.title}</h2>
71
+ <button type="submit">Log In</button>
72
+ </form>
73
+ );
74
+
75
+ const Dashboard: ComponentFn<typeof catalog, "Dashboard"> = ({ props }) => (
76
+ <div>Welcome, {props.username}!</div>
77
+ );
78
+
79
+ // 3. Build registry — wires components to catalog and declares no-op action stubs
80
+ const { registry } = defineRegistry(catalog, {
81
+ components: { Login, Dashboard },
82
+ actions: { login: async () => {}, logout: async () => {} },
83
+ });
84
+
85
+ // 4. Define machine with view metadata
86
+ const machine = setup({
87
+ types: {
88
+ context: {} as {
89
+ isAuthenticated: boolean;
90
+ username: string | null;
91
+ routeParams: Record<string, string>;
92
+ queryParams: Record<string, string>;
93
+ },
94
+ events: {} as
95
+ | { type: "auth.login"; username: string }
96
+ | { type: "auth.logout" }
97
+ | { type: "play.route"; to: string; params?: Record<string, string> },
98
+ },
99
+ }).createMachine(
100
+ formatPlayRouteTransitions({
101
+ id: "app",
102
+ initial: "login",
103
+ context: { isAuthenticated: false, username: null, routeParams: {}, queryParams: {} },
104
+ states: {
105
+ login: {
106
+ id: "login",
107
+ meta: {
108
+ route: "/login",
109
+ view: {
110
+ component: "Login",
111
+ spec: {
112
+ root: "root",
113
+ elements: {
114
+ root: { type: "Login", props: { title: "Sign In" }, children: [] },
115
+ },
116
+ },
117
+ },
118
+ },
119
+ },
120
+ dashboard: {
121
+ id: "dashboard",
122
+ meta: {
123
+ route: "/dashboard",
124
+ view: {
125
+ component: "Dashboard",
126
+ spec: {
127
+ root: "root",
128
+ elements: {
129
+ root: { type: "Dashboard", props: { username: "" }, children: [] },
130
+ },
131
+ },
132
+ },
133
+ },
134
+ },
135
+ },
136
+ on: {
137
+ "auth.login": {
138
+ target: ".dashboard",
139
+ guard: ({ context }) => !context.isAuthenticated,
140
+ actions: assign({
141
+ isAuthenticated: true,
142
+ username: ({ event }) => event.username,
143
+ }),
144
+ },
145
+ "auth.logout": {
146
+ target: ".login",
147
+ guard: ({ context }) => context.isAuthenticated,
148
+ actions: assign({ isAuthenticated: false, username: null }),
149
+ },
150
+ },
151
+ }),
152
+ );
153
+
154
+ // 5. Create actor and render
155
+ const createPlayer = definePlayer({ machine });
87
156
  const actor = createPlayer();
88
157
  actor.start();
89
158
 
90
- // 4. Render UI (actor via props)
91
- const root = createRoot(document.getElementById("app")!);
92
- root.render(<PlayRenderer actor={actor} components={components} />);
159
+ function App() {
160
+ return (
161
+ <PlayRenderer
162
+ actor={actor}
163
+ registry={registry}
164
+ actions={{ login: "auth.login", logout: "auth.logout" }}
165
+ />
166
+ );
167
+ }
93
168
  ```
94
169
 
95
170
  ## API Reference
96
171
 
97
- ### PlayRenderer
172
+ ### `PlayRenderer`
98
173
 
99
- Main renderer component subscribing to actor signals and dynamically rendering catalog components:
174
+ Main component. Subscribes to `actor.currentView` and renders the spec.
100
175
 
101
- ```typescript
102
- interface PlayRendererProps {
103
- actor: AbstractActor<any>;
104
- components: Record<string, React.ComponentType<any>>;
105
- fallback?: React.ReactNode;
106
- }
176
+ ```tsx
177
+ <PlayRenderer
178
+ actor={actor} // required
179
+ registry={registry} // required
180
+ actions={{ login: "auth.login" }} // optional
181
+ store={myStore} // optional — controlled mode
182
+ fallback={<p>Loading…</p>} // optional
183
+ />
107
184
  ```
108
185
 
109
- **Props:**
110
-
111
- - `actor` - Actor instance with `currentView` signal
112
- - `components` - Map of component names to React components
113
- - `fallback` - Component shown when `currentView` is null (default: `null`)
114
-
115
- **Behavior:**
186
+ **`actor`** — A `PlayerActor` (or any `AbstractActor & Viewable`). Provides the `currentView` signal.
116
187
 
117
- 1. Subscribes to `actor.currentView` signal via `useSignalEffect`
118
- 2. Looks up component from `components` map using `view.component` string
119
- 3. Renders component with props from `view.props` + `send` function
188
+ **`registry`** Built with `defineRegistry(catalog, { components, actions })` from `@xmachines/play-react`.
120
189
 
121
- **Example:**
190
+ **`actions`** — Maps json-render action names (from spec `on` bindings) to XState event type strings. Values are type-checked against `EventFromLogic<TLogic>["type"]` when `TLogic` is specified:
122
191
 
123
- ```typescript
124
- <PlayRenderer
192
+ ```tsx
193
+ // Typed: "bad.event" causes a compile error if it is not in the machine's event union
194
+ <PlayRenderer<typeof machine>
125
195
  actor={actor}
126
- components={{
127
- HomePage: ({ send }) => <div>Home</div>,
128
- AboutPage: ({ send }) => <div>About</div>,
129
- }}
130
- fallback={<div>Loading...</div>}
196
+ registry={registry}
197
+ actions={{ login: "auth.login", logout: "auth.logout" }}
131
198
  />
132
199
  ```
133
200
 
134
- ### useSignalEffect()
135
-
136
- Hook for subscribing to signal changes with automatic cleanup:
137
-
138
- ```typescript
139
- useSignalEffect(() => {
140
- const value = signal.get();
141
- // React re-renders when signal changes
142
- });
143
- ```
201
+ **`store`** (optional) — Controls per-view UI state (form values, `$state` bindings):
144
202
 
145
- **Behavior:**
203
+ - **Omitted (uncontrolled, default):** A fresh `@xstate/store` atom is created per view transition, seeded from `view.spec.state`. The atom resets automatically on each state transition.
204
+ - **Provided (controlled):** The caller owns the store lifecycle. `spec.state` is ignored.
146
205
 
147
- - Tracks signal dependencies automatically via `Signal.Computed` wrapper
148
- - Uses `Signal.subtle.Watcher` with microtask batching
149
- - Triggers React state update to force re-render
150
- - Cleans up watcher on unmount with explicit `unwatch`
206
+ ```tsx
207
+ import { createAtom } from "@xstate/store";
208
+ import { xstateStoreStateStore } from "@json-render/xstate";
209
+ import type { StateStore } from "@json-render/core";
151
210
 
152
- **Canonical watcher lifecycle:**
211
+ const store: StateStore = xstateStoreStateStore({ atom: createAtom({ username: "alice" }) });
153
212
 
154
- 1. `notify`
155
- 2. `queueMicrotask`
156
- 3. drain pending work (`getPending` and/or `Computed.get`)
157
- 4. run effect + trigger render
158
- 5. re-arm watcher via `watch()`
213
+ <PlayRenderer actor={actor} registry={registry} store={store} actions={{ login: "auth.login" }} />;
214
+ ```
159
215
 
160
- Watcher notification is one-shot, so re-arm and explicit cleanup are both required.
216
+ **`fallback`** Shown when `actor.currentView.get()` is `null` (machine in a no-view state, or during initialisation).
161
217
 
162
- **Example:**
218
+ ---
163
219
 
164
- ```typescript
165
- import { useSignalEffect } from "@xmachines/play-react";
166
- import { useState } from "react";
220
+ ### `useActor`
167
221
 
168
- function RouteDisplay({ actor }: { actor: AbstractActor<any> }) {
169
- const [route, setRoute] = useState<string | null>(null);
222
+ Hook for accessing the actor from inside any component rendered by `PlayRenderer`. No prop drilling needed.
170
223
 
171
- useSignalEffect(() => {
172
- const currentRoute = actor.currentRoute.get();
173
- setRoute(currentRoute);
174
- });
224
+ ```tsx
225
+ import { useActor } from "@xmachines/play-react";
175
226
 
176
- return <div>Current Route: {route ?? "None"}</div>;
227
+ // Works in any component rendered inside PlayRenderer:
228
+ function LogoutButton() {
229
+ const actor = useActor();
230
+ return <button onClick={() => actor.send({ type: "auth.logout" })}>Log Out</button>;
177
231
  }
178
232
  ```
179
233
 
180
- **Complete API:** See [API Documentation](../../docs/api/@xmachines/play-react)
181
-
182
- ## Examples
234
+ Throws `"useActor() must be called inside <PlayRenderer>"` if called outside the tree.
183
235
 
184
- ### Component Receiving Props from Catalog
236
+ ---
185
237
 
186
- ```typescript
187
- import { PlayRenderer } from "@xmachines/play-react";
188
- import { defineCatalog } from "@xmachines/play-catalog";
189
- import { z } from "zod";
190
-
191
- // Define schema in catalog
192
- const catalog = defineCatalog({
193
- UserProfile: z.object({
194
- userId: z.string(),
195
- name: z.string(),
196
- avatar: z.string().url().optional(),
197
- stats: z.object({
198
- posts: z.number(),
199
- followers: z.number(),
200
- }),
201
- }),
202
- });
203
-
204
- // Component receives type-safe props + send
205
- const components = {
206
- UserProfile: ({ userId, name, avatar, stats, send }) => (
207
- <div>
208
- {avatar && <img src={avatar} alt={name} />}
209
- <h1>{name}</h1>
210
- <p>ID: {userId}</p>
211
- <div>
212
- <span>{stats.posts} posts</span>
213
- <span>{stats.followers} followers</span>
214
- </div>
215
- <button
216
- onClick={() =>
217
- send({
218
- type: "profile.edit",
219
- userId,
220
- })
221
- }
222
- >
223
- Edit Profile
224
- </button>
225
- </div>
226
- ),
227
- };
228
-
229
- <PlayRenderer actor={actor} components={components} />;
230
- ```
238
+ ### `useSignalEffect`
231
239
 
232
- ### useSignalEffect for Custom Rendering
240
+ Hook for subscribing to TC39 Signals in React components that live outside a `PlayRenderer` tree (e.g. nav bars, status indicators driven by actor state).
233
241
 
234
- ```typescript
242
+ ```tsx
235
243
  import { useSignalEffect } from "@xmachines/play-react";
236
- import { AbstractActor } from "@xmachines/play-actor";
237
244
 
238
- function CustomRenderer({ actor }: { actor: AbstractActor<any> }) {
239
- const [view, setView] = useState(null);
245
+ function NavBar({ actor }: { actor: ReturnType<typeof createPlayer> }) {
246
+ const [isAuth, setIsAuth] = useState(false);
240
247
 
241
- // Subscribe to currentView signal
242
248
  useSignalEffect(() => {
243
- const currentView = actor.currentView.get();
244
- setView(currentView);
249
+ const snap = actor.state.get();
250
+ setIsAuth((snap.context as { isAuthenticated: boolean }).isAuthenticated);
245
251
  });
246
252
 
247
- if (!view) return <div>No view</div>;
248
-
249
- // Custom rendering logic
250
- if (view.component === "SpecialCase") {
251
- return <SpecialCaseComponent {...view.props} actor={actor} />;
252
- }
253
-
254
- // Fallback to standard rendering
255
- return <DefaultComponent view={view} actor={actor} />;
253
+ return <nav>{isAuth ? <LogoutBtn /> : <LoginBtn />}</nav>;
256
254
  }
257
255
  ```
258
256
 
259
- ### Provider Pattern
257
+ ---
260
258
 
261
- ```typescript
262
- import { PlayTanStackRouterProvider } from "@xmachines/play-tanstack-react-router";
263
- import { PlayRenderer } from "@xmachines/play-react";
259
+ ### `PlayErrorBoundary`
264
260
 
265
- // Renderer receives actor via props (not children)
266
- function App() {
267
- return (
268
- <PlayTanStackRouterProvider
269
- actor={actor}
270
- router={router}
271
- renderer={(currentActor, currentRouter) => {
272
- void currentRouter;
273
- return (
274
- <div>
275
- <Header actor={currentActor} />
276
- <PlayRenderer actor={currentActor} components={components} />
277
- <Footer />
278
- </div>
279
- );
280
- }}
281
- />
282
- );
283
- }
261
+ Class error boundary that wraps the rendered output. Catches errors thrown during component render and logs them without crashing the full page. `PlayRenderer` wraps its own output in this boundary automatically.
284
262
 
285
- // Header component also receives actor
286
- function Header({ actor }: { actor: AbstractActor<any> }) {
287
- const [route, setRoute] = useState<string | null>(null);
263
+ `componentDidCatch` invokes the `onError` prop (for observability tools) but does **not** re-throw — re-throwing from `componentDidCatch` can unmount the entire React 19 root. `getDerivedStateFromError` handles fallback state transition instead.
288
264
 
289
- useSignalEffect(() => {
290
- setRoute(actor.currentRoute.get());
291
- });
265
+ ```tsx
266
+ import { PlayErrorBoundary } from "@xmachines/play-react";
292
267
 
293
- return (
294
- <header>
295
- <nav>Current: {route}</nav>
296
- </header>
297
- );
298
- }
268
+ <PlayErrorBoundary
269
+ fallback={<p>Something went wrong.</p>}
270
+ onError={(err, info) => Sentry.captureException(err, { extra: info })}
271
+ >
272
+ <PlayRenderer actor={actor} registry={registry} />
273
+ </PlayErrorBoundary>;
299
274
  ```
300
275
 
301
- ## Architecture
276
+ ---
302
277
 
303
- This package implements **Signal-Only Reactivity (INV-05)** and **Passive Infrastructure (INV-04)**:
278
+ ## Route Parameters in Props
304
279
 
305
- 1. **No Business Logic in React:**
306
- - No useState/useReducer for business state
307
- - No useEffect for side effects
308
- - React only triggers renders, doesn't control state
280
+ When using `formatPlayRouteTransitions`, URL path parameters flow automatically into component props. Declare an `undefined` slot in the spec to opt in:
309
281
 
310
- 2. **Signals as Source of Truth:**
311
- - `actor.currentView.get()` provides UI structure
312
- - `actor.currentRoute.get()` provides navigation state
313
- - Components observe signals via `useSignalEffect`
314
-
315
- 3. **Event Forwarding:**
316
- - Components receive `send` function via props
317
- - User actions send events to actor (e.g., `{ type: "auth.login" }`)
318
- - Actor guards validate and process events
319
-
320
- 4. **Microtask Batching:**
321
- - `Signal.subtle.Watcher` coalesces rapid signal changes
322
- - Prevents React thrashing from multiple signal updates
323
- - Single React render per microtask batch
324
-
325
- 5. **Explicit Disposal Contract:**
326
- - Component teardown must call watcher `unwatch` in cleanup
327
- - Do not rely on GC-only cleanup
328
-
329
- **Pattern:**
330
-
331
- - Renderer receives actor via props (provider pattern)
332
- - Enables composition with navigation, headers, footers
333
- - Supports multiple renderers in same app
334
-
335
- **Architectural Invariants:**
282
+ ```ts
283
+ // spec: { section: undefined, user: "alice" }
284
+ // After play.route to /settings/profile → context.routeParams = { section: "profile" }
285
+ // Component receives: { section: "profile", user: "alice" }
286
+ ```
336
287
 
337
- - **Signal-Only Reactivity (INV-05):** No React state for business logic
338
- - **Passive Infrastructure (INV-04):** Components reflect, never decide
288
+ Priority: **route param fills `undefined` slots; explicit non-`undefined` spec props always win.**
339
289
 
340
- ## Benefits
290
+ ---
341
291
 
342
- - **Framework Swappable:** Business logic has zero React imports
343
- - **Type Safety:** Props validated against catalog schemas
344
- - **Simple Testing:** Test actors without React renderer
345
- - **Performance:** Microtask batching reduces unnecessary renders
346
- - **Composability:** Renderer prop enables complex layouts ()
292
+ ## Error Handling
347
293
 
348
- ## Related Packages
294
+ | Error | Cause | Fix |
295
+ | ------------------------------------------------- | ---------------------------- | ---------------------------------------------------------------- |
296
+ | `useActor() must be called inside <PlayRenderer>` | Hook called outside the tree | Move inside a component rendered by `PlayRenderer` |
297
+ | Component render error | Component throws | `PlayErrorBoundary` catches it; inspect component implementation |
349
298
 
350
- - **[@xmachines/play-xstate](../play-xstate)** - XState adapter providing actors
351
- - **[@xmachines/play-catalog](../play-catalog)** - UI schema validation
352
- - **[@xmachines/play-tanstack-react-router](../play-tanstack-react-router)** - TanStack Router integration
353
- - **[@xmachines/play-actor](../play-actor)** - Actor base
354
- - **[@xmachines/play-signals](../play-signals)** - TC39 Signals primitives
299
+ ---
355
300
 
356
- ## License
301
+ ## Architecture Notes
357
302
 
358
- MIT
303
+ - React `useState` is **only** used to trigger re-renders — never for business logic
304
+ - `actor.currentView` (TC39 Signal) is the sole render trigger; `PlayRenderer` is a passive observer
305
+ - Per-view UI state lives in an `@xstate/store` atom, not in React state
306
+ - `@json-render/react` drives rendering; `PlayRenderer` is the signal bridge — import `defineRegistry`, `ComponentFn`, `ComponentContext`, and `useStateBinding` from `@xmachines/play-react`
@@ -0,0 +1,55 @@
1
+ /**
2
+ * PlayErrorBoundary - React error boundary for catching catalog component render errors
3
+ *
4
+ * @packageDocumentation
5
+ */
6
+ import React from "react";
7
+ /**
8
+ * Props for PlayErrorBoundary
9
+ *
10
+ * @public
11
+ */
12
+ export interface PlayErrorBoundaryProps {
13
+ /** Fallback UI to render when a child component throws. Defaults to null. */
14
+ fallback?: React.ReactNode;
15
+ /** Child components to render */
16
+ children: React.ReactNode;
17
+ /** Optional error handler callback — forwards errors to observability tools (Sentry, etc.) */
18
+ onError?: (error: Error, info: React.ErrorInfo) => void;
19
+ }
20
+ /**
21
+ * Internal state shape for PlayErrorBoundary
22
+ *
23
+ * @public
24
+ */
25
+ export interface PlayErrorBoundaryState {
26
+ hasError: boolean;
27
+ error: Error | null;
28
+ }
29
+ /**
30
+ * React class component error boundary for catching catalog component render errors.
31
+ *
32
+ * Wraps catalog component renders so failures are caught and forwarded to standard
33
+ * React error boundary protocol. Consumers can attach the `onError` prop to forward
34
+ * errors to production observability tools (Sentry, Datadog, etc.).
35
+ *
36
+ * **React 19 safety (Phase 29):** `componentDidCatch` calls `onError` for observability
37
+ * but does NOT re-throw. `getDerivedStateFromError` already sets the fallback state —
38
+ * re-throwing from `componentDidCatch` can unmount the entire React 19 root.
39
+ *
40
+ * Per CONS-14: Class component pattern works with all React versions (18 and 19).
41
+ *
42
+ * @example
43
+ * ```tsx
44
+ * <PlayErrorBoundary fallback={<div>Something went wrong</div>} onError={Sentry.captureException}>
45
+ * <CatalogComponent {...props} />
46
+ * </PlayErrorBoundary>
47
+ * ```
48
+ */
49
+ export declare class PlayErrorBoundary extends React.Component<PlayErrorBoundaryProps, PlayErrorBoundaryState> {
50
+ constructor(props: PlayErrorBoundaryProps);
51
+ static getDerivedStateFromError(error: Error): PlayErrorBoundaryState;
52
+ componentDidCatch(error: Error, info: React.ErrorInfo): void;
53
+ render(): React.ReactNode;
54
+ }
55
+ //# sourceMappingURL=PlayErrorBoundary.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PlayErrorBoundary.d.ts","sourceRoot":"","sources":["../src/PlayErrorBoundary.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IACtC,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,iCAAiC;IACjC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,8FAA8F;IAC9F,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,SAAS,KAAK,IAAI,CAAC;CACxD;AAED;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IACtC,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAAa,iBAAkB,SAAQ,KAAK,CAAC,SAAS,CACrD,sBAAsB,EACtB,sBAAsB,CACtB;gBACY,KAAK,EAAE,sBAAsB;IAKzC,MAAM,CAAC,wBAAwB,CAAC,KAAK,EAAE,KAAK,GAAG,sBAAsB;IAI5D,iBAAiB,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,SAAS,GAAG,IAAI;IAI5D,MAAM,IAAI,KAAK,CAAC,SAAS;CAMlC"}