Hot reload for local development

Hot reload here means file-watched process restart, not in-process module hot-swap. stario watch restarts the server when matched files change, using the same MODULE:bootstrap entry as stario serve.

bash
uv run stario watch main:bootstrap

Examples below omit uv run when stario is already on your PATH; otherwise keep uv run stario …. That keeps one composition root—your bootstrap(app, span)—and lets the CLI own process lifecycle and reload.

Watch paths (--watch / --watch-ignore)

watch uses watchfiles (install it if the CLI tells you to). You control what triggers a restart:

  • --watch — add a file, directory, or glob (repeat the flag for multiple roots). If you omit --watch, the CLI watches the current directory (.).

  • --watch-ignore — exclude paths or globs (repeatable). SQLite database files are ignored by default (*.sqlite3, *.db, …).

Examples (same as the CLI epilog):

bash
stario watch main:bootstrap --watch app/
stario watch main:bootstrap --watch main.py
stario watch main:bootstrap --watch '**/*.py' --watch-ignore 'data/'
stario watch main:bootstrap --watch-ignore '*.sqlite3'

End directories with / when the folder does not exist yet (so the watcher can still attach to the parent). All other serve / watch flags (--host, --port, --tracer, …) apply the same way as stario serve.

Example

A useful development-only pattern: keep a long-lived SSE connection (for example a dev stream or a Datastar GET that stays open). When watch restarts the server, that connection drops. On the new process, you can handle the next client connection by streaming a full HTML document (from <html> through </html>) over SSE using ds.sse.patch_elements with selector="html" and mode="outer".

You need all of the following before the one-liner below makes sense:

  • A route that holds an SSE response open—usually async with w.alive(): around your send loop (Responses — alive()).

  • After reload, the next connection should receive a freshly rendered full HTML document (the same shell you would return from a normal page GET) so the client can resync without a manual refresh.

  • full_page in the snippet is that document: bytes, str, or HTML nodes for <html>…</html>.

python
from stario import datastar as ds
 
# full_page: bytes/str or html nodes for the whole document (<html>…</html>).
# Replaces the root so the tab resyncs without a manual refresh after a dev reload.
ds.sse.patch_elements(w, full_page, selector="html", mode="outer")

Wire this only where it makes sense (for example a dev-only route or a flag in bootstrap). It is not a substitute for normal navigation or production error recovery.

Use mindfully:

  • Scope — Treat this as a dev ergonomics trick, not a product feature, unless you fully own the UX implications.

  • Surprise — Replacing the whole document can reset client state, scroll position, and focus; only do it when you intend a full resync after reload.

  • Production — Do not depend on “SSE replaces the world” for real users without the same care you’d give any hot reload or live-edit system; prefer ordinary page loads or targeted patches for shipped behavior.