What It Teaches
This is the same contacts CRUD app as Contacts, rebuilt on the chirp-ui app shell — a persistent page chrome (sidebar, topbar) that stays put while htmx swaps the content region. Reach for this version once you have outgrown isolated fragments and want filesystem-mounted pages, URL-backed search state, and shell-aware navigation.
It demonstrates:
use_chirp_ui(app)andapp.mount_pages()chirpui/app_shell_layout.htmlfor the persistent shell- route-scoped shell actions from
_context.py - query-backed search state
- inline row editing without stale filtered results
- typed repeated-field parsing with [[docs/build-apps/forms-data/forms-validation|
form_from()]]
When to Reach for It
Both contacts examples cover the same CRUD domain. Pick by the UI layer you need.
| Example | UI layer | Use it for |
|---|---|---|
| Contacts | Plain htmx, no shell | The fragment / OOB / validation baseline in a single template |
| Contacts Shell | chirp-ui app shell + mounted pages | Persistent chrome, filesystem pages, URL-backed search state |
Minimal Example
The whole app is about 50 lines:use_chirp_uiinstalls the shell,
mount_pageswires the filesystem routes, and one explicit route parses a
repeatedcontact_ids field with form_from.
import sys
from dataclasses import dataclass
from pathlib import Path
from chirp import App, AppConfig, Fragment, Request, form_from, use_chirp_ui
from chirp.middleware.csrf import CSRFMiddleware
from chirp.middleware.sessions import SessionConfig, SessionMiddleware
ROOT_DIR = Path(__file__).parent
PAGES_DIR = ROOT_DIR / "pages"
sys.path.insert(0, str(ROOT_DIR))
from chirp_ui import register_colors
from contacts_shell_store import GROUP_COLORS, reset_store, store
@dataclass(frozen=True, slots=True)
class ContactSelectionForm:
contact_ids: list[int]
config = AppConfig(template_dir=PAGES_DIR, debug=True)
app = App(config=config)
use_chirp_ui(app)
register_colors(GROUP_COLORS)
app.add_middleware(SessionMiddleware(SessionConfig(secret_key="contacts-shell-dev-secret")))
app.add_middleware(CSRFMiddleware())
reset_store()
app.mount_pages(str(PAGES_DIR))
@app.route("/contacts/selection", methods=["POST"])
async def select_contacts(request: Request):
form = await form_from(request, ContactSelectionForm)
selected = [contact for contact_id in form.contact_ids if (contact := store.get(contact_id))]
return Fragment("contacts/selection.html", "selection_preview", selected_contacts=selected)
if __name__ == "__main__":
app.run()
Source: examples/chirpui/contacts_shell/app.py.
Thecontact_ids: list[int]annotation is the point of the selection route:
form_from parses repeated contact_idsform fields into a typed list of ints.
Run It
PYTHONPATH=src python examples/chirpui/contacts_shell/app.py
Open http://127.0.0.1:8000/.
Test It
pytest examples/chirpui/contacts_shell/
Source
When this example is a useful contract fixture
This app exercises app-shell behavior end to end: route metadata, mounted pages, shell actions, boosted navigation, and fragment scopes all have to agree. It is a good fixture when you change pages, shells, route contracts, or chirp-ui-facing docs.