djust 0.9.0rc4

Pre-releaseReleased

This is a pre-release. djust 0.9.0 has shipped since: read the djust 0.9.0 release notes.

Added

  • Debug Panel UI for time-travel — per-component scrubber, forward-replay button, branch indicator (PR-B for #1151) — the user-facing UI built on top of the wire-protocol shipped in PR-A (#1193). Closes #1151.

    • Branch indicator at the top of the Time Travel tab — distinct badge styling for main (blue) vs branched timelines (orange, branch-N from forward-replay). Tracks the active branch_id from every server frame (both ack and event push).
    • "X / max" event count in the header so the user can see when they're approaching the configured time_travel_max_events cap.
    • Forward-replay button (⏵ replay) on every history row. Clicking sends a forward_replay frame with from_index set to that row's index; the server allocates a new branch_id if the replay diverges (non-tip cursor or override_params present).
    • Per-component expand-toggle (▶ N comp) on rows whose snapshot includes a __components__ dict. Expanding reveals a sub-row for each component with its truncated state preview and ↶ comp / ↷ comp buttons that scrub a SINGLE component's state via time_travel_component_jump — leaves parent view + other components alone.
    • CSP-strict: zero inline event handlers, all interactivity via the existing delegated click handler on the panel root. Per CLAUDE.md canon #1175.
    • Replay-hint label appears in the header when forward_replay_enabled is true (cursor is not at the buffer tip).

    Files: python/djust/static/djust/src/debug/09a-tab-time-travel.js (rewrote from 156 LoC to 320 LoC), python/djust/static/djust/debug-panel.css (90 LoC of additive .tt-branch*/.tt-comp-*/.tt-forward-replay/ .tt-expand-toggle rules), regenerated bundles debug-panel.js / .min.js / .min.js.gz / .min.js.br via scripts/build-client.sh. 23 new vitest cases in tests/js/debug_panel_time_travel_ui.test.js covering: backwards- compat ack frames, branch badge selection, count formatting, replay hint, expand-toggle visibility, component sub-row rendering, click dispatch for component-jump and forward-replay, override-params passthrough, branch_id update from event push frames, AND end-to-end delegated-click integration (real DOM clicks through registerTimeTravelClickHandlers).

  • Time-travel wire-protocol exposure for branched timelines + per-component scrubbing (PR-A for #1151) — server-side surface that the v0.9.4 debug panel UI (PR-B, follow-up) consumes. The Python plumbing for per-component time-travel (#1041) and forward-replay through branched timelines (#1042) shipped in v0.9.0; this PR exposes the missing wire fields so the debug panel can drive both.

    • time_travel_state ack frame: 3 new additive fields — branch_id (defaults "main"; new branches allocated as branch-{N} on forward-replay from a non-tip cursor), forward_replay_enabled (true iff cursor is not at the tip — meaningful replay would produce a branch), max_events (the configured ring-buffer cap, so the UI can show "X / max"). Old clients ignore the new keys.
    • time_travel_event per-event push frame: 2 new additive fields — branch_id and a top-level components mirror of entry.state_after.__components__ so the UI doesn't have to dig into the nested entry.
    • New handler time_travel_component_jump: scrubs a SINGLE component's state without touching the parent view or other components. Mirrors the existing time_travel_jump validation and re-render path; backed by a new restore_component_snapshot() helper in python/djust/time_travel.py.
    • New handler forward_replay: replays a recorded event with optional override_params and allocates a fresh branch id when the cursor is not at the buffer tip. Backed by the existing replay_event() (#1042) plus a new next_branch_id() allocator.
    • Live-view init: 2 new instance fields on LiveView.__init___time_travel_branch_id (default "main") and _time_travel_branch_counter (default 0). Both are inert when the buffer isn't allocated; zero memory cost for views that don't opt in to time-travel.

    Files: python/djust/websocket.py (dispatch arms + 2 handlers + ack builder), python/djust/time_travel.py (restore_component_snapshot, next_branch_id), python/djust/live_view.py (branch fields). 12 new cases (8 integration + 4 unit) covering ack-frame shape, replay-enabled semantics, component-only restore isolation, branch-id allocation, defensive defaults, override-params-at-tip branching, branch-id no-leak on replay failure, and which="after" component restore. PR-B (the debug panel UI consuming these fields) is the next v0.9.4 PR.

Documentation

  • v0.9.4 process canon (closes #1185, closes #1143, closes #1144) — three retro patterns from the v0.9.x arc canonicalized so the next drain doesn't repeat the same mistakes:
    • #1185: docs/PULL_REQUEST_CHECKLIST.md Closing-Keywords rule expanded to call out the parenthesized form (closes #X, closes #Y) explicitly. PR #1176 used it in the title and silently failed to close both issues. The checklist now names the failure mode and recommends always using PR-body lines for closing keywords.
    • #1143: CLAUDE.md "Process canonicalizations from v0.9.0 retro arc" section added — Stage-4 first-principles grep before architecting. Lists 5 canonical grep targets (wire-protocol, state-snapshot, async dispatch, decorator composition, component lifecycle) so Plan stages cite file:line of the pattern being mirrored.
    • #1144: same section — branch-name verify reflex. Pre-commit one-liner that compares git symbolic-ref --short HEAD against the active state file's branch_name field, catching the silent "wrong-branch commit" failure observed twice in v0.9.0.

Fixed

  • v0.9.4 test-infra polish (closes #1188, closes #1189) — three small follow-ups bundled as one PR:
    • #1188 🟡 #1: narrowed vitest.config.js Pattern 2 filter to match only the diagnosed Closing rpc + onUserConsoleLog / onConsoleLog cause from PR #1187. Dropped the broader stack.includes('view-transitions') disjunct so future genuinely-different failure shapes in view-transitions.test.js can no longer be silently swallowed.
    • #1188 🟡 #2: added gc.collect() before the _wait_for_one-warning absence check in tests/integration/test_chunks_overlap.py::test_cancel_does_not_leak_wait_for_one_warning. The warning fires from CPython's coroutine GC, not explicit code; the prior test passed by accident of CPython's reference-counting timing. Forcing collection makes the assertion deterministic under PyPy / free-threaded / different GC modes.
    • #1189: bumped test_large_template wall-clock bound from 100ms → 500ms with a comment explaining the test is a regression bound, not a benchmark. The prior tight bound flaked on busy CI runners (5-10ms typical local; 100ms+ under py3.13 free-threaded parallel suite load). Real perf tracking lives in pytest-benchmark, not this assertion.

Changed

  • HVR auto-enabled in DEBUG (no AppConfig.ready() boilerplate required) — djust's own DjustConfig.ready() now auto-calls enable_hot_reload() whenever DEBUG=True and watchdog is installed. Existing per-consumer enable_hot_reload() calls keep working unchanged (idempotent via hot_reload_server.is_running()). Opt out via LIVEVIEW_CONFIG['hot_reload_auto_enable']: False for projects that orchestrate the file watcher externally. Test runs auto-skip via PYTEST_CURRENT_TEST so pytest sessions don't spawn a watchdog thread per test. Files: python/djust/apps.py (auto-enable call appended to ready()), python/djust/config.py (new hot_reload_auto_enable: True default), python/djust/__init__.py (docstring update). 6 new cases covering auto-fire, opt-out config, pytest-env skip, idempotency, exception isolation, and other-setup completion (new file python/djust/tests/test_auto_hot_reload.py). Drops the one-line enable_hot_reload() call from examples/demo_project/demo_app/apps.py. Closes the friction observed across downstream consumers (docs.djust.org, djust.org, djustlive) that were either rolling their own watchfiles process-restart wrappers or silently missing the integration step altogether — the framework's HVR is strictly better than process restart (preserves view state, scroll position, form input across edits) but the consumer-side integration step was easy to skip.

All releases · Atom feed