@xmachines/play-vue 1.0.0-beta.3 → 1.0.0-beta.31

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 (71) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +234 -84
  3. package/dist/PlayRenderer.js +2 -4
  4. package/dist/PlayRenderer.js.map +1 -1
  5. package/dist/PlayRenderer.vue.d.ts +40 -0
  6. package/dist/PlayRenderer.vue.d.ts.map +1 -0
  7. package/dist/PlayRenderer.vue_vue_type_script_lang.js +57 -18
  8. package/dist/PlayRenderer.vue_vue_type_script_lang.js.map +1 -1
  9. package/dist/define-registry.d.ts +79 -0
  10. package/dist/define-registry.d.ts.map +1 -0
  11. package/dist/define-registry.js +25 -0
  12. package/dist/define-registry.js.map +1 -0
  13. package/dist/index.d.ts +15 -1
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +5 -2
  16. package/dist/node_modules/@json-render/core/dist/chunk-AFLK3Q4T.js +111 -0
  17. package/dist/node_modules/@json-render/core/dist/chunk-AFLK3Q4T.js.map +1 -0
  18. package/dist/node_modules/@json-render/core/dist/index.js +956 -0
  19. package/dist/node_modules/@json-render/core/dist/index.js.map +1 -0
  20. package/dist/node_modules/@json-render/core/dist/store-utils.js +1 -0
  21. package/dist/node_modules/@json-render/vue/dist/chunk-WIPZLAF7.js +57 -0
  22. package/dist/node_modules/@json-render/vue/dist/chunk-WIPZLAF7.js.map +1 -0
  23. package/dist/node_modules/@json-render/vue/dist/index.js +798 -0
  24. package/dist/node_modules/@json-render/vue/dist/index.js.map +1 -0
  25. package/dist/node_modules/@json-render/xstate/dist/index.js +20 -0
  26. package/dist/node_modules/@json-render/xstate/dist/index.js.map +1 -0
  27. package/dist/node_modules/@xstate/store/dist/store-69e7e2d5.esm.js +227 -0
  28. package/dist/node_modules/@xstate/store/dist/store-69e7e2d5.esm.js.map +1 -0
  29. package/dist/node_modules/zod/v4/classic/errors.js +25 -0
  30. package/dist/node_modules/zod/v4/classic/errors.js.map +1 -0
  31. package/dist/node_modules/zod/v4/classic/iso.js +33 -0
  32. package/dist/node_modules/zod/v4/classic/iso.js.map +1 -0
  33. package/dist/node_modules/zod/v4/classic/parse.js +8 -0
  34. package/dist/node_modules/zod/v4/classic/parse.js.map +1 -0
  35. package/dist/node_modules/zod/v4/classic/schemas.js +362 -0
  36. package/dist/node_modules/zod/v4/classic/schemas.js.map +1 -0
  37. package/dist/node_modules/zod/v4/core/api.js +530 -0
  38. package/dist/node_modules/zod/v4/core/api.js.map +1 -0
  39. package/dist/node_modules/zod/v4/core/checks.js +285 -0
  40. package/dist/node_modules/zod/v4/core/checks.js.map +1 -0
  41. package/dist/node_modules/zod/v4/core/core.js +46 -0
  42. package/dist/node_modules/zod/v4/core/core.js.map +1 -0
  43. package/dist/node_modules/zod/v4/core/doc.js +25 -0
  44. package/dist/node_modules/zod/v4/core/doc.js.map +1 -0
  45. package/dist/node_modules/zod/v4/core/errors.js +43 -0
  46. package/dist/node_modules/zod/v4/core/errors.js.map +1 -0
  47. package/dist/node_modules/zod/v4/core/json-schema-processors.js +183 -0
  48. package/dist/node_modules/zod/v4/core/json-schema-processors.js.map +1 -0
  49. package/dist/node_modules/zod/v4/core/parse.js +70 -0
  50. package/dist/node_modules/zod/v4/core/parse.js.map +1 -0
  51. package/dist/node_modules/zod/v4/core/regexes.js +27 -0
  52. package/dist/node_modules/zod/v4/core/regexes.js.map +1 -0
  53. package/dist/node_modules/zod/v4/core/registries.js +42 -0
  54. package/dist/node_modules/zod/v4/core/registries.js.map +1 -0
  55. package/dist/node_modules/zod/v4/core/schemas.js +823 -0
  56. package/dist/node_modules/zod/v4/core/schemas.js.map +1 -0
  57. package/dist/node_modules/zod/v4/core/to-json-schema.js +224 -0
  58. package/dist/node_modules/zod/v4/core/to-json-schema.js.map +1 -0
  59. package/dist/node_modules/zod/v4/core/util.js +268 -0
  60. package/dist/node_modules/zod/v4/core/util.js.map +1 -0
  61. package/dist/node_modules/zod/v4/core/versions.js +10 -0
  62. package/dist/node_modules/zod/v4/core/versions.js.map +1 -0
  63. package/dist/types.d.ts +24 -6
  64. package/dist/types.d.ts.map +1 -1
  65. package/dist/useActor.d.ts +35 -0
  66. package/dist/useActor.d.ts.map +1 -0
  67. package/dist/useActor.js +15 -0
  68. package/dist/useActor.js.map +1 -0
  69. package/package.json +27 -12
  70. package/dist/_virtual/_plugin-vue_export-helper.js +0 -8
  71. package/dist/index.css +0 -2
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mikael Karon
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,137 +1,287 @@
1
1
  # @xmachines/play-vue
2
2
 
3
- Vue renderer component for XMachines Play architecture.
3
+ **Vue 3 renderer for XMachines Play Architecture**
4
4
 
5
- This package keeps Vue as passive infrastructure: it observes actor signals and forwards events, but does not enforce business policy.
5
+ Bridges TC39 Signal-driven actors to Vue's reactivity. Business logic stays in the actor; Vue is purely a rendering target.
6
+
7
+ ## Overview
8
+
9
+ `@xmachines/play-vue` provides `PlayRenderer`, a Vue component that:
10
+
11
+ - Subscribes to `actor.currentView` (TC39 Signal) and re-renders on every state transition
12
+ - Renders the current view's JSON spec via `@json-render/vue`
13
+ - Routes action names from spec elements to `actor.send()` via the `actions` prop
14
+ - Manages per-view UI state in an `@xstate/store` atom (automatic or caller-supplied)
15
+
16
+ Per [Play RFC](../docs/rfc/play.md):
17
+
18
+ - **Actor Authority (INV-01):** Guards in the machine decide all state transitions
19
+ - **Passive Infrastructure (INV-04):** Vue observes signals and dispatches events — never decides
20
+ - **Signal-Only Reactivity (INV-05):** `actor.currentView` signal is the sole render trigger
6
21
 
7
22
  ## Installation
8
23
 
9
24
  ```bash
10
- npm install @xmachines/play-vue vue
25
+ npm install @xmachines/play-vue
26
+ npm install @json-render/vue @json-render/core # peer deps
27
+ npm install @json-render/xstate @xstate/store # store integration
11
28
  ```
12
29
 
30
+ In this monorepo, the root install applies a `patch-package` patch to `@json-render/vue`
31
+ so `defineRegistry(..., { onRenderError })` can intercept inner element-boundary errors
32
+ without muting console output.
33
+
13
34
  ## Current Exports
14
35
 
15
- - `PlayRenderer` (Vue component)
36
+ - `PlayRenderer` — main renderer component (Vue SFC)
37
+ - `useActor` — composable for accessing the actor inside a `PlayRenderer` tree
38
+ - `defineRegistry` — SFC-aware wrapper; auto-wraps `.vue` SFCs via `h(SFC, ctx)`
39
+ - `useBoundProp` — re-exported from `@json-render/vue`
40
+ - `ComponentFn` (type) — re-exported from `@json-render/vue`
41
+ - `ComponentContext` (type) — re-exported from `@json-render/vue`
16
42
  - `PlayRendererProps` (type)
