Skip to content

fix: coalesce comm_msg state updates within a flush cycle#238

Draft
cpsievert wants to merge 3 commits intomainfrom
fix/coalesce-comm-msg
Draft

fix: coalesce comm_msg state updates within a flush cycle#238
cpsievert wants to merge 3 commits intomainfrom
fix/coalesce-comm-msg

Conversation

@cpsievert
Copy link
Copy Markdown
Collaborator

Summary

Fixes #212 — ipyleaflet LayersControl losing track of layers when reactive effects do remove+add within a single flush cycle.

Root cause: When multiple reactive effects modify the same widget (e.g., map.remove(old_layer) then map.add(new_layer)), each trait change sends a separate comm_msg to the client. The intermediate states (e.g., "layers after remove but before add") cause race conditions in ipyleaflet's async JS layer management. The final map rendering was correct, but the LayersControl overlay list would lose entries.

Fix: State-update comm_msg messages (method="update") for the same model are now merged within each flush cycle, sending only the final value of each changed trait. This is equivalent to wrapping every reactive effect in ipywidgets' hold_sync(), but applied automatically at the transport layer. Non-update messages (method="custom") are still sent individually.

Key changes:

  • _comm.py: Added coalescing logic in _publish_msg — first update for a comm_id registers an on_flushed callback; subsequent updates merge their state dict into the pending entry
  • Buffer paths for overridden state keys are properly replaced during merges
  • comm_close and non-update comm_msg messages are unaffected

Test plan

  • 5 new unit tests covering: basic coalescing, cross-trait merging, buffer handling, non-update passthrough, independent coalescing per model
  • New Playwright test (ipyleaflet_layer_update) that verifies LayersControl stays consistent after remove+add cycles
  • All 90 unit tests pass
  • All existing Playwright tests pass (marker click, geojson hover, rerender cleanup)
  • make check passes (format + types + unit tests)

🤖 Generated with Claude Code

cpsievert and others added 3 commits April 13, 2026 17:05
Pure style change — no functional difference. This separates the
indentation change from the upcoming API signature update, keeping
that diff focused on the meaningful changes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Upgrade the HTML manager dependency to v1.0.14 (ipywidgets 8.x line).

Key changes:
- Adapt display_view() / create_view() call signatures to new API
- Add get_model() override with async waiter to handle out-of-order
  comm_open messages (parent model referencing not-yet-registered children)
- Replace wildcard `.forward-fill-potential > *` CSS selector with
  `.forward-fill-potential > .lm-Widget` to avoid sizing transient
  tooltip siblings
- Update static libembed-amd.js bundle and font assets
- Add Playwright regression test for ipyleaflet GeoJSON hover

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When multiple reactive effects modify the same widget in a single flush
cycle (e.g., map.remove(old_layer) then map.add(new_layer)), each trait
change previously sent a separate comm_msg to the client. These rapid
intermediate states caused race conditions in widget JS views —
ipyleaflet's LayersControl would lose track of layers even though the
final map rendering was correct.

Now, state-update comm_msg messages (method="update") for the same model
are merged within each flush cycle, sending only the final value of each
changed trait. This is equivalent to wrapping every reactive effect in
ipywidgets' hold_sync(), but applied automatically at the transport
layer. Non-update messages (method="custom") are still sent individually.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@cpsievert cpsievert marked this pull request as draft April 13, 2026 23:07
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.

Inconsistent Layer Rendering and Unexpected Reactive Behavior in ipyleaflet within shinywidgets

1 participant