Skip to content

bengal-chirp 0.5.0

Choose a tag to compare

@lbliii lbliii released this 23 Apr 14:05
· 113 commits to main since this release
bf09e10

Chirp 0.5.0

Focus: tighten every silent-failure mode into an explicit error, expand static analysis, and adopt chirp-ui 0.5 + kida 0.7 strict defaults.

This is a pre-1.0 release with three breaking behavior changes. Each has a transitional opt-out; see Migration below.

Breaking

SSE default event name

EventStream now emits yielded Fragment payloads as unnamed SSE frames (htmx's default message channel). Previously the wire frame defaulted to event: fragment, requiring every consumer to add sse-swap="fragment" — a chirp-specific quirk that broke stock htmx-sse snippets.

Migration: change sse-swap="fragment" to sse-swap="message" in templates (htmx-sse needs the attribute present to wire up the listener). The chirp/sse.html macro now defaults to swap="message". To keep the old wire shape, yield SSEEvent(data=rendered, event="fragment") or pass target="fragment" on the Fragment.

AppConfig.strict_undefined = True by default

Templates referencing a missing attribute/key raise UndefinedError instead of silently rendering empty, matching kida 0.7's new default.

Migration: fix callsites with {{ obj.attr ?? "" }}, {{ obj.attr | default("") }}, or {% if obj.attr is defined %}…{% end %}. Transitional opt-out: AppConfig(strict_undefined=False).

Orphan OOB registrations now ERROR at freeze

app.check() emits Severity.ERROR (was WARNING) when the OOB registry contains a block no layout template defines. Non-optional orphans block debug-mode freeze.

Migration: add optional=True to register_oob_region() for regions that legitimately appear in only some layouts, or add the missing {% region <name> %}…{% end %} to the layout. Global opt-out: app.override_contract_severity("oob_registry", Severity.WARNING).

Highlights

Fail-loud OOB regions

execute_render_plan used to swallow every exception during OOB region render and substitute html = "" — silently wiping DOM content when a block was missing. Missing blocks now raise chirp.errors.BlockNotFoundError (multi-inherits from KeyError for back-compat); render errors propagate to the route error handler. See docs/guides/oob-registry.md.

New: register_oob_region(..., optional=True) — opt-out for shell regions that legitimately appear in only some layouts. chirp.ext.chirp_ui's auto-registered breadcrumb, sidebar, title, and shell-actions regions all default to optional=True.

Alpine on streaming HTML

AlpineInject now rewrites StreamingResponse bodies (e.g. Suspense shells) to insert the Alpine bundle before </body>, with the same deduplication as buffered pages. Alpine-powered components light up on streamed pages without manual script tags.

New template global: alpine_json_config("my-id", data) emits a safely-escaped <script type="application/json"> tag for wiring Alpine components to server data.

Suspense — explicit deferred blocks

Suspense.defer_blocks — optional explicit list of blocks to re-render as OOB chunks, bypassing block_metadata() static analysis. Use when deferred values are passed through macro arguments the analyzer can't trace. Shell renders now inject __chirp_defer_pending__ (a frozenset of unresolved context keys) so templates can branch on membership instead of overloading None / truthiness. Ancestor blocks whose depends_on is a strict superset of a matched leaf are now pruned, preventing wasted OOB chunks.

Native fragment blocks

Chirp now recognises kida 0.6.0+'s native {% fragment name %}…{% end %} directive as a swap-only target: the block body is suppressed during full-template renders and only emits when addressed by Fragment(...), the /_frag{path} dispatcher, or a {% fragment %} target in a PageShellContract. Use this instead of the {% block %} + {% if foo is defined %} workaround for success panels, SSE payloads, and OOB swap targets. Contract rules updated to treat fragment=True blocks as unreachable-by-design.

Expanded contract checks

chirp check gained four new rules:

  • SSE event cross-reference (AST-based) — walks each @contract(returns=SSEContract(...)) handler for literal SSEEvent(event="…") and Fragment(target="…") yields. A sse-swap="X" that matches neither the declared event_types nor any inferred literal is now ERROR at startup (was WARNING) — the silent-mismatch class of bug (htmx drops unmatched events) fails loud.
  • Form action contract — reports <form action="/path" method="post"> targets that lack a FormContract declaration.
  • Layout HX-Target + outletLayoutChain.find_start_index_for_target matches {# outlet: element_id #} as well as {# target: #}, so boosted HX-Target: #main resolves for chirp-ui app shells.
  • OOB target contract — warns when hx-swap-oob elements reference IDs not found in any template.
  • Reactive contract rules — validates that DependencyIndex block references point to real template blocks and that derivation graphs are acyclic.

chirp-ui 0.5 + kida 0.7

Bumps chirp-ui>=0.5.0 (agent-grounding manifest, composite contract tests, @layer chirpui.* cascade public API, set_strict("auto") + CHIRP_UI_DEV) and kida-templates>=0.7.0 (strict_undefined=True default, Jinja2 parser hints). use_chirp_ui(app, strict="auto") delegates to the CHIRP_UI_DEV env var so dev hosts opt in once without code changes. Per-request _ChirpUIStrictMiddleware retired.

Free-threading observability

ReactiveBus gained emitted_count, dropped_count, subscriber_count properties and a configurable maxsize=N per-subscriber queue (default 256). 25 new stress tests cover every Lock-protected module.

Fixed

  • Route contractchirp check no longer reports missing _meta.py for routes whose _meta.py defines only meta() (dynamic metadata).

Dependencies

  • chirp-ui>=0.5.0 (bumped from >=0.2.5)
  • kida-templates>=0.7.0 (bumped from >=0.3.2)
  • bengal-pounce>=0.6.0 (bumped from >=0.5.1)

Modernization

  • Removed 49 no-op from __future__ import annotations imports (PEP 649 default on 3.14), added 11 TypedDicts for stable dict shapes in tools/debug modules, converted 3 dispatch chains to match/case.

Upgrading

:::{tab-set}
:::{tab-item} UV

uv pip install --upgrade "bengal-chirp>=0.5.0"

:::

:::{tab-item} pip

pip install --upgrade "bengal-chirp>=0.5.0"

:::
:::{/tab-set}