43
+ - `PlayActor` (type)
17
44
 
18
- ## Usage
45
+ ## Quick Start
46
+
47
+ ```ts
48
+ // catalog.ts — shared contract
49
+ import { defineCatalog } from "@json-render/core";
50
+ import { z } from "zod";
51
+
52
+ export const catalog = defineCatalog({
53
+ elements: {
54
+ Login: { props: z.object({ title: z.string() }), description: "Login form" },
55
+ Dashboard: { props: z.object({ username: z.string() }), description: "Dashboard" },
56
+ },
57
+ });
58
+
59
+ export type Catalog = typeof catalog;
60
+ ```
19
61
 
20
62
  ```vue
63
+ <!-- Login.vue — Vue SFC; useBoundProp works in <script setup> -->
21
64
  <script setup lang="ts">
22
- import { PlayRenderer } from "@xmachines/play-vue";
23
- import { definePlayer } from "@xmachines/play-xstate";
24
- import { defineCatalog } from "@xmachines/play-catalog";
65
+ import { useBoundProp } from "@xmachines/play-vue";
66
+ import type { ComponentContext } from "@xmachines/play-vue";
67
+ import type { Catalog } from "./catalog.js";
68
+
69
+ const { props, emit, bindings } = defineProps<ComponentContext<Catalog, "Login">>();
70
+ const [username, setUsername] = useBoundProp<string>(bindings?.username ?? "/username");
71
+ </script>
25
72
 
26
- // Define catalog
27
- const catalog = defineCatalog({
28
- home: { component: "Home", props: { title: "string" } },
29
- login: { component: "Login", props: { error: "string?" } },
30
- dashboard: { component: "Dashboard", props: { userId: "string" } },
73
+ <template>
74
+ <div class="view">
75
+ <h2>{{ props.title }}</h2>
76
+ <form @submit.prevent="emit('submit')">
77
+ <input id="username" v-model="username" @input="setUsername(username)" />
78
+ <button type="submit">Log In</button>
79
+ </form>
80
+ </div>
81
+ </template>
82
+ ```
83
+
84
+ ```ts
85
+ // registry.ts — pass SFCs directly; defineRegistry auto-wraps them
86
+ import { defineRegistry } from "@xmachines/play-vue";
87
+ import { catalog } from "./catalog.js";
88
+ import LoginSFC from "./Login.vue";
89
+ import DashboardSFC from "./Dashboard.vue";
90
+
91
+ export const registryResult = defineRegistry(catalog, {
92
+ components: { Login: LoginSFC, Dashboard: DashboardSFC },
93
+ actions: {
94
+ login: async (params) => {
95
+ if (!params) return;
96
+ actor.send({ type: "auth.login", username: params.username });
97
+ },
98
+ logout: async (params) => {
99
+ actor.send({ type: "auth.logout" });
100
+ },
101
+ },
31
102
  });
103
+ ```
32
104
 
33
- // Create actor
34
- const actor = definePlayer({ machine: authMachine, catalog })();
35
- actor.start();
105
+ ```ts
106
+ // machine.ts
107
+ import { setup, assign } from "xstate";
108
+ import { formatPlayRouteTransitions } from "@xmachines/play-xstate";
109
+
110
+ export const machine = setup({
111
+ types: {
112
+ context: {} as {
113
+ isAuthenticated: boolean;
114
+ username: string | null;
115
+ params: Record<string, string>;
116
+ query: Record<string, string>;
117
+ },
118
+ events: {} as
119
+ | { type: "auth.login"; username: string }
120
+ | { type: "auth.logout" }
121
+ | { type: "play.route"; to: string; params?: Record<string, string> },
122
+ },
123
+ }).createMachine(
124
+ formatPlayRouteTransitions({
125
+ id: "app",
126
+ initial: "login",
127
+ context: { isAuthenticated: false, username: null, params: {}, query: {} },
128
+ states: {
129
+ login: {
130
+ id: "login",
131
+ meta: {
132
+ route: "/login",
133
+ view: {
134
+ component: "Login",
135
+ spec: {
136
+ root: "root",
137
+ elements: {
138
+ root: { type: "Login", props: { title: "Sign In" }, children: [] },
139
+ },
140
+ },
141
+ },
142
+ },
143
+ },
144
+ dashboard: {
145
+ id: "dashboard",
146
+ meta: {
147
+ route: "/dashboard",
148
+ view: {
149
+ component: "Dashboard",
150
+ spec: {
151
+ root: "root",
152
+ elements: {
153
+ root: { type: "Dashboard", props: { username: "" }, children: [] },
154
+ },
155
+ },
156
+ },
157
+ },
158
+ },
159
+ },
160
+ on: {
161
+ "auth.login": {
162
+ target: ".dashboard",
163
+ guard: ({ context }) => !context.isAuthenticated,
164
+ actions: assign({ isAuthenticated: true, username: ({ event }) => event.username }),
165
+ },
166
+ "auth.logout": {
167
+ target: ".login",
168
+ guard: ({ context }) => context.isAuthenticated,
169
+ actions: assign({ isAuthenticated: false, username: null }),
170
+ },
171
+ },
172
+ }),
173
+ );
174
+ ```
175
+
176
+ ```vue
177
+ <!-- App.vue -->
178
+ <script setup lang="ts">
179
+ import { definePlayer } from "@xmachines/play-xstate";
180
+ import { PlayRenderer } from "@xmachines/play-vue";
181
+ import { machine } from "./machine.js";
182
+ import { registryResult } from "./registry.js";
36
183
 
37
- // Define component map
38
- const components = {
39
- Home: HomeComponent,
40
- Login: LoginComponent,
41
- Dashboard: DashboardComponent,
42
- };
184
+ const createPlayer = definePlayer({ machine });
185
+ const actor = createPlayer();
186
+ actor.start();
43
187
  </script>
44
188
 
45
189
  <template>
46
- <PlayRenderer :actor="actor" :components="components">
47
- <template #fallback>
48
- <div>Loading...</div>
49
- </template>
50
- </PlayRenderer>
190
+ <PlayRenderer :actor="actor" :registryResult="registryResult" />
51
191
  </template>
52
192
  ```
53
193
 
54
- ## API
55
-
56
- ### PlayRenderer
194
+ ## API Reference
57
195
 
58
- Main renderer component that observes `actor.currentView` signal and dynamically renders catalog components.
196
+ ### `PlayRenderer`
59
197
 
60
- **Props:**
198
+ Main Vue component. Subscribes to `actor.currentView` and renders the spec.
61
199
 
62
- - `actor` (required) - Actor instance with `currentView` signal
63
- - `components` (required) - Map of component names to Vue components
64
- - `fallback` (optional) - Slot shown when `currentView` is null
200
+ ```vue
201
+ <PlayRenderer :actor="actor" :registryResult="registryResult" :store="myStore" />
202
+ ```
65
203
 
66
- **Component Props:**
204
+ **`actor`** — A `PlayerActor` (or any `AbstractActor & Viewable`). Provides the `currentView` signal.
67
205
 
68
- Each rendered component receives:
206
+ **`registryResult`** The full `DefineRegistryResult` returned by `defineRegistry(catalog, { components, actions })` from `@xmachines/play-vue`. Pass `.vue` SFCs directly — they are auto-wrapped via `h(SFC, ctx)` so `useBoundProp` and other composables work inside `<script setup>`.
69
207
 
