djust 0.6.0rc1

Pre-releaseSecurityReleased

Documentation

  • CSS @starting-style guide section (v0.6.0) — 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 side-by-side comparison vs dj-transition (browser support, runtime cost, per-element customization), interop notes with dj-remove for enter+exit coverage, and caveats around @supports gating for older browsers. ROADMAP parity-tracker row updated to ✅ Documented v0.6.0.

Changed

  • Package consolidation sunset — ADR-007 Phase 4 closure (v0.6.0) — 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 from djust.<name> and emits a DeprecationWarning. Path A was chosen over PyPI publish: existing releases remain installable indefinitely for legacy projects; no new PyPI versions will ship. djust core now exposes the consolidation via [project.optional-dependencies]djust[auth], djust[tenants] (with djust[tenants-redis] and djust[tenants-postgres] backend-specific sub-extras), djust[theming], djust[components], djust[admin]. Two new extras (auth, tenants) added in this release; the others shipped in v0.5.0. ADR-007 status updated from "Proposed" → "Accepted + Phase 4 complete". New migration guide: docs/website/guides/migration-from-standalone-packages.md (mechanical sed script + FAQ + edge cases). Cosmetic tech-debt: sibling repos retain dead pre-consolidation source files next to the shim — cleanup tracked separately; no user impact.

