@sphynxoid/stepwise 0.2.0 → 0.3.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/dist/index.js CHANGED
@@ -66,17 +66,23 @@ function useStepPlayer({
66
66
  engine,
67
67
  syncHash,
68
68
  onEnd,
69
- respectReducedMotion = true
69
+ respectReducedMotion = true,
70
+ holdAt,
71
+ follow = false
70
72
  }) {
71
73
  const core = useSteps(total);
72
74
  const { go, next: coreNext, prev: corePrev, first } = core;
73
75
  const [started, setStarted] = useState2(autoPlay && total > 0);
74
- const [playing, setPlaying] = useState2(autoPlay && total > 0);
76
+ const [playing, setPlaying] = useState2(autoPlay && (total > 0 || follow));
75
77
  const [speed, setSpeed] = useState2(1);
76
78
  const [ended, setEnded] = useState2(false);
77
79
  const endedRef = useRef2(false);
78
80
  const onEndRef = useRef2(onEnd);
79
81
  onEndRef.current = onEnd;
82
+ const holdAtRef = useRef2(holdAt);
83
+ holdAtRef.current = holdAt;
84
+ const [held, setHeld] = useState2(null);
85
+ const releasedRef = useRef2(/* @__PURE__ */ new Set());
80
86
  const markEnded = useCallback2(() => {
81
87
  if (endedRef.current) return;
82
88
  endedRef.current = true;
@@ -90,6 +96,7 @@ function useStepPlayer({
90
96
  }, []);
91
97
  const index = started && core.index >= 0 ? core.index : -1;
92
98
  const atEnd = total > 0 && index >= 0 && index >= total - 1;
99
+ const live = follow && held === null && (started ? playing || atEnd : playing);
93
100
  const totalRef = useRef2(total);
94
101
  totalRef.current = total;
95
102
  const cursorRef = useRef2(core.index);
@@ -98,6 +105,10 @@ function useStepPlayer({
98
105
  playingRef.current = playing;
99
106
  const atEndRef = useRef2(atEnd);
100
107
  atEndRef.current = atEnd;
108
+ const heldRef = useRef2(held);
109
+ heldRef.current = held;
110
+ const startedRef = useRef2(started);
111
+ startedRef.current = started;
101
112
  const engineRef = useRef2(engine);
102
113
  engineRef.current = engine;
103
114
  const engineApiRef = useRef2({});
@@ -135,6 +146,8 @@ function useStepPlayer({
135
146
  setStarted(true);
136
147
  setPlaying(true);
137
148
  clearEnded();
149
+ setHeld(null);
150
+ releasedRef.current = /* @__PURE__ */ new Set();
138
151
  first();
139
152
  if (engineRef.current) {
140
153
  engineApiRef.current.seek?.(0);
@@ -145,6 +158,8 @@ function useStepPlayer({
145
158
  setStarted(false);
146
159
  setPlaying(false);
147
160
  clearEnded();
161
+ setHeld(null);
162
+ releasedRef.current = /* @__PURE__ */ new Set();
148
163
  if (engineRef.current) engineApiRef.current.pause?.();
149
164
  }, [clearEnded]);
150
165
  const seek = useCallback2(
@@ -154,6 +169,7 @@ function useStepPlayer({
154
169
  setStarted(true);
155
170
  setPlaying(false);
156
171
  clearEnded();
172
+ setHeld(null);
157
173
  go(Math.max(0, Math.min(f, t - 1)));
158
174
  if (engineRef.current) {
159
175
  engineApiRef.current.pause?.();
@@ -172,6 +188,7 @@ function useStepPlayer({
172
188
  }
173
189
  setStarted(true);
174
190
  clearEnded();
191
+ setHeld(null);
175
192
  const target = Math.min(cursorRef.current + 1, t - 1);
176
193
  coreNext();
177
194
  if (engineRef.current) {
@@ -184,6 +201,7 @@ function useStepPlayer({
184
201
  const prev = useCallback2(() => {
185
202
  setPlaying(false);
186
203
  clearEnded();
204
+ setHeld(null);
187
205
  const target = Math.max(0, cursorRef.current - 1);
188
206
  corePrev();
189
207
  if (engineRef.current) {
@@ -204,6 +222,10 @@ function useStepPlayer({
204
222
  const nextPlaying = !playingRef.current;
205
223
  setPlaying(nextPlaying);
206
224
  setStarted(true);
225
+ if (nextPlaying) {
226
+ releasedRef.current.add(cursorRef.current);
227
+ setHeld(null);
228
+ }
207
229
  if (engineRef.current) {
208
230
  if (nextPlaying) engineApiRef.current.play?.();
209
231
  else engineApiRef.current.pause?.();
@@ -211,6 +233,36 @@ function useStepPlayer({
211
233
  },
212
234
  [start]
213
235
  );
236
+ const release = useCallback2(() => {
237
+ const h = heldRef.current;
238
+ if (h === null) return;
239
+ releasedRef.current.add(h);
240
+ setHeld(null);
241
+ setPlaying(true);
242
+ }, []);
243
+ const jump = useCallback2(
244
+ (f) => {
245
+ const t = totalRef.current;
246
+ if (t === 0) return;
247
+ setStarted(true);
248
+ go(Math.max(0, Math.min(f, t - 1)));
249
+ if (engineRef.current) engineApiRef.current.seek?.(f);
250
+ },
251
+ [go]
252
+ );
253
+ const goLive = useCallback2(() => {
254
+ const t = totalRef.current;
255
+ if (t === 0) return;
256
+ setStarted(true);
257
+ setPlaying(false);
258
+ clearEnded();
259
+ setHeld(null);
260
+ go(t - 1);
261
+ if (engineRef.current) {
262
+ engineApiRef.current.pause?.();
263
+ engineApiRef.current.seek?.(t - 1);
264
+ }
265
+ }, [go, clearEnded]);
214
266
  const changeSpeed = useCallback2((s) => {
215
267
  setSpeed(s);
216
268
  if (engineRef.current) engineApiRef.current.rate?.(s);
@@ -218,6 +270,21 @@ function useStepPlayer({
218
270
  useEffect2(() => {
219
271
  clearEnded();
220
272
  }, [total, clearEnded]);
273
+ const prevTotal = useRef2(total);
274
+ useEffect2(() => {
275
+ const before = prevTotal.current;
276
+ prevTotal.current = total;
277
+ if (!follow || engineRef.current || total <= before) return;
278
+ if (heldRef.current !== null) return;
279
+ if (!startedRef.current) {
280
+ if (playingRef.current) setStarted(true);
281
+ return;
282
+ }
283
+ if (playingRef.current || cursorRef.current >= before - 1) setPlaying(true);
284
+ }, [total, follow]);
285
+ useEffect2(() => {
286
+ if (held !== null && held !== core.index) setHeld(null);
287
+ }, [held, core.index]);
221
288
  useEffect2(() => {
222
289
  if (engineRef.current) return;
223
290
  if (!playing || !started || total === 0) return;
@@ -226,30 +293,48 @@ function useStepPlayer({
226
293
  setPlaying(false);
227
294
  return;
228
295
  }
296
+ if (holdAtRef.current?.(core.index) && !releasedRef.current.has(core.index)) {
297
+ setHeld(core.index);
298
+ setPlaying(false);
299
+ return;
300
+ }
229
301
  const id = setTimeout(() => coreNext(), baseDelayMs / speed);
230
302
  return () => clearTimeout(id);
231
303
  }, [playing, started, core.index, coreNext, total, speed, baseDelayMs, markEnded]);
232
304
  const lastWrittenHash = useRef2("");
305
+ const pendingRead = useRef2(null);
233
306
  const readHash = useCallback2(() => {
234
- if (!syncHash || typeof window === "undefined" || total === 0) return;
307
+ const t = totalRef.current;
308
+ if (!syncHash || typeof window === "undefined" || t === 0) return;
235
309
  const hash = window.location.hash;
236
310
  if (hash === lastWrittenHash.current) return;
237
311
  const value = readHashParam(hash, syncHash);
238
312
  if (value === null || !/^\d+$/.test(value)) return;
239
313
  const f = Number(value);
240
- if (f < total) {
314
+ if (f < t) {
315
+ pendingRead.current = f;
241
316
  setStarted(true);
242
317
  go(f);
243
318
  }
244
- }, [syncHash, total, go]);
319
+ }, [syncHash, go]);
320
+ const linkedRead = useRef2(false);
245
321
  useEffect2(() => {
246
322
  readHash();
323
+ linkedRead.current = totalRef.current > 0;
247
324
  const onHashChange = () => readHash();
248
325
  window.addEventListener("hashchange", onHashChange);
249
326
  return () => window.removeEventListener("hashchange", onHashChange);
250
327
  }, [readHash]);
328
+ useEffect2(() => {
329
+ if (linkedRead.current || total === 0) return;
330
+ linkedRead.current = true;
331
+ readHash();
332
+ }, [total, readHash]);
251
333
  useEffect2(() => {
252
334
  if (!syncHash || index < 0 || typeof window === "undefined") return;
335
+ const pending2 = pendingRead.current;
336
+ pendingRead.current = null;
337
+ if (pending2 !== null && pending2 !== index) return;
253
338
  const next2 = writeHashParam(window.location.hash, syncHash, String(index));
254
339
  if (window.location.hash !== next2) {
255
340
  lastWrittenHash.current = next2;
@@ -265,6 +350,11 @@ function useStepPlayer({
265
350
  speed,
266
351
  atEnd,
267
352
  ended,
353
+ held,
354
+ release,
355
+ jump,
356
+ live,
357
+ goLive,
268
358
  start,
269
359
  reset,
270
360
  seek,
@@ -274,7 +364,7 @@ function useStepPlayer({
274
364
  setPlaying,
275
365
  setSpeed: changeSpeed
276
366
  }),
277
- [total, index, playing, speed, atEnd, ended, start, reset, seek, next, prev, toggle, changeSpeed]
367
+ [total, index, playing, speed, atEnd, ended, held, release, jump, live, goLive, start, reset, seek, next, prev, toggle, changeSpeed]
278
368
  );
279
369
  }
280
370
  function hashParts(hash) {
@@ -298,18 +388,139 @@ function writeHashParam(hash, key, value) {
298
388
  }
299
389
 
300
390
  // src/use-trace.ts
301
- import { useCallback as useCallback3, useMemo as useMemo3, useRef as useRef3 } from "react";
391
+ import { useCallback as useCallback3, useEffect as useEffect3, useLayoutEffect, useMemo as useMemo3, useReducer, useRef as useRef3 } from "react";
392
+
393
+ // src/reanchor.ts
394
+ function reanchor(oldSteps, oldIndex, newSteps, keyOf) {
395
+ if (newSteps.length === 0) return -1;
396
+ if (oldIndex < 0 || oldSteps.length === 0) return 0;
397
+ const positions = /* @__PURE__ */ new Map();
398
+ newSteps.forEach((s, i) => {
399
+ const k = keyOf(s, i);
400
+ if (k !== void 0 && !positions.has(k)) positions.set(k, i);
401
+ });
402
+ for (let i = Math.min(oldIndex, oldSteps.length - 1); i >= 0; i--) {
403
+ const k = keyOf(oldSteps[i], i);
404
+ if (k === void 0) continue;
405
+ const j = positions.get(k);
406
+ if (j !== void 0) return j;
407
+ }
408
+ return 0;
409
+ }
410
+ var stepKey = (step) => step !== null && typeof step === "object" && typeof step.key === "string" ? step.key : void 0;
411
+
412
+ // src/use-trace.ts
413
+ var useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect3;
302
414
  function useTrace(steps, opts) {
303
- const player = useStepPlayer({ ...opts, total: steps.length });
415
+ const { keyOf, holdAt, ...playerOpts } = opts ?? {};
304
416
  const stepsRef = useRef3(steps);
417
+ const player = useStepPlayer({
418
+ ...playerOpts,
419
+ total: steps.length,
420
+ holdAt: holdAt ? (f) => holdAt(f, stepsRef.current[f]) : void 0
421
+ });
305
422
  stepsRef.current = steps;
306
- const frame = steps.length === 0 ? -1 : Math.max(0, Math.min(player.index, steps.length - 1));
423
+ const keyFn = keyOf ?? stepKey;
424
+ const shownRef = useRef3({ steps, index: player.index });
425
+ const shown = shownRef.current;
426
+ let anchored = null;
427
+ if (shown.steps !== steps && shown.index >= 0) {
428
+ const i = shown.index;
429
+ const k = i < shown.steps.length ? keyFn(shown.steps[i], i) : void 0;
430
+ if (k !== void 0 && i < steps.length && keyFn(steps[i], i) === k) anchored = i;
431
+ else if (steps.some((s, j) => keyFn(s, j) !== void 0)) anchored = reanchor(shown.steps, i, steps, keyFn);
432
+ }
433
+ const index = anchored ?? player.index;
434
+ const { jump } = player;
435
+ const [, rerender] = useReducer((n) => n + 1, 0);
436
+ useIsomorphicLayoutEffect(() => {
437
+ shownRef.current = { steps, index };
438
+ if (anchored === null) return;
439
+ if (anchored >= 0 && anchored !== player.index) jump(anchored);
440
+ rerender();
441
+ });
442
+ const frame = steps.length === 0 ? -1 : Math.max(0, Math.min(index, steps.length - 1));
307
443
  const step = frame >= 0 ? steps[frame] : void 0;
308
444
  const toneAt = useCallback3((i) => stepsRef.current[i]?.tone ?? "neutral", []);
309
445
  const titleAt = useCallback3((i) => stepsRef.current[i]?.label ?? `step ${i + 1}`, []);
310
446
  return useMemo3(() => ({ player, step, frame, toneAt, titleAt }), [player, step, frame, toneAt, titleAt]);
311
447
  }
312
448
 
449
+ // src/use-simulation.ts
450
+ import { useMemo as useMemo4 } from "react";
451
+
452
+ // src/simulation.ts
453
+ function hashSeed(seed) {
454
+ let h = 2166136261;
455
+ for (let i = 0; i < seed.length; i++) {
456
+ h ^= seed.charCodeAt(i);
457
+ h = Math.imul(h, 16777619);
458
+ }
459
+ return h >>> 0;
460
+ }
461
+ function createRng(seed = 0) {
462
+ const base = typeof seed === "string" ? hashSeed(seed) : seed >>> 0;
463
+ let state = base;
464
+ const next = () => {
465
+ state = state + 1831565813 >>> 0;
466
+ let t = state;
467
+ t = Math.imul(t ^ t >>> 15, t | 1);
468
+ t ^= t + Math.imul(t ^ t >>> 7, t | 61);
469
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
470
+ };
471
+ const int = (min, max) => min + Math.floor(next() * (max - min + 1));
472
+ return {
473
+ seed: base,
474
+ next,
475
+ int,
476
+ pick(items) {
477
+ if (items.length === 0) throw new RangeError("rng.pick: empty array");
478
+ return items[int(0, items.length - 1)];
479
+ },
480
+ shuffle(items) {
481
+ const out = items.slice();
482
+ for (let i = out.length - 1; i > 0; i--) {
483
+ const j = int(0, i);
484
+ [out[i], out[j]] = [out[j], out[i]];
485
+ }
486
+ return out;
487
+ }
488
+ };
489
+ }
490
+ function runSimulation(simulation, input, opts) {
491
+ const maxSteps = opts?.maxSteps ?? 1e4;
492
+ const rng = createRng(opts?.seed ?? 0);
493
+ const steps = [];
494
+ let truncated = false;
495
+ let error = null;
496
+ try {
497
+ for (const step of simulation(input, { rng })) {
498
+ if (steps.length >= maxSteps) {
499
+ truncated = true;
500
+ break;
501
+ }
502
+ steps.push(step);
503
+ }
504
+ } catch (e) {
505
+ error = e;
506
+ }
507
+ return { steps, truncated, error, seed: rng.seed };
508
+ }
509
+
510
+ // src/use-simulation.ts
511
+ function useSimulation(simulation, input, opts) {
512
+ const { seed, maxSteps, ...traceOpts } = opts ?? {};
513
+ const result = useMemo4(
514
+ () => runSimulation(simulation, input, { seed, maxSteps }),
515
+ [simulation, input, seed, maxSteps]
516
+ );
517
+ const trace = useTrace(result.steps, traceOpts);
518
+ return useMemo4(
519
+ () => ({ ...trace, steps: result.steps, truncated: result.truncated, error: result.error, seed: result.seed }),
520
+ [trace, result]
521
+ );
522
+ }
523
+
313
524
  // src/create-trace.ts
314
525
  function createTrace() {
315
526
  const frames = [];
@@ -332,7 +543,7 @@ function createTrace() {
332
543
  }
333
544
 
334
545
  // src/use-synced-tracks.ts
335
- import { useCallback as useCallback4, useMemo as useMemo4, useRef as useRef4 } from "react";
546
+ import { useCallback as useCallback4, useMemo as useMemo5, useRef as useRef4 } from "react";
336
547
  function alignTracks(tracks, key) {
337
548
  const names = Object.keys(tracks);
338
549
  const lens = names.map((n) => tracks[n].length);
@@ -420,7 +631,7 @@ function useSyncedTracks(tracks, opts) {
420
631
  const frame = rows.length === 0 ? -1 : Math.max(0, Math.min(player.index, rows.length - 1));
421
632
  const row = frame >= 0 ? rows[frame] : void 0;
422
633
  const aligned = cache.current;
423
- const view = useMemo4(() => {
634
+ const view = useMemo5(() => {
424
635
  const out = {};
425
636
  aligned.names.forEach((n, t) => {
426
637
  const list = aligned.arrays[t];
@@ -455,20 +666,20 @@ function useSyncedTracks(tracks, opts) {
455
666
  },
456
667
  [moved]
457
668
  );
458
- return useMemo4(
669
+ return useMemo5(
459
670
  () => ({ player, frame, total: rows.length, tracks: view, rows, toneAt, titleAt }),
460
671
  [player, frame, rows, view, toneAt, titleAt]
461
672
  );
462
673
  }
463
674
 
464
675
  // src/use-lesson-stream.ts
465
- import { useCallback as useCallback5, useEffect as useEffect4, useLayoutEffect, useMemo as useMemo5, useRef as useRef5, useState as useState4 } from "react";
676
+ import { useCallback as useCallback5, useEffect as useEffect5, useLayoutEffect as useLayoutEffect2, useMemo as useMemo6, useRef as useRef5, useState as useState4 } from "react";
466
677
 
467
678
  // src/use-prefers-reduced-motion.ts
468
- import { useEffect as useEffect3, useState as useState3 } from "react";
679
+ import { useEffect as useEffect4, useState as useState3 } from "react";
469
680
  function usePrefersReducedMotion() {
470
681
  const [reduced, setReduced] = useState3(false);
471
- useEffect3(() => {
682
+ useEffect4(() => {
472
683
  const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
473
684
  setReduced(mq.matches);
474
685
  const onChange = (e) => setReduced(e.matches);
@@ -479,7 +690,7 @@ function usePrefersReducedMotion() {
479
690
  }
480
691
 
481
692
  // src/use-lesson-stream.ts
482
- var useIsoLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect4;
693
+ var useIsoLayoutEffect = typeof window !== "undefined" ? useLayoutEffect2 : useEffect5;
483
694
  function useLessonStream(count, opts = {}) {
484
695
  const { radius = 1, approachBelow = 600, approachAbove = 300, scrollToSeed = true } = opts;
485
696
  const last = count - 1;
@@ -529,7 +740,7 @@ function useLessonStream(count, opts = {}) {
529
740
  setRange(grown);
530
741
  }, []);
531
742
  const scheduleRef = useRef5(null);
532
- useEffect4(() => {
743
+ useEffect5(() => {
533
744
  if (count === 0) return;
534
745
  let raf = 0;
535
746
  const update = () => {
@@ -572,7 +783,7 @@ function useLessonStream(count, opts = {}) {
572
783
  if (raf) cancelAnimationFrame(raf);
573
784
  };
574
785
  }, [count, expandTo]);
575
- useEffect4(() => {
786
+ useEffect5(() => {
576
787
  scheduleRef.current?.();
577
788
  }, [first, lastMounted]);
578
789
  const firstRef = useRef5(first);
@@ -592,7 +803,7 @@ function useLessonStream(count, opts = {}) {
592
803
  blocks.current.get(seed)?.scrollIntoView({ block: "start", behavior: "auto" });
593
804
  }, []);
594
805
  const [nav, setNav] = useState4(null);
595
- useEffect4(() => {
806
+ useEffect5(() => {
596
807
  if (!nav) return;
597
808
  const raf = requestAnimationFrame(
598
809
  () => blocks.current.get(nav.target)?.scrollIntoView({ behavior: reducedRef.current ? "auto" : "smooth", block: "start" })
@@ -611,12 +822,12 @@ function useLessonStream(count, opts = {}) {
611
822
  );
612
823
  const next = useCallback5(() => goTo(activeRef.current + 1), [goTo]);
613
824
  const prev = useCallback5(() => goTo(activeRef.current - 1), [goTo]);
614
- const mounted = useMemo5(() => {
825
+ const mounted = useMemo6(() => {
615
826
  const out = [];
616
827
  for (let i = first; i <= lastMounted; i++) out.push(i);
617
828
  return out;
618
829
  }, [first, lastMounted]);
619
- return useMemo5(
830
+ return useMemo6(
620
831
  () => ({
621
832
  active: core.index,
622
833
  seed,
@@ -634,7 +845,7 @@ function useLessonStream(count, opts = {}) {
634
845
  function useFadeIn(initiallyVisible = false) {
635
846
  const reduced = usePrefersReducedMotion();
636
847
  const [shown, setShown] = useState4(initiallyVisible);
637
- useEffect4(() => {
848
+ useEffect5(() => {
638
849
  if (shown) return;
639
850
  if (reduced) {
640
851
  setShown(true);
@@ -646,26 +857,105 @@ function useFadeIn(initiallyVisible = false) {
646
857
  return shown;
647
858
  }
648
859
 
860
+ // src/use-evaluator.ts
861
+ import { useCallback as useCallback6, useEffect as useEffect6, useMemo as useMemo7, useRef as useRef6, useState as useState5 } from "react";
862
+
863
+ // src/evaluate.ts
864
+ function toVerdict(result) {
865
+ return typeof result === "boolean" ? { status: result ? "pass" : "fail" } : result;
866
+ }
867
+ var pass = (feedback) => ({ status: "pass", feedback });
868
+ var fail = (feedback) => ({ status: "fail", feedback });
869
+ var pending = (feedback) => ({ status: "pending", feedback });
870
+
871
+ // src/use-evaluator.ts
872
+ var IDLE = { status: "idle", feedback: void 0, error: null };
873
+ function useEvaluator(evaluator, ctx, opts) {
874
+ const mode = opts?.mode ?? "auto";
875
+ const [state, setState] = useState5(IDLE);
876
+ const [attempts, setAttempts] = useState5(0);
877
+ const evaluatorRef = useRef6(evaluator);
878
+ evaluatorRef.current = evaluator;
879
+ const ctxRef = useRef6(ctx);
880
+ ctxRef.current = ctx;
881
+ const runRef = useRef6(null);
882
+ useEffect6(() => () => runRef.current?.abort(), []);
883
+ const run = useCallback6(async () => {
884
+ runRef.current?.abort();
885
+ const controller = new AbortController();
886
+ runRef.current = controller;
887
+ let result;
888
+ try {
889
+ result = evaluatorRef.current(ctxRef.current, controller.signal);
890
+ } catch (error) {
891
+ setState({ status: "fail", feedback: void 0, error });
892
+ return { status: "fail" };
893
+ }
894
+ if (!(result instanceof Promise)) {
895
+ const verdict = toVerdict(result);
896
+ setState({ status: verdict.status, feedback: verdict.feedback, error: null });
897
+ return verdict;
898
+ }
899
+ setState((s) => ({ status: "pending", feedback: s.feedback, error: null }));
900
+ try {
901
+ const verdict = toVerdict(await result);
902
+ if (controller.signal.aborted) return null;
903
+ setState({ status: verdict.status, feedback: verdict.feedback, error: null });
904
+ return verdict;
905
+ } catch (error) {
906
+ if (controller.signal.aborted) return null;
907
+ setState({ status: "fail", feedback: void 0, error });
908
+ return { status: "fail" };
909
+ }
910
+ }, []);
911
+ const check = useCallback6(() => {
912
+ setAttempts((n) => n + 1);
913
+ return run();
914
+ }, [run]);
915
+ const reset = useCallback6(() => {
916
+ runRef.current?.abort();
917
+ runRef.current = null;
918
+ setState(IDLE);
919
+ setAttempts(0);
920
+ }, []);
921
+ useEffect6(() => {
922
+ if (mode === "auto") {
923
+ void run();
924
+ } else {
925
+ runRef.current?.abort();
926
+ setState(IDLE);
927
+ }
928
+ }, [ctx, mode, run]);
929
+ return useMemo7(
930
+ () => ({ ...state, passed: state.status === "pass", attempts, check, reset }),
931
+ [state, attempts, check, reset]
932
+ );
933
+ }
934
+
649
935
  // src/use-guided-tour.ts
650
- import { useCallback as useCallback6, useEffect as useEffect5, useMemo as useMemo6, useRef as useRef6, useState as useState5 } from "react";
936
+ import { useCallback as useCallback7, useEffect as useEffect7, useMemo as useMemo8, useRef as useRef7, useState as useState6 } from "react";
937
+ function gateVerdict(step) {
938
+ const r = step?.gate?.();
939
+ return r === void 0 ? { status: "pass", feedback: void 0 } : toVerdict(r);
940
+ }
651
941
  function useGuidedTour(steps, opts) {
652
- const [running, setRunning] = useState5(false);
653
- const runningRef = useRef6(running);
942
+ const [running, setRunning] = useState6(false);
943
+ const runningRef = useRef7(running);
654
944
  runningRef.current = running;
655
945
  const core = useSteps(steps.length);
656
- const stepsRef = useRef6(steps);
946
+ const stepsRef = useRef7(steps);
657
947
  stepsRef.current = steps;
658
- const driverRef = useRef6(opts?.driver);
948
+ const driverRef = useRef7(opts?.driver);
659
949
  driverRef.current = opts?.driver;
660
- const coreGoRef = useRef6(core.go);
950
+ const coreGoRef = useRef7(core.go);
661
951
  coreGoRef.current = core.go;
662
- const cursorRef = useRef6(core.index);
952
+ const cursorRef = useRef7(core.index);
663
953
  cursorRef.current = core.index;
664
- const isSkipped = useCallback6(
954
+ const isSkipped = useCallback7(
665
955
  (i) => stepsRef.current[i]?.when?.() === false,
666
956
  []
667
957
  );
668
- const settle = useCallback6(
958
+ const settle = useCallback7(
669
959
  (start2, dir) => {
670
960
  const len = stepsRef.current.length;
671
961
  let i = start2;
@@ -677,7 +967,7 @@ function useGuidedTour(steps, opts) {
677
967
  },
678
968
  [isSkipped]
679
969
  );
680
- const arrive = useCallback6(
970
+ const arrive = useCallback7(
681
971
  (i, dir) => {
682
972
  const target = settle(i, dir);
683
973
  if (target === null) {
@@ -689,14 +979,14 @@ function useGuidedTour(steps, opts) {
689
979
  },
690
980
  [settle]
691
981
  );
692
- const goTo = useCallback6(
982
+ const goTo = useCallback7(
693
983
  (i) => {
694
984
  if (stepsRef.current.length === 0) return;
695
985
  arrive(Math.max(0, Math.min(i, stepsRef.current.length - 1)), 1);
696
986
  },
697
987
  [arrive]
698
988
  );
699
- const goToId = useCallback6(
989
+ const goToId = useCallback7(
700
990
  (id) => {
701
991
  const i = stepsRef.current.findIndex((s) => s.id === id);
702
992
  if (i >= 0) goTo(i);
@@ -704,11 +994,11 @@ function useGuidedTour(steps, opts) {
704
994
  [goTo]
705
995
  );
706
996
  const index = running ? core.index : null;
707
- useEffect5(() => {
997
+ useEffect7(() => {
708
998
  if (!running) return;
709
999
  stepsRef.current[core.index]?.onEnter?.();
710
1000
  }, [running, core.index]);
711
- useEffect5(() => {
1001
+ useEffect7(() => {
712
1002
  const driver = driverRef.current;
713
1003
  if (!driver) return;
714
1004
  if (!running) {
@@ -728,13 +1018,13 @@ function useGuidedTour(steps, opts) {
728
1018
  end: () => setRunning(false)
729
1019
  });
730
1020
  }, [running, core.index, goTo]);
731
- useEffect5(() => () => driverRef.current?.hide(), []);
732
- const start = useCallback6(() => {
1021
+ useEffect7(() => () => driverRef.current?.hide(), []);
1022
+ const start = useCallback7(() => {
733
1023
  if (stepsRef.current.length === 0) return;
734
1024
  arrive(0, 1);
735
1025
  }, [arrive]);
736
- const end = useCallback6(() => setRunning(false), []);
737
- const resolveNext = useCallback6((i) => {
1026
+ const end = useCallback7(() => setRunning(false), []);
1027
+ const resolveNext = useCallback7((i) => {
738
1028
  const list = stepsRef.current;
739
1029
  const edge = list[i]?.next?.();
740
1030
  if (edge != null) {
@@ -743,10 +1033,10 @@ function useGuidedTour(steps, opts) {
743
1033
  }
744
1034
  return i + 1;
745
1035
  }, []);
746
- const next = useCallback6(() => {
1036
+ const next = useCallback7(() => {
747
1037
  if (!runningRef.current) return;
748
1038
  const i = cursorRef.current;
749
- if (stepsRef.current[i]?.gate?.() === false) return;
1039
+ if (gateVerdict(stepsRef.current[i]).status !== "pass") return;
750
1040
  const target = resolveNext(i);
751
1041
  if (target > stepsRef.current.length - 1) {
752
1042
  setRunning(false);
@@ -754,22 +1044,24 @@ function useGuidedTour(steps, opts) {
754
1044
  }
755
1045
  arrive(target, 1);
756
1046
  }, [arrive, resolveNext]);
757
- const back = useCallback6(() => {
1047
+ const back = useCallback7(() => {
758
1048
  if (!runningRef.current) return;
759
1049
  const i = cursorRef.current;
760
1050
  if (i <= 0) return;
761
1051
  arrive(i - 1, -1);
762
1052
  }, [arrive]);
763
1053
  const current = running ? stepsRef.current[core.index] ?? null : null;
764
- const blocked = current?.gate?.() === false;
765
- return useMemo6(
766
- () => ({ index, current, active: running, blocked, start, end, next, back, goto: goTo, gotoId: goToId }),
767
- [index, current, running, blocked, start, end, next, back, goTo, goToId]
1054
+ const verdict = current ? gateVerdict(current) : null;
1055
+ const blocked = verdict !== null && verdict.status !== "pass";
1056
+ const feedback = verdict?.feedback;
1057
+ return useMemo8(
1058
+ () => ({ index, current, active: running, blocked, feedback, start, end, next, back, goto: goTo, gotoId: goToId }),
1059
+ [index, current, running, blocked, feedback, start, end, next, back, goTo, goToId]
768
1060
  );
769
1061
  }
770
1062
 
771
1063
  // src/use-chapters.ts
772
- import { useCallback as useCallback7, useEffect as useEffect6, useMemo as useMemo7, useRef as useRef7, useState as useState6 } from "react";
1064
+ import { useCallback as useCallback8, useEffect as useEffect8, useMemo as useMemo9, useRef as useRef8, useState as useState7 } from "react";
773
1065
 
774
1066
  // src/flatten.ts
775
1067
  function flattenChapters(chapters, depth = 0) {
@@ -788,17 +1080,17 @@ function flattenChapters(chapters, depth = 0) {
788
1080
 
789
1081
  // src/use-chapters.ts
790
1082
  function useChapters(chapters) {
791
- const [mounted, setMounted] = useState6(false);
792
- const flat = useMemo7(() => flattenChapters(chapters), [chapters]);
793
- const flatRef = useRef7(flat);
1083
+ const [mounted, setMounted] = useState7(false);
1084
+ const flat = useMemo9(() => flattenChapters(chapters), [chapters]);
1085
+ const flatRef = useRef8(flat);
794
1086
  flatRef.current = flat;
795
- const [active, setActive] = useState6(0);
796
- const activeRef = useRef7(0);
1087
+ const [active, setActive] = useState7(0);
1088
+ const activeRef = useRef8(0);
797
1089
  activeRef.current = active;
798
1090
  const core = useSteps(flat.length, { initialIndex: 0 });
799
- const coreGoRef = useRef7(core.go);
1091
+ const coreGoRef = useRef8(core.go);
800
1092
  coreGoRef.current = core.go;
801
- useEffect6(() => {
1093
+ useEffect8(() => {
802
1094
  if (!mounted) return;
803
1095
  let raf = 0;
804
1096
  const update = () => {
@@ -836,8 +1128,8 @@ function useChapters(chapters) {
836
1128
  if (raf) cancelAnimationFrame(raf);
837
1129
  };
838
1130
  }, [mounted, flat.length]);
839
- useEffect6(() => setMounted(true), []);
840
- const goTo = useCallback7((i) => {
1131
+ useEffect8(() => setMounted(true), []);
1132
+ const goTo = useCallback8((i) => {
841
1133
  const entry = flatRef.current[i];
842
1134
  if (!entry) return;
843
1135
  setActive(i);
@@ -846,25 +1138,25 @@ function useChapters(chapters) {
846
1138
  if (!el) return;
847
1139
  el.scrollIntoView({ behavior: "smooth", block: "start" });
848
1140
  }, []);
849
- const next = useCallback7(() => {
1141
+ const next = useCallback8(() => {
850
1142
  if (activeRef.current < flatRef.current.length - 1) goTo(activeRef.current + 1);
851
1143
  }, [goTo]);
852
- const prev = useCallback7(() => {
1144
+ const prev = useCallback8(() => {
853
1145
  if (activeRef.current > 0) goTo(activeRef.current - 1);
854
1146
  }, [goTo]);
855
- return useMemo7(() => ({ active: core.index, goTo, next, prev }), [core.index, goTo, next, prev]);
1147
+ return useMemo9(() => ({ active: core.index, goTo, next, prev }), [core.index, goTo, next, prev]);
856
1148
  }
857
1149
 
858
1150
  // src/use-global-keys.ts
859
- import { useEffect as useEffect7, useRef as useRef8 } from "react";
1151
+ import { useEffect as useEffect9, useRef as useRef9 } from "react";
860
1152
  var DEFAULT_IGNORED_TAGS = ["INPUT", "TEXTAREA", "SELECT", "BUTTON"];
861
1153
  function useGlobalKeys(map, opts) {
862
- const mapRef = useRef8(map);
1154
+ const mapRef = useRef9(map);
863
1155
  mapRef.current = map;
864
- const ignoreRef = useRef8(new Set(opts?.ignoreTags ?? DEFAULT_IGNORED_TAGS));
865
- const enabledRef = useRef8(opts?.enabled ?? true);
1156
+ const ignoreRef = useRef9(new Set(opts?.ignoreTags ?? DEFAULT_IGNORED_TAGS));
1157
+ const enabledRef = useRef9(opts?.enabled ?? true);
866
1158
  enabledRef.current = opts?.enabled ?? true;
867
- useEffect7(() => {
1159
+ useEffect9(() => {
868
1160
  const onKey = (e) => {
869
1161
  const gate = enabledRef.current;
870
1162
  const enabled = typeof gate === "function" ? gate() : gate;
@@ -882,7 +1174,7 @@ function useGlobalKeys(map, opts) {
882
1174
  }
883
1175
 
884
1176
  // src/use-discoveries.ts
885
- import { useCallback as useCallback8, useEffect as useEffect8, useMemo as useMemo8, useRef as useRef9, useState as useState7 } from "react";
1177
+ import { useCallback as useCallback9, useEffect as useEffect10, useMemo as useMemo10, useRef as useRef10, useState as useState8 } from "react";
886
1178
  function localStorageStore(key = "stepwise.discoveries") {
887
1179
  return {
888
1180
  read() {
@@ -933,23 +1225,23 @@ function memoryStore(seed = []) {
933
1225
  };
934
1226
  }
935
1227
  function useDiscoveries(opts) {
936
- const defaultStoreRef = useRef9(null);
937
- const storeRef = useRef9(null);
1228
+ const defaultStoreRef = useRef10(null);
1229
+ const storeRef = useRef10(null);
938
1230
  if (opts?.store) {
939
1231
  storeRef.current = opts.store;
940
1232
  } else {
941
1233
  if (!defaultStoreRef.current) defaultStoreRef.current = localStorageStore(opts?.key);
942
1234
  storeRef.current = defaultStoreRef.current;
943
1235
  }
944
- const [ids, setIds] = useState7(() => /* @__PURE__ */ new Set());
945
- const [ready, setReady] = useState7(false);
946
- const idsRef = useRef9(ids);
1236
+ const [ids, setIds] = useState8(() => /* @__PURE__ */ new Set());
1237
+ const [ready, setReady] = useState8(false);
1238
+ const idsRef = useRef10(ids);
947
1239
  idsRef.current = ids;
948
- useEffect8(() => {
1240
+ useEffect10(() => {
949
1241
  setIds(new Set(storeRef.current.read()));
950
1242
  setReady(true);
951
1243
  }, []);
952
- const markDiscovered = useCallback8((id) => {
1244
+ const markDiscovered = useCallback9((id) => {
953
1245
  const additions = Array.isArray(id) ? id : [id];
954
1246
  const merged = new Set(idsRef.current);
955
1247
  let changed = false;
@@ -964,24 +1256,24 @@ function useDiscoveries(opts) {
964
1256
  setIds(merged);
965
1257
  storeRef.current.write([...merged]);
966
1258
  }, []);
967
- const reset = useCallback8(() => {
1259
+ const reset = useCallback9(() => {
968
1260
  const empty = /* @__PURE__ */ new Set();
969
1261
  idsRef.current = empty;
970
1262
  setIds(empty);
971
1263
  storeRef.current.write([]);
972
1264
  }, []);
973
- const isDiscovered = useCallback8((id) => idsRef.current.has(id), []);
974
- return useMemo8(
1265
+ const isDiscovered = useCallback9((id) => idsRef.current.has(id), []);
1266
+ return useMemo10(
975
1267
  () => ({ discovered: ids, ready, isDiscovered, markDiscovered, reset }),
976
1268
  [ids, ready, isDiscovered, markDiscovered, reset]
977
1269
  );
978
1270
  }
979
1271
 
980
1272
  // src/use-focus-trap.ts
981
- import { useEffect as useEffect9, useRef as useRef10 } from "react";
1273
+ import { useEffect as useEffect11, useRef as useRef11 } from "react";
982
1274
  function useFocusTrap(active) {
983
- const ref = useRef10(null);
984
- useEffect9(() => {
1275
+ const ref = useRef11(null);
1276
+ useEffect11(() => {
985
1277
  if (!active) return;
986
1278
  const container = ref.current;
987
1279
  if (!container) return;
@@ -1018,14 +1310,14 @@ function useFocusTrap(active) {
1018
1310
  // src/glossary.tsx
1019
1311
  import {
1020
1312
  createContext,
1021
- useCallback as useCallback9,
1313
+ useCallback as useCallback10,
1022
1314
  useContext,
1023
- useEffect as useEffect10,
1315
+ useEffect as useEffect12,
1024
1316
  useId,
1025
- useLayoutEffect as useLayoutEffect2,
1026
- useMemo as useMemo9,
1027
- useRef as useRef11,
1028
- useState as useState8
1317
+ useLayoutEffect as useLayoutEffect3,
1318
+ useMemo as useMemo11,
1319
+ useRef as useRef12,
1320
+ useState as useState9
1029
1321
  } from "react";
1030
1322
  import { createPortal } from "react-dom";
1031
1323
 
@@ -1083,7 +1375,7 @@ function GlossaryProvider({
1083
1375
  theme = defaultTheme,
1084
1376
  children
1085
1377
  }) {
1086
- const value = useMemo9(
1378
+ const value = useMemo11(
1087
1379
  () => ({ byId: new Map(entries.map((e) => [e.id, e])), entries, theme }),
1088
1380
  [entries, theme]
1089
1381
  );
@@ -1093,23 +1385,23 @@ function Term({ id, children, className }) {
1093
1385
  const ctx = useContext(GlossaryContext);
1094
1386
  const depth = useContext(DepthContext);
1095
1387
  const entry = ctx?.byId.get(id);
1096
- const [open, setOpen] = useState8(false);
1097
- const [pos, setPos] = useState8(null);
1098
- const openRef = useRef11(false);
1099
- const wrapperRef = useRef11(null);
1100
- const triggerRef = useRef11(null);
1101
- const cardRef = useRef11(null);
1102
- const openTimer = useRef11(0);
1103
- const closeTimer = useRef11(0);
1388
+ const [open, setOpen] = useState9(false);
1389
+ const [pos, setPos] = useState9(null);
1390
+ const openRef = useRef12(false);
1391
+ const wrapperRef = useRef12(null);
1392
+ const triggerRef = useRef12(null);
1393
+ const cardRef = useRef12(null);
1394
+ const openTimer = useRef12(0);
1395
+ const closeTimer = useRef12(0);
1104
1396
  const tipId = useId();
1105
1397
  const display = children ?? entry?.term ?? id;
1106
- const closeNow = useCallback9(() => {
1398
+ const closeNow = useCallback10(() => {
1107
1399
  window.clearTimeout(openTimer.current);
1108
1400
  window.clearTimeout(closeTimer.current);
1109
1401
  openRef.current = false;
1110
1402
  setOpen(false);
1111
1403
  }, []);
1112
- const openNow = useCallback9((focusCard = false) => {
1404
+ const openNow = useCallback10((focusCard = false) => {
1113
1405
  window.clearTimeout(closeTimer.current);
1114
1406
  window.clearTimeout(openTimer.current);
1115
1407
  openRef.current = true;
@@ -1118,18 +1410,18 @@ function Term({ id, children, className }) {
1118
1410
  requestAnimationFrame(() => cardRef.current?.focus({ preventScroll: true }));
1119
1411
  }
1120
1412
  }, []);
1121
- const scheduleOpen = useCallback9(() => {
1413
+ const scheduleOpen = useCallback10(() => {
1122
1414
  window.clearTimeout(closeTimer.current);
1123
1415
  if (openRef.current) return;
1124
1416
  window.clearTimeout(openTimer.current);
1125
1417
  openTimer.current = window.setTimeout(() => openNow(), OPEN_DELAY_MS);
1126
1418
  }, [openNow]);
1127
- const scheduleClose = useCallback9(() => {
1419
+ const scheduleClose = useCallback10(() => {
1128
1420
  window.clearTimeout(openTimer.current);
1129
1421
  window.clearTimeout(closeTimer.current);
1130
1422
  closeTimer.current = window.setTimeout(closeNow, CLOSE_GRACE_MS);
1131
1423
  }, [closeNow]);
1132
- useLayoutEffect2(() => {
1424
+ useLayoutEffect3(() => {
1133
1425
  if (!open) {
1134
1426
  setPos(null);
1135
1427
  return;
@@ -1166,7 +1458,7 @@ function Term({ id, children, className }) {
1166
1458
  if (raf) cancelAnimationFrame(raf);
1167
1459
  };
1168
1460
  }, [open]);
1169
- useEffect10(() => {
1461
+ useEffect12(() => {
1170
1462
  if (!open) return;
1171
1463
  const onPointerDown = (e) => {
1172
1464
  const target = e.target;
@@ -1177,7 +1469,7 @@ function Term({ id, children, className }) {
1177
1469
  document.addEventListener("pointerdown", onPointerDown);
1178
1470
  return () => document.removeEventListener("pointerdown", onPointerDown);
1179
1471
  }, [open, closeNow]);
1180
- useEffect10(() => () => {
1472
+ useEffect12(() => () => {
1181
1473
  window.clearTimeout(openTimer.current);
1182
1474
  window.clearTimeout(closeTimer.current);
1183
1475
  }, []);
@@ -1330,7 +1622,7 @@ function GlossaryAppendix({
1330
1622
  }
1331
1623
 
1332
1624
  // src/components.tsx
1333
- import { useEffect as useEffect11, useState as useState9 } from "react";
1625
+ import { useEffect as useEffect13, useState as useState10 } from "react";
1334
1626
  import { createPortal as createPortal2 } from "react-dom";
1335
1627
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
1336
1628
  var TONE_SEGMENT_CLASS = defaultTheme.toneSegment;
@@ -1469,15 +1761,19 @@ function PlayerBar({
1469
1761
  className,
1470
1762
  theme = defaultTheme,
1471
1763
  groupOf,
1472
- labels
1764
+ labels,
1765
+ live
1473
1766
  }) {
1474
1767
  const L = {
1475
1768
  previous: labels?.previous ?? "Previous step",
1476
1769
  play: labels?.play ?? "Play",
1477
1770
  pause: labels?.pause ?? "Pause",
1478
1771
  replay: labels?.replay ?? "Replay",
1479
- next: labels?.next ?? "Next step"
1772
+ next: labels?.next ?? "Next step",
1773
+ live: labels?.live ?? "Live",
1774
+ goLive: labels?.goLive ?? "Go live"
1480
1775
  };
1776
+ const behind = Math.max(0, player.total - 1 - Math.max(player.index, 0));
1481
1777
  const playLabel = player.playing ? L.pause : player.total > 0 && player.atEnd ? L.replay : L.play;
1482
1778
  return /* @__PURE__ */ jsxs2("div", { "data-stepwise": "", className: cn("flex items-center gap-2 p-2", className), children: [
1483
1779
  leading,
@@ -1497,7 +1793,15 @@ function PlayerBar({
1497
1793
  groupOf
1498
1794
  }
1499
1795
  ),
1500
- /* @__PURE__ */ jsx2("span", { className: cn("text-[10px] font-mono w-11 text-right shrink-0", theme.muted), children: player.total > 0 ? `${player.index + 1}/${player.total}` : "\u2014" })
1796
+ /* @__PURE__ */ jsx2("span", { className: cn("text-[10px] font-mono w-11 text-right shrink-0", theme.muted), children: player.total > 0 ? `${player.index + 1}/${player.total}` : "\u2014" }),
1797
+ live && (player.live || behind === 0 ? /* @__PURE__ */ jsxs2("span", { role: "status", className: "flex items-center gap-1 text-[10px] font-mono uppercase text-primary shrink-0", children: [
1798
+ /* @__PURE__ */ jsx2("span", { "aria-hidden": "true", className: cn("h-1.5 w-1.5 rounded-full bg-primary", player.playing && "motion-safe:animate-pulse") }),
1799
+ L.live
1800
+ ] }) : /* @__PURE__ */ jsxs2(StepButton, { variant: "ghost", onClick: () => player.goLive?.(), className: "h-7 px-2 text-[10px] font-mono shrink-0", children: [
1801
+ L.goLive,
1802
+ " +",
1803
+ behind
1804
+ ] }))
1501
1805
  ] });
1502
1806
  }
1503
1807
  function ChapterNav({
@@ -1602,8 +1906,8 @@ function TourSpotlight({
1602
1906
  target,
1603
1907
  theme = defaultTheme
1604
1908
  }) {
1605
- const [rect, setRect] = useState9(null);
1606
- useEffect11(() => {
1909
+ const [rect, setRect] = useState10(null);
1910
+ useEffect13(() => {
1607
1911
  if (!target) {
1608
1912
  setRect(null);
1609
1913
  return;
@@ -1659,7 +1963,8 @@ function TourCard({
1659
1963
  labels,
1660
1964
  className,
1661
1965
  theme = defaultTheme,
1662
- nextDisabled = false
1966
+ nextDisabled = false,
1967
+ feedback
1663
1968
  }) {
1664
1969
  const L = {
1665
1970
  title: labels?.title ?? "Guided tour",
@@ -1707,6 +2012,7 @@ function TourCard({
1707
2012
  /* @__PURE__ */ jsx2("p", { className: "text-sm font-bold text-foreground/95 mb-1.5", children: step.title }),
1708
2013
  /* @__PURE__ */ jsx2("div", { className: "text-[12px] font-mono text-foreground/70 leading-relaxed", children: step.body }),
1709
2014
  step.waitHint && /* @__PURE__ */ jsx2("p", { className: "text-[11px] font-mono text-accent mt-2 animate-pulse", children: step.waitHint }),
2015
+ /* @__PURE__ */ jsx2("div", { "aria-live": "polite", children: feedback != null && feedback !== false && /* @__PURE__ */ jsx2("p", { className: "text-[11px] font-mono text-foreground/70 mt-2", children: feedback }) }),
1710
2016
  /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2 mt-3 pt-3 border-t border-white/10", children: [
1711
2017
  /* @__PURE__ */ jsx2(StepButton, { variant: "outline", onClick: onBack, disabled: index === 0, className: "h-7 px-3 text-[11px]", children: L.back }),
1712
2018
  /* @__PURE__ */ jsx2(
@@ -1732,10 +2038,10 @@ function ShortcutsDialog({
1732
2038
  className,
1733
2039
  theme = defaultTheme
1734
2040
  }) {
1735
- const [mounted, setMounted] = useState9(false);
2041
+ const [mounted, setMounted] = useState10(false);
1736
2042
  const trapRef = useFocusTrap(open);
1737
- useEffect11(() => setMounted(true), []);
1738
- useEffect11(() => {
2043
+ useEffect13(() => setMounted(true), []);
2044
+ useEffect13(() => {
1739
2045
  if (!open) return;
1740
2046
  const onKey = (e) => {
1741
2047
  if (e.key === "Escape") onClose();
@@ -1807,22 +2113,33 @@ export {
1807
2113
  TourSpotlight,
1808
2114
  alignTracks,
1809
2115
  cookieStore,
2116
+ createRng,
1810
2117
  createTrace,
1811
2118
  defaultTheme,
2119
+ fail,
1812
2120
  flattenChapters,
1813
2121
  localStorageStore,
1814
2122
  memoryStore,
2123
+ pass,
2124
+ pending,
2125
+ readHashParam,
2126
+ reanchor,
2127
+ runSimulation,
2128
+ toVerdict,
1815
2129
  useChapters,
1816
2130
  useDiscoveries,
2131
+ useEvaluator,
1817
2132
  useFadeIn,
1818
2133
  useFocusTrap,
1819
2134
  useGlobalKeys,
1820
2135
  useGuidedTour,
1821
2136
  useLessonStream,
1822
2137
  usePrefersReducedMotion,
2138
+ useSimulation,
1823
2139
  useStepPlayer,
1824
2140
  useSteps,
1825
2141
  useSyncedTracks,
1826
- useTrace
2142
+ useTrace,
2143
+ writeHashParam
1827
2144
  };
1828
2145
  //# sourceMappingURL=index.js.map