@sphynxoid/stepwise 0.1.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.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { P as PlayerEngine, a as TourStepInput, T as TourDriver, C as Chapter, S as StepTone, b as ShortcutItem } from './types-CPf9ZD_Z.js';
2
- export { c as PlayerEngineApi, d as PlayerEngineHandle, e as StepwiseStep, f as TourContext } from './types-CPf9ZD_Z.js';
1
+ import { P as PlayerEngine, S as StepwiseStep, a as StepTone, V as VerdictStatus, b as Verdict, E as Evaluator, c as TourStepInput, T as TourDriver, C as Chapter, d as ShortcutItem } from './types-CNMxISXe.js';
2
+ export { e as EvaluatorResult, f as PlayerEngineApi, g as PlayerEngineHandle, h as TourContext, i as fail, p as pass, j as pending, t as toVerdict } from './types-CNMxISXe.js';
3
3
  import * as react from 'react';
4
4
  import { ReactNode } from 'react';
5
5
 
@@ -53,7 +53,8 @@ interface StepPlayerOptions {
53
53
  engine?: PlayerEngine;
54
54
  /**
55
55
  * Deep-link the current frame into the URL hash (`#<key>=<index>`), opt-in.
56
- * The initial hash is honored once on mount.
56
+ * The initial hash is honored once on mount. Only this key is written —
57
+ * other `&`-separated hash parts (anchors, other players' keys) are kept.
57
58
  */
58
59
  syncHash?: string;
59
60
  /**
@@ -62,6 +63,30 @@ interface StepPlayerOptions {
62
63
  * reset/new timeline) — the next full playthrough fires it again.
63
64
  */
64
65
  onEnd?: () => void;
66
+ /**
67
+ * Honor `prefers-reduced-motion: reduce` by not auto-advancing: with
68
+ * `autoPlay`, the player still lands on frame 0 but stays paused until the
69
+ * user presses play. Default true.
70
+ */
71
+ respectReducedMotion?: boolean;
72
+ /**
73
+ * Checkpoints: when it returns true for the frame playback just reached,
74
+ * the built-in timer pauses there and reports `held` until `release()` —
75
+ * "stop and predict what happens next". Manual stepping and seeks are never
76
+ * held (the reader keeps control). Ignored when a custom `engine` drives
77
+ * playback. Each checkpoint holds once per playthrough.
78
+ */
79
+ holdAt?: (frame: number) => boolean;
80
+ /**
81
+ * Follow the live edge of a growing timeline (a stream, a log, a game's
82
+ * move history). When frames are appended while the reader is live
83
+ * (playing, or parked on the last frame), playback continues through them
84
+ * at the normal pace. A reader who scrubbed back or paused mid-trace stays
85
+ * put — `live` turns false and `goLive()` returns them. With `autoPlay`, a
86
+ * player that mounts empty starts on the first frames. Checkpoints still
87
+ * hold; a custom `engine` owns its own timing and ignores this.
88
+ */
89
+ follow?: boolean;
65
90
  }
