fate-studio: Seeing and Driving Statecharts in the Browser
A viewer and live simulator for fate statecharts — send events, fire timers, resolve invocations, and watch the active state change over Server-Sent Events. One static binary, no outbound requests at all.
If fate is the engine, fate-studio is where you watch it run. Open a machine and its diagram appears; then step through it — send events, fire delayed transitions, resolve or reject invocations — and the active state updates live on screen.
Why it’s a separate repo
This is the question it gets most, and the answer is one sentence: the engine has no dependencies, and the studio needs a web server.
In one repo, go get github.com/arisros/fate would drag net/http and the whole studio into the program of someone who only wanted a state machine. Splitting them means people pay only for what they use.
It embeds into your own program
The studio is an ordinary http.Handler, so it isn’t only a demo server — it can be mounted inside a running application:
srv := studio.NewServer("my app")
srv.Register(studio.Entry{
Name: "checkout",
Summary: "the checkout flow",
Build: checkoutMachine().Describe,
BuildLive: func() studio.LiveInstance {
return studio.NewLiveActor(checkoutMachine(), dispatch, checkoutMachine().Describe)
},
})
http.Handle("/studio/", http.StripPrefix("/studio", srv.Handler()))
The key is the LiveInstance interface. fate is generic — Actor[Ctx, Evt] — while the simulator has to drive any machine without knowing its concrete types. LiveInstance erases them, and the dispatch function you supply maps an event name from the UI onto one of your typed events. A machine registered with only Build still shows its diagram, just without the simulator.
Canvas and transport
The diagram is drawn with React Flow (@xyflow/react) and laid out by elkjs, so node positions never have to be authored by hand. State changes travel from server to browser over Server-Sent Events rather than WebSockets — the data really does flow one way, and user events go back as ordinary requests. SSE reconnects on its own and needs no special handling in a proxy.
Snapshots can be inspected, diffed, imported and exported, and a timeline records every step — which turns out to be the practical way to retell the exact sequence that produced a strange state.
One binary, no outbound calls
The built UI (Vite output) is committed into assets/ and embedded in the Go binary. What gets deployed is a single file: no Node, no CDN, no external font request. That matters because this thing is most likely to be mounted inside an internal network with no egress at all.
go run ./cmd/fate-studio # then open http://localhost:8090
The address is set via FATE_STUDIO_ADDR. The demo server ships a handful of example machines — a traffic light, a parallel media player, a build pipeline, a deep-history editor, a live-context counter, and timeout / fetch machines for trying the timer and invocation controls.