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

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 +233 -83
  3. package/dist/PlayRenderer.js +2 -4
  4. package/dist/PlayRenderer.js.map +1 -1
  5. package/dist/PlayRenderer.vue.d.ts +49 -0
  6. package/dist/PlayRenderer.vue.d.ts.map +1 -0
  7. package/dist/PlayRenderer.vue_vue_type_script_lang.js +40 -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 +796 -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 +31 -7
  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
+ - `useStateBinding` — 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; useStateBinding 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 { useStateBinding } 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] = useStateBinding<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 { registry } = defineRegistry(catalog, {
92
+ components: { Login: LoginSFC, Dashboard: DashboardSFC },
93
+ actions: { login: async () => {}, logout: async () => {} },
31
94
  });
95
+ ```
32
96
 
33
- // Create actor
34
- const actor = definePlayer({ machine: authMachine, catalog })();
35
- actor.start();
97
+ ```ts
98
+ // machine.ts
99
+ import { setup, assign } from "xstate";
100
+ import { formatPlayRouteTransitions } from "@xmachines/play-xstate";
101
+
102
+ export const machine = setup({
103
+ types: {
104
+ context: {} as {
105
+ isAuthenticated: boolean;
106
+ username: string | null;
107
+ params: Record<string, string>;
108
+ query: Record<string, string>;
109
+ },
110
+ events: {} as
111
+ | { type: "auth.login"; username: string }
112
+ | { type: "auth.logout" }
113
+ | { type: "play.route"; to: string; params?: Record<string, string> },
114
+ },
115
+ }).createMachine(
116
+ formatPlayRouteTransitions({
117
+ id: "app",
118
+ initial: "login",
119
+ context: { isAuthenticated: false, username: null, params: {}, query: {} },
120
+ states: {
121
+ login: {
122
+ id: "login",
123
+ meta: {
124
+ route: "/login",
125
+ view: {
126
+ component: "Login",
127
+ spec: {
128
+ root: "root",
129
+ elements: {
130
+ root: { type: "Login", props: { title: "Sign In" }, children: [] },
131
+ },
132
+ },
133
+ },
134
+ },
135
+ },
136
+ dashboard: {
137
+ id: "dashboard",
138
+ meta: {
139
+ route: "/dashboard",
140
+ view: {
141
+ component: "Dashboard",
142
+ spec: {
143
+ root: "root",
144
+ elements: {
145
+ root: { type: "Dashboard", props: { username: "" }, children: [] },
146
+ },
147
+ },
148
+ },
149
+ },
150
+ },
151
+ },
152
+ on: {
153
+ "auth.login": {
154
+ target: ".dashboard",
155
+ guard: ({ context }) => !context.isAuthenticated,
156
+ actions: assign({ isAuthenticated: true, username: ({ event }) => event.username }),
157
+ },
158
+ "auth.logout": {
159
+ target: ".login",
160
+ guard: ({ context }) => context.isAuthenticated,
161
+ actions: assign({ isAuthenticated: false, username: null }),
162
+ },
163
+ },
164
+ }),
165
+ );
166
+ ```
167
+
168
+ ```vue
169
+ <!-- App.vue -->
170
+ <script setup lang="ts">
171
+ import { definePlayer } from "@xmachines/play-xstate";
172
+ import { PlayRenderer } from "@xmachines/play-vue";
173
+ import { machine } from "./machine.js";
174
+ import { registry } from "./registry.js";
36
175
 
37
- // Define component map
38
- const components = {
39
- Home: HomeComponent,
40
- Login: LoginComponent,
41
- Dashboard: DashboardComponent,
42
- };
176
+ const createPlayer = definePlayer({ machine });
177
+ const actor = createPlayer();
178
+ actor.start();
43
179
  </script>
44
180
 
45
181
  <template>
46
- <PlayRenderer :actor="actor" :components="components">
47
- <template #fallback>
48
- <div>Loading...</div>
49
- </template>
50
- </PlayRenderer>
182
+ <PlayRenderer
183
+ :actor="actor"
184
+ :registry="registry"
185
+ :actions="{ login: 'auth.login', logout: 'auth.logout' }"
186
+ />
51
187
  </template>
