{"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":"865db9f64ee33688ea78365399fe6ab6","page_id":"e63313db96834fceb59f044d0f4e14ff","address":""}

Keeping a page alive without anybody clicking

A dashboard that refreshes, a counter going down, polling a running task. An invisible component is enough.

Three parameters, and that is all

ParameterWhat it does
on_tickwhat fires — a server handler, or a string evaluated in place
secondsthe interval, in seconds (1.0 by default)
activea boolean OR a `ClientBinding` — that is what makes the cadence stoppable
# Server: the zone re-renders every 5 s.
@refreshable(deps=[Metrics])
def board() -> None:
    ui.text(f"{Metrics().running} running")

def sample() -> None:
    Metrics().running = count()

board()
ui.interval(on_tick=sample, seconds=5)

Stopping it — with no task to cancel

`active=` accepts a `ClientBinding`. A switch writes into the client state, the interval reads it, and the cadence stops — with no round trip, and with no server lifecycle needing to exist.
class Monitoring(ClientState):
    running: bool = field(default=True)

m = Monitoring()
ui.switch(value=m.running, label="Refresh")
ui.interval(on_tick=sample, seconds=5,
            active=m.running)

Without touching the server

`on_tick=` is polymorphic, like every `on_*`: a callable leaves as a signed POST, a STRING is evaluated in place. So a countdown needs no request at all.
class Countdown(ClientState):
    left: int = field(default=60)

c = Countdown()
ui.text(c.left)
ui.interval(on_tick=c.left.decrement(1),
            seconds=1, active=c.left > 0)
`active=m.remaining > 0` is an expression of the client algebra: it becomes JavaScript, re-evaluated on every change. So the timer stops by itself at zero.

What to know before using it

This is NOT a background task
The cadence lives in the PAGE. A closed tab stops running — which is exactly what one wants for a screen refresh, and exactly what one does not want for work that must finish. For that, it is `@background` — it survives the response, hence the visitor's departure.
The cost, in requests
Every tick of a server `on_tick=` is a request. A one-second interval across fifty open tabs makes fifty requests a second. When the data comes from the server and changes rarely, `@refreshable(broadcast=True)` costs less: it is the server that pushes, when it has something to say.
The zone that re-renders, and realtime:Server reactivity →Client reactivity →