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}, anintroute). - 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 regionaria-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:
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.