djust 0.3.2 was stabilized through 1 pre-release, and most of its changes are recorded under them: 0.3.2rc1.
Before you upgrade, read Deprecated below.
Added
- TypeScript definitions (
djust.d.ts) — Comprehensive ambient TypeScript declaration file shipped with the Python package atstatic/djust/djust.d.ts. Covers:window.djustnamespace,LiveViewWebSocketandLiveViewSSEtransport classes,DjustHooklifecycle interface (mounted,beforeUpdate,updated,destroyed,disconnected,reconnected),DjustHookContext(this.el,this.pushEvent,this.handleEvent),dj-modelbinding types, streaming API types (DjustStreamMessage,DjustStreamOp), upload progress event types (DjustUploadEntry,DjustUploadConfig,DjustUploadProgressEventDetail), and thedjust:upload:progresscustom DOM event. Use via/// <reference path="..." />or add totsconfig.json. - Python type stubs (
_rust.pyi) — PEP 561 compliant type stubs for the PyO3 Rust extension module (djust._rust). Covers all exported functions (render_template,render_template_with_dirs,diff_html,resolve_template_inheritance,fast_json_dumps, serialization helpers, tag handler registry) and classes (RustLiveView,SessionActorHandle,SupervisorStatsPy, and all 15 Rust UI components). Enables full IDE autocomplete and mypy type checking for the Rust extension. - SSE (Server-Sent Events) fallback transport — djust now automatically falls back to SSE when WebSocket is unavailable (corporate proxies, enterprise firewalls). Architecture:
EventSourcefor server→client push, HTTP POST for client→server events. Transport negotiation is automatic: WebSocket is tried first; SSE activates after all reconnect attempts fail. Register the endpoint withpath("djust/", include(djust.sse.sse_urlpatterns))and include03b-sse.jsin your template. Feature limitations: no binary file uploads, no presence tracking, no actor-based state. Seedocs/sse-transport.mdfor full setup guide. - Type stub files (.pyi) for LiveView and mixins — Added PEP 561 compliant type stubs for
NavigationMixin,PushEventMixin,StreamsMixin,StreamingMixin, andLiveViewto enable IDE autocomplete and mypy type checking for runtime-injected methods likelive_redirect,live_patch,push_event,stream,stream_insert,stream_delete, andstream_to. Includespy.typedmarker file and comprehensive test suite. @backgrounddecorator for async event handlers — New decorator that automatically runs the entire event handler in a background thread viastart_async(). Simplifies syntax for long-running operations (AI generation, API calls, file processing) without needing explicit callback splitting. Can be combined with other decorators like@debounce. Task name is automatically set to the handler's function name for cancellation tracking. (#313)start_async()keeps loading state active during background work — WebSocket responses includeasync_pendingflag when astart_async()callback is running, preventing loading spinners from disappearing prematurely. Async completion responses includeevent_nameso the client clears the correct loading state. Supports named tasks for tracking and cancellation viacancel_async(name). Optionalhandle_async_result(name, result, error)callback for completion/error handling. (#313, #314) Seedocs/website/guides/loading-states.md.dj-loading.forattribute — Scope anydj-loading.*directive to a specific event name, regardless of DOM position. Allows spinners, disabled buttons, and other loading indicators anywhere in the page to react to a named event. (#314)AsyncWorkMixinincluded inLiveViewbase class —start_async()is now available on all LiveViews without explicit mixin import. (#314) Seedocs/website/guides/loading-states.md.- Loading state re-scan after DOM patches —
scanAndRegister()is called after everybindLiveViewEvents()so dynamically rendered elements (e.g., inside modals) get loading state registration. Stale entries for disconnected elements are cleaned up automatically. (#314) Seedocs/website/guides/loading-states.md. - System check
djust.T010for dj-click navigation antipattern — Detects elements usingdj-clickwith navigation-related data attributes (data-view,data-tab,data-page,data-section). This pattern should usedj-patchinstead for proper URL updates, browser history support, and bookmarkable views. Warning severity. (#305) - System check
djust.Q010for navigation state in event handlers — Heuristic INFO-level check that detects@event_handlermethods setting navigation state variables (self.active_view,self.current_tab, etc.) without usingpatch()orhandle_params(). Suggests converting todj-patchpattern for URL updates and back-button support. Can be suppressed with# noqa: Q010. (#305) - Type stubs for Rust extension and LiveView — Added
.pyitype stub files for_rustmodule andLiveViewclass, enabling IDE autocomplete, mypy/pyright type checking, and catching typos likelive_navigate(should belive_patch) at lint time. Includespy.typedmarker for PEP 561 compliance and comprehensive documentation indocs/TYPE_STUBS.md.
Deprecated
data.typefallback inhandleNavigation— Thedata.action || data.typefallback for pre-#307 clients (added for backwards compatibility in #318) will be removed in the next minor release. Server now sendsdata.actionon all navigation messages. Update any custom client code that sends navigation messages without anactionfield.
Fixed
- Silent
str()coercion for non-serializable LiveView state — Non-serializable objects stored inself.*duringmount()(e.g., service instances, API clients) were silently converted to strings, causing confusingAttributeErroron subsequent requests far from the root cause.normalize_django_value()now logs a warning before falling back with the type name, module, and guidance on how to fix. Opt-in strict mode (DJUST_STRICT_SERIALIZATION = True) raisesTypeErrorinstead of coercing, recommended for development. New static checkdjust.V008(AST-based) detects non-primitive assignments inmount()at development time. (#292) - System check S005 incorrectly warns on views with
login_required = False— The S005 security check now correctly distinguishes between intentionally public views (login_required = False) and views that haven't addressed authentication at all (login_required = None). Previously, views withlogin_required = Falsewere incorrectly flagged as missing authentication due to a truthy test. The check now uses explicitis not Nonecomparisons to distinguish intentional public access from unaddressed auth. (#303) |safefilter rendering empty string for nested SafeString values — When mark_safe() HTML was stored in lists of dicts or nested dicts, the |safe filter rendered an empty string instead of preserving the HTML. The _collect_safe_keys() function now recursively scans nested dicts and lists using dotted path notation (e.g., "items.0.content") to track all SafeString locations. Includes circular reference protection to prevent RecursionError on tree/graph structures. (#317)- VDOM diff incorrectly matching siblings when
{% if %}removes nodes — When{% if %}blocks evaluated to false and removed elements, siblings shifted left, causingdiff_indexed_children()to incorrectly match unrelated nodes and generate wrong patches. The template engine now emits<!--dj-if-->placeholder comments when conditions are false (matching Phoenix LiveView's approach), maintaining consistent sibling positions. The VDOM diff detects placeholder-to-content transitions and generatesRemoveChild+InsertChildpatches instead ofReplacepatches for semantic consistency. Eliminates DJE-053 fallback to full HTML updates and removes need forstyle='display:none'workarounds. (#295) - Event listener leak causing duplicate WebSocket sends — Single user actions were triggering the same event multiple times (e.g.
select_project5×,mount3×) because listeners accumulated across VDOM patch/morph cycles without cleanup. Fixed four root causes: (1)initReactCountersnow uses aWeakSetguard to skip already-initialized containers; (2)createNodeFromVNodeno longer pre-marks elements as bound beforebindLiveViewEvents()runs, eliminating a race where newly inserted elements were silently skipped; (3)dj-clickhandlers now read the attribute at fire-time rather than bind-time, somorphElementattribute updates take effect immediately; (4) three unguardedconsole.logcalls in12-vdom-patch.jsare now wrapped inif (globalThis.djustDebug). The existingWeakMap-based deduplication inbindLiveViewEvents()(introduced in #312) correctly prevents re-binding when called repeatedly. (#315) dj-patch('/')failed to update URL andlive_patchrouting broken — Removedurl.pathname !== '/'guard inbindNavigationDirectivesso root-path navigation works. Fixed dict merge order in_flush_navigationso server sendstype='navigation'instead oftype='live_patch'. UpdatedhandleNavigationto dispatch viadata.actionwithdata.action || data.typefallback for backwards compatibility. (#318)- 52 unguarded
console.logcalls in client JS — Allconsole.logcalls across 12 files instatic/djust/src/(excluding the intentional debug panel insrc/debug/) are now wrapped withif (globalThis.djustDebug). Bare logging in production code leaks internal state to browser consoles and violates thedjust.Q003system check. Files affected:00-namespace.js,02-response-handler.js,03-websocket.js,04-cache.js,05-state-bus.js,06-draft-manager.js,07-form-data.js,09-event-binding.js,10-loading-states.js,11-event-handler.js,12-vdom-patch.js,13-lazy-hydration.js. - dj-submit forms sent empty params when created by VDOM patches —
createNodeFromVNodenow correctly collectsFormDatafor submit events; replaceddata-liveview-*-boundattribute tracking withWeakMapto prevent stale binding flags after DOM replacement (#312)
Security
- F-strings in logging calls — Converted 9 logger calls to use %-style formatting (
logger.error("msg %s", val)) instead of f-strings (logger.error(f"msg {val}")). F-strings defeat lazy evaluation, causing string interpolation before the log level check, potentially exposing sensitive data and wasting CPU. Affected files:mixins/template.py,security/__init__.py,security/error_handling.py,template_tags/__init__.py,template_tags/static.py,template_tags/url.py.
Tests
- Regression tests for
|safefilter with nested dicts — Added comprehensive tests verifying that|safefilter works correctly for HTML content in nested dict/list values, preventing issue #317 from recurring