Skip to content
LoboGuardian 🐺 edited this page Jul 11, 2026 · 7 revisions

HTeaLeaf introduces a reactive state layer inspired by modern frontend frameworks.
It allows you to define global or user-specific state directly in Python, and bind it to HTML components that update automatically when the state changes.

HTeaLeaf includes a full server-driven reactivity system built around three components:

  • Store: Global shared state
  • AuthStore: Per-user state bound to sessions
  • SuperStore: The orchestrator that connects and synchronizes stores through HTTP and JavaScript

What is a Store?

A Store is a shared, reactive state container that lives on the server side and synchronizes with the client through HTeaLeaf’s reactivity engine.

from htealeaf import Store

cstore = Store({"counter": 1})

Each Store behaves like a reactive dictionary: read values server-side with .read(path) and mutate them from the browser through the .js namespace. After a client-side mutation, the page is re-fetched and the DOM is reconciled, so every element rendered from store data updates automatically. Each Store gets a unique auto-generated id, so several stores can coexist without collisions.

Navigating store paths

HTeaLeaf stores can be accessed using path-like keys, similar to JSON pointers. This allows deep updates inside nested data structures such as lists or dictionaries.

For example:

store = Store({"todo": [{"value": "Buy milk", "done": False}]})

This is used by the JavaScript JSCode helper:

store.js.update("todo/0/done", True)

Example: Reactive counter

@app.route("/counter")
def counter():
    return div(
        button("-").attr(onclick=cstore.js.update("counter", cstore.read("counter") - 1)),
        h3(cstore.read("counter")),
        button("+").attr(onclick=cstore.js.update("counter", cstore.read("counter") + 1)),
    )

AuthStore: per-user reactive state

AuthStore behaves just like Store, but each user Session has its own isolated copy.

from htealeaf import AuthStore
from htealeaf.server import SessionData

def auth_session(session: SessionData):
    if session.has("userName"):
        return session["userName"]
    return None

todoStore = AuthStore(auth_session, {"todo": []})

Each authenticated user gets their own instance of the todo data, independent from others.

⚠️ If the auth function returns None, the user is not rejected — all unauthenticated users share a single store keyed on None. Gate access in your route handler (e.g. redirect to a login page) rather than relying on the auth function to deny access.

from htealeaf.js import js
from htealeaf.js.common import document
from htealeaf.server.utils import redirect

@app.route("/")
def home(session):
    if not session.has("userName"):
        return redirect("/login")

    @js
    def addTask(inputId):
        val = document.getElementById(inputId).value
        todoStore.set("todo", {"done": False, "value": val})

    return div(
        [todoItem(idx, t) for idx, t in enumerate(todoStore.auth(session).read("todo"))],
        textInput().id("todo_item"),
        button("Add Task").attr(onclick=addTask("todo_item")),
    )

Each logged-in user interacts with their own store content, persisted in memory and linked to their session ID. Note the two sides of the boundary: server-side reads use todoStore.auth(session).read(...), while inside a @js body the store is referenced directly (todoStore.set(...)) and the transpiler rewrites it to the client-side call.

SuperStore: the orchestrator

SuperStore acts as the reactivity controller for all stores. It connects them to the app and automatically registers endpoints for synchronization and data mutation.

from htealeaf import HteaLeaf, SuperStore, adapters

app = HteaLeaf(adapters.WSGI)
SuperStore(app)

Once initialized:

  • All Store and AuthStore instances become discoverable.
  • The framework exposes internal HTTP endpoints for reading and writing store data.
  • The Store.js objects (injected into the frontend) communicate with these endpoints.

You typically only need one SuperStore per application.

How SuperStore works

When the client triggers an update, like:

store.js.update("todo/0/done", True)

translated in the frontend to:

store_xyz.update("todo/0/done", true)

the call is routed to an automatically exposed backend endpoint, typically something like

PATCH /api/_store/{api_id}/todo/0/done

(set maps to POST, update to PATCH, delete to DELETE, and get to GET.)

SuperStore:

  1. Parses the path (todo/0/done).
  2. Locates the right Store or AuthStore instance.
  3. Applies the mutation, after which the client re-fetches the page and reconciles the DOM.

This allows fully reactive data flow over plain HTTP, no WebSockets required.

Example: Complete setup

from htealeaf import HteaLeaf, SuperStore, Store, AuthStore, adapters
from htealeaf.server import SessionData

app = HteaLeaf(adapters.WSGI)
SuperStore(app)  # register the orchestrator

globalStore = Store({"counter": 0})

def auth_session(session: SessionData):
    return session["userName"] if session.has("userName") else None

todoStore = AuthStore(auth_session, {"todo": []})

After this, globalStore and todoStore are automatically exposed and reactive.

Data flow summary

+-------------+       +----------------+       +---------------+
|  Python App | <---> |  SuperStore    | <---> |  Client (JS)  |
+-------------+       +----------------+       +---------------+
       ▲                       ▲                        ▲
       |                       |                        |
       |     Store updates     |   HTTP fetch/update    |
       |-----------------------|------------------------|
  • SuperStore handles orchestration, routing, and propagation.
  • Store / AuthStore define the reactive data models.
  • JSCode translates Python actions into client-side JS that calls the correct store endpoints.

Next: Session

Clone this wiki locally