narraleaf-react 0.45.0 → 0.45.1

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.
@@ -436,6 +436,34 @@ export declare class GameState {
436
436
  isStateMounted(key: Values<ExposedKeys>): boolean;
437
437
  getExposedState<T extends ExposedStateType>(key: ExposedKeys[T]): ExposedState[T] | null;
438
438
  getExposedStateForce<T extends ExposedStateType>(key: ExposedKeys[T]): ExposedState[T];
439
+ /**
440
+ * Wait for a component to expose its state, then hand it over - **never in the caller's own
441
+ * task**, and always the state that is on screen when the hand-over happens.
442
+ *
443
+ * The waiting branch used to call back straight out of `mountState`, which runs inside the
444
+ * component's mount effect, and that is what made starting a scene one unbroken synchronous
445
+ * chain: the action flushes the stage, React commits, the new displayable mounts, this fires,
446
+ * the action resolves, `next()` runs the following action, which flushes the stage again - one
447
+ * commit per element, all of it without ever returning to the event loop.
448
+ *
449
+ * React counts those: a commit that leaves synchronous work pending on the same root bumps
450
+ * `nestedUpdateCount`, and at fifty it **throws** `Maximum update depth exceeded` - in
451
+ * production as well as in development. So a scene was only ever allowed about twenty-five
452
+ * displayables before the whole player died at its error boundary, with the game silently
453
+ * never starting. Measured 2026-09-04: a scene of 23 images ran, one of 26 did not.
454
+ *
455
+ * Two things follow from handing over a turn later, and both are load-bearing:
456
+ *
457
+ * - The turn is taken through {@link yieldToBrowser} rather than a timer. A scene of 44 images
458
+ * took **45 seconds** to start through `setTimeout(0)` when its window was not the foreground
459
+ * one, because a background window's timers are throttled to about one a second; the boot
460
+ * preload gave up first.
461
+ * - The state is **re-read at hand-over** instead of being taken from the event. React mounts an
462
+ * effect, tears it down and mounts it again under `StrictMode`, so what announced itself one
463
+ * turn ago can already be the throwaway mount's state, and calling `initDisplayable` on that
464
+ * one leaves the action waiting for ever. A hand-over that finds nothing mounted stays
465
+ * subscribed and waits for the mount that follows.
466
+ */
439
467
  getExposedStateAsync<T extends ExposedStateType>(key: ExposedKeys[T], onExpose: (state: ExposedState[T]) => void): LiveGameEventToken;
440
468
  /**
441
469
  * Dispose of the game state
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Hand a callback to the next turn of the event loop, without a timer.
3
+ *
4
+ * Exists because two different things need to be true at once, and `setTimeout(fn, 0)` only manages
5
+ * one of them:
6
+ *
7
+ * - It has to be a **task**, not a microtask. React counts commits that leave synchronous work
8
+ * pending on the same root, and throws `Maximum update depth exceeded` at fifty of them - so a
9
+ * chain of work driven out of mount effects has to return to the event loop to reset that count.
10
+ * Microtasks drain before the loop turns and would not.
11
+ * - It must not be a **timer**. A window that is not the foreground one has its timers throttled to
12
+ * roughly one a second by the browser, so a chain of fifty `setTimeout(0)` hand-overs takes about
13
+ * fifty seconds in a background window and milliseconds in a focused one. Measured 2026-09-04:
14
+ * starting a scene of 44 images this way timed the boot preload out at 45s.
15
+ *
16
+ * A `MessageChannel` message is a task and is not a timer, which is the same reason React's own
17
+ * scheduler reaches for one. The `setTimeout` fallback is for hosts that have no `MessageChannel`
18
+ * (a plain Node test run, say), where throttling is not a thing either.
19
+ *
20
+ * The returned function cancels the hand-over if it has not happened yet.
21
+ */
22
+ export declare function yieldToBrowser(callback: () => void): () => void;