Everything exported from the package root, grouped by layer. Hooks are headless and SSR-safe; the callbacks they return are referentially stable. Adapters live on subpaths so their optional peers never become required imports.
Policies
Hooks that drive the cursor from a timer, a tour, the scroll position or a ledger.
useStepPlayer(opts: StepPlayerOptions): StepPlayerPlayer policy: timer, speed, seek, optional #hash deep link and an engine port for GSAP or scroll. index === -1 means not started.
StepPlayerOptions
| Name | Type | Default | Description |
|---|
| total | number | - | Frames in the current timeline (0 = no timeline loaded). Required. |
| baseDelayMs | number | 900 | Delay per frame at 1x speed (built-in timer engine). |
| autoPlay | boolean | false | Start playing immediately when a timeline exists on first render. |
| engine | PlayerEngine | - | Alternate frame source replacing the timer. Memoize per timeline - identity changes reconnect it. |
| syncHash | string | - | Deep-link the frame into the URL hash as #<key>=<index>. Other hash parts are kept, including a bare #anchor, which becomes #anchor&frame=3: the page still works, but a shared copy of that URL no longer scrolls to the anchor. Read on mount and on hashchange. |
| onEnd | () => void | - | Fired once when playback runs to the final frame on its own. Cleared by any cursor movement. |
| respectReducedMotion | boolean | true | With autoPlay, land on frame 0 but stay paused under prefers-reduced-motion. |
| holdAt | (frame: number) => boolean | - | Checkpoints: autoplay pauses on these frames and reports held. It doesn't resume on its own: call release() (a Continue button) or press play. Manual steps and seeks are never held; each checkpoint holds once per playthrough. |
| follow | boolean | false | Follow the live edge of a growing timeline: when frames are appended while the reader is live (playing, or parked on the last frame), playback continues into them. A reader who scrubbed back or paused mid-trace stays put. With autoPlay, a player that mounts empty starts on the first frames. Checkpoints still hold; ignored with an engine. |
StepPlayer (returned)
| Name | Type | Default | Description |
|---|
| total | number | - | Total frames in the current timeline. |
| frame | number | - | Raw frame counter; -1 means no timeline / before the first frame. |
| index | number | - | Frame clamped into [0, total-1]; -1 when there is nothing to show. |
| playing / speed | boolean / number | - | Playback state. |
| atEnd | boolean | - | Parked on the final frame (a seek counts too). |
| ended | boolean | - | Playback ran to the end on its own and nothing has moved since. toggle() at ended replays. |
| held | number | null | - | The checkpoint frame playback is paused on (see holdAt). |
| release | () => void | - | Release the checkpoint and resume playing. |
| jump | (frame: number) => void | - | Move the cursor without touching play state - for re-anchoring after the timeline changes. User-facing moves should seek. |
| live | boolean | - | With follow: the reader is at the live edge, so appended frames will play. Always false without follow. |
| goLive | () => void | - | Move to the last frame (paused); with follow, new frames play from there. |
| start | () => void | - | Jump to frame 0 and play. |
| reset | () => void | - | Clear the player (frame = -1, paused). |
| seek | (frame: number) => void | - | Jump to a frame. |
| next | (onEmpty?: () => void) => void | - | Step forward; pauses first. onEmpty fires when there is no timeline. |
| prev | () => void | - | Step backward; pauses first. |
| toggle | (onEmpty?: () => void) => void | - | Play/pause; replays from 0 at the end. |
| setPlaying / setSpeed | (v) => void | - | Direct setters. |
useTrace<S extends StepwiseStep>(steps: readonly S[], opts?: TraceOptions<S>): TracePlayer<S>A player bound to a typed trace. Resolves the frame to render (frame 0 before start) and returns stable scrubber accessors. steps may change: keyed steps re-anchor, so the playhead stays on the same logical step and playback continues.
TraceOptions<S> (plus every StepPlayerOptions field except total)
| Name | Type | Default | Description |
|---|
| keyOf | (step: S, index: number) => string | undefined | step.key | Step identity for re-anchoring when steps changes. Unkeyed traces keep the same index, clamped. |
| holdAt | (frame: number, step: S | undefined) => boolean | - | Checkpoints, with the step from the current trace. |
TracePlayer<S> (returned)
| Name | Type | Default | Description |
|---|
| player | StepPlayer | - | The underlying player - pass it to PlayerBar. |
| step | S | undefined | - | The frame to render. Frame 0 before the player starts; undefined only for an empty trace. |
| frame | number | - | Index of step in the trace (0 before start; -1 for an empty trace). |
| toneAt | (i: number) => StepTone | - | Stable accessor for PlayerBar / StepScrubber. |
| titleAt | (i: number) => string | - | Stable accessor for PlayerBar / StepScrubber. |
useSyncedTracks<T extends Tracks>(tracks: T, opts?: SyncedTracksOptions<T>): SyncedTracks<T>Comparison mode: several traces of the same input under one player. Shorter tracks hold their last frame, or steps align on shared sync keys. alignTracks is the pure alignment, usable on the server.
SyncedTracksOptions (plus every StepPlayerOptions field except total)
| Name | Type | Default | Description |
|---|
| key | (step, index, track) => string | null | undefined | - | Sync-point key. Matching keys share a frame; the n-th occurrence pairs with the n-th. Omit to hold shorter tracks. |
SyncedTracks<T> (returned)
| Name | Type | Default | Description |
|---|
| player | StepPlayer | - | One player over the aligned timeline - pass it to PlayerBar. |
| frame / total | number | - | Aligned frame to render (0 before start, -1 when all tracks are empty) and frame count. |
| tracks | { [K]: TrackView } | - | Per track: step, index, held (did not advance this frame), done, length. |
| rows | readonly AlignedRow[] | - | The full alignment table. |
| toneAt / titleAt | (i: number) => ... | - | Stable scrubber accessors: most severe tone, and track: label for every track that moved. |
createTrace<Data>(): TraceBuilder<Data>Typed trace builder: run your algorithm at module scope and record frames as you go. Pure data - no React, safe on the server.
TraceBuilder<Data>
| Name | Type | Default | Description |
|---|
| step | (step: StepwiseStep<Data>) => TraceBuilder<Data> | - | Append a frame; chains. |
| length | number | - | Frames appended so far. |
| last | () => StepwiseStep<Data> | undefined | - | Most recent frame. |
| build | () => StepwiseStep<Data>[] | - | Snapshot - a fresh array each call. |
useSimulation<Input, S extends StepwiseStep>(simulation: Simulation<Input, S>, input: Input, opts?: SimulationOptions<S>): SimulationPlayer<S>A live trace: runs a generator on the input and plays the result. Change the input or seed and the trace is recomputed while keyed steps keep the playhead in place. Define the simulation at module scope, and keep object inputs in state or useMemo: an inline { arr, target } is a new object every render, so the trace would be recomputed every render (development builds warn about it).
SimulationOptions<S> (plus every TraceOptions field)
| Name | Type | Default | Description |
|---|
| seed | number | string | 0 | Seed for ctx.rng. Same simulation + input + seed = same trace. |
| maxSteps | number | 10000 | Stop collecting after this many steps and report truncated. |
| syncInput | SyncInputOptions<Input> | - | Keep the input in the URL next to the frame, so a link replays the exact run (see below). Since 0.4. |
SyncInputOptions<Input>
| Name | Type | Default | Description |
|---|
| key | string | - | Hash key for the input (#in=...). Must differ from syncHash. |
| serialize | (input: Input) => string | - | Input to a URL-safe string ([5, 2, 8] to "5.2.8"). |
| parse | (value: string) => Input | null | - | Back to an input; return null to ignore a malformed or hostile value. |
| onRestore | (input: Input) => void | - | Gets the linked input once on mount; pass your setInput. The linked syncHash frame is then applied to the trace of that input, not the default one. Edits are written back to the URL. |
SimulationPlayer<S> (returned: every TracePlayer field, plus)
| Name | Type | Default | Description |
|---|
| steps | readonly S[] | - | The computed trace. |
| truncated | boolean | - | maxSteps cut the run short. |
| error | unknown | - | What the simulation threw; steps holds what it yielded before. |
| seed | number | - | Numeric seed used (strings are hashed). |
runSimulation(simulation, input, opts?: { seed?, maxSteps? }): SimulationResult<S> · createRng(seed?: number | string): RngThe pure pieces: run a simulation to completion (safe at module scope and on the server), and a seeded mulberry32 generator - the only randomness a simulation should use, so runs replay exactly.
Simulation<Input, S>
| Name | Type | Default | Description |
|---|
| (input, ctx) => Iterable<S> | generator or array | - | Yield one step per moment worth showing. Yield snapshots (copies), and give steps a key. |
| ctx.rng | Rng | - | Seeded randomness: next(), int(min, max), pick(items), shuffle(items), seed. |
reanchor
reanchor(oldSteps, oldIndex, newSteps, keyOf): numberThe re-anchoring rule useTrace applies: walk back from the old position to the latest step whose key both traces share, and land on it. No shared key returns 0; an empty trace returns -1. Pure - use it in custom players.
useGuidedTour(steps: TourStepInput[], opts?: GuidedTourOptions): GuidedTourTour policy: onEnter effects over your state, conditional steps (when), guards (gate), branching edges (next) and an optional imperative driver.
TourStepInput
| Name | Type | Default | Description |
|---|
| id | string | - | Stable id for gotoId and next edges. |
| title / body | string / ReactNode | - | Card content. |
| target | string | - | Element to spotlight: DOM id first, then [data-tour="..."]. |
| onEnter | () => void | - | Effect run when the step becomes active - drive your app state here. |
| waitHint | string | - | Pulsing hint shown on the card. |
| when | () => boolean | - | Conditional step: false skips it in the direction of travel. Evaluated on arrival. |
| gate | () => boolean | Verdict | - | Guard: until it passes, next() is a no-op and the tour reports blocked. Return a Verdict to explain what's missing (tour.feedback). |
| next | () => string | null | - | Edge override: advance to this step id instead of index+1. |
GuidedTourOptions
| Name | Type | Default | Description |
|---|
| driver | TourDriver | - | Imperative presenter (driver.js, Shepherd...). When set, don't render TourSpotlight/TourCard. |
GuidedTour (returned)
| Name | Type | Default | Description |
|---|
| index | number | null | - | Active step index, or null when the tour is not running. |
| current | TourStepInput | null | - | The active step. |
| active | boolean | - | Whether the tour is running. |
| blocked | boolean | - | The active step's gate is closed; pair with TourCard nextDisabled. |
| feedback | ReactNode | - | Feedback from the active gate's Verdict; pair with TourCard feedback. |
| start / end | () => void | - | Run from the first eligible step / stop. |
| next / back | () => void | - | Move along edges, honoring gates and skips. |
| goto | (index: number) => void | - | Programmatic jump (ignores gates). |
| gotoId | (id: string) => void | - | Jump to a step by id. No-op when unknown. |
useChapters(chapters: Chapter[]): ChaptersApiScroll-tracked chapters. Active = the section containing the viewport center. Sections are resolved by DOM id; give them scroll-mt to clear sticky headers.
Chapter
| Name | Type | Default | Description |
|---|
| id | string | - | DOM id of the chapter's section. |
| label / hint | string | - | Nav label and tooltip. |
| children | Chapter[] | - | Nested sub-chapters, any depth. |
| numbered | boolean | true | Show the ordinal chip in ChapterNav. |
ChaptersApi (returned)
| Name | Type | Default | Description |
|---|
| active | number | - | Index of the chapter in view (flattened order). |
| goTo | (index: number) => void | - | Smooth-scroll to a chapter. |
| next / prev | () => void | - | Neighbouring chapter. |
useLessonStream / useFadeIn
useLessonStream(count: number, opts?: LessonStreamOptions): LessonStreamHeadless notebook reader: mounts a window of lessons around the seed, loads neighbours on approach, compensates the scroll for content prepended above, and tracks the active lesson by viewport center. Composes with ChapterNav and LessonPager. useFadeIn(initiallyVisible) fades streamed blocks in.
LessonStreamOptions
| Name | Type | Default | Description |
|---|
| seed | number | 0 | Lesson to open on (deep link). Clamped. |
| radius | number | 1 | Lessons mounted on each side of the seed initially. |
| approachBelow / approachAbove | number | 600 / 300 | Pixel thresholds for loading the next / previous lesson. |
| scrollToSeed | boolean | true | Scroll the seed into view before first paint. |
| onActiveChange | (index: number) => void | - | Scroll-tracked active lesson changed - sync the URL or title here. |
LessonStream (returned)
| Name | Type | Default | Description |
|---|
| active / seed | number | - | Lesson at the viewport center; the seed lesson. |
| mounted | number[] | - | Indices to render, in order. |
| complete | boolean | - | The window reaches the last lesson. |
| refFor | (i: number) => RefCallback | - | Stable ref for lesson i's block. |
| goTo / next / prev | ... | - | Mount through a lesson and scroll to it. |
| reducedMotion | boolean | - | The user prefers reduced motion. |
useDiscoveries(opts?: DiscoveriesOptions): DiscoveriesA persisted set of discovered ids, hydrated after mount. Pair with useGuidedTour for show-once feature tutorials.
DiscoveriesOptions
| Name | Type | Default | Description |
|---|
| store | DiscoveryStore | localStorageStore() | Storage seam: localStorageStore, cookieStore, memoryStore, or your own { read, write }. |
| key | string | "stepwise.discoveries" | Storage key used by the built-in stores. |
Discoveries (returned)
| Name | Type | Default | Description |
|---|
| discovered | Set<string> | - | Discovered ids. Empty until hydration. |
| ready | boolean | - | False during SSR and before the first client pass - gate auto-starts on it. |
| isDiscovered | (id: string) => boolean | - | Ref-backed check for event handlers. |
| markDiscovered | (id: string | string[]) => void | - | Record ids and persist the merged set. |
| reset | () => void | - | Forget everything. |
useGlobalKeys(map: KeyMap, opts?: GlobalKeysOptions): voidGlobal keyboard bindings matched on event.key (exact, then lowercase). Handlers fire with preventDefault; Escape always fires.
GlobalKeysOptions
| Name | Type | Default | Description |
|---|
| ignoreTags | string[] | INPUT, TEXTAREA, SELECT, BUTTON | Tags that swallow keys (except Escape). |
| enabled | boolean | (() => boolean) | true | Gate, checked per event. |
useFocusTrap
useFocusTrap(active: boolean): RefObject<HTMLDivElement | null>Traps Tab inside the ref'd container while active, focuses it on activation and restores focus on release. Attach to a tabIndex={-1} element.
usePrefersReducedMotion
usePrefersReducedMotion(): booleanSSR-safe prefers-reduced-motion query (false on the server, corrected after mount).
Evaluators
One verdict shape for every "did the reader get it?" question: tour gates, player checkpoints, exercises. Booleans are accepted everywhere.
useEvaluator<Ctx>(evaluator: Evaluator<Ctx>, ctx: Ctx, opts?: { mode?: "auto" | "manual" }): EvaluatorStateKeeps an evaluator's verdict as state. Auto re-judges on every context change (gates). Manual judges only on check() (quizzes); when ctx changes identity afterwards the verdict goes back to idle, but attempts are kept (same question), so call reset() when the question itself changes, e.g. in an effect on player.held. Async checks report pending, abort superseded runs and drop late results.
EvaluatorState (returned)
| Name | Type | Default | Description |
|---|
| status | "idle" | "pass" | "fail" | "pending" | - | idle until the first verdict. |
| feedback | ReactNode | - | The verdict's feedback. |
| passed | boolean | - | status === "pass". |
| attempts | number | - | check() calls so far - for hints after a few tries. |
| error | unknown | - | What the evaluator threw (status is then fail). |
| check | () => Promise<Verdict | null> | - | Judge now; resolves null if superseded. Stable. |
| reset | () => void | - | Back to idle, attempts to 0. Stable. |
Evaluator / Verdict / pass / fail / pending
type Evaluator<Ctx> = (ctx: Ctx, signal: AbortSignal) => boolean | Verdict | Promise<boolean | Verdict>A Verdict is { status: "pass" | "fail" | "pending", feedback? }. pass(), fail() and pending() build one; toVerdict() normalizes a boolean. All are exported from the package root. The signal aborts when the result is no longer wanted. Grading a checkpoint: the truth is usually the next frame (sim.steps[player.held + 1]), so phrase the question so every possible next frame is an answer option.
Components
Optional styled presentation. Every component takes a theme; the hooks work without them.
<PlayerBar player={...} toneAt={...} titleAt={...} />Transport controls plus a tone-colored scrubber for a StepPlayer.
Props
| Name | Type | Default | Description |
|---|
| player | StepPlayer (subset) | - | total, index, playing, atEnd, next, prev, toggle, seek (+ live, goLive for the live badge). |
| toneAt / titleAt | (i) => StepTone / string | - | Segment color and tooltip per frame. |
| leading | ReactNode | - | Content before the controls. |
| onEmpty | () => void | - | Fired when the user plays/steps with no timeline. |
| emptyHint | string | - | Scrubber text when there are no frames. |
| groupOf | (i: number) => number | - | Group consecutive segments (see StepScrubber). |
| live | boolean | - | Show the live-edge badge for a following player: Live at the edge, Go live +N when the reader is behind. |
| labels | { previous, play, pause, replay, next, live, goLive } | - | Button labels / aria. |
| theme | StepwiseTheme | defaultTheme | Styling tokens. Pass your own object to restyle without forking. |
| className | string | - | Extra classes, merged last (tailwind-merge). |
<StepScrubber count={n} index={i} onSeek={go} />Keyboard-accessible slider of tone-colored segments, usable with any cursor.
Props
| Name | Type | Default | Description |
|---|
| count / index | number | - | Segment count and the active segment. |
| onSeek | (i: number) => void | - | Called on click or arrow keys. Required. |
| toneAt / titleAt | (i) => StepTone / string | - | Per-segment color and title. |
| groupOf | (i: number) => number | - | Group contiguous segments into one proportional span - a chapter fills as its children pass. |
| emptyHint | string | - | Shown when count is 0. |
| labels | { scrubber?, frame?(i) } | - | aria-label and aria-valuetext. |
| theme | StepwiseTheme | defaultTheme | Styling tokens. Pass your own object to restyle without forking. |
| className | string | - | Extra classes, merged last (tailwind-merge). |
<ChapterNav chapters={CHAPTERS} active={c.active} onNavigate={c.goTo} />Recursive chapter navigation; sticky by default. Sub-chapters render inline after their parent.
Props
| Name | Type | Default | Description |
|---|
| chapters | Chapter[] | - | The same tree passed to useChapters. |
| active | number | - | Flattened active index. |
| onNavigate | (i: number) => void | - | Called with the flattened index. |
| hint | string | - | Right-aligned hint line (md and up). |
| theme | StepwiseTheme | defaultTheme | Styling tokens. Pass your own object to restyle without forking. |
| className | string | - | Extra classes, merged last (tailwind-merge). |
<TourSpotlight target={tour.current?.target ?? null} />Dims the page around the target, scrolls it into view once and tracks it every frame. Waits for targets that render late.
Props
| Name | Type | Default | Description |
|---|
| target | string | null | - | DOM id, then [data-tour] value. null hides the spotlight. |
| theme | StepwiseTheme | defaultTheme | Styling tokens. Pass your own object to restyle without forking. |
<TourCard step={tour.current} index={tour.index} total={n} onNext onBack onEnd />Focus-trapped dialog narrating the active tour step, fixed to the bottom of the viewport.
Props
| Name | Type | Default | Description |
|---|
| step | TourStepInput | - | The active step. |
| index / total | number | - | Position, shown in the header. |
| onNext / onBack / onEnd | () => void | - | Wire to tour.next, tour.back, tour.end. |
| nextDisabled | boolean | false | Disable Next/Finish - pair with tour.blocked. |
| feedback | ReactNode | - | Gate feedback, announced politely - pair with tour.feedback. |
| labels | { title, back, next, finish, end } | - | Text overrides. |
| theme | StepwiseTheme | defaultTheme | Styling tokens. Pass your own object to restyle without forking. |
| className | string | - | Extra classes, merged last (tailwind-merge). |
ShortcutsDialog
<ShortcutsDialog open={open} onClose={close} items={SHORTCUTS} />Modal list of keyboard shortcuts with kbd chips; closes on Escape or backdrop click.
Props
| Name | Type | Default | Description |
|---|
| open | boolean | - | Visibility. |
| onClose | () => void | - | Called on Escape, backdrop or close button. |
| items | ShortcutItem[] | - | { keys, action } pairs. |
| title | string | - | Dialog heading. |
| theme | StepwiseTheme | defaultTheme | Styling tokens. Pass your own object to restyle without forking. |
| className | string | - | Extra classes, merged last (tailwind-merge). |
<CodeBlock lines={["const x = 1", "// comment"]} />Monospace lines with // comments dimmed. This site's View source panels use it.
Props
| Name | Type | Default | Description |
|---|
| lines | readonly string[] | [] | One entry per line. Optional, so a step's optional code passes as is: <CodeBlock lines={step.code} />. |
| theme | StepwiseTheme | defaultTheme | Styling tokens. Pass your own object to restyle without forking. |
| className | string | - | Extra classes, merged last (tailwind-merge). |
Utilities, theme and ports
Helpers, the theme contract, and the inversion points adapters plug into.
flattenChapters
flattenChapters(chapters: Chapter[]): FlatChapter[]Depth-first flattening - reading order for prev/next and scroll tracking.
FlatChapter
| Name | Type | Default | Description |
|---|
| chapter | Chapter | - | The original node. |
| depth | number | - | 0 for top level. |
| rootIndex | number | - | Flat index of the top-level ancestor - use as StepScrubber groupOf. |
| parentIndex | number | - | Flat index of the parent; -1 at depth 0. |
readHashParam(hash: string, key: string): string | null
writeHashParam(hash: string, key: string, value: string): stringThe fragment format syncHash and syncInput use: &-separated parts, key=value parts are params, anything else (a bare #anchor) is kept verbatim. writeHashParam replaces the key in place or appends it, and returns the new hash; apply it with history.replaceState. For a simulation's input, prefer useSimulation's syncInput.
TONE_CHIP_CLASS[step.tone] // "text-accent border-accent/40 ..."The default theme's per-tone classes as plain records, for your own chips and badges that should match the scrubber. Styled by styles.css or your Tailwind build like the components.
localStorageStore / cookieStore / memoryStore
localStorageStore(key?) | cookieStore(key?) | memoryStore(seed?): DiscoveryStoreBuilt-in DiscoveryStore implementations: { read(): string[]; write(ids: string[]): void }.
const theme: StepwiseTheme = { ...defaultTheme, surface: "..." }Every non-CSS-variable class the components use. TONE_SEGMENT_CLASS and TONE_CHIP_CLASS re-export the defaults. Without Tailwind, import "@sphynxoid/stepwise/styles.css" (precompiled, scoped to [data-stepwise], --stepwise-* tokens); or copy the components in via the shadcn registry at /r/*.json.
Tokens
| Name | Type | Default | Description |
|---|
| toneSegment / toneChip | Record<StepTone, string> | - | Scrubber segment and chip colors per tone. |
| buttonBase / buttonVariants | string / Record<ghost|outline|primary, string> | - | Internal buttons. |
| surface | string | - | Panel chrome for nav bars, cards, dialogs and tooltips. |
| overlay | string | - | Dimmed backdrop behind modals. |
| codeBlock / codeMuted | string | - | Code panel frame and comment lines. |
| kbd | string | - | Keyboard chip. |
| spotlight | string | - | Ring around the tour target. |
| muted | string | - | Counters, hints, empty states. |
StepwiseStep / StepTone
type StepwiseStep<Data> = { tone: StepTone; label: string; code?: string[]; note?: string; key?: string } & DataOne frame of a trace, carrying your domain payload. StepTone is exactly "neutral" | "accent" | "good" | "bad" | "write" | "idle". key names the moment ("compare at i=3, j=2") so the playhead keeps its place when the trace is recomputed; give every step of a simulation one.
PlayerEngine / TourDriver
PlayerEngine.connect(api) => handle | TourDriver.show(step, ctx) / hide()Ports for alternate frame sources and tour presenters. Adapters ship on subpaths: @sphynxoid/stepwise/adapters/gsap, /driver-js and /scroll.
PlayerEngine
| Name | Type | Default | Description |
|---|
| connect(api) | (api: PlayerEngineApi) => PlayerEngineHandle | - | api: total, onFrame(frame), onEnd(). handle: play, pause, seek, setRate, disconnect. |
TourDriver
| Name | Type | Default | Description |
|---|
| show(step, ctx) | (TourStepInput, TourContext) => void | - | ctx: index, total, next(), back(), end(). |
| hide() | () => void | - | Called on end and unmount. |
Experimental: transitions
From @sphynxoid/stepwise/experimental: may change in a minor release. A trace becomes a graph: choice points branch on the reader's answers, and the path taken is still a plain array, so the player, links and re-anchoring work unchanged.
useWalk<Input, S extends WalkStep, Choice>(simulation: WalkSimulation<Input, S, Choice>, input: Input, opts?: WalkOptions<S, Input, Choice>): WalkPlayer<S, Choice>useSimulation plus choices. A walk is replayed from (input, seed, choices) on every change, so it stays pure. The unanswered choice point at the end of the path holds playback until choose().
WalkOptions (plus every SimulationOptions field)
| Name | Type | Default | Description |
|---|
| choices | readonly Choice[] | - | Choices to start with (read once, on mount). |
| anchor | "key" | "index" | "key" | Where the playhead stays when the input changes: on the step with the same key, or at the same index (for walks where step i means "after i inputs", like fromGraph). |
| syncChoices | { key, serialize, parse } | - | Keep the choices in the URL (#p=a.b.a). Restored together with syncInput, before the linked frame. A choice parse rejects drops the whole linked path. |
WalkPlayer<S, Choice> (returned: every SimulationPlayer field, plus)
| Name | Type | Default | Description |
|---|
| choose | (choice: Choice) => void | - | Answer the choice point the playhead is on. At the end of the path the walk continues; on an earlier choice point a different answer cuts off the old future (the same answer keeps it). The playhead moves past the choice point; held playback resumes. Off a choice point: no-op (dev warning). |
| choices | readonly Choice[] | - | The choices made so far. With the input, they are the walk. |
| choicePoints | readonly number[] | - | Indices of the choice points on the path. |
| awaiting | boolean | - | The path ends on a choice point waiting for an answer. |
| choiceAt | (frame: number) => Choice | undefined | - | The answer given at frame, if it's an answered choice point. |
| lastChange | TraceDiff | null | - | What the last re-run changed (see diffTraces), paired the way the playhead anchors. |
runWalk / WalkSimulation
runWalk(simulation, input, opts?: { seed?, maxSteps?, choices? }): WalkResult<S>A walk simulation is a generator; a step with await: true is a choice point, and that yield returns the reader's choice: const pick = yield { key: "fork", await: true, … }. runWalk replays it purely, stopping at the first unanswered choice point. Linear simulations are walks without choice points.
WalkResult<S>
| Name | Type | Default | Description |
|---|
| steps | S[] | - | The path, up to the end or the first unanswered choice point. |
| choicePoints | number[] | - | Indices of the choice points on the path. |
| awaiting | boolean | - | The path ends on an unanswered choice point. |
| used | number | - | How many choices were consumed; extra ones are ignored. |
| truncated / error / seed | boolean / unknown / number | - | As in runSimulation. |
fromGraph<Sym extends string, N>(spec: { start, nodes, edges: { from, on, to }[], accept?, otherwise? }): StepGraph<Sym, N>A declared automaton: nodes, labeled edges, accept states. Throws on an inconsistent graph. A missing edge goes to otherwise (a dead state) or ends the run with an error.
StepGraph (returned)
| Name | Type | Default | Description |
|---|
| simulation | GraphSimulation<Sym> | - | Runs the graph over a word (a string or any iterable): one step per symbol read, keyed by position. Use with useSimulation or useWalk (anchor: "index"). |
| graph | { start, nodes, edges, accept: Set, otherwise } | - | The declared graph, to draw it. |
| delta | (from: string, on: Sym) => string | null | - | The transition function, otherwise included. |
| stepAt | (read, node, from?, on?) => GraphStep | - | The step for a position, for custom walks over the same graph. |
GraphStep (plus StepwiseStep)
| Name | Type | Default | Description |
|---|
| node / from / on | string / string | null / Sym | null | - | Where the walk is, where it came from, and the symbol read (null on the start step). |
| read | number | - | Symbols read so far. |
| accepted | boolean | - | The node is an accepting one (tone good). |
diffTraces
diffTraces<S>(oldSteps, newSteps, opts?: { keyOf?, equals? }): TraceDiffWhat an edit changed between two runs, to mark the changed steps instead of only saying that something did. useWalk exposes it as lastChange.
TraceDiff
| Name | Type | Default | Description |
|---|
| divergedAt | number | - | First index where the runs differ position by position; -1 when equal. Everything before it is the same. |
| changed | ReadonlySet<number> | - | Indices in the new run that are new or differ from their counterpart (by key with keyOf, else by index). |
Options
| Name | Type | Default | Description |
|---|
| keyOf | (step, index) => string | undefined | - | Pair steps by key instead of index. |
| equals | (a, b) => boolean | structuralEqual | Step equality. The default compares plain data structurally; anything else by identity. |