Skip to content

The studio

fate-studio is an embeddable, self-hosted chart viewer and live simulator that renders and drives any fate machine in the browser. Pick a machine, watch the active configuration light up as you send events, inspect context, fire timers, and resolve invocations — all live, over Server-Sent Events.

The picture is never hand-drawn: it is generated from the machine's own structure, so the diagram can never drift from the code.

What you can do in it

  • Browse a gallery of machines and open any one as an interactive diagram.
  • Simulate live: send events by clicking a state's transitions, then undo, reset, import/export a snapshot, or replay a timeline.
  • Inspect the active state path, context (updating live), and event history.
  • Drive effects: a Pending effects panel fires a delayed (after) timer or resolves/rejects an invocation with JSON output — exactly as a real adapter would, so the effect model is visible.
  • Read dense charts: an auto-layout canvas (ELK) with orthogonal wiring, a re-tidy control, and automatic badging of global/hub transitions so a TERMINATE-from-everywhere doesn't bury the chart.

Why it's a separate project

The studio lives in its own repository (github.com/arisros/fate-studio) on purpose: the engine has no dependencies, and the studio needs a web server and a browser toolchain. Keeping them apart means adopting the engine never drags in net/http or a UI build. This split is the refactor behind v0.4.0 — see the changelog.

Embed it in your app

A studio is a Server you register machines on. Each entry supplies a Build function (the static descriptor, for the diagram) and an optional BuildLive function (a fresh actor, for the simulator):

go
import studio "github.com/arisros/fate-studio"

srv := studio.NewServer("my app — statecharts")

srv.Register(studio.Entry{
    Name:    "checkout",
    Summary: "Cart → payment → fulfilment.",
    Build:   checkoutMachine.Describe,          // structure for the chart
    BuildLive: func() studio.LiveInstance {       // a running actor for the sim
        return studio.NewLiveActor(
            checkoutMachine,
            dispatch,                              // map an event name → typed event
            checkoutMachine.Describe,
        )
    },
})

log.Fatal(srv.ListenAndServe(":8090"))

dispatch is the one piece of glue: it turns an event name (what a button click sends) into your typed Evt. The rest — graph endpoint, SSE stream, sessions, effects — the studio provides. The whole UI is embedded in the binary, so this is a single self-contained server.

A real production case

The studio isn't just for demos. In the LORA loan-origination platform, the four human-task state machines that run in production — e-sign, underwriting, BPKB review, and survey — are registered in exactly the form above and served at fate-studio-dp.arisjirat.com. The same Machine values that drive real Temporal workflows are the ones you walk and simulate in the browser.

go
func registerSurvey(srv *studio.Server) {
    describe := func() fate.MachineDescriptor { m, _ := svfsm.NewMachine(); return m.Describe() }
    dispatch := func(name string) (svfsm.Evt, error) {
        switch name {
        case svfsm.EventSubmitPin: return svfsm.EvtSubmitPin{PIN: "1234"}, nil
        case svfsm.EventForward:   return svfsm.EvtForward{}, nil
        // … one case per event the machine accepts …
        case svfsm.EventTerminate: return svfsm.EvtTerminate{}, nil
        }
        return nil, studio.ErrUnknownEvent{Name: name}
    }
    srv.Register(studio.Entry{
        Name:    "survey",
        Summary: "Survey — 4 parallel regions (main / head_vd / env_check / unscheduled).",
        Build:   describe,
        BuildLive: func() studio.LiveInstance {
            m, _ := svfsm.NewMachineWithContext(svfsm.Ctx{PIN: "1234", BPKBOnHand: true})
            return studio.NewLiveActor(m, dispatch, describe)
        },
    })
}

Survey is a genuinely large machine — 33 states across four parallel regions, 125 transitions — and it renders and simulates live. It's the worked example behind Keeping dense charts readable.

On the command line

If you just want to render or diff a machine without a browser, the engine ships a small CLI:

sh
go install github.com/arisros/fate/cmd/fate@latest
fate mermaid machine.json
fate diff a.json b.json

See Install → The CLI.

Released under the MIT License · v0.4.0 · pkg.go.dev