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 (509 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 — carries 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, the region aria-busy — no double-POSTs.

Region updates are deliberately coarse: one data-region, re-rendered from fresh context() and swapped. 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) — perfect for one server process; horizontal scaling needs a real broker behind the same interface. And this 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 — 10,000 rows, ~40 in the DOM (demo /data-grid).

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.