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-Nfrom forward-replay). Tracks the activebranch_idfrom 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_eventscap. - Forward-replay button (
⏵ replay) on every history row. Clicking sends aforward_replayframe withfrom_indexset to that row's index; the server allocates a newbranch_idif 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/↷ compbuttons that scrub a SINGLE component's state viatime_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_enabledis 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-togglerules), regenerated bundlesdebug-panel.js/.min.js/.min.js.gz/.min.js.brviascripts/build-client.sh. 23 new vitest cases intests/js/debug_panel_time_travel_ui.test.jscovering: 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 throughregisterTimeTravelClickHandlers).- Branch indicator at the top of the Time Travel tab — distinct badge styling for
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_stateack frame: 3 new additive fields —branch_id(defaults"main"; new branches allocated asbranch-{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_eventper-event push frame: 2 new additive fields —branch_idand a top-levelcomponentsmirror ofentry.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 existingtime_travel_jumpvalidation and re-render path; backed by a newrestore_component_snapshot()helper inpython/djust/time_travel.py. - New handler
forward_replay: replays a recorded event with optionaloverride_paramsand allocates a fresh branch id when the cursor is not at the buffer tip. Backed by the existingreplay_event()(#1042) plus a newnext_branch_id()allocator. - Live-view init: 2 new instance fields on
LiveView.__init__—_time_travel_branch_id(default"main") and_time_travel_branch_counter(default0). 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, andwhich="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.mdClosing-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 HEADagainst the active state file'sbranch_namefield, catching the silent "wrong-branch commit" failure observed twice in v0.9.0.
- #1185:
Fixed
- v0.9.4 test-infra polish (closes #1188, closes #1189) — three small follow-ups bundled as one PR:
- #1188 🟡 #1: narrowed
vitest.config.jsPattern 2 filter to match only the diagnosedClosing rpc+onUserConsoleLog/onConsoleLogcause from PR #1187. Dropped the broaderstack.includes('view-transitions')disjunct so future genuinely-different failure shapes inview-transitions.test.jscan no longer be silently swallowed. - #1188 🟡 #2: added
gc.collect()before the_wait_for_one-warning absence check intests/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_templatewall-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.
- #1188 🟡 #1: narrowed
Changed
- HVR auto-enabled in DEBUG (no AppConfig.ready() boilerplate required) — djust's own
DjustConfig.ready()now auto-callsenable_hot_reload()wheneverDEBUG=Trueandwatchdogis installed. Existing per-consumerenable_hot_reload()calls keep working unchanged (idempotent viahot_reload_server.is_running()). Opt out viaLIVEVIEW_CONFIG['hot_reload_auto_enable']: Falsefor projects that orchestrate the file watcher externally. Test runs auto-skip viaPYTEST_CURRENT_TESTso pytest sessions don't spawn a watchdog thread per test. Files:python/djust/apps.py(auto-enable call appended toready()),python/djust/config.py(newhot_reload_auto_enable: Truedefault),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 filepython/djust/tests/test_auto_hot_reload.py). Drops the one-lineenable_hot_reload()call fromexamples/demo_project/demo_app/apps.py. Closes the friction observed across downstream consumers (docs.djust.org, djust.org, djustlive) that were either rolling their ownwatchfilesprocess-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.