@xmachines/play-solid 2.1.1 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  > Solid renderer for XMachines Play architecture
4
4
 
5
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Version](https://img.shields.io/badge/version-2.1.1-blue)](https://www.npmjs.com/package/@xmachines/play-solid)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Version](https://img.shields.io/badge/version-3.0.0-blue)](https://www.npmjs.com/package/@xmachines/play-solid)
6
6
 
7
7
  The SolidJS rendering layer observes the actor signals and renders the UI components through `@xmachines/json-render-solid`. SolidJS reactivity only triggers the re-render. The TC39 Signals are the source of truth.
8
8
 
@@ -122,6 +122,24 @@ const MyRenderer = () => {
122
122
  };
123
123
  ```
124
124
 
125
+ > **Read the fields where you use them, and destructure them not.** `spec`, `fallback`
126
+ > and `loading` are GETTERS on the context object. The provider keeps the subtree across
127
+ > an emission that holds the viewKey, so it publishes a new spec through the getter
128
+ > instead of building the tree again — which is what keeps the focus and the caret of an
129
+ > input alive while a stream writes into the view.
130
+ >
131
+ > ```tsx
132
+ > // ✅ The Renderer reads `view.spec` inside its own tracking scope, so it follows.
133
+ > const view = usePlayView();
134
+ > return <Renderer spec={view.spec} registry={view.registry} />;
135
+ >
136
+ > // ❌ The value freezes at the first render, and the screen stops following the actor.
137
+ > const { spec } = usePlayView();
138
+ > ```
139
+ >
140
+ > This changed in **2.3.0**. Before it, every emission built the subtree again, so a
141
+ > value that a component read one time was fresh because the component itself was new.
142
+
125
143
  ## API Summary
126
144
 
127
145
  ### Components
@@ -134,10 +152,10 @@ const MyRenderer = () => {
134
152
 
135
153
  ### Hooks
136
154
 
137
- | Export | Description |
138
- | --------------- | ---------------------------------------------------------------------------------------------------- |
139
- | `useActor()` | Returns the raw `AnyPlayActor` instance from the context. It throws outside a provider tree |
140
- | `usePlayView()` | Returns the current `ViewContextValue` (spec, handlers, registry, store). It throws outside the tree |
155
+ | Export | Description |
156
+ | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
157
+ | `useActor()` | Returns the raw `AnyPlayActor` instance from the context. It throws outside a provider tree |
158
+ | `usePlayView()` | Returns the current `ViewContextValue` (spec, handlers, registry, store). Read the fields where you use them: `spec`, `fallback` and `loading` are getters. It throws outside the tree |
141
159
 
142
160
  ### Context
143
161
 
@@ -24,12 +24,33 @@ import { type BaseActorProviderProps, type BaseViewContextValue } from "@xmachin
24
24
  /**
25
25
  * The value that the ViewContext of ActorProvider provides.
26
26
  * usePlayView() gives it inside the ActorProvider tree.
27
+ *
28
+ * **`spec` is a live GETTER, and not a copied value.** The provider keeps the subtree
29
+ * across an emission that holds the viewKey, so a read of `spec` inside a tracking
30
+ * scope gives the newest spec and the Renderer reconciles in place. A read outside a
31
+ * tracking scope gives a snapshot that goes old: `const { spec } = usePlayView()` and
32
+ * `{ ...usePlayView() }` both freeze on the spec of that moment. Read `view.spec` at
33
+ * the place of the use instead. The other three fields are copied values, and the
34
+ * provider builds the subtree again when one of them changes.
27
35
  */
28
36
  export interface ViewContextValue extends BaseViewContextValue<ComponentRegistry> {
29
37
  }
30
38
  /**
31
39
  * The hook that gives the current view context inside an ActorProvider tree.
32
40
  *
41
+ * **Read a field where you use it, and destructure it not.** `spec` is a GETTER: the
42
+ * provider keeps the subtree across an emission that holds the viewKey, and it publishes
43
+ * the new spec through that getter. `const { spec } = usePlayView()` therefore freezes at
44
+ * the first read, and the screen stops following the actor — with no error and no
45
+ * warning.
46
+ *
47
+ * The context holds `spec`, `handlers`, `registry` and `store`, and no other field. The
48
+ * three beside `spec` are copied values, and the provider builds the subtree again when
49
+ * one of them changes — see {@link ViewContextValue}.
50
+ *
51
+ * This changed in 2.3.0. Before it, every emission built the subtree again, so a value
52
+ * that a component read one time was fresh because the component itself was new.
53
+ *
33
54
  * @throws {Error} When the caller is outside an ActorProvider tree or a PlayUIProvider tree
34
55
  *
35
56
  * @example
@@ -54,8 +75,36 @@ export interface ActorProviderProps extends BaseActorProviderProps<DefineRegistr
54
75
  * The optional fallback element. The provider shows it when currentView is null, and when the ErrorBoundary catches an error
55
76
  */
56
77
  fallback?: JSX.Element;
57
- /** The optional callback. The provider calls it when the SolidJS ErrorBoundary catches an error */
58
- onError?: (error: unknown) => void;
78
+ /**
79
+ * The optional callback. The provider calls it when the SolidJS ErrorBoundary
80
+ * catches an error.
81
+ *
82
+ * While the error is active, the provider renders the `fallback` instead of its
83
+ * children. The provider clears the error on the next view emission (when
84
+ * `currentView` changes), so a view transition retries the render.
85
+ *
86
+ * Element-level catalog component throws are caught upstream by the per-element
87
+ * boundary of @json-render, and reported through `onRenderError`. This boundary is
88
+ * the outer net for everything else.
89
+ *
90
+ * The second parameter is the RESET, for a retry that the host starts — a "Retry"
91
+ * button of its own. It clears the caught error and renders again, and it resolves the
92
+ * view that the actor holds at the MOMENT OF THE CALL, so a host that keeps the
93
+ * callback cannot rewind the screen to the view that failed. A reset that the host
94
+ * calls from inside this handler starts no second retry, because no input changed
95
+ * between the two attempts, and a reset after the provider is disposed does nothing.
96
+ * The five renderers hold the same three rules.
97
+ *
98
+ * **A handler that THROWS reaches no caller.** The provider contains that throw, and
99
+ * it writes the throw to `console.error`. The fallback of this boundary is the RETURN
100
+ * of the callback that reports, so a throw that left this handler would take the
101
+ * fallback off the screen AND let the error escape the boundary.
102
+ *
103
+ * Report a failure from this handler. To ESCALATE one, raise it from a task of your
104
+ * own — `queueMicrotask(() => { throw error; })` — which reaches the global handler of
105
+ * the page and leaves the containment whole. The five renderers hold this one rule.
106
+ */
107
+ onError?: (error: unknown, reset: () => void) => void;
59
108
  /**
60
109
  * The children. They are necessary, and they must hold a <PlayRenderer />. You can also use the PlayUIProvider short form
61
110
  */
@@ -1 +1 @@
1
- {"version":3,"file":"ActorProvider.d.ts","sourceRoot":"","sources":["../src/ActorProvider.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAWH,OAAO,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAE/C,OAAO,KAAK,EAAE,oBAAoB,EAAY,MAAM,8BAA8B,CAAC;AAEnF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAKtE,OAAO,EAIN,KAAK,sBAAsB,EAC3B,KAAK,oBAAoB,EACzB,MAAM,uBAAuB,CAAC;AAO/B;;;GAGG;AACH,MAAM,WAAW,gBAAiB,SAAQ,oBAAoB,CAAC,iBAAiB,CAAC;CAAG;AAIpF;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,WAAW,IAAI,gBAAgB,CAE9C;AAMD;;;;;GAKG;AACH,MAAM,WAAW,kBAAmB,SAAQ,sBAAsB,CAAC,oBAAoB,CAAC;IACvF;;OAEG;IACH,QAAQ,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC;IAEvB,mGAAmG;IACnG,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IAEnC;;OAEG;IACH,QAAQ,EAAE,GAAG,CAAC,OAAO,CAAC;CACtB;AA4CD;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,aAAa,EAAE,SAAS,CAAC,kBAAkB,CAkGvD,CAAC"}
1
+ {"version":3,"file":"ActorProvider.d.ts","sourceRoot":"","sources":["../src/ActorProvider.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAaH,OAAO,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAE/C,OAAO,KAAK,EAAE,oBAAoB,EAAY,MAAM,8BAA8B,CAAC;AAEnF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAKtE,OAAO,EAMN,KAAK,sBAAsB,EAC3B,KAAK,oBAAoB,EACzB,MAAM,uBAAuB,CAAC;AAO/B;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,gBAAiB,SAAQ,oBAAoB,CAAC,iBAAiB,CAAC;CAAG;AAIpF;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,WAAW,IAAI,gBAAgB,CAE9C;AAMD;;;;;GAKG;AACH,MAAM,WAAW,kBAAmB,SAAQ,sBAAsB,CAAC,oBAAoB,CAAC;IACvF;;OAEG;IACH,QAAQ,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC;IAEvB;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;IAEtD;;OAEG;IACH,QAAQ,EAAE,GAAG,CAAC,OAAO,CAAC;CACtB;AAoDD;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,aAAa,EAAE,SAAS,CAAC,kBAAkB,CA0OvD,CAAC"}
@@ -1,12 +1,12 @@
1
1
  import { ActorContext } from "./useActor.js";
2
2
  import { createComponent } from "solid-js/web";
3
3
  import { StateProvider, useStateStore } from "@xmachines/json-render-solid";
4
- import { ErrorBoundary, createContext, createEffect, createMemo, createSignal, onCleanup, useContext } from "solid-js";
4
+ import { ErrorBoundary, Show, createContext, createEffect, createMemo, createSignal, onCleanup, untrack, useContext } from "solid-js";
5
5
  import { createAtom } from "@xstate/store";
6
6
  import { xstateStoreStateStore } from "@xmachines/json-render-xstate";
7
7
  import { watchSignal } from "@xmachines/play-signals";
8
8
  import { assertNonNullable } from "@xmachines/play";
9
- import { attachRenderErrorHandler, createViewStoreLifecycle } from "@xmachines/play-actor";
9
+ import { attachRenderErrorHandler, createReportGuard, createViewStoreLifecycle, sameViewInputs } from "@xmachines/play-actor";
10
10
  //#region packages/play-solid/src/ActorProvider.tsx
11
11
  /**
12
12
  * ActorProvider — the SolidJS provider component of the XMachines Play actor lifecycle.
@@ -31,6 +31,19 @@ var ViewContext = createContext(null);
31
31
  /**
32
32
  * The hook that gives the current view context inside an ActorProvider tree.
33
33
  *
34
+ * **Read a field where you use it, and destructure it not.** `spec` is a GETTER: the
35
+ * provider keeps the subtree across an emission that holds the viewKey, and it publishes
36
+ * the new spec through that getter. `const { spec } = usePlayView()` therefore freezes at
37
+ * the first read, and the screen stops following the actor — with no error and no
38
+ * warning.
39
+ *
40
+ * The context holds `spec`, `handlers`, `registry` and `store`, and no other field. The
41
+ * three beside `spec` are copied values, and the provider builds the subtree again when
42
+ * one of them changes — see {@link ViewContextValue}.
43
+ *
44
+ * This changed in 2.3.0. Before it, every emission built the subtree again, so a value
45
+ * that a component read one time was fresh because the component itself was new.
46
+ *
34
47
  * @throws {Error} When the caller is outside an ActorProvider tree or a PlayUIProvider tree
35
48
  *
36
49
  * @example
@@ -57,10 +70,11 @@ var ActorProviderInner = (innerProps) => {
57
70
  const prev = stateCtx.getSnapshot();
58
71
  stateCtx.update(updater(prev));
59
72
  };
60
- const handlers = innerProps.registryResult.handlers(() => setStateAdapter, () => stateCtx.getSnapshot());
61
73
  const viewValue = {
62
- spec: innerProps.spec,
63
- handlers,
74
+ get spec() {
75
+ return innerProps.spec;
76
+ },
77
+ handlers: innerProps.registryResult.handlers(() => setStateAdapter, () => stateCtx.getSnapshot()),
64
78
  registry: innerProps.registryResult.registry,
65
79
  store: innerProps.store
66
80
  };
@@ -118,30 +132,101 @@ var ActorProvider = (props) => {
118
132
  });
119
133
  onCleanup(() => unwatch());
120
134
  });
135
+ let resetBoundary;
136
+ const guard = createReportGuard({
137
+ noHandler: "[@xmachines/play-solid] ActorProvider contained an error of a descendant. Give an onError prop to receive it.",
138
+ handlerThrew: "[@xmachines/play-solid] the onError handler of the host threw. ActorProvider contained the failure of the view all the same."
139
+ });
140
+ onCleanup(() => {
141
+ guard.dispose();
142
+ });
143
+ let lastView;
144
+ let lastActor;
145
+ let lastStore;
146
+ let seeded = false;
147
+ createEffect(() => {
148
+ const nextView = view();
149
+ const nextActor = props.actor;
150
+ const nextStore = props.store;
151
+ const changed = !seeded || !sameViewInputs({
152
+ actor: lastActor,
153
+ view: lastView,
154
+ store: lastStore
155
+ }, {
156
+ actor: nextActor,
157
+ view: nextView,
158
+ store: nextStore
159
+ });
160
+ lastView = nextView;
161
+ lastActor = nextActor;
162
+ lastStore = nextStore;
163
+ if (!seeded) {
164
+ seeded = true;
165
+ return;
166
+ }
167
+ if (!changed) return;
168
+ const reset = resetBoundary;
169
+ resetBoundary = void 0;
170
+ reset?.();
171
+ });
121
172
  return createComponent(ActorContext.Provider, {
122
173
  value: actorProxy,
123
174
  get children() {
124
175
  return createComponent(ErrorBoundary, {
125
- fallback: (err) => {
126
- props.onError?.(err);
176
+ fallback: (err, reset) => {
177
+ resetBoundary = reset;
178
+ const hostRetry = () => {
179
+ if (guard.blocked()) return;
180
+ resetBoundary = void 0;
181
+ reset();
182
+ };
183
+ const handler = props.onError;
184
+ guard.report(err, handler ? () => handler(err, hostRetry) : void 0);
127
185
  return props.fallback ?? null;
128
186
  },
129
187
  get children() {
130
188
  return (() => {
131
- const currentView = view();
132
- if (!currentView) return props.fallback ?? null;
133
- const store = storeLifecycle.resolve(props.actor, currentView, props.store).guardedStore;
134
- return createComponent(StateProvider, {
135
- store,
136
- get children() {
137
- return createComponent(ActorProviderInner, {
138
- get registryResult() {
139
- return resolvedRegistryResult();
140
- },
141
- spec: currentView,
189
+ const resolution = createMemo(() => {
190
+ const currentView = view();
191
+ if (!currentView) return null;
192
+ const { guardedStore } = storeLifecycle.resolve(props.actor, currentView, props.store);
193
+ return {
194
+ view: currentView,
195
+ store: guardedStore
196
+ };
197
+ });
198
+ const subtreeStore = createMemo(() => resolution()?.store ?? null);
199
+ return createComponent(Show, {
200
+ get when() {
201
+ return subtreeStore();
202
+ },
203
+ keyed: true,
204
+ get fallback() {
205
+ return props.fallback ?? null;
206
+ },
207
+ children: (store) => {
208
+ const seed = untrack(resolution);
209
+ if (!seed) return null;
210
+ const currentSpec = () => resolution()?.view ?? seed.view;
211
+ return createComponent(StateProvider, {
142
212
  store,
143
213
  get children() {
144
- return props.children;
214
+ return createComponent(Show, {
215
+ get when() {
216
+ return resolvedRegistryResult();
217
+ },
218
+ keyed: true,
219
+ children: (registryResult) => createComponent(ActorProviderInner, {
220
+ registryResult,
221
+ get spec() {
222
+ return currentSpec();
223
+ },
224
+ store,
225
+ get children() {
226
+ return props.children;
227
+ }
228
+ })
229
+ });
145
230
  }
146
231
  });
147
232
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ActorProvider.js","names":["createSignal","createEffect","createMemo","onCleanup","createContext","useContext","ErrorBoundary","Component","JSX","StateProvider","useStateStore","DefineRegistryResult","SetState","StateStore","ComponentRegistry","createAtom","xstateStoreStateStore","watchSignal","assertNonNullable","attachRenderErrorHandler","createViewStoreLifecycle","PlaySpec","BaseActorProviderProps","BaseViewContextValue","ActorContext","AnyPlayActor","ViewContextValue","ViewContext","usePlayView","ActorProviderProps","fallback","Element","onError","error","children","ActorProviderInner","registryResult","spec","store","innerProps","stateCtx","setStateAdapter","updater","prev","getSnapshot","update","handlers","viewValue","registry","_$createComponent","Provider","value","ActorProvider","props","view","setView","actorProxy","Proxy","get","_target","prop","current","actor","Reflect","bind","has","resolvedRegistryResult","onRenderError","storeLifecycle","seed","atom","nextView","currentView","unwatch","err","resolve","guardedStore"],"sources":["../src/ActorProvider.tsx"],"sourcesContent":["/**\n * ActorProvider — the SolidJS provider component of the XMachines Play actor lifecycle.\n *\n * This is the low-level provider, for the author of a library who needs the control.\n * Most users take PlayUIProvider, the composite provider, instead.\n *\n * This component:\n * - subscribes to the actor.currentView signal with watchSignal, in the body of the component (Phase 29)\n * - manages the StateStore lifecycle of each view, controlled and uncontrolled\n * - resolves each action handler with the inner component pattern, inside StateProvider\n * - puts onRenderError into the registry, when the caller gives one\n * - gives ActorContext (the actor) and ViewContext (the spec, the handlers, and the registry) to the children\n * - wraps the render path in a SolidJS ErrorBoundary\n *\n * D-11: the old alias `ActorProvider = ActorContext.Provider` is gone. This component\n * has the name now. Use `ActorContext.Provider` directly for the raw access.\n *\n * @packageDocumentation\n */\n\nimport {\n\tcreateSignal,\n\tcreateEffect,\n\tcreateMemo,\n\tonCleanup,\n\tcreateContext,\n\tuseContext,\n\tErrorBoundary,\n} from \"solid-js\";\nimport type { Component, JSX } from \"solid-js\";\nimport { StateProvider, useStateStore } from \"@xmachines/json-render-solid\";\nimport type { DefineRegistryResult, SetState } from \"@xmachines/json-render-solid\";\nimport type { StateStore } from \"@xmachines/json-render-core\";\nimport type { ComponentRegistry } from \"@xmachines/json-render-solid\";\nimport { createAtom } from \"@xstate/store\";\nimport { xstateStoreStateStore } from \"@xmachines/json-render-xstate\";\nimport { watchSignal } from \"@xmachines/play-signals\";\nimport { assertNonNullable } from \"@xmachines/play\";\nimport {\n\tattachRenderErrorHandler,\n\tcreateViewStoreLifecycle,\n\ttype PlaySpec,\n\ttype BaseActorProviderProps,\n\ttype BaseViewContextValue,\n} from \"@xmachines/play-actor\";\nimport { ActorContext, type AnyPlayActor } from \"./useActor.js\";\n\n// ---------------------------------------------------------------------------\n// ViewContextValue — the shape of the context value that ActorProvider gives\n// ---------------------------------------------------------------------------\n\n/**\n * The value that the ViewContext of ActorProvider provides.\n * usePlayView() gives it inside the ActorProvider tree.\n */\nexport interface ViewContextValue extends BaseViewContextValue<ComponentRegistry> {}\n\nconst ViewContext = createContext<ViewContextValue | null>(null);\n\n/**\n * The hook that gives the current view context inside an ActorProvider tree.\n *\n * @throws {Error} When the caller is outside an ActorProvider tree or a PlayUIProvider tree\n *\n * @example\n * ```tsx\n * import { usePlayView } from \"@xmachines/play-solid\";\n *\n * const MyRenderer: Component = () => {\n * const view = usePlayView();\n * return <Renderer spec={view.spec} registry={view.registry} />;\n * };\n * ```\n */\nexport function usePlayView(): ViewContextValue {\n\treturn assertNonNullable(useContext(ViewContext), \"ViewContext\");\n}\n\n// ---------------------------------------------------------------------------\n// ActorProviderProps\n// ---------------------------------------------------------------------------\n\n/**\n * The props of ActorProvider, the low-level provider.\n *\n * For the standard use, prefer PlayUIProvider. That component wraps ActorProvider\n * with JSONUIProvider and with every necessary sub-provider.\n */\nexport interface ActorProviderProps extends BaseActorProviderProps<DefineRegistryResult> {\n\t/**\n\t * The optional fallback element. The provider shows it when currentView is null, and when the ErrorBoundary catches an error\n\t */\n\tfallback?: JSX.Element;\n\n\t/** The optional callback. The provider calls it when the SolidJS ErrorBoundary catches an error */\n\tonError?: (error: unknown) => void;\n\n\t/**\n\t * The children. They are necessary, and they must hold a <PlayRenderer />. You can also use the PlayUIProvider short form\n\t */\n\tchildren: JSX.Element;\n}\n\n// ---------------------------------------------------------------------------\n// ActorProviderInner — it resolves the handlers inside the StateProvider tree\n// ---------------------------------------------------------------------------\n\n/**\n * The inner component runs inside StateProvider. It can therefore call\n * useStateStore() to get the live set function and the live getSnapshot function, for\n * the resolution of the handlers.\n */\nconst ActorProviderInner: Component<{\n\tregistryResult: DefineRegistryResult;\n\tspec: PlaySpec;\n\tstore: StateStore;\n\tchildren: JSX.Element;\n}> = (innerProps) => {\n\tconst stateCtx = useStateStore();\n\n\t// Build the SetState adapter. It joins stateCtx.update and stateCtx.getSnapshot\n\tconst setStateAdapter: SetState = (updater) => {\n\t\tconst prev = stateCtx.getSnapshot();\n\t\tstateCtx.update(updater(prev));\n\t};\n\n\tconst handlers = innerProps.registryResult.handlers(\n\t\t() => setStateAdapter,\n\t\t() => stateCtx.getSnapshot(),\n\t);\n\n\tconst viewValue: ViewContextValue = {\n\t\tspec: innerProps.spec,\n\t\thandlers,\n\t\tregistry: innerProps.registryResult.registry,\n\t\tstore: innerProps.store,\n\t};\n\n\treturn <ViewContext.Provider value={viewValue}>{innerProps.children}</ViewContext.Provider>;\n};\n\n// ---------------------------------------------------------------------------\n// ActorProvider — the component. D-11 gives it the ActorProvider name\n// ---------------------------------------------------------------------------\n\n/**\n * The ActorProvider component. It owns the actor bridge, the signal subscription, the\n * StateStore lifecycle, the resolution of the handlers, and the error boundary.\n *\n * D-11: this component replaces the old raw alias\n * `ActorProvider = ActorContext.Provider`. A consumer with an\n * `<ActorProvider value={actor}>` element takes\n * `<ActorContext.Provider value={actor}>` now, for the raw access to the provider.\n * That consumer can also move to this component, or to PlayUIProvider.\n *\n * @example\n * ```tsx\n * import { ActorProvider, PlayRenderer } from \"@xmachines/play-solid\";\n *\n * <ActorProvider actor={myActor} registryResult={registryResult}>\n * <PlayRenderer />\n * </ActorProvider>\n * ```\n */\nexport const ActorProvider: Component<ActorProviderProps> = (props) => {\n\t// The SolidJS signal of the current view (PlaySpec | null)\n\tconst [view, setView] = createSignal<PlaySpec | null>(null);\n\n\t// The code gives a stable Proxy as the value of ActorContext, and not the raw actor.\n\t// The Context.Provider of Solid reads `value` one time, at its creation. Therefore\n\t// `props.actor` gives the FIRST actor only, and a useActor() consumer sees a change of\n\t// the prop never. With the proxy, each consumer keeps the reference of the creation,\n\t// and every property access (send, currentView, and the others) resolves against the\n\t// newest actor. A read of `props.actor` inside a trap is a reactive read. Therefore a\n\t// consumer that reads a property in a tracking scope, such as createEffect,\n\t// createMemo, or the JSX, runs again after a change of the actor. Each method binds to\n\t// the current actor. Therefore `this`, and also each private field, works exactly as\n\t// in a direct call. The ActorProvider proxy of play-vue does the same.\n\tconst actorProxy = new Proxy({} as AnyPlayActor, {\n\t\tget(_target, prop) {\n\t\t\tconst current = props.actor as AnyPlayActor;\n\t\t\tconst value = Reflect.get(current, prop, current) as unknown;\n\t\t\treturn typeof value === \"function\" ? value.bind(current) : value;\n\t\t},\n\t\thas(_target, prop) {\n\t\t\treturn prop in (props.actor as AnyPlayActor);\n\t\t},\n\t});\n\n\t// Put onRenderError into the registry, when the caller gives one. The property is\n\t// not enumerable, and it replaces the handler below it.\n\t// The code keeps the result, because a new object on each reactive evaluation renders\n\t// each child component that receives it as a prop again, for nothing.\n\tconst resolvedRegistryResult = createMemo(() => {\n\t\tif (!props.onRenderError) return props.registryResult;\n\t\treturn {\n\t\t\t...props.registryResult,\n\t\t\tregistry: attachRenderErrorHandler(props.registryResult.registry, props.onRenderError),\n\t\t};\n\t});\n\n\t// The store lifecycle: it seeds the store again on a change of the viewKey, it\n\t// refreshes /context in place in every other case, it resets the store on a change of\n\t// the actor, and it guards the identity cache. The shared coordinator comes from\n\t// @xmachines/play-actor. Only the wiring of the reactivity in the tracked JSX scope\n\t// below belongs to Solid.\n\tconst storeLifecycle = createViewStoreLifecycle((seed) =>\n\t\txstateStoreStateStore({ atom: createAtom(seed) }),\n\t);\n\n\t// Connect the TC39 Signal to the SolidJS signal. The code seeds the value AND starts\n\t// the watch in one createEffect, at the same moment. Therefore no race window is\n\t// between the .get() call and the registration of the watcher. The TC39 signal can\n\t// change between the first .get() call and the first notification of the watcher, and\n\t// the update function then holds the newest value.\n\tcreateEffect(() => {\n\t\tconst update = (nextView: PlaySpec | null) => setView(nextView);\n\t\tupdate(props.actor.currentView.get() as PlaySpec | null);\n\t\tconst unwatch = watchSignal(props.actor.currentView, (nextView) => {\n\t\t\tupdate(nextView as PlaySpec | null);\n\t\t});\n\t\tonCleanup(() => unwatch());\n\t});\n\n\treturn (\n\t\t<ActorContext.Provider value={actorProxy}>\n\t\t\t<ErrorBoundary\n\t\t\t\tfallback={(err: unknown) => {\n\t\t\t\t\tprops.onError?.(err);\n\t\t\t\t\treturn props.fallback ?? null;\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t{(() => {\n\t\t\t\t\tconst currentView = view();\n\t\t\t\t\tif (!currentView) return props.fallback ?? null;\n\n\t\t\t\t\t// Resolve the store, which is external and controlled, or internal for each viewKey,\n\t\t\t\t\t// through the shared lifecycle. The children receive the store with the guard, because\n\t\t\t\t\t// /context is read-only to the spec. The code reads props.actor and props.store HERE.\n\t\t\t\t\t// Both are therefore tracked in this scope, and a change of one of them runs the\n\t\t\t\t\t// resolution again.\n\t\t\t\t\tconst store: StateStore = storeLifecycle.resolve(\n\t\t\t\t\t\tprops.actor,\n\t\t\t\t\t\tcurrentView,\n\t\t\t\t\t\tprops.store,\n\t\t\t\t\t).guardedStore;\n\n\t\t\t\t\treturn (\n\t\t\t\t\t\t<StateProvider store={store}>\n\t\t\t\t\t\t\t<ActorProviderInner\n\t\t\t\t\t\t\t\tregistryResult={resolvedRegistryResult()}\n\t\t\t\t\t\t\t\tspec={currentView}\n\t\t\t\t\t\t\t\tstore={store}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{props.children}\n\t\t\t\t\t\t\t</ActorProviderInner>\n\t\t\t\t\t\t</StateProvider>\n\t\t\t\t\t);\n\t\t\t\t})()}\n\t\t\t</ErrorBoundary>\n\t\t</ActorContext.Provider>\n\t);\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyDA,IAAM2B,cAAcvB,cAAuC,IAAI;;;;;;;;;;;;;;;;AAiB/D,SAAgBwB,cAAgC;CAC/C,OAAOV,kBAAkBb,WAAWsB,WAAW,GAAG,aAAa;AAChE;;;;;;AAoCA,IAAMQ,sBAKAI,eAAe;CACpB,MAAMC,WAAW9B,cAAc;CAG/B,MAAM+B,mBAA6BC,YAAY;EAC9C,MAAMC,OAAOH,SAASI,YAAY;EAClCJ,SAASK,OAAOH,QAAQC,IAAI,CAAC;CAC9B;CAEA,MAAMG,WAAWP,WAAWH,eAAeU,eACpCL,uBACAD,SAASI,YAAY,CAC5B;CAEA,MAAMG,YAA8B;EACnCV,MAAME,WAAWF;EACjBS;EACAE,UAAUT,WAAWH,eAAeY;EACpCV,OAAOC,WAAWD;CACnB;CAEA,OAAAW,gBAAQtB,YAAYuB,UAAQ;EAACC,OAAOJ;EAAS,IAAAb,WAAA;GAAA,OAAGK,WAAWL;EAAQ;CAAA,CAAA;AACpE;;;;;;;;;;;;;;;;;;;;AAyBA,IAAakB,iBAAgDC,UAAU;CAEtE,MAAM,CAACC,MAAMC,WAAWvD,aAA8B,IAAI;CAY1D,MAAMwD,aAAa,IAAIC,MAAM,CAAC,GAAmB;EAChDC,IAAIC,SAASC,MAAM;GAClB,MAAMC,UAAUR,MAAMS;GACtB,MAAMX,QAAQY,QAAQL,IAAIG,SAASD,MAAMC,OAAO;GAChD,OAAO,OAAOV,UAAU,aAAaA,MAAMa,KAAKH,OAAO,IAAIV;EAC5D;EACAc,IAAIN,SAASC,MAAM;GAClB,OAAOA,QAASP,MAAMS;EACvB;CACD,CAAC;CAMD,MAAMI,yBAAyBhE,iBAAiB;EAC/C,IAAI,CAACmD,MAAMc,eAAe,OAAOd,MAAMjB;EACvC,OAAO;GACN,GAAGiB,MAAMjB;GACTY,UAAU7B,yBAAyBkC,MAAMjB,eAAeY,UAAUK,MAAMc,aAAa;EACtF;CACD,CAAC;CAOD,MAAMC,iBAAiBhD,0BAA0BiD,SAChDrD,sBAAsB,EAAEsD,MAAMvD,WAAWsD,IAAI,EAAE,CAAC,CACjD;CAOApE,mBAAmB;EAClB,MAAM4C,UAAU0B,aAA8BhB,QAAQgB,QAAQ;EAC9D1B,OAAOQ,MAAMS,MAAMU,YAAYd,IAAI,CAAoB;EACvD,MAAMe,UAAUxD,YAAYoC,MAAMS,MAAMU,cAAcD,aAAa;GAClE1B,OAAO0B,QAA2B;EACnC,CAAC;EACDpE,gBAAgBsE,QAAQ,CAAC;CAC1B,CAAC;CAED,OAAAxB,gBACEzB,aAAa0B,UAAQ;EAACC,OAAOK;EAAU,IAAAtB,WAAA;GAAA,OAAAe,gBACtC3C,eAAa;IACbwB,WAAW4C,QAAiB;KAC3BrB,MAAMrB,UAAU0C,GAAG;KACnB,OAAOrB,MAAMvB,YAAY;IAC1B;IAAC,IAAAI,WAAA;KAAA,cAEO;MACP,MAAMsC,cAAclB,KAAK;MACzB,IAAI,CAACkB,aAAa,OAAOnB,MAAMvB,YAAY;MAO3C,MAAMQ,QAAoB8B,eAAeO,QACxCtB,MAAMS,OACNU,aACAnB,MAAMf,KACP,CAAC,CAACsC;MAEF,OAAA3B,gBACExC,eAAa;OAAQ6B;OAAK,IAAAJ,WAAA;QAAA,OAAAe,gBACzBd,oBAAkB;SAAA,IAClBC,iBAAc;UAAA,OAAE8B,uBAAuB;SAAC;SACxC7B,MAAMmC;SACClC;SAAK,IAAAJ,WAAA;UAAA,OAEXmB,MAAMnB;SAAQ;QAAA,CAAA;OAAA;MAAA,CAAA;KAInB,EAAA,CAAG;IAAC;GAAA,CAAA;EAAA;CAAA,CAAA;AAIR"}
1
+ {"version":3,"file":"ActorProvider.js","names":["createSignal","createEffect","createMemo","onCleanup","createContext","useContext","untrack","ErrorBoundary","Show","Component","JSX","StateProvider","useStateStore","DefineRegistryResult","SetState","StateStore","ComponentRegistry","createAtom","xstateStoreStateStore","watchSignal","assertNonNullable","attachRenderErrorHandler","createReportGuard","createViewStoreLifecycle","sameViewInputs","PlaySpec","BaseActorProviderProps","BaseViewContextValue","ActorContext","AnyPlayActor","ViewContextValue","ViewContext","usePlayView","ActorProviderProps","fallback","Element","onError","error","reset","children","ActorProviderInner","registryResult","spec","store","innerProps","stateCtx","setStateAdapter","updater","prev","getSnapshot","update","handlers","viewValue","registry","_$createComponent","Provider","value","ActorProvider","props","view","setView","actorProxy","Proxy","get","_target","prop","current","actor","Reflect","bind","has","resolvedRegistryResult","onRenderError","storeLifecycle","seed","atom","nextView","currentView","unwatch","resetBoundary","guard","noHandler","handlerThrew","dispose","lastView","lastActor","lastStore","seeded","nextActor","nextStore","changed","undefined","err","hostRetry","blocked","handler","report","resolution","guardedStore","resolve","subtreeStore","when","keyed","currentSpec"],"sources":["../src/ActorProvider.tsx"],"sourcesContent":["/**\n * ActorProvider — the SolidJS provider component of the XMachines Play actor lifecycle.\n *\n * This is the low-level provider, for the author of a library who needs the control.\n * Most users take PlayUIProvider, the composite provider, instead.\n *\n * This component:\n * - subscribes to the actor.currentView signal with watchSignal, in the body of the component (Phase 29)\n * - manages the StateStore lifecycle of each view, controlled and uncontrolled\n * - resolves each action handler with the inner component pattern, inside StateProvider\n * - puts onRenderError into the registry, when the caller gives one\n * - gives ActorContext (the actor) and ViewContext (the spec, the handlers, and the registry) to the children\n * - wraps the render path in a SolidJS ErrorBoundary\n *\n * D-11: the old alias `ActorProvider = ActorContext.Provider` is gone. This component\n * has the name now. Use `ActorContext.Provider` directly for the raw access.\n *\n * @packageDocumentation\n */\n\nimport {\n\tcreateSignal,\n\tcreateEffect,\n\tcreateMemo,\n\tonCleanup,\n\tcreateContext,\n\tuseContext,\n\tuntrack,\n\tErrorBoundary,\n\tShow,\n} from \"solid-js\";\nimport type { Component, JSX } from \"solid-js\";\nimport { StateProvider, useStateStore } from \"@xmachines/json-render-solid\";\nimport type { DefineRegistryResult, SetState } from \"@xmachines/json-render-solid\";\nimport type { StateStore } from \"@xmachines/json-render-core\";\nimport type { ComponentRegistry } from \"@xmachines/json-render-solid\";\nimport { createAtom } from \"@xstate/store\";\nimport { xstateStoreStateStore } from \"@xmachines/json-render-xstate\";\nimport { watchSignal } from \"@xmachines/play-signals\";\nimport { assertNonNullable } from \"@xmachines/play\";\nimport {\n\tattachRenderErrorHandler,\n\tcreateReportGuard,\n\tcreateViewStoreLifecycle,\n\tsameViewInputs,\n\ttype PlaySpec,\n\ttype BaseActorProviderProps,\n\ttype BaseViewContextValue,\n} from \"@xmachines/play-actor\";\nimport { ActorContext, type AnyPlayActor } from \"./useActor.js\";\n\n// ---------------------------------------------------------------------------\n// ViewContextValue — the shape of the context value that ActorProvider gives\n// ---------------------------------------------------------------------------\n\n/**\n * The value that the ViewContext of ActorProvider provides.\n * usePlayView() gives it inside the ActorProvider tree.\n *\n * **`spec` is a live GETTER, and not a copied value.** The provider keeps the subtree\n * across an emission that holds the viewKey, so a read of `spec` inside a tracking\n * scope gives the newest spec and the Renderer reconciles in place. A read outside a\n * tracking scope gives a snapshot that goes old: `const { spec } = usePlayView()` and\n * `{ ...usePlayView() }` both freeze on the spec of that moment. Read `view.spec` at\n * the place of the use instead. The other three fields are copied values, and the\n * provider builds the subtree again when one of them changes.\n */\nexport interface ViewContextValue extends BaseViewContextValue<ComponentRegistry> {}\n\nconst ViewContext = createContext<ViewContextValue | null>(null);\n\n/**\n * The hook that gives the current view context inside an ActorProvider tree.\n *\n * **Read a field where you use it, and destructure it not.** `spec` is a GETTER: the\n * provider keeps the subtree across an emission that holds the viewKey, and it publishes\n * the new spec through that getter. `const { spec } = usePlayView()` therefore freezes at\n * the first read, and the screen stops following the actor — with no error and no\n * warning.\n *\n * The context holds `spec`, `handlers`, `registry` and `store`, and no other field. The\n * three beside `spec` are copied values, and the provider builds the subtree again when\n * one of them changes — see {@link ViewContextValue}.\n *\n * This changed in 2.3.0. Before it, every emission built the subtree again, so a value\n * that a component read one time was fresh because the component itself was new.\n *\n * @throws {Error} When the caller is outside an ActorProvider tree or a PlayUIProvider tree\n *\n * @example\n * ```tsx\n * import { usePlayView } from \"@xmachines/play-solid\";\n *\n * const MyRenderer: Component = () => {\n * const view = usePlayView();\n * return <Renderer spec={view.spec} registry={view.registry} />;\n * };\n * ```\n */\nexport function usePlayView(): ViewContextValue {\n\treturn assertNonNullable(useContext(ViewContext), \"ViewContext\");\n}\n\n// ---------------------------------------------------------------------------\n// ActorProviderProps\n// ---------------------------------------------------------------------------\n\n/**\n * The props of ActorProvider, the low-level provider.\n *\n * For the standard use, prefer PlayUIProvider. That component wraps ActorProvider\n * with JSONUIProvider and with every necessary sub-provider.\n */\nexport interface ActorProviderProps extends BaseActorProviderProps<DefineRegistryResult> {\n\t/**\n\t * The optional fallback element. The provider shows it when currentView is null, and when the ErrorBoundary catches an error\n\t */\n\tfallback?: JSX.Element;\n\n\t/**\n\t * The optional callback. The provider calls it when the SolidJS ErrorBoundary\n\t * catches an error.\n\t *\n\t * While the error is active, the provider renders the `fallback` instead of its\n\t * children. The provider clears the error on the next view emission (when\n\t * `currentView` changes), so a view transition retries the render.\n\t *\n\t * Element-level catalog component throws are caught upstream by the per-element\n\t * boundary of @json-render, and reported through `onRenderError`. This boundary is\n\t * the outer net for everything else.\n\t *\n\t * The second parameter is the RESET, for a retry that the host starts — a \"Retry\"\n\t * button of its own. It clears the caught error and renders again, and it resolves the\n\t * view that the actor holds at the MOMENT OF THE CALL, so a host that keeps the\n\t * callback cannot rewind the screen to the view that failed. A reset that the host\n\t * calls from inside this handler starts no second retry, because no input changed\n\t * between the two attempts, and a reset after the provider is disposed does nothing.\n\t * The five renderers hold the same three rules.\n\t *\n\t * **A handler that THROWS reaches no caller.** The provider contains that throw, and\n\t * it writes the throw to `console.error`. The fallback of this boundary is the RETURN\n\t * of the callback that reports, so a throw that left this handler would take the\n\t * fallback off the screen AND let the error escape the boundary.\n\t *\n\t * Report a failure from this handler. To ESCALATE one, raise it from a task of your\n\t * own — `queueMicrotask(() => { throw error; })` — which reaches the global handler of\n\t * the page and leaves the containment whole. The five renderers hold this one rule.\n\t */\n\tonError?: (error: unknown, reset: () => void) => void;\n\n\t/**\n\t * The children. They are necessary, and they must hold a <PlayRenderer />. You can also use the PlayUIProvider short form\n\t */\n\tchildren: JSX.Element;\n}\n\n// ---------------------------------------------------------------------------\n// ActorProviderInner — it resolves the handlers inside the StateProvider tree\n// ---------------------------------------------------------------------------\n\n/**\n * The inner component runs inside StateProvider. It can therefore call\n * useStateStore() to get the live set function and the live getSnapshot function, for\n * the resolution of the handlers.\n */\nconst ActorProviderInner: Component<{\n\tregistryResult: DefineRegistryResult;\n\tspec: PlaySpec;\n\tstore: StateStore;\n\tchildren: JSX.Element;\n}> = (innerProps) => {\n\tconst stateCtx = useStateStore();\n\n\t// Build the SetState adapter. It joins stateCtx.update and stateCtx.getSnapshot\n\tconst setStateAdapter: SetState = (updater) => {\n\t\tconst prev = stateCtx.getSnapshot();\n\t\tstateCtx.update(updater(prev));\n\t};\n\n\tconst handlers = innerProps.registryResult.handlers(\n\t\t() => setStateAdapter,\n\t\t() => stateCtx.getSnapshot(),\n\t);\n\n\t// `spec` is a GETTER, and not a copied value. PlayRenderer reads it inside the\n\t// Renderer, which is a tracking scope. An emission that keeps the viewKey therefore\n\t// reaches the Renderer, and the Renderer reconciles the tree in place. A copied\n\t// value freezes at the first render, and the provider then needs a remount of the\n\t// subtree to show a new spec — which destroys the DOM, the focus, and the\n\t// uncommitted input of the user.\n\tconst viewValue: ViewContextValue = {\n\t\tget spec() {\n\t\t\treturn innerProps.spec;\n\t\t},\n\t\thandlers,\n\t\tregistry: innerProps.registryResult.registry,\n\t\tstore: innerProps.store,\n\t};\n\n\treturn <ViewContext.Provider value={viewValue}>{innerProps.children}</ViewContext.Provider>;\n};\n\n// ---------------------------------------------------------------------------\n// ActorProvider — the component. D-11 gives it the ActorProvider name\n// ---------------------------------------------------------------------------\n\n/**\n * The ActorProvider component. It owns the actor bridge, the signal subscription, the\n * StateStore lifecycle, the resolution of the handlers, and the error boundary.\n *\n * D-11: this component replaces the old raw alias\n * `ActorProvider = ActorContext.Provider`. A consumer with an\n * `<ActorProvider value={actor}>` element takes\n * `<ActorContext.Provider value={actor}>` now, for the raw access to the provider.\n * That consumer can also move to this component, or to PlayUIProvider.\n *\n * @example\n * ```tsx\n * import { ActorProvider, PlayRenderer } from \"@xmachines/play-solid\";\n *\n * <ActorProvider actor={myActor} registryResult={registryResult}>\n * <PlayRenderer />\n * </ActorProvider>\n * ```\n */\nexport const ActorProvider: Component<ActorProviderProps> = (props) => {\n\t// The SolidJS signal of the current view (PlaySpec | null)\n\tconst [view, setView] = createSignal<PlaySpec | null>(null);\n\n\t// The code gives a stable Proxy as the value of ActorContext, and not the raw actor.\n\t// The Context.Provider of Solid reads `value` one time, at its creation. Therefore\n\t// `props.actor` gives the FIRST actor only, and a useActor() consumer sees a change of\n\t// the prop never. With the proxy, each consumer keeps the reference of the creation,\n\t// and every property access (send, currentView, and the others) resolves against the\n\t// newest actor. A read of `props.actor` inside a trap is a reactive read. Therefore a\n\t// consumer that reads a property in a tracking scope, such as createEffect,\n\t// createMemo, or the JSX, runs again after a change of the actor. Each method binds to\n\t// the current actor. Therefore `this`, and also each private field, works exactly as\n\t// in a direct call. The ActorProvider proxy of play-vue does the same.\n\tconst actorProxy = new Proxy({} as AnyPlayActor, {\n\t\tget(_target, prop) {\n\t\t\tconst current = props.actor as AnyPlayActor;\n\t\t\tconst value = Reflect.get(current, prop, current) as unknown;\n\t\t\treturn typeof value === \"function\" ? value.bind(current) : value;\n\t\t},\n\t\thas(_target, prop) {\n\t\t\treturn prop in (props.actor as AnyPlayActor);\n\t\t},\n\t});\n\n\t// Put onRenderError into the registry, when the caller gives one. The property is\n\t// not enumerable, and it replaces the handler below it.\n\t// The code keeps the result, because a new object on each reactive evaluation renders\n\t// each child component that receives it as a prop again, for nothing.\n\tconst resolvedRegistryResult = createMemo(() => {\n\t\tif (!props.onRenderError) return props.registryResult;\n\t\treturn {\n\t\t\t...props.registryResult,\n\t\t\tregistry: attachRenderErrorHandler(props.registryResult.registry, props.onRenderError),\n\t\t};\n\t});\n\n\t// The store lifecycle: it seeds the store again on a change of the viewKey, it\n\t// refreshes /context in place in every other case, it resets the store on a change of\n\t// the actor, and it guards the identity cache. The shared coordinator comes from\n\t// @xmachines/play-actor. Only the wiring of the reactivity in the tracked JSX scope\n\t// below belongs to Solid.\n\tconst storeLifecycle = createViewStoreLifecycle((seed) =>\n\t\txstateStoreStateStore({ atom: createAtom(seed) }),\n\t);\n\n\t// Connect the TC39 Signal to the SolidJS signal. The code seeds the value AND starts\n\t// the watch in one createEffect, at the same moment. Therefore no race window is\n\t// between the .get() call and the registration of the watcher. The TC39 signal can\n\t// change between the first .get() call and the first notification of the watcher, and\n\t// the update function then holds the newest value.\n\tcreateEffect(() => {\n\t\tconst update = (nextView: PlaySpec | null) => setView(nextView);\n\t\tupdate(props.actor.currentView.get() as PlaySpec | null);\n\t\tconst unwatch = watchSignal(props.actor.currentView, (nextView) => {\n\t\t\tupdate(nextView as PlaySpec | null);\n\t\t});\n\t\tonCleanup(() => unwatch());\n\t});\n\n\t// RESET RULE: the boundary clears a caught error on the next view emission, so a\n\t// view transition retries the render. Without the rule the fallback stays for the\n\t// complete life of the provider: the user sends a recovery event, the actor moves to\n\t// a healthy view, and the screen keeps the fallback. play-react and play-svelte keep\n\t// the same rule.\n\tlet resetBoundary: (() => void) | undefined;\n\t// The report guard, from @xmachines/play-actor: it blocks a reset that the host calls\n\t// from INSIDE the report, and every reset once the owner is disposed — the host can\n\t// keep the callback, and a \"Retry\" button of a toast outlives the route that opened\n\t// it. The five renderers share the one implementation.\n\t//\n\t// It CONTAINS a handler of the host that throws. The fallback of this boundary is the\n\t// RETURN of the callback that reports, so a throw that leaves the report skips that\n\t// return: the screen keeps no fallback AND the error escapes the boundary, which is\n\t// both outcomes that the boundary exists to prevent. play-vue holds the same rule.\n\tconst guard = createReportGuard({\n\t\tnoHandler:\n\t\t\t\"[@xmachines/play-solid] ActorProvider contained an error of a descendant. \" +\n\t\t\t\"Give an onError prop to receive it.\",\n\t\thandlerThrew:\n\t\t\t\"[@xmachines/play-solid] the onError handler of the host threw. \" +\n\t\t\t\"ActorProvider contained the failure of the view all the same.\",\n\t});\n\tonCleanup(() => {\n\t\tguard.dispose();\n\t});\n\t// This renderer needs the report-once latch of play-vue and play-svelte NOT, and it\n\t// carries none. Solid calls a two-argument ErrorBoundary fallback inside `untrack`\n\t// (`untrack(() => f(e, () => setErrored()))` of solid-js), so the reads in that\n\t// fallback track nothing and it can run again on its own never: one caught error is\n\t// one call. The double report that this renderer had came from the effect below, which\n\t// ran on any prop of the host and retried a view that still fails.\n\t// The actor and the store BESIDE the view. A render fails on any of the three, so a\n\t// reset that watches the view alone leaves the fallback on the screen when the caller\n\t// repairs a controlled store, or swaps the actor, without a new emission. play-react\n\t// and play-svelte hold the same three.\n\t//\n\t// The effect COMPARES the three, and it trusts the notification not. A read of\n\t// `props.actor` or `props.store` goes through the spread of PlayUIProvider, and a read\n\t// of one key there subscribes to the props object of the parent — so ANY prop of the\n\t// host runs this effect again. Measured: a host that swaps its `fallback` element reset\n\t// the boundary, which retried the render of a view that still fails and reported the\n\t// same failure a second time. `on(..., { defer: true })` cannot hold that line, because\n\t// it re-runs on the notification and never asks whether a value moved.\n\tlet lastView: PlaySpec | null | undefined;\n\tlet lastActor: ActorProviderProps[\"actor\"] | undefined;\n\tlet lastStore: StateStore | undefined;\n\tlet seeded = false;\n\tcreateEffect(() => {\n\t\tconst nextView = view();\n\t\tconst nextActor = props.actor;\n\t\tconst nextStore = props.store;\n\t\t// The comparison of the three inputs, from @xmachines/play-actor. play-react and\n\t\t// play-svelte read the same function, so the rule of a NEW attempt stands one time.\n\t\tconst changed =\n\t\t\t!seeded ||\n\t\t\t!sameViewInputs(\n\t\t\t\t{ actor: lastActor, view: lastView, store: lastStore },\n\t\t\t\t{ actor: nextActor, view: nextView, store: nextStore },\n\t\t\t);\n\t\tlastView = nextView;\n\t\tlastActor = nextActor;\n\t\tlastStore = nextStore;\n\t\tif (!seeded) {\n\t\t\t// The first run seeds the record, and it resets nothing: there is no error yet.\n\t\t\tseeded = true;\n\t\t\treturn;\n\t\t}\n\t\tif (!changed) return;\n\t\tconst reset = resetBoundary;\n\t\tresetBoundary = undefined;\n\t\treset?.();\n\t});\n\n\treturn (\n\t\t<ActorContext.Provider value={actorProxy}>\n\t\t\t<ErrorBoundary\n\t\t\t\tfallback={(err: unknown, reset: () => void) => {\n\t\t\t\t\tresetBoundary = reset;\n\t\t\t\t\t// The reset of the boundary, wrapped for the HOST. Solid builds the\n\t\t\t\t\t// children again, so the retry resolves the view that the actor holds at\n\t\t\t\t\t// the moment of the call — a retry cannot rewind the screen to the view\n\t\t\t\t\t// that failed. The two guards are the ones that the five renderers hold:\n\t\t\t\t\t// a reset after the owner is disposed does nothing, and a reset that the\n\t\t\t\t\t// host calls from INSIDE this report does nothing, because no input\n\t\t\t\t\t// changed between the two attempts and Solid would rebuild on the same\n\t\t\t\t\t// stack until it ends.\n\t\t\t\t\tconst hostRetry = (): void => {\n\t\t\t\t\t\tif (guard.blocked()) return;\n\t\t\t\t\t\tresetBoundary = undefined;\n\t\t\t\t\t\treset();\n\t\t\t\t\t};\n\t\t\t\t\t// The boundary CONTAINS the error, and a boundary that throws it again\n\t\t\t\t\t// would take the application down — which is the failure that the\n\t\t\t\t\t// boundary exists to prevent. Report it instead. Without `onError` the\n\t\t\t\t\t// error reaches no handler and no console, because the ErrorBoundary of\n\t\t\t\t\t// Solid logs nothing itself, so write it to `console.error`. play-vue,\n\t\t\t\t\t// play-dom, and the inner renderer of @json-render each do the same.\n\t\t\t\t\t// A silent crash is the one result that no caller can debug.\n\t\t\t\t\tconst handler = props.onError;\n\t\t\t\t\tguard.report(err, handler ? () => handler(err, hostRetry) : undefined);\n\t\t\t\t\treturn props.fallback ?? null;\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t{(() => {\n\t\t\t\t\t// The two memos below belong to the OWNER of the ErrorBoundary, because\n\t\t\t\t\t// the boundary evaluates its children in its own owner. A memo of the\n\t\t\t\t\t// component scope above stays outside the boundary, and a throw of\n\t\t\t\t\t// `spec.state` then escapes to the application. This function reads no\n\t\t\t\t\t// signal. Therefore it runs exactly one time.\n\n\t\t\t\t\t// The resolution of one emission. It runs for EVERY emission, because\n\t\t\t\t\t// the lifecycle must refresh /context in place, and it must seed the\n\t\t\t\t\t// store again on a new viewKey. The code reads props.actor and\n\t\t\t\t\t// props.store here. Both are therefore tracked, and a change of one of\n\t\t\t\t\t// them runs the resolution again.\n\t\t\t\t\tconst resolution = createMemo(() => {\n\t\t\t\t\t\tconst currentView = view();\n\t\t\t\t\t\tif (!currentView) return null;\n\t\t\t\t\t\tconst { guardedStore } = storeLifecycle.resolve(\n\t\t\t\t\t\t\tprops.actor,\n\t\t\t\t\t\t\tcurrentView,\n\t\t\t\t\t\t\tprops.store,\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn { view: currentView, store: guardedStore };\n\t\t\t\t\t});\n\n\t\t\t\t\t// The key of the SUBTREE REMOUNT. The lifecycle keeps one guard for each\n\t\t\t\t\t// store below it. The identity of the guard therefore changes only after\n\t\t\t\t\t// a new seed of the store, and never after a refresh of /context in\n\t\t\t\t\t// place. `<Show keyed>` builds the subtree again only on a change of this\n\t\t\t\t\t// value. Without this rule, each emission of a stream removes the DOM,\n\t\t\t\t\t// and the user loses the focus, the caret, and the input that the form\n\t\t\t\t\t// holds. play-vue keeps the same rule with its storeKey.\n\t\t\t\t\tconst subtreeStore = createMemo(() => resolution()?.store ?? null);\n\n\t\t\t\t\treturn (\n\t\t\t\t\t\t<Show when={subtreeStore()} keyed fallback={props.fallback ?? null}>\n\t\t\t\t\t\t\t{(store: StateStore) => {\n\t\t\t\t\t\t\t\t// The spec that belongs to this seed. `untrack` keeps the read\n\t\t\t\t\t\t\t\t// out of the scope of the branch, so a later emission does not\n\t\t\t\t\t\t\t\t// build the branch again.\n\t\t\t\t\t\t\t\tconst seed = untrack(resolution);\n\t\t\t\t\t\t\t\tif (!seed) return null;\n\n\t\t\t\t\t\t\t\tconst currentSpec = (): PlaySpec => resolution()?.view ?? seed.view;\n\n\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t<StateProvider store={store}>\n\t\t\t\t\t\t\t\t\t\t{/* A swap of the registry, or of onRenderError, builds\n\t\t\t\t\t\t\t\t\t\t the tree again: ActorProviderInner resolves the\n\t\t\t\t\t\t\t\t\t\t handlers one time, at its own build. The memo keeps\n\t\t\t\t\t\t\t\t\t\t one identity for each pair. Therefore an emission\n\t\t\t\t\t\t\t\t\t\t alone never reaches this key. */}\n\t\t\t\t\t\t\t\t\t\t<Show when={resolvedRegistryResult()} keyed>\n\t\t\t\t\t\t\t\t\t\t\t{(registryResult: DefineRegistryResult) => (\n\t\t\t\t\t\t\t\t\t\t\t\t<ActorProviderInner\n\t\t\t\t\t\t\t\t\t\t\t\t\tregistryResult={registryResult}\n\t\t\t\t\t\t\t\t\t\t\t\t\tspec={currentSpec()}\n\t\t\t\t\t\t\t\t\t\t\t\t\tstore={store}\n\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t{props.children}\n\t\t\t\t\t\t\t\t\t\t\t\t</ActorProviderInner>\n\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t</Show>\n\t\t\t\t\t\t\t\t\t</StateProvider>\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t</Show>\n\t\t\t\t\t);\n\t\t\t\t})()}\n\t\t\t</ErrorBoundary>\n\t\t</ActorContext.Provider>\n\t);\n};\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xmachines/play-solid",
3
- "version": "2.1.1",
3
+ "version": "3.0.0",
4
4
  "description": "Solid renderer for XMachines Play architecture",
5
5
  "keywords": [
6
6
  "catalog",
@@ -47,18 +47,18 @@
47
47
  "test:ui": "vitest --ui"
48
48
  },
49
49
  "dependencies": {
50
- "@xmachines/play": "2.1.1",
51
- "@xmachines/play-actor": "2.1.1",
52
- "@xmachines/play-signals": "2.1.1"
50
+ "@xmachines/play": "3.0.0",
51
+ "@xmachines/play-actor": "3.0.0",
52
+ "@xmachines/play-signals": "3.0.0"
53
53
  },
54
54
  "devDependencies": {
55
55
  "@solidjs/testing-library": "^0.8.10",
56
56
  "@testing-library/jest-dom": "^6.9.1",
57
57
  "@types/node": "^26.2.0",
58
58
  "@vitest/browser-playwright": "^4.1.11",
59
- "@xmachines/json-render-core": "^0.20.0-xm.2",
60
- "@xmachines/json-render-solid": "^0.20.0-xm.2",
61
- "@xmachines/json-render-xstate": "^0.20.0-xm.2",
59
+ "@xmachines/json-render-core": "^0.20.0-xm.4",
60
+ "@xmachines/json-render-solid": "^0.20.0-xm.4",
61
+ "@xmachines/json-render-xstate": "^0.20.0-xm.4",
62
62
  "@xstate/store": "^3.17.0",
63
63
  "jsdom": "^29.1.0",
64
64
  "oxfmt": "^0.64.0",
@@ -72,9 +72,9 @@
72
72
  "zod": "^4.4.1"
73
73
  },
74
74
  "peerDependencies": {
75
- "@xmachines/json-render-core": "^0.20.0-xm.2",
76
- "@xmachines/json-render-solid": "^0.20.0-xm.2",
77
- "@xmachines/json-render-xstate": "^0.20.0-xm.2",
75
+ "@xmachines/json-render-core": "^0.20.0-xm.4",
76
+ "@xmachines/json-render-solid": "^0.20.0-xm.4",
77
+ "@xmachines/json-render-xstate": "^0.20.0-xm.4",
78
78
  "@xstate/store": "^3.17.0",
79
79
  "solid-js": "^1.8.0",
80
80
  "xstate": "^5.31.0"