52
188
  ```
53
189
 
54
- ## API
190
+ ## API Reference
55
191
 
56
- ### PlayRenderer
192
+ ### `PlayRenderer`
57
193
 
58
- Main renderer component that observes `actor.currentView` signal and dynamically renders catalog components.
194
+ Main Vue component. Subscribes to `actor.currentView` and renders the spec.
59
195
 
60
- **Props:**
196
+ ```vue
197
+ <PlayRenderer
198
+ :actor="actor"
199
+ :registry="registry"
200
+ :actions="{ login: 'auth.login' }"
201
+ :store="myStore"
202
+ />
203
+ ```
61
204
 
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
205
+ **`actor`** — A `PlayerActor` (or any `AbstractActor & Viewable`). Provides the `currentView` signal.
65
206
 
66
- **Component Props:**
207
+ **`registry`** — Built with `defineRegistry(catalog, { components, actions })` from `@xmachines/play-vue`. Pass `.vue` SFCs directly — they are auto-wrapped via `h(SFC, ctx)` so `useStateBinding` and other composables work inside `<script setup>`.
67
208
 
68
- Each rendered component receives:
209
+ `defineRegistry` also accepts `onRenderError(error, elementType)`, which receives errors
210
+ caught by `@json-render/vue`'s inner element boundary before the default logger is used.
69
211
 
70
- - All props from `view.props` (spread via `v-bind`)
71
- - `send` function for sending events to actor
212
+ **`actions`** — Maps json-render action names (from spec `on` bindings) to XState event type strings. Type-checked against `EventFromLogic<TLogic>["type"]` when `TLogic` is specified.
72
213
 
73
- **Example Component:**
214
+ **`store`** (optional) — Controls per-view UI state (`$state` bindings, form values):
74
215
 
75
- ```vue
76
- <script setup lang="ts">
77
- import type { AbstractActor } from "@xmachines/play-actor";
216
+ - **Omitted (uncontrolled, default):** A fresh `@xstate/store` atom is created per view transition, seeded from `view.spec.state`.
217
+ - **Provided (controlled):** The caller owns the store; `spec.state` is ignored.
78
218
 
79
- defineProps<{
80
- userId: string;
81
- send: AbstractActor<any>["send"];
82
- }>();
219
+ ```ts
220
+ import { createAtom } from "@xstate/store";
221
+ import { xstateStoreStateStore } from "@json-render/xstate";
222
+ import type { StateStore } from "@json-render/core";
83
223
 
84
- function handleClick() {
85
- send({ type: "user.click", payload: { action: "details" } });
86
- }
87
- </script>
224
+ const store: StateStore = xstateStoreStateStore({ atom: createAtom({ username: "" }) });
225
+ ```
88
226
 
89
- <template>
90
- <div>
91
- <h1>User: {{ userId }}</h1>
92
- <button @click="handleClick">View Details</button>
93
- </div>
94
- </template>
227
+ **Inner render errors** — You can intercept catalog component render failures without
228
+ overriding the outer Vue error boundary:
229
+
230
+ ```ts
231
+ export const { registry } = defineRegistry(catalog, {
232
+ components: { Login: LoginSFC, Dashboard: DashboardSFC },
233
+ actions: { login: async () => {}, logout: async () => {} },
234
+ onRenderError(error, elementType) {
235
+ reportExpectedRenderError(error, elementType);
236
+ },
237
+ });
95
238
  ```
96
239
 
