Playground Internals
The Playground on this site is an interactive AFSM example runner.
Architecture
Playground.vue ├── ParamControl.vue parameter controls ├── CodeBlock.vue source display (read-only) ├── MermaidView.vue state diagram rendering ├── Timeline.vue state-change timeline └── ConsoleOut.vue console outputAll components live in docs/.vitepress/components/.
Running the real library
The Playground directly import { FSM, ChangeState } from 'afsm' via a file:.. link to the parent package. It runs the real AFSM library, not a simplified version.
Example registry
Each example is defined under docs/.vitepress/examples/presets/ and registered via registerExample:
import { FSM, ChangeState } from 'afsm'import { registerExample } from '../registry'import source from './trafficLight.ts?raw' // source as string
class TrafficLight extends FSM { @ChangeState(FSM.INIT, 'red') async init() {} // ...}
registerExample({ key: 'traffic-light', source, // shown in CodeBlock params: [...], // parameter schema create(params) { // instantiate the FSM return new TrafficLight() }, run(fsm, params, log) { // kick off the demo fsm.init() }, cleanup(fsm) { // clear timers // ... }, title: { zh: '红绿灯', en: 'Traffic Light' }, description: { ... }})Source display matches running code
The Vite ?raw suffix imports the source as a string:
import source from './trafficLight.ts?raw'So the CodeBlock shows exactly the code that runs — single source of truth, never out of sync.
Event flow
function run() { const inst = example.create(params) inst.on(FSM.STATECHANGED, (newState, oldState, err) => { // push to Timeline // update currentState // trigger MermaidView re-render }) example.run(inst, params, log)}log is injected by the Playground and routes output to the ConsoleOut panel (without replacing the global console).
State diagram rendering
StateDiagramView uses the shared afsm-diagram package (Cytoscape) to render AFSM stateDiagram edge lines:
import { createDiagram, syncDiagram } from 'afsm-diagram'const cy = createDiagram(container, 'docs')syncDiagram(cy, diagram, currentState, sourceKey)The current state is highlighted with .current / .processing node classes (no mermaid note markers). The same renderer powers the DevTools panel.
Cleanup
On example switch or “Reset”:
fsm.removeAllListeners()— remove all listenersexample.cleanup(fsm)— clear timers and other resources- Clear
history,consoleLog,currentState
Contributing a new example
- Create
docs/.vitepress/examples/presets/myExample.ts - Define an FSM class (using
@ChangeStateand other decorators) - Call
registerExample({...}) - Import your own source via
?rawassource - The example auto-appears in the Playground dropdown
Limitations
- No free-form code editing (security: avoid arbitrary execution)
- Examples avoid
opt.context(FSM.getcaches in a static Map, which could leak across resets) - Source must be self-contained (no external module deps besides
afsm)
Next steps
- DevTools Extension — observe the Playground live
- GitHub source