Debugging Swaps

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

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

When an htmx swap paints the wrong thing — a whole page inside a<div>, a section that goes blank, an out-of-band update that does nothing, a Suspense skeleton that never resolves — work through this page top to bottom: run the contract checker, turn on debug mode, then read the symptom table. Most swap bugs trace to one wrong return type.

Diagnose in order

  1. 1

    Start with the contract

    Before guessing from screenshots, run the contract checker:

    chirp check app:app
    

    For CI or release branches, fail the build on warnings too:

    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, and known htmx footguns. For the full list of categories and their severities, see contract categories.

  2. 2

    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, the fragment validator (debug_fragment_validator, on by default), and the browser DevTools overlay.

  3. 3

    Open browser DevTools

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

    For browser-capable agents, export records straight 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. Reach for this when the visual symptom is vague but the request, target, and 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(...); the 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 deferred %}before testing resolved values
SSE stream stops after one bad event Error boundary widened beyond one event Check theEventStreamgenerator and fragment render errors
SSE events arrive but DOM never updates sse-swapevent name mismatch or swap on the connect element Runchirp check for sse_crossref / sse_self_swap; use assert_sse_wired in tests; put sse-swapon a child sink
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

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.