This is a pre-release. djust 1.1.0 has shipped since: read the djust 1.1.0 release notes.
Fixed
live_redirectto a non-LiveView path now falls back to a full-page navigation instead of stranding the page (#1934). Withauto_navigatedefaulting ON in v1.1, alive_redirectwhose target is a plain Django view (e.g. aTemplateView) left the URL bar on the new path while the previous LiveView stayed mounted — the URL led the DOM with no swap. Two coupled client bugs inhandleLiveRedirect(python/djust/static/djust/src/18-navigation.js): (1) thepushStatefired BEFORE the view resolution, so the URL changed for a target that never got a DOM swap; and (2) — the load-bearing root cause found by symptom-up tracing, NOT the issue's cited "resolveViewPath returns falsy" — the resolution usedresolveViewPath(), which has a container fallback that returns the CURRENT[dj-view]'s class on a route-map miss. That fallback is documented "only works for live_patch, not cross-view navigation", so for a cross-viewlive_redirectto a non-LiveView it returned the SOURCE view (truthy) and the client SPA-mounted the OLD view under the NEW URL — the exact reported symptom (URL/onboarding/, but the jira view mounts). The server's #1647_resolve_view_path_from_urlguard also returnsNonefor a non-LiveView URL and keeps the stale client-supplied view, so the client must make the full-nav decision. Fix: a new STRICTresolveLiveViewPath()(route map ONLY, no container fallback) drives the cross-view decision; thepushState+ URL-dependent side effects (updateAriaCurrent, scroll,before-navigate) are DEFERRED into the LiveView-resolved + WS-connected branch, so the URL never leads the DOM. A non-LiveView target (or a disconnected WS) does a full-page navigation validated throughwindow.djust.safeNavigationTarget(mirroring the existing cross-origin branch, with thesafeNavigationTargetopen-redirect/javascript:guard). The popstate back-nav redirect (the #1646 twin) also switched to the strict resolver, so a back-nav to a non-LiveView reloads correctly instead of re-mounting the source view. The served minified bundle (client.min.js+.gz/.br/.map) was rebuilt to carry the fix. Reproduce-first + gate-off (#1468) verified: new cases indescribe('issue #1934 …')(tests/js/navigation.test.js) —non-LiveView target: full-page nav, NO pushState, NO WS mount (strand-free),positive case: a LiveView target still SPA-mounts, andLiveView target but WS not connected: full-page nav— go RED when EITHER half of the fix is reverted (the strict-resolver call → the SPA branch fires safeNavigationTarget never called; the pushState-first order → the strand pushState assertion fails). Full JS suite green (1746 passed).De-flaked
TestMountAsyncAndPushDrain::test_mount_dispatches_async_workunder parallel-n autoby OWNING THE COMPLETION SIGNAL instead of bounded-polling the scheduler (#1931; the async-dispatch sibling of #1930). The test mounts a view that schedules a background callback viastart_async()inmount(), then asserts the callback ran (view.value == 42). The runtime dispatches that callback FIRE-AND-FORGET viaasyncio.ensure_future(self._execute_async_task(...))insideViewRuntime._dispatch_async_work(python/djust/runtime.py:4444), and_execute_async_taskITSELF awaits async_to_async(callback)thread-pool round-trip before settingvalue=42. The original test waited for that to land via a BOUNDED poll —for _ in range(10): await asyncio.sleep(0)— a wall-clock-fragile race: under a CPU-saturated parallel loop the asyncio scheduler can fail to run the spawned task (which also competes for the thread pool) within 10 yields, so the assertion fired whilevaluewas still 0 (flaked 1/4 runs in the #1930 worktree; passed 3/3 in isolation). This is the CLAUDE.md bounded-poll-racing-a-real-scheduler class (#1830/#1815 family), NOT a code regression — the mount async-dispatch feature is correct and lands every time given enough scheduler turns. Fix (inpython/djust/tests/test_transport_behavioral_parity.py, test-only — production unchanged): wrap the runtime'sasyncio.ensure_futureseam duringdispatch_mountto capture the EXACT_execute_async_tasktask handle it spawns (the_flush_push_eventsfire-and-forget send, which uses the same primitive, is excluded by coroutine name so the gate-off stays sharp), thenawait asyncio.gather(*async_work_tasks)— a deterministic completion signal, no timing bound. Reproduce-first verified: shrinking the poll bound to 0–1 yields makes the OLD form fail 25/25 (proving the margin is razor-thin), while the new form passes 0/15 failures under 8-way CPU saturation. Gate-off (#1468) verified: disabling the_dispatch_async_work(None)call indispatch_mountspawns no_execute_async_task→assert async_work_tasksfails (empty list), so the test is load-bearing on the actual async-dispatch path. 3-clean-runs gate (#1174): full suite-n auto× 3 all clean (8604 passed each). New behavior intest_mount_dispatches_async_work.De-flaked the six rate-limit burst-exhaustion tests under
-n autoby OWNING THE CLOCK —test_ping_flood_triggers_disconnectno longer flakes on a wall-clock token refill (#1930).TestRateLimiter+TestGlobalRateLimit(tests/unit/test_event_security.py) build test-localTokenBucket/ConnectionRateLimiterinstances and assert burst exhaustion (e.g.rate=100, burst=2→ the 3rdcheck()must beFalse).TokenBucketreadtime.monotonic()directly, so under CPU-saturated parallelmake testreal wall-clock elapsed betweenconsume()calls and the refill mathtokens + elapsed * rate(rate=100 = 1 token / 10ms) added a token back — flipping a "burst exhausted → False" assertion non-deterministically toTrue. This is the CLAUDE.md flaky-timing class (never gate pass/fail on wall-clock; #1830/#1815 family), NOT the #1883 shared-global pollution class the issue hypothesized — every limiter here is test-local and reads no leaked global. Fix: a_monotonic = time.monotonicmodule-level seam inpython/djust/rate_limit.py(production behavior identical; one indirection) routesTokenBucket.__init__+consume()through a patchable name WITHOUT patching the globaltimemodule; aFakeClock+frozen_clockpytest fixture monkeypatchesdjust.rate_limit._monotonicso the five burst-exhaustion tests run on a FROZEN clock (elapsed == 0→ no refill → deterministic), andtest_token_bucket_refillsreplacestime.sleep(0.05)withfrozen_clock.advance(0.05)(deterministic, instant, still genuinely exercises the refill path). Reproduce-first verified: advancing the clock 15ms between burst checks flips the 3rd ping checkFalse → True. Gate-off (#1468) verified: with an advancing clock the rate=100 tests go RED at a 15ms stall and the slower rate=10/rate=1 tests go RED at a 2s stall (frozen clock load-bearing for all six), and the refilladvance()is load-bearing (without it the drained token stays unavailable). 3-clean-runs gate (#1174): full suite-n auto× 3 all clean (8603 passed each, the unrelated pre-existing async-timing flake #1931 deselected). Fixture applies to the burst tests inTestRateLimiterandTestGlobalRateLimit.