70
- - All props from `view.props` (spread via `v-bind`)
71
- - `send` function for sending events to actor
208
+ `defineRegistry` also accepts `onRenderError(error, elementType)`, which receives errors
209
+ caught by `@json-render/vue`'s inner element boundary before the default logger is used.
72
210
 
73
- **Example Component:**
211
+ **`store`** (optional) — Controls per-view UI state (`$state` bindings, form values):
74
212
 
75
- ```vue
76
- <script setup lang="ts">
77
- import type { AbstractActor } from "@xmachines/play-actor";
213
+ - **Omitted (uncontrolled, default):** A fresh `@xstate/store` atom is created per view transition, seeded from `view.spec.state`.
214
+ - **Provided (controlled):** The caller owns the store; `spec.state` is ignored.
78
215
 
79
- defineProps<{
80
- userId: string;
81
- send: AbstractActor<any>["send"];
82
- }>();
216
+ ```ts
217
+ import { createAtom } from "@xstate/store";
218
+ import { xstateStoreStateStore } from "@json-render/xstate";
219
+ import type { StateStore } from "@json-render/core";
83
220
 
84
- function handleClick() {
85
- send({ type: "user.click", payload: { action: "details" } });
86
- }
87
- </script>
221
+ const store: StateStore = xstateStoreStateStore({ atom: createAtom({ username: "" }) });
222
+ ```
88
223
 
89
- <template>
90
- <div>
91
- <h1>User: {{ userId }}</h1>
92
- <button @click="handleClick">View Details</button>
93
- </div>
94
- </template>
224
+ **Inner render errors** — You can intercept catalog component render failures without
225
+ overriding the outer Vue error boundary:
226
+
227
+ ```ts
228
+ export const registryResult = defineRegistry(catalog, {
229
+ components: { Login: LoginSFC, Dashboard: DashboardSFC },
230
+ actions: {
231
+ login: async (params) => {
232
+ if (!params) return;
233
+ actor.send({ type: "auth.login", username: params.username });
234
+ },
235
+ logout: async (params) => {
236
+ actor.send({ type: "auth.logout" });
237
+ },
238
+ },
239
+ onRenderError(error, elementType) {
240
+ reportExpectedRenderError(error, elementType);
241
+ },
242
+ });
95
243
  ```
96
244
 
97
- ## Features
245
+ ```vue
246
+ <PlayRenderer :actor="actor" :registryResult="registryResult" :store="store" />
247
+ ```
98
248
 
99
- - **Signal Bridge:** Uses TC39 Signal.subtle.Watcher to bridge actor signals to Vue reactivity
100
- - **Dynamic Rendering:** Renders components based on `actor.currentView.component` string
101
- - **Type Safe:** Full TypeScript support with generic type inference
102
- - **Error Handling:** Gracefully handles missing components and null catalogs
103
- - **One-Shot Re-Watch:** Implements proper signal watcher pattern with microtask batching
249
+ ---
104
250
 
105
- ## Canonical Watcher Lifecycle
251
+ ### `useActor`
106
252
 
107
- Use the same watcher flow as other adapters/packages:
253
+ Vue composable for accessing the actor from inside any component rendered by `PlayRenderer`.
108
254
 
109
- 1. `notify`
110
- 2. `queueMicrotask`
111
- 3. `getPending()`
112
- 4. read actor signals and update framework-local state
113
- 5. re-arm with `watch(...)` or `watch()`
255
+ ```ts
256
+ import { useActor } from "@xmachines/play-vue";
114
257
 
