Arquitetura¶
tempestweb é o reconciliador renderer-agnostic do tempestroid com um
terceiro renderizador-folha (DOM) e dois transportes de patch (FFI
Pyodide e WebSocket/SSE). Esta página dá a visão geral didática; o documento
canônico, mantido junto ao código, é
docs/arquitetura.md.
A ideia central¶
Python tipado (view + state) ← idêntico nos dois modos
│
▼
core: IR (Pydantic) ──diff──► patches ← reusado do tempestroid
│
┌─────────┴──────────┐
Modo A: transporte Modo B: transporte
FFI Pyodide WebSocket / SSE
(no browser) (servidor → cliente)
└─────────┬──────────┘
▼
cliente JS (puro): aplica patches no DOM
+ tradutor Style → CSS + captura de eventos ← MESMO código nos dois modos
O reconciliador (Python) e o cliente JS são os mesmos nos dois modos. A única coisa que muda é a camada de transporte.
As quatro camadas¶
| Camada | O que faz | Onde vive |
|---|---|---|
| Core | IR, diff/patch, estado, estilo, widgets | pacote tempest-core (import tempest_core), extraído do tempestroid |
| Renderizador-folha | Aplica patches no DOM, traduz Style → CSS, captura eventos |
client/ — JavaScript puro |
| Transporte | Leva patches Python→JS e eventos JS→Python | tempestweb/transports/{wasm,websocket}.py + client/transport-*.js |
| Runtime / host | Hospeda o Python | Pyodide no browser (A) · FastAPI (B) |
A divergência entre os dois modos fica trancada na camada de transporte. Tudo acima (o app Python) e tudo abaixo (o cliente JS que muta o DOM) é compartilhado.
Por que o renderizador é o mesmo nos dois modos
Patches são dados puros serializados. O cliente JS só sabe consumir patch
e mutar o DOM — não liga de onde o patch veio. No Modo A, o patch chega por
uma chamada de função em-processo (pyodide.ffi); no Modo B, chega por uma
mensagem WebSocket. O byte do patch é o mesmo. Por isso client/dom.js e
client/style.js são escritos uma vez e servem os dois.
A costura de transporte¶
A interface de transporte abstrai a diferença entre os modos. No cliente:
// client/transport.js — contrato comum
// onPatches(callback): registra quem recebe listas de patches
// sendEvent(event): envia um evento (click/input) de volta ao Python
E no lado Python:
from typing import Protocol
class PatchTransport(Protocol):
"""Carries patches Python→client and events client→Python."""
async def send_patches(self, patches: list[dict]) -> None: ...
async def recv_event(self) -> dict: ...
transports/wasm.py implementa isso sobre pyodide.ffi;
transports/websocket.py sobre uma conexão WebSocket. Trocar de modo é trocar a
implementação — o view() do usuário não muda.
Style → CSS: o alvo mais fácil¶
No tempestroid, Style → Compose é a parte difícil, porque os vocabulários
divergem. Na web não há divergência: o Style do core já foi desenhado
copiando o CSS (flexbox, box model, tipografia). O tradutor Style → CSS é
quase identidade e vive no cliente JS — um só tradutor para os dois modos,
então não há como A e B discordarem na tradução de estilo.
Onde a tipagem "vaza" (o contrato)¶
Análogo ao request/response do FastAPI, o Pydantic valida três cruzamentos na fronteira Python↔cliente:
- IR → cliente — a árvore/patches serializados.
- Eventos → handlers — payloads do click/input validados antes de entrar no Python.
- Chamadas nativas — wrappers tipados sobre Web APIs, expostos como awaitables.
O schema é o mesmo nos dois modos; só o meio de transporte difere.
Regra de ouro de execução (detalhe)
O Python roda sobre um event loop asyncio.
- Modo A: o Pyodide integra o asyncio ao event loop do browser. Não há UI thread separada — trabalho pesado em Python trava a aba, então handlers devem ser async/leves.
- Modo B: cada conexão WebSocket tem sua sessão asyncio no servidor. Patches saem pela conexão, eventos entram por ela; o estado vive no servidor, isolado por cliente.
Conformância A ↔ B¶
Como o renderizador e o tradutor de estilo são únicos, o risco de divergência não
é de tradução, é de transporte: A e B não podem produzir DOM diferente para a
mesma view(). A suite de conformância fixa isso com golden snapshots, no CI — o
análogo web do Qt-vs-Compose do tempestroid.
Recap¶
- Quatro camadas: core, renderizador-folha, transporte, host.
- Tudo acima e abaixo do transporte é compartilhado; só o transporte muda entre os modos.
- O risco real é de transporte, não de tradução — daí a suite de conformância.
Para mergulhar no detalhe completo, leia
docs/arquitetura.md
e o Plano de design.
Para ver tudo isso na prática, vá ao Tutorial. 🚀