Contrato de fronteira¶
O contrato de fronteira (wire format) é o acordo entre o Python (o
reconciliador, vindo do core) e o cliente JS (que muta o DOM). Ele é o
mesmo nos três transportes — pyodide.ffi (Modo A), WebSocket e SSE (Modo B).
Só o envelope muda; nunca o shape dos dados. 🤝
Esta página é o resumo didático
O documento canônico, pinado por golden fixtures derivadas do core real,
vive junto ao código:
docs/contract.md.
Qualquer agente que trabalhe no cliente ou nos transportes programa contra ele
e contra as fixtures. Aqui damos a visão geral; o link tem cada campo.
Os quatro cruzamentos¶
A tipagem "vaza" pela fronteira em quatro pontos — análogo ao request/response do FastAPI:
-
1. IR → cliente
A árvore de
Nodeserializada e a lista de patches dodiff. -
2. Evento → handler
O payload do click/input que sobe e é validado (Pydantic) antes de entrar no Python.
-
3. Style → CSS
O objeto
Styletipado que o cliente traduz para CSS. -
4. Chamada nativa
Web APIs expostas como awaitables tipados (
native_call/native_result).
1. Node — a IR serializada¶
Cada nó da árvore tem o mesmo shape:
type— o nome do widget (Column,Row,Text,Button,Container, …).key— chave estável para reconciliação (pode sernull).props— props do widget, incluindo"style"(objetoStyleounull).children— lista deNodes filhos.
Handlers não atravessam como função
O core serializa uma referência; o evento volta com a key do widget. É
o lado Python que resolve qual handler chamar — o cliente nunca executa lógica
de app.
2. Os 5 patches¶
O reconciliador faz diff(old, new) e emite uma lista. path endereça o nó-alvo
por índices ([] = raiz, [0] = primeiro filho).
| Tipo | Distingue-se por | Semântica |
|---|---|---|
| Update | set_props |
Aplica props e remove unset_props. |
| Insert | node + index |
Insere um filho na posição. |
| Remove | só index |
Remove o filho na posição. |
| Reorder | order |
Reordena os filhos. |
| Replace | node sem index |
Troca o nó inteiro. |
3. Style¶
props.style é um objeto Style (ou null). Color é {r,g,b,a} (r/g/b
0–255, a 0–1) → CSS rgba(...). Edge é {top,right,bottom,left} em px.
{
"direction": "column",
"gap": 8.0,
"padding": { "top": 16, "right": 16, "bottom": 16, "left": 16 },
"background": { "r": 255, "g": 255, "b": 255, "a": 1.0 },
"color": { "r": 17, "g": 17, "b": 17, "a": 1.0 },
"width": 320.0
}
Style → CSS é quase identidade
O Style foi desenhado copiando o vocabulário do CSS, então a tradução é
direta e vive no cliente (client/style.js) — um só tradutor para os dois
modos.
4. Evento (cliente → Python)¶
O lado Python resolve a key → handler do nó na árvore atual, valida o payload
com Pydantic e invoca o handler (sync ou async).
Enquadramento por transporte¶
O shape de Node/Patch/Evento não muda entre transportes; muda só o envelope:
Chamada de função em-processo via pyodide.ffi. Python passa a lista de
patches direto ao cliente; eventos voltam por callback. Sem rede, sem
envelope.
Cada mensagem WS é um JSON com kind:
O servidor responde text/event-stream. Cada tick é um evento SSE cujo
data: é o JSON da mesma lista de patches. Eventos sobem por HTTP POST
(corpo = Evento). Reconnect usa Last-Event-ID.
A chamada nativa (Modo B — proxy)¶
O 4º cruzamento. No Modo A uma capacidade native/ chama a Web API direto no
browser. No Modo B ela é proxiada por um round-trip:
// servidor → cliente: pedido de capacidade nativa
{ "kind": "native_call", "call_id": "c1", "capability": "geolocation.get", "args": {} }
// cliente → servidor: resultado tipado (ou erro)
{ "kind": "native_result", "call_id": "c1", "ok": true, "value": { "lat": -23.5, "lon": -46.6 } }
{ "kind": "native_result", "call_id": "c1", "ok": false, "error": "PermissionDenied" }
call_idcorrelaciona pedido ↔ resultado (várias chamadas podem estar em voo).capabilityé o nome estável (geolocation.get,clipboard.read, …).- O lado Python expõe isso como awaitable tipado — ver Capacidades.
A API Python é idêntica nos dois modos
No Modo A o mesmo await geolocation.get() resolve em-processo; no Modo B ele
dispara o round-trip native_call/native_result. Só o caminho muda — por
isso a assinatura tipada mora no contrato, não no transporte.
Recap¶
- O contrato é o mesmo nos três transportes; só o envelope difere.
- Quatro cruzamentos: IR → cliente, Evento → handler, Style → CSS, chamada nativa.
- Os shapes são fixados por golden fixtures derivadas do core real.
Para cada campo, leia o
docs/contract.md
canônico. Para ver o contrato em ação, faça o Tutorial. 🚀