Custom route undeclared response
The corpus prior¶
LLM training data is full of web handlers that just return HTMLResponse("<html>…")
— a self-contained page that owns its whole document and layout. Ported into a Dazzle
project, that handler escapes the app shell (an hx-boost navigation swaps the returned
document into <body>, deleting the sidebar/nav) and, if it touches a domain entity,
silently bypasses the permit/scope model the rest of the app enforces. Both happen
because the handler declared nothing about what it returns or what it touches.
Wrong shape¶
# routes/dashboard.py
# dazzle:route-override GET /app/dashboard
async def handler(request):
rows = some_repo.all() # touches a domain entity, no RBAC binding
return HTMLResponse(f"<!doctype html><html>…{rows}…</html>") # owns the whole document
Nothing tells the framework this returns a full document (so it can't chrome it, and an
hx-boost nav deletes the shell), and nothing binds the entity access to permit/scope.
dazzle validate/lint pass; the failure shows up only at runtime.
Right shape¶
Declare BOTH — the response shape (your choice) and the RBAC binding (mandatory):
# routes/dashboard.py
# dazzle:route-override GET /app/dashboard
# dazzle:implements Report.list via report_id # RBAC: the mandatory line
# dazzle:returns fragment # response shape: live in the app shell
async def handler(request, report_id: str):
rows = some_repo.scoped(request) # permit/scope already ran (the binding)
return "<section>…rows…</section>" # inner HTML — the framework chromes it
- Want a deliberately full-bleed / novel UI (a kiosk, a fullscreen canvas, a page
hosting
islandcomponents)? Declare# dazzle:returns page— it is served as-is and never refused. Novel UI is welcome. - Returning a raw fragment for a targeted HTMX swap (not
#main-content)?# dazzle:returns partial. - Returning data?
# dazzle:returns json.
Why this matters here¶
Two guardrails, two postures — get them straight:
- RBAC is the line (mandatory). A domain-touching route declares
# dazzle:implementsor fails thedazzle rbac routes --strictmatrix-completeness gate (#1420/ADR-0040). Novel UI does not get to skip permit/scope. - Response shape + chrome are your declared choice (novel UI welcome). Declare
# dazzle:returnsto say what you return and whether you live in the shell. The framework enforces consistency with what you declared — afragment/partialthat returns a full<!doctype>is a loud error; apageis served untouched. An undeclared HTML override under/appgets a one-time advisory steering you to declare intent (#1392 item 2) — it is a nudge, never a block.