115
- Watcher notify is one-shot. Re-arm is required for continuous observation.
258
+ // Inside any component rendered inside PlayRenderer:
259
+ const actor = useActor();
260
+ actor.send({ type: "auth.logout" });
261
+ ```
116
262
 
117
- ## Cleanup Contract
263
+ Throws `"useActor() must be called inside <PlayRenderer>"` if called outside the tree.
118
264
 
119
- Cleanup must be explicit:
265
+ ---
120
266
 
121
- - Call `unwatch(...)` during Vue lifecycle teardown (`onUnmounted`).
122
- - Treat renderer/provider disposal as deterministic teardown, not GC-only cleanup.
123
- - Keep actor-driven routing and view decisions in the actor layer.
267
+ ## Route Parameters in Props
124
268
 
125
- ## Architecture
269
+ When using `formatPlayRouteTransitions`, URL path parameters flow automatically into component props. Declare an `undefined` slot in the spec to opt in:
126
270
 
127
- PlayRenderer follows the XMachines Play architecture principles:
271
+ ```ts
272
+ // spec: { section: undefined, user: "alice" }
273
+ // After play.route to /settings/profile → context.params = { section: "profile" }
274
+ // Component receives: { section: "profile", user: "alice" }
275
+ ```
128
276
 
129
- 1. **Actor Authority:** Actor decides all state transitions
130
- 2. **Passive Infrastructure:** Renderer observes signals, sends events
131
- 3. **Signal-Only Reactivity:** Business logic state lives in actor signals
277
+ Priority: **route param fills `undefined` slots; explicit non-`undefined` spec props always win.**
132
278
 
133
- Signals are reactivity substrate only. They are not a business mutation channel.
279
+ ---
134
280
 
135
- ## License
281
+ ## Architecture Notes
136
282
 
137
- MIT
283
+ - Vue reactivity is only used to trigger re-renders — not for business logic
284
+ - `actor.currentView` (TC39 Signal) is bridged to Vue's reactive system inside `PlayRenderer`
285
+ - Per-view UI state lives in an `@xstate/store` atom, not in Vue reactive state
286
+ - `@json-render/vue` drives rendering; `PlayRenderer` is the signal bridge — import `defineRegistry`, `ComponentFn`, `ComponentContext`, and `useBoundProp` from `@xmachines/play-vue`
287
+ - Vue views should be `.vue` SFCs using `ComponentContext<MyCatalog, "X">` — `defineRegistry` from `@xmachines/play-vue` auto-wraps them via `h(SFC, ctx)`, giving each SFC its own `setup()` context where `useBoundProp` and Vue composables work correctly
@@ -1,9 +1,7 @@
1
1
  import e from "./PlayRenderer.vue_vue_type_script_lang.js";
2
- /* empty css */
3
- import t from "./_virtual/_plugin-vue_export-helper.js";
4
2
  //#region src/PlayRenderer.vue
5
- var n = /* @__PURE__ */ t(e, [["__scopeId", "data-v-275eda6e"]]);
3
+ var t = e;
6
4
  //#endregion
7
- export { n as default };
5
+ export { t as default };
8
6
 
9
7
  //# sourceMappingURL=PlayRenderer.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"PlayRenderer.js","names":[],"sources":["../src/PlayRenderer.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * PlayRenderer - Main Vue renderer component for XMachines Play architecture\n *\n * Architecture (per RESEARCH.md Pattern 1):\n * - Subscribes to actor.currentView signal via Signal.subtle.Watcher\n * - Dynamically renders catalog components based on view.component string\n * - Forwards user events to actor via actor.send()\n * - Vue ref only for triggering renders, NOT business logic\n *\n * Signal bridge uses one-shot re-watch pattern:\n * TC39 Signal watchers stop watching after notification, so watcher.watch()\n * must be called inside a microtask after getPending() to re-arm for the\n * next notification.\n *\n * CRITICAL: Never call signal.get() or signal.set() inside the Watcher's\n * notify callback. The callback runs synchronously during the signal graph's\n * dirty-propagation phase. All reads must be deferred to a queueMicrotask.\n *\n * @invariant Actor Authority - Actor decides all state transitions via guards\n * @invariant Passive Infrastructure - Component observes signals, sends events\n * @invariant Signal-Only Reactivity - Business logic state lives in actor signals\n */\n\nimport { defineComponent, ref, computed, toRaw, onUnmounted, h, type PropType } from \"vue\";\nimport { Signal } from \"@xmachines/play-signals\";\nimport type { PlayRendererProps } from \"./types.js\";\nimport type { AbstractActor, Viewable } from \"@xmachines/play-actor\";\nimport type { AnyActorLogic } from \"xstate\";\nimport type { Component } from \"vue\";\n\nexport default defineComponent({\n\tname: \"PlayRenderer\",\n\tprops: {\n\t\tactor: {\n\t\t\ttype: Object as PropType<AbstractActor<AnyActorLogic> & Viewable>,\n\t\t\trequired: true,\n\t\t},\n\t\tcomponents: {\n\t\t\ttype: Object as PropType<Record<string, Component>>,\n\t\t\trequired: true,\n\t\t},\n\t},\n\tsetup(props, { slots }) {\n\t\t// Unwrap actor from Vue's reactive proxy to access raw Signal objects\n\t\t// CRITICAL: toRaw() preserves Signal's 'this' binding for .get() and watcher operations\n\t\tconst actor = toRaw(props.actor);\n\n\t\t// Get initial value from unwrapped signal\n\t\tconst initialView = actor.currentView.get();\n\n\t\t// Vue ref for triggering re-renders (NOT business logic state)\n\t\t// Signal is source of truth, ref is just Vue's render trigger\n\t\tconst view = ref<{ component: string; props: Record<string, unknown> } | null>(initialView);\n\n\t\t// Signal watcher for bridging TC39 Signals to Vue reactivity\n\t\t// Created immediately (not in onMounted) so signal changes during the\n\t\t// synchronous mount phase are captured.\n\t\t//\n\t\t// The notify callback runs synchronously during the signal graph's\n\t\t// dirty-propagation phase. NEVER read (.get()) or write (.set()) signals\n\t\t// inside it. Only schedule a microtask to do the actual work.\n\t\tconst watcher = new Signal.subtle.Watcher(() => {\n\t\t\tqueueMicrotask(() => {\n\t\t\t\t// Step 1: Acknowledge notification (clears watcher's dirty flags)\n\t\t\t\twatcher.getPending();\n\n\t\t\t\t// Step 2: Read signal value (safe — notification phase is over)\n\t\t\t\tview.value = actor.currentView.get();\n\n\t\t\t\t// Step 3: Re-watch for next notification (one-shot pattern)\n\t\t\t\t// TC39 watchers stop notifying after first notification until re-armed\n\t\t\t\twatcher.watch(actor.currentView);\n\t\t\t});\n\t\t});\n\n\t\t// Start watching the signal\n\t\twatcher.watch(actor.currentView);\n\n\t\tonUnmounted(() => {\n\t\t\t// Unwatch to stop receiving notifications\n\t\t\twatcher.unwatch(actor.currentView);\n\t\t});\n\n\t\t// Bind send function to actor for correct 'this' context\n\t\tconst sendBound = actor.send.bind(actor);\n\n\t\t// Compute Component from view.component lookup\n\t\tconst ResolvedComponent = computed(() => {\n\t\t\tif (!view.value) return null;\n\n\t\t\t// Handle null/undefined components catalog gracefully\n\t\t\tif (!props.components) {\n\t\t\t\tconsole.error(\n\t\t\t\t\t`Components catalog is ${props.components === null ? \"null\" : \"undefined\"}. ` +\n\t\t\t\t\t\t`Cannot render component \"${view.value.component}\".`,\n\t\t\t\t);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\t// Look up component from catalog\n\t\t\tconst comp = toRaw(props.components[view.value.component]);\n\n\t\t\tif (!comp) {\n\t\t\t\tconsole.error(\n\t\t\t\t\t`Component \"${view.value.component}\" not found in catalog. ` +\n\t\t\t\t\t\t`Available components: ${Object.keys(props.components).join(\", \")}`,\n\t\t\t\t);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\treturn comp;\n\t\t});\n\n\t\t// Use render function to avoid Vue 3.5 SFC template <slot> + jsdom renderSlot crash\n\t\treturn () => {\n\t\t\t// No view — show fallback slot\n\t\t\tif (!view.value) {\n\t\t\t\treturn slots.fallback ? slots.fallback() : null;\n\t\t\t}\n\n\t\t\t// View exists but component not found\n\t\t\tif (!ResolvedComponent.value) {\n\t\t\t\treturn h(\n\t\t\t\t\t\"div\",\n\t\t\t\t\t{ class: \"play-renderer-error\" },\n\t\t\t\t\t`Component \"${view.value.component}\" not found in catalog`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Render matched component dynamically\n\t\t\treturn h(ResolvedComponent.value, {\n\t\t\t\t...view.value.props,\n\t\t\t\tsend: sendBound,\n\t\t\t});\n\t\t};\n\t},\n});\n</script>\n\n<style scoped>\n.play-renderer-error {\n\tpadding: 1rem;\n\tbackground-color: #fee;\n\tborder: 1px solid #fcc;\n\tborder-radius: 4px;\n\tcolor: #c00;\n\tfont-family: monospace;\n}\n</style>\n"],"mappings":""}
