Design¶
This page records how the v0.1 specification (ideas/) was matured into an
implementation: the open questions it left, and the decisions taken.
Principles¶
Composition over inheritance for styles and reusable behaviour.
Explicit remote exposure: Python methods are never callable from browsers by accident.
Predictable execution: it is always clear whether code runs on the server or in the browser.
Immutable style values and render nodes; state is explicitly mutable.
Secure by default; progressive enhancement; a small stable core with inspectable output.
Non-goals for 0.x: a Python-to-JavaScript compiler, a React replacement, automatic distributed state, reimplementing SQLAlchemy, Bootstrap/Tailwind parity.
Decisions¶
Area |
Decision |
|---|---|
Rendering |
Async render pass → immutable node IR → deterministic HTML serializer. |
Updates |
The server re-renders each dirty component and sends its HTML; the runtime
morphs the live DOM toward it (keyed by |
Transport |
HTTP |
Identity |
|
DOM boundary |
A single-root render carries |
State |
|
Handlers |
Registered at render time under opaque ids |
Manifest |
JSON in |
Local operations |
A closed |
Sessions |
Opaque cookie id → server data; rotation on login/logout; in-memory stores behind
the |
Remote API |
|
Database |
Plain SQLAlchemy 2.0 async engine and sessions; no second query language. |
Styles |
snake_case properties, px for numbers, scale and colour tokens, validated values,
|
Responsive |
Named viewport breakpoints ( |
Themes |
Semantic tokens as CSS custom properties; four presets; light/dark/system. |
Running |
|
The event protocol (v1)¶
Request (POST /_rational/event, header X-RW-CSRF, session cookie):
{"v": 1, "page": "<page view id>", "rev": 3, "handler": "c1.1", "payload": {"value": "Ada"}}
Response:
{"v": 1, "rev": 4,
"patches": [{"op": "morph", "id": "c1", "html": "<div data-rw-c=\"c1\">…</div>"}],
"css": [".rw-1a2b3c4d{…}"],
"actions": [{"op": "navigate", "url": "/done"}],
"error": {"code": "validation_error", "message": "…", "details": [{"field": "age", "message": "…"}]}}
410 means the page view expired (the runtime reloads); 403 a session or CSRF
mismatch; 429 the rate limit. An unknown handler answers with error.code ==
"stale" plus a full re-render, so the page resynchronizes instead of breaking.
Known limitations¶
Server-held page state requires one process or sticky sessions.
Unkeyed siblings of the same class in conditional branches can swap state: use
key=.js.*changes to an element are reset when the server re-renders it.Mounted sub-applications do not receive lifespan events, so
on_startuphooks of a mountedAppmust be called by the host application.