Skip to content

fix(reflex): stream on_view_change during gestures with a latest-wins throttle#201

Merged
masenf merged 1 commit into
mainfrom
fix/reflex-view-change-throttle
Jul 22, 2026
Merged

fix(reflex): stream on_view_change during gestures with a latest-wins throttle#201
masenf merged 1 commit into
mainfrom
fix/reflex-view-change-throttle

Conversation

@FarhanAliRaza

Copy link
Copy Markdown
Contributor

Problem

PR #113 replaced the Reflex wrapper's immediate on_view_change dispatch with a 200 ms trailing-edge debounce. The render client emits a view_change per animation frame during a pan/zoom, so every frame reset the timer — the handler could only fire once the gesture went quiet. An on_view_change-computed detail chart (showcase §4) froze during the interaction and only re-rendered ~a second after it ended, where it previously tracked the gesture in real time.

Fix

dispatchView in XYChart.jsx now uses a leading + trailing, latest-wins throttle (VIEW_THROTTLE_MS = 120, matching the hover throttle):

  • the first event of a gesture dispatches immediately,
  • updates stream at most once per 120 ms while the gesture is in progress,
  • a trailing flush guarantees the resting viewport always lands as the last event.

The envelope's phase field now reflects the client's gesture phase — "update" mid-gesture, "final" at rest — so handlers that only care about settled views can filter on it. linked/republish source suppression and unmount timer cleanup are unchanged. Handlers written against the old contract keep working: they simply receive intermediate update events before the same final one.

Verification

  • Drove the running showcase app headlessly (repo CDP driver): 24 wheel-zoom steps at 140 ms spacing — a continuous gesture the old debounce can never fire inside — produced 23 mid-gesture Reflex state updates, detail histogram tracking each step, with the exact resting view (phase: "final") arriving last.
  • scripts/reflex_ws_smoke.py mount/shared-websocket/pixel assertions pass. Its §16 density-drill step times out, but it fails identically with the unmodified main XYChart.jsx swapped into the served app — pre-existing on main, unrelated to this change (tracked separately).
  • pre-commit run --all-files, ruff check, ruff format --check all green; the wrapper contract test assertions replayed against the modified JSX all hold.

Spec

Per the spec-first contract: spec/design/reflex-integration.md now documents the throttle + phase stream (and drops a stale line-number cite), ViewChangeEvent typing/docstring, python/reflex-xy/README.md, the demo comment, and tests/reflex_adapter/test_component.py are updated to pin the new behavior.

… throttle

PR #113 replaced the wrapper's immediate on_view_change dispatch with a
200 ms trailing-edge debounce. A continuous pan/zoom emits a view_change
per animation frame, so the timer reset on every frame and the handler
could only fire after the gesture went quiet — an on_view_change-computed
detail chart froze until the interaction ended instead of tracking it
live (demo section 4).

Replace the debounce with a leading+trailing latest-wins throttle
(VIEW_THROTTLE_MS = 120, matching hover): the first event of a gesture
dispatches immediately, updates stream at most once per window while it
is in progress, and the trailing flush guarantees the resting viewport
always lands last. The envelope's phase field now reflects the client's
gesture phase — "update" mid-gesture, "final" at rest — so handlers
that only want settled views can filter on it; linked/republish
suppression and unmount cleanup are unchanged.

Verified against the running showcase app with a CDP probe: 24 wheel
steps at 140 ms spacing produced 23 mid-gesture Reflex state updates
(the old debounce produces none), with the exact resting view arriving
as the final event.

Spec (reflex-integration.md), ViewChangeEvent typing/docstring, README,
demo comment, and the wrapper contract test updated to pin the new
behavior.
@codspeed-hq

codspeed-hq Bot commented Jul 22, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 97 untouched benchmarks
⏩ 1 skipped benchmark1


Comparing fix/reflex-view-change-throttle (44c23de) with main (2a7d898)

Open in CodSpeed

Footnotes

  1. 1 benchmark was skipped, so the baseline result was used instead. If it was deleted from the codebase, click here and archive it to remove it from the performance reports.

@greptile-apps

greptile-apps Bot commented Jul 22, 2026

Copy link
Copy Markdown

Greptile Summary

This PR streams Reflex viewport changes during active gestures. The main changes are:

  • Replaces the view-change debounce with a 120 ms latest-wins throttle.
  • Emits immediate and trailing viewport updates with update and final phases.
  • Updates the public event type, documentation, example, and contract assertion.

Confidence Score: 5/5

This looks safe to merge.

No blocking issues found in the changed code. The client terminal phase is mapped to final before Reflex handlers receive it. Pending events are replaced with the latest viewport and flushed in order.

Unmount cleanup clears the timer and pending event.

T-Rex T-Rex Logs

What T-Rex did

  • Observed the pre-change debounce behavior, where the prior implementation emitted zero update-phase events.
  • During the wheel interaction, verified that the new implementation emitted update-phase websocket events and noted that the live detail chart showed 17,673 visible points at step 12.
  • After the interaction, confirmed the final viewport settled at [47.29270439603073, 48.707044219295895], the detail chart settled at 1,783 points, and the last websocket event had phase final.

View all artifacts

T-Rex Ran code and verified through T-Rex

Important Files Changed

Filename Overview
python/reflex-xy/reflex_xy/assets/XYChart.jsx Replaces trailing debounce behavior with a leading-and-trailing latest-wins throttle while retaining source suppression and unmount cleanup.
python/reflex-xy/reflex_xy/events.py Expands the viewport event phase type and documentation to cover streamed updates and settled final events.
tests/reflex_adapter/test_component.py Updates the static contract assertion for the renamed throttle constant but does not exercise timer ordering.
spec/design/reflex-integration.md Documents the new throttle interval, phase mapping, and final-event guarantee.
python/reflex-xy/README.md Updates the user-facing description of viewport event delivery.
examples/reflex/xy_reflex_demo/xy_reflex_demo.py Updates the example comment to describe live throttled viewport updates.

Reviews (1): Last reviewed commit: "fix(reflex): stream on_view_change durin..." | Re-trigger Greptile

@masenf masenf left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do the reflex built-in throttle and debounce event actions not work with this event? if the event debouncing is only in the reflex wrapper, do we need to re-implement this functionality in a way that is not configurable by the developer?

@masenf
masenf self-requested a review July 22, 2026 19:01
@masenf
masenf merged commit a9926a3 into main Jul 22, 2026
29 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants