Sync a UI to a server over transports¶
This guide shows you how to serve a spaday UI from a webserver and keep it in sync with a server-side state model using transports — so a two-way control’s change is applied on the server and fanned to every connected browser. This is the multi-tenant, no-notebook path; for the zero-setup version see the notebook guide.
The split to keep in mind: spaday owns the UI (the tree and its reactive Store); transports
owns the wire (a Client that mirrors a model and sends edits); a single adapter, connectStore, is
the only place they meet.
Install both:
pip install spaday transports uvicorn
cd js && pnpm install && pnpm build # builds the runtime + bundles served to the browser
Wire the store to the client (browser)¶
In the page, create a spaday Store, a transports Client, and link them with connectStore. Then
mount the tree with that store:
import { mount, init, Store, connectStore } from "spaday";
import { Client, fromValue, toValue, wasm } from "@1kbgz/transports";
await init(); // spaday wasm (action interpreter)
await wasm.default(); // transports wasm
const store = new Store();
const client = new Client();
const ws = new WebSocket(`ws://${location.host}/ws`);
ws.binaryType = "arraybuffer";
// the seam: model fields <-> store fields; a two-way control's change becomes a server edit
const link = connectStore(store, client, (frame) => ws.send(frame), { fromValue, toValue });
ws.addEventListener("message", (e) =>
link.receive(typeof e.data === "string" ? e.data : new Uint8Array(e.data)),
);
mount(document.body, await (await fetch("/tree.json")).json(), store);
Inbound model patches flow model → store → bound props; a two-way control’s change becomes a
server-authoritative client.edit. Edits take effect when the server echoes them back, so two browser
tabs stay in sync.
A complete, runnable version of this is spaday/examples/reactive.py in the repository.
Go multi-tenant¶
Swap the Session for a Hub, which routes each connection to
its own tenant session (and can share models across tenants). The UI code is unchanged — connectStore
and the bindings don’t know or care whether the model is private or shared.