Chirp Examples

August 8, 2026 · View on GitHub

Examples are organized by runtime layer so the support matrix is obvious:

For the maintained app-owned template architecture, generate the executable example directly with chirp new modular_app. Its pages/ tree owns layouts and named full/fragment response blocks, while the explicit templates/ root separates typed Kida components, product patterns, and rare private partials. Generation does not change when chirp-ui is installed; request the compatibility example deliberately with --with-chirpui.

Learning path

TierExampleTeaches
1standalone/hello, standalone/contactsRoutes, forms, Page / Fragment
2chirpui/contacts_shellApp shell, _actions.py, _context.py
3chirpui/lucky_catSignals, Suspense, SSE, OOB capstone

Featured: chirpui/lucky_cat — flagship ChirpUI demo (tier 3). Not your first Chirp app — complete tiers 1–2 first.

For a database-backed path across the maintained examples, follow the full-application journey.

AI curious

Branch off after tier 1 if you want to stream LLM tokens to the browser:

StepExampleTeaches
Startstandalone/llm_minimalSimulated token streaming, TemplateStream vs EventStream, optional Ollama (chirp[ai])
Nextstandalone/ollamaReal local LLM, AgentRun, @app.tool(), live tool-call activity over SSE
In a shellchirpui/llm_playgroundThe same streaming inside a ChirpUI app shell

