Added
{% theme_css_link %}cache-busting helper tag (v0.8.2 drain — Group T, closes #1012) — Chrome'sVary: Cookiehandling 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 indjust.theming.templatetags.theme_tagsemits<link href="/_theming/theme.css?p=<pack>&m=<mode>&r=<preset>">with cache-busting query params derived from the sameThemeManager.get_state()the view itself reads. Different pack/mode = different URL = guaranteed fresh fetch. Usage:<link rel="stylesheet" href="{% theme_css_link %}">.prose.cssfor@tailwindcss/typography↔ pack bridge (v0.8.2 drain — Group T, closes #1009) — newdjust_theming/static/djust_theming/css/prose.cssships pack-aware overrides for the typography plugin's--tw-prose-*variables. Opt in by addingprose-djustalongsideproseon your<article>. Reads--color-brand-*tokens the active pack emits, so flipping packs at runtime updates prose without a stylesheet swap. Includes both light-mode and dark-mode invert variables. Pulled from docs.djust.org's reference implementation. ~95 lines.enable_client_overrideflag forLIVEVIEW_CONFIG['theme'](v0.8.2 drain — Group T, closes #1013) —ThemeManager.get_state()readsdjust_theme_pack/djust_theme_presetcookies with priority over config defaults. Default behavior unchanged (back-compatTrue). Sites without a user-facing theme switcher can setLIVEVIEW_CONFIG['theme']['enable_client_override']: Falseto ignore cookie reads — prevents cross-project bleed on localhost where multiple djust apps share a cookie jar.
Fixed
.card/.alertoverflow:hidden for clean rounded corners (v0.8.2 drain — Group T, closes #1011) —djust_theming/static/djust_theming/css/components.css.cardand.alertselectors now setoverflow: 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 corners. Affects every theme pack.mount_batchfallback for old-server compat (v0.8.1 reconcile drain — Group F, closes #1031) — themount_batchWebSocket 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 the client tracks the in-flight batch inlazyHydrationManager.inFlightBatch; if the websocket error handler seesmount_batchorUnknown message typein the error string, it invokeshandleMountBatchFallback()which iterates the stashed mounts and falls back to per-view mount calls. Idempotent (clearsinFlightBatchbefore iterating) so a late-arriving successful response can't double-trigger. 7 new JSDOM tests undertests/js/mount-batch-fallback.test.js.
Security
- Drop exception text from JSON-parse error responses (v0.8.1 reconcile drain — Group B, closes #1026) —
python/djust/api/dispatch.py(two sites at the API event-dispatch and server-function paths) was returningf"Malformed JSON body: {exc}"— a small but real stack-trace-style leak that could surface parser internals (offsets, snippets of the malformed input) to the client. Aligned to matchobservability/views.py:401's existing pattern: log the exception server-side vialogger.exception(...), return a generic"Malformed JSON body — see server logs"message. Theinvalid_jsonerror code is unchanged, so callers that branch onerrorkeep working.
Changed
WebSocket cache-write failures now log under
djustDebug(v0.8.1 reconcile drain — Group B, closes #1030) —python/djust/static/djust/src/03-websocket.js:386previously swallowed cache-put exceptions with a barecatch (_e) {}. Now logs the failure viaif (globalThis.djustDebug) console.log(...)so developers can diagnose cache-write misses without polluting production console output.Test infrastructure cleanup (v0.8.1 reconcile drain — Group A, closes #1027, #1028, #1034, #1036) — four small test-quality refactors bundled in one PR:
- #1036:
_assert_benchmark_underand the per-segment budget constants (TARGET_PER_EVENT_S,TARGET_LIST_UPDATE_S,TARGET_WS_MOUNT_S) moved fromtests/benchmarks/test_request_path.pyintotests/benchmarks/conftest.pyfor shared scope across benchmark files. - #1034: replaced the
TARGET_LIST_UPDATE_S * 20magic-number budget for the WS-mount benchmark with a namedTARGET_WS_MOUNT_S = 0.1constant — rationale lives in the constant name, not the multiplier. - #1028: extracted the duplicated
_make_userfactory intopython/djust/tests/conftest.pyasmake_staff_user(...). Two test files (test_admin_widgets_per_page.py,test_bulk_progress.py) now import the shared factory. - #1027: replaced the
inspect.getsource-based regression test intest_stack_trace_exposure.pywith a behavior-level test that triggers a serialize-error via a sentinel-ladenRuntimeErrorand asserts neither the sentinel nor the exception class name reach the response body. Defends against regressions even if the leak vector moves.
- #1036:
Added
make roadmap-lint— mechanical ROADMAP-vs-codebase drift check (Action #142, closes #1057) —scripts/roadmap-lint.pyparses the "Not started" entries inROADMAP.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). Pure mechanical check — for semantic auditing (LLM reads each entry, decides if the cited feature actually ships) use thepipeline-roadmap-auditskill instead. Exit code 0 unless drift exceeds threshold (25 suspect entries). Run viamake roadmap-lintormake roadmap-lint VERBOSE=1.Pre-push hook for
# noqa: F822in__all__patterns (Action #146, closes #1061) —scripts/check-noqa-f822.shflags newnoqa: F822annotations introduced inpython/**/*.pyortests/**/*.pysince the last push. Ruff silencespy/undefined-exportwithnoqa: F822, but CodeQL flags it as a security alert later — the canonical fix is aTYPE_CHECKING-conditional import (PR #924 pattern). Hook fires only on changed files (incremental); pass--allto scan the whole tree manually.