{"version":"v1.0","client_state":{"DocsNavigation.default":{"fields":{"query":""},"persist":"memory","send_to_server":true},"ColorScheme.default":{"fields":{"mode":"system"},"persist":"local","send_to_server":true}},"endpoints":{"action":"/_bretzel/action","sse":"/_bretzel/sse","refetch":"/_bretzel/refetch"},"csrf":"946b07952ae588366ff8aa0aae604983","page_id":"5be5463c4ef1433ab9cfa34cf0df6f9e","address":""}

Server reactivity

Once a handler has changed the state (cf.Server actions), here is what re-renders on screen, and how to control it.

The automatic re-render

A `@refreshable(deps=[…])` zone declares the states it reads. When a handler mutates one of them, the zone is re-rendered automatically — no refresh to call.
@refreshable(deps=[Cart])
def cart_summary() -> None:
    ui.text(f"{len(Cart().items)} items")


def add_to_cart(product_id: str) -> None:
    Cart().items.append(product_id)
    # cart_summary re-renders: Cart is in its deps

Mutation = re-render

At the end of an action, the server compares the state before and after — including in-place modifications (`items.append(...)`, `d[k] = v`). Every zone whose `deps=` changed is re-rendered and sent back to the browser.Conversely, if a handler changes no state declared in a `deps=`, no zone is re-rendered — the round trip brings back nothing to refresh.

Triggering by hand — refresh()

`refresh(zone)` forces a zone's re-render. Useful when the change does not come from a declared state, or from somewhere that is not an action handler. It can also be addressed by its `name`.
from bretzel import refresh

def reload_prices() -> None:
    fetch_latest()
    refresh(price_table)          # by the zone
    # or: refresh("price_table")  # by its name=

Realtime between clients — broadcast

`deps` and `broadcast` are two ORTHOGONAL lists, and the question they ask is the same: who changes this state?
• ME → `deps`. The zone is re-rendered in the action's response: one round trip, one swap.• THE OTHERS → `broadcast`. An SSE signal, then a refetch: two round trips, but the tab that did nothing follows.• BOTH → both lists. It is not a redundancy: it says “instant for me, pushed to the others”.
@refreshable(deps=[Cart])                     # local
@refreshable(broadcast=[Queue])               # I never
                                              # change it
@refreshable(deps=[Presence], broadcast=[Presence],
             name="online_users")             # both
What crosses is only a SIGNAL: every client refetches in its own context, no data passes from one client to another. `name=` gives a stable address for `refresh("…")`.

What next

The reactivity that lives in the browser, with no round trip:Client reactivity →