This post is about one of the cleaner interactions in the stack: Chirp does not hard-code which regions to update out-of-band. It asks the template.
When you add {% region sidebar_oob(current_path="/") %}...{% end %} to your layout, Chirp discovers it at build time. No config file. No registry. The template declares; Chirp follows.
Most frameworks require the opposite: you register OOB regions in a config file or a central registry. Add the block to your template, then wire it up elsewhere — in settings.py, a route decorator, or a component registry. Two places to update. Forget one, and the region is invisible to the framework. Chirp inverts that: the template is the source of truth.
That is possible because Kida compiles templates to an AST and exposes template_metadata(): blocks, regions, dependencies, and cache scope. Chirp consumes that metadata to build a LayoutContract per template. On boosted navigation, it renders the page fragment plus the matching OOB regions as hx-swap-oob updates.
The flow
Template source → Kida parser → AST → Analyzer → TemplateMetadata
↓
Chirp build_layout_contract() → LayoutContract
↓
OOB region list (cache_scope, depends_on per block)
If you do not care about the internals, the important idea is simple: the template is the source of truth for which fragments exist and when they are safe to render.
-
Kida parses and analyzes the template.
TemplateMetadatahasblocks,regions(), and per-blockBlockMetadatawithdepends_on,cache_scope,is_region. -
Chirp calls
adapter.template_metadata(layout_template)on the root layout. -
build_layout_contract() discovers blocks named
*_oob(or region blocks with that suffix), extractscache_scopeanddepends_onfrom each, and builds a cachedLayoutContract. -
On boosted navigation (HTMX
hx-boost), Chirp renders the page block and the OOB regions. Each region getshx-swap-oob="true"and targets the right DOM id. -
On full page load, the entire layout renders normally. OOB regions are suppressed — they're only for fragment updates.
Regions: one definition, two uses
Kida's {% region %} compiles to both a block, for render_block(), and a callable, for {{ name(args) }}:
{% region sidebar_oob(current_path="/") %}
{% call sidebar() %}
{{ sidebar_link("/", "Home", active=current_path == "/") }}
{{ sidebar_link("/settings", "Settings", active=current_path.startswith("/settings")) }}
{% end %}
{% end %}
- In the app shell slot:
{{ sidebar_oob(current_path=current_path | default("/")) }}— callable, renders inline. - For OOB updates:
render_block("sidebar_oob", current_path=...)— block, rendered ashx-swap-oobfragment.
One definition. No duplication. Chirp discovers it via meta.regions() and filters by the *_oob suffix.
Metadata in action
| BlockMetadata field | Chirp use |
|---|---|
blocks |
Block existence, OOB discovery fallback |
regions() |
Prefer region-typed OOB blocks |
depends_on |
Skip OOB when required context missing |
cache_scope |
Future: cache page-level fragments |
is_region |
Filter to region blocks for OOB |
When template_metadata is unavailable, such as through a Jinja2 adapter, Chirp falls back to well-known ChirpUI OOB blocks. The adapter contract is intentionally loose, blocks plus optional regions(), so other template engines could plug in.
Why it matters
The reader payoff is straightforward:
Declarative: Add a region to the template and Chirp picks it up. No framework config.
Extensible: New OOB regions require only the template change and a DOM id mapping. The LayoutContract is built from the AST.
Type-safe: Block validation uses meta.blocks before render. Missing block means a KeyError at plan time, not a silent empty fragment.
Zero duplication: Regions serve both app shell slots and OOB updates. One definition, two render paths.
Further reading
- Chirp — Kida Integration — full doc with diagrams
- Kida — Framework Integration — Chirp as consumer
- Static Analysis for Templates — what Kida can do that Jinja2 can't