Skip to content

Why Stator

Stator exists because the dominant way we build interactive apps — canonical state in the browser, mirrored to the server — creates a class of problems that never fully go away. Stator makes the server’s state canonical and gives the client a precise, declared window into it.

Client-canonical architectures share three recurring costs:

  • State drift. The same fact lives in two places (client store and server database), and keeping them honest is a permanent tax — optimistic updates, refetching, cache invalidation, reconciliation bugs.
  • Hydration cost. The server renders HTML, then the client re-renders the same tree to “attach” behavior, shipping and executing a component runtime to do it.
  • Invisible boundaries. Which code runs where is encoded in conventions ("use client", file location, framework magic) that are easy to get subtly wrong and hard to see when reading a file.
  • One canonical state. A machine is the single source of truth. There is no second copy to reconcile; the client holds no authoritative state unless you deliberately put it in an island.
  • Explicit reads. A node says exactly what state it shows with read(machine, selector). Nothing is auto-tracked, so what updates — and why — is legible from the template.
  • A boundary you can see. Where a machine runs is decided by where you import it: in a .stator file’s frontmatter (server) or its <script> (client). The compiler checks that a machine using a server-only capability never ends up on the client.

Stator is opinionated, and the opinions cost something:

  • A server round-trip for server-state events. Changing canonical state is a request. For server-authoritative apps this is correct; for state that should be instant and local, you use a client island instead.
  • Explicit read() over auto-tracking. You declare dependencies rather than having them inferred. More to type, far less to debug.
  • No client-side JSX re-render. The browser does not re-run your template. DOM creation in the browser happens through native APIs inside a client component, never a render loop.

The round-trip tradeoff deserves a straight answer, because it’s the first question anyone with a SPA background asks.

An event POST carries a small JSON body and returns a patch list, not a page — on an ordinary connection the update lands in tens of milliseconds, comparable to what a SPA spends reconciling and re-rendering after its own state change. For forms, lists, dashboards, and flows — the apps Stator is for — that is beneath notice. The framework owns the seams you’d otherwise hand-roll: the dispatching element carries data-stator-pending while its POST is in flight (a CSS hook, no loading flag to manage), failed POSTs retry under an idempotency key so a flaky link can’t double-commit, and a 10-second deadline turns a dead connection into an explicit error instead of a hang.

What Stator deliberately does not do is optimistic updates. An optimistic layer is a second copy of the truth — a client-side guess at what the machine will decide — and reconciling guesses against outcomes is precisely the class of bug this architecture exists to remove. Guards decide on the server; a button that pretends a guarded event committed is lying to the user. When an interaction genuinely can’t wait for the network, that’s the signal it isn’t server state — put it in a client island, where it updates synchronously because it truly lives in the browser.

Client-side prediction (a preview the server’s answer always overwrites, never a commit) is a design direction we’ve explored and parked — it stays out until it can ship without reintroducing a second source of truth.

Stator is a strong fit when:

  • State is server-authoritative — carts, dashboards, admin tools, multi-step flows, anything backed by a database or shared across a session.
  • You want fine-grained updates without owning a client state-management stack.
  • You value a readable, statically analyzable boundary between server and client.

Stator ships today:

Deferred, each with a designed path:

  • The durable inbox — app→session delivery and reaching sessions with no open connection.
  • Horizontal scaling — a Redis pub/sub backplane over the existing fan-out choke point.
  • Statechart richness — nested/parallel/history/invoke; machines are flat today, with extension points where depth would land.