This is a pre-release. djust 0.9.5 has shipped since: read the djust 0.9.5 release notes.
Before you upgrade, read Removed below.
Added
- Bundle-init-order structural lint:
scripts/check-bundle-init-order.mjs(#1372, #1370 follow-up). Static check for the direct-top-level-read TDZ subclass — catches the case where a late-declaredlet/constis referenced directly at top level of an earlier module. Enumerates module-scopelet/constacrosspython/djust/static/djust/src/*.js, finds top-level use sites via acorn AST, and flags any cross-module use where the use-site lex-orders BEFORE the declaration. Does NOT catch transitive call-graph TDZ (e.g.,djustInit()callingmountHooks()whose body reads a latelet— this is exactly the #1370 shape). The runtime regression testbundle-init-no-tdz.test.jscontinues to catch transitive cases via JSDOM eval. The two checks are complementary; extending this lint to a depth-N call-graph walker is filed as a follow-up. Wired intoMakefile(make check-bundle-init-orderis part ofmake check) and pre-push hook. Currently clean on main.
Changed
- JS micro-cleanup: deduplicated transition helpers + tightened
routeMapaccess (#1360, #1361). Two follow-ups deferred from PR #1359 Stage 11._parseTimeMsand_computeTransitionTimingextracted from41-dj-transition.jsand42-dj-remove.jsinto a new shared40a-transition-helpers.js(loads before both consumers per the bundle's lexicographic concat order). CodeQL alerts atclient.js:13162and:13171("Conflicting function declarations") clear; bundle has exactly one definition of each (#1360).routeMap[pathname]access in18-navigation.jsreplaced with anObject.entries(routeMap)walk — prototype-pollution-immune by construction (own enumerable string-keyed entries only). Lints cleanly withouteslint-disable-next-line. Same shape applied to46-state-snapshot.js. Map conversion (option B) rejected because it would change the wire-protocol shape emitted bypython/djust/routing.pyand break downstream consumers (#1361).
Removed
- Dead
InMemoryStateBackend.get_and_update()removed (#1356). Method had zero callers and would re-introduce the #1353 shared-mutable-state race class if a future caller was added without auditing. PR #1355 fixed the siblingget()to clone via msgpack round-trip;get_and_update()was overlooked. Per the issue body's preferred-fix order, deletion was cleanest. Removes ~22 lines of dead code frompython/djust/state_backends/memory.py. (Surfaced as PR #1355 Stage 13 Re-Review #1.)
Fixed
Node::Includeround-trip no longer double-quotes the template path (#1396). Parser was preserving the outer quotes onInclude.template; emitternodes_to_template_stringthen wrapped again, producing{% include ""partials/header.html"" %}on round-trip. Surfaced during PR #1397's conversion of round-trip tests to drive from parser output (Action #158 working as designed). Fixed by aligning the parser to strip outer quotes (matching the existingExtends,Static, andNowcontracts) — single source of truth, emitter unchanged.test_nodes_to_template_string_includeun-ignored. Addedtest_nodes_to_template_string_nowfor defense-in-depth.
Security
sanitize_for_logcache_key on HTTP cache-lookup debug log (#1368). Pre-existing log-injection asymmetry between WebSocket and HTTP paths inpython/djust/mixins/rust_bridge.py: the WS site at line 333 sanitized correctly; the HTTP site at line 363 did not. Sincecache_keyderives fromrequest.path(user-controlled), an attacker-supplied path like/page/\n[FAKE LOG ENTRY]could inject newlines into the log stream. Mirrors the WS-path call tosanitize_for_log(self._cache_key)and adds the matching CodeQL annotation. Surfaced in PR #1367 Stage 11 SHOULD-FIX #3 (deferred per Action #1079).