@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.
Files changed (180) hide show
  1. package/api/@xmachines/play/README.md +56 -17
  2. package/api/@xmachines/play/classes/PlayError.md +240 -0
  3. package/api/@xmachines/play/type-aliases/PlayEvent.md +4 -4
  4. package/api/@xmachines/play-actor/README.md +32 -31
  5. package/api/@xmachines/play-actor/classes/AbstractActor.md +20 -19
  6. package/api/@xmachines/play-actor/interfaces/PlaySpec.md +37 -0
  7. package/api/@xmachines/play-actor/interfaces/Routable.md +5 -4
  8. package/api/@xmachines/play-actor/interfaces/ViewMetadata.md +6 -6
  9. package/api/@xmachines/play-actor/interfaces/Viewable.md +8 -8
  10. package/api/@xmachines/play-dom/README.md +36 -0
  11. package/api/@xmachines/play-dom/classes/PlayRenderer.md +83 -0
  12. package/api/@xmachines/play-dom/functions/connectRenderer.md +51 -0
  13. package/api/@xmachines/play-dom/functions/renderSpec.md +28 -0
  14. package/api/@xmachines/play-dom/interfaces/ConnectRendererOptions.md +18 -0
  15. package/api/@xmachines/play-dom/interfaces/DomRenderContext.md +18 -0
  16. package/api/@xmachines/play-dom/interfaces/PlayDomOptions.md +14 -0
  17. package/api/@xmachines/play-dom/type-aliases/DomComponentRenderer.md +23 -0
  18. package/api/@xmachines/play-dom/type-aliases/DomRegistry.md +11 -0
  19. package/api/@xmachines/play-react/README.md +226 -305
  20. package/api/@xmachines/play-react/classes/PlayErrorBoundary.md +5 -5
  21. package/api/@xmachines/play-react/functions/defineRegistry.md +47 -0
  22. package/api/@xmachines/play-react/functions/useActor.md +13 -0
  23. package/api/@xmachines/play-react/functions/useSignalEffect.md +1 -1
  24. package/api/@xmachines/play-react/functions/useStateBinding.md +32 -0
  25. package/api/@xmachines/play-react/interfaces/ComponentContext.md +35 -0
  26. package/api/@xmachines/play-react/interfaces/PlayErrorBoundaryProps.md +4 -4
  27. package/api/@xmachines/play-react/interfaces/PlayErrorBoundaryState.md +3 -3
  28. package/api/@xmachines/play-react/interfaces/PlayRendererProps.md +15 -7
  29. package/api/@xmachines/play-react/type-aliases/ComponentFn.md +34 -0
  30. package/api/@xmachines/play-react/type-aliases/PlayActor.md +9 -0
  31. package/api/@xmachines/play-react/variables/PlayRenderer.md +18 -30
  32. package/api/@xmachines/play-react-router/classes/ReactRouterBridge.md +32 -32
  33. package/api/@xmachines/play-react-router/classes/RouteMap.md +5 -5
  34. package/api/@xmachines/play-react-router/functions/PlayRouterProvider.md +11 -5
  35. package/api/@xmachines/play-react-router/functions/createRouteMapFromTree.md +1 -1
  36. package/api/@xmachines/play-react-router/interfaces/PlayRouterProviderProps.md +14 -8
  37. package/api/@xmachines/play-react-router/interfaces/RouteMapping.md +3 -3
  38. package/api/@xmachines/play-router/README.md +51 -0
  39. package/api/@xmachines/play-router/classes/BaseRouteMap.md +6 -6
  40. package/api/@xmachines/play-router/classes/RouterBridgeBase.md +33 -35
  41. package/api/@xmachines/play-router/functions/buildRouteTree.md +1 -1
  42. package/api/@xmachines/play-router/functions/connectRouter.md +1 -1
  43. package/api/@xmachines/play-router/functions/crawlMachine.md +1 -1
  44. package/api/@xmachines/play-router/functions/createBrowserHistory.md +4 -1
  45. package/api/@xmachines/play-router/functions/createRouteMap.md +3 -3
  46. package/api/@xmachines/play-router/functions/createRouter.md +1 -1
  47. package/api/@xmachines/play-router/functions/detectDuplicateRoutes.md +1 -1
  48. package/api/@xmachines/play-router/functions/extractMachineRoutes.md +1 -1
  49. package/api/@xmachines/play-router/functions/extractRoute.md +1 -1
  50. package/api/@xmachines/play-router/functions/findRouteById.md +1 -1
  51. package/api/@xmachines/play-router/functions/findRouteByPath.md +1 -1
  52. package/api/@xmachines/play-router/functions/getNavigableRoutes.md +1 -1
  53. package/api/@xmachines/play-router/functions/getRoutableRoutes.md +1 -1
  54. package/api/@xmachines/play-router/functions/routeExists.md +1 -1
  55. package/api/@xmachines/play-router/functions/validateRouteFormat.md +1 -1
  56. package/api/@xmachines/play-router/functions/validateStateExists.md +1 -1
  57. package/api/@xmachines/play-router/interfaces/BaseRouteMapping.md +3 -3
  58. package/api/@xmachines/play-router/interfaces/BrowserHistory.md +19 -15
  59. package/api/@xmachines/play-router/interfaces/BrowserWindow.md +14 -14
  60. package/api/@xmachines/play-router/interfaces/ConnectRouterOptions.md +6 -6
  61. package/api/@xmachines/play-router/interfaces/PlayRouteEvent.md +6 -6
  62. package/api/@xmachines/play-router/interfaces/RouteInfo.md +8 -8
  63. package/api/@xmachines/play-router/interfaces/RouteMap.md +4 -4
  64. package/api/@xmachines/play-router/interfaces/RouteNode.md +10 -10
  65. package/api/@xmachines/play-router/interfaces/RouteObject.md +2 -2
  66. package/api/@xmachines/play-router/interfaces/RouteTree.md +4 -4
  67. package/api/@xmachines/play-router/interfaces/RouteWatcherHandle.md +55 -0
  68. package/api/@xmachines/play-router/interfaces/RouterBridge.md +3 -3
  69. package/api/@xmachines/play-router/interfaces/StateVisit.md +4 -4
  70. package/api/@xmachines/play-router/interfaces/VanillaRouter.md +4 -4
  71. package/api/@xmachines/play-router/type-aliases/RouteMetadata.md +1 -1
  72. package/api/@xmachines/play-signals/README.md +22 -10
  73. package/api/@xmachines/play-signals/functions/watchSignal.md +35 -0
  74. package/api/@xmachines/play-signals/interfaces/ComputedOptions.md +2 -2
  75. package/api/@xmachines/play-signals/interfaces/SignalComputed.md +2 -2
  76. package/api/@xmachines/play-signals/interfaces/SignalOptions.md +2 -2
  77. package/api/@xmachines/play-signals/interfaces/SignalState.md +3 -3
  78. package/api/@xmachines/play-signals/interfaces/SignalWatcher.md +4 -4
  79. package/api/@xmachines/play-signals/type-aliases/WatcherNotify.md +1 -1
  80. package/api/@xmachines/play-solid/README.md +193 -219
  81. package/api/@xmachines/play-solid/functions/defineRegistry.md +47 -0
  82. package/api/@xmachines/play-solid/functions/useActor.md +13 -0
  83. package/api/@xmachines/play-solid/functions/useStateBinding.md +23 -0
  84. package/api/@xmachines/play-solid/interfaces/ComponentContext.md +35 -0
  85. package/api/@xmachines/play-solid/interfaces/PlayRendererProps.md +15 -7
  86. package/api/@xmachines/play-solid/type-aliases/ComponentFn.md +34 -0
  87. package/api/@xmachines/play-solid/type-aliases/PlayActor.md +9 -0
  88. package/api/@xmachines/play-solid/variables/PlayRenderer.md +15 -43
  89. package/api/@xmachines/play-solid-router/README.md +2 -0
  90. package/api/@xmachines/play-solid-router/classes/RouteMap.md +6 -6
  91. package/api/@xmachines/play-solid-router/classes/SolidRouterBridge.md +37 -37
  92. package/api/@xmachines/play-solid-router/functions/PlayRouterProvider.md +11 -5
  93. package/api/@xmachines/play-solid-router/functions/createRouteMap.md +1 -1
  94. package/api/@xmachines/play-solid-router/interfaces/AbstractActor.md +18 -17
  95. package/api/@xmachines/play-solid-router/interfaces/PlayRouterProviderProps.md +14 -8
  96. package/api/@xmachines/play-solid-router/interfaces/RouteMapping.md +3 -3
  97. package/api/@xmachines/play-solid-router/type-aliases/RoutableActor.md +3 -1
  98. package/api/@xmachines/play-solid-router/type-aliases/SolidRouterHooks.md +4 -4
  99. package/api/@xmachines/play-tanstack-react-router/README.md +1 -5
  100. package/api/@xmachines/play-tanstack-react-router/classes/RouteMap.md +5 -5
  101. package/api/@xmachines/play-tanstack-react-router/classes/TanStackReactRouterBridge.md +45 -33
  102. package/api/@xmachines/play-tanstack-react-router/functions/PlayRouterProvider.md +11 -5
  103. package/api/@xmachines/play-tanstack-react-router/functions/createRouteMap.md +2 -2
  104. package/api/@xmachines/play-tanstack-react-router/functions/createRouteMapFromTree.md +1 -1
  105. package/api/@xmachines/play-tanstack-react-router/functions/extractParams.md +1 -1
  106. package/api/@xmachines/play-tanstack-react-router/functions/extractQueryParams.md +1 -1
  107. package/api/@xmachines/play-tanstack-react-router/interfaces/PlayRouterProviderProps.md +14 -8
  108. package/api/@xmachines/play-tanstack-react-router/interfaces/RouteMapping.md +3 -3
  109. package/api/@xmachines/play-tanstack-react-router/interfaces/RouteNavigateEvent.md +3 -3
  110. package/api/@xmachines/play-tanstack-react-router/type-aliases/TanStackRouterInstance.md +1 -1
  111. package/api/@xmachines/play-tanstack-react-router/type-aliases/TanStackRouterLike.md +24 -4
  112. package/api/@xmachines/play-tanstack-solid-router/classes/RouteMap.md +6 -6
  113. package/api/@xmachines/play-tanstack-solid-router/classes/SolidRouterBridge.md +33 -33
  114. package/api/@xmachines/play-tanstack-solid-router/functions/PlayRouterProvider.md +11 -5
  115. package/api/@xmachines/play-tanstack-solid-router/functions/createRouteMap.md +1 -1
  116. package/api/@xmachines/play-tanstack-solid-router/interfaces/PlayRouterProviderProps.md +14 -8
  117. package/api/@xmachines/play-tanstack-solid-router/interfaces/RouteMapping.md +3 -3
  118. package/api/@xmachines/play-tanstack-solid-router/type-aliases/RoutableActor.md +3 -1
  119. package/api/@xmachines/play-tanstack-solid-router/type-aliases/TanStackRouterInstance.md +1 -1
  120. package/api/@xmachines/play-tanstack-solid-router/type-aliases/TanStackRouterLike.md +4 -4
  121. package/api/@xmachines/play-vue/README.md +216 -209
  122. package/api/@xmachines/play-vue/functions/defineRegistry.md +32 -0
  123. package/api/@xmachines/play-vue/functions/useActor.md +13 -0
  124. package/api/@xmachines/play-vue/functions/useStateBinding.md +30 -0
  125. package/api/@xmachines/play-vue/interfaces/ComponentContext.md +35 -0
  126. package/api/@xmachines/play-vue/interfaces/PlayRendererProps.md +14 -6
  127. package/api/@xmachines/play-vue/type-aliases/ComponentFn.md +33 -0
  128. package/api/@xmachines/play-vue/type-aliases/PlayActor.md +9 -0
  129. package/api/@xmachines/play-vue/variables/PlayRenderer.md +1 -1
  130. package/api/@xmachines/play-vue-router/README.md +21 -0
  131. package/api/@xmachines/play-vue-router/classes/RouteMap.md +7 -7
  132. package/api/@xmachines/play-vue-router/classes/VueBaseRouteMap.md +7 -7
  133. package/api/@xmachines/play-vue-router/classes/VueRouterBridge.md +48 -51
  134. package/api/@xmachines/play-vue-router/functions/createRouteMap.md +1 -1
  135. package/api/@xmachines/play-vue-router/interfaces/RouteMapping.md +4 -4
  136. package/api/@xmachines/play-vue-router/type-aliases/RoutableActor.md +3 -1
  137. package/api/@xmachines/play-vue-router/variables/PlayRouterProvider.md +7 -1
  138. package/api/@xmachines/play-xstate/README.md +236 -111
  139. package/api/@xmachines/play-xstate/classes/PlayerActor.md +36 -33
  140. package/api/@xmachines/play-xstate/functions/buildRouteUrl.md +24 -18
  141. package/api/@xmachines/play-xstate/functions/composeGuards.md +1 -1
  142. package/api/@xmachines/play-xstate/functions/composeGuardsOr.md +1 -1
  143. package/api/@xmachines/play-xstate/functions/definePlayer.md +12 -61
  144. package/api/@xmachines/play-xstate/functions/deriveRoute.md +1 -1
  145. package/api/@xmachines/play-xstate/functions/eventMatches.md +1 -1
  146. package/api/@xmachines/play-xstate/functions/formatPlayRouteTransitions.md +1 -1
  147. package/api/@xmachines/play-xstate/functions/hasContext.md +1 -1
  148. package/api/@xmachines/play-xstate/functions/isAbsoluteRoute.md +1 -1
  149. package/api/@xmachines/play-xstate/functions/negateGuard.md +1 -1
  150. package/api/@xmachines/play-xstate/functions/stateMatches.md +1 -1
  151. package/api/@xmachines/play-xstate/interfaces/PlayerConfig.md +9 -13
  152. package/api/@xmachines/play-xstate/interfaces/PlayerFactoryResumeOptions.md +2 -2
  153. package/api/@xmachines/play-xstate/interfaces/PlayerOptions.md +8 -9
  154. package/api/@xmachines/play-xstate/interfaces/RouteContext.md +5 -5
  155. package/api/@xmachines/play-xstate/type-aliases/ComposedGuard.md +1 -1
  156. package/api/@xmachines/play-xstate/type-aliases/Guard.md +1 -1
  157. package/api/@xmachines/play-xstate/type-aliases/GuardArray.md +1 -1
  158. package/api/@xmachines/play-xstate/type-aliases/PlayerFactory.md +1 -1
  159. package/api/@xmachines/play-xstate/type-aliases/RouteMachineConfig.md +14 -4
  160. package/api/@xmachines/play-xstate/type-aliases/RouteStateNode.md +19 -4
  161. package/api/@xmachines/shared/functions/defineXmVitestConfig.md +2 -2
  162. package/api/@xmachines/shared/functions/xmAliases.md +1 -1
  163. package/api/README.md +1 -1
  164. package/api/llms.txt +11 -5
  165. package/examples/multi-router-integration.md +31 -19
  166. package/package.json +2 -2
  167. package/api/@xmachines/play-catalog/README.md +0 -331
  168. package/api/@xmachines/play-catalog/functions/defineCatalog.md +0 -98
  169. package/api/@xmachines/play-catalog/functions/defineComponents.md +0 -134
  170. package/api/@xmachines/play-catalog/type-aliases/Catalog.md +0 -48
  171. package/api/@xmachines/play-catalog/type-aliases/ComponentsFor.md +0 -20
  172. package/api/@xmachines/play-catalog/type-aliases/InferComponentProps.md +0 -65
  173. package/api/@xmachines/play-catalog/type-aliases/NoExtraKeys.md +0 -17
  174. package/api/@xmachines/play-xstate/functions/mergeViewProps.md +0 -26
  175. package/api/@xmachines/play-xstate/functions/validateComponentBinding.md +0 -39
  176. package/api/@xmachines/play-xstate/functions/validateViewProps.md +0 -80
  177. package/api/@xmachines/play-xstate/interfaces/CatalogEntry.md +0 -16
  178. package/api/@xmachines/play-xstate/type-aliases/Catalog.md +0 -21
  179. package/api/@xmachines/play-xstate/type-aliases/ValidationResult.md +0 -17
  180. 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 consuming signals and UI schema with provider pattern**
