v0.9.0 · The headline
React 19 mental model — without the React.
Decorate a method with @action. The template gets pending, error, and result for free. No useState, no setLoading, no API client, no JSON contract you maintain twice. The submit button disables itself; the spinner shows; the error renders — zero per-handler wiring.
Every reactive primitive React 19 ships, plus the bits Phoenix LiveView taught the industry — and you write Python with HTML templates. No client framework. No JSON contract to maintain on both sides.
useFormStatus, anywhere in the form.
Any descendant of a <form dj-submit> can declare dj-form-pending="hide|show|disabled" and react to the in-flight state automatically. No prop drilling, no per-button wiring, no client state.
React 19 <Activity> parity.
Pre-rendered hidden regions of a LiveView that preserve their local DOM state — form inputs, scroll position, transient JS — across show/hide cycles. Toggling a tab doesn't blow away what the user was typing.
Read the guide →Declarative enter/leave animations.
Phoenix JS.transition + React <TransitionGroup> parity. Mark the parent container, declare classes for the three phases — djust wires the animations onto every child without a custom hook.
Flip the UI before the server confirms.
Toggle a todo and see it flip immediately; if the server rejects the write (rate limit, validation, race) djust reconciles automatically. The optimistic state and the rollback path are one decorator.
Read the guide →Pushes from the database, from a worker, from a webhook — all land in the connected user's open page in ~50ms. Same template, same Python view, same auth. No Celery for fanout, no Redis for pub/sub, no second service to operate.
A model save reaches every reader instantly.
Decorate a Django model and every connected LiveView subscribed to it gets notified the moment a row changes. Backed by PostgreSQL LISTEN/NOTIFY — no Celery, no Redis pub/sub, no second service to operate.
Avatars on the page, in 6 lines of Python.
Show who else is reading the same record right now. Survives reconnects via the framework's heartbeat; pairs with flash messages for the join/leave toast.
Read the guide →Webhooks, jobs, signals — pipe straight into the UI.
Stripe webhook fires → the user's open dashboard updates in ~50ms. Same one-liner from any background worker, Django signal, or external integration. The transport is invisible.
Read the guide →Defer the slow parts. Real async render path.
Mark a child view lazy=True, lazy="visible", or lazy={...} and djust flushes the shell first, then renders the heavy parts in parallel via asyncio.as_completed. Time-to-first-byte without giving up the SSR model.
The boring-but-load-bearing parts that decide whether a framework survives a real team for a year. Hot reload that keeps form input. Type-checked templates. Time-travel debugging with branched timelines. A real error overlay. 60+ system checks at startup.
Save a Python file. The page updates. The form keeps your input.
Edit a LiveView, hit save — connected sessions re-render without losing form values, scroll position, or open tabs. React Fast Refresh parity. Gated on DEBUG=True; zero cost in production.
Scrub through every state your view has ever been in.
The dev panel records a state snapshot around every @event_handler dispatch. Scrub back, branch off, replay forward — Redux DevTools “swap action” parity, including per-component time-travel. Reproducing a weird-state bug stops requiring the user to do it again.
Next.js-grade traceback, in your Django app.
Full-screen panel when a handler raises: the event that triggered it, the server-sent traceback, and a copy button. Fewer round trips between browser and terminal.
Read the guide →Static analysis for templates.
manage.py djust_typecheck reads every template, extracts every variable + tag, and reports names not declared on the view. Catches typos before the user does.
Agents drive your app, not just inspect it.
Chrome extension + Node MCP server with 53 tools for AI-driven end-to-end interaction with a running djust app: dom_snapshot, record_start/replay_actions, regression_suite, visual_diff, multi_session_simulate, find_dead_bindings, ws_inject/ws_throttle. 20-30× faster than Playwright. Agents are first-class users — not an afterthought.
No build step in user projects. No node_modules. A single readable client. Every layer of complexity removed is a bug that won't exist.
WebSockets, VDOM, reconnection, security — all handled. Clear errors. System checks at startup. You write app logic, not infrastructure.
Less surface area means generated code can be trusted on the boring parts. The AI doesn't write your auth, state, or API contracts.
No API layer. No frontend / backend split. One Python codebase, one deploy, one stack trace.
Rust for the VDOM and template engine isn't premature optimization — it's the right tool for that layer of the stack.
Single readable client.js. No npm registry to trust. MIT licensed. The code you ship is the code you can read.
Strong opinions on security, state, transport. Zero opinions on CSS, markup, design. Bring your own everything visual.
release-blocker for v0.9.0rc3. Three test-runtime warnings/errors that surfaced during local `make test` but never affected production behavior, all unblocking the canonical exit-0 gate:
final v0.9.2 drain item; tightens the row-navigation client module that shipped in #1170:
Stage 11 follow-ups from PR #1168 (the original cookie-namespace work for #1158):
two Stage 11 follow-ups from PR #1166 (which wired the raw-Python sidecar into ``Node::CustomTag``):
Stage 11 review of PR #1161 (which closed #1121 by adding the eager Rust filter registry) flagged six follow-ups. All are addressed in this PR: 1. **Hot-path Mutex perf**: ``is_custom_filter_safe`` and ``apply_custom_filter`` short-circuit on a new…
defense-in-depth strengthening of the v0.9.0 #1042 forward-replay path. The original guard rejected only dunder/private `event_name` (`startswith("_")`), which still admitted ANY public method on the view — helpers, inherited utilities, property getters…
Django projects registering custom filters via ``@register.filter`` in their ``templatetags/`` modules saw them work in the Python render path but fail under the Rust ``RustLiveView`` render path with ``RuntimeError: Template error: Unknown filter: <name>``.…
bisected two independent polluters that surfaced after v0.9.0 PR-A (#1135) added the `aget`/`ChunkEmitter` async-render path and after PR #998 added the `block_watchdog` test fixture:
Stage 11 follow-ups from the v0.9.1 retro arc, batched as a single chore PR:
Docs: components,
adds explicit guidance in `CLAUDE.md`, `docs/PULL_REQUEST_CHECKLIST.md`, and `docs/guides/security.md` that any new framework feature emitting HTML must default to: external static JS modules (no inline `<script>` blocks), no inline event handlers (no…
Docs: PULL_REQUEST_CHECKLIST, security,
both packages are runtime deps of the `[components]` extra (see `python/djust/components/components/markdown.py`) and the components subpackage's `__init__.py` eagerly imports them via `from .markdown import Markdown`. Tests that import…
layers v0.9.1 quality additions onto the prior #1111 row-navigation scaffolding (which shipped `row_click_event` / `row_url` template-tag args, mixin defaults, and structural wiring). What's added:
adds opt-in `LIVEVIEW_CONFIG['theme']['cookie_namespace']` setting so multiple djust projects on `localhost:80xx` (or any shared domain) don't overwrite each other's theme preferences. Browsers scope cookies by domain only — not by port — so the four…
the Rust template engine now ships a registered handler for `{% live_render %}`, closing the v0.9.0 PR-B (#1138) gap. Before this, production users on `RustLiveView` got a "no handler registered for tag: live_render" template error if they used `lazy=True`…
promotes the existing tag-eval-time `TemplateSyntaxError` to a startup-time warning so the misuse surfaces during `manage.py check` instead of waiting for a request to render the offending template. Sticky preservation requires the slot to exist at…
`{% live_render lazy=True %}` now propagates `request.csp_nonce` (the Django convention set by `django-csp` middleware) onto BOTH the `<template id="djl-fill-X">` element AND the inline `<script>` activator that calls `window.djust.lazyFill(...)`. Sites with…
previously the view-level ``wizard_input_event`` attribute applied uniformly to every widget the wizard rendered. An author setting ``wizard_input_event = "dj-input"`` to capture unblurred text edits (per #1095) unintentionally also stamped ``dj-input`` on…
`DEFAULT_RATE_LIMITS` in `python/djust/static/djust/src/08-event-parsing.js` was missing entries for `radio`, `checkbox`, `select-one`, and `select-multiple`. The input handler's fallback (`{ type: 'debounce', ms: 300 }`) kicked in for these widget types, so…
Redux DevTools "swap action" parity. Time-travel previously only scrubbed BACK through linear history; `replay_event(view, snapshot, override_params=None, record_replay=True)` now replays a recorded event from its `state_before` baseline either…
extends the v0.6.1 time-travel ring buffer to capture per-component public state alongside the parent LiveView's state. Multi-component pages can now scrub back through history with each component's state faithfully restored.
closes the v0.9.0 streaming arc. PR-B shipped sequential thunk invocation in `arender_chunks` Phase 5 (one thunk runs to completion before the next starts; total wall-clock time = sum of thunk durations). PR-C swaps the for-loop for `asyncio.as_completed`…
ships the user-facing API on top of PR-A's async render foundation. Three forms: `lazy=True` (parent-flush trigger, default), `lazy="visible"` (IntersectionObserver-deferred), `lazy=dict` (full control — `trigger`, `timeout_s`, `on_error`, `placeholder` keys).
first PR of the v0.9.0 P2 streaming arc (#1043). Closes the v0.6.1 retro #116 doc-claim debt: Phase 1 was a regex-split-after-render with no real TTFB win; Phase 2 PR-A introduces the actual async render path so `streaming_render = True` shell-flushes to the…
Docs: 015-phase-2-streaming,
the v0.6.0 Sticky LiveViews work shipped Dashboard→Settings→Reports preservation but left a known limitation: returning to a page that declares the sticky inline (`Dashboard → Settings → Dashboard`) re-mounted the child instead of reattaching the survivor…
Stage 11 review of PR #1117 surfaced that `show_stats` was present in `_PRE_MOUNT_TABLE_CONTEXT` (the empty-table default returned before `init_table_state()` runs) but missing from the post-mount return dict. A template containing `{% if show_stats %}` would…
Five Stage 11 / retro-tracker learnings from PRs #1115 / #1117 / #1119 / #1120 are now canonicalized as additions to the existing "Process canonicalizations" section in `CLAUDE.md`. Each rule names the source PR so the audit trail is preserved.
Eight Stage 11 / retro-tracker learnings from the View Transitions PR-A → PR-B arc and the downstream-consumer gap-fix arc are now canonicalized as a single "Process canonicalizations" section in `CLAUDE.md`. Each rule names the source PR so the audit trail is…
anyone with `STATIC_ROOT` configured (typical production deployment behind WhiteNoise / nginx / a CDN) can ship a stale `client.min.js` after a djust wheel upgrade if they forget `collectstatic --clear`. The server runs new code; the browser loads old…
`dj-hook` lifecycle methods (`mounted`, `updated`, `beforeUpdate`, `destroyed`, `disconnected`, `reconnected`, `handleEvent`) may now be `async`. The dispatcher detects Promise return and chains `.catch` to log rejections via `console.error` — no Unhandled…
covers the `<body dj-view-transitions>` opt-in, browser support matrix (Chrome 111+, Edge 111+, Safari 18+, Firefox graceful degrade), `prefers-reduced-motion` accessibility bypass, shared-element transitions via `view-transition-name`, custom animation…
Docs: view-transitions,
column dicts now accept a `link` key naming another row dict key that holds the href, and an optional `link_class` for the `<a>` element's CSS class:
the entire `<tr>` becomes clickable for navigation. Two API shapes:
both filters previously fell through silently to the original value when chrono failed to parse the input string. The #1081 4-round-reopen investigation would have collapsed to a 5-minute diagnosis if a single line had been logged at parse-failure time. Now…
Stage 13 review of PR #1091 flagged that the WS-side `hasattr` guard had a parallel test (`test_flush_deferred_handles_view_without_drain_method`) but the SSE-side did not. New `test_sse_flush_deferred_handles_view_without_drain_method` in…
`.github/workflows/release.yml` previously built only cp310/cp311/cp312 wheels. Users on Python 3.13 or 3.14 fell back to source-compiling the sdist at `pip install` time, producing untested binaries whose runtime behavior could diverge from CI-tested cp312…
Opt-in via `<body dj-view-transitions>`. When the browser supports `document.startViewTransition()` AND the body attribute is present AND the user has not requested `prefers-reduced-motion: reduce`, every server-driven VDOM patch is wrapped in a View…
using `DataTableMixin` in a `LiveView` (rather than a `Component`) caused a blank/empty table on every page load even when `refresh_table_server()` correctly populated `self.table_rows` in `mount()`. Three compounding root causes:
PR-A (v0.8.5rc1) made `LiveViewWebSocket.handleMessage` and `LiveViewSSE.handleMessage` async without serializing the inbound frame queue. Two adjacent inbound frames could fire-and-forget `_handleMessageImpl` concurrently and interleave their `await…
`WizardMixin.get_context_data()` unconditionally pre-rendered `field_html` for **every** field on the current step's form, regardless of whether the template referenced that field. Wizards with conditional fields (e.g. owner-info hidden behind…
`WizardMixin.as_live_field()` previously emitted `dj-change="<handler>"` unconditionally on text/textarea/select/checkbox/ radio inputs. `dj-change` fires only on blur, so a user who edits a pre-filled field and clicks Next without tabbing away has their edit…
kwargs)` — Phoenix-style post-render callback scheduling** — new method on `AsyncWorkMixin` (and therefore on every `LiveView`) that schedules a callback to run **once, after the current render+patch cycle completes**. Phoenix `send(self(), :foo)` / React…
foundational refactor preparing for View Transitions API integration (ADR-013). Previously `applyPatches(patches, rootEl)` returned `boolean` synchronously; now `async function applyPatches(patches, rootEl) -> Promise<boolean>`. The patch-loop body itself is…
`T012` (template uses `dj-*` event directives but missing `dj-view`) fired unconditionally for any template containing `dj-click`, `dj-input`, etc., even when the file was an intentional fragment included from a parent LiveView root. Wizards with 15+ step…
the test-counter pre-push hook's `PY_TEST_FN_RE` matched only `def test_*`, silently undercounting pytest-asyncio test files (any module-level `async def test_*` was invisible). Updated the pattern to `^[ \t]*(?:async\s+)?def\s+test_\w+\s*\(` so async tests…
`nodes_to_template_string` in `crates/djust_templates/src/inheritance.rs` was wrapping every filter arg in `\"…\"` when serializing the resolved-inheritance AST back to a template string. But `parse_filter_specs` deliberately preserves any surrounding quotes…
issue reported `{{ d|date:"M d, Y" }}` rendering as `"Apr 25, 2026"` (literal double-quotes wrapping the result) and `{{ x|default:"fallback" }}` rendering as `"fallback"`. Investigation across all renderer code paths confirmed the…
verified each unchecked Priority Matrix row, Quick Wins bullet, Medium Effort bullet, Major Features bullet, and Phoenix LiveView Parity Tracker row against the codebase. Marked ~30 items with ✅ + strikethrough + the actual implementation path (e.g.…
/*.md for stale cross-references (closes #1075)** — new `scripts/docs-lint.py` walks every markdown link in `docs/` (excluding the rendered `docs/website/` site dir), parses `[text](target.md)` patterns, and reports any whose relative target doesn't resolve.…
follow-up to #1010. Sweep found 53 broken refs across 16 files: 34 relocatable (file moved to a different docs/ subdir; rewrote relative path), 12 marketing-cluster files that no longer exist (unlinked — kept link text without the `[](url)` syntax), 7…
Docs: forms,
Chrome's `Vary: Cookie` handling is unreliable for per-cookie dynamic CSS; after a pack switch the browser often serves the prior pack's stylesheet from its own HTTP cache and the page renders with stale palette. The new `{% theme_css_link %}` tag in…
new `djust_theming/static/djust_theming/css/prose.css` ships pack-aware overrides for the typography plugin's `--tw-prose-*` variables. Opt in by adding `prose-djust` alongside `prose` on your `<article>`. Reads `--color-brand-*` tokens the active pack emits…
`ThemeManager.get_state()` reads `djust_theme_pack` / `djust_theme_preset` cookies with priority over config defaults. Default behavior unchanged (back-compat `True`). Sites without a user-facing theme switcher can set…
`scripts/roadmap-lint.py` parses the "Not started" entries in `ROADMAP.md`, extracts grep-able tokens from each feature name, and reports entries whose tokens have zero hits in code paths (`python/`, `crates/`, `static/`, `scripts/`, `tests/`, `Makefile`).…
`scripts/check-noqa-f822.sh` flags new `noqa: F822` annotations introduced in `python/**/*.py` or `tests/**/*.py` since the last push. Ruff silences `py/undefined-export` with `noqa: F822`, but CodeQL flags it as a security alert later — the canonical fix is…
`djust_theming/static/djust_theming/css/components.css` `.card` and `.alert` selectors now set `overflow: hidden`. Without this, child borders (e.g. `.card-header { border-bottom: ... }`) cross the parent's rounded arc and produce a visible 1-2 px notch at…
the `mount_batch` WebSocket frame was added in v0.6.0 (PR #970) for lazy- hydration efficiency. A v0.6.0+ client talking to a pre-v0.6.0 server previously got a generic `"Unknown message type: mount_batch"` error and the lazy-hydrated views never mounted. Now…
`python/djust/api/dispatch.py` (two sites at the API event-dispatch and server-function paths) was returning `f"Malformed JSON body: {exc}"` — a small but real stack-trace-style leak that could surface parser internals (offsets, snippets of the malformed…
`python/djust/static/djust/src/03-websocket.js:386` previously swallowed cache-put exceptions with a bare `catch (_e) {}`. Now logs the failure via `if (globalThis.djustDebug) console.log(...)` so developers can diagnose cache-write misses without polluting…
four small test-quality refactors bundled in one PR:
mark a method as a Server Action and `_action_state[<method_name>]` is auto-populated with `{pending, error, result}` at handler entry/exit. Templates access the state via context injection: each action's name becomes a context variable. Pairs with the v0.8.0…
any element nested inside a `<form dj-submit>` can declare `dj-form-pending="hide|show|disabled"` and react automatically when the ancestor form's submit handler is in-flight. No prop drilling, no per-button wiring, no client-side state. The form itself gets…
four retro follow-ups from v0.7.2 + v0.7.3 milestones bundled into a single docs PR. New file `docs/development/check-authoring.md` documents two reusable patterns surfaced during the v0.7.x check-refinement work:
two tests intermittently failed on the py3.14 CI runner only: `python/tests/test_hotreload.py::TestHotReloadMessage::test_hotreload_slow_patch_warning` (PR #1001 caught it once; passed on rerun) and…
`check_preset_contrast` previously iterated `get_registry().list_presets().items()` and ran WCAG AA contrast checks on every registered preset × mode × token pair. With djust's 65+ built-in presets, that produced hundreds of warnings on every `manage.py…
the A070 / A071 scanner walks template source as raw text. Templates that document the `{% dj_activity %}` tag — common pattern on docs / marketing pages that include literal example markup wrapped in `{% verbatim %}` so Django renders the example as-is — got…
`_check_missing_compiled_css` in `python/djust/checks.py` previously tested only `os.path.exists()`. A committed-but-stale `output.css` (e.g. a placeholder `/* Run tailwindcss ... */`) silently passed the check, the site rendered without any Tailwind…
opt-in horizontal layout for `forms.RadioSelect` fields without writing any new Python. Users add `widget=forms.RadioSelect(attrs={"data-dj-inline": "true"})` to a `ChoiceField` and load `{% static 'djust/djust-forms.css' %}` once in their base template; the…
Docs: forms,
v0.5.7 #762 added a `_FRAMEWORK_INTERNAL_ATTRS` frozenset in `python/djust/live_view.py` to prevent ~25 framework-set attrs (`sync_safe`, `login_required`, `template_name`, ...) from leaking into `get_state()` / reactive-state debug payloads. The v0.5.7 retro…
all v0.5.7 upload-writer tests mock the SDKs. Happy-path end-to-end verification against real AWS S3 / Google Cloud Storage / Azure Blob was missing; silent regressions in credential handling, SDK auth chain changes, or bucket permissions could reach…
`djust.contrib.uploads.s3_events.parse_s3_event` extracts `upload_id` by finding the first UUID-shaped path segment in the S3 object key; apps whose `key_template` doesn't produce such a segment silently fall back to the full key as `upload_id`, and hooks…
Docs: uploads,
`djust.serialization._serialize_model_safely` sets `"__str__": str(obj)` on every dict it produces so `{{ obj }}` in a Rust-engine template can match Django's default `str(obj)` semantics. The Rust `Value::Object` Display impl (`crates/djust_core/src/lib.rs`)…
the `try/except ImportError` block at `dev_server.py:13-19` sets `WATCHDOG_AVAILABLE = False` but the class statement `class DjustFileChangeHandler(FileSystemEventHandler)` on line 25 referenced the symbol unconditionally. When watchdog is absent, class…
new template tag `{% djust_client_config %}` in `djust.templatetags.live_tags` emits `<meta name="djust-api-prefix" content="...">`. The content is derived via Django's `reverse()` so it honors both `FORCE_SCRIPT_NAME` and any custom…
Docs: server-functions, http-api,
server-side Markdown renderer built on `pulldown-cmark 0.12` with three safety guarantees wired in at the crate level: raw HTML in the source is escaped (`Options::ENABLE_HTML` is never set; because pulldown-cmark 0.12 still emits `Event::Html` /…
Docs: streaming-markdown,
two additions to `djust.admin_ext` close the most-requested gaps in the alternative reactive admin:
Docs: admin-widgets,
React 19.2 `<Activity>` parity: pre-rendered hidden regions of a LiveView that preserve their local DOM state (form inputs, scroll, transient JS) across show/hide cycles. The new block tag ``{% dj_activity "name" visible=expr eager=expr %}...{% enddj_activity…
Docs: activity,
hover- and touch-driven navigation prefetch that complements the existing service-worker-mediated hover prefetch. Links opting in with ``<a dj-prefetch href="...">`` are prefetched after a 65 ms hover debounce (cancelled on ``mouseleave`` before the debounce…
Docs: prefetch,
same-origin browser RPC without VDOM re-render. Decorate a LiveView method with ``@server_function`` and invoke it from JavaScript as ``await djust.call('<view_slug>', '<fn>', {params})``; the return value is JSON-serialized straight back to the caller. The…
Docs: server-functions,
dev-only debug-panel tab that records a state snapshot around every `@event_handler` dispatch (`state_before` / `state_after`), then lets developers scrub back through the timeline and jump to any past state. The server restores the snapshot via…
Docs: time-travel-debugging,
opt-in chunked HTTP response for LiveView GET requests. Setting `streaming_render = True` on a LiveView class returns a `StreamingHttpResponse` that flushes the page in three chunks: shell-open (everything before `<div dj-root>`), main content (the `<div…
Docs: streaming-render,
state-preserving Python code reload in development. When a LiveView module changes on disk, the dev server `importlib.reload()`s the module and swaps `__class__` in place on every live instance of the changed class, then re-renders via the existing VDOM diff…
Docs: hot-view-replacement,
documents that browser-native `@starting-style` works unmodified with djust's VDOM insert path. No new djust attributes or JS — the feature is pure CSS. Guide section in `docs/website/guides/declarative-ux-attrs.md` includes a quick-start example, a…
Docs: declarative-ux-attrs,
the three-phase consolidation that started in v0.5.0 is now complete. The five sibling repos (`djust-auth`, `djust-tenants`, `djust-theming`, `djust-components`, `djust-admin`) are sunset at `v99.0.0` — each retains a shim-only `__init__.py` that re-exports…
reproducible profile of the mount → event → VDOM diff → patch path. New `scripts/profile-request-path.py` (cProfile wrapper, optional py-spy hint, writes `artifacts/profile-<timestamp>.{txt,pstats}`; exits non-zero on target-miss for CI). New…
Docs: v0.6.0-profile,
three SW-backed optimizations landed in one PR:
Docs: service-worker,
Phoenix `live_render sticky: true` parity. Shipped across three PRs: #966 (Phase A — embedding primitive), #967 (Phase B — preservation across `live_redirect`), #969 (Phase C — ADR-011, user guide, demo app). Mark a LiveView class with `sticky = True` +…
Docs: 011-sticky-liveviews, sticky-liveviews,
Opt-in per container via `dj-flip`. Declarative attribute on a list parent animates direct-child reorders using First-Last-Invert-Play. Tunables: `dj-flip-duration` (default 300ms, parsed via `Number` + `isFinite` + clamp `[0, 30000]` — trailing garbage…
Template tag for placeholder blocks. Props: `shape` (line|circle|rect, whitelist-validated), `width`/`height` (regex-whitelisted against `^[\d.]+(px|em|rem|%|vh|vw|ch)?$`, invalid falls back to shape default), `count` (clamped to `[1, 100]`), `class_`. All…
Long mobile uploads now survive network hiccups, backgrounded tabs, and brief WS drops. New `djust.uploads.resumable.ResumableUploadWriter` wraps any existing `UploadWriter` (S3 MPU, GCS, Azure, tempfile) and persists chunk-level state into a pluggable…
Docs: 010-resumable-uploads,
New `djust.contrib.uploads.s3_presigned` module lets clients upload directly to S3 via a pre-signed URL; djust only signs and observes completion via S3 event webhook. New `djust.contrib.uploads.gcs.GCSMultipartWriter` and…
Docs: uploads,
dj-remove no-CSS-transition gotcha (#902), dj-transition-group long-form precedence (#907), Django 5.1 + 5.2 classifiers in `pyproject.toml` (#912), new guide page for `dj-virtual` variable-height mode at `docs/website/guides/virtual-lists.md` (#952).
Docs: virtual-lists,
PR #796 shipped `dj-virtual` with fixed-height items only. This adds opt-in variable-height support via a new `dj-virtual-variable-height` boolean attribute. Implementation: ResizeObserver per rendered item feeds a `Map<index, number>` height cache; a…
new `scripts/check-changelog-test-counts.py` parses phrases like `N JSDOM cases`, `N regression tests`, `N unit tests`, `N test cases`, `N parameterized cases` in the `[Unreleased]` section, resolves every backticked `tests/js/*.test.js` /…
`scripts/codeql-triage.sh [rule-id]` paginates `/repos/{owner}/{repo}/code-scanning/alerts?state=open` via `gh api` and emits a markdown triage doc grouped by `rule.id`, sorted within each group by file/line. Optional positional arg filters to a single rule…
new extension pack at `.github/codeql/models/` (qlpack.yml + `djust-sanitizers.model.yml`) teaches CodeQL that `djust._log_utils.sanitize_for_log()` is a log-injection sanitizer. Referenced from `.github/codeql/codeql-config.yml` via a new `packs:` section.…
formalizes the `_restore_<concept>()` pattern first shipped ad-hoc in PRs #891 (UploadMixin, #889) and #895 (PresenceMixin + NotificationMixin, #893 / #894). Codifies the serialization contract (JSON-only saved attrs), error handling (WARNING-level wrap…
Docs: 009-mixin-side-effect-replay,
djust.A010 / A011 system checks now recognize proxy-trusted deployments: when `SECURE_PROXY_SSL_HEADER` + `DJUST_TRUSTED_PROXIES` are both set, `ALLOWED_HOSTS=['*']` is accepted (supports AWS ALB, Cloudflare, Fly.io, and other L7 load balancers where task…
tag_input hidden-input payload now JSON-encoded instead of comma-separated, so tag values containing commas round-trip intact (#949). dj-virtual variable-height cache now keyed by `data-key` attribute (configurable via `dj-virtual-key-attr`), falling back to…
bumped `ruff-pre-commit` from v0.8.4 to v0.15.11 (#948) and applied `ruff format` to all resulting drift (#791 — expanded beyond the original 5 files due to modern-ruff disagreements; 19 files total across `python/djust/` and `tests/`). Added `logger.debug`…
**#935**: fixed 3 stale test assertions that were checking for leaked exception-class names in API error responses. The implementations in `api/dispatch.py`, `observability/views.py` deliberately sanitize error payloads (don't echo `RuntimeError` / internal…
`_restore_upload_configs` now wraps each per-slot `allow_upload(**cfg)` in try/except `TypeError`. On signature mismatch (kwarg added / renamed / removed between djust versions), logs a WARNING identifying the slot + the mismatched kwarg, then falls back to…
`_restore_listen_channels` now detects when the `PostgresNotifyListener` singleton is stranded on a closed event loop (server restart with fresh ASGI loop, test harness per-test loops, sticky-session LB cross-worker handoff) and calls a new…
**#879**: `37-dj-mutation.js` and `38-dj-sticky-scroll.js` document-level root observers now detect attribute REMOVAL on already-observed elements (via `attributes: true` + `attributeFilter: ['dj-mutation']` / `['dj-sticky-scroll']`) and call the module's…
**#914**: dropped redundant `ch == " "` clause in `_log_utils.sanitize_for_log` — ASCII space is already printable so the explicit check was dead. **#915**: bulk-applied `ruff format` (pinned pre-commit version 0.8.4) to 4 pre-drifted files (3 theming test…
**#930 FormArrayNode inner content**: `{% form_array %}...{% endform_array %}` parsed the block body into a nodelist via `parser.parse(("endform_array",))` but `FormArrayNode.render` never rendered that nodelist — users' inner template markup silently…
Extracted shared `_teardownState(el, state)` helper in `42-dj-remove.js` so `_finalizeRemoval` and `_cancelRemoval` no longer duplicate the clearTimeout + removeEventListener + observer.disconnect + _pendingRemovals.delete block (Stage 11 nit from PR #898).…
**#886** `_parseSpec` in `41-dj-transition.js` now rejects comma, paren, and bracket separators up front (returns `null` and emits a debug warning gated on `globalThis.djustDebug`) instead of letting `classList.add` throw `InvalidCharacterError` at runtime…
**#905** The VDOM `RemoveChild` integration test in `tests/js/dj_transition_group.test.js` waited 700 ms per run for the default dj-remove fallback timer. Pinned `dj-remove-duration="50"` on the child and reduced the wait to ~80 ms, dropping this file's…
(`requires-python = ">=3.10"`). Python 3.9 reached end-of-life on 2025-10-05; the ecosystem has since moved on (orjson, pytest, python-dotenv, requests, and mcp have all dropped py3.9 support in versions that carry security fixes). Keeping py3.9 in the…
Phoenix `JS.hide` / `phx-remove` parity. When a VDOM patch, morph loop, or `dj-update` prune would physically remove an element carrying `dj-remove="..."`, djust delays the actual `removeChild()` until the CSS transition the attribute describes has played out…
React `<TransitionGroup>` / Vue `<transition-group>` parity. Authors mark a parent container and specify enter + leave specs once; djust wires those specs onto each child by setting `dj-transition` (enter) and `dj-remove` (leave) — re-using the…
Real refactor: extracted `ContextProviderMixin` from `live_view.py` to a new `_context_provider.py` module so `components/base.py` can import it without creating a module-level cycle back through `live_view -> serialization -> components/base`. `live_view.py`…
Narrowed over-broad `except Exception: pass` to specific exception types where the call surface was knowable, and added `logger.debug(...)` (with `import logging; logger = logging.getLogger(__name__)` where not already present) for optional-feature probes in…
mechanical fixes: deleted unused imports (treated re-exports with `__all__` + `# noqa: F401` preservation; replaced side-effect submodule imports with `importlib.import_module`), removed ~30 unused local variables across `rust_handlers.py`…
CodeQL's `py/unsafe-cyclic-import` rule flagged 872 alerts across the theming subsystem: `themes/_base.py` imported dataclasses + shared style instances from `..presets` and `..theme_packs`, and those two modules re-imported each theme file under `.themes.*`…
the label-visibility check at line 88 had `isinstance(field.widget, template.library.InvalidTemplateLibrary if False else type(None))`. The `if False else type(None)` ternary always evaluated to `type(None)`, making the first operand unreachable dead code…
`djust/auth/__init__.py` and `djust/tenants/__init__.py` use a `__getattr__`-based lazy-import dispatcher to defer Django-ORM-dependent imports. CodeQL's static analysis doesn't recognize this pattern; names declared in `__all__` but only resolved via…
**`python/djust/components/gallery/views.py`** (`py/stack-trace-exposure`, 2 alerts): the gallery's per-variant render fallback interpolated the raw `Exception` repr into the HTML returned to the user (`f'<div ...>Render error: {exc}</div>'`), leaking…
`inlineFormat` in `python/djust/components/static/djust_components/markdown-textarea.js` applied regex-based markdown substitutions on raw user input and wrote the result into the preview pane via `innerHTML`, so a user typing `# <script>alert(1)</script>`…
`python/djust/static/djust/service-worker.js` processed any incoming `message` event without inspecting `event.source`. Service workers are inherently same-origin (they cannot be loaded cross-origin, so `postMessage` from a cross-origin page can't reach the…
**Real (3 code fixes, closing 4 alerts):** `python/djust/auth/views.py` `SignupView.get_success_url` accepted any `next` POST param and passed it straight to `redirect()`, so a crafted form post could bounce newly-authenticated users to an attacker-controlled…
Stack traces and exception messages can reveal internal file paths, local variable names, DB schema details, and dependency versions, giving attackers a head-start on probing. Three call sites were rewritten to return generic messages and log the full…
Three real reflective-XSS sites in `python/djust/theming/gallery/views.py` (lines 276, 281, 306): `storybook_detail_view` and `storybook_category_view` echoed the user-controlled URL kwargs `component_name` / `category` into `HttpResponseNotFound(f"Unknown…
Added `djust._log_utils.sanitize_for_log()`: strips CR/LF/TAB/control chars, replaces with `?`, truncates to 200 chars, always returns a string (None / non-string inputs become their `repr`). Applied at 5 call sites in `python/djust/api/dispatch.py` (wrapping…
Addresses 23 open Dependabot alerts (13 unique CVEs). Bumps: Django 4.2.29 → 5.2.13 (CVE floor 4.2.30; tightened `pyproject.toml` ceiling to `<6` to keep the major-version jump out of a security-only PR), cryptography 46.0.5 → 46.0.7 (buffer overflow + DNS…
Pre-commit config has used `ruff` + `ruff-format` hooks since v0.5.x; no `Makefile` / CI / import site references black. Removed `black>=24.10.0` / `black>=26.3.1` from the `dev` group in `pyproject.toml` and the `[tool.black]` config section. Ruff already…
Sibling bugs to #889, found via audit after the `UploadMixin` fix shipped in #891. Both issues share the same root cause: a mixin's `mount()`-called method has a process-wide side effect beyond setting instance attrs, and the WS consumer's state-restoration…
Production-critical bug affecting every app using `UploadMixin` with the default pre-rendered HTTP→WS flow. The WS consumer's state-restoration path (`websocket.py:1540-1572`) skips `mount()` when pre-rendered session state exists, and the live…
Phoenix `JS.transition` parity. Three-phase class orchestration so template authors can drive CSS transitions without writing a `dj-hook`. Attribute value is three space-separated class tokens — phase 1 (start) applied synchronously, phases 2 (active) + 3…
Docs: declarative-ux-attrs,
Phoenix 1.1 parity. An event handler can swap the surrounding layout template (nav, sidebar, footer, wrapper markup) without a full page reload: inner state — form values, scroll position, focused element, `dj-hook` bookkeeping, third-party-widget references…
Docs: layouts,
VDOM patches compress extremely well (repetitive HTML fragments + JSON structure → 60-80 % wire-size reduction via zlib). Uvicorn and Daphne both negotiate `permessage-deflate` with browsers out of the box, so the wire-level compression is already free in…
Docs: deployment,
Three small client-side declarative attributes that replace boilerplate `dj-hook`s every production app tends to write. **`dj-mutation`** (new `static/djust/src/37-dj-mutation.js`, ~100 LOC) fires a `dj-mutation-fire` CustomEvent when the marked element's…
Docs: declarative-ux-attrs,
Previously the only way to detach the `post_save` / `post_delete` receivers from a `@notify_on_save`-decorated model was to clear the entire `signals.receivers` list, which scorched unrelated test fixtures. `untrack()` now disconnects exactly the two…
Docs: database-notifications,
Production now serves `client.min.js` (terser-minified) instead of the 35-module readable concat, with `.gz` and `.br` pre-compressed siblings built alongside it for whitenoise / nginx static serving. Measured impact: `client.js` 410 KB → `client.min.js` 146…
The extractor now captures positional context-variable references in `{% firstof a b c %}` and `{% cycle a b c %}` (string literals and `as <name>` suffixes are correctly ignored), and the `with x=expr` (and `count x=expr`) clauses of `{% blocktrans %}` / `{%…
Docs: typecheck,
Two low-priority gaps deferred from PR #802 are now surfaced in both the Rust-side `register_block_tag_handler` docstring (`crates/djust_templates/src/registry.rs`) and the Python-side `.pyi` stub (`python/djust/_rust.pyi`). The "no parent-tag propagation"…
`isIgnoredAttr` now skips empty tokens produced by double-comma (`"open,,close"`) or trailing-comma (`"open,"`) CSV values, and rejects empty attribute-name queries. Previously those edge cases could accidentally match an empty attribute name. Four regression…
`_extract_context_keys_from_ast` now iterates `cls.__mro__` (skipping `djust.*`, `djust_*`, `django.*`, `rest_framework.*`, and `builtins`), so a child view that relies on attributes set in a parent `mount()` no longer produces spurious "unresolved" reports.…
`_walk_subclasses`, `_is_user_class`, and `_app_label_for_class` are now a single source of truth in the new `djust.management._introspect` module; `djust_audit` and `djust_typecheck` both import from it. No behavior change; purely a refactor to prevent drift…
Two rapid `assign_async("metrics", loader)` calls used to race: the first loader's worker thread could still be in-flight when the second call scheduled a new task, and when the slow loader finally completed, its `setattr(self, "metrics"…
`{{ value|default:fallback }}` now tracks `fallback` as a template dependency alongside `value`. Previously the dep-extractor walked filter chains but dropped all filter arguments, so a pattern like `{% if show %}{{ value|default:dynamic }}{% endif %}` would…
`{% for x in foo.bar %}` now uses `Context::resolve` (which walks getattr through the raw-PyObject sidecar) with a fallback to `Context::get`, instead of only consulting the value-stack. Previously dotted iterables silently rendered as empty when the…
PostgreSQL caps `NOTIFY` payloads at 8000 bytes. `send_pg_notify()` now warns at 4KB (soft limit) and drops + error-logs at 7500 bytes (hard limit). (`python/djust/db/decorators.py`)
The existing `reset_for_tests()` fire-and-forget cancel is now documented as such; new async variant awaits the cancelled task so async test teardowns don't race. (`python/djust/db/notifications.py`)
100ms timeout is best-effort under contention; dropped notifications do not queue. (`python/djust/websocket.py`)
Locks in that `getattr(view, '_listen_channels', None)` + truthy gate handles both absent-attr and empty-set paths. (`tests/unit/test_db_notifications.py`)
Server trims `items_list` to at-most `limit` before emitting inserts. (`python/djust/mixins/streams.py`)
Teardown now restores pre-virtualization children and removes the shell/spacer. (`python/djust/static/djust/src/29-virtual-list.js`)
Cosmetic cleanup. (`python/djust/static/djust/src/17-streaming.js`)
Test-client drain now mirrors the production WS consumer. (`python/djust/testing.py`)
Raises `AssertionError` with all queued paths. (`python/djust/testing.py`)
Non-JSON returns caught at finalize time and abort the upload cleanly. (`python/djust/uploads.py`)
`_finalized` flag now actively enforced; repeated `close()` is idempotent. (`python/djust/uploads.py`)
Fast-path at DEBUG log; `writer.abort()` called once. (`python/djust/uploads.py`)
The VDOM morph loop at `python/djust/static/djust/src/12-vdom-patch.js:746-758` previously stripped and overwrote attributes without consulting `djust.isIgnoredAttr`. Attributes listed in `dj-ignore-attrs` would survive individual `SetAttr` patches (the guard…
Handlers serving both WebSocket and HTTP API callers often have split needs: WS only wants state mutation (VDOM renders the UI), HTTP wants actual data in the response. Serializing query results on every WS keystroke is wasteful. Resolved with three-tier…
Docs: http-api,
Seven new methods on `LiveViewTestClient` for 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)…
Docs: testing,
Declarative opt-in for the HTML `<dialog>` element's built-in modal behavior. Mark a `<dialog>` with `dj-dialog="open"` to call `showModal()` (backdrop, focus-trap, and Escape handling all browser-native); set `dj-dialog="close"` to call `close()`. A…
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…
Docs: typecheck,
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…
Docs: error-overlay,
djust-native support for Django formset / inline-formset patterns. Template side: `{% inputs_for formset as form %}...{% endinputs_for %}` iterates any `BaseFormSet` and exposes each bound child form with its per-row prefix intact so rendered inputs submit…
`djust_theming/static/djust_theming/css/scaffold.css` gains ~729 lines of framework-generic scaffold covering typography, responsive grid utilities (`.grid-2/3/4`), hero section, flash messages (Django + LiveView), accessibility utilities (`.sr-only`)…
The `make test` baseline went from `2135 passed, 61 failed, 21 errors` (which had blocked normal merges for the entire v0.5.1 milestone and forced `--admin` on every PR) to `2219 passed, 0 failed, 0 errors`. Four fix clusters:
Auto-extraction regex gained a negative lookbehind `(?<![\w])` to prevent capturing domain fragments inside data-URIs (previously `.org` and `.w3` in `http://www.w3.org/2000/svg` were mis-captured as class selectors, producing…
Five entries marked as "v0.5.1 Not started" were actually shipped earlier: djust-theming fold (v0.5.0 PR #772), WizardMixin (PR #632), Error boundaries (v0.5.0 PR #773), and `dj-lazy` lazy LiveView hydration (PR #54). All marked with strikethrough + ✅ and a…
Three related form-UX primitives:
Four small related primitives for derived state, dirty tracking, stable IDs, and cross-component context sharing:
Docs: state-primitives,
Opt-in `@event_handler(expose_api=True)` exposes a handler at `POST /djust/api/<view_slug>/<handler_name>/` with an auto-generated OpenAPI 3.1 schema served at `/djust/api/openapi.json`. Unlocks non-browser callers (mobile, S2S, CLI, AI agents) without…
Docs: 008-auto-generated-http-api-from-event-handlers, http-api,
Two independent SW features that close the v0.5.0 milestone. Both are OFF by default; users opt in explicitly via `djust.registerServiceWorker({ instantShell: true, reconnectionBridge: true })` from their own init code. No auto-registration.
Docs: service-worker,
New `UploadWriter` base class in `djust.uploads` with an `open()` → `write_chunk(bytes)` → `close() -> Any` / `abort(error)` lifecycle, wired into `allow_upload(name, writer=MyWriter)`. When a writer is configured, binary WebSocket chunks are piped straight…
Docs: uploads,
Mark specific HTML attributes as client-owned so VDOM `SetAttr` patches skip them. `<dialog dj-ignore-attrs="open">` prevents the server from resetting the `open` attribute that the browser manages; `<div dj-ignore-attrs="data-lib-state, aria-expanded">`…
Write hook JavaScript inline alongside the template that uses it, instead of in a separate file. `{% colocated_hook "Chart" %}hook.mounted = function() { renderChart(this.el); };{% endcolocated_hook %}` emits a `<script type="djust/hook" data-hook="Chart">`…
Docs: hooks,
Subscribe LiveViews to Postgres pg_notify channels so database changes push real-time updates to every connected user with zero explicit pub/sub wiring. Three APIs: `@notify_on_save(channel="orders")` model decorator hooks Django `post_save` / `post_delete`…
Docs: database-notifications,
Templates can now reference Django model instances passed through context without manual dict conversion. `{{ user.username }}` resolves via Python `getattr` when `user` is a raw Python object rather than a JSON-serialized dict. Implementation: Python's…
New tag-handler variety complementing `register_tag_handler` (emits HTML) and `register_block_tag_handler` (wraps content). An assign tag's `render(args, context)` method returns a `dict[str, Any]` that's merged into the template context for subsequent…
Docs: template-cheatsheet,
Render only the visible slice of a large list, recycling DOM nodes as the user scrolls. `<div dj-virtual="items" dj-virtual-item-height="48" dj-virtual-overscan="5" style="height: 600px; overflow: auto;">` keeps ~visible-plus-overscan children in the DOM even…
Docs: large-lists,
Fire server events when the first or last child of a stream container enters the viewport via `IntersectionObserver`. `<div dj-stream="messages" dj-viewport-top="load_older" dj-viewport-bottom="load_newer" dj-viewport-threshold="0.1">`. Once-per-entry firing…
High-level async data loading inspired by Phoenix LiveView's `assign_async`. Call `self.assign_async("metrics", self._load_metrics)` in `mount()` (or any event handler); the attribute is set to `AsyncResult.pending()` immediately, the loader runs via the…
Declarative counterpart to `assign_async`: wrap a section depending on one or more `AsyncResult` assigns, and the boundary emits a fallback while any are loading, an error div if any failed, or the body once all are `ok`. Explicit `await="metrics,chart"`…
Docs: loading-states,
Stateless Python render functions registerable as template-invokable components. `@component def button(assigns): ...` is callable from templates via `{% call "button" variant="primary" %}Go{% endcall %}` (with `{% component %}` as a synonymous alias). Closes…
`Assign("variant", type=str, default="default", values=["primary", "danger"], required=True)` and `Slot("col", multiple=True)` DSL, declared on a `LiveComponent` class attribute (`assigns = [...]` / `slots = [...]`) or on function components via…
Docs: components,
Parent templates pass named content blocks with attributes into components: `{% call "card" %}{% slot header label="Title" %}Header{% endslot %}Body{% endcall %}`. Multiple same-name slots collect into a list (essential for table columns, tab panels). Slots…
Variables inside HTML attribute values now route through a dedicated `html_escape_attr()` that's guaranteed to escape `"` → `"` and `'` → `'` (in addition to `&`/`<`/`>`). Detection reuses the existing `is_inside_html_tag_at()` parser helper — the…
Same failure mode as nested `{% include %}`: `extract_from_nodes` had no arm for `Node::InlineIf`, so its `true_expr` / `condition` / `false_expr` variables were silently dropped from the dep set of any surrounding `{% if %}` / `{% for %}` / `{% with %}`.…
Rust partial renderer reused the cached fragment of an `{% if %}` / `{% for %}` / `{% with %}` wrapping a nested `{% include %}`, because `extract_from_nodes` treated `Include` as having no variable references. When the included template referenced a context…
When `_force_full_html` was set, `_sync_state_to_rust()` cleared `prev_refs` to force all context to Rust, but the `set_changed_keys` call was gated by `if prev_refs` which evaluated to False after clearing. Rust's partial renderer saw no `changed_keys`, fell…
The v0.5.0 ROADMAP entry claiming `temporary_assigns` was "completely absent from djust today" was inaccurate. The feature has shipped in earlier releases (`LiveView._initialize_temporary_assigns` / `_reset_temporary_assigns`, wired into the render cycle and…
`tests/unit/test_temporary_assigns.py` covers reset-after-render semantics, default-value cloning per type (list / dict / set / scalar), idempotent initialization, pre-existing-attribute preservation, instance-level override, and the empty-mapping no-op path.
`tests/unit/test_assign_async.py` (18 tests) covers state-flag invariants, frozen dataclass immutability, pending-is-set-immediately, success & failure propagation, multi-concurrent scheduling, cancellation interop with `cancel_async`, sync and async loaders…
`tests/unit/test_suspense.py` (12 tests) covers ok → body, loading → fallback, failed → error-div, HTML-escaped error messages, no-`await=` passthrough, unknown / non-`AsyncResult` refs defaulting to loading, default spinner, Django template fallback…
`tests/test_rust_vdom_safe_diff_783.py` exercises the WizardMixin-style pattern where `field_html` is derived in `get_context_data()` from an instance attribute. Covers dict reassignment, in-place nested mutation, the `_force_full_html` codepath, an `{% if…
Three-part hardening against silent dep-drop regressions in `crates/djust_templates/src/parser.rs::extract_from_nodes`:
(`crates/djust_live/src/lib.rs`) — clears `node_html_cache`, `last_html`, `fragment_text_map`, `text_node_index` while preserving `last_vdom` so the diff baseline is unchanged. Exclusively supports the partial-render correctness harness above; not intended…
New `Bootstrap4Adapter` for projects using Bootstrap 4 (NYC Core Framework, government sites, legacy projects). Set `DJUST_CONFIG = {"css_framework": "bootstrap4"}`. Includes proper `custom-select`, `custom-control-*` classes for checkboxes/radios, and…
Docs: css-frameworks,
Radio buttons now use `radio_class`, `radio_label_class`, and `radio_wrapper_class` config keys (with fallback to checkbox classes). Both Bootstrap 4 and 5 configs define radio-specific classes.
`ChoiceField` with `Select` widget uses `select_class` config key (e.g., `custom-select` for BS4, `form-select` for BS5) instead of the generic `field_class`.
New `{% theme_framework_overrides %}` template tag generates `<style>` overrides that map djust theme variables (`--primary`, `--border`, etc.) onto the active CSS framework's selectors (`.btn-primary`, `.form-control`, `.alert-*`, etc.). Switching themes now…
Docs: css-frameworks,
The Rust state sync used `id()` comparison for all non-immutable context values, which is unreliable for containers (dict, list, tuple) due to CPython address reuse after GC. Derived values like `current_step = wizard_steps[step_index]` could be missed when…
One install, one version, one CHANGELOG. `pip install djust` stays lean; `pip install djust[all]` gets everything.
DEBUG-gated, localhost-only HTTP endpoints that give external tooling (like the djust Python MCP and djust-browser-mcp) live visibility into framework state without in-process coupling. Ships as seven endpoints under `/_djust/observability/`: `health`…
Real server-side `self.*` state of the mounted LiveView for a given session. Complements browser-mcp's client-only `djust_state_diff` with the source of truth. Per-attr fallback tags non-serializable values with `{_repr, _type}` rather than an all-or-nothing…
Ring-buffered (50) exception log populated from `handle_exception()`. Replaces "can you paste the terminal?" for 80% of blind-debugging cases.
Ring-buffered (500) Django/djust log records with `since_ms` + `level` filters. `djust.*` captured at DEBUG+, `django.*` at WARNING+. See `docs/website/guides/mcp-server.md`.
Docs: mcp-server,
Per-handler rolling 100-sample distribution (min/max/avg/p50/p90/p99). Reuses existing `timing["handler"]` measurements; no extra perf counters.
Per-event SQL capture via `connection.execute_wrappers`. Queries are tagged with `(session_id, event_id, handler_name)` + `stack_top` filtered to skip framework frames.
Replay `view.mount()` on a registered instance. Clears public attrs, re-invokes `mount(stashed_request, **stashed_kwargs)`. Useful between fixture replays.
Dry-run a handler against a live view's current state. Returns `{before_assigns, after_assigns, delta, result}`. v2 `dry_run=True` installs a `DryRunContext` that blocks `Model.save`/`delete`, `QuerySet.update`/`delete`/`bulk_create`/`bulk_update`…
Cross-references a template file against every view that uses it, returning dj-* handlers wired in the template and the diff against view handler methods. Catches dead bindings at author time (complements djust-browser-mcp's runtime `find_dead_bindings`). See…
Docs: djust-audit,
Subprocess wrapper around `manage.py loaddata` for regression-fixture DB setup.
Docs: mcp-server,
When a Python file changes that doesn't affect the currently-mounted view, re-render produces zero patches. The old code still broadcast ~14 KB (empty patches + full `_debug` state dump) to every connected session. Early-return when `hotreload=True AND…
Per `djust/CLAUDE.md` rule, no `console.log` without `if (globalThis.djustDebug)` guard. Introduced a `djLog` helper in `00-namespace.js` and replaced bare `console.log` → `djLog` across 12 client modules. `console.warn`/`console.error` untouched (real…
Silent `except Exception: pass` meant the process could run indefinitely with a wrapped `Model.save` if uninstall partially failed — catastrophic for a dev server. Replaced with a `logger.warning` so the failure is observable.
Side-effect blocking now covers QuerySet bulk writes ([#758](https://github.com/djust-org/djust/issues/758)): `QuerySet.update`/`delete`/`bulk_create`/`bulk_update` are patched alongside `Model.save`/`delete`, so a handler that does…
Two tests claimed to verify the record-but-allow contract but only checked detection. Now use `unittest.mock` to assert the original callable was actually invoked (`call_count == 1`) alongside the violation-recorded assertion.
The scanner that builds the VDOM text-node position index used to process the full pre-hydration HTML, but the VDOM is rooted at `[dj-root]`. On templates extending a base (with `<title>`, meta tags, scripts outside dj-root), the scanner counted text runs in…
Docs: templates,
Extends the existing text-fast-path to handle changes that differ only in a text span, even when the surrounding fragment contains tags. Computes byte-level common prefix/suffix on pre-hydration HTML; if the divergence is a single tag-free text run, locates…
New public entry point in `djust_vdom` that uses html5ever's `parse_fragment` with a parent-element context. Enables parsing isolated HTML fragments with correct tokenization for context-sensitive elements (`<tr>`, `<td>`, `<option>`), without resetting the…
Previously collected `<!--dj-if-->` placeholders into the text-node list, shifting every subsequent ordinal by one and breaking any position-based patching. Text and comment VNodes both carry `text`, so an explicit `is_text()` filter was needed.
Per-node dependency tracking at template parse time. On re-render, only template nodes whose context variable dependencies changed are re-rendered; unchanged nodes reuse cached HTML. For a single-variable change on a page with 50 template nodes, template…
Templates using `{% extends %}` now participate in partial rendering. Inheritance is resolved once via `OnceLock<ResolvedInheritance>` on the `Template` struct (shared via `TEMPLATE_CACHE`). Final merged nodes and their deps are cached, so subsequent renders…
When all changed template fragments are plain text (no HTML tags), skip both html5ever parsing and VDOM diffing entirely. The old VDOM is mutated in-place via a fragment→text-node map built on first render, and SetText patches are produced directly. For…
`{% block %}` nodes left by Django's template engine are flattened to expose each child as a separate fragment. This enables the text fast path to activate on pages using `{% extends %}` where Django resolves blocks.
`_snapshot_assigns` uses identity + shallow fingerprints (id, length, content hash for list-of-dicts) instead of `copy.deepcopy`. Framework-internal keys (`csrf_token`, `kwargs`, `temporary_assigns`, `DATE_FORMAT`, `TIME_FORMAT`) and auto-generated `_count`…
Pre-sized attribute HashMap, eliminated redundant `to_lowercase()` call, removed form element debug output.
`_sync_state_to_rust` previously skipped id()-based change detection for immutable types (int/str/bool/bytes) to avoid false positives from Python's int cache, which meant derived values computed in `get_context_data` (e.g. `completed_count = sum(...)`…
When the patcher morphs an input into a different field (e.g., wizard step 1 name → step 2 email), the old field's typed value no longer leaks into the new field. Both `morphElement` and `SetAttr` patches now clear `.value` when the `name` attribute changes.
`_snapshot_assigns` now fingerprints list contents (id + dict values hash) to detect mutations like `todo['completed'] = True` that don't change the list's id or length. Falls back to id-only for unhashable values.
When `_changed_keys` is set, the sync also checks non-immutable context values by id() to catch derived values (e.g., `products` from `_products_cache`) that change via private attributes.
These were called in both `applyPatches()` and `reinitAfterDOMUpdate()`, scanning the full DOM twice per patch cycle. Removed from `applyPatches()`. Saves ~5ms per event.
Replaced `querySelectorAll('*')` full DOM scan with a registry-based delegation pattern. Scoped elements are scanned once at mount time and registered in a Map. Event listeners on window/document dispatch to the registry. Handles dotted attribute variants…
2-3x faster than stdlib `json.loads()` when orjson is installed. Falls back gracefully.
`get_debug_update()` (dir + getattr + json.dumps per attribute) only runs when the debug panel is actually open, not on every event in DEBUG mode. Saves ~2-5ms per event. Panel sends `debug_panel_open`/`debug_panel_close` WS messages on toggle.
The JS patcher was counting all HTML comment nodes during path traversal, but the Rust VDOM parser only preserves `<!--dj-if-->` placeholders. This caused every page with HTML comments in `dj-root` to fail VDOM patching and fall back to full HTML recovery.
`handleLiveRedirect()` now scrolls to the top of the page (or to anchor if URL has a hash) after `pushState`.
`bindLiveViewEvents()` no longer scans the DOM after every VDOM patch. Instead, one listener per event type is installed on the `dj-root` element via delegation (`e.target.closest('[dj-click]')`). This reduces client-side post-patch handling from ~56ms to…
Instrumentation measuring template render, html5ever parse, VDOM diff, and HTML serialization. Exposed to Python via `get_render_timing()` and propagated to WebSocket response performance metadata.
The Rust template engine now renders an empty string when no CSRF token is in context (instead of a placeholder that poisoned client.js's CSRF lookup). Python LiveView `_sync_state_to_rust()` now injects the real token from `get_token(request)`. Three-layer…
The POST handler now applies `_apply_context_processors()` before `render_with_diff()` so auth context (user, perms, messages) is available during re-render. Context processor cleanup uses `_processor_context()` context manager for guaranteed cleanup. Merged…
New `apply_filter_with_context()` checks the template context for format settings when no explicit format argument is given. Python injects Django settings into the Rust context during `_sync_state_to_rust()`. Merged as PR #714.
The `|date` filter previously only parsed RFC 3339 datetime strings. `DateField` values (bare dates like "2026-03-15") are now parsed via a `NaiveDate` fallback pinned to midnight UTC. Merged as PR #720.
The CSRF hidden input now uses the shared `filters::html_escape()` utility (escaping &, ", <, >, and single quotes) instead of a manual `.replace()` chain that missed single quotes. Defense-in-depth. Merged as PR #727.
The CSRF token injection in `_sync_state_to_rust()` previously swallowed all exceptions silently. Now logs via `djust.rust_bridge` logger with `exc_info=True`. Merged as PR #721.
Replaced the manual try/finally in the HTTP fallback POST handler with a reusable `@contextmanager` that guarantees cleanup of temporarily injected view attributes. Merged as PR #721 + #727.
`test_debug_state_sizes` corrected for `json.dumps(default=str)` behavior and `\uXXXX` escaping. `navigation.test.js` suppresses happy-dom/undici WebSocket mock `dispatchEvent` incompatibility.
4 tests verifying `_sync_state_to_rust` injects DATE_FORMAT/TIME_FORMAT from Django settings. Merged as PR #721.
4 Rust tests covering invalid dates, non-date strings, empty strings, and partial dates (filter returns original value per Django convention). Merged as PR #727.
Docs: live-input,
Documents supported input formats (RFC 3339, YYYY-MM-DD) and unsupported types (epoch ints, locale strings). Merged as PR #727.
`_sync_state_to_rust()` now collects `id()`s of all sub-objects reachable from changed instance attrs and includes any derived context var whose `id()` appears in that set. Previously, context vars computed in `get_context_data()` that returned sub-objects of…
The initial #683 fix merged `widget.attrs` but `type` was still ignored because Django moves `type=` from `attrs` into `widget.input_type` during widget `__init__`. `_get_field_type()` now checks `widget.input_type` against the widget class's default and uses…
The `@background` decorator now detects `asyncio.iscoroutinefunction` and creates a native async closure so `_run_async_work` can `await` it directly on the event loop instead of routing through `sync_to_async`. The fragile `inspect.iscoroutine(result)`…
`PushEventMixin.flush_push_events()` now resolves the flush callback via `self._ws_consumer._flush_push_events` at call time instead of relying on a stored `_push_events_flush_callback` that was only wired during initial mount. After a WebSocket reconnect the…
Handlers that only call `push_commands()` / `push_event()` without changing public state no longer trigger a VDOM re-render. The `_snapshot_assigns` deep-copy comparison could report false positives for views with non-copyable public attributes (querysets…
Django's `View.__init__` does not call `super().__init__()`, so writing `class MyView(LiveView, TutorialMixin)` silently skips TutorialMixin's initialisation. A new `djust.V010` system check scans all LiveView subclasses at startup and emits an Error with a…
`@background` wraps handlers in a sync closure; when the handler is `async def`, the closure returned an unawaited coroutine and the handler body never ran. The fix in `_run_async_work` (already on main via workaround) detects coroutine returns and awaits…
Push events queued by `push_commands` inside a `@background` handler only reached the client when the entire task completed. The `_flush_pending_push_events` callback mechanism (already on main) lets TutorialMixin and other background handlers flush events…
The MRO walker in `ContextMixin.get_context_data()` added class-level attributes (like `tutorial_steps`) to the template context. Non-JSON-serializable values were silently converted to their `str()` repr, corrupting state on subsequent events. The fix skips…
SVG attributes like `viewBox` and `path d` in the debug toolbar were rendered garbled because the Rust VDOM's `to_html()` method HTML-escaped text content inside `<script>` and `<style>` elements. Per the HTML spec, these are "raw text elements" whose content…
`datetime.date`, `datetime.datetime`, `datetime.time`, `Decimal`, and `UUID` values in `form.cleaned_data` stored in public view state are now properly serialized to their JSON representations (ISO strings, floats, strings) instead of silently becoming…
Storing a Python `set()` or `frozenset()` in public view state no longer crashes `json.dumps`. Sets are serialized as sorted lists (falling back to unsorted when elements aren't comparable). Both `DjangoJSONEncoder.default()` and `normalize_django_value()`…
Round-tripping state through the Rust MessagePack serialization boundary could corrupt `dict` values into `list` because `#[serde(untagged)]` on the `Value` enum let `rmp_serde` match a msgpack map against the `List` variant before trying `Object`. The fix…
The `as_live_field()` method (and `{% live_field %}` tag) dropped any attributes defined on a Django widget's `attrs` dict — `type="email"`, `placeholder`, `pattern`, `min`/`max`, custom `data-*`, and any other HTML attributes were silently lost. The fix adds…
The VDOM diff patcher called `setAttribute()`, `removeAttribute()`, `appendChild()`, `removeChild()`, and `replaceChild()` on `#text` nodes, which don't implement these methods. This crashed conditional rendering whenever a text node sat where the patcher…
Dynamically inserted `<input autofocus>` elements didn't receive focus after a VDOM patch because the browser only honours the `autofocus` attribute on initial page load. The patcher now detects `autofocus` on newly inserted elements after each patch cycle…
Two related state-management bugs caused any attribute starting with `_` (the documented convention for private/internal state) to be silently wiped. The root cause was that session save used the output of `get_context_data()`, which by design strips…
Carry-over bugfix from v0.4.1. When a page is pre-rendered via HTTP GET, the WebSocket mount used to call `reinitAfterDOMUpdate()` synchronously right after stamping `dj-id` attributes onto the existing DOM. That synchronous call triggered a full DOM…
Carry-over bugfix from v0.4.1. Previously, `python -m djust startproject mysite` and `python -m djust new mysite` both generated a `settings.py` with `DEBUG = True` and `ALLOWED_HOSTS = ["*"]` as hardcoded literals. A developer who deployed the scaffolded…
Closes the "known limitation" documented in the v0.4.2 tutorials guide: `await self.wait_for_event("foo")` on a LiveView now resolves when the matching handler fires on an embedded `LiveComponent`, not just when it fires on the view itself. Without this, a…
Capstone of ADR-002 Phase 1: a one-import, zero-JavaScript way for any djust app to ship a real guided tour, onboarding flow, or wizard. Apps declare the tour as a list of `TutorialStep` dataclasses on a `LiveView` that mixes in `TutorialMixin`; the framework…
Second half of the backend-driven UI Phase 1 primitives. Adds a new `WaiterMixin` (automatically included in `LiveView`) that lets a `@background` handler suspend until a specific `@event_handler` is called by the user, optionally filtered by a predicate…
First half of the backend-driven UI primitives proposed in ADR-002. Adds a one-line server-side helper `self.push_commands(chain)` that takes a `djust.js.JSChain` (shipped in v0.4.1 as the JS Commands fluent API) and pushes it to the current session as a…
If the `{% tutorial_bubble %}` tag is placed inside the `dj-root` container, morphdom recovery (which replaces the entire `dj-root` content on patch failure) destroys the bubble mid-tour, causing it to silently disappear. The tutorials guide now has a…
How `data-foo-bar` on an HTML element maps to `foo_bar` in the event handler's kwargs was undocumented. The Events guide now has a dedicated "Data Attribute Naming Convention" section covering: the dash-to-underscore rule, client-side type-hint suffixes…
These three informational checks fire on every `manage.py` invocation and are noisy for projects that deliberately don't use the checked features (daphne, explicit `dj-root`, non-primitive mount state). A new `suppress_checks` config key in `DJUST_CONFIG` (or…
v7 validates `target_commitish` against the GitHub releases API and rejects `refs/pull/<n>/merge` refs, which is what `github.ref` resolves to under a `pull_request` trigger. v6 silently tolerated this; v7 does not, causing every PR to fail with `Validation…
Drains the dependabot backlog that was held behind the v0.4.1 release. Single consolidated PR so one CI run catches any inter-dep interactions:
Closes the single biggest DX gap vs Phoenix LiveView 1.0. Eleven commands (`show`, `hide`, `toggle`, `add_class`, `remove_class`, `transition`, `dispatch`, `focus`, `set_attr`, `remove_attr`, `push`) that run locally without a server round-trip, plus a `push`…
Docs: js-commands,
New attribute that fires a server event when the user pastes content into a bound element (`<textarea dj-paste="handle_paste">`). The client extracts structured payload from the `ClipboardEvent` in one pass: `text` (`clipboardData.getData('text/plain')`)…
Docs: dj-paste,
Adds a new mode to `djust_audit` that walks the project's Python source and Django templates looking for five specific security anti-patterns, each motivated by a live vulnerability or near-miss in the 2026-04-10 enterprise pentest engagement. Seven stable…
Docs: djust-audit, error-codes,
`docs/guides/djust-audit.md` documents all five modes of the command (default introspection, `--permissions`, `--dump-permissions`, `--live`, `--ast`), every CLI flag, CI integration examples, and exit-code conventions. Cross-linked from…
Docs: djust-audit, security,
`docs/guides/error-codes.md` now covers the A0xx static audit checks (7 codes: A001, A010, A011, A012, A014, A020, A030), the P0xx permissions-document findings (7 codes: P001–P007), and the L0xx runtime-probe findings (30 codes: L001–L091). Every code gets…
Docs: error-codes,
`FormMixin.as_live_field()` and `WizardMixin.as_live_field()` render form fields with proper CSS classes, `dj-input`/`dj-change` bindings, and framework-aware styling — but only for views backed by a Django `Form` class. This leaves non-form views (modals…
Docs: live-input,
Adds a new mode to `djust_audit` that fetches a running deployment with stdlib `urllib` and validates security headers (HSTS, CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy, COOP, CORP), cookies (HttpOnly, Secure, SameSite…
Docs: djust-audit,
Seven new check IDs fire from `check_configuration` when Django runs `python manage.py check`: **A001** (ERROR) — WebSocket router not wrapped in `AllowedHostsOriginValidator` (static-analysis companion to #653 for existing apps built from older scaffolds).…
Adds a new flag to `djust_audit` that validates every LiveView against a committed, human-readable YAML document describing the expected auth configuration for each view. CI fails on any deviation (view declared public but has auth in code, permission list…
Docs: permissions-document,
General-purpose mixin managing step navigation, per-step validation, and data collection for guided form flows. Provides `next_step`, `prev_step`, `go_to_step`, `update_step_field`, `validate_field`, and `submit_wizard` event handlers. Template context…
Docs: wizards,
djust's inline `<script>` and `<style>` emissions (handler metadata bootstrap in `TemplateMixin._inject_handler_metadata`, `live_session` route map in `routing.get_route_map_script`, and the PWA template tags `djust_sw_register`, `djust_offline_indicator`…
Docs: security,
`LiveViewConsumer` previously attached `timing` (handler/render/total ms) and `performance` (full nested timing tree with handler and phase names) to every VDOM patch response unconditionally, regardless of `settings.DEBUG`. Combined with CSWSH…
`LiveViewConsumer.connect()` previously accepted the WebSocket handshake without validating the `Origin` header, and `DjustMiddlewareStack` did not wrap the router in an origin validator. A cross-origin attacker could mount any LiveView and dispatch any event…
Views with `login_required = True` rendered full HTML to unauthenticated users on the initial HTTP GET. The WebSocket connection was correctly rejected, but the pre-rendered page content was already visible. Now calls `check_view_auth()` before `mount()` on…
The tracker called `sys.getsizeof(str(context))`, which triggered `QuerySet.__repr__()` on any unevaluated querysets in the context dict. `__repr__` calls `list(self[:21])`, evaluating the queryset against the database — raising `SynchronousOnlyOperation` in…
`applyPatches` in `client.js:1379-1440` was filtering `InsertChild` patches out of each parent group and applying them via `DocumentFragment` before iterating the group for the `RemoveChild` patches in that same parent, violating the top-level Remove → Insert…
Boolean `dj-patch` on anchor elements (`<a href="?tab=docs" dj-patch>`) was resolving to the current URL instead of the href destination. Now falls back to `el.getAttribute('href')` when `dj-patch` is empty and the element is `<a>`.…
Django FK fields are class-level descriptors not present in `__dict__`. Rust's `FromPyObject` extracts `__dict__` which has `claimant_id=1` (raw FK int) instead of the related object. Now always calls `normalize_django_value()` on pre-serialized context so FK…
`{{ form.field_name }}` rendered as empty string because the Rust renderer extracted `Form.__dict__` which doesn't contain computed `BoundField` attributes. Now pre-renders Form and BoundField objects to SafeString HTML via `widget.render()` in all four code…
`websocket.py` checked for `"data-dj-id="` but the Rust renderer emits `"dj-id="` attributes. This caused `_stampDjIds()` to be skipped on pre-rendered pages, breaking VDOM patches for large content swaps (e.g. tab switching) while small patches still worked.…
When navigating backward in a multi-step wizard, text input values were not visually restored even though the server sent correct VDOM patches. `setAttribute('value', x)` only updates the HTML attribute (defaultValue), not the `.value` DOM property. Now syncs…
Added UNSAFE_KEYS guard to VDOM SetAttr/RemoveAttr patches (rejects `__proto__`, `constructor`, `prototype` keys), replaced direct property assignment with `Object.defineProperty()` in debug panel state cloning, converted template literal logs to format…
djust's `ASGIStaticFilesHandler` in `djust.asgi.get_application()` already handles static file serving at the ASGI layer, making WhiteNoise middleware redundant. Removed `whitenoise` from dependencies, scaffolded projects, and the demo project. Removed system…
Registered `DjFlashTagHandler` so the flash container renders correctly when templates are processed by the Rust engine. Previously, the tag was only registered as a Django template tag and silently dropped by the Rust renderer.…
Docs: flash-messages,
`djust:navigate-start` / `djust:navigate-end` CustomEvents and `.djust-navigating` CSS class on `[dj-root]` during `dj-navigate` transitions. Enables CSS-only page transitions without monkey-patching `pageLoading`.…
Docs: events,
checks Rust extension, Python/Django versions, Channels, Redis, templates, static files, routing, and ASGI server in one command. Supports `--json`, `--quiet`, `--check NAME`, and `--verbose` flags.
patch failures now include patch type, `dj-id`, parent element info, and suggested causes (third-party DOM modification, `{% if %}` block changes). In `DEBUG_MODE`, a console group with full patch detail is shown. Batch failure summaries include which patch…
Docs: flash-messages,
`send_error` includes `debug_detail` (unsanitized message), `traceback` (last 3 frames), and `hint` (actionable suggestion) when `settings.DEBUG=True`. `handle_mount` lists available LiveView classes when class lookup fails.
Docs: error-overlay,
intercepts `console.warn` calls matching `[LiveView]` prefix and surfaces them as a warning badge on the debug button. Configurable auto-open via `LIVEVIEW_CONFIG.debug_auto_open_on_error`.
Docs: debug-panel,
test loading states and optimistic updates with simulated network delay. Presets (Off/50/100/200/500ms), custom value, jitter control, localStorage persistence, and visual badge on the debug button. Latency is injected on both WebSocket send and receive for…
Docs: debug-panel,
After WebSocket reconnects, form fields with `dj-change` or `dj-input` automatically fire change events to restore server state. Compares DOM values against server-rendered defaults and only fires for fields that differ. Use `dj-no-recover` to opt out…
Exponential backoff with random jitter (AWS full-jitter strategy) prevents thundering herd on server restart. Min delay 500ms, max delay 30s, increased from 5 to 10 max attempts. Attempt count shown in reconnection banner (`dj-reconnecting-banner` CSS class)…
Docs: reconnection,
Update `document.title` and `<meta>` tags from any LiveView handler via property setters (`self.page_title = "..."`, `self.page_meta = {"description": "..."}`). Uses side-channel WebSocket messages (no VDOM diff needed). Supports `og:` and `twitter:` meta…
Selector-based copy (`dj-copy="#code-block"` copies the element's `textContent`), configurable feedback text (`dj-copy-feedback="Done!"`), CSS class feedback (`dj-copy-class` adds a custom class for 2s, default `dj-copied`), and optional server event…
After WebSocket reconnects, elements with `dj-auto-recover="handler_name"` automatically fire a server event with serialized DOM state (form field values and `data-*` attributes from the container). Enables the server to restore custom state lost during…
Apply debounce or throttle to any `dj-*` event attribute (`dj-click`, `dj-change`, `dj-input`, `dj-keydown`, `dj-keyup`) directly in HTML: `<button dj-click="search" dj-debounce="300">`. Takes precedence over `data-debounce`/`data-throttle`. Supports…
`dj-connected` and `dj-disconnected` classes are automatically applied to `<body>` based on WebSocket/SSE transport state. Enables CSS-driven UI feedback for connection status (e.g., dimming content, showing offline banners). Both classes are removed on…
Elements with `dj-cloak` are hidden (`display: none !important`) until the WebSocket/SSE mount response is received, preventing flash of unconnected content. CSS is injected automatically by client.js — no user stylesheet changes needed. Phoenix LiveView's…
NProgress-style thin loading bar at the top of the page during TurboNav and `live_redirect` navigation. Always active by default. Exposed as `window.djust.pageLoading` with `start()`, `finish()`, and `enabled` for manual control. Disable via…
Docs: navigation,
Elements with `dj-scroll-into-view` are automatically scrolled into view after DOM updates (mount, VDOM patch). Supports scroll behavior options: `""` (smooth/nearest, default), `"instant"`, `"center"`, `"start"`, `"end"`. One-shot per DOM node — uses WeakSet…
Docs: events,
Bind event listeners on `window` or `document` while using the declaring element for context extraction (component_id, dj-value-* params). Supports `dj-window-keydown`, `dj-window-keyup`, `dj-window-scroll`, `dj-window-click`, `dj-window-resize`…
Docs: events,
Fire a server event when the user clicks outside an element: `<div dj-click-away="close_dropdown">`. Uses capture-phase document listener so `stopPropagation()` inside the element doesn't prevent detection. Supports `dj-confirm` for confirmation dialogs and…
Bind keyboard shortcuts on any element with modifier key support: `<div dj-shortcut="ctrl+k:open_search:prevent, escape:close_modal">`. Supports `ctrl`, `alt`, `shift`, `meta` modifiers, comma-separated multiple bindings, and `prevent` modifier to suppress…
When multiple form fields share one `dj-change` or `dj-input` handler, the `_target` param now includes the triggering element's `name` (or `id`, or `null`), letting the server know which field changed. For `dj-submit`, includes the submitter button's name if…
Docs: events,
Automatically disable submit buttons during form submission and replace their text with a loading message: `<button type="submit" dj-disable-with="Saving...">Save</button>`. Prevents double-submit and gives instant visual feedback. Works with both `dj-submit`…
Disable an element until its event handler response arrives from the server: `<button dj-click="save" dj-lock>Save</button>`. Prevents rapid double-clicks from triggering duplicate server events. For non-form elements (e.g., `<div>`), applies a `djust-locked`…
Docs: events,
Fire a server event when an element with `dj-mounted="handler_name"` enters the DOM after a VDOM patch: `<div dj-mounted="on_chart_ready" dj-value-chart-type="bar">`. Does not fire on initial page load (only after subsequent patches). Includes `dj-value-*`…
Docs: events,
Server-initiated broadcasts (`server_push`) and async completions (`_run_async_work`) are now tagged with `source="broadcast"` and `source="async"` respectively, and the client buffers them during pending user event round-trips (same as tick buffering from…
Generate a complete CRUD LiveView scaffold from a model name and field definitions: `python manage.py djust_gen_live blog Post title:string body:text`. Creates views.py (with `@event_handler` CRUD operations), urls.py (using `live_session()` routing), HTML…
Docs: scaffolding,
Module-level hooks that run on every LiveView mount, declared via `@on_mount` decorator and `on_mount` class attribute. Use cases: authentication checks, telemetry, tenant resolution, feature flags. Hooks run after auth checks, before `mount()`. Return a…
Docs: on-mount-hooks,
Phoenix `put_flash` parity. Queue transient messages (info, success, warning, error) from any event handler; they are flushed to the client over WebSocket/SSE after each response. Includes `{% dj_flash %}` template tag with auto-dismiss and ARIA…
Docs: flash-messages,
`handle_params(params, uri)` is now invoked after `mount()` on the initial WebSocket connect, not just on subsequent URL changes. This matches Phoenix LiveView's `handle_params/3` contract and eliminates the need to duplicate URL-parsing logic between…
Docs: liveview,
Pass static values alongside events without `data-*` attributes or hidden inputs: `<button dj-click="delete" dj-value-id:int="{{ item.id }}" dj-value-type="soft">`. Supports type-hint suffixes (`:int`, `:float`, `:bool`, `:json`, `:list`), kebab-to-snake_case…
Docs: events,
`get_value()` didn't recognize Python boolean/None literals, so `{% tag show_labels=False %}` produced `show_labels=` (empty string) instead of `show_labels=False`. Now handles `True`/`true`, `False`/`false`, and `None`/`none` as literal values.…
`put_flash()` and `page_title`/`page_meta` side-channel commands were only flushed over WebSocket. HTTP POST responses now drain `_pending_flash` and `_pending_page_metadata` and include them as `_flash` and `_page_metadata` arrays in the JSON response.…
`Value::List` and `Value::Object` in custom tag arguments were stringified via the `Display` trait, destroying structured data before it reached Python handlers. Now serialized as JSON via `serde_json`. ([#589](https://github.com/djust-org/djust/issues/589))
`{% tag key=var|length %}` rendered the literal string instead of the computed value because arg resolution used `context.get()` (plain lookup) instead of `get_value()` (filter-aware). ([#591](https://github.com/djust-org/djust/pull/591))
`is_inside_html_tag()` only checked the immediately preceding token, missing tag context when `{{ variable }}` tokens appeared between the tag opening and `{% if %}`. Added `is_inside_html_tag_at()` that scans all preceding tokens.…
Server-initiated ticks could collide with user events, causing VDOM version divergence that silently discarded patches. Added server-side `asyncio.Lock` to serialize tick and event render operations, priority yielding so ticks skip during user events…
When the server pushed VDOM patches (e.g., updating a counter while the user was typing), the focused input/textarea lost focus, cursor position, selection range, and scroll position. Added `saveFocusState()` / `restoreFocusState()` around the…
Comment node placeholders (`<!--dj-if-->`) emitted by the Rust template engine were excluded from client-side child index resolution (`getSignificantChildren` and `getNodeByPath`), causing path traversal errors and silent patch failures. Also added `#comment`…
`_run_tick` always called `render_with_diff()` even when `handle_tick()` made no state changes, incrementing the VDOM version on every tick. When a user event interleaved with a tick, the client and server versions diverged, causing all subsequent patches to…
All WebSocket LiveViews shared a single RustLiveView cache slot keyed by `/ws/live/`, causing multi-tab sessions to overwrite each other's compiled templates. Cache key now uses `request.path` (the actual page URL) so each view gets its own VDOM baseline.…
`morphElement` removed attributes absent from server HTML, resetting canvas 2D contexts and blanking Chart.js charts. Canvas `width` and `height` are now preserved during attribute sync. ([#561](https://github.com/djust-org/djust/pull/561))
Views that set `_force_full_html = True` in `handle_params` (e.g., when `{% for %}` loop lengths change) still received VDOM patches instead of full HTML. The flag is now checked after `render_with_diff()` in both `handle_event` and `handle_url_change`.…
Select and input elements with `dj-patch` now update via pushState + WebSocket `url_change` instead of full page reload. A delegated `document` change listener survives DOM replacement by morphdom. `dj-patch-reload` attribute remains as an opt-in escape hatch…
Fixed 6 issues blocking production use of `FormMixin` with `ModelForm` over WebSocket: added `@event_handler` to `submit_form()` and `validate_field()`; renamed `form_instance` to private `_form_instance` with backward-compatible property; store…
When VDOM patches failed and djust fell back to full HTML replacement, `updateHooks()` was never called, leaving hook elements stale (charts showing old data, canvases empty). Added `updateHooks()` to all DOM replacement paths: `html_update`, `html_recovery`…
`make version` only updated `pyproject.toml` and `Cargo.toml` but not the hardcoded `__version__` in `__init__.py` files. `djust.__version__` now stays in sync with the package version. ([#547](https://github.com/djust-org/djust/issues/547))
The repeated pattern of `initReactCounters()` + `initTodoItems()` + `bindLiveViewEvents()` + `updateHooks()` across 10+ call sites is now a single function. New DOM replacement paths only need one call. ([#549](https://github.com/djust-org/djust/issues/549))
The 8-line `getComponentId`/`getEmbeddedViewId` pattern appeared 4 times in event binding; now a single helper. ([#551](https://github.com/djust-org/djust/issues/551))
The `liveViewWS && liveViewWS.ws && liveViewWS.ws.readyState === WebSocket.OPEN` pattern appeared across 4 files; now a single predicate. ([#552](https://github.com/djust-org/djust/issues/552))
The `querySelectorAll('.optimistic-pending')` removal loop appeared 4 times across 2 files; now a single function. ([#553](https://github.com/djust-org/djust/issues/553))
Replaced 10+ inline `getattr(settings, "DJUST_CONFIG", {})` try/except blocks across tenants, PWA, and storage modules with a single `get_djust_config()` helper in `config.py`. ([#554](https://github.com/djust-org/djust/issues/554))
The duplicated lazy-init / set / reset pattern in `state_backends/registry.py` and `backends/registry.py` now delegates to a shared `BackendRegistry` class in `utils.py`. ([#555](https://github.com/djust-org/djust/issues/555))
The repeated `isinstance(value, list) and value and isinstance(value[0], models.Model)` check is now a single `is_model_list()` function in `utils.py`, used in `mixins/context.py` and `mixins/request.py`. ([#556](https://github.com/djust-org/djust/issues/556))
`_serialize_model_safely()` previously wrapped `obj.pk` with `str()` when producing the `"id"` key, causing template comparisons like `{% if edit_id == todo.id %}` to fail silently when `edit_id` was an integer. `model.id` now matches `model.pk` and returns…
When the client sends `has_prerendered=true` on WS connect and saved state exists in the session (written during the HTTP GET), the view's attributes are restored from session instead of re-running `mount()`. This eliminates the double page-load cost for…
The CLI called `cleanup_expired(ttl=0)` to force-clear sessions, but the semantics of `ttl=0` changed in 0.3.5 to mean "never expire". The command now calls the explicit `delete_all()` method, which uses a Redis pipeline for an efficient single round-trip…
Between 0.3.2 and 0.3.6rc2, `dj-params` was removed from the client event-binding code. Templates using `dj-params='{"key": value}'` continued to fire click events but the server received `params: {}`. The attribute is now read and merged into the params…
The client-side `_prefetched` Set persisted across `live_redirect` navigations, preventing links on the new view from being prefetched. Added `clear()` to `window.djust._prefetch` and call it in `handleLiveRedirect()` so each SPA navigation starts with a…
When VDOM patch recovery fails because recovery HTML is unavailable (e.g. after server restart), the client now auto-reloads the page instead of showing a confusing error overlay. The server sends `recoverable: false` to signal the client.…
The Rust template lexer used `split_whitespace()` to tokenize tag arguments, which broke quoted values like `name="My App"` into separate tokens (`name="My` and `App"`). This caused the downstream Python handler to receive malformed arguments, silently…
The Rust parser treated `{% load %}` as `Node::Comment`, which `nodes_to_template_string()` discarded during inheritance reconstruction. When the resolved template was re-parsed, custom tags that relied on Django tag libraries (e.g. `{% djust_pwa_head %}`)…
`SetAttr` and `RemoveAttr` patches only called `setAttribute`/`removeAttribute`, which updates the HTML attribute but not the DOM property. After user interaction the browser separates the two, so server-driven state changes via `dj-click` had no visible…
`cleanup_expired()` methods in both `InMemoryStateBackend` and `RedisStateBackend` now treat `TTL ≤ 0` as "never expire". Previously `SESSION_TTL=0` caused `cutoff = time.time() - 0`, making all sessions appear expired, deleting them immediately, and leaving…
Replaced `hasattr(scope_session, "session_key")` with `getattr(scope_session, "session_key", None)` in the consumer's request context builder. `hasattr()` on a Django Channels `LazyObject` can raise non-`AttributeError` exceptions during lazy evaluation…
Use individual `data-*` attributes with optional type-coercion suffixes instead. `dj-params` will be removed in a future release.
new `python/djust/deploy_cli.py` module providing deployment commands for [djustlive.com](https://djustlive.com). Available via the `djust-deploy` entry point after installation. ([#437](https://github.com/djust-org/djust/pull/437))
Docs: djust-deploy,
`DjustStreamOp` now includes `"done"` and `"start"` operation types and an optional `mode` field (`"append" | "replace" | "prepend"`). `getActiveStreams()` return type changed from `Map` to `Record`. See `docs/website/guides/typecheck.md`.
Docs: typecheck,
Added to demo project's `utilities.css` for laying out flex children horizontally with space-between. Use on card headers or any flex container that needs a title on the left and action widget on the right.…
Docs: css-frameworks,
New "Size Breakdown" table in State tab shows per-variable memory and serialized byte sizes with human-readable formatting (B/KB/MB). Added `_debug_state_sizes()` method to `PostProcessingMixin` included in both mount and event debug payloads.…
Event, patch, network, and state history now persist across TurboNav navigation via sessionStorage (30s window). Panel state restores on next page if navigated within 30 seconds. ([#459](https://github.com/djust-org/djust/pull/459)) See…
Docs: debug-panel,
Comprehensive guide covering setup, navigation lifecycle, inline script handling, known caveats, and design decisions: `docs/guides/turbonav-integration.md`. ([#459](https://github.com/djust-org/djust/pull/459))
Docs: turbonav-integration,
The search bar in the debug panel now filters across all data tabs. The Network tab shows a `N / total` count label when a query narrows the message list (#530). The State tab filters history entries by trigger, event name, and serialized state content, with…
Docs: debug-panel,
When the client sends `has_prerendered=true` on WS connect and saved state exists in the session (written during the HTTP GET), the view's attributes are restored from session instead of re-running `mount()`. This eliminates the double page-load cost for…
`_serialize_model_safely()` previously wrapped `obj.pk` with `str()` when producing the `"id"` key, causing template comparisons like `{% if edit_id == todo.id %}` to fail silently when `edit_id` was an integer. `model.id` now matches `model.pk` and returns…
The CLI called `cleanup_expired(ttl=0)` to force-clear sessions, but the semantics of `ttl=0` changed in 0.3.5 to mean "never expire". The command now calls the explicit `delete_all()` method, which uses a Redis pipeline for an efficient single round-trip…
Between 0.3.2 and 0.3.6rc2, `dj-params` was removed from the client event-binding code. Templates using `dj-params='{"key": value}'` continued to fire click events but the server received `params: {}`. The attribute is now read and merged into the params…
The client-side `_prefetched` Set persisted across `live_redirect` navigations, preventing links on the new view from being prefetched. Added `clear()` to `window.djust._prefetch` and call it in `handleLiveRedirect()` so each SPA navigation starts with a…
When VDOM patch recovery fails because recovery HTML is unavailable (e.g. after server restart), the client now auto-reloads the page instead of showing a confusing error overlay. The server sends `recoverable: false` to signal the client.…
The Rust template lexer used `split_whitespace()` to tokenize tag arguments, which broke quoted values like `name="My App"` into separate tokens (`name="My` and `App"`). This caused the downstream Python handler to receive malformed arguments, silently…
The Rust parser treated `{% load %}` as `Node::Comment`, which `nodes_to_template_string()` discarded during inheritance reconstruction. When the resolved template was re-parsed, custom tags that relied on Django tag libraries (e.g. `{% djust_pwa_head %}`)…
`SetAttr` and `RemoveAttr` patches only called `setAttribute`/`removeAttribute`, which updates the HTML attribute but not the DOM property. After user interaction the browser separates the two, so server-driven state changes via `dj-click` had no visible…
`cleanup_expired()` methods in both `InMemoryStateBackend` and `RedisStateBackend` now treat `TTL ≤ 0` as "never expire". Previously `SESSION_TTL=0` caused `cutoff = time.time() - 0`, making all sessions appear expired, deleting them immediately, and leaving…
Replaced `hasattr(scope_session, "session_key")` with `getattr(scope_session, "session_key", None)` in the consumer's request context builder. `hasattr()` on a Django Channels `LazyObject` can raise non-`AttributeError` exceptions during lazy evaluation…
Use individual `data-*` attributes with optional type-coercion suffixes instead. `dj-params` will be removed in a future release.
new `python/djust/deploy_cli.py` module providing deployment commands for [djustlive.com](https://djustlive.com). Available via the `djust-deploy` entry point after installation. ([#437](https://github.com/djust-org/djust/pull/437))
Docs: djust-deploy,
`DjustStreamOp` now includes `"done"` and `"start"` operation types and an optional `mode` field (`"append" | "replace" | "prepend"`). `getActiveStreams()` return type changed from `Map` to `Record`. See `docs/website/guides/typecheck.md`.
Docs: typecheck,
Added to demo project's `utilities.css` for laying out flex children horizontally with space-between. Use on card headers or any flex container that needs a title on the left and action widget on the right.…
Docs: css-frameworks,
New "Size Breakdown" table in State tab shows per-variable memory and serialized byte sizes with human-readable formatting (B/KB/MB). Added `_debug_state_sizes()` method to `PostProcessingMixin` included in both mount and event debug payloads.…
Event, patch, network, and state history now persist across TurboNav navigation via sessionStorage (30s window). Panel state restores on next page if navigated within 30 seconds. ([#459](https://github.com/djust-org/djust/pull/459)) See…
Docs: debug-panel,
Comprehensive guide covering setup, navigation lifecycle, inline script handling, known caveats, and design decisions: `docs/guides/turbonav-integration.md`. ([#459](https://github.com/djust-org/djust/pull/459))
Docs: turbonav-integration,
The search bar in the debug panel now filters across all data tabs. The Network tab shows a `N / total` count label when a query narrows the message list (#530). The State tab filters history entries by trigger, event name, and serialized state content, with…
Docs: debug-panel,
new `python/djust/deploy_cli.py` module providing deployment commands for [djustlive.com](https://djustlive.com). Install with `pip install djust[deploy]`. Available via the `djust-deploy` entry point:
Docs: djust-deploy,
`updateHooks()` is called after `live_redirect_mount` replaces DOM content via WebSocket and SSE mount handlers. Previously, hook lifecycle callbacks (`mounted()`, `destroyed()`) were skipped after client-side navigation, leaving hook-dependent elements…
Previously, `handle_exception()` only logged the exception class name (e.g. `ValueError`) when `DEBUG=False`, hiding the error message and stack trace. Now logs type, message, and traceback at `ERROR` level regardless of `DEBUG` mode. Client responses remain…
When an `@event_handler` runs successfully but produces no DOM changes (e.g. toggle clicked in target state, debounced input with unchanged results, side-effect-only handlers), the empty diff is now silently dropped at `DEBUG` level rather than logged as a…
`InsertChild` and `RemoveChild` patches now include `ref_d` and `child_d` fields for ID-based DOM resolution, preventing stale-index mis-targeting when `{% if %}` blocks add or remove elements that shift sibling positions. Falls back to index-based resolution…
`.pyi` stubs for `live_redirect`, `live_patch`, `push_event`, `stream`, and related methods so mypy/pyright catch typos at lint time. ([#390](https://github.com/djust-org/djust/pull/390)) See `docs/website/guides/typecheck.md`.
Docs: typecheck,
Documents when to use `dj-navigate` vs `live_redirect` vs `live_patch`. ([#390](https://github.com/djust-org/djust/pull/390))
Django testing best practices and pytest setup for djust applications. ([#390](https://github.com/djust-org/djust/pull/390)) See `docs/website/api-reference/testing.md`.
Docs: testing,
New `docs/system-checks.md` covering all 37 check IDs (C/V/S/T/Q) with severity, detection method, suppression patterns, and known false positives. ([#398](https://github.com/djust-org/djust/pull/398))
Docs: system-checks,
`components/base.py` now uses `format_html()` to avoid XSS risk in component rendering. ([#390](https://github.com/djust-org/djust/pull/390))
`render_template()` previously returned `f"<div>Error: {e}</div>"` unconditionally, leaking internal Rust template engine details. Now returns a generic message in production; error details are only shown when `settings.DEBUG = True`.…
Replaced `innerHTML` assignment with a sandboxed iframe for user-editable preview content. ([#384](https://github.com/djust-org/djust/pull/384))
Added safeguards against prototype pollution in client-side JS. ([#384](https://github.com/djust-org/djust/pull/384))
Conditional attribute fragments were causing off-by-one errors in VDOM diffing. ([#390](https://github.com/djust-org/djust/pull/390))
`TenantAwareRedisBackend`, `TenantAwareMemoryBackend`, and several example components were missing `super().__init__()` calls, causing MRO issues. ([#386](https://github.com/djust-org/djust/pull/386))
CodeQL alert resolved. ([#387](https://github.com/djust-org/djust/pull/387))
`no_template_demo.py` override now correctly accepts `serialized_context`. ([#387](https://github.com/djust-org/djust/pull/387))
`handle_params()`, `handle_disconnect()`, `handle_connect()`, and `handle_event()` no longer incorrectly trigger the V004 system check. ([#398](https://github.com/djust-org/djust/pull/398))
`dj-view="{{ view_path }}"` (Django template variable injection) is now correctly recognised as valid by T013. ([#398](https://github.com/djust-org/djust/pull/398))
Functions with primitive return-type annotations (e.g. `-> str`, `-> int`) no longer trigger V008 when their result is assigned in `mount()`. ([#398](https://github.com/djust-org/djust/pull/398))
`test_checks.py` and `double_bind.test.js` no longer fail when run as part of the full suite. ([#390](https://github.com/djust-org/djust/pull/390))
`{% widthratio %}`, `{% firstof %}`, `{% templatetag %}`, `{% spaceless %}`, `{% cycle %}`, `{% now %}`. ([#329](https://github.com/djust-org/djust/issues/329)) See `docs/website/guides/template-cheatsheet.md`.
Docs: template-cheatsheet,
Warns at startup for unsupported Rust template tags, missing `dj-view`, and invalid `dj-view` paths. ([#293](https://github.com/djust-org/djust/issues/293), [#329](https://github.com/djust-org/djust/issues/329))
Railway, Render, and Fly.io. ([#247](https://github.com/djust-org/djust/issues/247))
([#304](https://github.com/djust-org/djust/issues/304), [#316](https://github.com/djust-org/djust/issues/316))
Produced malformed HTML (e.g. `class="btn <!--dj-if-->"`). Empty string is emitted instead; text-node VDOM anchor is unaffected. ([#381](https://github.com/djust-org/djust/pull/381))
All elif nodes in a chain now inherit the outer `{% if %}`'s attribute context. ([#383](https://github.com/djust-org/djust/pull/383))
([#365](https://github.com/djust-org/djust/issues/365))
([#366](https://github.com/djust-org/djust/issues/366))
([#367](https://github.com/djust-org/djust/issues/367))
([#307](https://github.com/djust-org/djust/issues/307))
`handleNavigation` dispatch now fires correctly. ([#307](https://github.com/djust-org/djust/issues/307))
`{% include %}` check now examines the include path, not whole-file content. ([#331](https://github.com/djust-org/djust/issues/331))
When `{% if a %}...{% elif b %}...{% endif %}` appears inside an attribute value and all conditions are false, the elif node previously emitted `<!--dj-if-->` (malformed HTML). Fixed by threading `in_tag_context` as a parameter into `parse_if_block()` so elif…
`_extract_liveview_root_with_wrapper` and the other extraction methods treated both branches of a `{% if/else %}` block as independent div opens, causing depth to never reach 0 when both branches opened a div sharing a single closing `</div>`. This caused the…
For inherited templates, `get_template()` extracted the VDOM root from the fully-resolved document (base HTML + inlined blocks), which contains surrounding HTML that the depth counter could trip over. Now prefers the child template source when it contains…
`_hookExistingWebSocket` called native WebSocket getter/setter functions via `Function.prototype.call()` from external code, which fails V8's brand check on IDL-generated bindings. Fixed by using normal property access (`ws.onmessage`) and assignment…
Implemented `{% widthratio %}`, `{% firstof %}`, `{% templatetag %}`, `{% spaceless %}`, `{% cycle %}`, and `{% now %}` in the Rust template engine. These tags were previously rendered as HTML comments with warnings.…
Docs: template-cheatsheet,
Warns at startup when templates use Django tags not yet implemented in the Rust renderer (`ifchanged`, `regroup`, `resetcycle`, `lorem`, `debug`, `filter`, `autoescape`). Suppressible with `{# noqa: T011 #}`.…
Detects templates that use `dj-*` event directives without a `dj-view` attribute, which would silently fail at runtime. ([#293](https://github.com/djust-org/djust/issues/293)) See `docs/website/getting-started/first-liveview.md`.
Docs: first-liveview,
Detects empty or malformed `dj-view` attribute values. ([#293](https://github.com/djust-org/djust/issues/293)) See `docs/website/getting-started/first-liveview.md`.
Docs: first-liveview,
Including `S` (ordinal suffix), `t` (days in month), `w`/`W` (weekday/week number), `L` (leap year), `c` (ISO 8601), `r` (RFC 2822), `U` (Unix timestamp), and Django's special `P` format (noon/midnight).
Added deployment documentation for Railway, Render, and Fly.io. ([#247](https://github.com/djust-org/djust/issues/247))
Documented `dj-patch` vs `dj-click` for client-side navigation, with `handle_params()` patterns. ([#304](https://github.com/djust-org/djust/issues/304)) See `docs/guides/BEST_PRACTICES.md`.
Docs: BEST_PRACTICES,
Documented root container requirement and `**kwargs` convention for event handlers. ([#316](https://github.com/djust-org/djust/issues/316))
When a `{% if %}` block with no else branch evaluates to false inside an HTML attribute value (e.g. `class="btn {% if active %}active{% endif %}"`), the Rust renderer now emits an empty string instead of the `<!--dj-if-->` VDOM placeholder. The placeholder is…
Gives the VDOM diffing engine a stable DOM anchor to target when the condition later becomes true, resolving DJE-053 / issue #295.
Removed the `url.pathname !== '/'` guard in `bindNavigationDirectives` that prevented the browser URL from being updated when patching to `/`. The guard was silently ignoring root-path patches. ([#307](https://github.com/djust-org/djust/issues/307))
Fixed dict merge order in `_flush_navigation` so `type: 'navigation'` is no longer overwritten by `**cmd`. Added an `action` field to carry the nav sub-type (`live_patch` / `live_redirect`); `handleNavigation` now dispatches on `data.action` instead of…
The `{% include %}` check now examines the include path instead of the whole file content, preventing false warnings on templates that include SVGs or modals alongside `dj-*` directives. ([#331](https://github.com/djust-org/djust/issues/331))
Comprehensive ambient TypeScript declaration file shipped with the Python package at `static/djust/djust.d.ts`. Covers: `window.djust` namespace, `LiveViewWebSocket` and `LiveViewSSE` transport classes, `DjustHook` lifecycle interface (`mounted`…
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…
djust now automatically falls back to SSE when WebSocket is unavailable (corporate proxies, enterprise firewalls). Architecture: `EventSource` for server→client push, HTTP POST for client→server events. Transport negotiation is automatic: WebSocket is tried…
Docs: sse-transport,
Added PEP 561 compliant type stubs for `NavigationMixin`, `PushEventMixin`, `StreamsMixin`, `StreamingMixin`, and `LiveView` to enable IDE autocomplete and mypy type checking for runtime-injected methods like `live_redirect`, `live_patch`, `push_event`…
New decorator that automatically runs the entire event handler in a background thread via `start_async()`. Simplifies syntax for long-running operations (AI generation, API calls, file processing) without needing explicit callback splitting. Can be combined…
WebSocket responses include `async_pending` flag when a `start_async()` callback is running, preventing loading spinners from disappearing prematurely. Async completion responses include `event_name` so the client clears the correct loading state. Supports…
Docs: loading-states,
Scope any `dj-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](https://github.com/djust-org/djust/pull/314))
`start_async()` is now available on all LiveViews without explicit mixin import. ([#314](https://github.com/djust-org/djust/pull/314)) See `docs/website/guides/loading-states.md`.
Docs: loading-states,
`scanAndRegister()` is called after every `bindLiveViewEvents()` so dynamically rendered elements (e.g., inside modals) get loading state registration. Stale entries for disconnected elements are cleaned up automatically.…
Docs: loading-states,
Detects elements using `dj-click` with navigation-related data attributes (`data-view`, `data-tab`, `data-page`, `data-section`). This pattern should use `dj-patch` instead for proper URL updates, browser history support, and bookmarkable views. Warning…
Heuristic INFO-level check that detects `@event_handler` methods setting navigation state variables (`self.active_view`, `self.current_tab`, etc.) without using `patch()` or `handle_params()`. Suggests converting to `dj-patch` pattern for URL updates and…
Added `.pyi` type stub files for `_rust` module and `LiveView` class, enabling IDE autocomplete, mypy/pyright type checking, and catching typos like `live_navigate` (should be `live_patch`) at lint time. Includes `py.typed` marker for PEP 561 compliance and…
Docs: TYPE_STUBS,
The `data.action || data.type` fallback for pre-#307 clients (added for backwards compatibility in [#318](https://github.com/djust-org/djust/pull/318)) will be removed in the next minor release. Server now sends `data.action` on all navigation messages.…
Non-serializable objects stored in `self.*` during `mount()` (e.g., service instances, API clients) were silently converted to strings, causing confusing `AttributeError` on subsequent requests far from the root cause. `normalize_django_value()` now logs a…
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 with `login_required = False` were…
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.…
When `{% if %}` blocks evaluated to false and removed elements, siblings shifted left, causing `diff_indexed_children()` to incorrectly match unrelated nodes and generate wrong patches. The template engine now emits `<!--dj-if-->` placeholder comments when…
Single user actions were triggering the same event multiple times (e.g. `select_project` 5×, `mount` 3×) because listeners accumulated across VDOM patch/morph cycles without cleanup. Fixed four root causes: (1) `initReactCounters` now uses a `WeakSet` guard…
Removed `url.pathname !== '/'` guard in `bindNavigationDirectives` so root-path navigation works. Fixed dict merge order in `_flush_navigation` so server sends `type='navigation'` instead of `type='live_patch'`. Updated `handleNavigation` to dispatch via…
All `console.log` calls across 12 files in `static/djust/src/` (excluding the intentional debug panel in `src/debug/`) are now wrapped with `if (globalThis.djustDebug)`. Bare logging in production code leaks internal state to browser consoles and violates the…
`createNodeFromVNode` now correctly collects `FormData` for submit events; replaced `data-liveview-*-bound` attribute tracking with `WeakMap` to prevent stale binding flags after DOM replacement ([#312](https://github.com/djust-org/djust/pull/312))
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…
Added comprehensive tests verifying that `|safe` filter works correctly for HTML content in nested dict/list values, preventing issue [#317](https://github.com/djust-org/djust/issues/317) from recurring
Client-only properties (`_targetElement`, `_optimisticUpdateId`, `_skipLoading`, `_djTargetSelector`) are now stripped from event params before serialization. Previously, `HTMLFormElement` references in params corrupted the JSON payload, overwriting form…
All three framework adapters (Bootstrap 5, Tailwind, Plain) rendered `@change="validate_field"` instead of `dj-change="validate_field"`, causing real-time field validation to silently fail. ([#310](https://github.com/djust-org/djust/pull/310))
`_get_field_type()` checked `CharField` before `EmailField` (which inherits from `CharField`), so email fields never got `type="email"`. Reordered the isinstance checks. ([#310](https://github.com/djust-org/djust/pull/310))
Removed `render_field()`, `_render_field_widget()`, and `_attrs_to_string()` from `FormMixin`. These methods used f-strings with no escaping to build HTML, allowing stored XSS via form field values. Use `as_live()` / `as_live_field()` (which delegate to…
`_render_input()` passed raw textarea values to `_build_tag()` content without `escape()`. Added `escape(str(value))` for textarea content. ([#310](https://github.com/djust-org/djust/pull/310))
Created `BaseAdapter` with all shared rendering logic. `Bootstrap5Adapter`, `TailwindAdapter`, and `PlainAdapter` reduced from ~200 lines each to ~10 lines of class attributes. `frameworks.py` reduced from ~657 to ~349 lines.…
`FormMixin.mount()` now reads field values from `_model_instance` if set and the form is a `ModelForm`. `_create_form()` passes `instance=` to the form constructor. ([#310](https://github.com/djust-org/djust/pull/310))
Emits `DeprecationWarning` on subclass. Adds no functionality over `django.forms.Form`. Will be removed in 0.4. ([#310](https://github.com/djust-org/djust/pull/310))
Insecure (XSS via f-strings) and duplicated adapter logic. Use `as_live_field()` instead. ([#310](https://github.com/djust-org/djust/pull/310))
Dead code, never called. Removed from `forms.py` and `__all__`. ([#310](https://github.com/djust-org/djust/pull/310))
Optimized `get_context_data()` by replacing `dir(self)` iteration (~300 inherited Django View attributes, ~50ms) with targeted `__dict__` + MRO walk (<1ms). Added `dj-update="ignore"` optimization to Rust VDOM diff engine, skipping subtrees the client won't…
Declarative confirmation dialogs for event handlers. Add `dj-confirm="Are you sure?"` to any `dj-click` element to show a browser confirmation dialog before dispatching the event. ([#302](https://github.com/djust-org/djust/pull/302))
Comprehensive Tailwind CSS integration with three-part system: (1) System checks (`djust.C010`, `djust.C011`, `djust.C012`) automatically warn about Tailwind CDN in production, missing compiled CSS, and manual `client.js` loading. (2) Graceful fallback…
Docs: css-frameworks,
All template extraction methods (`_extract_liveview_content`, `_extract_liveview_root_with_wrapper`, `_extract_liveview_template_content`, `_strip_liveview_root_in_html`) now fall back to `[dj-view]` when `[dj-root]` is not present, matching the client-side…
Fixed `autoMount()` to use `getAttribute('dj-view')` instead of `container.dataset.djView`. The `dataset` API reads `data-*` attributes, but `dj-view` is not a data attribute, causing the attribute to be missed.…
Since `dj-root` is now optional and auto-inferred from `dj-view` (per PR #297), the T002 check is now informational rather than a warning. The message now clarifies that auto-inference is working correctly.…
djust now automatically detects and warns (via `djust.C012` system check) when base or layout templates manually include `<script src="{% static 'djust/client.js' %}">`. Since the framework auto-injects `client.js`, manual loading causes double-initialization…
New `djust.C010` system check warns when Tailwind CDN (`cdn.tailwindcss.com`) is detected in production templates (`DEBUG=False`). Provides actionable guidance to compile CSS with `djust_setup_css` command or Tailwind CLI. Prevents slow CDN performance and…
Comprehensive security infrastructure to prevent vulnerabilities like the mount handler RCE (Issue #298) from reaching production. Includes 259 new security tests (Python + Rust) covering parameter injection, file upload attacks, URL injection, and XSS…
Phoenix-style render optimization. The framework automatically detects which context values changed between renders and only sends those to Rust's `update_state()`. Replaces the manual `static_assigns` API. Two-layer detection: snapshot comparison for…
Replaced by automatic change tracking. The framework now detects unchanged values automatically — no manual annotation needed.
The Rust template engine now supports the complete set of Django built-in filters. Added 24 filters across two batches: `default_if_none`, `wordcount`, `wordwrap`, `striptags`, `addslashes`, `ljust`, `rjust`, `center`, `make_list`, `json_script`…
Docs: template-cheatsheet,
Opinionated, framework-enforced auth for LiveViews. View-level `login_required` and `permission_required` class attributes (plus `LoginRequiredMixin`/`PermissionRequiredMixin` for Django-familiar patterns). Custom auth logic via `check_permissions()` hook.…
`live_patch()` updates URL query params without remount, `live_redirect()` navigates to a different view over the same WebSocket. Includes `handle_params()` callback, `live_session()` URL routing helper, and client-side `dj-patch`/`dj-navigate` directives…
Real-time user presence with `PresenceMixin` and `PresenceManager`. Pluggable backends (in-memory and Redis). Includes `LiveCursorMixin` and `CursorTracker` for collaborative live cursor features. ([#236](https://github.com/djust-org/djust/pull/236)) See…
Docs: presence,
`StreamingMixin` for real-time partial DOM updates (e.g., LLM token-by-token streaming). Provides `stream_to()`, `stream_insert()`, `stream_text()`, `stream_error()`, `stream_start()`/`stream_done()`, and `push_state()`. Batched at ~60fps to prevent flooding.…
Docs: streaming-markdown,
`UploadMixin` with binary WebSocket frame protocol for chunked file uploads. Includes progress tracking, magic bytes validation, file size/extension/MIME checking, and client-side `dj-upload`/`dj-upload-drop` directives.…
Docs: uploads,
`dj-hook` attribute for client-side JavaScript lifecycle hooks (mounted, updated, destroyed, disconnected, reconnected). ([#236](https://github.com/djust-org/djust/pull/236))
`dj-model` two-way data binding with `.lazy` and `.debounce-N` modifiers. Server-side `ModelBindingMixin` with security field blocklist and type coercion. ([#236](https://github.com/djust-org/djust/pull/236)) See `docs/website/guides/model-binding.md`.
Docs: model-binding,
`dj-confirm` confirmation dialogs, `dj-target` scoped updates, embedded view routing in event handlers. ([#236](https://github.com/djust-org/djust/pull/236))
Background tasks (Celery, management commands, cron jobs) can now push state updates to connected LiveView clients via `push_to_view()`. Includes per-view channel groups (auto-joined on mount), a sync/async public API (`push_to_view` / `apush_to_view`), and…
Complete offline-first PWA implementation with service worker integration, IndexedDB/LocalStorage abstraction, optimistic UI updates, and offline-aware template directives. Includes comprehensive template tags (`{% djust_pwa_head %}`, `{% djust_pwa_manifest…
Docs: pwa,
Production-ready multi-tenant architecture with flexible tenant resolution strategies (subdomain, path, header, session, custom, chained), automatic data isolation, tenant-aware state backends, and comprehensive template context injection. Includes…
Declarative polling for LiveView elements. Add `dj-poll="handler_name"` to any element to trigger the handler at regular intervals. Configurable via `dj-poll-interval` (default: 5000ms). Automatically pauses when the page is hidden and resumes on visibility…
New ASGI middleware for apps that don't use `django.contrib.auth`. Wraps WebSocket routes with session middleware only (no auth required). Updated `C005` system check to recognize both `AuthMiddlewareStack` and `DjustMiddlewareStack`.…
Warns when `daphne` is in `INSTALLED_APPS` but `whitenoise` middleware is missing. ([#259](https://github.com/djust-org/djust/issues/259))
`python -m djust new myapp` creates a full project with optional features (`--with-auth`, `--with-db`, `--with-presence`, `--with-streaming`, `--from-schema`). Legacy `startproject` and `startapp` commands also available.…
Automates MCP server setup for Claude Code, Cursor, and Windsurf. Tries `claude mcp add` first (canonical for Claude Code), falls back to writing `.mcp.json` directly. Merges with existing config, backs up malformed files, idempotent. See…
Docs: mcp-server,
`dj-view` is now the only required attribute on LiveView container elements. The client auto-stamps `dj-root` and `dj-liveview-root` at init time. Old three-attribute format still works. ([#258](https://github.com/djust-org/djust/issues/258))
`{{ model.pk }}` now works in Rust-rendered templates. Model serialization includes a `pk` key with the native primary key value. ([#262](https://github.com/djust-org/djust/issues/262)) See `docs/website/guides/template-cheatsheet.md`.
Docs: template-cheatsheet,
Improved error messages for common LiveView event handler mistakes (missing `@event_handler`, wrong method signature). ([#248](https://github.com/djust-org/djust/issues/248)) See `docs/website/guides/flash-messages.md`.
Docs: flash-messages,
Automated smoke and fuzz testing for LiveView classes. ([#251](https://github.com/djust-org/djust/pull/251))
`python manage.py djust_mcp` starts a Model Context Protocol server for AI assistant integration. Provides framework introspection, system checks, scaffolding, and validation tools. Used by `djust mcp install` to configure Claude Code, Cursor, and Windsurf.…
Docs: mcp-server,
Security audit showing auth posture, exposed state, and handler signatures per view.
Django system checks for project validation. Gains `--fix` flag for safe auto-fixes and `--format json` for enhanced output with fix hints.
Extract and generate Django models from JSON schema files. See `docs/guides/djust-audit.md`.
Docs: djust-audit,
Generate AI-focused context files for LLM integrations. See `docs/guides/djust-audit.md`.
Docs: djust-audit,
`docs/ai/` with focused guides for events, forms, JIT, lifecycle, security, and templates. `docs/llms.txt` and `docs/llms-full.txt` for LLM context.
Pre-commit hook runs `build-client.sh` when `src/` files change. ([#211](https://github.com/djust-org/djust/issues/211))
New proptest generator produces tree B by mutating tree A, exercising keyed diff paths more effectively. Proptest cases bumped from 500 to 1000. ([#216](https://github.com/djust-org/djust/issues/216), [#217](https://github.com/djust-org/djust/issues/217))
Client-side `extractTypedParams()` now strips the `dj_` prefix from `data-dj-*` attributes. `data-dj-preset="dark"` sends `{preset: "dark"}` instead of `{dj_preset: "dark"}`. Update handler parameter names accordingly: `dj_foo` → `foo`.
Enhanced with tenant-aware isolation support (`TenantAwareRedisBackend`, `TenantAwareMemoryBackend`).
Event handler processing now uses 2 thread hops instead of 4, saving ~1-4ms per event. ([#277](https://github.com/djust-org/djust/issues/277))
Direct `normalize_django_value()` Python-to-Python type normalization replaces 17 `json.loads(json.dumps(...))` patterns. Saves 2-5ms per event for views with database objects. ([#279](https://github.com/djust-org/djust/issues/279))
Rust `extract_template_variables()` results cached by content hash (SHA-256). Size-capped at 256 entries with automatic eviction. ([#280](https://github.com/djust-org/djust/issues/280))
`resolve_context_processors()` results cached per settings configuration. Invalidated on `setting_changed` signal. ([#281](https://github.com/djust-org/djust/issues/281))
Views without QuerySets or Models in context skip the entire JIT serialization pipeline. Saves ~0.5ms per event for simple views. ([#278](https://github.com/djust-org/djust/issues/278))
Event responses send only state variables; handler metadata moved to initial mount as static data. ~68% smaller debug payloads (~25KB → ~8KB per event).
`dj-change`, `dj-input`, `dj-blur`, `dj-focus` now parse inline arguments (e.g., `dj-change="toggle(3)"`) before sending to server. Also fixed state change detection to use deep copy comparison, catching in-place mutations.
Suppress "WebSocket Connection Failed" overlay during TurboNav navigation via `_intentionalDisconnect` flag.
When VDOM patches fail, the client requests recovery HTML on demand instead of reloading the page. Uses DOM morphing to preserve event listeners and form state. ([#259](https://github.com/djust-org/djust/issues/259))
`post()` now accepts the HTTP fallback format where the event name is in the `X-Djust-Event` header and params are flat in the body JSON. ([#255](https://github.com/djust-org/djust/issues/255))
POST responses include `_debug` payload when `DEBUG=True`, enabling the debug panel in HTTP-only mode. ([#267](https://github.com/djust-org/djust/issues/267))
Client JS now shows helpful `console.error` when no LiveView containers are found. Added system check `V005` for modules not in `LIVEVIEW_ALLOWED_MODULES`. ([#257](https://github.com/djust-org/djust/issues/257))
`get()` now saves view state to the session immediately when `use_websocket: False`. ([#264](https://github.com/djust-org/djust/issues/264))
Setting now actually prevents WebSocket connections. ([#260](https://github.com/djust-org/djust/issues/260))
`html_update` now uses morphdom-style DOM diffing instead of `innerHTML`. ([#236](https://github.com/djust-org/djust/pull/236))
Template whitespace stripping no longer collapses newlines inside `<textarea>` elements. ([#236](https://github.com/djust-org/djust/pull/236))
`track_presence()` now checks for `request.user` before accessing it. ([#236](https://github.com/djust-org/djust/pull/236))
`server_push()` now checks `_skip_render`, preventing phantom renders and VDOM version mismatches. ([#236](https://github.com/djust-org/djust/pull/236))
MoveChild patches now include `child_d` for `data-dj-id` resolution. ([#225](https://github.com/djust-org/djust/issues/225))
Patches now processed level-by-level (shallowest parent first). ([#212](https://github.com/djust-org/djust/issues/212))
Resolves parent nodes by `djust_id` instead of path-based traversal. ([#216](https://github.com/djust-org/djust/issues/216))
Emits `MoveChild` patches for unkeyed element children in keyed contexts. ([#219](https://github.com/djust-org/djust/issues/219))
`SetText` patches carry `djust_id` when available; `sync_ids` propagates IDs to text nodes. ([#221](https://github.com/djust-org/djust/issues/221))
`clear_tag_handlers()` now restores built-in handlers in teardown. ([#261](https://github.com/djust-org/djust/issues/261))
`post()` now enforces the same security model as the WebSocket path: only `@event_handler`-decorated methods can be invoked. Validates event names with `is_safe_event_name()` to block dunders and private methods.
`SafeString` values propagated to Rust for proper auto-escaping.
Both filters now escape their output to prevent XSS. ([#254](https://github.com/djust-org/djust/issues/254))
All PWA template tags now use `format_html()` and `escape()` instead of `mark_safe()` with f-string interpolation.
Removed `@csrf_exempt` from `sync_endpoint_view`. Added authentication requirement, payload validation, and safe field extraction.
All `except: pass` patterns replaced with appropriate logging calls.
All `console.log` calls guarded behind `djustDebug` flag.
The backwards-compatibility escape hatch that allowed undecorated methods to be called via WebSocket or HTTP POST has been removed. All event handlers must now use the `@event_handler` decorator.
After deleting a todo, the remaining button's click handler sent the wrong `_args` (stale closure from bind time) because `SetAttribute` patches updated the `dj-click` DOM attribute but not the listener closure. Event listeners now re-parse `dj-*` attributes…
Rust parser stripped ` `-only text nodes (used in syntax highlighting) because `char::is_whitespace()` includes U+00A0. Now preserves `\u00A0` text nodes in parser, `to_html()`, and client-side path traversal. Also adds `sync_ids()` to prevent ID drift…
Pages without a `<form>` element failed to send CSRF tokens with WebSocket events. Token lookup now falls back to the `csrftoken` cookie. ([#210](https://github.com/djust-org/djust/pull/210))
Template expressions like `{{ posts.0.url }}` produced paths starting with a numeric index (`0.url`), generating invalid Python (`obj.0`). Codegen now skips numeric-leading paths since list items are serialized individually.
Fixed multiple issues in JIT auto-serialization: ([#140](https://github.com/djust-org/djust/pull/140))
`template_dirs` was not included in msgpack serialization of `RustLiveView`. After a cache hit, the restored view had empty search paths, causing `{% include %}` tags to fail with "Template not found". Now calls `set_template_dirs()` on both WebSocket and…
Fixed `data-djust-replace` inserting children into wrong parent when the replace container has siblings. `groupPatchesByParent()` now uses the full path for child-operation patches, and `groupConsecutiveInserts()` checks parent identity before batching.…
Fixed `data-djust-replace` not removing old children before inserting new ones, causing duplicate content on re-render. ([#142](https://github.com/djust-org/djust/pull/142), [#143](https://github.com/djust-org/djust/pull/143))
View context now takes precedence over context processors. Previously, context processors could overwrite view-defined variables (e.g., Django's messages processor overwriting a view's `messages` variable).
Fixed `apply_patches` for keyed diff insert ordering where items were inserted in the wrong position. ([#154](https://github.com/djust-org/djust/pull/154))
Fixed `MoveChild` in `apply_patch` by resolving children via `djust_id` instead of index. ([#150](https://github.com/djust-org/djust/pull/150))
Network tab now captures both sent and received WebSocket messages by intercepting the `onmessage` property setter (not just `addEventListener`). ([#188](https://github.com/djust-org/djust/pull/188))
Events tab now populates by extracting event data from sent WebSocket messages and matching responses, replacing the broken `window.liveView` hook. ([#188](https://github.com/djust-org/djust/pull/188))
Handler discovery now finds all public methods; `debug-panel.js` auto-loads; handler dict normalized to array; retroactive WebSocket hooking for late-loading panels. ([#191](https://github.com/djust-org/djust/pull/191)…
When `DEBUG=True`, WebSocket event responses now include a `_debug` field with updated variables, handlers, patches, and performance metrics. ([#191](https://github.com/djust-org/djust/pull/191)) See `docs/website/advanced/debug-panel.md`.
Docs: debug-panel,
Events tab filter controls to search by event/handler name and filter by status. ([#180](https://github.com/djust-org/djust/pull/180))
Replay button (⟳) that re-sends events through the WebSocket with original params. ([#181](https://github.com/djust-org/djust/pull/181))
Panel UI state scoped per view class via localStorage. ([#182](https://github.com/djust-org/djust/pull/182))
Directional color coding and copy-to-clipboard for expanded payloads. ([#183](https://github.com/djust-org/djust/pull/183))
Integration tests against the actual `DjustDebugPanel` class. ([#185](https://github.com/djust-org/djust/pull/185))
Property-based testing for the VDOM diff algorithm with `proptest`. ([#153](https://github.com/djust-org/djust/pull/153))
VDOM keyed diff now warns on duplicate keys. ([#149](https://github.com/djust-org/djust/pull/149))
Official logo variants (dark, light, icon, wordmark, transparent). ([#208](https://github.com/djust-org/djust/pull/208), [#213](https://github.com/djust-org/djust/pull/213))
The `@event` shorthand is deprecated in favor of `@event_handler`. `@event` will be removed in v0.3.0. A deprecation warning is emitted at import time. ([#141](https://github.com/djust-org/djust/pull/141))
Refactored monolithic `live_view.py` into focused mixins: `RequestMixin`, `ContextMixin`, `JITMixin`, `TemplateMixin`, `RustBridgeMixin`, `ComponentMixin`, `LifecycleMixin`. No public API changes. ([#130](https://github.com/djust-org/djust/pull/130))
Split `client.js` into source modules with concat build, extracted `websocket_utils.py`, `session_utils.py`, `serialization.py`, split `state_backend.py` into `state_backends` package, split `template_backend.py` into `template` package.…
Upgraded uuid 1.19→1.20, thiserror 1→2, bincode 1→2, happy-dom 20.3.7→20.4.0, actions/setup-python 5→6, actions/upload-artifact 4→6, actions/checkout 4→6, softprops/action-gh-release 1→2
Three-layer defense for WebSocket event dispatch: ([#104](https://github.com/djust-org/djust/pull/104))
`{% if %}` conditions now support `and`, `or`, and `in` boolean/membership operators with correct precedence and chaining. ([#103](https://github.com/djust-org/djust/pull/103))
Docs: installation,
WebSocket mount no longer replaces `innerHTML` when content was pre-rendered via HTTP GET. Instead, `data-dj-id` attributes are stamped onto existing DOM elements, preserving whitespace in code blocks and syntax-highlighted content.…
Unkeyed children in keyed diffing contexts are now matched by relative position among unkeyed siblings, eliminating spurious insert+remove patch pairs when keyed children reorder. ([#95](https://github.com/djust-org/djust/pull/95)…
`dj-*` event handler attributes are no longer removed during VDOM patching. ([#100](https://github.com/djust-org/djust/pull/100))
Lists of Django Model instances are now properly serialized on GET requests. ([#103](https://github.com/djust-org/djust/pull/103))
WebSocket mount requests now use the actual page URL instead of a hardcoded path. ([#95](https://github.com/djust-org/djust/pull/95))
Upgraded html5ever 0.27→0.36, markup5ever_rcdom 0.3→0.36, vitest 2.x→4.x, actions/download-artifact 4→7. ([#101](https://github.com/djust-org/djust/pull/101), [#102](https://github.com/djust-org/djust/pull/102)…
`debug_vdom` Django config is now bridged to Rust VDOM tracing. Mixed keyed/unkeyed children emit developer warnings. ([#97](https://github.com/djust-org/djust/pull/97))
Reduced ~275 lines of duplicate code across the codebase through helper function extraction. These are internal improvements that don't affect the public API. ([#93](https://github.com/djust-org/djust/pull/93)…
Standardized all event bindings to use `dj-` prefix instead of `@` prefix. This affects all event attributes: `@click` → `dj-click`, `@input` → `dj-input`, `@change` → `dj-change`, `@submit` → `dj-submit`, `@blur` → `dj-blur`, `@focus` → `dj-focus`…
Removed legacy `python/djust/component.py`. Use `djust.Component` which now imports from `components/base.py`. ([#89](https://github.com/djust-org/djust/pull/89))
`LiveComponent.get_context()` → `get_context_data()` for Django consistency. ([#89](https://github.com/djust-org/djust/pull/89))
Deprecated decorator attributes removed: `_is_event_handler`, `_event_name`, `_debounce_seconds`, `_debounce_ms`, `_throttle_seconds`, `_throttle_ms`. Use `_djust_decorators` dict instead. ([#89](https://github.com/djust-org/djust/pull/89))
Standardized data attribute naming for consistency:
Renamed message types for consistency:
Added missing methods to `LiveComponent`: `_set_parent_callback()`, `send_parent()`, `unmount()`. ([#89](https://github.com/djust-org/djust/pull/89))
`LiveComponent` now supports inline `template` attribute for template strings, in addition to `template_name` for file-based templates. ([#89](https://github.com/djust-org/djust/pull/89))
Docs: template-cheatsheet,
`ForeignKeySelect` and `ManyToManySelect` are now exported from `djust.components`. ([#89](https://github.com/djust-org/djust/pull/89))
Docs: components,
Template parser now correctly handles `{% elif %}` conditionals. Previously, elif branches fell through to the unknown tag handler and rendered all branches instead of just the matching one. ([#80](https://github.com/djust-org/djust/pull/80))
Component `render()` methods now fall back to Django templates when Rust template engine fails (e.g., for `{% include %}` tags). ([#89](https://github.com/djust-org/djust/pull/89))
Fixed template inheritance for nested blocks. When a child template overrides a block that is nested inside another block in the parent (e.g., `content` inside `body`), the override is now correctly applied. ([#71](https://github.com/djust-org/djust/pull/71))
Extensible system for custom Django template tags in Rust. Register Python callbacks for tags like `{% url %}` and `{% static %}` with ~100-500ns overhead per call. Built-in tags (if, for, block) remain zero-overhead native Rust. Includes ADR documenting…
Template conditions now support `>`, `<`, `>=`, `<=` operators in addition to `==` and `!=`. ([#65](https://github.com/djust-org/djust/pull/65))
Full support for `with` clause (pass variables) and `only` keyword (isolate context). ([#65](https://github.com/djust-org/djust/pull/65))
Comprehensive benchmarking with Criterion (Rust) and pytest-benchmark (Python). New Makefile commands: `make benchmark`, `make benchmark-quick`, `make benchmark-e2e`. Enables tracking performance across releases and detecting regressions.…
Event handlers now support function-call syntax with arguments directly in the template attribute. Use `dj-click="handler('arg')"` instead of `dj-click="handler" data-value="arg"`. Supports strings, numbers, booleans, null, and multiple arguments.…
WebSocket consumer now properly supports `async def` event handlers. Previously only synchronous handlers worked correctly. ([#63](https://github.com/djust-org/djust/pull/63))
Django's `{% url %}` template tag is now fully supported with automatic Python-side URL resolution. Supports named URLs, namespaced URLs, and positional/keyword arguments. ([#55](https://github.com/djust-org/djust/pull/55))
Fixed template include functionality by passing template directories to the Rust engine. Included templates are now correctly resolved from configured template paths. ([#55](https://github.com/djust-org/djust/pull/55))
Added the `urlencode` filter for URL-safe encoding of strings. Supports encoding all characters or preserving safe characters. ([#55](https://github.com/djust-org/djust/pull/55))
Added support for `>`, `<`, `>=`, `<=` comparison operators in conditional expressions. ([#55](https://github.com/djust-org/djust/pull/55))
Context variables with Django types (datetime, date, time, Decimal, UUID, FieldFile) are now automatically serialized for Rust rendering. No manual JSON conversion required. ([#55](https://github.com/djust-org/djust/pull/55))
LiveView elements can now defer WebSocket connections until they enter the viewport or receive user interaction. Use `dj-lazy` attribute with modes: `viewport` (default), `click`, `hover`, or `idle`. Reduces memory usage by 20-40% per page for below-fold…
LiveView now works seamlessly with Turbo-style client-side navigation. WebSocket connections are properly disconnected on navigation and reinitialized when returning to a page. ([#54](https://github.com/djust-org/djust/pull/54))
Docs: turbonav-integration,
Template parser now merges adjacent Text nodes during AST optimization, reducing allocations and improving render time by 5-15%. Comment nodes are also removed during optimization as they produce no output. ([#54](https://github.com/djust-org/djust/pull/54))
Fixed template inheritance for nested blocks (e.g., `docs_content` inside `content`). Block overrides are now recursively applied to merged content, ensuring deeply nested blocks are correctly resolved. ([#57](https://github.com/djust-org/djust/pull/57))
Added `parse_html_continue()` function to maintain ID counter continuity across parsing operations. Prevents ID collisions when inserting dynamically generated elements (like validation error messages) that caused first-click validation issues.…
Whitespace is now preserved inside `<pre>`, `<code>`, `<textarea>`, `<script>`, and `<style>` elements during both Rust parsing and client-side DOM patching. ([#54](https://github.com/djust-org/djust/pull/54))
Upgraded pyo3 from 0.22 to 0.24 to address RUSTSEC-2025-0020 (buffer overflow vulnerability in `PyString::from_object`). ([#55](https://github.com/djust-org/djust/pull/55))
LiveView now automatically applies Django context processors configured in `DjustTemplateBackend`. Variables like `GOOGLE_ANALYTICS_ID`, `user`, `messages`, etc. are now available in LiveView templates without manual passing.…
Cache keys now include URL path and query string hash, preventing render corruption when navigating between views with different template structures (e.g., `/emails/` vs `/emails/?sender=1`). ([#24](https://github.com/djust-org/djust/pull/24))