@xmachines/play-react 1.0.0-beta.21 → 1.0.0-beta.22
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 +20 -0
- package/dist/PlayRenderer.d.ts.map +1 -1
- package/dist/PlayRenderer.js +2 -2
- package/dist/PlayRenderer.js.map +1 -1
- package/dist/types.d.ts +14 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/useSignalEffect.d.ts +6 -0
- package/dist/useSignalEffect.d.ts.map +1 -1
- package/dist/useSignalEffect.js +23 -13
- package/dist/useSignalEffect.js.map +1 -1
- package/package.json +7 -5
package/README.md
CHANGED
|
@@ -27,6 +27,10 @@ npm install @json-render/react @json-render/core # peer deps
|
|
|
27
27
|
npm install @json-render/xstate @xstate/store # store integration
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
+
In this monorepo, the root install applies a `patch-package` patch to `@json-render/react`
|
|
31
|
+
so `defineRegistry(..., { onRenderError })` can intercept inner element-boundary errors
|
|
32
|
+
without muting console output.
|
|
33
|
+
|
|
30
34
|
## Current Exports
|
|
31
35
|
|
|
32
36
|
- `PlayRenderer` — main renderer component
|
|
@@ -187,6 +191,9 @@ Main component. Subscribes to `actor.currentView` and renders the spec.
|
|
|
187
191
|
|
|
188
192
|
**`registry`** — Built with `defineRegistry(catalog, { components, actions })` from `@xmachines/play-react`.
|
|
189
193
|
|
|
194
|
+
`defineRegistry` also accepts `onRenderError(error, elementType)`, which receives errors
|
|
195
|
+
caught by `@json-render/react`'s inner element boundary before the default logger is used.
|
|
196
|
+
|
|
190
197
|
**`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:
|
|
191
198
|
|
|
192
199
|
```tsx
|
|
@@ -213,6 +220,19 @@ const store: StateStore = xstateStoreStateStore({ atom: createAtom({ username: "
|
|
|
213
220
|
<PlayRenderer actor={actor} registry={registry} store={store} actions={{ login: "auth.login" }} />;
|
|
214
221
|
```
|
|
215
222
|
|
|
223
|
+
**Inner render errors** — You can intercept catalog component render failures without
|
|
224
|
+
overriding the outer `PlayErrorBoundary`:
|
|
225
|
+
|
|
226
|
+
```tsx
|
|
227
|
+
const { registry } = defineRegistry(catalog, {
|
|
228
|
+
components: { Login, Dashboard },
|
|
229
|
+
actions: { login: async () => {}, logout: async () => {} },
|
|
230
|
+
onRenderError(error, elementType) {
|
|
231
|
+
reportExpectedRenderError(error, elementType);
|
|
232
|
+
},
|
|
233
|
+
});
|
|
234
|
+
```
|
|
235
|
+
|
|
216
236
|
**`fallback`** — Shown when `actor.currentView.get()` is `null` (machine in a no-view state, or during initialisation).
|
|
217
237
|
|
|
218
238
|
---
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlayRenderer.d.ts","sourceRoot":"","sources":["../src/PlayRenderer.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAA2B,MAAM,OAAO,CAAC;AAOhD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAYpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,
|
|
1
|
+
{"version":3,"file":"PlayRenderer.d.ts","sourceRoot":"","sources":["../src/PlayRenderer.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAA2B,MAAM,OAAO,CAAC;AAOhD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAYpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CAqEpD,CAAC"}
|
package/dist/PlayRenderer.js
CHANGED
|
@@ -57,7 +57,7 @@ function createViewStore(initialState) {
|
|
|
57
57
|
* @param props - Component props
|
|
58
58
|
* @returns React element rendering current view from actor
|
|
59
59
|
*/
|
|
60
|
-
export const PlayRenderer = ({ actor, registry, store: externalStore, fallback = null, actions = {}, }) => {
|
|
60
|
+
export const PlayRenderer = ({ actor, registry, store: externalStore, fallback = null, onError, actions = {}, }) => {
|
|
61
61
|
// React state for triggering re-renders (NOT business logic state)
|
|
62
62
|
// Signal is source of truth, useState is just React's render trigger
|
|
63
63
|
const [view, setView] = useState(() => actor.currentView.get());
|
|
@@ -98,6 +98,6 @@ export const PlayRenderer = ({ actor, registry, store: externalStore, fallback =
|
|
|
98
98
|
actionName,
|
|
99
99
|
async (params = {}) => actor.send({ type: eventType, ...params }),
|
|
100
100
|
]));
|
|
101
|
-
return (_jsx(ActorContext.Provider, { value: actor, children: _jsx(PlayErrorBoundary, { fallback: fallback, children: _jsx(StateProvider, { store: store, children: _jsx(ActionProvider, { handlers: handlers, children: _jsx(VisibilityProvider, { children: _jsx(Renderer, { spec: view.spec ?? null, registry: registry }) }) }) }) }) }));
|
|
101
|
+
return (_jsx(ActorContext.Provider, { value: actor, children: _jsx(PlayErrorBoundary, { fallback: fallback, ...(onError && { onError }), children: _jsx(StateProvider, { store: store, children: _jsx(ActionProvider, { handlers: handlers, children: _jsx(VisibilityProvider, { children: _jsx(Renderer, { spec: view.spec ?? null, registry: registry }) }) }) }) }) }));
|
|
102
102
|
};
|
|
103
103
|
//# sourceMappingURL=PlayRenderer.js.map
|
package/dist/PlayRenderer.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlayRenderer.js","sourceRoot":"","sources":["../src/PlayRenderer.tsx"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"PlayRenderer.js","sourceRoot":"","sources":["../src/PlayRenderer.tsx"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -22,6 +22,8 @@ import type { AnyActorLogic, EventFromLogic } from "xstate";
|
|
|
22
22
|
* store is the single source of truth for UI state. When omitted, a fresh
|
|
23
23
|
* @xstate/store atom is created internally per view transition, seeded from spec.state.
|
|
24
24
|
* @property fallback - Optional component shown when currentView is null
|
|
25
|
+
* @property onError - Optional callback invoked when a catalog component throws during render.
|
|
26
|
+
* Receives the error and React ErrorInfo — use to forward to Sentry, Datadog, etc.
|
|
25
27
|
* @property actions - Maps json-render action names to XState event type strings.
|
|
26
28
|
* Values are constrained to `EventFromLogic<TLogic>["type"]` — passing a non-existent
|
|
27
29
|
* event type string is a compile error when TLogic is specified.
|
|
@@ -40,8 +42,19 @@ export interface PlayRendererProps<TLogic extends AnyActorLogic = AnyActorLogic>
|
|
|
40
42
|
* new view, mirroring the actor's currentView lifecycle.
|
|
41
43
|
*/
|
|
42
44
|
store?: StateStore;
|
|
43
|
-
/** Optional component shown when currentView is null */
|
|
45
|
+
/** Optional component shown when currentView is null or a catalog component throws */
|
|
44
46
|
fallback?: React.ReactNode;
|
|
47
|
+
/**
|
|
48
|
+
* Optional error handler callback invoked when a catalog component throws during render.
|
|
49
|
+
* Forwarded directly to the internal `PlayErrorBoundary` — use to integrate with
|
|
50
|
+
* production observability tools (Sentry, Datadog, etc.).
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```tsx
|
|
54
|
+
* <PlayRenderer actor={actor} registry={registry} onError={Sentry.captureException} />
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
onError?: (error: Error, info: React.ErrorInfo) => void;
|
|
45
58
|
/**
|
|
46
59
|
* Maps json-render action names to XState event type strings.
|
|
47
60
|
* Values are constrained to valid event types for TLogic — wrong event type strings
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACrE,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AAE5D
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACrE,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AAE5D;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,WAAW,iBAAiB,CAAC,MAAM,SAAS,aAAa,GAAG,aAAa;IAC9E,4EAA4E;IAC5E,KAAK,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG,QAAQ,CAAC;IAExC,oEAAoE;IACpE,QAAQ,EAAE,iBAAiB,CAAC;IAE5B;;;;;;;OAOG;IACH,KAAK,CAAC,EAAE,UAAU,CAAC;IAEnB,sFAAsF;IACtF,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAE3B;;;;;;;;;OASG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,SAAS,KAAK,IAAI,CAAC;IAExD;;;;OAIG;IACH,OAAO,CAAC,EAAE;QAAE,CAAC,UAAU,EAAE,MAAM,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAA;KAAE,CAAC;CACnE"}
|
|
@@ -51,6 +51,12 @@
|
|
|
51
51
|
* **Implementation note:** We wrap the callback in Signal.Computed because
|
|
52
52
|
* Signal.subtle.Watcher cannot automatically track arbitrary function calls.
|
|
53
53
|
* The Computed handles dependency tracking, and the Watcher monitors it.
|
|
54
|
+
*
|
|
55
|
+
* **Memory safety (Phase 29):**
|
|
56
|
+
* - `disposed` flag prevents post-cleanup callback execution: if cleanup is
|
|
57
|
+
* called before a pending microtask fires, the microtask returns early.
|
|
58
|
+
* - `needsEnqueue` guard dedups rapid synchronous signal changes: only one
|
|
59
|
+
* microtask is ever queued per batch of synchronous mutations.
|
|
54
60
|
*/
|
|
55
61
|
export declare const useSignalEffect: (callback: () => void | (() => void)) => void;
|
|
56
62
|
//# sourceMappingURL=useSignalEffect.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useSignalEffect.d.ts","sourceRoot":"","sources":["../src/useSignalEffect.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAWH
|
|
1
|
+
{"version":3,"file":"useSignalEffect.d.ts","sourceRoot":"","sources":["../src/useSignalEffect.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAWH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AACH,eAAO,MAAM,eAAe,GAAI,UAAU,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,KAAG,IA+DrE,CAAC"}
|
package/dist/useSignalEffect.js
CHANGED
|
@@ -58,6 +58,12 @@ const EFFECT_RUN_MARKER = Symbol("effect-run");
|
|
|
58
58
|
* **Implementation note:** We wrap the callback in Signal.Computed because
|
|
59
59
|
* Signal.subtle.Watcher cannot automatically track arbitrary function calls.
|
|
60
60
|
* The Computed handles dependency tracking, and the Watcher monitors it.
|
|
61
|
+
*
|
|
62
|
+
* **Memory safety (Phase 29):**
|
|
63
|
+
* - `disposed` flag prevents post-cleanup callback execution: if cleanup is
|
|
64
|
+
* called before a pending microtask fires, the microtask returns early.
|
|
65
|
+
* - `needsEnqueue` guard dedups rapid synchronous signal changes: only one
|
|
66
|
+
* microtask is ever queued per batch of synchronous mutations.
|
|
61
67
|
*/
|
|
62
68
|
export const useSignalEffect = (callback) => {
|
|
63
69
|
// Store callback in a ref so the effect closure always calls the latest version
|
|
@@ -70,6 +76,7 @@ export const useSignalEffect = (callback) => {
|
|
|
70
76
|
useEffect(() => {
|
|
71
77
|
let cleanup;
|
|
72
78
|
let needsEnqueue = true;
|
|
79
|
+
let disposed = false;
|
|
73
80
|
// Wrap callback in a Computed to automatically track dependencies
|
|
74
81
|
// The Computed will re-evaluate when any accessed signal changes
|
|
75
82
|
const effect = new Signal.Computed(() => {
|
|
@@ -84,19 +91,21 @@ export const useSignalEffect = (callback) => {
|
|
|
84
91
|
// Create watcher to detect when Computed needs re-evaluation
|
|
85
92
|
const watcher = new Signal.subtle.Watcher(() => {
|
|
86
93
|
// Batching flag prevents multiple microtasks for rapid changes
|
|
87
|
-
if (needsEnqueue)
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
94
|
+
if (disposed || !needsEnqueue)
|
|
95
|
+
return;
|
|
96
|
+
needsEnqueue = false;
|
|
97
|
+
// Schedule microtask to batch updates (prevent React thrashing)
|
|
98
|
+
queueMicrotask(() => {
|
|
99
|
+
if (disposed)
|
|
100
|
+
return;
|
|
101
|
+
needsEnqueue = true;
|
|
102
|
+
// Re-evaluate the Computed (runs callback with new signal values)
|
|
103
|
+
effect.get();
|
|
104
|
+
// Force React re-render
|
|
105
|
+
forceUpdate();
|
|
106
|
+
// Re-watch for next change
|
|
107
|
+
watcher.watch();
|
|
108
|
+
});
|
|
100
109
|
});
|
|
101
110
|
// Watch the Computed signal
|
|
102
111
|
watcher.watch(effect);
|
|
@@ -104,6 +113,7 @@ export const useSignalEffect = (callback) => {
|
|
|
104
113
|
effect.get();
|
|
105
114
|
// Cleanup on unmount
|
|
106
115
|
return () => {
|
|
116
|
+
disposed = true;
|
|
107
117
|
if (typeof cleanup === "function")
|
|
108
118
|
cleanup();
|
|
109
119
|
// Unwatch to stop receiving signal updates
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useSignalEffect.js","sourceRoot":"","sources":["../src/useSignalEffect.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AACtD,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAEjD;;;GAGG;AACH,MAAM,iBAAiB,GAAG,MAAM,CAAC,YAAY,CAAC,CAAC;AAE/C
|
|
1
|
+
{"version":3,"file":"useSignalEffect.js","sourceRoot":"","sources":["../src/useSignalEffect.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AACtD,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAEjD;;;GAGG;AACH,MAAM,iBAAiB,GAAG,MAAM,CAAC,YAAY,CAAC,CAAC;AAE/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,QAAmC,EAAQ,EAAE;IAC5E,gFAAgF;IAChF,2FAA2F;IAC3F,MAAM,WAAW,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;IACrC,8EAA8E;IAC9E,WAAW,CAAC,OAAO,GAAG,QAAQ,CAAC;IAE/B,8EAA8E;IAC9E,MAAM,CAAC,EAAE,WAAW,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IAEpD,SAAS,CAAC,GAAG,EAAE;QACd,IAAI,OAA4B,CAAC;QACjC,IAAI,YAAY,GAAG,IAAI,CAAC;QACxB,IAAI,QAAQ,GAAG,KAAK,CAAC;QAErB,kEAAkE;QAClE,iEAAiE;QACjE,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE;YACvC,mCAAmC;YACnC,IAAI,OAAO,OAAO,KAAK,UAAU;gBAAE,OAAO,EAAE,CAAC;YAE7C,kEAAkE;YAClE,OAAO,GAAG,WAAW,CAAC,OAAO,EAAE,CAAC;YAEhC,yDAAyD;YACzD,OAAO,iBAAiB,CAAC;QAC1B,CAAC,CAAC,CAAC;QAEH,6DAA6D;QAC7D,MAAM,OAAO,GAAG,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE;YAC9C,+DAA+D;YAC/D,IAAI,QAAQ,IAAI,CAAC,YAAY;gBAAE,OAAO;YACtC,YAAY,GAAG,KAAK,CAAC;YACrB,gEAAgE;YAChE,cAAc,CAAC,GAAG,EAAE;gBACnB,IAAI,QAAQ;oBAAE,OAAO;gBACrB,YAAY,GAAG,IAAI,CAAC;gBAEpB,kEAAkE;gBAClE,MAAM,CAAC,GAAG,EAAE,CAAC;gBAEb,wBAAwB;gBACxB,WAAW,EAAE,CAAC;gBAEd,2BAA2B;gBAC3B,OAAO,CAAC,KAAK,EAAE,CAAC;YACjB,CAAC,CAAC,CAAC;QACJ,CAAC,CAAC,CAAC;QAEH,4BAA4B;QAC5B,OAAO,CAAC,KAAK,CAAC,MAAkC,CAAC,CAAC;QAElD,yCAAyC;QACzC,MAAM,CAAC,GAAG,EAAE,CAAC;QAEb,qBAAqB;QACrB,OAAO,GAAG,EAAE;YACX,QAAQ,GAAG,IAAI,CAAC;YAChB,IAAI,OAAO,OAAO,KAAK,UAAU;gBAAE,OAAO,EAAE,CAAC;YAC7C,2CAA2C;YAC3C,OAAO,CAAC,OAAO,CAAC,MAAkC,CAAC,CAAC;QACrD,CAAC,CAAC;IACH,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,sEAAsE;AAC/E,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xmachines/play-react",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.22",
|
|
4
4
|
"description": "React renderer for XMachines Play architecture with signal-driven rendering",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"actor",
|
|
@@ -36,15 +36,17 @@
|
|
|
36
36
|
},
|
|
37
37
|
"scripts": {
|
|
38
38
|
"build": "tsc --build",
|
|
39
|
-
"clean": "rm -rf dist *.tsbuildinfo coverage .vitest-attachments test/browser/__screenshots__",
|
|
39
|
+
"clean": "rm -rf dist *.tsbuildinfo coverage .vitest-attachments test/browser/__screenshots__ node_modules/.vite-temp",
|
|
40
|
+
"lint": "oxlint .",
|
|
41
|
+
"format": "oxfmt .",
|
|
40
42
|
"test": "vitest",
|
|
41
43
|
"test:vitest": "vitest run",
|
|
42
44
|
"test:browser": "vitest --browser.enabled --browser.name=chromium test/browser",
|
|
43
45
|
"prepublishOnly": "npm run build"
|
|
44
46
|
},
|
|
45
47
|
"dependencies": {
|
|
46
|
-
"@xmachines/play-actor": "1.0.0-beta.
|
|
47
|
-
"@xmachines/play-signals": "1.0.0-beta.
|
|
48
|
+
"@xmachines/play-actor": "1.0.0-beta.22",
|
|
49
|
+
"@xmachines/play-signals": "1.0.0-beta.22"
|
|
48
50
|
},
|
|
49
51
|
"devDependencies": {
|
|
50
52
|
"@json-render/core": "^0.16.0",
|
|
@@ -55,7 +57,7 @@
|
|
|
55
57
|
"@types/node": "^25.5.0",
|
|
56
58
|
"@types/react": "^19.2.14",
|
|
57
59
|
"@types/react-dom": "^19.2.3",
|
|
58
|
-
"@xmachines/shared": "1.0.0-beta.
|
|
60
|
+
"@xmachines/shared": "1.0.0-beta.22",
|
|
59
61
|
"@xstate/store": ">=3.17.0",
|
|
60
62
|
"jsdom": "^29.0.1",
|
|
61
63
|
"react": "^19.2.4",
|