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 +84 -4
- package/package.json +1 -1
- package/public/embed/assets/auto-visualizer-C-FJ6u_a.js +89 -0
- package/public/embed/assets/{auto-visualizer-B7AuNkEA.js.map → auto-visualizer-C-FJ6u_a.js.map} +1 -1
- package/public/embed/assets/{in-page-CRyqBvfM.js → in-page-C6bs-yMR.js} +2 -2
- package/public/embed/assets/{in-page-CRyqBvfM.js.map → in-page-C6bs-yMR.js.map} +1 -1
- package/public/embed/assets/style-DmL1hcYl.css +3 -0
- package/public/embed/custom-elements.json +107 -3
- package/public/embed/embed.d.ts +29 -2
- package/public/embed/embed.js +1 -1
- package/public/embed/assets/auto-visualizer-B7AuNkEA.js +0 -89
- package/public/embed/assets/style-DUwvQ-A9.css +0 -3
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
|
-
|
|
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
|
+

|
|
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
|
-
|
|
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
|
|