1
+ {"version":3,"file":"PlayRenderer.js","names":[],"sources":["../src/PlayRenderer.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * PlayRenderer - Main Vue renderer component for XMachines Play architecture\n *\n * Architecture:\n * - Subscribes to actor.currentView signal via TC39 Signal watcher\n * - Renders view.spec via StateProvider ActionProvider VisibilityProvider → Renderer\n * - Routes actor actions via registryResult.handlers() — real async dispatch functions\n * - Vue ref only for triggering renders, NOT business logic\n *\n * State store: uses external `store` prop if provided (controlled mode); otherwise\n * creates a fresh @xstate/store atom per view transition seeded from spec.state.\n *\n * @invariant Actor Authority - Actor decides all state transitions via guards\n * @invariant Passive Infrastructure - Component observes signals, sends events\n * @invariant Signal-Only Reactivity - Business logic state lives in actor signals\n */\n\nimport { defineComponent, ref, toRaw, markRaw, onUnmounted, h } from \"vue\";\nimport type { PropType } from \"vue\";\nimport { watchSignal } from \"@xmachines/play-signals\";\nimport type { PlayRendererProps } from \"./types.js\";\nimport type { AbstractActor, Viewable, ViewMetadata } from \"@xmachines/play-actor\";\nimport type { AnyActorLogic } from \"xstate\";\nimport type { DefineRegistryResult } from \"@json-render/vue\";\nimport type { StateStore } from \"@json-render/core\";\nimport {\n\tRenderer,\n\tStateProvider,\n\tActionProvider,\n\tVisibilityProvider,\n\tuseStateStore,\n} from \"@json-render/vue\";\nimport type { SetState } from \"@json-render/vue\";\nimport { createAtom } from \"@xstate/store\";\nimport { xstateStoreStateStore } from \"@json-render/xstate\";\nimport { provideActor, type PlayActor } from \"./useActor.js\";\n\n/**\n * Inner component that renders inside StateProvider so it can call useStateStore()\n * to get the live set/getSnapshot functions needed by registryResult.handlers().\n */\nconst PlayRendererInner = defineComponent({\n\tname: \"PlayRendererInner\",\n\tprops: {\n\t\tregistryResult: {\n\t\t\ttype: Object as PropType<DefineRegistryResult>,\n\t\t\trequired: true,\n\t\t},\n\t\tspec: {\n\t\t\ttype: Object as PropType<import(\"@json-render/core\").Spec | null>,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\tsetup(props) {\n\t\treturn () => {\n\t\t\tconst { update, getSnapshot } = useStateStore();\n\t\t\t// Build a SetState adapter: handlers factory expects updater-function pattern\n\t\t\tconst setStateAdapter: SetState = (updater) => {\n\t\t\t\tconst prev = getSnapshot();\n\t\t\t\tupdate(updater(prev));\n\t\t\t};\n\t\t\tconst handlers = props.registryResult.handlers(\n\t\t\t\t() => setStateAdapter,\n\t\t\t\t() => getSnapshot(),\n\t\t\t);\n\t\t\tconst rawRegistry = props.registryResult.registry;\n\n\t\t\treturn h(ActionProvider, { handlers }, () =>\n\t\t\t\th(VisibilityProvider, {}, () =>\n\t\t\t\t\th(Renderer, { spec: props.spec, registry: rawRegistry }),\n\t\t\t\t),\n\t\t\t);\n\t\t};\n\t},\n});\n\nexport default defineComponent({\n\tname: \"PlayRenderer\",\n\tprops: {\n\t\tactor: {\n\t\t\ttype: Object as PropType<AbstractActor<AnyActorLogic> & Viewable>,\n\t\t\trequired: true,\n\t\t},\n\t\tregistryResult: {\n\t\t\ttype: Object as PropType<DefineRegistryResult>,\n\t\t\trequired: true,\n\t\t},\n\t\tstore: {\n\t\t\ttype: Object as PropType<StateStore>,\n\t\t\tdefault: undefined,\n\t\t},\n\t},\n\tsetup(props, { slots }) {\n\t\t// Unwrap actor from Vue's reactive proxy to access raw Signal objects\n\t\tconst actor = toRaw(props.actor);\n\n\t\t// Unwrap the registryResult and mark components as raw to avoid Vue reactivity overhead\n\t\tconst rawRegistryResult: DefineRegistryResult = {\n\t\t\t...toRaw(props.registryResult),\n\t\t\tregistry: markRaw(\n\t\t\t\tObject.fromEntries(\n\t\t\t\t\tObject.entries(toRaw(props.registryResult).registry).map(([k, v]) => [\n\t\t\t\t\t\tk,\n\t\t\t\t\t\tmarkRaw(v as object),\n\t\t\t\t\t]),\n\t\t\t\t) as DefineRegistryResult[\"registry\"],\n\t\t\t),\n\t\t};\n\n\t\t// Provide the raw actor to all descendants via Vue's provide/inject mechanism\n\t\tprovideActor(actor as PlayActor);\n\n\t\t// Get initial value from unwrapped signal\n\t\tconst initialView = actor.currentView.get();\n\n\t\t// Vue ref for triggering re-renders (NOT business logic state)\n\t\tconst view = ref<ViewMetadata | null>(initialView);\n\n\t\t// Internal per-view store — recreated on each view transition when no external store.\n\t\tlet internalStore: StateStore | null = null;\n\t\tlet lastView: ViewMetadata | null = null;\n\t\tlet storeKey = 0;\n\n\t\t// Signal watcher for bridging TC39 Signals to Vue reactivity\n\t\tconst unwatch = watchSignal(actor.currentView, (nextView) => {\n\t\t\tview.value = nextView;\n\t\t});\n\n\t\tonUnmounted(() => {\n\t\t\tunwatch();\n\t\t});\n\n\t\treturn () => {\n\t\t\t// No view — show fallback slot\n\t\t\tif (!view.value) {\n\t\t\t\treturn slots.fallback ? slots.fallback() : null;\n\t\t\t}\n\n\t\t\tconst spec = view.value.spec;\n\n\t\t\t// Resolve the store: external (controlled) or internal per-view atom\n\t\t\tlet store: StateStore;\n\t\t\tif (props.store) {\n\t\t\t\tstore = props.store;\n\t\t\t} else {\n\t\t\t\tif (internalStore === null || lastView !== view.value) {\n\t\t\t\t\tconst initialState = (spec.state as Record<string, unknown>) ?? {};\n\t\t\t\t\tinternalStore = xstateStoreStateStore({ atom: createAtom(initialState) });\n\t\t\t\t\tlastView = view.value;\n\t\t\t\t\tstoreKey++;\n\t\t\t\t}\n\t\t\t\tstore = internalStore;\n\t\t\t}\n\n\t\t\t// PlayRendererInner renders inside StateProvider so useStateStore() works\n\t\t\treturn h(StateProvider, { store, key: storeKey }, () =>\n\t\t\t\th(PlayRendererInner, { registryResult: rawRegistryResult, spec }),\n\t\t\t);\n\t\t};\n\t},\n});\n</script>\n"],"mappings":""}
@@ -0,0 +1,40 @@
1
+ import { PropType } from 'vue';
2
+ import { AbstractActor, Viewable } from '@xmachines/play-actor';
3
+ import { AnyActorLogic } from 'xstate';
4
+ import { DefineRegistryResult } from '@json-render/vue';
5
+ import { StateStore } from '@json-render/core';
6
+ declare const _default: import('vue', { with: { "resolution-mode": "import" } }).DefineComponent<import('vue', { with: { "resolution-mode": "import" } }).ExtractPropTypes<{
7
+ actor: {
8
+ type: PropType<AbstractActor<AnyActorLogic> & Viewable>;
9
+ required: true;
10
+ };
11
+ registryResult: {
12
+ type: PropType<DefineRegistryResult>;
13
+ required: true;
14
+ };
15
+ store: {
16
+ type: PropType<StateStore>;
17
+ default: undefined;
18
+ };
19
+ }>, () => import('vue', { with: { "resolution-mode": "import" } }).VNode<import('vue', { with: { "resolution-mode": "import" } }).RendererNode, import('vue', { with: { "resolution-mode": "import" } }).RendererElement, {
20
+ [key: string]: any;
21
+ }> | import('vue', { with: { "resolution-mode": "import" } }).VNode<import('vue', { with: { "resolution-mode": "import" } }).RendererNode, import('vue', { with: { "resolution-mode": "import" } }).RendererElement, {
22
+ [key: string]: any;
23
+ }>[] | null, {}, {}, {}, import('vue', { with: { "resolution-mode": "import" } }).ComponentOptionsMixin, import('vue', { with: { "resolution-mode": "import" } }).ComponentOptionsMixin, {}, string, import('vue', { with: { "resolution-mode": "import" } }).PublicProps, Readonly<import('vue', { with: { "resolution-mode": "import" } }).ExtractPropTypes<{
24
+ actor: {
25
+ type: PropType<AbstractActor<AnyActorLogic> & Viewable>;
26
+ required: true;
27
+ };
28
+ registryResult: {
29
+ type: PropType<DefineRegistryResult>;
30
+ required: true;
31
+ };
32
+ store: {
33
+ type: PropType<StateStore>;
34
+ default: undefined;
35
+ };
36
+ }>> & Readonly<{}>, {
37
+ store: StateStore;
38
+ }, {}, {}, {}, string, import('vue', { with: { "resolution-mode": "import" } }).ComponentProvideOptions, true, {}, any>;
39
+ export default _default;
40
+ //# sourceMappingURL=PlayRenderer.vue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PlayRenderer.vue.d.ts","sourceRoot":"","sources":["../src/PlayRenderer.vue"],"names":[],"mappings":"AAuLA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,KAAK,CAAC;AAGpC,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAgB,MAAM,uBAAuB,CAAC;AACnF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AAC5C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;;;cAwDjC,QAAQ,CAAC,aAAa,CAAC,aAAa,CAAC,GAAG,QAAQ,CAAC;;;;cAIjD,QAAQ,CAAC,oBAAoB,CAAC;;;;cAI9B,QAAQ,CAAC,UAAU,CAAC;;;;;;;;;cARpB,QAAQ,CAAC,aAAa,CAAC,aAAa,CAAC,GAAG,QAAQ,CAAC;;;;cAIjD,QAAQ,CAAC,oBAAoB,CAAC;;;;cAI9B,QAAQ,CAAC,UAAU,CAAC;;;;;;AAZvC,wBAoFG"}
@@ -1,35 +1,74 @@
1
- import { computed as e, defineComponent as t, h as n, onUnmounted as r, ref as i, toRaw as a } from "vue";
2
- import { Signal as o } from "@xmachines/play-signals";
1
+ import { ActionProvider as e, Renderer as t, StateProvider as n, VisibilityProvider as r, useStateStore as i } from "./node_modules/@json-render/vue/dist/index.js";
2
+ import { createAtom as a } from "./node_modules/@xstate/store/dist/store-69e7e2d5.esm.js";
3
+ import { xstateStoreStateStore as o } from "./node_modules/@json-render/xstate/dist/index.js";
4
+ import { provideActor as s } from "./useActor.js";
5
+ import { defineComponent as c, h as l, markRaw as u, onUnmounted as d, ref as f, toRaw as p } from "vue";
6
+ import { watchSignal as m } from "@xmachines/play-signals";
3
7
  //#region src/PlayRenderer.vue?vue&type=script&lang.ts
