Skip to content

The wire protocol

You never write this protocol. The compiler and the mount derive both ends of it. This page documents it anyway, because auditable transport is part of situ's contract: everything below is visible in your browser's network tab.

What a page loads

GET  /todos                  the rendered page, containing:
                               <script id="siting-signals" type="application/json">
                                 {"local": {"draft": ""}, "url": ["filter"], "sets": []}
                               </script>
GET  /static/_rt.js          the shared runtime shim (~700 lines, cacheable, shared by every situ app)
GET  /todos/island.js        this mount's generated island (typically 5–12 KB, no-cache)

The bootstrap seeds Local defaults (Local[set]s rehydrate to real JS Sets), lists the Url signal names and, for live mounts, the feed/region URLs.

The command round trip

POST /todos/cmd/add                      ← derived: one route per server handler
     X-Siting: 1
     Content-Type: application/x-www-form-urlencoded
     body: draft=Buy+milk&filter=all     ← form fields + riding local signals + current url params

  ←  200 text/html
     body: <the region's re-rendered inner HTML>
     X-Siting-Signals: {"draft": ""}     ← optional client-signal patch

On the client, in order: drop all region-scoped subscriptions → innerHTML-swap the region → apply the signal patch → re-bind binders inside the region. On failure (non-2xx or network error): log, roll back any optimistic apply, leave the region untouched.

  • Path args: a call's positional arguments become path segments (toggle(t.id) → /cmd/toggle/{id}, an int route).
  • Sets cross as JSON arrays. The bulk-action idiom: the selection rides the command; the server updates only ids that still exist.
  • In flight: the invoking control is disabled and the region aria-busy, so no request double-POSTs.

Region updates are coarse: the server re-renders one data-region from fresh context() and swaps it. There is no virtual DOM and no HTML-morphing library; the one keyed reconciler in the system is the client :each list engine (keyed by row id, focus-preserving, nested- and tree-capable). The demo programme found region-level coarseness right everywhere except broadcast streaming, which is what the feed is for.

Live updates (SSE)

mount_component(..., live=True) adds two routes and a hub:

GET /poll/feed      an SSE stream; emits "dirty" whenever any client's command mutates state
GET /poll/region    the region's HTML, re-rendered for the current query string

Each client keeps an EventSource on the feed; on dirty it re-fetches /region?{search} and swaps. A vote in one browser updates every connected peer (demo /poll, verified across two Chromium contexts).

Scope

The hub is in-process (asyncio queues): fine for one server process, while horizontal scaling needs a real broker behind the same interface. The live-refetch is the labelled stand-in for the reserved Synced site; a local-first replica is future work.

Windowed data

mount_component(..., window_template=...) adds:

GET /grid/window?offset=1200&count=40    positioned row HTML for the visible window

used by the :virtual binder. Scroll position stays a client signal; the row window is fetched and placed with a translate. The demo (/data-grid) renders 10,000 rows with ~40 in the DOM.

The reverse channel

X-Siting-Signals is the one server→client channel: a JSON object of signal patches attached to a command response. It carries reset(...) defaults and signal(name, value) pushes from your handlers, applied atomically before re-bind so binders never observe a half-applied patch.