Added

  • Request-path profiling harness (v0.6.0, investigative, ROADMAP Group 5 P2) — 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 tests/benchmarks/test_request_path.py with eight pytest-benchmark cases across four groups (HTTP render, WebSocket mount, event dispatch, VDOM diff+patch) with hard assertions against the 2 ms per-event / 5 ms list-update budgets. New docs/performance/v0.6.0-profile.md reporting all measured timings (mount 0.07 ms, event 4 µs, VDOM diff 4 µs, list reorder 0.38 ms — all within targets by at least 5x). New make profile target wired to the harness (the prior make profile runtime-stats target is now make profile-stats). No optimizations were required; the profile confirms the existing Rust-side architecture is well under target.

  • Service Worker advanced features (v0.6.0) — three SW-backed optimizations landed in one PR:

    • VDOM patch cache: per-URL HTML snapshots served instantly on popstate, then reconciled against the live WebSocket mount reply. Configurable via DJUST_VDOM_CACHE_ENABLED / DJUST_VDOM_CACHE_TTL_SECONDS / DJUST_VDOM_CACHE_MAX_ENTRIES. New system checks djust.C301 / C302 / C303 guard config ranges.
    • LiveView state snapshots: opt-in per view via enable_state_snapshot = True on a LiveView subclass. Client captures JSON-serializable public state on djust:before-navigate; server restores via _restore_snapshot(state) in lieu of mount() when the user hits back. Views override _should_restore_snapshot(request) to reject stale snapshots. System check djust.C304 warns when a snapshot-opt-in view declares attributes matching PII naming patterns.
    • Mount batching: when multiple dj-lazy LiveViews hydrate together, the client sends one mount_batch WebSocket frame instead of N separate mount frames. Server responds with one mount_batch carrying all rendered views; per-view failures are isolated in a failed[] array (atomicity relaxed so one bad view doesn't kill the batch). Opt out via window.DJUST_USE_MOUNT_BATCH = false.
    • New client module 46-state-snapshot.js (~120 LOC); new senders on djust._sw.cacheVdom/lookupVdom/captureState/lookupState.
    • registerServiceWorker({vdomCache: true, stateSnapshot: true}) gates the new behaviors alongside existing instantShell / reconnectionBridge options.

    See docs/website/guides/service-worker.md.

Changed

  • LiveViewConsumer.handle_mount() accepts new state_snapshot kwarg; dispatches to the snapshot-restore path when the view opts in and the payload's view_slug matches. New method handle_mount_batch() + _mount_one() collector seam enable the mount-batch path without regressing the single-view mount flow.

Security

  • State snapshots are JSON-only (no pickle). safe_setattr blocks dunder keys and private (_-prefixed) attributes during restoration. SW enforces a 256 KB upper bound on state_json payloads; client clamps at 64 KB. System check djust.C304 warns when snapshot-opt-in views declare attribute names matching password|token|secret|api_key|pii.

  • Sticky LiveViews (v0.6.0) — 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 + sticky_id and embed it via {% live_render "myapp.views.AudioPlayerView" sticky=True %}. Destination layouts declare <div dj-sticky-slot="<id>"></div> at the re-attachment point; the same Python instance, DOM subtree, form values, scroll/focus, and background tasks all survive live_redirect navigation. Use case: app-shell widgets (audio players, sidebars, notification centers), wizard preview panes.

    User-facing API

    • LiveView.sticky: bool = False + sticky_id: Optional[str] = None class attrs.
    • {% live_render "dotted.path" sticky=True %} template tag (validates class opt-in at render time; TemplateSyntaxError on mismatch).
    • [dj-sticky-slot="<id>"] slot markers in destination layouts.
    • djust:sticky-preserved / djust:sticky-unmounted CustomEvents for lifecycle hooks (reasons: server-unmount, no-slot, auth).
    • _on_sticky_unmount() per-instance hook (default: cancels pending start_async tasks).

    Wire protocol

    • child_update (Phase A) — scoped VDOM patches for embedded non-sticky children.
    • sticky_hold (server→client, sent BEFORE mount on live_redirect) — enumerates surviving sticky_ids so the client reconciles its stash against the authoritative list. Ordering is load-bearing: the mount handler eagerly reattaches, so a late sticky_hold would reattach auth-revoked views.
    • sticky_update (server→client) — per-child VDOM patches scoped to [dj-sticky-view="<id>"] via a new applyPatches(patches, rootEl) variant in 12-vdom-patch.js (when rootEl is non-null, node lookups / focus save-restore / autofocus queries all scope to that subtree).
    • Per-view VDOM version tracking via clientVdomVersions: Map<view_id, number> with "__root" sentinel for top-level patches.

    Client-side

    • static/djust/src/45-child-view.jsstickyStash Map; stashStickySubtrees() (detach on outbound nav), reconcileStickyHold(views) (drop non-authoritative), reattachStickyAfterMount() (replace [dj-sticky-slot] with stashed subtree via replaceWith() — DOM identity preserved), handleStickyUpdate(msg) (scoped patch apply), clearStash() (abnormal-close cleanup).
    • 18-navigation.js calls stashStickySubtrees() BEFORE outbound live_redirect_mount (and before popstate-triggered redirects).
    • 03-websocket.js onclose calls clearStash() on abnormal disconnect.
    • [dj-root] audit across 40-dj-layout.js, 24-page-loading.js, 12-vdom-patch.js autofocus sites adds :not([dj-sticky-root]) so sticky children don't masquerade as layout / page roots.

    Security

    • Per-sticky auth re-check via new djust.auth.check_view_auth_lightweight(view, request) -> bool; a sticky view whose permissions are revoked mid-session is unmounted on the next navigation.
    • DJUST_LIVE_RENDER_ALLOWED_MODULES prefix-allowlist gates dotted-path resolution (unset = permit-all, backward compatible).
    • sticky_id HTML-escaped via server-side escape() + CSS.escape on client-side selectors.
    • Client stash bounded by developer-authored content; idempotent stashStickySubtrees coalesces duplicates; cleared on abnormal WS close.
    • Inbound sticky_update / sticky_hold frames rejected by the consumer's allowlist (server-to-client only).

    Testing (32 Python + 20 JSDOM + 6 integration)

    • 11 Phase A tests in tests/unit/test_live_render_tag.py (HTML-parsed) + 21 Phase B/C tests in tests/unit/test_sticky_preserve.py.
    • 7 Phase A tests in tests/js/child_view.test.js + 15 Phase B/C tests in tests/js/sticky_preserve.test.js.
    • 3 end-to-end tests in tests/integration/test_sticky_redirect_flow.py (Dashboard→Settings preservation, rapid A→B→A instance identity, no-slot reconcile path) + 3 demo-app smoke tests covering the full navigation cycle.
    • Phase C regression tests: skipMountHtml mount branch reattaches sticky subtrees (Fix F1); disconnect() drains _sticky_preserved so background tasks don't leak (Fix F2).

    Documentation

    • ADR-011 — wire protocol, DOM attributes, client/server flow diagrams, full security model + threat matrix, failure modes, relationship to v0.7.0 dj-activity.
    • User guide — quick start, common patterns, limitations, debugging, FAQ.
    • Runnable demo app in examples/demo_project/sticky_demo/ — Dashboard, Settings, Reports pages with sticky AudioPlayer + NotificationCenter widgets showing preservation + no-slot unmount.
  • FLIP list-reorder animations (v0.6.0 animations milestone finale) — 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 rejects to fallback), dj-flip-easing (default cubic-bezier(.2,.8,.2,1), strings containing ;"'<> rejected to defeat CSS-property-breakout). Respects prefers-reduced-motion. Nested [dj-flip] isolated via subtree: false. Author-specified inline transform on children is preserved across the animation. Overlapping reorders are guarded against cache corruption via an in-flight-transition check. Works with keyed lists where items carry stable id= (Rust VDOM emits MoveChild). Lands in static/djust/src/44-dj-flip.js (~260 LOC). 12 JSDOM tests in tests/js/dj_flip.test.js.

  • {% djust_skeleton %} shimmer placeholder (v0.6.0 animations milestone finale) — 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 values HTML-escaped via build_tag(). Shimmer @keyframes emitted once per render via context.render_context. Integrates with existing dj-loading shorthand and with {% if async_pending %} server blocks. 21 Python tests in tests/unit/test_djust_skeleton_tag.py.

All releases · Atom feed