4
- var s = t({
8
+ var h = c({
9
+ name: "PlayRendererInner",
10
+ props: {
11
+ registryResult: {
12
+ type: Object,
13
+ required: !0
14
+ },
15
+ spec: {
16
+ type: Object,
17
+ default: null
18
+ }
19
+ },
20
+ setup(n) {
21
+ return () => {
22
+ let { update: a, getSnapshot: o } = i(), s = (e) => {
23
+ a(e(o()));
24
+ }, c = n.registryResult.handlers(() => s, () => o()), u = n.registryResult.registry;
25
+ return l(e, { handlers: c }, () => l(r, {}, () => l(t, {
26
+ spec: n.spec,
27
+ registry: u
28
+ })));
29
+ };
30
+ }
31
+ }), g = c({
5
32
  name: "PlayRenderer",
6
33
  props: {
7
34
  actor: {
8
35
  type: Object,
9
36
  required: !0
10
37
  },
11
- components: {
38
+ registryResult: {
12
39
  type: Object,
13
40
  required: !0
41
+ },
42
+ store: {
43
+ type: Object,
44
+ default: void 0
14
45
  }
15
46
  },
16
- setup(t, { slots: s }) {
17
- let c = a(t.actor), l = i(c.currentView.get()), u = new o.subtle.Watcher(() => {
18
- queueMicrotask(() => {
19
- u.getPending(), l.value = c.currentView.get(), u.watch(c.currentView);
20
- });
21
- });
22
- u.watch(c.currentView), r(() => {
23
- u.unwatch(c.currentView);
47
+ setup(e, { slots: t }) {
48
+ let r = p(e.actor), i = {
49
+ ...p(e.registryResult),
50
+ registry: u(Object.fromEntries(Object.entries(p(e.registryResult).registry).map(([e, t]) => [e, u(t)])))
51
+ };
52
+ s(r);
53
+ let c = f(r.currentView.get()), g = null, _ = null, v = 0, y = m(r.currentView, (e) => {
54
+ c.value = e;
24
55
  });
25
- let d = c.send.bind(c), f = e(() => l.value ? t.components ? a(t.components[l.value.component]) || (console.error(`Component "${l.value.component}" not found in catalog. Available components: ${Object.keys(t.components).join(", ")}`), null) : (console.error(`Components catalog is ${t.components === null ? "null" : "undefined"}. Cannot render component "${l.value.component}".`), null) : null);
26
- return () => l.value ? f.value ? n(f.value, {
27
- ...l.value.props,
28
- send: d
29
- }) : n("div", { class: "play-renderer-error" }, `Component "${l.value.component}" not found in catalog`) : s.fallback ? s.fallback() : null;
56
+ return d(() => {
57
+ y();
58
+ }), () => {
59
+ if (!c.value) return t.fallback ? t.fallback() : null;
60
+ let r = c.value.spec, s;
61
+ return e.store ? s = e.store : ((g === null || _ !== c.value) && (g = o({ atom: a(r.state ?? {}) }), _ = c.value, v++), s = g), l(n, {
62
+ store: s,
63
+ key: v
64
+ }, () => l(h, {
65
+ registryResult: i,
66
+ spec: r
67
+ }));
68
+ };
30
69
  }
31
70
  });
32
71
  //#endregion
33
- export { s as default };
72
+ export { g as default };
34
73
 
35
74
  //# sourceMappingURL=PlayRenderer.vue_vue_type_script_lang.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"PlayRenderer.vue_vue_type_script_lang.js","names":[],"sources":["../src/PlayRenderer.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * PlayRenderer - Main Vue renderer component for XMachines Play architecture\n *\n * Architecture (per RESEARCH.md Pattern 1):\n * - Subscribes to actor.currentView signal via Signal.subtle.Watcher\n * - Dynamically renders catalog components based on view.component string\n * - Forwards user events to actor via actor.send()\n * - Vue ref only for triggering renders, NOT business logic\n *\n * Signal bridge uses one-shot re-watch pattern:\n * TC39 Signal watchers stop watching after notification, so watcher.watch()\n * must be called inside a microtask after getPending() to re-arm for the\n * next notification.\n *\n * CRITICAL: Never call signal.get() or signal.set() inside the Watcher's\n * notify callback. The callback runs synchronously during the signal graph's\n * dirty-propagation phase. All reads must be deferred to a queueMicrotask.\n *\n * @invariant Actor Authority - Actor decides all state transitions via guards\n * @invariant Passive Infrastructure - Component observes signals, sends events\n * @invariant Signal-Only Reactivity - Business logic state lives in actor signals\n */\n\nimport { defineComponent, ref, computed, toRaw, onUnmounted, h, type PropType } from \"vue\";\nimport { Signal } from \"@xmachines/play-signals\";\nimport type { PlayRendererProps } from \"./types.js\";\nimport type { AbstractActor, Viewable } from \"@xmachines/play-actor\";\nimport type { AnyActorLogic } from \"xstate\";\nimport type { Component } from \"vue\";\n\nexport default defineComponent({\n\tname: \"PlayRenderer\",\n\tprops: {\n\t\tactor: {\n\t\t\ttype: Object as PropType<AbstractActor<AnyActorLogic> & Viewable>,\n\t\t\trequired: true,\n\t\t},\n\t\tcomponents: {\n\t\t\ttype: Object as PropType<Record<string, Component>>,\n\t\t\trequired: true,\n\t\t},\n\t},\n\tsetup(props, { slots }) {\n\t\t// Unwrap actor from Vue's reactive proxy to access raw Signal objects\n\t\t// CRITICAL: toRaw() preserves Signal's 'this' binding for .get() and watcher operations\n\t\tconst actor = toRaw(props.actor);\n\n\t\t// Get initial value from unwrapped signal\n\t\tconst initialView = actor.currentView.get();\n\n\t\t// Vue ref for triggering re-renders (NOT business logic state)\n\t\t// Signal is source of truth, ref is just Vue's render trigger\n\t\tconst view = ref<{ component: string; props: Record<string, unknown> } | null>(initialView);\n\n\t\t// Signal watcher for bridging TC39 Signals to Vue reactivity\n\t\t// Created immediately (not in onMounted) so signal changes during the\n\t\t// synchronous mount phase are captured.\n\t\t//\n\t\t// The notify callback runs synchronously during the signal graph's\n\t\t// dirty-propagation phase. NEVER read (.get()) or write (.set()) signals\n\t\t// inside it. Only schedule a microtask to do the actual work.\n\t\tconst watcher = new Signal.subtle.Watcher(() => {\n\t\t\tqueueMicrotask(() => {\n\t\t\t\t// Step 1: Acknowledge notification (clears watcher's dirty flags)\n\t\t\t\twatcher.getPending();\n\n\t\t\t\t// Step 2: Read signal value (safe — notification phase is over)\n\t\t\t\tview.value = actor.currentView.get();\n\n\t\t\t\t// Step 3: Re-watch for next notification (one-shot pattern)\n\t\t\t\t// TC39 watchers stop notifying after first notification until re-armed\n\t\t\t\twatcher.watch(actor.currentView);\n\t\t\t});\n\t\t});\n\n\t\t// Start watching the signal\n\t\twatcher.watch(actor.currentView);\n\n\t\tonUnmounted(() => {\n\t\t\t// Unwatch to stop receiving notifications\n\t\t\twatcher.unwatch(actor.currentView);\n\t\t});\n\n\t\t// Bind send function to actor for correct 'this' context\n\t\tconst sendBound = actor.send.bind(actor);\n\n\t\t// Compute Component from view.component lookup\n\t\tconst ResolvedComponent = computed(() => {\n\t\t\tif (!view.value) return null;\n\n\t\t\t// Handle null/undefined components catalog gracefully\n\t\t\tif (!props.components) {\n\t\t\t\tconsole.error(\n\t\t\t\t\t`Components catalog is ${props.components === null ? \"null\" : \"undefined\"}. ` +\n\t\t\t\t\t\t`Cannot render component \"${view.value.component}\".`,\n\t\t\t\t);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\t// Look up component from catalog\n\t\t\tconst comp = toRaw(props.components[view.value.component]);\n\n\t\t\tif (!comp) {\n\t\t\t\tconsole.error(\n\t\t\t\t\t`Component \"${view.value.component}\" not found in catalog. ` +\n\t\t\t\t\t\t`Available components: ${Object.keys(props.components).join(\", \")}`,\n\t\t\t\t);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\treturn comp;\n\t\t});\n\n\t\t// Use render function to avoid Vue 3.5 SFC template <slot> + jsdom renderSlot crash\n\t\treturn () => {\n\t\t\t// No view — show fallback slot\n\t\t\tif (!view.value) {\n\t\t\t\treturn slots.fallback ? slots.fallback() : null;\n\t\t\t}\n\n\t\t\t// View exists but component not found\n\t\t\tif (!ResolvedComponent.value) {\n\t\t\t\treturn h(\n\t\t\t\t\t\"div\",\n\t\t\t\t\t{ class: \"play-renderer-error\" },\n\t\t\t\t\t`Component \"${view.value.component}\" not found in catalog`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Render matched component dynamically\n\t\t\treturn h(ResolvedComponent.value, {\n\t\t\t\t...view.value.props,\n\t\t\t\tsend: sendBound,\n\t\t\t});\n\t\t};\n\t},\n});\n</script>\n\n<style scoped>\n.play-renderer-error {\n\tpadding: 1rem;\n\tbackground-color: #fee;\n\tborder: 1px solid #fcc;\n\tborder-radius: 4px;\n\tcolor: #c00;\n\tfont-family: monospace;\n}\n</style>\n"],"mappings":";;;AA+BA,IAAA,IAAe,EAAgB;CAC9B,MAAM;CACN,OAAO;EACN,OAAO;GACN,MAAM;GACN,UAAU;GACV;EACD,YAAY;GACX,MAAM;GACN,UAAU;GACV;EACD;CACD,MAAM,GAAO,EAAE,YAAS;EAGvB,IAAM,IAAQ,EAAM,EAAM,MAAM,EAO1B,IAAO,EAJO,EAAM,YAAY,KAAK,CAIgD,EASrF,IAAU,IAAI,EAAO,OAAO,cAAc;AAC/C,wBAAqB;AASpB,IAPA,EAAQ,YAAY,EAGpB,EAAK,QAAQ,EAAM,YAAY,KAAK,EAIpC,EAAQ,MAAM,EAAM,YAAY;KAC/B;IACD;AAKF,EAFA,EAAQ,MAAM,EAAM,YAAY,EAEhC,QAAkB;AAEjB,KAAQ,QAAQ,EAAM,YAAY;IACjC;EAGF,IAAM,IAAY,EAAM,KAAK,KAAK,EAAM,EAGlC,IAAoB,QACpB,EAAK,QAGL,EAAM,aASE,EAAM,EAAM,WAAW,EAAK,MAAM,WAAW,KAGzD,QAAQ,MACP,cAAc,EAAK,MAAM,UAAU,gDACT,OAAO,KAAK,EAAM,WAAW,CAAC,KAAK,KAAK,GAClE,EACM,SAfP,QAAQ,MACP,yBAAyB,EAAM,eAAe,OAAO,SAAS,YAAY,6BAC7C,EAAK,MAAM,UAAU,IAClD,EACM,QARgB,KAuBvB;AAGF,eAEM,EAAK,QAKL,EAAkB,QAShB,EAAE,EAAkB,OAAO;GACjC,GAAG,EAAK,MAAM;GACd,MAAM;GACN,CAAC,GAXM,EACN,OACA,EAAE,OAAO,uBAAuB,EAChC,cAAc,EAAK,MAAM,UAAU,wBACnC,GATM,EAAM,WAAW,EAAM,UAAS,GAAI;;CAmB9C,CAAC"}
1
+ {"version":3,"file":"PlayRenderer.vue_vue_type_script_lang.js","names":[],"sources":["../src/PlayRenderer.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * PlayRenderer - Main Vue renderer component for XMachines Play architecture\n *\n * Architecture:\n * - Subscribes to actor.currentView signal via TC39 Signal watcher\n * - Renders view.spec via StateProvider ActionProvider VisibilityProvider → Renderer\n * - Routes actor actions via registryResult.handlers() — real async dispatch functions\n * - Vue ref only for triggering renders, NOT business logic\n *\n * State store: uses external `store` prop if provided (controlled mode); otherwise\n * creates a fresh @xstate/store atom per view transition seeded from spec.state.\n *\n * @invariant Actor Authority - Actor decides all state transitions via guards\n * @invariant Passive Infrastructure - Component observes signals, sends events\n * @invariant Signal-Only Reactivity - Business logic state lives in actor signals\n */\n\nimport { defineComponent, ref, toRaw, markRaw, onUnmounted, h } from \"vue\";\nimport type { PropType } from \"vue\";\nimport { watchSignal } from \"@xmachines/play-signals\";\nimport type { PlayRendererProps } from \"./types.js\";\nimport type { AbstractActor, Viewable, ViewMetadata } from \"@xmachines/play-actor\";\nimport type { AnyActorLogic } from \"xstate\";\nimport type { DefineRegistryResult } from \"@json-render/vue\";\nimport type { StateStore } from \"@json-render/core\";\nimport {\n\tRenderer,\n\tStateProvider,\n\tActionProvider,\n\tVisibilityProvider,\n\tuseStateStore,\n} from \"@json-render/vue\";\nimport type { SetState } from \"@json-render/vue\";\nimport { createAtom } from \"@xstate/store\";\nimport { xstateStoreStateStore } from \"@json-render/xstate\";\nimport { provideActor, type PlayActor } from \"./useActor.js\";\n\n/**\n * Inner component that renders inside StateProvider so it can call useStateStore()\n * to get the live set/getSnapshot functions needed by registryResult.handlers().\n */\nconst PlayRendererInner = defineComponent({\n\tname: \"PlayRendererInner\",\n\tprops: {\n\t\tregistryResult: {\n\t\t\ttype: Object as PropType<DefineRegistryResult>,\n\t\t\trequired: true,\n\t\t},\n\t\tspec: {\n\t\t\ttype: Object as PropType<import(\"@json-render/core\").Spec | null>,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\tsetup(props) {\n\t\treturn () => {\n\t\t\tconst { update, getSnapshot } = useStateStore();\n\t\t\t// Build a SetState adapter: handlers factory expects updater-function pattern\n\t\t\tconst setStateAdapter: SetState = (updater) => {\n\t\t\t\tconst prev = getSnapshot();\n\t\t\t\tupdate(updater(prev));\n\t\t\t};\n\t\t\tconst handlers = props.registryResult.handlers(\n\t\t\t\t() => setStateAdapter,\n\t\t\t\t() => getSnapshot(),\n\t\t\t);\n\t\t\tconst rawRegistry = props.registryResult.registry;\n\n\t\t\treturn h(ActionProvider, { handlers }, () =>\n\t\t\t\th(VisibilityProvider, {}, () =>\n\t\t\t\t\th(Renderer, { spec: props.spec, registry: rawRegistry }),\n\t\t\t\t),\n\t\t\t);\n\t\t};\n\t},\n});\n\nexport default defineComponent({\n\tname: \"PlayRenderer\",\n\tprops: {\n\t\tactor: {\n\t\t\ttype: Object as PropType<AbstractActor<AnyActorLogic> & Viewable>,\n\t\t\trequired: true,\n\t\t},\n\t\tregistryResult: {\n\t\t\ttype: Object as PropType<DefineRegistryResult>,\n\t\t\trequired: true,\n\t\t},\n\t\tstore: {\n\t\t\ttype: Object as PropType<StateStore>,\n\t\t\tdefault: undefined,\n\t\t},\n\t},\n\tsetup(props, { slots }) {\n\t\t// Unwrap actor from Vue's reactive proxy to access raw Signal objects\n\t\tconst actor = toRaw(props.actor);\n\n\t\t// Unwrap the registryResult and mark components as raw to avoid Vue reactivity overhead\n\t\tconst rawRegistryResult: DefineRegistryResult = {\n\t\t\t...toRaw(props.registryResult),\n\t\t\tregistry: markRaw(\n\t\t\t\tObject.fromEntries(\n\t\t\t\t\tObject.entries(toRaw(props.registryResult).registry).map(([k, v]) => [\n\t\t\t\t\t\tk,\n\t\t\t\t\t\tmarkRaw(v as object),\n\t\t\t\t\t]),\n\t\t\t\t) as DefineRegistryResult[\"registry\"],\n\t\t\t),\n\t\t};\n\n\t\t// Provide the raw actor to all descendants via Vue's provide/inject mechanism\n\t\tprovideActor(actor as PlayActor);\n\n\t\t// Get initial value from unwrapped signal\n\t\tconst initialView = actor.currentView.get();\n\n\t\t// Vue ref for triggering re-renders (NOT business logic state)\n\t\tconst view = ref<ViewMetadata | null>(initialView);\n\n\t\t// Internal per-view store — recreated on each view transition when no external store.\n\t\tlet internalStore: StateStore | null = null;\n\t\tlet lastView: ViewMetadata | null = null;\n\t\tlet storeKey = 0;\n\n\t\t// Signal watcher for bridging TC39 Signals to Vue reactivity\n\t\tconst unwatch = watchSignal(actor.currentView, (nextView) => {\n\t\t\tview.value = nextView;\n\t\t});\n\n\t\tonUnmounted(() => {\n\t\t\tunwatch();\n\t\t});\n\n\t\treturn () => {\n\t\t\t// No view — show fallback slot\n\t\t\tif (!view.value) {\n\t\t\t\treturn slots.fallback ? slots.fallback() : null;\n\t\t\t}\n\n\t\t\tconst spec = view.value.spec;\n\n\t\t\t// Resolve the store: external (controlled) or internal per-view atom\n\t\t\tlet store: StateStore;\n\t\t\tif (props.store) {\n\t\t\t\tstore = props.store;\n\t\t\t} else {\n\t\t\t\tif (internalStore === null || lastView !== view.value) {\n\t\t\t\t\tconst initialState = (spec.state as Record<string, unknown>) ?? {};\n\t\t\t\t\tinternalStore = xstateStoreStateStore({ atom: createAtom(initialState) });\n\t\t\t\t\tlastView = view.value;\n\t\t\t\t\tstoreKey++;\n\t\t\t\t}\n\t\t\t\tstore = internalStore;\n\t\t\t}\n\n\t\t\t// PlayRendererInner renders inside StateProvider so useStateStore() works\n\t\t\treturn h(StateProvider, { store, key: storeKey }, () =>\n\t\t\t\th(PlayRendererInner, { registryResult: rawRegistryResult, spec }),\n\t\t\t);\n\t\t};\n\t},\n});\n</script>\n"],"mappings":";;;;;;;AA0CA,IAAM,IAAoB,EAAgB;CACzC,MAAM;CACN,OAAO;EACN,gBAAgB;GACf,MAAM;GACN,UAAU;GACV;EACD,MAAM;GACL,MAAM;GACN,SAAS;GACT;EACD;CACD,MAAM,GAAO;AACZ,eAAa;GACZ,IAAM,EAAE,WAAQ,mBAAgB,GAAe,EAEzC,KAA6B,MAAY;AAE9C,MAAO,EADM,GAAa,CACN,CAAC;MAEhB,IAAW,EAAM,eAAe,eAC/B,SACA,GAAa,CACnB,EACK,IAAc,EAAM,eAAe;AAEzC,UAAO,EAAE,GAAgB,EAAE,aAAU,QACpC,EAAE,GAAoB,EAAE,QACvB,EAAE,GAAU;IAAE,MAAM,EAAM;IAAM,UAAU;IAAa,CAAC,CACxD,CACD;;;CAGH,CAAC,EAEF,IAAe,EAAgB;CAC9B,MAAM;CACN,OAAO;EACN,OAAO;GACN,MAAM;GACN,UAAU;GACV;EACD,gBAAgB;GACf,MAAM;GACN,UAAU;GACV;EACD,OAAO;GACN,MAAM;GACN,SAAS,KAAA;GACT;EACD;CACD,MAAM,GAAO,EAAE,YAAS;EAEvB,IAAM,IAAQ,EAAM,EAAM,MAAM,EAG1B,IAA0C;GAC/C,GAAG,EAAM,EAAM,eAAe;GAC9B,UAAU,EACT,OAAO,YACN,OAAO,QAAQ,EAAM,EAAM,eAAe,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,OAAO,CACpE,GACA,EAAQ,EAAY,CACpB,CAAC,CACH,CACA;GACD;AAGD,IAAa,EAAmB;EAMhC,IAAM,IAAO,EAHO,EAAM,YAAY,KAAK,CAGO,EAG9C,IAAmC,MACnC,IAAgC,MAChC,IAAW,GAGT,IAAU,EAAY,EAAM,cAAc,MAAa;AAC5D,KAAK,QAAQ;IACZ;AAMF,SAJA,QAAkB;AACjB,MAAS;IACR,QAEW;AAEZ,OAAI,CAAC,EAAK,MACT,QAAO,EAAM,WAAW,EAAM,UAAS,GAAI;GAG5C,IAAM,IAAO,EAAK,MAAM,MAGpB;AAcJ,UAbI,EAAM,QACT,IAAQ,EAAM,UAEV,MAAkB,QAAQ,MAAa,EAAK,WAE/C,IAAgB,EAAsB,EAAE,MAAM,EADxB,EAAK,SAAqC,EAAE,CACG,EAAG,CAAC,EACzE,IAAW,EAAK,OAChB,MAED,IAAQ,IAIF,EAAE,GAAe;IAAE;IAAO,KAAK;IAAU,QAC/C,EAAE,GAAmB;IAAE,gBAAgB;IAAmB;IAAM,CAAC,CACjE;;;CAGH,CAAC"}