5
+ **React renderer for XMachines Play Architecture**
6
6
 
7
- Signal-driven React rendering layer observing actor state with zero React state for business logic.
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` 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.
11
+ `@xmachines/play-react` provides `PlayRenderer`, a React component that:
12
12
 
13
- Per [RFC Play v1](https://gitlab.com/xmachin-es/rfc/-/blob/main/src/play-v1.md), this package implements:
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
- - **Signal-Only Reactivity (INV-05):** No useState/useReducer for business logic, signals only
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
- **Key Principle:** React state is never used for business logic. Signals are the source of truth.
19
-
20
- Renderer receives actor via props (provider pattern), not children.
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
- - `useSignalEffect`
33
- - `PlayErrorBoundary`
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
- - `PlayErrorBoundaryProps` (type)
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
- ```typescript
45
- import { createRoot } from "react-dom/client";
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 (business logic layer)
56
+ // 1. Define catalog the contract between machine spec and UI components
52
57
  const catalog = defineCatalog({
53
- LoginForm: z.object({ error: z.string().optional() }),
54
- Dashboard: z.object({
55
- userId: z.string(),
56
- username: z.string(),
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. Create React components (view layer)
61
- const components = {
62
- LoginForm: ({ error, send }) => (
63
- <form
64
- onSubmit={(e) => {
65
- e.preventDefault();
66
- const data = new FormData(e.currentTarget);
67
- send({
68
- type: "auth.login",
69
- username: data.get("username"),
70
- });
71
- }}
72
- >
73
- {error && <p style={{ color: "red" }}>{error}</p>}
74
- <input name="username" required placeholder="Username" />
75
- <button type="submit">Log In</button>
76
- </form>
77
- ),
78
- Dashboard: ({ userId, username, send }) => (
79
- <div>
80
- <h1>Welcome, {username}!</h1>
81
- <p>User ID: {userId}</p>
82
- <button onClick={() => send({ type: "auth.logout" })}>Log Out</button>
83
- </div>
84
- ),
85
- };
86
-
87
- // 3. Create player actor (business logic runtime)
88
- const createPlayer = definePlayer({ machine: authMachine, catalog });
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
- // 4. Render UI (actor via props)
93
- const root = createRoot(document.getElementById("app")!);
94
- root.render(<PlayRenderer actor={actor} components={components} />);
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 renderer component subscribing to actor signals and dynamically rendering catalog components:
176
+ Main component. Subscribes to `actor.currentView` and renders the spec.
102
177
 
103
- ```typescript
104
- interface PlayRendererProps {
105
- actor: AbstractActor<AnyActorLogic> & Viewable;
106
- components: Record<string, React.ElementType>;
107
- fallback?: React.ReactNode;
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
- **Props:**
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
- **Behavior:**
190
+ **`registry`** — Built with `defineRegistry(catalog, { components, actions })` from `@xmachines/play-react`.
118
191
 
119
- 1. Subscribes to `actor.currentView` signal via `useSignalEffect`
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
- **Example:**
124
-
125
- ```typescript
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
- components={{
129
- HomePage: ({ send }) => <div>Home</div>,
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
- ### useSignalEffect()
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
- - Tracks signal dependencies automatically via `Signal.Computed` wrapper
150
- - Uses `Signal.subtle.Watcher` with microtask batching
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
- **Canonical watcher lifecycle:**
155
-
156
- 1. `notify`
157
- 2. `queueMicrotask`
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
- function RouteDisplay({ actor }: { actor: AbstractActor<any> }) {
171
- const [route, setRoute] = useState<string | null>(null);
213
+ const store: StateStore = xstateStoreStateStore({ atom: createAtom({ username: "alice" }) });
172
214
 
173
- useSignalEffect(() => {
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
- ### PlayErrorBoundary
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
- **Props:**
220
+ ---
197
221
 
198
- - `fallback` — ReactNode rendered when a child component throws. Defaults to `null`.
199
- - `onError` — Optional callback forwarded on every caught error. Use for production observability (Sentry, Datadog, custom logging).
222
+ ### `useActor`
200
223
 
201
- **Example:**
224
+ Hook for accessing the actor from inside any component rendered by `PlayRenderer`. No prop drilling needed.
202
225
 
203
226
  ```tsx
204
- import { PlayErrorBoundary } from "@xmachines/play-react";
227
+ import { useActor } from "@xmachines/play-react";
205
228
 
206
- <PlayErrorBoundary
207
- fallback={<div className="error">Something went wrong.</div>}
208
- onError={(error) => Sentry.captureException(error)}
209
- >
210
- <YourCatalogComponent />
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
- Works with React 18 and React 19. Uses the standard class component `componentDidCatch` + `getDerivedStateFromError` pattern.
236
+ Throws `"useActor() must be called inside <PlayRenderer>"` if called outside the tree.
215
237
 
216
- ## Examples
238
+ ---
217
239
 
218
- ### Component Receiving Props from Catalog
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
- // Define schema in catalog
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
- ### useSignalEffect for Custom Rendering
267
-
268
- ```typescript
244
+ ```tsx
269
245
  import { useSignalEffect } from "@xmachines/play-react";
270
- import { AbstractActor } from "@xmachines/play-actor";
271
246
 
272
- function CustomRenderer({ actor }: { actor: AbstractActor<any> }) {
273
- const [view, setView] = useState(null);
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 currentView = actor.currentView.get();
278
- setView(currentView);
251
+ const snap = actor.state.get();
252
+ setIsAuth((snap.context as { isAuthenticated: boolean }).isAuthenticated);
279
253
  });
280
254
 
281
- if (!view) return <div>No view</div>;
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
- ### Provider Pattern
294
-
295
- ```typescript
296
- import { PlayTanStackRouterProvider } from "@xmachines/play-tanstack-react-router";
297
- import { PlayRenderer } from "@xmachines/play-react";
259
+ ---
298
260
 
299
- // Renderer receives actor via props (not children)
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
- // Header component also receives actor
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
- useSignalEffect(() => {
324
- setRoute(actor.currentRoute.get());
325
- });
265
+ ```tsx
266
+ import { PlayErrorBoundary } from "@xmachines/play-react";
326
267
 
327
- return (
328
- <header>
329
- <nav>Current: {route}</nav>
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
- ## Architecture
336
-
337
- This package implements **Signal-Only Reactivity (INV-05)** and **Passive Infrastructure (INV-04)**:
273
+ ---
338
274
 
339
- 1. **No Business Logic in React:**
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
- 2. **Signals as Source of Truth:**
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
- 3. **Event Forwarding:**
350
- - Components receive `send` function via props
351
- - User actions send events to actor (e.g., `{ type: "auth.login" }`)
352
- - Actor guards validate and process events
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
- ## Benefits
285
+ Priority: **route param fills `undefined` slots; explicit non-`undefined` spec props always win.**
375
286
 
376
- - **Framework Swappable:** Business logic has zero React imports
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
- ## Related Packages
289
+ ## Error Handling
383
290
 
384
- - **[@xmachines/play-xstate](../play-xstate/README.md)** - XState adapter providing actors
385
- - **[@xmachines/play-catalog](../play-catalog/README.md)** - UI schema validation
386
- - **[@xmachines/play-tanstack-react-router](../play-tanstack-react-router/README.md)** - TanStack Router integration
387
- - **[@xmachines/play-actor](../play-actor/README.md)** - Actor base
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
- ## License
296
+ ---
391
297
 
392
- Copyright (c) 2016 [Mikael Karon](mailto:mikael@karon.se). All rights reserved.
298
+ ## Architecture Notes
393
299
 
394
- This work is licensed under the terms of the MIT license.
395
- For a copy, see <https://opensource.org/licenses/MIT>.
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 from catalog definitions. This package enables
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)