Added
- LiveView testing utilities (v0.5.1 P2) — Seven new methods on
LiveViewTestClientfor Phoenix LiveViewTest parity:assert_push_event(event_name, params=None)verifies a handler queued a client-bound push event (payload match is subset-based so tests stay resilient to later payload additions);assert_patch(path=None, params=None)/assert_redirect(path=None, params=None)assertlive_patch/live_redirectcalls;render_async()drains pendingstart_async/assign_asynctasks synchronously so subsequent assertions see their results;follow_redirect()resolves the queued redirect via Django's URL router and returns a new test client mounted on the destination view;assert_stream_insert(stream_name, item=None)verifies stream operations (item subset-match for dicts);trigger_info(message)synthetically delivers ahandle_infomessage so pubsub / pg_notify handlers can be tested without real backend wiring. Full user-facing guide atdocs/website/guides/testing.md. 21 new test cases. (python/djust/testing.py) dj-dialog— native<dialog>modal integration (v0.5.1 P2) — Declarative opt-in for the HTML<dialog>element's built-in modal behavior. Mark a<dialog>withdj-dialog="open"to callshowModal()(backdrop, focus-trap, and Escape handling all browser-native); setdj-dialog="close"to callclose(). A document-levelMutationObserverwatches for attribute changes and DOM insertions so VDOM morphs that swapdj-dialogwork automatically without per-element re-registration. Idempotent — re-asserting"open"on an already-open dialog is a no-op; gracefully ignores non-<dialog>elements carrying the attribute. ~80 LOC JS inpython/djust/static/djust/src/35-dj-dialog.js. 8 JSDOM tests intests/js/dj_dialog.test.js.- Type-safe template validation —
manage.py djust_typecheck(v0.5.1 P2, differentiator) — Static analysis that reads every LiveView template, extracts every variable and tag reference, and reports names not covered by the view's declared context. "Declared context" is the union of public class attributes,self.foo = ...assignments anywhere in the class (AST-extracted — not run),@propertymethods, literal-dict keys returned fromget_context_data, template-local bindings ({% for %}/{% with %}/{% inputs_for as %}), framework built-ins (user,request,csrf_token,forloop,djust, etc.), and anything listed insettings.DJUST_TEMPLATE_GLOBALS. Silencing: per-template pragma ({# djust_typecheck: noqa name1, name2 #}), per-viewstrict_context = Trueopt-in, or the project-wide globals setting. Flags:--json,--strict,--app,--view. Neither Phoenix nor React catches template-variable typos statically without an external type system — this is a genuine djust differentiator. 14 tests inpython/djust/tests/test_djust_typecheck.py. Full guide atdocs/website/guides/typecheck.md. (python/djust/management/commands/djust_typecheck.py) - Dev-mode error overlay (v0.5.1 P2) — Next.js/Vite-style full-screen error panel that renders in the browser whenever a LiveView handler raises an exception and Django
DEBUG=True. Displays the error message, the event that triggered the handler, the server-sent Python traceback, an optional hint, and validation details when present. Dismissal: Escape key, close button, or backdrop click. A second error replaces the current panel rather than stacking. All field values HTML-escaped to prevent traceback injection. Gated onwindow.DEBUG_MODE— production builds render nothing (Django also stripstraceback/debug_detail/hintfrom the error frame in non-DEBUG mode, so there's nothing to leak). Exposeswindow.djustErrorOverlay.show(detail)/.dismiss()for manual invocation from devtools. 10 JSDOM tests intests/js/error_overlay.test.js. Full guide atdocs/website/guides/error-overlay.md. (python/djust/static/djust/src/36-error-overlay.js) - Nested formset helpers —
{% inputs_for %}+FormSetHelpersMixin(v0.5.1 P2) — djust-native support for Django formset / inline-formset patterns. Template side:{% inputs_for formset as form %}...{% endinputs_for %}iterates anyBaseFormSetand exposes each bound child form with its per-row prefix intact so rendered inputs submit under the correct Django-expected names; loop metadata (inputs_for_loop.counter,.counter0,.first,.last) mirrors the{% for %}conventions. Server side:djust.formsets.add_row(cls, data=..., prefix=...)andremove_row(cls, row_prefix, data=..., prefix=...)handle management-form bookkeeping —add_rowbumpsTOTAL_FORMS(capped atmax_numwhen set,absolute_maxotherwise) and preserves existing row data;remove_rowwrites the standardDELETE=onflag soformset.deleted_formspicks it up onsave().FormSetHelpersMixinwires pre-bakedadd_row/remove_rowevent handlers to aformset_classes = {"addresses": AddressFormSet}declaration, with the formset name doubling as the prefix so multiple formsets on one view don't collide on management-form keys. Fails loud ifmount()forgets to initializeself._formset_data. 16 tests inpython/djust/tests/test_formsets.py. (python/djust/formsets.py,python/djust/templatetags/djust_formsets.py)