autovisualizer 0.4.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -81,12 +81,56 @@ export const door = createMachine({ id: "door", initial: "closed", states: { clo
81
81
  | `snapshot` | Resume exactly where an actor left off: the JSON from `actor.getPersistedSnapshot()`. Its state, context, history and pending timers come back as they were, and no entry actions run again, which `state` and `context` can't promise. Wins over both. A snapshot that doesn't fit the machine (another machine's id or version, or a state it doesn't have) shows why. |
82
82
  | `events` | Events to send before the chart shows, space-separated: `events="ADD_ITEM CHECKOUT"` opens at `address`. For events that carry data, write a JSON array of names and whole events: `events='["ADD_ITEM",{"type":"xstate.done.actor.pay","output":{"id":"tx_1"}}]'`. |
83
83
  | `walkthrough` | Steps to walk through, as JSON: `[{"event":"SUBMIT","note":"take payment"}]`. Adds previous, play and next under the chart, with each step's note. An event that carries data is written whole: `{"event":{"type":"xstate.done.actor.pay","output":{"id":"tx_1"}}}`. |
84
- | `autoplay` | Steps through the walkthrough on its own. Readers who ask for less motion step themselves. |
84
+ | `autoplay` | Steps through the walkthrough on its own. Readers who ask for less motion step themselves. With narration, each step waits for its audio to end. |
85
+ | `speak` | Reads each step's note aloud with the browser's voice. |
86
+ | `voice` | Part of the browser voice's name for `speak`, matched case-insensitively: `voice="samantha"`. |
87
+ | `rate` | Speaking rate for `speak`, `0.1` to `10`. |
88
+ | `audio-src` | A recorded clip per step: `audio-src="audio/{step}.wav"`. A clip that can't play falls back to `speak`. |
85
89
  | `counts` | How often each transition was taken, as JSON keyed by state then event: `{"payment":{"PAID":120,"DECLINED":0}}`. Each count shows on its event, and a transition taken 0 times is drawn dashed: a path the code allows that nothing took. Name a state by its key or its full id (`checkout.payment`). |
86
90
  | `direction` | `auto` (default), `right` or `down`. `auto` lays the chart out both ways and keeps the one that shows it larger in its space: a long chain goes top to bottom in a wide column, a wide machine left to right in a short box, and a small machine that fits either way goes right. |
87
91
 
88
92
  An event the machine doesn't accept, or a `machine` id it doesn't create, shows an error in the chart.
89
93
 
94
+ **Narrate the walkthrough.** Each step's note is its script. Add `speak` and the browser reads each note aloud, and `autoplay` moves on when the sentence ends:
95
+
96
+ ```html
97
+ <auto-visualizer src="/machines/checkout.ts" speak voice="samantha" rate="0.9"
98
+ walkthrough='[{"event":"CHECKOUT","note":"The cart is ready, so we ask for an address."},{"event":"SUBMIT","note":"Then we take payment."}]'
99
+ ></auto-visualizer>
100
+ ```
101
+
102
+ For a voice model, record a clip per step and point `audio-src` at them: `audio-src="audio/{step}.wav"`, where `{step}` is the step's number. `element.getSteps()` returns each step's `index`, `event`, `note` and `text` (the note, or the event when there's no note). Send the text to any speech model and save the clips. This records them with [Phonon](https://ollama.com/library/phonon) in Ollama, through [ai-sdk-ollama](https://github.com/jagreehal/ai-sdk-ollama):
103
+
104
+ ```js
105
+ import { writeFile } from "node:fs/promises"
106
+ import { generateSpeech } from "ai"
107
+ import { ollama } from "ai-sdk-ollama"
108
+
109
+ // The chart's steps: JSON.stringify(document.querySelector("auto-visualizer").getSteps()) in the browser console.
110
+ for (const step of steps) {
111
+ const { audio } = await generateSpeech({
112
+ model: ollama.speech("phonon:40m-multilingual"),
113
+ text: step.text,
114
+ voice: "Hugh",
115
+ outputFormat: "wav"
116
+ })
117
+ await writeFile(`audio/${step.index}.wav`, audio.uint8Array)
118
+ }
119
+ ```
120
+
121
+ To voice steps live, set `narrate` to a function that returns each step's audio as a URL or a `Blob`, for example from your own speech endpoint:
122
+
123
+ ```js
124
+ const chart = document.querySelector("auto-visualizer")
125
+ chart.narrate = async (step) => {
126
+ const response = await fetch("/api/speak", { method: "POST", body: step.text })
127
+ return response.ok ? response.blob() : undefined
128
+ }
129
+ chart.addEventListener("step", (event) => console.log(event.detail.index, event.detail.text))
130
+ ```
131
+
132
+ The chart tries `narrate` first, then the `audio-src` clip, then `speak`, and waits for each step's audio before autoplay moves on. The `step` event fires on every walkthrough step, played or stepped by hand. Browsers play sound only after the reader interacts with the page, so narration starts when they press play or next.
133
+
90
134
  The events `events` and a walkthrough send are drawn as a path: each transition they took is highlighted, so a walkthrough that replays a real run shows its whole route, not only where it ended.
91
135
 
92
136
  From React, set `walkthrough`, `counts`, `input` and `context` as JSON strings with `setAttribute` (or as attributes in markup). React renders before `embed.js` defines the element, so an object passed as a prop arrives as the text `[object Object]`.
@@ -95,9 +139,45 @@ The chart shows states and events only, so it leaves the machine's name to your
95
139
 
96
140
  ### In READMEs, issues and pull requests
97
141
 
98
- Export the chart as an image. In [the app](https://auto-visualizer.vercel.app), **Save → SVG image** exports the chart as one self-contained SVG, and **Save → Animated SVG** exports every step you simulated since the last reset, like the demo above. Both follow the reader's light or dark setting, and readers who ask for less motion see the last step as a still image.
142
+ GitHub can't run the element, so give it an image. Put each chart in your Markdown as an SVG link with its source in a collapsed `<details>` block below it:
143
+
144
+ ````md
145
+ ![Door statechart](docs/door.svg)
146
+ <details><summary>Statechart source</summary>
147
+
148
+ ```statechart walkthrough='[{"event":"OPEN","note":"someone knocks"}]'
149
+ import { createMachine } from "xstate"
150
+ export const door = createMachine({ id: "door", initial: "closed", states: { closed: { on: { OPEN: { target: "open" } } }, open: {} } })
151
+ ```
152
+ </details>
153
+ ````
154
+
155
+ Then let the action draw the images. With `render`, it finds each chart, draws it with the same export as the app, and commits the SVGs that changed. A chart with a `walkthrough` becomes an animated SVG. Attributes on the fence line are the element's own (`walkthrough`, `events`, `state`, `direction`), and source that starts with `{` is MachineJSON:
156
+
157
+ ```yaml
158
+ on:
159
+ pull_request:
160
+ paths: ['**/*.md']
161
+ permissions:
162
+ contents: write
163
+ jobs:
164
+ statecharts:
165
+ runs-on: ubuntu-latest
166
+ steps:
167
+ - uses: actions/checkout@v7
168
+ with: { ref: '${{ github.head_ref }}' }
169
+ - uses: actions/setup-node@v7
170
+ with: { node-version: 24 }
171
+ - uses: jagreehal/autovisualizer@main
172
+ with:
173
+ render: README.md docs/guide.md
174
+ ```
175
+
176
+ It draws in the runner's Chrome. To render locally, build the CLI and the embed from an Autovisualizer checkout (`pnpm build:review && pnpm build:embed`), then run `node /path/to/autovisualizer/dist/review/cli.mjs render README.md`.
177
+
178
+ You can also export by hand. In [the app](https://auto-visualizer.vercel.app), **Save → SVG image** exports the chart as one self-contained SVG, and **Save → Animated SVG** exports every step you simulated since the last reset, like the demo above. Both follow the reader's light or dark setting, and readers who ask for less motion see the last step as a still image.
99
179
 
100
- To build one in CI, ask a chart on your docs page for its walkthrough as an animated SVG: `await element.toAnimatedSvg({ title, stepMs, direction })` (`direction` is `right`, the default, or `down`). It returns the SVG text, a frame per step, captioned with the step's event and note, so keep notes short. Call it from a headless browser once the chart has loaded:
180
+ From your own docs build, ask a chart on your page for its walkthrough as an animated SVG: `await element.toAnimatedSvg({ title, stepMs, direction })` (`direction` is `right`, the default, or `down`). It returns the SVG text, a frame per step, captioned with the step's event and note, so keep notes short. Call it from a headless browser once the chart has loaded:
101
181
 
102
182
  ```js
103
183
  const chart = page.locator("auto-visualizer#checkout")
@@ -127,7 +207,7 @@ steps:
127
207
  fail-on-regression: true # optional: fail when a machine lost a state or transition
128
208
  ```
129
209
 
130
- It finds machines in TypeScript or JavaScript that call `createMachine`, `setup().createMachine`, `setupEffect().createMachine` or `machineFromJSON`, and in MachineJSON files (`machineToJSON` output, or any `.json` with `states`). Removed states or transitions, and states that became unreachable, make the merge risk high. A target it can't read, or a file it couldn't run, makes it moderate. The first run takes about a minute.
210
+ It compares the pull request with the commit it branched from, so changes that landed on the base branch since then stay out of the review. It finds machines in TypeScript or JavaScript that call `createMachine`, `setup().createMachine`, `setupEffect().createMachine` or `machineFromJSON`, and in MachineJSON files (`machineToJSON` output, or any `.json` with `states`). Removed states or transitions, and states that became unreachable, make the merge risk high. A target it can't read, or a file it couldn't run, makes it moderate. The first run takes about a minute.
131
211
 
132
212
  The review runs the machine files, the same as your tests do. Imports other than `xstate`, `@xstate/effect` and `effect` get stand-ins, so a machine can import its own actions and helpers. Use the `pull_request` trigger. The action refuses `pull_request_target`, which would run a fork's code with a write token.
133
213
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autovisualizer",
3
- "version": "0.4.1",
3
+ "version": "0.6.0",
4
4
  "description": "See and step through XState v6 and @xstate/effect machines in the browser.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Jag Reehal",