@xmachines/docs 1.0.0-beta.16 → 1.0.0-beta.17
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/api/@xmachines/play/README.md +56 -17
- package/api/@xmachines/play/classes/PlayError.md +240 -0
- package/api/@xmachines/play/type-aliases/PlayEvent.md +4 -4
- package/api/@xmachines/play-actor/README.md +32 -31
- package/api/@xmachines/play-actor/classes/AbstractActor.md +20 -19
- package/api/@xmachines/play-actor/interfaces/PlaySpec.md +37 -0
- package/api/@xmachines/play-actor/interfaces/Routable.md +5 -4
- package/api/@xmachines/play-actor/interfaces/ViewMetadata.md +6 -6
- package/api/@xmachines/play-actor/interfaces/Viewable.md +8 -8
- package/api/@xmachines/play-dom/README.md +36 -0
- package/api/@xmachines/play-dom/classes/PlayRenderer.md +83 -0
- package/api/@xmachines/play-dom/functions/connectRenderer.md +51 -0
- package/api/@xmachines/play-dom/functions/renderSpec.md +28 -0
- package/api/@xmachines/play-dom/interfaces/ConnectRendererOptions.md +18 -0
- package/api/@xmachines/play-dom/interfaces/DomRenderContext.md +18 -0
- package/api/@xmachines/play-dom/interfaces/PlayDomOptions.md +14 -0
- package/api/@xmachines/play-dom/type-aliases/DomComponentRenderer.md +23 -0
- package/api/@xmachines/play-dom/type-aliases/DomRegistry.md +11 -0
- package/api/@xmachines/play-react/README.md +226 -305
- package/api/@xmachines/play-react/classes/PlayErrorBoundary.md +5 -5
- package/api/@xmachines/play-react/functions/defineRegistry.md +47 -0
- package/api/@xmachines/play-react/functions/useActor.md +13 -0
- package/api/@xmachines/play-react/functions/useSignalEffect.md +1 -1
- package/api/@xmachines/play-react/functions/useStateBinding.md +32 -0
- package/api/@xmachines/play-react/interfaces/ComponentContext.md +35 -0
- package/api/@xmachines/play-react/interfaces/PlayErrorBoundaryProps.md +4 -4
- package/api/@xmachines/play-react/interfaces/PlayErrorBoundaryState.md +3 -3
- package/api/@xmachines/play-react/interfaces/PlayRendererProps.md +15 -7
- package/api/@xmachines/play-react/type-aliases/ComponentFn.md +34 -0
- package/api/@xmachines/play-react/type-aliases/PlayActor.md +9 -0
- package/api/@xmachines/play-react/variables/PlayRenderer.md +18 -30
- package/api/@xmachines/play-react-router/classes/ReactRouterBridge.md +32 -32
- package/api/@xmachines/play-react-router/classes/RouteMap.md +5 -5
- package/api/@xmachines/play-react-router/functions/PlayRouterProvider.md +11 -5
- package/api/@xmachines/play-react-router/functions/createRouteMapFromTree.md +1 -1
- package/api/@xmachines/play-react-router/interfaces/PlayRouterProviderProps.md +14 -8
- package/api/@xmachines/play-react-router/interfaces/RouteMapping.md +3 -3
- package/api/@xmachines/play-router/README.md +51 -0
- package/api/@xmachines/play-router/classes/BaseRouteMap.md +6 -6
- package/api/@xmachines/play-router/classes/RouterBridgeBase.md +33 -35
- package/api/@xmachines/play-router/functions/buildRouteTree.md +1 -1
- package/api/@xmachines/play-router/functions/connectRouter.md +1 -1
- package/api/@xmachines/play-router/functions/crawlMachine.md +1 -1
- package/api/@xmachines/play-router/functions/createBrowserHistory.md +4 -1
- package/api/@xmachines/play-router/functions/createRouteMap.md +3 -3
- package/api/@xmachines/play-router/functions/createRouter.md +1 -1
- package/api/@xmachines/play-router/functions/detectDuplicateRoutes.md +1 -1
- package/api/@xmachines/play-router/functions/extractMachineRoutes.md +1 -1
- package/api/@xmachines/play-router/functions/extractRoute.md +1 -1
- package/api/@xmachines/play-router/functions/findRouteById.md +1 -1
- package/api/@xmachines/play-router/functions/findRouteByPath.md +1 -1
- package/api/@xmachines/play-router/functions/getNavigableRoutes.md +1 -1
- package/api/@xmachines/play-router/functions/getRoutableRoutes.md +1 -1
- package/api/@xmachines/play-router/functions/routeExists.md +1 -1
- package/api/@xmachines/play-router/functions/validateRouteFormat.md +1 -1
- package/api/@xmachines/play-router/functions/validateStateExists.md +1 -1
- package/api/@xmachines/play-router/interfaces/BaseRouteMapping.md +3 -3
- package/api/@xmachines/play-router/interfaces/BrowserHistory.md +19 -15
- package/api/@xmachines/play-router/interfaces/BrowserWindow.md +14 -14
- package/api/@xmachines/play-router/interfaces/ConnectRouterOptions.md +6 -6
- package/api/@xmachines/play-router/interfaces/PlayRouteEvent.md +6 -6
- package/api/@xmachines/play-router/interfaces/RouteInfo.md +8 -8
- package/api/@xmachines/play-router/interfaces/RouteMap.md +4 -4
- package/api/@xmachines/play-router/interfaces/RouteNode.md +10 -10
- package/api/@xmachines/play-router/interfaces/RouteObject.md +2 -2
- package/api/@xmachines/play-router/interfaces/RouteTree.md +4 -4
- package/api/@xmachines/play-router/interfaces/RouteWatcherHandle.md +55 -0
- package/api/@xmachines/play-router/interfaces/RouterBridge.md +3 -3
- package/api/@xmachines/play-router/interfaces/StateVisit.md +4 -4
- package/api/@xmachines/play-router/interfaces/VanillaRouter.md +4 -4
- package/api/@xmachines/play-router/type-aliases/RouteMetadata.md +1 -1
- package/api/@xmachines/play-signals/README.md +22 -10
- package/api/@xmachines/play-signals/functions/watchSignal.md +35 -0
- package/api/@xmachines/play-signals/interfaces/ComputedOptions.md +2 -2
- package/api/@xmachines/play-signals/interfaces/SignalComputed.md +2 -2
- package/api/@xmachines/play-signals/interfaces/SignalOptions.md +2 -2
- package/api/@xmachines/play-signals/interfaces/SignalState.md +3 -3
- package/api/@xmachines/play-signals/interfaces/SignalWatcher.md +4 -4
- package/api/@xmachines/play-signals/type-aliases/WatcherNotify.md +1 -1
- package/api/@xmachines/play-solid/README.md +193 -219
- package/api/@xmachines/play-solid/functions/defineRegistry.md +47 -0
- package/api/@xmachines/play-solid/functions/useActor.md +13 -0
- package/api/@xmachines/play-solid/functions/useStateBinding.md +23 -0
- package/api/@xmachines/play-solid/interfaces/ComponentContext.md +35 -0
- package/api/@xmachines/play-solid/interfaces/PlayRendererProps.md +15 -7
- package/api/@xmachines/play-solid/type-aliases/ComponentFn.md +34 -0
- package/api/@xmachines/play-solid/type-aliases/PlayActor.md +9 -0
- package/api/@xmachines/play-solid/variables/PlayRenderer.md +15 -43
- package/api/@xmachines/play-solid-router/README.md +2 -0
- package/api/@xmachines/play-solid-router/classes/RouteMap.md +6 -6
- package/api/@xmachines/play-solid-router/classes/SolidRouterBridge.md +37 -37
- package/api/@xmachines/play-solid-router/functions/PlayRouterProvider.md +11 -5
- package/api/@xmachines/play-solid-router/functions/createRouteMap.md +1 -1
- package/api/@xmachines/play-solid-router/interfaces/AbstractActor.md +18 -17
- package/api/@xmachines/play-solid-router/interfaces/PlayRouterProviderProps.md +14 -8
- package/api/@xmachines/play-solid-router/interfaces/RouteMapping.md +3 -3
- package/api/@xmachines/play-solid-router/type-aliases/RoutableActor.md +3 -1
- package/api/@xmachines/play-solid-router/type-aliases/SolidRouterHooks.md +4 -4
- package/api/@xmachines/play-tanstack-react-router/README.md +1 -5
- package/api/@xmachines/play-tanstack-react-router/classes/RouteMap.md +5 -5
- package/api/@xmachines/play-tanstack-react-router/classes/TanStackReactRouterBridge.md +45 -33
- package/api/@xmachines/play-tanstack-react-router/functions/PlayRouterProvider.md +11 -5
- package/api/@xmachines/play-tanstack-react-router/functions/createRouteMap.md +2 -2
- package/api/@xmachines/play-tanstack-react-router/functions/createRouteMapFromTree.md +1 -1
- package/api/@xmachines/play-tanstack-react-router/functions/extractParams.md +1 -1
- package/api/@xmachines/play-tanstack-react-router/functions/extractQueryParams.md +1 -1
- package/api/@xmachines/play-tanstack-react-router/interfaces/PlayRouterProviderProps.md +14 -8
- package/api/@xmachines/play-tanstack-react-router/interfaces/RouteMapping.md +3 -3
- package/api/@xmachines/play-tanstack-react-router/interfaces/RouteNavigateEvent.md +3 -3
- package/api/@xmachines/play-tanstack-react-router/type-aliases/TanStackRouterInstance.md +1 -1
- package/api/@xmachines/play-tanstack-react-router/type-aliases/TanStackRouterLike.md +24 -4
- package/api/@xmachines/play-tanstack-solid-router/classes/RouteMap.md +6 -6
- package/api/@xmachines/play-tanstack-solid-router/classes/SolidRouterBridge.md +33 -33
- package/api/@xmachines/play-tanstack-solid-router/functions/PlayRouterProvider.md +11 -5
- package/api/@xmachines/play-tanstack-solid-router/functions/createRouteMap.md +1 -1
- package/api/@xmachines/play-tanstack-solid-router/interfaces/PlayRouterProviderProps.md +14 -8
- package/api/@xmachines/play-tanstack-solid-router/interfaces/RouteMapping.md +3 -3
- package/api/@xmachines/play-tanstack-solid-router/type-aliases/RoutableActor.md +3 -1
- package/api/@xmachines/play-tanstack-solid-router/type-aliases/TanStackRouterInstance.md +1 -1
- package/api/@xmachines/play-tanstack-solid-router/type-aliases/TanStackRouterLike.md +4 -4
- package/api/@xmachines/play-vue/README.md +216 -209
- package/api/@xmachines/play-vue/functions/defineRegistry.md +32 -0
- package/api/@xmachines/play-vue/functions/useActor.md +13 -0
- package/api/@xmachines/play-vue/functions/useStateBinding.md +30 -0
- package/api/@xmachines/play-vue/interfaces/ComponentContext.md +35 -0
- package/api/@xmachines/play-vue/interfaces/PlayRendererProps.md +14 -6
- package/api/@xmachines/play-vue/type-aliases/ComponentFn.md +33 -0
- package/api/@xmachines/play-vue/type-aliases/PlayActor.md +9 -0
- package/api/@xmachines/play-vue/variables/PlayRenderer.md +1 -1
- package/api/@xmachines/play-vue-router/README.md +21 -0
- package/api/@xmachines/play-vue-router/classes/RouteMap.md +7 -7
- package/api/@xmachines/play-vue-router/classes/VueBaseRouteMap.md +7 -7
- package/api/@xmachines/play-vue-router/classes/VueRouterBridge.md +48 -51
- package/api/@xmachines/play-vue-router/functions/createRouteMap.md +1 -1
- package/api/@xmachines/play-vue-router/interfaces/RouteMapping.md +4 -4
- package/api/@xmachines/play-vue-router/type-aliases/RoutableActor.md +3 -1
- package/api/@xmachines/play-vue-router/variables/PlayRouterProvider.md +7 -1
- package/api/@xmachines/play-xstate/README.md +236 -111
- package/api/@xmachines/play-xstate/classes/PlayerActor.md +36 -33
- package/api/@xmachines/play-xstate/functions/buildRouteUrl.md +24 -18
- package/api/@xmachines/play-xstate/functions/composeGuards.md +1 -1
- package/api/@xmachines/play-xstate/functions/composeGuardsOr.md +1 -1
- package/api/@xmachines/play-xstate/functions/definePlayer.md +12 -61
- package/api/@xmachines/play-xstate/functions/deriveRoute.md +1 -1
- package/api/@xmachines/play-xstate/functions/eventMatches.md +1 -1
- package/api/@xmachines/play-xstate/functions/formatPlayRouteTransitions.md +1 -1
- package/api/@xmachines/play-xstate/functions/hasContext.md +1 -1
- package/api/@xmachines/play-xstate/functions/isAbsoluteRoute.md +1 -1
- package/api/@xmachines/play-xstate/functions/negateGuard.md +1 -1
- package/api/@xmachines/play-xstate/functions/stateMatches.md +1 -1
- package/api/@xmachines/play-xstate/interfaces/PlayerConfig.md +9 -13
- package/api/@xmachines/play-xstate/interfaces/PlayerFactoryResumeOptions.md +2 -2
- package/api/@xmachines/play-xstate/interfaces/PlayerOptions.md +8 -9
- package/api/@xmachines/play-xstate/interfaces/RouteContext.md +5 -5
- package/api/@xmachines/play-xstate/type-aliases/ComposedGuard.md +1 -1
- package/api/@xmachines/play-xstate/type-aliases/Guard.md +1 -1
- package/api/@xmachines/play-xstate/type-aliases/GuardArray.md +1 -1
- package/api/@xmachines/play-xstate/type-aliases/PlayerFactory.md +1 -1
- package/api/@xmachines/play-xstate/type-aliases/RouteMachineConfig.md +14 -4
- package/api/@xmachines/play-xstate/type-aliases/RouteStateNode.md +19 -4
- package/api/@xmachines/shared/functions/defineXmVitestConfig.md +2 -2
- package/api/@xmachines/shared/functions/xmAliases.md +1 -1
- package/api/README.md +1 -1
- package/api/llms.txt +11 -5
- package/examples/multi-router-integration.md +31 -19
- package/package.json +2 -2
- package/api/@xmachines/play-catalog/README.md +0 -331
- package/api/@xmachines/play-catalog/functions/defineCatalog.md +0 -98
- package/api/@xmachines/play-catalog/functions/defineComponents.md +0 -134
- package/api/@xmachines/play-catalog/type-aliases/Catalog.md +0 -48
- package/api/@xmachines/play-catalog/type-aliases/ComponentsFor.md +0 -20
- package/api/@xmachines/play-catalog/type-aliases/InferComponentProps.md +0 -65
- package/api/@xmachines/play-catalog/type-aliases/NoExtraKeys.md +0 -17
- package/api/@xmachines/play-xstate/functions/mergeViewProps.md +0 -26
- package/api/@xmachines/play-xstate/functions/validateComponentBinding.md +0 -39
- package/api/@xmachines/play-xstate/functions/validateViewProps.md +0 -80
- package/api/@xmachines/play-xstate/interfaces/CatalogEntry.md +0 -16
- package/api/@xmachines/play-xstate/type-aliases/Catalog.md +0 -21
- package/api/@xmachines/play-xstate/type-aliases/ValidationResult.md +0 -17
- package/api/@xmachines/play-xstate/type-aliases/ViewMergeContext.md +0 -35
|
@@ -2,422 +2,343 @@
|
|
|
2
2
|
|
|
3
3
|
# @xmachines/play-react
|
|
4
4
|
|
|
5
|
-
**React renderer
|
|
5
|
+
**React renderer for XMachines Play Architecture**
|
|
6
6
|
|
|
7
|
-
Signal-driven React
|
|
7
|
+
Bridges TC39 Signal-driven actors to React's render cycle. Business logic stays in the actor; React is purely a rendering target.
|
|
8
8
|
|
|
9
9
|
## Overview
|
|
10
10
|
|
|
11
|
-
`@xmachines/play-react` provides `PlayRenderer
|
|
11
|
+
`@xmachines/play-react` provides `PlayRenderer`, a React component that:
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
- Subscribes to `actor.currentView` (TC39 Signal) and re-renders on every state transition
|
|
14
|
+
- Renders the current view's JSON spec via `@json-render/react`
|
|
15
|
+
- Routes action names from spec elements to `actor.send()` via the `actions` prop
|
|
16
|
+
- Manages per-view UI state in an `@xstate/store` atom (automatic or caller-supplied)
|
|
14
17
|
|
|
15
|
-
|
|
16
|
-
- **Passive Infrastructure (INV-04):** Components observe signals, send events to actor
|
|
18
|
+
Per [RFC Play v1](https://gitlab.com/xmachin-es/rfc/-/blob/main/src/play-v1.md):
|
|
17
19
|
|
|
18
|
-
**
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
- **Actor Authority (INV-01):** Guards in the machine decide all state transitions
|
|
21
|
+
- **Passive Infrastructure (INV-04):** React observes signals and dispatches events — never decides
|
|
22
|
+
- **Signal-Only Reactivity (INV-05):** `actor.currentView` signal is the sole render trigger
|
|
21
23
|
|
|
22
24
|
## Installation
|
|
23
25
|
|
|
24
26
|
```bash
|
|
25
|
-
npm install react@^18.0.0 react-dom@^18.0.0
|
|
26
27
|
npm install @xmachines/play-react
|
|
28
|
+
npm install @json-render/react @json-render/core # peer deps
|
|
29
|
+
npm install @json-render/xstate @xstate/store # store integration
|
|
27
30
|
```
|
|
28
31
|
|
|
29
32
|
## Current Exports
|
|
30
33
|
|
|
31
|
-
- `PlayRenderer`
|
|
32
|
-
- `
|
|
33
|
-
- `
|
|
34
|
+
- `PlayRenderer` — main renderer component
|
|
35
|
+
- `useActor` — hook for accessing the actor inside a `PlayRenderer` tree
|
|
36
|
+
- `useSignalEffect` — React hook for subscribing to TC39 Signals
|
|
37
|
+
- `PlayErrorBoundary` — error boundary wrapping renderer output
|
|
38
|
+
- `defineRegistry` — re-exported from `@json-render/react`
|
|
39
|
+
- `useStateBinding` — re-exported from `@json-render/react`
|
|
40
|
+
- `ComponentFn` (type) — re-exported from `@json-render/react`
|
|
41
|
+
- `ComponentContext` (type) — re-exported from `@json-render/react`
|
|
34
42
|
- `PlayRendererProps` (type)
|
|
35
|
-
- `
|
|
36
|
-
|
|
37
|
-
**Peer dependencies:**
|
|
38
|
-
|
|
39
|
-
- `react` ^18.0.0 || ^19.0.0 — React runtime
|
|
40
|
-
- `react-dom` ^18.0.0 || ^19.0.0 — React DOM renderer
|
|
43
|
+
- `PlayActor` (type)
|
|
41
44
|
|
|
42
45
|
## Quick Start
|
|
43
46
|
|
|
44
|
-
```
|
|
45
|
-
import {
|
|
46
|
-
import { definePlayer } from "@xmachines/play-xstate";
|
|
47
|
-
import { defineCatalog } from "@xmachines/play-catalog";
|
|
47
|
+
```tsx
|
|
48
|
+
import { definePlayer, formatPlayRouteTransitions } from "@xmachines/play-xstate";
|
|
48
49
|
import { PlayRenderer } from "@xmachines/play-react";
|
|
50
|
+
import { defineCatalog } from "@json-render/core";
|
|
51
|
+
import { defineRegistry } from "@xmachines/play-react";
|
|
52
|
+
import type { ComponentFn } from "@xmachines/play-react";
|
|
53
|
+
import { setup, assign } from "xstate";
|
|
49
54
|
import { z } from "zod";
|
|
50
55
|
|
|
51
|
-
// 1. Define catalog
|
|
56
|
+
// 1. Define catalog — the contract between machine spec and UI components
|
|
52
57
|
const catalog = defineCatalog({
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}),
|
|
58
|
+
elements: {
|
|
59
|
+
Login: { props: z.object({ title: z.string() }), description: "Login form" },
|
|
60
|
+
Dashboard: { props: z.object({ username: z.string() }), description: "Dashboard" },
|
|
61
|
+
},
|
|
58
62
|
});
|
|
59
63
|
|
|
60
|
-
// 2.
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
64
|
+
// 2. Implement components using ComponentFn — typed against catalog entries
|
|
65
|
+
const Login: ComponentFn<typeof catalog, "Login"> = ({ props, emit }) => (
|
|
66
|
+
<form
|
|
67
|
+
onSubmit={(e) => {
|
|
68
|
+
e.preventDefault();
|
|
69
|
+
emit("submit");
|
|
70
|
+
}}
|
|
71
|
+
>
|
|
72
|
+
<h2>{props.title}</h2>
|
|
73
|
+
<button type="submit">Log In</button>
|
|
74
|
+
</form>
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const Dashboard: ComponentFn<typeof catalog, "Dashboard"> = ({ props }) => (
|
|
78
|
+
<div>Welcome, {props.username}!</div>
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
// 3. Build registry — wires components to catalog and declares no-op action stubs
|
|
82
|
+
const { registry } = defineRegistry(catalog, {
|
|
83
|
+
components: { Login, Dashboard },
|
|
84
|
+
actions: { login: async () => {}, logout: async () => {} },
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// 4. Define machine with view metadata
|
|
88
|
+
const machine = setup({
|
|
89
|
+
types: {
|
|
90
|
+
context: {} as {
|
|
91
|
+
isAuthenticated: boolean;
|
|
92
|
+
username: string | null;
|
|
93
|
+
routeParams: Record<string, string>;
|
|
94
|
+
queryParams: Record<string, string>;
|
|
95
|
+
},
|
|
96
|
+
events: {} as
|
|
97
|
+
| { type: "auth.login"; username: string }
|
|
98
|
+
| { type: "auth.logout" }
|
|
99
|
+
| { type: "play.route"; to: string; params?: Record<string, string> },
|
|
100
|
+
},
|
|
101
|
+
}).createMachine(
|
|
102
|
+
formatPlayRouteTransitions({
|
|
103
|
+
id: "app",
|
|
104
|
+
initial: "login",
|
|
105
|
+
context: { isAuthenticated: false, username: null, routeParams: {}, queryParams: {} },
|
|
106
|
+
states: {
|
|
107
|
+
login: {
|
|
108
|
+
id: "login",
|
|
109
|
+
meta: {
|
|
110
|
+
route: "/login",
|
|
111
|
+
view: {
|
|
112
|
+
component: "Login",
|
|
113
|
+
spec: {
|
|
114
|
+
root: "root",
|
|
115
|
+
elements: {
|
|
116
|
+
root: { type: "Login", props: { title: "Sign In" }, children: [] },
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
dashboard: {
|
|
123
|
+
id: "dashboard",
|
|
124
|
+
meta: {
|
|
125
|
+
route: "/dashboard",
|
|
126
|
+
view: {
|
|
127
|
+
component: "Dashboard",
|
|
128
|
+
spec: {
|
|
129
|
+
root: "root",
|
|
130
|
+
elements: {
|
|
131
|
+
root: { type: "Dashboard", props: { username: "" }, children: [] },
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
on: {
|
|
139
|
+
"auth.login": {
|
|
140
|
+
target: ".dashboard",
|
|
141
|
+
guard: ({ context }) => !context.isAuthenticated,
|
|
142
|
+
actions: assign({
|
|
143
|
+
isAuthenticated: true,
|
|
144
|
+
username: ({ event }) => event.username,
|
|
145
|
+
}),
|
|
146
|
+
},
|
|
147
|
+
"auth.logout": {
|
|
148
|
+
target: ".login",
|
|
149
|
+
guard: ({ context }) => context.isAuthenticated,
|
|
150
|
+
actions: assign({ isAuthenticated: false, username: null }),
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
}),
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
// 5. Create actor and render
|
|
157
|
+
const createPlayer = definePlayer({ machine });
|
|
89
158
|
const actor = createPlayer();
|
|
90
159
|
actor.start();
|
|
91
160
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
161
|
+
function App() {
|
|
162
|
+
return (
|
|
163
|
+
<PlayRenderer
|
|
164
|
+
actor={actor}
|
|
165
|
+
registry={registry}
|
|
166
|
+
actions={{ login: "auth.login", logout: "auth.logout" }}
|
|
167
|
+
/>
|
|
168
|
+
);
|
|
169
|
+
}
|
|
95
170
|
```
|
|
96
171
|
|
|
97
172
|
## API Reference
|
|
98
173
|
|
|
99
|
-
### PlayRenderer
|
|
174
|
+
### `PlayRenderer`
|
|
100
175
|
|
|
101
|
-
Main
|
|
176
|
+
Main component. Subscribes to `actor.currentView` and renders the spec.
|
|
102
177
|
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
actor
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
178
|
+
```tsx
|
|
179
|
+
<PlayRenderer
|
|
180
|
+
actor={actor} // required
|
|
181
|
+
registry={registry} // required
|
|
182
|
+
actions={{ login: "auth.login" }} // optional
|
|
183
|
+
store={myStore} // optional — controlled mode
|
|
184
|
+
fallback={<p>Loading…</p>} // optional
|
|
185
|
+
/>
|
|
109
186
|
```
|
|
110
187
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
- `actor` - Actor instance with `currentView` signal
|
|
114
|
-
- `components` - Map of component names to React components
|
|
115
|
-
- `fallback` - Component shown when `currentView` is null (default: `null`)
|
|
188
|
+
**`actor`** — A `PlayerActor` (or any `AbstractActor & Viewable`). Provides the `currentView` signal.
|
|
116
189
|
|
|
117
|
-
|
|
190
|
+
**`registry`** — Built with `defineRegistry(catalog, { components, actions })` from `@xmachines/play-react`.
|
|
118
191
|
|
|
119
|
-
|
|
120
|
-
2. Looks up component from `components` map using `view.component` string
|
|
121
|
-
3. Renders component with props from `view.props` + `send` function
|
|
192
|
+
**`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
193
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
<PlayRenderer
|
|
194
|
+
```tsx
|
|
195
|
+
// Typed: "bad.event" causes a compile error if it is not in the machine's event union
|
|
196
|
+
<PlayRenderer<typeof machine>
|
|
127
197
|
actor={actor}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
AboutPage: ({ send }) => <div>About</div>,
|
|
131
|
-
}}
|
|
132
|
-
fallback={<div>Loading...</div>}
|
|
198
|
+
registry={registry}
|
|
199
|
+
actions={{ login: "auth.login", logout: "auth.logout" }}
|
|
133
200
|
/>
|
|
134
201
|
```
|
|
135
202
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
Hook for subscribing to signal changes with automatic cleanup:
|
|
139
|
-
|
|
140
|
-
```typescript
|
|
141
|
-
useSignalEffect(() => {
|
|
142
|
-
const value = signal.get();
|
|
143
|
-
// React re-renders when signal changes
|
|
144
|
-
});
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
**Behavior:**
|
|
203
|
+
**`store`** (optional) — Controls per-view UI state (form values, `$state` bindings):
|
|
148
204
|
|
|
149
|
-
-
|
|
150
|
-
-
|
|
151
|
-
- Triggers React state update to force re-render
|
|
152
|
-
- Cleans up watcher on unmount with explicit `unwatch`
|
|
205
|
+
- **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.
|
|
206
|
+
- **Provided (controlled):** The caller owns the store lifecycle. `spec.state` is ignored.
|
|
153
207
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
3. drain pending work (`getPending` and/or `Computed.get`)
|
|
159
|
-
4. run effect + trigger render
|
|
160
|
-
5. re-arm watcher via `watch()`
|
|
161
|
-
|
|
162
|
-
Watcher notification is one-shot, so re-arm and explicit cleanup are both required.
|
|
163
|
-
|
|
164
|
-
**Example:**
|
|
165
|
-
|
|
166
|
-
```typescript
|
|
167
|
-
import { useSignalEffect } from "@xmachines/play-react";
|
|
168
|
-
import { useState } from "react";
|
|
208
|
+
```tsx
|
|
209
|
+
import { createAtom } from "@xstate/store";
|
|
210
|
+
import { xstateStoreStateStore } from "@json-render/xstate";
|
|
211
|
+
import type { StateStore } from "@json-render/core";
|
|
169
212
|
|
|
170
|
-
|
|
171
|
-
const [route, setRoute] = useState<string | null>(null);
|
|
213
|
+
const store: StateStore = xstateStoreStateStore({ atom: createAtom({ username: "alice" }) });
|
|
172
214
|
|
|
173
|
-
|
|
174
|
-
const currentRoute = actor.currentRoute.get();
|
|
175
|
-
setRoute(currentRoute);
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
return <div>Current Route: {route ?? "None"}</div>;
|
|
179
|
-
}
|
|
215
|
+
<PlayRenderer actor={actor} registry={registry} store={store} actions={{ login: "auth.login" }} />;
|
|
180
216
|
```
|
|
181
217
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
React class component error boundary for catching catalog component render errors.
|
|
185
|
-
|
|
186
|
-
`PlayRenderer` wraps its render output in `PlayErrorBoundary` automatically. You can also use it directly to wrap any component that may throw during render.
|
|
187
|
-
|
|
188
|
-
```typescript
|
|
189
|
-
interface PlayErrorBoundaryProps {
|
|
190
|
-
fallback?: React.ReactNode; // UI shown when a child throws (default: null)
|
|
191
|
-
children: React.ReactNode;
|
|
192
|
-
onError?: (error: Error, info: React.ErrorInfo) => void; // Forward to Sentry, Datadog, etc.
|
|
193
|
-
}
|
|
194
|
-
```
|
|
218
|
+
**`fallback`** — Shown when `actor.currentView.get()` is `null` (machine in a no-view state, or during initialisation).
|
|
195
219
|
|
|
196
|
-
|
|
220
|
+
---
|
|
197
221
|
|
|
198
|
-
|
|
199
|
-
- `onError` — Optional callback forwarded on every caught error. Use for production observability (Sentry, Datadog, custom logging).
|
|
222
|
+
### `useActor`
|
|
200
223
|
|
|
201
|
-
|
|
224
|
+
Hook for accessing the actor from inside any component rendered by `PlayRenderer`. No prop drilling needed.
|
|
202
225
|
|
|
203
226
|
```tsx
|
|
204
|
-
import {
|
|
227
|
+
import { useActor } from "@xmachines/play-react";
|
|
205
228
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
>
|
|
210
|
-
|
|
211
|
-
</PlayErrorBoundary>;
|
|
229
|
+
// Works in any component rendered inside PlayRenderer:
|
|
230
|
+
function LogoutButton() {
|
|
231
|
+
const actor = useActor();
|
|
232
|
+
return <button onClick={() => actor.send({ type: "auth.logout" })}>Log Out</button>;
|
|
233
|
+
}
|
|
212
234
|
```
|
|
213
235
|
|
|
214
|
-
|
|
236
|
+
Throws `"useActor() must be called inside <PlayRenderer>"` if called outside the tree.
|
|
215
237
|
|
|
216
|
-
|
|
238
|
+
---
|
|
217
239
|
|
|
218
|
-
###
|
|
219
|
-
|
|
220
|
-
```typescript
|
|
221
|
-
import { PlayRenderer } from "@xmachines/play-react";
|
|
222
|
-
import { defineCatalog } from "@xmachines/play-catalog";
|
|
223
|
-
import { z } from "zod";
|
|
240
|
+
### `useSignalEffect`
|
|
224
241
|
|
|
225
|
-
|
|
226
|
-
const catalog = defineCatalog({
|
|
227
|
-
UserProfile: z.object({
|
|
228
|
-
userId: z.string(),
|
|
229
|
-
name: z.string(),
|
|
230
|
-
avatar: z.string().url().optional(),
|
|
231
|
-
stats: z.object({
|
|
232
|
-
posts: z.number(),
|
|
233
|
-
followers: z.number(),
|
|
234
|
-
}),
|
|
235
|
-
}),
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
// Component receives type-safe props + send
|
|
239
|
-
const components = {
|
|
240
|
-
UserProfile: ({ userId, name, avatar, stats, send }) => (
|
|
241
|
-
<div>
|
|
242
|
-
{avatar && <img src={avatar} alt={name} />}
|
|
243
|
-
<h1>{name}</h1>
|
|
244
|
-
<p>ID: {userId}</p>
|
|
245
|
-
<div>
|
|
246
|
-
<span>{stats.posts} posts</span>
|
|
247
|
-
<span>{stats.followers} followers</span>
|
|
248
|
-
</div>
|
|
249
|
-
<button
|
|
250
|
-
onClick={() =>
|
|
251
|
-
send({
|
|
252
|
-
type: "profile.edit",
|
|
253
|
-
userId,
|
|
254
|
-
})
|
|
255
|
-
}
|
|
256
|
-
>
|
|
257
|
-
Edit Profile
|
|
258
|
-
</button>
|
|
259
|
-
</div>
|
|
260
|
-
),
|
|
261
|
-
};
|
|
262
|
-
|
|
263
|
-
<PlayRenderer actor={actor} components={components} />;
|
|
264
|
-
```
|
|
242
|
+
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).
|
|
265
243
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
```typescript
|
|
244
|
+
```tsx
|
|
269
245
|
import { useSignalEffect } from "@xmachines/play-react";
|
|
270
|
-
import { AbstractActor } from "@xmachines/play-actor";
|
|
271
246
|
|
|
272
|
-
function
|
|
273
|
-
const [
|
|
247
|
+
function NavBar({ actor }: { actor: ReturnType<typeof createPlayer> }) {
|
|
248
|
+
const [isAuth, setIsAuth] = useState(false);
|
|
274
249
|
|
|
275
|
-
// Subscribe to currentView signal
|
|
276
250
|
useSignalEffect(() => {
|
|
277
|
-
const
|
|
278
|
-
|
|
251
|
+
const snap = actor.state.get();
|
|
252
|
+
setIsAuth((snap.context as { isAuthenticated: boolean }).isAuthenticated);
|
|
279
253
|
});
|
|
280
254
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
// Custom rendering logic
|
|
284
|
-
if (view.component === "SpecialCase") {
|
|
285
|
-
return <SpecialCaseComponent {...view.props} actor={actor} />;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
// Fallback to standard rendering
|
|
289
|
-
return <DefaultComponent view={view} actor={actor} />;
|
|
255
|
+
return <nav>{isAuth ? <LogoutBtn /> : <LoginBtn />}</nav>;
|
|
290
256
|
}
|
|
291
257
|
```
|
|
292
258
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
```typescript
|
|
296
|
-
import { PlayTanStackRouterProvider } from "@xmachines/play-tanstack-react-router";
|
|
297
|
-
import { PlayRenderer } from "@xmachines/play-react";
|
|
259
|
+
---
|
|
298
260
|
|
|
299
|
-
|
|
300
|
-
function App() {
|
|
301
|
-
return (
|
|
302
|
-
<PlayTanStackRouterProvider
|
|
303
|
-
actor={actor}
|
|
304
|
-
router={router}
|
|
305
|
-
renderer={(currentActor, currentRouter) => {
|
|
306
|
-
void currentRouter;
|
|
307
|
-
return (
|
|
308
|
-
<div>
|
|
309
|
-
<Header actor={currentActor} />
|
|
310
|
-
<PlayRenderer actor={currentActor} components={components} />
|
|
311
|
-
<Footer />
|
|
312
|
-
</div>
|
|
313
|
-
);
|
|
314
|
-
}}
|
|
315
|
-
/>
|
|
316
|
-
);
|
|
317
|
-
}
|
|
261
|
+
### `PlayErrorBoundary`
|
|
318
262
|
|
|
319
|
-
|
|
320
|
-
function Header({ actor }: { actor: AbstractActor<any> }) {
|
|
321
|
-
const [route, setRoute] = useState<string | null>(null);
|
|
263
|
+
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.
|
|
322
264
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
});
|
|
265
|
+
```tsx
|
|
266
|
+
import { PlayErrorBoundary } from "@xmachines/play-react";
|
|
326
267
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
</header>
|
|
331
|
-
);
|
|
332
|
-
}
|
|
268
|
+
<PlayErrorBoundary fallback={<p>Something went wrong.</p>}>
|
|
269
|
+
<PlayRenderer actor={actor} registry={registry} />
|
|
270
|
+
</PlayErrorBoundary>;
|
|
333
271
|
```
|
|
334
272
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
This package implements **Signal-Only Reactivity (INV-05)** and **Passive Infrastructure (INV-04)**:
|
|
273
|
+
---
|
|
338
274
|
|
|
339
|
-
|
|
340
|
-
- No useState/useReducer for business state
|
|
341
|
-
- No useEffect for side effects
|
|
342
|
-
- React only triggers renders, doesn't control state
|
|
275
|
+
## Route Parameters in Props
|
|
343
276
|
|
|
344
|
-
|
|
345
|
-
- `actor.currentView.get()` provides UI structure
|
|
346
|
-
- `actor.currentRoute.get()` provides navigation state
|
|
347
|
-
- Components observe signals via `useSignalEffect`
|
|
277
|
+
When using `formatPlayRouteTransitions`, URL path parameters flow automatically into component props. Declare an `undefined` slot in the spec to opt in:
|
|
348
278
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
4. **Microtask Batching:**
|
|
355
|
-
- `Signal.subtle.Watcher` coalesces rapid signal changes
|
|
356
|
-
- Prevents React thrashing from multiple signal updates
|
|
357
|
-
- Single React render per microtask batch
|
|
358
|
-
|
|
359
|
-
5. **Explicit Disposal Contract:**
|
|
360
|
-
- Component teardown must call watcher `unwatch` in cleanup
|
|
361
|
-
- Do not rely on GC-only cleanup
|
|
362
|
-
|
|
363
|
-
**Pattern:**
|
|
364
|
-
|
|
365
|
-
- Renderer receives actor via props (provider pattern)
|
|
366
|
-
- Enables composition with navigation, headers, footers
|
|
367
|
-
- Supports multiple renderers in same app
|
|
368
|
-
|
|
369
|
-
**Architectural Invariants:**
|
|
370
|
-
|
|
371
|
-
- **Signal-Only Reactivity (INV-05):** No React state for business logic
|
|
372
|
-
- **Passive Infrastructure (INV-04):** Components reflect, never decide
|
|
279
|
+
```ts
|
|
280
|
+
// spec: { section: undefined, user: "alice" }
|
|
281
|
+
// After play.route to /settings/profile → context.routeParams = { section: "profile" }
|
|
282
|
+
// Component receives: { section: "profile", user: "alice" }
|
|
283
|
+
```
|
|
373
284
|
|
|
374
|
-
|
|
285
|
+
Priority: **route param fills `undefined` slots; explicit non-`undefined` spec props always win.**
|
|
375
286
|
|
|
376
|
-
|
|
377
|
-
- **Type Safety:** Props validated against catalog schemas
|
|
378
|
-
- **Simple Testing:** Test actors without React renderer
|
|
379
|
-
- **Performance:** Microtask batching reduces unnecessary renders
|
|
380
|
-
- **Composability:** Renderer prop enables complex layouts ()
|
|
287
|
+
---
|
|
381
288
|
|
|
382
|
-
##
|
|
289
|
+
## Error Handling
|
|
383
290
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
- **[@xmachines/play-signals](../play-signals/README.md)** - TC39 Signals primitives
|
|
291
|
+
| Error | Cause | Fix |
|
|
292
|
+
| ------------------------------------------------- | ---------------------------- | ---------------------------------------------------------------- |
|
|
293
|
+
| `useActor() must be called inside <PlayRenderer>` | Hook called outside the tree | Move inside a component rendered by `PlayRenderer` |
|
|
294
|
+
| Component render error | Component throws | `PlayErrorBoundary` catches it; inspect component implementation |
|
|
389
295
|
|
|
390
|
-
|
|
296
|
+
---
|
|
391
297
|
|
|
392
|
-
|
|
298
|
+
## Architecture Notes
|
|
393
299
|
|
|
394
|
-
|
|
395
|
-
|
|
300
|
+
- React `useState` is **only** used to trigger re-renders — never for business logic
|
|
301
|
+
- `actor.currentView` (TC39 Signal) is the sole render trigger; `PlayRenderer` is a passive observer
|
|
302
|
+
- Per-view UI state lives in an `@xstate/store` atom, not in React state
|
|
303
|
+
- `@json-render/react` drives rendering; `PlayRenderer` is the signal bridge — import `defineRegistry`, `ComponentFn`, `ComponentContext`, and `useStateBinding` from `@xmachines/play-react`
|
|
396
304
|
|
|
397
305
|
@xmachines/play-react - React renderer for XMachines Play architecture
|
|
398
306
|
|
|
399
307
|
Provides a thin React rendering layer that passively observes actor signals
|
|
400
|
-
and renders UI components
|
|
308
|
+
and renders UI components via @json-render/react. This package enables
|
|
401
309
|
framework-swappable architecture where React is just a rendering target
|
|
402
310
|
that subscribes to signal changes.
|
|
403
311
|
|
|
404
312
|
**Key principle:** React state is NEVER used for business logic—only for
|
|
405
313
|
triggering React's render cycle. Signals are the source of truth.
|
|
406
314
|
|
|
315
|
+
Re-exports `defineRegistry`, `useStateBinding`, `ComponentFn`, and
|
|
316
|
+
`ComponentContext` from `@json-render/react` so consumers import everything
|
|
317
|
+
from `@xmachines/play-react` rather than `@json-render/react` directly.
|
|
318
|
+
|
|
407
319
|
## Classes
|
|
408
320
|
|
|
409
321
|
- [PlayErrorBoundary](classes/PlayErrorBoundary.md)
|
|
410
322
|
|
|
411
323
|
## Interfaces
|
|
412
324
|
|
|
325
|
+
- [ComponentContext](interfaces/ComponentContext.md)
|
|
413
326
|
- [PlayErrorBoundaryProps](interfaces/PlayErrorBoundaryProps.md)
|
|
414
327
|
- [PlayErrorBoundaryState](interfaces/PlayErrorBoundaryState.md)
|
|
415
328
|
- [PlayRendererProps](interfaces/PlayRendererProps.md)
|
|
416
329
|
|
|
330
|
+
## Type Aliases
|
|
331
|
+
|
|
332
|
+
- [ComponentFn](type-aliases/ComponentFn.md)
|
|
333
|
+
- [PlayActor](type-aliases/PlayActor.md)
|
|
334
|
+
|
|
417
335
|
## Variables
|
|
418
336
|
|
|
419
337
|
- [PlayRenderer](variables/PlayRenderer.md)
|
|
420
338
|
|
|
421
339
|
## Functions
|
|
422
340
|
|
|
341
|
+
- [defineRegistry](functions/defineRegistry.md)
|
|
342
|
+
- [useActor](functions/useActor.md)
|
|
423
343
|
- [useSignalEffect](functions/useSignalEffect.md)
|
|
344
|
+
- [~~useStateBinding~~](functions/useStateBinding.md)
|