Debugging Swaps

Use chirp check, debug headers, and DevTools to diagnose broken htmx, OOB, Suspense, and SSE updates

4 min read 736 words

Start With The Contract

When a page or fragment swaps incorrectly, run the contract checker before guessing from screenshots:

chirp check app:app

For CI or release branches:

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

chirp checkcatches the failures that most often turn into blank or wrong DOM: missing template blocks, route-name collisions, OOB registrations that no layout satisfies, route-directory metadata mismatches, reactive block typos, form-contract gaps, local Kida component-call mistakes, trusted-markup review points, sensitive-looking template output, and known htmx footguns.

Then Enable Debug Mode

Use the development CLI when possible:

chirp dev app:app

Or configure the app directly:

from chirp import App, AppConfig

app = App(AppConfig(debug=True))

Debug mode enables richer errors, template reloads, debug headers, fragment validator warnings, and the browser DevTools overlay.

Use Browser DevTools

Open the app and pressCtrl+Shift+Dto open Chirp DevTools.

For browser-capable agents, export records from the page:

window.ChirpHtmxDebug.help()
window.ChirpHtmxDebug.exportRecordsJson()

The exported records include htmx requests, errors, SSE connections and events, View Transition events, render plans, and Swap Doctor diagnostics. Use this when the visual symptom is vague but the request/target/render intent should be precise.

Symptom Table

Symptom Likely cause First check
A whole page appears inside a<div> Handler returnedTemplate(...)for an htmx request UsePage(...) or Fragment(...); debug fragment validator should warn
A section goes blank after a swap Targeted block is missing or rendered empty chirp check; verify the Fragment or Pageblock name
OOB update does nothing hx-swap-oobtarget id does not exist in the current layout Check the OOB registry and layout block ids
Suspense skeleton never resolves Deferred block was not discovered or mapped to the wrong target Checkdefer_blocks, defer_map, and block dependencies
Empty list looks like loading Template used{% if items %}for a deferred value Use{% if items is not none %} or __chirp_defer_pending__
SSE stream stops after one bad event Error boundary widened beyond one event CheckEventStreamgenerator and fragment render errors
Boosted link reloads the page Link crosses shell boundaries or boost is disabled CheckHX-Redirect, shell layout domains, and hx-boostinheritance
Duplicate shell actions or badges appear OOB region rendered inline and out-of-band Register the region and keep the DOM id in one owner

Kida-powered template diagnostics use their own categories:

Category Meaning Typical fix
component A local{% def %}call has unknown, missing, duplicate, or literal type-mismatched arguments Fix the call site or the component signature
template_context An opt-in dotted context contract does not cover what the template reads Add the missing path toprovided, move it to optional, or stop reading it
template_escape A template deliberately trusts markup, such as ` safe`, and should document the trust boundary Addsafe(reason="...")or remove the trust override
template_privacy A template reads sensitive-looking data such as tokens, secrets, or password paths Confirm the value belongs in rendered output or remove it

For dotted context checks, register template contracts through the existing check-data channel:

app.set_contract_check_data(
    "template_context_contracts",
    {
        "page.html": {
            "provided": {"page.title", "user.name"},
            "optional": {"flash.message"},
        }
    },
)

Read The Headers

Whendebug=True, Chirp can expose request and route context through headers:

  • X-Chirp-Route-Kind
  • X-Chirp-Route-Files
  • X-Chirp-Route-Meta
  • X-Chirp-Route-Section
  • X-Chirp-Context-Chain
  • X-Chirp-Shell-Context

These are useful when a filesystem route, section, shell mode, or layout chain does not match the route you thought was serving the request.

Keep The Return Type Honest

Most swap bugs reduce to the wrong return type:

Need Return
Full page only Template(...)
Full page for browsers, fragment for htmx Page(...)
One named block Fragment(...)
Main fragment plus extra regions OOB(...)
Initial shell plus deferred blocks Suspense(...)
Post-load long-lived updates EventStream(...)

If the response shape is unclear, start at Return Values.