This is a pre-release. djust 1.0.0 has shipped since: read the djust 1.0.0 release notes.
Before you upgrade, read the upgrade guide.
Added
dj_buttonaccepts aconfirm=""kwarg (#1621). When non-empty, emits the standarddj-confirm="<message>"attribute consumed by djust's client.js (python/djust/static/djust/src/09-event-binding.js:7) — clicking the button shows a JSconfirm()dialog with the message; on OK the event fires, on Cancel nothing happens. Closes a small DX gap where users wanting the dialog had to either skipdj_button(losing the theme integration / variant→class mapping from #1619) or wrap the tag in ad-hoc JS. The underlyingdj-confirmprimitive was already wired in client.js across multiple directives (dj-click,dj-submit, etc.); this PR just exposes it through the component tag. Emission is independent ofevent=value — the attribute is useful on event-less buttons users have wired up via other directives.conditional_escapeneutralizes XSS surface, matching the existingeventattr escaping. Preset override supported via the existing preset-priority pattern (preset values fill defaults; explicit kwargs win). Discovered buildingdjust-org/djust-start's reset-demo button on djust 1.0.0rc12. 5 new regression cases inpython/djust/components/tests/test_dj_button_confirm_1621.py; gate-the-fix-off self-test (Action #1200/#1468) passes.PresenceMixin.online_countinstance attribute — zero-config{{ online_count }}template binding (#1611).PresenceMixinnow auto-maintainsself.online_count(an integer count of presences in the group) insidetrack_presence,untrack_presence,_restore_presence, and the new_on_presence_changebroadcast handler. Templates can use{{ online_count }}with zero scaffolding — noget_context_dataoverride required. The attribute is set on the view instance (NOT viaget_context_data) because djust's diff dirty-tracking watches instance attribute mutations, and a value that only lives in the context dict doesn't trigger patches. Discovered buildingdjust-org/djust-starton djust 1.0.0rc7. Behavior change: NEW public attribute onPresenceMixinusers. Existing code that already doesself.online_count = ...will be transparently overwritten by the mixin's auto-set; rename your attribute if you need different semantics.PresenceMixin.presence_unique_per_connection: bool = Falseopt-in flag for anonymous-tab uniqueness (#1613). By default, two browser tabs of the same anonymous user share a Django session and therefore oneanon_<session_key>user_id — the presence count stays at "1 online" no matter how many tabs are open. This is correct for an authenticated user collaborating with themselves but wrong for a demo. Whenpresence_unique_per_connection = True, anonymous users getanon_conn_<ws_session_id>derived from the per-WebSocket-connection ephemeral UUID instead, so each tab counts as a distinct presence. Authenticated users always userequest.user.idregardless of the flag — logged-in tab collapse is intentional. Behavior change: NEW class-level opt-in flag. Existing apps unchanged (False default).PresenceMixin._on_presence_changedefault@event_handlerfor auto-broadcast fanout (#1614).track_presenceanduntrack_presencenow fire apush_to_viewbroadcast to a well-known handler name_on_presence_changeon the view class.PresenceMixinships a default@event_handler-decorated_on_presence_changethat refreshesself.online_counton the receiving session. Body is exclusivelyself._refresh_online_count()— explicitly does NOT calltrack_presence, so the broadcast loop terminates after one hop. Subclasses may override but should callsuper()._on_presence_change(**kwargs)to preserve count refresh. Behavior change: everytrack_presence/untrack_presencecall now sends one extra channel-layer broadcast. No regression on existing apps; the broadcast is no-op for any session whose view class doesn't have an_on_presence_changelistener.
Fixed
C003 now accepts uvicorn or hypercorn as an ASGI server (#1630). The check previously fired an INFO whenever
daphnewas missing fromINSTALLED_APPS, telling users topip install daphne. But djust's canonical recommendation has beenuvicornsince the README rewrite (uvicorn myproject.asgi:application) anddjust-org/djust-startships uvicorn by default — every uvicorn-based project was forced to ship a permanent"C003"inDJUST_CONFIG['suppress_checks']to silence guidance that pointed at the non-recommended server. New_has_asgi_server()helper probesdaphne/uvicorn/hypercornviaimportlib.util.find_spec(no actual import, can't ImportError-breakmanage.py check); C003 only fires when none is installed, and the hint now points at uvicorn as the canonical pick. The existing daphne-ordering branch (when daphne IS inINSTALLED_APPSbut afterstaticfiles) is unchanged. New cases inTestC003AsgiServers1630cover uvicorn-installed/hypercorn-installed/daphne-via-find_spec/no-server-at-all + helper unit tests. Updatedtest_no_suppress_by_defaultto stub_has_asgi_serverper Action #1200 so the suppression contract is isolated from the broadening. Impact ondjust-start:suppress_checksdrops from["C003", "T002"]to["T002"].Broke the four-module
presets/registry/manager/css_generatorcyclic-import SCC indjust.theming(CodeQL alerts #2352/#2351/#1900/#1883). Extracted the built-in theme imports + theTHEME_PRESETSregistry dict into a newpython/djust/theming/_builtin_presets.pyleaf module.registry._do_discovernow importsTHEME_PRESETSfrom_builtin_presets(notpresets), andmanager.py/css_generator.pydefer theirget_preset/get_theme_configimports to call sites and pullThemePreset/ThemeTokensannotations from the leaf_typesmodule._builtin_presetshas no runtime dependency onpresets/registry/manager/css_generator, so the back-edge that closed every cycle in the SCC is gone. Back-compat preserved:presets.pyre-exports the named*_THEMEconstants viafrom ._builtin_presets import *so external code doingfrom djust.theming.presets import BLUE_THEMEkeeps working, and thedjust.theming.__init__public surface is unchanged. 7 new AST-based regression cases inpython/djust/tests/test_theming_no_cyclic_import.pypin every broken edge so a future refactor can't silently re-introduce the cycle; gate-the-fix-off self-test (Action #1200/#1468) confirmed each is non-tautological.
Changed
PresenceMixin.track_presence()now no-ops during the HTTP-prerender phase (#1612). Every djust LiveView'smount(request, **kwargs)runs twice per page load — once for HTTP prerendering and again when the WebSocket connects. Each run creates a separate view instance. Previously, code that calledtrack_presence()inmount()registered the connection twice; the HTTP-mount entry was an orphan that lingered forPRESENCE_TIMEOUT(~60s) until stale cleanup. After this PR,track_presenceearly-returns when_websocket_session_idis absent (set only byLiveViewConsumeron the WS path), so presence registers only once. Reporter's existingif hasattr(self, "_websocket_session_id"):workaround is now built-in. Behavior change: anyone callingtrack_presence()outside the WebSocket lifecycle (unit tests, management commands) will see a silent skip; the new reproducer tests + downstream fixture updates set_websocket_session_id = "test_ws"to opt back in.