Streaming good-first issues (#454 and similar) should also:

  • keep app.check() / chirp check free of ERROR-severity issues
  • pair TemplateStream with plain form POST (full page), not hx-target swaps
  • pair EventStream with Fragment scaffolding and parametric sse-connect
  • test htmx paths with HX-Request when forms use hx-*
  • add @pytest.mark.issue(N) acceptance coverage when closing an issue

Which Bucket To Use

Use examples/standalone when you want to learn or validate:

  • raw Template / Fragment / EventStream
  • plain HTMX forms, fragments, and SSE
  • standalone Chirp behavior without delegation=True
  • data, auth, middleware, and API examples without ChirpUI dependencies

Use examples/chirpui when you want to learn or validate:

  • use_chirp_ui(app)
  • app-shell layouts and boosted navigation
  • delegation=True and shell-aware swaps
  • richer component-driven examples built on the newer UI layer

Running Examples

Run examples from the repo root so local src/chirp is always used:

# From the repo root:
source .venv/bin/activate
PYTHONPATH=src python examples/standalone/hello/app.py

For ChirpUI examples:

# From the repo root:
source .venv/bin/activate
PYTHONPATH=src python examples/chirpui/pages_shell/app.py

If you also need the local pounce checkout rather than an installed package:

PYTHONPATH=src:../pounce/src python examples/standalone/ollama/app.py

Pairings

Some examples intentionally come in pairs so you can compare the baseline and app-shell lanes:

  • examples/standalone/contacts and examples/chirpui/contacts_shell
  • examples/chirpui/forum_shell for a compact product-shaped ChirpUI shell with mounted pages, form contracts, OOB shell state, and JSON data islands; it is a regression fixture, not a full forum product
  • examples/standalone/kanban and examples/chirpui/kanban_shell
  • examples/standalone/islands and examples/chirpui/islands_shell

Notes

  • examples/AUDIT.md remains a shared cross-example document.
  • examples/conftest.py remains shared so example tests can still reuse common fixtures.
  • examples/__init__.py remains at the root because the examples tree is still treated as one test/import surface.

Patterns

Lessons from building these examples — things that aren't bugs but require intentional decisions from the developer.

Mark third-party HTML with | safe

Kida auto-escapes all template output by default. This is correct — it prevents XSS. But when you're rendering pre-formatted HTML from an external source (API responses, CMS content, markdown output), you need to explicitly opt out:

{# Bad: <p> tags rendered as literal &lt;p&gt; text #}
<div class="comment-text">{{ comment.text }}</div>

{# Good: HTML rendered as intended #}
<div class="comment-text">{{ comment.text | safe }}</div>

The | safe filter accepts an optional reason= parameter for documenting why the content is trusted:

{{ api_html | safe(reason="sanitized by HN API") }}
{{ rendered_md | safe(reason="generated by markdown parser") }}

Auto-escaping is a guardrail, not a sharp edge. The | safe call is your declaration that you've thought about trust.

Use a unique ID for your htmx swap target

When chirp swaps fragments into the page, the hx-target selector must resolve to exactly one element. A class like .container is a layout utility that may appear in headers, footers, and sidebars. Use a unique ID instead:

{# Bad: ambiguous — multiple .container elements on the page #}
<a hx-get="/story/1" hx-target=".container">...</a>

{# Good: one unambiguous target #}
<div id="main" class="container">...</div>
<a hx-get="/story/1" hx-target="#main">...</a>

This also matters for View Transitions — view-transition-name must be unique per page. Applying it to a class that matches multiple elements triggers a browser warning and breaks the transition animation.

When using app_shell_layout.html, chirpui-transitions.css automatically applies view-transition-name: page-content to #main and suppresses the root transition so the shell stays frozen. No custom VT CSS needed for the common case.

OOB + View Transitions: If #main (or similar) is a parent of elements that receive OOB swaps (e.g. hx-swap-oob), three things can go wrong:

  1. hx-target inheritancesse-connect inside #main inherits hx-target="#main". Fragments then swap into #main, wiping the whole list. Add hx-disinherit="hx-target hx-swap" on sse-connect.

  2. view-transition-name on the parent — triggers full-page transition when OOB updates arrive. Scope it to elements that change only on full navigation (e.g. .story-detail), not on parents of OOB targets.

  3. transition:true on the swap target — htmx wraps swaps in the View Transitions API. OOB swaps to children can trigger this and cause the whole content area to flicker/disappear. Put transition:true only on the links that trigger full navigation, not on the container (#main).

ID-based targeting is faster for the browser, unambiguous for htmx, and compatible with the CSS view-transition-name uniqueness requirement.

Running Tests

Each example has a test_app.py that verifies it works through the ASGI pipeline using chirp's TestClient. No HTTP server required.

# All examples
pytest examples/

# One standalone example
pytest examples/standalone/hello/

# One chirpui example
pytest examples/chirpui/pages_shell/

What Each Example Exercises

The table below is generated from inventory.toml. It is the authoritative catalog for lane, support status, learning tier, required extras, network access, capabilities, README coverage, and test entrypoints. Run python -m examples.inventory after editing the manifest.

  • canonical examples are recommended reference implementations for their capability set.
  • supporting examples are maintained, focused demonstrations rather than the default learning path.
  • experimental examples prove provisional or advanced patterns and should not be treated as framework defaults.

Network describes the runnable app: none is offline, optional has an offline default with an opt-in integration, and required needs an external or local service. Extras names installable bengal-chirp[...] extras and is checked against pyproject.toml. A missing README is recorded explicitly so it cannot disappear from the catalog while its documentation debt remains visible.

ExampleLaneStatusTierExtrasNetworkCapabilitiesREADMETests
chirpui/contacts_shellchirpuicanonical2sessions, uinoneapp-shell, chirpui, csrf, forms, fragments, pages, sessionsyescontacts_shell
chirpui/forum_shellchirpuicanonical3uinoneapp-shell, chirpui, contracts, forms, oob, pagesyesforum_shell
chirpui/islands_shellchirpuisupporting2uinoneapp-shell, chirpui, islandsyesislands_shell
chirpui/kanban_shellchirpuicanonical3auth, sessions, uinoneapp-shell, auth, chirpui, csrf, forms, fragments, oob, sessions, sse, validationyeskanban_shell
chirpui/llm_playgroundchirpuisupporting3ai, markdown, uirequiredai, app-shell, chirpui, fragments, sse, streamingyesllm_playground
chirpui/lucky_catchirpuicanonical3auth, passkeys, sessions, uinoneapp-shell, auth, chirpui, csrf, data, forms, fragments, mutations, oob, pages, passkeys, reactive, sessions, sse, suspense, validationyeslucky_cat
chirpui/pages_shellchirpuicanonical2uinoneapp-shell, chirpui, pages, routingyespages_shell
chirpui/rag_demochirpuicanonical3ai, markdown, sessions, uirequiredai, app-shell, chirpui, data, fragments, sessions, sse, streamingyesrag_demo
chirpui/shell_oobchirpuicanonical2uinoneapp-shell, chirpui, oob, pagesmissingshell_oob
chirpui/sortable_reorderchirpuisupporting2sessions, uinoneapp-shell, chirpui, csrf, fragments, mutations, sessionsyessortable_reorder
standalone/accessibilitystandalonesupporting2noneaccessibility, forms, validationyesaccessibility
standalone/apistandalonesupporting1noneapi, middleware, routingyesapi
standalone/authstandalonecanonical2auth, sessionsnoneauth, forms, pages, security, sessionsyesauth
standalone/chatstandalonesupporting2sessionsnonefragments, sessions, sseyeschat
standalone/contactsstandalonecanonical1noneforms, fragments, mutations, oob, pages, validationyescontacts
standalone/custom_middlewarestandalonesupporting2nonemiddleware, routingyescustom_middleware
standalone/dashboardstandalonesupporting2nonefragments, sse, suspenseyesdashboard
standalone/dashboard_livestandalonesupporting3nonedata, fragments, sse, suspenseyesdashboard_live
standalone/devtools_htmx4standalonesupporting2requiredcontracts, fragments, islands, oob, view-transitionsyesdevtools_htmx4
standalone/docs_sitestandalonecanonical2markdownnonedocs, routing, toolsmissingdocs_site
standalone/form_getstandalonesupporting1noneforms, routingyesform_get
standalone/freeze_sitestandalonecanonical2markdownnonedocs, freeze, pagesmissingfreeze_site
standalone/hackernewsstandalonesupporting3testingrequiredfragments, sse, view-transitionsyeshackernews
standalone/hellostandalonecanonical1noneroutingyeshello
standalone/htmx_managedstandaloneexperimental2nonecontracts, fragmentsyeshtmx_managed
standalone/islandsstandalonesupporting2noneislandsyesislands
standalone/islands_swapstandaloneexperimental2nonefragments, islandsyesislands_swap
standalone/kanbanstandalonecanonical3auth, sessionsnoneauth, csrf, forms, fragments, oob, sessions, sse, validationyeskanban
standalone/llm_minimalstandalonecanonical1optionalai, fragments, sse, streamingyesllm_minimal
standalone/llm_streaming_kidastandalonesupporting2optionalai, streamingyesllm_streaming_kida
standalone/milo_mcp_appsstandaloneexperimental3nonecontracts, pages, toolsyesmilo_mcp_apps
standalone/mutation_resultstandalonesupporting2nonefragments, mutations, pagesyesmutation_result
standalone/nojs_floorstandalonecanonical2noneforms, fragments, mutations, no-js, pages, validationyesnojs_floor
standalone/ollamastandalonecanonical3ai, markdownrequiredai, fragments, sse, streaming, toolsyesollama
standalone/oob_layout_chainstandaloneexperimental3nonecontracts, oob, pagesyesoob_layout_chain
standalone/optimistic_applystandaloneexperimental3nonefragments, islands, mutationsyesoptimistic_apply
standalone/orrerystandalonecanonical3sessions, skillnonecontracts, csrf, fragments, pages, security, sessions, sse, toolsyesorrery
standalone/passkeys_minimalstandalonesupporting3auth, passkeys, sessionsnoneauth, csrf, forms, passkeys, security, sessionsyespasskeys_minimal
standalone/pokedexstandalonesupporting2noneapi, data, middleware, pagesyespokedex
standalone/productionstandalonecanonical3sessionsnonecsrf, forms, security, sessionsyesproduction
standalone/query_searchstandalonecanonical3optionalforms, fragments, no-js, pages, query, routing, validationyesquery_search
standalone/reactive_tasksstandalonecanonical3nonefragments, reactive, sse, validationyesreactive_tasks
standalone/returns_gallerystandalonecanonical2nonefragments, mutations, oob, pages, sse, streaming, suspense, validationyesreturns_gallery
standalone/searchstandalonesupporting1noneforms, pages, routingyessearch
standalone/shapes_workspacesstandaloneexperimental3nonecontracts, data, forms, pagesyesshapes_workspaces
standalone/signupstandalonesupporting2sessionsnonecsrf, forms, sessions, validationyessignup
standalone/ssestandalonecanonical2nonefragments, sseyessse
standalone/sse_reconnectstandalonesupporting3nonefragments, sseyessse_reconnect
standalone/static_sitestandalonesupporting2nonesse, static-filesyesstatic_site
standalone/streamingstandalonecanonical2nonestreamingyesstreaming
standalone/surveystandalonesupporting2noneforms, validationyessurvey
standalone/suspense_dashboardstandalonesupporting2nonesuspenseyessuspense_dashboard
standalone/themingstandalonesupporting1nonepagesyestheming
standalone/todostandalonecanonical2sessionsnonecsrf, data, forms, fragments, mutations, no-js, pages, security, sessions, validationyestodo
standalone/toolsstandalonesupporting2nonefragments, sse, toolsyestools
standalone/tools_hitlstandaloneexperimental3ai, sessionsnoneai, csrf, fragments, sessions, sse, toolsyestools_hitl
standalone/uploadstandalonesupporting2forms, sessionsnonecsrf, forms, sessions, uploads, validationyesupload
standalone/webmcp_formstandaloneexperimental2sessionsnonecsrf, forms, fragments, mutations, sessions, validation, webmcpyeswebmcp_form
standalone/wizardstandalonesupporting2sessionsnoneforms, pages, sessions, validationyeswizard