OOB Registry & Fail-Loud Rendering

Register out-of-band shell regions, mark them optional, and fix BlockNotFoundError

Page actions AI-ready formats and sharing
Open LLM text
Share with AI
Ask Claude Ask ChatGPT Ask Gemini Ask Copilot

Overview

You added a shell region — breadcrumbs, a sidebar, a document title — and on boosted navigation it either raisesBlockNotFoundError or silently vanishes from the page. This page shows you how to register an out-of-band (OOB) region, when to mark it optional, and how to read the error.

The OOB registry is your app's map from a template block name (likebreadcrumbs_oob) to the DOM element it swaps into. Chirp checks that map at startup and again at render time, so a missing region fails loudly instead of wiping live DOM content.

Register a region

Callapp.register_oob_region() during setup, before app.run():

app.register_oob_region(
    "breadcrumbs_oob",
    target_id="chirpui-topbar-breadcrumbs",
    swap="innerHTML",
)

The three kwargs you set in practice:

Kwarg Default What it does
target_id required The DOM id the OOB fragment updates.
swap "innerHTML" The htmx swap strategy.innerHTML and outerHTMLare the common ones; the validator accepts any htmx strategy plus modifiers.
optional False WhenTrue, layouts may legitimately omit this block. See below.

Defining the matching block in your layout is part of the [[docs/build-apps/ui-extensions/app-shell|app-shell{% region %} pattern]] — one definition serves both full-page renders and OOB swaps. Plain {% block name %}...{% endblock %}works too when you don't need the dual output.

FixBlockNotFoundError

The error names the block, the template, and the target DOM id. Work down these in order — the first one is the right fix the vast majority of the time.

  1. 1

    Add the block to the layout

    The registry says you promised this region exists. Add it to the layout template the error names:

    {% region breadcrumbs_oob(breadcrumb_items=[]) %}
      {% if breadcrumb_items %}
        <nav aria-label="breadcrumb">...</nav>
      {% end %}
    {% end %}
    

    This is the fix when the cause is a missing layout region or a typo in the block name.

  2. 2

    Mark the region optional

    If the region is genuinely absent from some layouts by design — a shell concern defined in a framework-level layout that custom routes skip — passoptional=True. The render path then drops the region instead of raising. See when to mark a region optional.

  3. 3

    Remove the registration

    If nothing uses the region anymore, delete theregister_oob_regioncall. A startup WARNING about an optional orphan is the signal that a registration has gone stale.

When to mark a region optional

Mark a regionoptional=True only when it is expected to be absent from some layouts. The canonical case is chirp-ui's shell regions: an app using the full app-shell layout defines breadcrumbs_oob and sidebar_oob, but a bare custom layout may not — and that is fine.

app.register_oob_region(
    "breadcrumbs_oob",
    target_id="chirpui-topbar-breadcrumbs",
    optional=True,   # shell region; bare custom layouts may omit it
)

Catch it before users do

app.check() runs the oob_registrycontract check at startup. For each registered block it walks every layout template and confirms at least one defines a matching block. An orphaned non-optional registration is an ERROR, so gate CI on it and a missing region fails the build, not production.

The canonical CI gate is the CLI —chirp check loads your app, runs every contract check, and exits non-zero on any ERROR (add --warnings-as-errorsto fail on optional-orphan WARNINGs too):

chirp check myapp:app --warnings-as-errors

To assert the same thing from a pytest suite, call app.check() on a frozen app. It prints the report and raises SystemExit(1) on any ERROR (it returns None— it does not hand back a list of issues), so the test passes only when the contract is clean:

import pytest

def test_app_contracts():
    app = make_app()  # build and freeze your app
    try:
        app.check()
    except SystemExit as exc:
        pytest.fail(f"app.check() reported contract errors (exit {exc.code})")

What changed in 0.5

Changed in 0.5

Earlier versions silently swallowed a region update whose block didn't exist: the region was emitted withhtml="", which wipes the target element's DOM content on every boosted navigation. Bugs like "my breadcrumbs keep disappearing" were impossible to trace from the server. Fail-loud rendering surfaces them at startup (the contract check) and at render time (the exception) instead.