66
91
  interface StepPlayer {
67
92
  /** Total frames in the current timeline. */
@@ -80,6 +105,28 @@ interface StepPlayer {
80
105
  * not set it — `toggle()` at `ended` replays from frame 0.
81
106
  */
82
107
  ended: boolean;
108
+ /**
109
+ * The checkpoint frame playback is paused on (see `holdAt`), or null.
110
+ * Cleared by `release()` or any cursor movement.
111
+ */
112
+ held: number | null;
113
+ /** Release the current checkpoint and resume playing. No-op when not held. */
114
+ release: () => void;
115
+ /**
116
+ * Move the cursor without touching playback state: playing stays playing,
117
+ * completion and checkpoints are kept. For re-anchoring after the timeline
118
+ * changes underneath (`useTrace` with keyed steps) — user-facing moves
119
+ * should use `seek`.
120
+ */
121
+ jump: (frame: number) => void;
122
+ /**
123
+ * With `follow`: the reader is at the live edge (playing toward it, parked
124
+ * on the last frame, or waiting to start), so appended frames will play.
125
+ * Always false without `follow`.
126
+ */
127
+ live: boolean;
128
+ /** Move to the last frame (paused, so new frames play from there with `follow`). */
129
+ goLive: () => void;
83
130
  /** Jump to frame 0 and play. */
84
131
  start: () => void;
85
132
  /** Clear the player (frame = -1, paused). */
@@ -94,7 +141,358 @@ interface StepPlayer {
94
141
  setPlaying: (playing: boolean) => void;
95
142
  setSpeed: (speed: number) => void;
96
143
  }
97
- declare function useStepPlayer({ total, baseDelayMs, autoPlay, engine, syncHash, onEnd }: StepPlayerOptions): StepPlayer;
144
+ declare function useStepPlayer({ total, baseDelayMs, autoPlay, engine, syncHash, onEnd, respectReducedMotion, holdAt, follow, }: StepPlayerOptions): StepPlayer;
145
+ /** Value of `key` in the hash, or null when absent. */
146
+ declare function readHashParam(hash: string, key: string): string | null;
147
+ /** The hash with `key` set to `value` — replaced in place, else appended. */
148
+ declare function writeHashParam(hash: string, key: string, value: string): string;
149
+
150
+ interface TraceOptions<S extends StepwiseStep> extends Omit<StepPlayerOptions, "total" | "holdAt"> {
151
+ /**
152
+ * Checkpoints (see `StepPlayerOptions.holdAt`), with the step at hand:
153
+ * `holdAt: (_, step) => step?.kind === "compare"`. Always sees the current
154
+ * trace, also after it's recomputed.
155
+ */
156
+ holdAt?: (frame: number, step: S | undefined) => boolean;
157
+ /**
158
+ * Step identity used to keep the playhead in place when `steps` changes
159
+ * (see `reanchor`). Defaults to each step's `key` field. Traces without
160
+ * keys keep the old behavior: the same index, clamped.
161
+ */
162
+ keyOf?: (step: S, index: number) => string | undefined;
163
+ }
164
+ interface TracePlayer<S extends StepwiseStep> {
165
+ /** The underlying player — pass it to PlayerBar. */
166
+ player: StepPlayer;
167
+ /**
168
+ * The frame to render. Never undefined for a non-empty trace: before the
169
+ * player starts (`player.index === -1`) this is frame 0, not the last one.
170
+ */
171
+ step: S | undefined;
172
+ /** Index of `step` in the trace (0 before start; -1 for an empty trace). */
173
+ frame: number;
174
+ /** Stable `toneAt` for PlayerBar / StepScrubber. */
175
+ toneAt: (i: number) => StepTone;
176
+ /** Stable `titleAt` for PlayerBar / StepScrubber. */
177
+ titleAt: (i: number) => string;
178
+ }
179
+ /**
180
+ * A player bound to a concrete trace: the steps array is the timeline, and
181
+ * the hook resolves the frame to render (frame 0 before start — invariant
182
+ * "index -1 means not started"), plus scrubber accessors that read the
183
+ * latest steps through a ref. Replaces the per-page
184
+ * `player.index >= 0 ? … : 0` boilerplate.
185
+ *
186
+ * const trace = useTrace(STEPS, { autoPlay: true });
187
+ * <Viz arr={trace.step.arr} />
188
+ * <PlayerBar player={trace.player} toneAt={trace.toneAt} titleAt={trace.titleAt} />
189
+ *
190
+ * `steps` may change (new input, new seed — see `useSimulation`). Keyed
191
+ * steps re-anchor: the playhead stays on the same logical step, and playback
192
+ * state is untouched.
193
+ */
194
+ declare function useTrace<S extends StepwiseStep>(steps: readonly S[], opts?: TraceOptions<S>): TracePlayer<S>;
195
+
196
+ /**
197
+ * Where should the playhead go when the trace changes underneath it?
198
+ *
199
+ * Walk back from the old position to the most recent step whose key still
200
+ * exists in the new trace, and land on that step there. So editing an input
201
+ * keeps you on "the same moment" of the algorithm when it still happens, and
202
+ * otherwise on the last moment both runs share — never somewhere unrelated.
203
+ * With no shared key at all, the playhead returns to frame 0.
204
+ *
205
+ * Pure: `useTrace` calls it during render, and it's exported for custom
206
+ * players. Keys come from `keyOf`, by default each step's `key` field; a
207
+ * step without a key never matches.
208
+ */
209
+ declare function reanchor<A, B>(oldSteps: readonly A[], oldIndex: number, newSteps: readonly B[], keyOf: (step: A | B, index: number) => string | undefined): number;
210
+
211
+ /** Deterministic pseudo-random source (mulberry32). */
212
+ interface Rng {
213
+ /** The seed this generator was created from (numbers pass through; strings are hashed). */
214
+ readonly seed: number;
215
+ /** Uniform float in [0, 1). */
216
+ next(): number;
217
+ /** Uniform integer in [min, max], both inclusive. */
218
+ int(min: number, max: number): number;
219
+ /** A uniformly chosen element. Throws on an empty array. */
220
+ pick<T>(items: readonly T[]): T;
221
+ /** A shuffled copy (Fisher-Yates); the input is not modified. */
222
+ shuffle<T>(items: readonly T[]): T[];
223
+ }
224
+ declare function createRng(seed?: number | string): Rng;
225
+ interface SimulationContext {
226
+ /** Seeded randomness. Use it instead of Math.random to keep runs replayable. */
227
+ rng: Rng;
228
+ }
229
+ /**
230
+ * A simulation turns an input into a trace. Write it as a generator that
231
+ * yields one step per moment worth showing (or return an array):
232
+ *
233
+ * function* bubble(input: number[]): Generator<SortStep> {
234
+ * const arr = [...input];
235
+ * for (let pass = 0; …) {
236
+ * yield { key: `p${pass}:j${j}`, tone: "neutral", label: "compare", arr: [...arr] };
237
+ * }
238
+ * }
239
+ *
240
+ * Yield snapshots (copies), not live references: every step is kept.
241
+ * Give steps a `key` so the playhead stays put when the input changes.
242
+ */
243
+ type Simulation<Input, S extends StepwiseStep> = (input: Input, ctx: SimulationContext) => Iterable<S>;
244
+ interface SimulationRunOptions {
245
+ /** Seed for `ctx.rng`. Default 0. */
246
+ seed?: number | string;
247
+ /**
248
+ * Safety cap: stop collecting after this many steps and report
249
+ * `truncated` (a non-terminating simulation shouldn't hang the page).
250
+ * Default 10 000.
251
+ */
252
+ maxSteps?: number;
253
+ }
254
+ interface SimulationResult<S> {
255
+ steps: S[];
256
+ /** True when `maxSteps` cut the run short. */
257
+ truncated: boolean;
258
+ /** What the simulation threw, if it did; `steps` holds what it yielded before. */
259
+ error: unknown;
260
+ /** The numeric seed actually used (strings are hashed). */
261
+ seed: number;
262
+ }
263
+ /** Run a simulation to completion (or the cap). Pure; safe at module scope and on the server. */
264
+ declare function runSimulation<Input, S extends StepwiseStep>(simulation: Simulation<Input, S>, input: Input, opts?: SimulationRunOptions): SimulationResult<S>;
265
+
266
+ interface SimulationOptions<S extends StepwiseStep> extends TraceOptions<S>, SimulationRunOptions {
267
+ }
268
+ interface SimulationPlayer<S extends StepwiseStep> extends TracePlayer<S> {
269
+ /** The computed trace. */
270
+ steps: readonly S[];
271
+ /** True when `maxSteps` cut the run short. */
272
+ truncated: boolean;
273
+ /** What the simulation threw, if anything (the trace holds the steps before it). */
274
+ error: unknown;
275
+ /** The numeric seed used for this run. */
276
+ seed: number;
277
+ }
278
+ /**
279
+ * A live trace: run `simulation` on `input` and play the result. Change the
280
+ * input (or the seed) and the trace is recomputed; keyed steps keep the
281
+ * playhead on the same logical moment, and a playing player keeps playing.
282
+ *
283
+ * const [input, setInput] = useState([5, 2, 8, 1]);
284
+ * const sim = useSimulation(bubbleSort, input, { autoPlay: true, syncHash: "frame" });
285
+ * <Bars arr={sim.step!.arr} />
286
+ * <PlayerBar player={sim.player} toneAt={sim.toneAt} titleAt={sim.titleAt} />
287
+ *
288
+ * The trace is recomputed when `simulation`, `input`, `seed` or `maxSteps`
289
+ * change identity: define the simulation at module scope, and keep object
290
+ * inputs in state (or memoize them) rather than rebuilding them per render.
291
+ */
292
+ declare function useSimulation<Input, S extends StepwiseStep>(simulation: Simulation<Input, S>, input: Input, opts?: SimulationOptions<S>): SimulationPlayer<S>;
293
+
294
+ interface TraceBuilder<Data extends object> {
295
+ /** Append one frame. Returns the builder, so calls chain. */
296
+ step(step: StepwiseStep<Data>): TraceBuilder<Data>;
297
+ /** Frames appended so far. */
298
+ readonly length: number;
299
+ /** The most recently appended frame (handy for "carry the state forward"). */
300
+ last(): StepwiseStep<Data> | undefined;
301
+ /** Snapshot the trace. A fresh array each call — the builder stays usable. */
302
+ build(): StepwiseStep<Data>[];
303
+ }
304
+ /**
305
+ * Typed trace builder. Authors run their algorithm at module scope and record
306
+ * frames as they go — no IIFE, no pre-declared `const STEPS = []` that a
307
+ * component might read before it is filled (invariant 6):
308
+ *
309
+ * const t = createTrace<{ arr: number[] }>();
310
+ * for (…) t.step({ tone: "write", label: "swap", arr: [...arr] });
311
+ * export const STEPS = t.build();
312
+ *
313
+ * Pure data — no React, safe on the server.
314
+ */
315
+ declare function createTrace<Data extends object = Record<never, never>>(): TraceBuilder<Data>;
316
+
317
+ /** Named traces to play side by side: `{ bubble: stepsA, insertion: stepsB }`. */
318
+ type Tracks = Record<string, readonly StepwiseStep<object>[]>;
319
+ /**
320
+ * Sync-point key for a step. Steps with the same key in two or more tracks
321
+ * land on the same aligned frame; `null`/`undefined` marks a free step. The
322
+ * n-th occurrence of a key in one track pairs with the n-th in another.
323
+ */
324
+ type TrackKeyFn<T extends Tracks = Tracks> = (step: T[keyof T][number], index: number, track: keyof T & string) => string | null | undefined;
325
+ /** One track's slot in one aligned frame. */
326
+ interface AlignedCell {
327
+ /** Index into the track's own steps; -1 only for an empty track. */
328
+ index: number;
329
+ /**
330
+ * The track did not advance on this frame: it is holding its last frame
331
+ * (finished, or waiting for a sync point), or waiting at frame 0 for its
332
+ * first sync point.
333
+ */
334
+ held: boolean;
335
+ }
336
+ type AlignedRow<K extends string = string> = Record<K, AlignedCell>;
337
+ /**
338
+ * Align several traces into one timeline. Pure — use it directly for server
339
+ * rendering or tests; `useSyncedTracks` memoizes it per track identity.
340
+ *
341
+ * - No `key` (hold): frame i shows step i of every track; a shorter track
342
+ * holds its last frame once it runs out. Length = the longest track.
343
+ * - With `key`: tracks advance together frame by frame, but a track whose
344
+ * next step is a sync point holds until every other track that shares the
345
+ * point has reached it, so matching steps share a frame. Keys must occur
346
+ * in the same relative order across tracks; where they conflict, that
347
+ * frame degrades to lockstep instead of deadlocking.
348
+ */
349
+ declare function alignTracks<T extends Tracks>(tracks: T, key?: TrackKeyFn<T>): AlignedRow<keyof T & string>[];
350
+ interface TrackView<S> {
351
+ /** The step to render for this track (undefined only for an empty track). */
352
+ step: S | undefined;
353
+ /** Index into the track's own steps; -1 for an empty track. */
354
+ index: number;
355
+ /** The track did not advance on this frame (see `AlignedCell.held`). */
356
+ held: boolean;
357
+ /** The track is on its final step. */
358
+ done: boolean;
359
+ /** Length of the track's own trace. */
360
+ length: number;
361
+ }
362
+ interface SyncedTracksOptions<T extends Tracks = Tracks> extends Omit<StepPlayerOptions, "total"> {
363
+ /**
364
+ * Sync-point alignment (see `alignTracks`). Omit to hold shorter tracks on
365
+ * their last frame. Read when the tracks change — pass the same tracks to
366
+ * keep the alignment memoized; an inline function is fine.
367
+ */
368
+ key?: TrackKeyFn<T>;
369
+ }
370
+ interface SyncedTracks<T extends Tracks> {
371
+ /** One player over the aligned timeline — pass it to PlayerBar. */
372
+ player: StepPlayer;
373
+ /** Aligned frame to render: 0 before the player starts, -1 when all tracks are empty. */
374
+ frame: number;
375
+ /** Number of aligned frames. */
376
+ total: number;
377
+ /** Per-track view of `frame`. */
378
+ tracks: {
379
+ [K in keyof T]: TrackView<T[K][number]>;
380
+ };
381
+ /** The full alignment table (stable while the tracks are). */
382
+ rows: readonly AlignedRow<keyof T & string>[];
383
+ /** Stable scrubber accessors: the most severe tone among tracks that moved. */
384
+ toneAt: (i: number) => StepTone;
385
+ /** Stable scrubber accessor: `track: label` for every track that moved. */
386
+ titleAt: (i: number) => string;
387
+ }
388
+ /**
389
+ * Comparison mode: several traces of the same input (two engines, two
390
+ * algorithms) under ONE player. The cursor walks the aligned timeline from
391
+ * `alignTracks`; each track resolves its own step, so payload types stay
392
+ * per-track. Like `useTrace`, frame 0 renders before playback starts.
393
+ *
394
+ * const cmp = useSyncedTracks({ bubble: BUBBLE, insertion: INSERTION }, { autoPlay: true });
395
+ * <Lane step={cmp.tracks.bubble.step} held={cmp.tracks.bubble.held} />
396
+ * <PlayerBar player={cmp.player} toneAt={cmp.toneAt} titleAt={cmp.titleAt} />
397
+ */
398
+ declare function useSyncedTracks<T extends Tracks>(tracks: T, opts?: SyncedTracksOptions<T>): SyncedTracks<T>;
399
+
400
+ interface LessonStreamOptions {
401
+ /** Lesson to open on (deep link). Clamped. Default 0. */
402
+ seed?: number;
403
+ /** Lessons mounted on each side of the seed initially. Default 1. */
404
+ radius?: number;
405
+ /** Load the next lesson when the last mounted one's bottom is within this many px below the viewport. Default 600. */
406
+ approachBelow?: number;
407
+ /** Load the previous lesson when the first mounted one's top is below `-approachAbove`. Default 300. */
408
+ approachAbove?: number;
409
+ /** Scroll the seed lesson into view before first paint (seed > 0). Default true. */
410
+ scrollToSeed?: boolean;
411
+ /**
412
+ * Fired when the scroll-tracked active lesson changes (not on mount) — sync
413
+ * the URL / document title here. Read through a ref; inline is fine.
414
+ */
415
+ onActiveChange?: (index: number) => void;
416
+ }
417
+ interface LessonStream {
418
+ /** Lesson containing the viewport center (the seed until the first measure). */
419
+ active: number;
420
+ /** The seed lesson — SSR'd visible; every other block should fade in. */
421
+ seed: number;
422
+ /** Indices to render, in order (the mounted window). */
423
+ mounted: number[];
424
+ /** The window reaches the last lesson — render an end-of-stream footer. */
425
+ complete: boolean;
426
+ /** Stable callback ref for lesson `i`'s block element (cached per index). */
427
+ refFor: (index: number) => (el: HTMLElement | null) => void;
428
+ /** Mount through lesson `i` (if needed) and scroll to it. */
429
+ goTo: (index: number) => void;
430
+ next: () => void;
431
+ prev: () => void;
432
+ reducedMotion: boolean;
433
+ }
434
+ /**
435
+ * Continuous reader for a sequence of lessons (a notebook): renders a growing
436
+ * window of lessons around the seed instead of all of them, and tracks the
437
+ * active one by viewport-center containment (invariant 1).
438
+ *
439
+ * - The lesson after the window mounts as the reader approaches its place;
440
+ * the one before mounts near the top, and a layout effect compensates the
441
+ * scroll so prepended content never shoves the reader down.
442
+ * - Navigation (`goTo`) expansions are NOT compensated — their scrollIntoView
443
+ * is absolute, and compensating them displaced the landing.
444
+ * - The cursor is a useSteps core, so it composes with ChapterNav
445
+ * (`active` + `goTo`) and LessonPager (`prev`/`next`).
446
+ *
447
+ * Headless: the consumer renders `mounted.map(i => <Block ref={refFor(i)} />)`
448
+ * and owns the content; pair blocks with `useFadeIn(i === seed)`.
449
+ */
450
+ declare function useLessonStream(count: number, opts?: LessonStreamOptions): LessonStream;
451
+ /**
452
+ * Mount fade for streamed blocks: `false` on the first client render, `true`
453
+ * a frame later (immediately with reduced motion). Pass `initiallyVisible`
454
+ * for server-rendered blocks (the stream's seed) so they never flash.
455
+ */
456
+ declare function useFadeIn(initiallyVisible?: boolean): boolean;
457
+
458
+ interface EvaluatorOptions {
459
+ /**
460
+ * "auto" (default) re-judges whenever `ctx` changes: a gate that opens the
461
+ * moment the reader does the thing. "manual" judges only on `check()`: a
462
+ * quiz with a Check button; changing `ctx` afterwards clears the verdict.
463
+ */
464
+ mode?: "auto" | "manual";
465
+ }
466
+ interface EvaluatorState {
467
+ /** "idle" until the first verdict (manual mode, or after `reset`). */
468
+ status: VerdictStatus | "idle";
469
+ feedback: ReactNode;
470
+ /** status === "pass". */
471
+ passed: boolean;
472
+ /** How many times `check()` ran, for "show a hint after two tries". */
473
+ attempts: number;
474
+ /** What the evaluator threw (the verdict is then "fail"). */
475
+ error: unknown;
476
+ /** Judge the current context now. Resolves with the verdict, or null if superseded. */
477
+ check: () => Promise<Verdict | null>;
478
+ /** Back to "idle", attempts to 0, any running check abandoned. */
479
+ reset: () => void;
480
+ }
481
+ /**
482
+ * Run an evaluator against a context and keep its verdict as state. Async
483
+ * evaluators report "pending" while they run; a newer check (or a context
484
+ * change) aborts the older one and drops its late result, so the verdict
485
+ * always describes the latest context. `check` and `reset` are
486
+ * referentially stable.
487
+ *
488
+ * const answer = useEvaluator(
489
+ * (guess: string | null) => (guess === truth ? pass("Right!") : fail("Look at the pivot again.")),
490
+ * guess,
491
+ * { mode: "manual" }
492
+ * );
493
+ * <button onClick={answer.check}>Check</button> {answer.feedback}
494
+ */
495
+ declare function useEvaluator<Ctx>(evaluator: Evaluator<Ctx>, ctx: Ctx, opts?: EvaluatorOptions): EvaluatorState;
98
496
 
99
497
  interface GuidedTour {
100
498
  /** Active step index, or null when the tour is not running. */
@@ -107,6 +505,11 @@ interface GuidedTour {
107
505
  * the waitHint) so the user knows what is being waited on.
108
506
  */
109
507
  blocked: boolean;
508
+ /**
509
+ * The active step's gate feedback, when its gate returns a `Verdict` with
510
+ * one ("Stage at least one file"). Pass it to `TourCard`'s `feedback`.
511
+ */
512
+ feedback: ReactNode;
110
513
  start: () => void;
111
514
  end: () => void;
112
515
  next: () => void;
@@ -124,19 +527,6 @@ interface GuidedTourOptions {
124
527
  */
125
528
  driver?: TourDriver;
126
529
  }
127
- /**
128
- * Tour state whose steps are effects over app state: `onEnter` fires whenever
129
- * the active step changes, reading the latest `steps` via ref so closures stay
130
- * fresh without re-triggering the effect on every render. Composed on
131
- * useSteps — a tour is a step sequence whose `next` past the end exits.
132
- *
133
- * On top of the linear cursor, steps may declare `when` (skip on arrival),
134
- * `gate` (block `next` until true), and `next` (edge override by step id) —
135
- * a small state machine where states are steps, edges are `next` functions,
136
- * and guards are gates. The cursor itself stays a cursor: skipped steps are
137
- * never landed on, and skipped/gated behavior is evaluated at transition
138
- * time, not on every render.
139
- */
140
530
  declare function useGuidedTour(steps: TourStepInput[], opts?: GuidedTourOptions): GuidedTour;
141
531
 
142
532
  interface FlatChapter {
@@ -350,7 +740,7 @@ declare function StepScrubber({ count, index, toneAt, titleAt, onSeek, emptyHint
350
740
  */
351
741
  groupOf?: (i: number) => number;
352
742
  }): react.JSX.Element;
353
- declare function PlayerBar({ player, toneAt, titleAt, leading, onEmpty, emptyHint, className, theme, groupOf, labels, }: {
743
+ declare function PlayerBar({ player, toneAt, titleAt, leading, onEmpty, emptyHint, className, theme, groupOf, labels, live, }: {
354
744
  player: {
355
745
  total: number;
356
746
  index: number;
@@ -360,10 +750,18 @@ declare function PlayerBar({ player, toneAt, titleAt, leading, onEmpty, emptyHin
360
750
  prev: () => void;
361
751
  toggle: (onEmpty?: () => void) => void;
362
752
  seek: (frame: number) => void;
753
+ live?: boolean;
754
+ goLive?: () => void;
363
755
  };
364
756
  toneAt?: (i: number) => StepTone;
365
757
  titleAt?: (i: number) => string;
366
758
  leading?: ReactNode;
759
+ /**
760
+ * Show the live-edge badge for a following player (`follow` option):
761
+ * "Live" at the edge, "Go live" with the frame count when the reader is
762
+ * behind.
763
+ */
764
+ live?: boolean;
367
765
  /** Fired when the user plays/steps with no timeline loaded. */
368
766
  onEmpty?: () => void;
369
767
  emptyHint?: string;
@@ -380,6 +778,8 @@ declare function PlayerBar({ player, toneAt, titleAt, leading, onEmpty, emptyHin
380
778
  pause?: string;
381
779
  replay?: string;
382
780
  next?: string;
781
+ live?: string;
782
+ goLive?: string;
383
783
  };
384
784
  }): react.JSX.Element;
385
785
  declare function ChapterNav({ chapters, active, onNavigate, hint, className, theme, }: {
@@ -410,7 +810,7 @@ declare function TourSpotlight({ target, theme, }: {
410
810
  target: string | null;
411
811
  theme?: StepwiseTheme;
412
812
  }): react.JSX.Element | null;
413
- declare function TourCard({ step, index, total, onNext, onBack, onEnd, labels, className, theme, nextDisabled, }: {
813
+ declare function TourCard({ step, index, total, onNext, onBack, onEnd, labels, className, theme, nextDisabled, feedback, }: {
414
814
  step: TourStepInput;
415
815
  index: number;
416
816
  total: number;
@@ -428,6 +828,8 @@ declare function TourCard({ step, index, total, onNext, onBack, onEnd, labels, c
428
828
  theme?: StepwiseTheme;
429
829
  /** Disable the primary (Next/Finish) button — pair with tour.blocked. */
430
830
  nextDisabled?: boolean;
831
+ /** Gate feedback ("Stage a file first") — pair with tour.feedback. Announced politely. */
832
+ feedback?: ReactNode;
431
833
  }): react.JSX.Element;
432
834
  declare function ShortcutsDialog({ open, onClose, items, title, className, theme, }: {
433
835
  open: boolean;
@@ -438,4 +840,4 @@ declare function ShortcutsDialog({ open, onClose, items, title, className, theme
438
840
  theme?: StepwiseTheme;
439
841
  }): react.ReactPortal | null;
440
842
 
441
- export { Chapter, ChapterNav, type ChaptersApi, CodeBlock, type Discoveries, type DiscoveriesOptions, type DiscoveryStore, type FlatChapter, type GlobalKeysOptions, GlossaryAppendix, type GlossaryEntry, GlossaryProvider, type GuidedTour, type GuidedTourOptions, type KeyMap, LessonPager, PlayerBar, PlayerEngine, ShortcutItem, ShortcutsDialog, type StepPlayer, type StepPlayerOptions, StepScrubber, StepTone, type StepsCore, type StepwiseTheme, TONE_CHIP_CLASS, TONE_SEGMENT_CLASS, Term, TourCard, TourDriver, TourSpotlight, TourStepInput, type UseStepsOptions, cookieStore, defaultTheme, flattenChapters, localStorageStore, memoryStore, useChapters, useDiscoveries, useFocusTrap, useGlobalKeys, useGuidedTour, usePrefersReducedMotion, useStepPlayer, useSteps };
843
+ export { type AlignedCell, type AlignedRow, Chapter, ChapterNav, type ChaptersApi, CodeBlock, type Discoveries, type DiscoveriesOptions, type DiscoveryStore, Evaluator, type EvaluatorOptions, type EvaluatorState, type FlatChapter, type GlobalKeysOptions, GlossaryAppendix, type GlossaryEntry, GlossaryProvider, type GuidedTour, type GuidedTourOptions, type KeyMap, LessonPager, type LessonStream, type LessonStreamOptions, PlayerBar, PlayerEngine, type Rng, ShortcutItem, ShortcutsDialog, type Simulation, type SimulationContext, type SimulationOptions, type SimulationPlayer, type SimulationResult, type SimulationRunOptions, type StepPlayer, type StepPlayerOptions, StepScrubber, StepTone, type StepsCore, StepwiseStep, type StepwiseTheme, type SyncedTracks, type SyncedTracksOptions, TONE_CHIP_CLASS, TONE_SEGMENT_CLASS, Term, TourCard, TourDriver, TourSpotlight, TourStepInput, type TraceBuilder, type TraceOptions, type TracePlayer, type TrackKeyFn, type TrackView, type Tracks, type UseStepsOptions, Verdict, VerdictStatus, alignTracks, cookieStore, createRng, createTrace, defaultTheme, flattenChapters, localStorageStore, memoryStore, readHashParam, reanchor, runSimulation, useChapters, useDiscoveries, useEvaluator, useFadeIn, useFocusTrap, useGlobalKeys, useGuidedTour, useLessonStream, usePrefersReducedMotion, useSimulation, useStepPlayer, useSteps, useSyncedTracks, useTrace, writeHashParam };