97
- ## Features
240
+ ```vue
241
+ <PlayRenderer
242
+ :actor="actor"
243
+ :registry="registry"
244
+ :store="store"
245
+ :actions="{ login: 'auth.login' }"
246
+ />
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 `useStateBinding` 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 `useStateBinding` 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 (per RESEARCH.md Pattern 1):\n * - Subscribes to actor.currentView signal via Signal.subtle.Watcher\n * - Renders view.spec via @json-render/vue Renderer + StateProvider + ActionProvider\n * - Routes actor.send() calls via ActionProvider handlers\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 * The atom resets automatically when the actor transitions to a new view.\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, 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 { ComponentRegistry } from \"@json-render/vue\";\nimport type { StateStore } from \"@json-render/core\";\nimport { Renderer, StateProvider, ActionProvider, VisibilityProvider } from \"@json-render/vue\";\nimport { createAtom } from \"@xstate/store\";\nimport { xstateStoreStateStore } from \"@json-render/xstate\";\nimport { provideActor, type PlayActor } from \"./useActor.js\";\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\tregistry: {\n\t\t\ttype: Object as PropType<ComponentRegistry>,\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\tactions: {\n\t\t\ttype: Object as PropType<Record<string, string>>,\n\t\t\tdefault: () => ({}),\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// Unwrap the registry prop from Vue's reactive proxy first, then mark the\n\t\t// container and each component value as raw.\n\t\t//\n\t\t// props.registry is already a reactive proxy when setup() runs reading values\n\t\t// from it yields proxied component objects. toRaw() strips the proxy layer so\n\t\t// markRaw() stamps __v_skip on the real objects, not the proxy wrappers.\n\t\t// Without toRaw(), markRaw() marks throw-away proxy objects that Vue re-creates\n\t\t// on the next prop pass, leaving the underlying component objects unmarked.\n\t\tconst rawRegistry: typeof props.registry = markRaw(\n\t\t\tObject.fromEntries(\n\t\t\t\tObject.entries(toRaw(props.registry)).map(([k, v]) => [k, markRaw(v as object)]),\n\t\t\t) as typeof props.registry,\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\t// Signal is source of truth, ref is just Vue's render trigger\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\t// Key counter: forces Vue to remount StateProvider (and all descendants) when the\n\t\t// store changes. Without this, StateProvider's setup() only runs once — its provide()\n\t\t// call captures the FIRST store and never updates, leaving ActionProvider and child\n\t\t// components referencing a stale store.\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\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\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// Map json-render action names to actor.send() calls\n\t\t\tconst handlers = Object.fromEntries(\n\t\t\t\tObject.entries(props.actions ?? {}).map(([actionName, eventType]) => [\n\t\t\t\t\tactionName,\n\t\t\t\t\tasync (params: Record<string, unknown> = {}) =>\n\t\t\t\t\t\tactor.send({ type: eventType, ...params }),\n\t\t\t\t]),\n\t\t\t);\n\n\t\t\treturn h(StateProvider, { store, key: storeKey }, () =>\n\t\t\t\th(ActionProvider, { handlers }, () =>\n\t\t\t\t\th(VisibilityProvider, {}, () => h(Renderer, { spec, registry: rawRegistry })),\n\t\t\t\t),\n\t\t\t);\n\t\t};\n\t},\n});\n</script>\n"],"mappings":""}
@@ -0,0 +1,49 @@
1
+ import { PropType } from 'vue';
2
+ import { AbstractActor, Viewable } from '@xmachines/play-actor';
3
+ import { AnyActorLogic } from 'xstate';
4
+ import { ComponentRegistry } 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
+ registry: {
12
+ type: PropType<ComponentRegistry>;
13
+ required: true;
14
+ };
15
+ store: {
16
+ type: PropType<StateStore>;
17
+ default: undefined;
18
+ };
19
+ actions: {
20
+ type: PropType<Record<string, string>>;
21
+ default: () => {};
22
+ };
23
+ }>, () => import('vue', { with: { "resolution-mode": "import" } }).VNode<import('vue', { with: { "resolution-mode": "import" } }).RendererNode, import('vue', { with: { "resolution-mode": "import" } }).RendererElement, {
24
+ [key: string]: any;
25
+ }> | import('vue', { with: { "resolution-mode": "import" } }).VNode<import('vue', { with: { "resolution-mode": "import" } }).RendererNode, import('vue', { with: { "resolution-mode": "import" } }).RendererElement, {
26
+ [key: string]: any;
27
+ }>[] | 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<{
28
+ actor: {
29
+ type: PropType<AbstractActor<AnyActorLogic> & Viewable>;
30
+ required: true;
31
+ };
32
+ registry: {
33
+ type: PropType<ComponentRegistry>;
34
+ required: true;
35
+ };
36
+ store: {
37
+ type: PropType<StateStore>;
38
+ default: undefined;
39
+ };
40
+ actions: {
41
+ type: PropType<Record<string, string>>;
42
+ default: () => {};
43
+ };
44
+ }>> & Readonly<{}>, {
45
+ store: StateStore;
46
+ actions: Record<string, string>;
47
+ }, {}, {}, {}, string, import('vue', { with: { "resolution-mode": "import" } }).ComponentProvideOptions, true, {}, any>;
48
+ export default _default;
49
+ //# sourceMappingURL=PlayRenderer.vue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PlayRenderer.vue.d.ts","sourceRoot":"","sources":["../src/PlayRenderer.vue"],"names":[],"mappings":"AAmLA,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,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;;;cAUjC,QAAQ,CAAC,aAAa,CAAC,aAAa,CAAC,GAAG,QAAQ,CAAC;;;;cAIjD,QAAQ,CAAC,iBAAiB,CAAC;;;;cAI3B,QAAQ,CAAC,UAAU,CAAC;;;;cAIpB,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;;;;;;;;;cAZhC,QAAQ,CAAC,aAAa,CAAC,aAAa,CAAC,GAAG,QAAQ,CAAC;;;;cAIjD,QAAQ,CAAC,iBAAiB,CAAC;;;;cAI3B,QAAQ,CAAC,UAAU,CAAC;;;;cAIpB,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;;;;;;;AAhBnD,wBA0GG"}
@@ -1,35 +1,57 @@
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 } from "./node_modules/@json-render/vue/dist/index.js";
2
+ import { createAtom as i } from "./node_modules/@xstate/store/dist/store-69e7e2d5.esm.js";
3
+ import { xstateStoreStateStore as a } from "./node_modules/@json-render/xstate/dist/index.js";
4
+ import { provideActor as o } from "./useActor.js";
5
+ import { defineComponent as s, h as c, markRaw as l, onUnmounted as u, ref as d, toRaw as f } from "vue";
6
+ import { watchSignal as p } from "@xmachines/play-signals";
3
7
  //#region src/PlayRenderer.vue?vue&type=script&lang.ts
4
- var s = t({
8
+ var m = s({
5
9
  name: "PlayRenderer",
6
10
  props: {
7
11
  actor: {
8
12
  type: Object,
9
13
  required: !0
10
14
  },
11
- components: {
15
+ registry: {
12
16
  type: Object,
13
17
  required: !0
18
+ },
19
+ store: {
20
+ type: Object,
21
+ default: void 0
22
+ },
23
+ actions: {
24
+ type: Object,
25
+ default: () => ({})
14
26
  }
15
27
  },
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);
28
+ setup(s, { slots: m }) {
29
+ let h = f(s.actor), g = l(Object.fromEntries(Object.entries(f(s.registry)).map(([e, t]) => [e, l(t)])));
30
+ o(h);
31
+ let _ = d(h.currentView.get()), v = null, y = null, b = 0, x = p(h.currentView, (e) => {
32
+ _.value = e;
24
33
  });
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;
34
+ return u(() => {
35
+ x();
36
+ }), () => {
37
+ if (!_.value) return m.fallback ? m.fallback() : null;
38
+ let o = _.value.spec, l;
39
+ s.store ? l = s.store : ((v === null || y !== _.value) && (v = a({ atom: i(o.state ?? {}) }), y = _.value, b++), l = v);
40
+ let u = Object.fromEntries(Object.entries(s.actions ?? {}).map(([e, t]) => [e, async (e = {}) => h.send({
41
+ type: t,
42
+ ...e
43
+ })]));
44
+ return c(n, {
45
+ store: l,
46
+ key: b
47
+ }, () => c(e, { handlers: u }, () => c(r, {}, () => c(t, {
48
+ spec: o,
49
+ registry: g
50
+ }))));
51
+ };
30
52
  }
31
53
  });
32
54
  //#endregion
33
- export { s as default };
55
+ export { m as default };
34
56
 
35
57
  //# 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 (per RESEARCH.md Pattern 1):\n * - Subscribes to actor.currentView signal via Signal.subtle.Watcher\n * - Renders view.spec via @json-render/vue Renderer + StateProvider + ActionProvider\n * - Routes actor.send() calls via ActionProvider handlers\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 * The atom resets automatically when the actor transitions to a new view.\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, 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 { ComponentRegistry } from \"@json-render/vue\";\nimport type { StateStore } from \"@json-render/core\";\nimport { Renderer, StateProvider, ActionProvider, VisibilityProvider } from \"@json-render/vue\";\nimport { createAtom } from \"@xstate/store\";\nimport { xstateStoreStateStore } from \"@json-render/xstate\";\nimport { provideActor, type PlayActor } from \"./useActor.js\";\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\tregistry: {\n\t\t\ttype: Object as PropType<ComponentRegistry>,\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\tactions: {\n\t\t\ttype: Object as PropType<Record<string, string>>,\n\t\t\tdefault: () => ({}),\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// Unwrap the registry prop from Vue's reactive proxy first, then mark the\n\t\t// container and each component value as raw.\n\t\t//\n\t\t// props.registry is already a reactive proxy when setup() runs reading values\n\t\t// from it yields proxied component objects. toRaw() strips the proxy layer so\n\t\t// markRaw() stamps __v_skip on the real objects, not the proxy wrappers.\n\t\t// Without toRaw(), markRaw() marks throw-away proxy objects that Vue re-creates\n\t\t// on the next prop pass, leaving the underlying component objects unmarked.\n\t\tconst rawRegistry: typeof props.registry = markRaw(\n\t\t\tObject.fromEntries(\n\t\t\t\tObject.entries(toRaw(props.registry)).map(([k, v]) => [k, markRaw(v as object)]),\n\t\t\t) as typeof props.registry,\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\t// Signal is source of truth, ref is just Vue's render trigger\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\t// Key counter: forces Vue to remount StateProvider (and all descendants) when the\n\t\t// store changes. Without this, StateProvider's setup() only runs once — its provide()\n\t\t// call captures the FIRST store and never updates, leaving ActionProvider and child\n\t\t// components referencing a stale store.\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\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\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// Map json-render action names to actor.send() calls\n\t\t\tconst handlers = Object.fromEntries(\n\t\t\t\tObject.entries(props.actions ?? {}).map(([actionName, eventType]) => [\n\t\t\t\t\tactionName,\n\t\t\t\t\tasync (params: Record<string, unknown> = {}) =>\n\t\t\t\t\t\tactor.send({ type: eventType, ...params }),\n\t\t\t\t]),\n\t\t\t);\n\n\t\t\treturn h(StateProvider, { store, key: storeKey }, () =>\n\t\t\t\th(ActionProvider, { handlers }, () =>\n\t\t\t\t\th(VisibilityProvider, {}, () => h(Renderer, { spec, registry: rawRegistry })),\n\t\t\t\t),\n\t\t\t);\n\t\t};\n\t},\n});\n</script>\n"],"mappings":";;;;;;;AAyCA,IAAA,IAAe,EAAgB;CAC9B,MAAM;CACN,OAAO;EACN,OAAO;GACN,MAAM;GACN,UAAU;GACV;EACD,UAAU;GACT,MAAM;GACN,UAAU;GACV;EACD,OAAO;GACN,MAAM;GACN,SAAS,KAAA;GACT;EACD,SAAS;GACR,MAAM;GACN,gBAAgB,EAAE;GAClB;EACD;CACD,MAAM,GAAO,EAAE,YAAS;EAGvB,IAAM,IAAQ,EAAM,EAAM,MAAM,EAU1B,IAAqC,EAC1C,OAAO,YACN,OAAO,QAAQ,EAAM,EAAM,SAAS,CAAC,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,GAAG,EAAQ,EAAY,CAAC,CAAC,CACjF,CACA;AAGD,IAAa,EAAmB;EAOhC,IAAM,IAAO,EAJO,EAAM,YAAY,KAAK,CAIO,EAG9C,IAAmC,MACnC,IAAgC,MAKhC,IAAW,GAGT,IAAU,EAAY,EAAM,cAAc,MAAa;AAC5D,KAAK,QAAQ;IACZ;AAOF,SALA,QAAkB;AACjB,MAAS;IACR,QAGW;AAEZ,OAAI,CAAC,EAAK,MACT,QAAO,EAAM,WAAW,EAAM,UAAS,GAAI;GAG5C,IAAM,IAAO,EAAK,MAAM,MAGpB;AACJ,GAAI,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;GAIT,IAAM,IAAW,OAAO,YACvB,OAAO,QAAQ,EAAM,WAAW,EAAE,CAAC,CAAC,KAAK,CAAC,GAAY,OAAe,CACpE,GACA,OAAO,IAAkC,EAAE,KAC1C,EAAM,KAAK;IAAE,MAAM;IAAW,GAAG;IAAQ,CAAC,CAC3C,CAAC,CACF;AAED,UAAO,EAAE,GAAe;IAAE;IAAO,KAAK;IAAU,QAC/C,EAAE,GAAgB,EAAE,aAAU,QAC7B,EAAE,GAAoB,EAAE,QAAQ,EAAE,GAAU;IAAE;IAAM,UAAU;IAAa,CAAC,CAAC,CAC7E,CACD;;;CAGH,CAAC"}