djust 0.9.2rc1

Pre-releaseReleased

This is a pre-release. djust 0.9.2 has shipped since: read the djust 0.9.2 release notes.

First release candidate for 0.9.2. Bundles three drain buckets shipped after 0.9.1 (2026-04-30): 0.9.2-1 (SSE transport DRY refactor — 5 issues, headlined by #1237), 0.9.2-2 (pipeline-template canon batch — 3 issues), 0.9.2-3 (VDOM correctness hardening Phase 1 — 5 issues). Plus the v0.9.2-3 audit doc (docs/vdom/AUDIT-2026-04-30.md). 13 issues closed across 5 PRs (#1238, #1239, #1241, #1242, #1246, #1247, #1257, #1258); 1 known issue surfaced during RC pre-flight (#1260 — fuzz-test mixed-keyed/unkeyed diff round-trip; deferred to v0.9.2-4 before stable).

Fixed

  • VDOM: stale cached_html for dj-update="ignore" subtrees (#1252). splice_ignore_subtrees (crates/djust_vdom/src/lib.rs) used to copy the old node's cached_html into the new node, which meant a conditional re-render that wraps an ignored subtree would keep serving the OLD cached HTML on subsequent diffs. The cache is now cleared (= None) during splice; cache_ignore_subtree_html recomputes lazily on the next render. 4 regression tests in crates/djust_vdom/tests/test_ignore_subtree_invalidation_1252.rs.

  • VDOM: dj-id template-injection defense-in-depth (#1253). The Rust parser now validates user-supplied dj-id attribute values against base62 (^[0-9a-zA-Z]+$) before the server-side ID generator overwrites them. Malformed values (whitespace, special chars, Unicode tricks) are dropped with a debug-level parser_trace! warning. The server-generated ID always wins; this fix tightens the pre-overwrite read path so any error/log surface that touches the prior value sees a sanitized form. 4 regression tests in crates/djust_vdom/tests/test_dj_id_validation_1253.rs.

  • VDOM: duplicate dj-key and mixed-keyed-unkeyed warnings now fire at tracing::warn! (#1254). Both warnings in crates/djust_vdom/src/diff.rs previously used vdom_trace!() (gated behind DJUST_VDOM_TRACE=1), so developers in production had no visibility into silent VDOM correctness issues. The mixed-keyed warning now fires with stable error code DJE-050; the duplicate-key warning with DJE-051. The previously-cited https://djust.org/errors/DJE-050 URL — which didn't exist — has been removed. Structured logging (key passed via {} placeholder) ensures the warnings are not log-injection vulnerable. 4 regression tests in crates/djust_vdom/tests/test_diff_warnings_1254.rs.

  • VDOM JS: Web Components and custom elements no longer silently replaced with <span> (#1255). The patcher's element-creation whitelist in python/djust/static/djust/src/12-vdom-patch.js was hardcoded to ALLOWED_HTML_TAGS + SVG_TAGS, rejecting Web Components (<my-component>, <sl-button>, <model-viewer>, etc.) and replacing them with a fallback <span>. The patcher now accepts any tag matching the HTML spec's custom-element rule (tag.includes('-')) and exposes a window.djustAllowedTags runtime-configurable hook for same-origin allowlist extensions. <script> and <iframe> remain blocked unchanged (<script> is not in the allowlist and lacks a hyphen; <iframe> is unaffected by this change since it's already in the existing ALLOWED_HTML_TAGS whitelist for legitimate use). 7 regression tests in tests/js/vdom_web_components_1255.test.js.

  • VDOM: extended SVG attribute camelCase normalization (#1256). The Rust parser's normalize_svg_attribute() table in crates/djust_vdom/src/parser.rs was missing modern SVG attributes (filter primitives, animation timing, gradient transforms, font-face metrics). Browsers' setAttributeNS is case-sensitive; without normalization, the unknown camelCase attrs were silently ignored, producing visually-broken SVG. 11 new attrs added; 11 regression tests in crates/djust_vdom/tests/test_svg_attr_normalization_1256.rs

    • 10 new cases on the existing in-module test.

Added

  • Transport-agnostic ViewRuntime shared between WebSocket and SSE (#1237). New python/djust/runtime.py module factors out view-lifecycle dispatch (dispatch_mount, dispatch_event, dispatch_url_change) so both transports share one code path for these message types. WebSocket's handle_url_change is now a thin shim over ViewRuntime.dispatch_url_change; SSE's new POST /djust/sse/<session_id>/message/ endpoint dispatches identically. First slice of a multi-PR migration that will progressively move the remaining WS handlers (handle_event, handle_mount, handle_mount_batch) onto the shared runtime. Architecture decision documented in ADR-016.
  • LiveViewSSE.sendMessage(data) — parity with LiveViewWebSocket (#1237). Existing liveViewWS.sendMessage(...) call sites in 18-navigation.js, 02-response-handler.js, 13-lazy-hydration.js, and 15-uploads.js now work transparently when the SSE transport is active — no callsite-by-callsite branching. The existing sendEvent API is preserved (delegates to sendMessage). The legacy POST /djust/sse/<sid>/event/ endpoint stays as a back-compat alias.

Fixed

  • SSE: URL kwargs resolved from the mount-frame URL, not the SSE endpoint path (#1237). Previously a view like path("items/<int:pk>/", ItemView.as_view()) mounted with empty kwargs over SSE because _sse_mount_view resolved against request.path (the SSE endpoint URL /djust/sse/<uuid>/, not the page). The client now sends a WebSocket-shaped mount frame containing url: window.location.pathname, and the server resolves kwargs against that URL — matching the WebSocket transport exactly. The HTTP Referer header is deliberately not used for this; see docs/sse-transport.md#why-not-the-referer-header for why.
  • SSE: LiveView.handle_params() is now invoked after mount and on url_change (#1237). Phoenix-parity contract: handle_params(params, uri) fires once after mount() and on every subsequent URL change. Previously SSE never called it, causing views that read URL state in handle_params (active tab, sort, page) to keep mount-time defaults regardless of query string.
  • SSE: liveViewWS.sendMessage({type: 'url_change', ...}) no longer TypeErrors (#1237). _executePatch() in 18-navigation.js calls sendMessage for dj-patch URL updates; under SSE this previously crashed because LiveViewSSE had no sendMessage method. Now both transports expose the same outbound API. Eight other JS call sites (popstate, lazy-hydration, response-handler, uploads, navigation) are also unblocked.
  • Service Worker reconnection bridge no longer needlessly buffers SSE payloads (#1237). 33-sw-registration.js patches sendMessage to buffer payloads when the WebSocket is closed. With SSE now also exposing sendMessage, the patch was unconditionally applying — and because ws.ws is undefined on LiveViewSSE, every SSE payload was treated as "socket closed" and forwarded to the SW. The patch now short-circuits on LiveViewSSE instances since SSE uses fetch() directly and doesn't need the WS reconnection buffer.
  • ViewRuntime.dispatch_mount rejects use_actors=True views with a structured error envelope over SSE (#1240). Closes plan-fidelity gap from #1237 — actor-based state management requires the channel-layer code in websocket.py which the runtime path doesn't traverse. Previously a use_actors=True mount over SSE would partially succeed and fail downstream with an opaque AttributeError. Now dispatch_mount short-circuits with a clear "use_actors is not supported over SSE; mount over WebSocket instead" envelope. ADR-016 §Implementation notes promised this guard; PR #1239 deferred it to this follow-up.

Developer Experience

  • Pipeline-bypass CI check — daily retro-gate audit (#1234). New scheduled GHA .github/workflows/retro-gate-audit.yml runs scripts/audit-pipeline-bypass.py daily at 13:00 UTC against the most recent 50 merged PRs and surfaces any PR missing retro markers as workflow annotations. Part 2 of #1212 (part 1 was the audit script shipped in PR #1229). Manual workflow_dispatch trigger included for ad-hoc audits.
  • Isolated cargo-test target for filter_registry::tests (#1235). The hot-path short-circuit tests for the ANY_CUSTOM_FILTERS_REGISTERED AtomicBool now live at crates/djust_templates/tests/test_filter_registry_isolated.rs (an integration-test binary). Cargo runs each integration-test file in its own process, so the process-global flag starts clean for every run — the previous OnceLock workaround that gated the in-module test on whether a prior test had already registered a filter is no longer needed. Carryover from #1180 item 4.
  • VDOM engine audit and v0.9.2-3 milestone (docs/vdom/AUDIT-2026-04-30.md). Synthesizes architecture map, bug archaeology (14 historical bugs across 7 themes), 10 ranked current-code weaknesses (3 🔴 / 7 🟡), test gaps, and a 4-phase improvement roadmap. Phase 1 (5 quick wins, #1252-#1256) opens as the v0.9.2-3 drain bucket; Phase 2 (correctness hardening) and Phase 3 (architectural — text-node djust_ids, unified focus state- machine) are deferred to later milestones.
  • Pipeline-template canon — Stage 4 + Stage 7 additions (#1243 + #1244). Two mandatory checklist items added symmetrically to .pipeline-templates/{feature,bugfix}-state.json:
    • Stage 4 VERIFY LITERAL API CONTRACTS — for every literal API call in the plan (function names, kwargs, return shapes), grep for the existing convention before locking. Pattern from #1240/#1242 where the plan said type="mount_error" but convention was error_type=.
    • Stage 7 WORKFLOW-HEADER CROSS-REF — when changed files include .github/workflows/*.yml or any file with a runtime-behavior docstring, list every behavioural claim and verify each against actual step semantics. Pattern from #1241 where the workflow's header said "annotations not red runs" but pipefail made every flagged run red.
  • Pipeline-run Stage 14 retro-post — Write tool + gh --body-file (#1245). Updates .pipeline-templates/{feature,bugfix}-state.json Stage 14 subagent_prompt to use Claude's Write tool to create pr/feedback/retro-<N>.md and gh pr comment <N> --body-file <path> to post — replacing the previous cat > file <<EOF + --body "$(cat file)" pattern that silently failed under zsh set -o noclobber (a common .zshrc safety guard). All 3 v0.9.2-1 implementation PRs (#1239, #1241, #1242) hit this and had their retros backfilled during the milestone retro audit; the new pattern is structural (sidesteps any shell-init quirk, not just noclobber) rather than a per-quirk patch.
  • Release-workflow dep-bump label gate (#1236). New GHA .github/workflows/check-release-workflow-deps.yml runs on PRs modifying release-critical workflow files (release.yml, publish.yml, release-drafter.yml, pre-release-security-audit.yml) and fails unless the PR carries the release-workflow-reviewed label, forcing explicit human risk-review before merge. Triggered by PR #1233 (action-gh-release v2 → v3) landing in the same window as the v0.9.1 cut. The release-workflow-reviewed label was added to the repo alongside this workflow.

All releases · Atom feed