One question at a time
Chirp has five mechanisms that move HTML after (or while) a page loads. Pick by when the update happens and how many targets it must hit in one round trip — not by habit.
| Situation | Reach for | Transport |
|---|---|---|
| Initial render, slow sections, one HTTP trip, shell paints first | Suspense |
Single chunked response + OOB swaps |
| Initial render, SEO-heavy sections, no shell-first step | Stream |
Single chunked response |
| Post-load live region on one page (chart, book, tape) | EventStream + sse_scope() |
Long-lived SSE on that page |
| Cross-page chrome, fan-out, or post-mutation push to many bindings | signal() on /_chirp/live |
One shared SSE connection |
| Multi-target mutation response in the same round trip | OOB / FormActionfragments |
Normal htmx POST response |
Rule of thumb: initial render that streams →Suspense (or Streamwhen you
want progressive first byte without a skeleton shell). Updates after the page is
live →EventStream for page-local regions, signal()when the same value must
update chrome on every page. Several DOM targets from one POST →OOB.
Transport × client shape (LLM and chunked answers)
TemplateStreamalways renders a whole template file over chunked HTTP.
It is not a fragment return type. Pair transport with the client wiring:
| Full-page client | htmx swap client | |
|---|---|---|
Chunked HTTP (TemplateStream) |
plain<form method="post"> |
not supported — nests a document inside#target |
SSE (EventStream) |
rare | Fragment scaffold + parametric sse-connect |
Chirp warns on the bad pairing via thetemplate_stream_client_shapecontract.
See Streaming answers
for the three safe recipes and Contract categories.
See also Return values for the full type reference and Streaming HTML & Suspense for template patterns.
Lucky Cat map
The flagship Lucky Cat example uses all five on purpose — use it as a worked map, not as the default for a first app.
| Feature | Route / trigger | Mechanism | Why this one |
|---|---|---|---|
| Portfolio dashboard | GET /portfolio |
Suspense |
Shell + six deferred panels, one trip |
| Market detail live blocks | GET /markets/{symbol}/stream |
EventStream |
Page-local chart/book/tape after load |
| Free-threading proof panel | GET /ft/stream |
EventStream |
Same — one page, one live region |
| Topbar ticker, balance, bell | /_chirp/live |
signal() |
Cross-page chrome; one connection |
| Markets lobby board | /_chirp/live(derived cascade) |
signal() |
One snapshot → three regions in lockstep |
| Deposit / trade fill | POST /deposit, POST /trade/order |
signal()emit + 204 |
Balance/bell update without response body |
| Watchlist star toggle | POST /watchlist/toggle |
OOB |
Star + rail count (+ optional card delete) |
| Trade fill (positions table) | POST /trade/order |
FormAction+ OOB fragments |
Form reset + table + toast same POST |
| Trending / Research grids | GETwith htmx |
Page / Fragment |
Snapshot per swap — no live re-rank |
Doctrine and footguns for the shell live in the example'sDESIGN.md§4 and §7.
Start simpler
Before combining all five in one app, learn each mechanism in isolation:
- Suspense dashboard — one deferred panel
- SSE — minimal post-load stream
- Contacts shell — boosted nav +
_actions.py - Lucky Cat — capstone that composes everything