Fixed
PresenceMixin+NotificationMixin— side-effect replay on WS state restoration (#893, #894) — Sibling bugs to #889, found via audit after theUploadMixinfix shipped in #891. Both issues share the same root cause: a mixin'smount()-called method has a process-wide side effect beyond setting instance attrs, and the WS consumer's state-restoration path (which skipsmount()) never re-issues the side effect. #893 (Presence):track_presence()callsPresenceManager.join_presence(...)as a per-process singleton registration; after restore, the restored user's presence is invisible to other users andhandle_presence_joindoesn't fire for the user's own join. #894 (Notifications):listen(channel)callsPostgresNotifyListener.instance().ensure_listening(channel)which issues the PostgresLISTEN channelSQL statement on the current process; after a cross-process restore (server restart between HTTP and WS, sticky-session LB routing WS to a different worker, worker reshuffle under load) the destination process's listener has no subscriptions, so NOTIFYs never reach the restored view. Fix (mirrors PR #891):PresenceMixin._restore_presence()replaysjoin_presencewhen_presence_tracked=True;NotificationMixin._restore_listen_channels()replaysensure_listeningper channel (both convergent under replay —PresenceManager.join_presenceoverwrites the existing record with identical data so repeated calls are a no-op in effect;ensure_listeningexplicitly early-returns on known channels); WS consumer's state-restoration path calls both right after_restore_private_state(), alongside the existing_restore_upload_configs()call. All three methods are defensive: missing attributes / backend errors / per-item failures are logged at WARNING and swallowed — restoration must never kill the WebSocket. 11 regression cases intests/unit/test_mixin_restoration_893_894.pycover both mixins' happy paths, no-op guards (not-tracked, missing user_id, empty channel set, missing attribute), exception handling (backend exception, per-channel failure, postgres unavailable), and an end-to-end session-round-trip test. (python/djust/presence.py,python/djust/mixins/notifications.py,python/djust/websocket.py)UploadMixin— uploads broken after HTTP→WS state restoration (#889) — Production-critical bug affecting every app usingUploadMixinwith the default pre-rendered HTTP→WS flow. The WS consumer's state-restoration path (websocket.py:1540-1572) skipsmount()when pre-rendered session state exists, and the liveUploadManagerinstance isn't JSON-serializable — so_upload_managersilently dropped by_get_private_state(), never restored, and any upload request hit_handle_upload_registerwith"No uploads configured for this view". Fix:allow_upload()now also records each call as a JSON-serializable dict inself._upload_configs_saved(list of kwarg dicts with primitive values); the newUploadMixin._restore_upload_configs()method replays the saved calls; the WS consumer calls it at the end of the state-restoration path (right after_restore_private_state). Result: restored views behave identically to fresh-mount views. Caveat:allow_upload(writer=CustomWriterClass)— the writer class itself still can't round-trip through JSON; a warning is logged at replay time and the config falls back to the default buffered writer. Apps that rely on custom writers with session restoration need a follow-up design (out of scope for this fix). 10 regression cases intests/unit/test_upload_restoration_889.pycover: call-list recording, writer-marker flag, multi-slot tracking, JSON round-trip survival, manager rebuild from the list, no-op on empty / missing list, idempotency across repeated restores, writer-fallback warning, and a full HTTP→session→WS-restore end-to-end scenario. (python/djust/uploads.py,python/djust/websocket.py)
Added
dj-transition— declarative CSS enter/leave transitions (v0.6.0) — PhoenixJS.transitionparity. Three-phase class orchestration so template authors can drive CSS transitions without writing adj-hook. Attribute value is three space-separated class tokens — phase 1 (start) applied synchronously, phases 2 (active) + 3 (end) applied on the next animation frame so the browser commits the start layout before the transition begins.transitionendremoves the active class (phase 3 stays as the final-state). 600 ms fallback timeout covers thedisplay: none/ zero-duration corner cases wheretransitionendnever fires. Any attribute-value change re-runs the sequence so authors can retrigger from JS. Newstatic/djust/src/41-dj-transition.js(~120 LOC); document-level MutationObserver matches thedj-dialog/dj-mutation/dj-sticky-scrollregistration pattern. 7 JSDOM cases intests/js/dj_transition.test.jscover spec parsing, phase-1 synchronous application, next-frame phase-2/3 application, transitionend cleanup, fallback-timeout cleanup, global export, and re-trigger-on-attribute-change. This is phase 1 of the v0.6.0 Animations & transitions work; FLIP,dj-remove,dj-transition-group, and skeleton components will ship as separate follow-ups. (python/djust/static/djust/src/41-dj-transition.js)See
docs/website/guides/declarative-ux-attrs.md.