Skip to content

Enhancement/anywidget#8428

Draft
MarcSkovMadsen wants to merge 14 commits intomainfrom
enhancement/any-widget
Draft

Enhancement/anywidget#8428
MarcSkovMadsen wants to merge 14 commits intomainfrom
enhancement/any-widget

Conversation

@MarcSkovMadsen
Copy link
Copy Markdown
Collaborator

@MarcSkovMadsen MarcSkovMadsen commented Feb 21, 2026

panel-anywidget-poc.mp4

Add native AnyWidget pane

Summary

Adds a new pn.pane.AnyWidget that renders any anywidget.AnyWidget subclass natively
through Panel's ReactiveESM pipeline, bypassing ipywidgets_bokeh entirely. This gives
Panel users access to the entire AnyWidget ecosystem (75+ widgets) with full Param
reactivity (param.watch, pn.bind, .rx).

from ipymolstar import PDBeMolstar
import panel as pn

widget = PDBeMolstar(molecule_id="1LOL")
pane = pn.pane.AnyWidget(widget)
pane.servable()

How it works

  1. Detection: Duck-typing (_esm + traitlets) at priority 0.8, above IPyWidget's 0.6
  2. Trait mapping: Each traitlet is mapped to a typed param.Parameter with
    bidirectional sync and re-entrancy guards
  3. ESM rendering: The widget's _esm/_css are serialized and rendered through a
    dynamically-generated AnyWidgetComponent (ReactiveESM subclass)
  4. Name collision handling: Reserved names (width, height, name, etc.) get a
    w_ prefix; the JS adapter transparently maps them back
  5. Binary transfer: Bytes/Arrow data is base64-encoded in messages; nested bytes use
    a _pnl_bytes marker for recursive encoding/decoding
  6. anywidget protocol: Full model.get/set/on/off/send/save_changes API,
    including experimental.invoke() RPC and generic "change" events

Key features

  • Eager pane.component creation enables reactive programming before first render
  • pane.trait_name_map property for discovering renamed traits
  • Class-level caching of generated component classes (one per widget class)
  • Shadow DOM query patching for jQuery/D3-based widgets
  • msg:custom queuing for handlers registered during initialize() before view exists
  • Compound widget support via _collect_child_widgets and _serialize_child_state
  • Sucrase JSX fallback with error-pattern detection

Files changed

New files

File Lines Description
panel/pane/anywidget.py 1,166 Core pane: detection, trait mapping, sync, binary transfer, class caching
panel/tests/pane/test_anywidget.py 1,840 95 unit tests: trait mapping, serialization, collisions, messaging, buffers, compound widgets
panel/tests/ui/anywidget/ 2,986 14 Playwright UI tests (inline ESM widgets): counter, slider, multi-trait, styled card, canvas draw, toggle theme, leaflet map, todo list, custom events, widget replace, rapid updates, large data, multi widget, nested data
examples/reference/panes/AnyWidget.ipynb 241 Reference guide with inline ESM and third-party examples

Modified files

File Description
panel/models/anywidget_component.ts +878 lines: full anywidget protocol adapter (model API, ESM lifecycle, shadow DOM patching, binary decoding, invoke RPC, msg:custom queuing)
panel/pane/__init__.py Import and export AnyWidget
panel/custom.py Support __css_raw__ for inline CSS injection
panel/io/datamodel.py Handle dynamic Bytes properties and _pnl_bytes nested encoding
panel/io/document.py Binary-safe document patching
panel/models/reactive_esm.ts Expose _render_code hook for AnyWidget adapter
panel/tests/pane/test_base.py Add AnyWidget to SKIP_PANES
pixi.toml Add anywidget dependency to anywidget-examples feature

Test plan

# Unit tests (95 tests, ~2s)
pixi run -e default pytest panel/tests/pane/test_anywidget.py -x -v

# Pane base regression tests (199 tests)
pixi run -e default pytest panel/tests/pane/test_base.py -x -v

# Playwright UI tests (14 tests, requires --ui flag)
pixi run -e default pytest panel/tests/ui/anywidget/ --ui -v -W 'ignore::UserWarning'

# Lint
pixi run lint

Compatibility with third-party widgets

Extensive testing against 48 real-world anywidget packages is maintained in a separate
repository: panel-anywidget-gallery.

The gallery contains:

  • 27 GREEN (fully working): altair, bandsplot, bzvisualizer, chromospyce, codeinput,
    cosmograph, d2widget, graphviz, ipyaladin, ipyclipboard, ipymafs, ipymolstar, ipyscore,
    itables, mapwidget, modraw, mopaint, mosaic, moutils, navio, periodictable, pglite,
    pyobsplot, tesseract, vitessce, weaswidget, wigglystuff
  • 11 YELLOW (caveats): drawdata, ipymario, ipymidi, ipyniivue, pygv, pylifemap,
    pynodewidget, quak, tldraw, vizarr, xarray_repr
  • 10 RED (known incompatible): anymap_ts, cev, higlass, ipydeck, ipyreactplayer,
    jbar, jupyter_scatter, lonboard, rerun, soupernova

This separation keeps the Panel PR focused on the core implementation while the gallery
serves as a comprehensive compatibility test suite with Playwright tests, screenshots,
and upstream issue documentation for each widget.

When to use AnyWidget vs IPyWidget

Widget type Pane
Flat anywidget.AnyWidget subclass pn.pane.AnyWidget
Compound widget with IPY_MODEL_ child refs (lonboard, higlass) pn.pane.IPyWidget + pn.extension("ipywidgets")
Classic ipywidgets.DOMWidget / VBox / HBox pn.pane.IPyWidget + pn.extension("ipywidgets")

Known limitations

Limitation Root cause Workaround
Large ESM bundles (>10MB) WebSocket message size limits Widget should use CDN imports
Compound widget trees (IPY_MODEL_ refs) No widget manager in Panel Use pn.pane.IPyWidget
SharedArrayBuffer (quak, DuckDB-WASM) Requires COOP/COEP headers pn.serve(transforms=[SecurityHeadersTransform])
React-based ESM (ipydeck, ipyreactplayer) Exports React component, not render() Upstream fix needed
WebGL dimension forwarding (jupyter-scatter) 0x0 container at ESM render time Set explicit width/height on pane

MarcSkovMadsen and others added 3 commits February 21, 2026 07:13
Renders anywidget instances natively via ReactiveESM, extracting ESM/CSS
and mapping traitlets to param Parameters with bidirectional sync. Uses
duck-typing detection (priority 0.8 > IPyWidget 0.6) and eager component
creation for immediate param.watch/pn.bind/.rx support.

Includes 20 unit tests, 12 example scripts, upstream issue drafts for
drawdata/wigglystuff/anymap-ts, and GitHub issue + PR descriptions.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@codecov
Copy link
Copy Markdown

codecov bot commented Feb 21, 2026

Codecov Report

❌ Patch coverage is 94.99055% with 53 lines in your changes missing coverage. Please review.
✅ Project coverage is 70.87%. Comparing base (b5981cc) to head (f81073e).

Files with missing lines Patch % Lines
panel/pane/anywidget.py 86.25% 47 Missing ⚠️
panel/tests/pane/test_anywidget.py 99.13% 6 Missing ⚠️

❗ There is a different number of reports uploaded between BASE (b5981cc) and HEAD (f81073e). Click for more details.

HEAD has 44 uploads less than BASE
Flag BASE (b5981cc) HEAD (f81073e)
51 7
Additional details and impacted files
@@             Coverage Diff             @@
##             main    #8428       +/-   ##
===========================================
- Coverage   86.11%   70.87%   -15.24%     
===========================================
  Files         349      350        +1     
  Lines       54841    55897     +1056     
===========================================
- Hits        47227    39618     -7609     
- Misses       7614    16279     +8665     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

MarcSkovMadsen and others added 5 commits February 21, 2026 10:40
Add 14 new example scripts for the most popular anywidgets from the
community gallery: altair, rerun, mosaic, lonboard, jupyter-scatter,
quak, jupyter-tldraw, pyobsplot, vizarr, ipyaladin, higlass, pygv,
vitessce, and cev. Includes workable bidirectional sync demos for
widgets with small ESM bundles and documentation-only examples for
widgets with known limitations (large bundles, binary serialization).

Also adds iteration_plan.md detailing the 4 remaining iterations:
edge cases & hardening, third-party smoke tests, gallery examples,
and documentation.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
- Reorder Instance check after TRAITLET_MAP walk (fixes Set/List/Tuple/Dict)
- Add Enum→Selector, Instance→ClassSelector, Set→List, Union→Parameter mappings
- Convert set defaults to list for param.List compatibility
- Add _resolve_text() helper for ESM/CSS from Path, FileContents, str
- Bounded OrderedDict cache with LRU eviction (max 256)
- BkSpacer for None objects, tightened applies() duck-typing
- Specific TraitError catch + logging for unexpected sync errors
- 39 tests (20 existing + 19 new) all passing

Co-Authored-By: Claude Opus 4.6 <[email protected]>
- Allow underscore-prefixed sync traits (fixes Altair reembed TypeError)
  _vl_selections and _params were filtered out; now only _FRAMEWORK_TRAITS excluded
- Rename third-party examples with ext_ prefix for easy identification
- Remove try/except ImportError wrappers (Panel handles import errors natively)
- Add all third-party anywidget deps to pixi.toml anywidget-examples feature
- Fix ipymario sizing (min_height=200)
- Add test for underscore-prefixed sync traits (40 tests total)

Co-Authored-By: Claude Opus 4.6 <[email protected]>
- Fix cache comment: eviction is LRU (move_to_end on hit), not FIFO
- Update pr.md with current stats (40 tests, ~495 lines, type mapping table)
- Update issue.md with current stats (26 examples, reference notebook)
- Update todo.md to mark completed iterations

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@MarcSkovMadsen MarcSkovMadsen changed the title Enhancement/any widget Enhancement/anywidget Feb 21, 2026
MarcSkovMadsen and others added 2 commits February 21, 2026 15:35
## Issue 1: Mosaic view.model.send is not a function
Mosaic's ESM code expects to access the model adapter via view.model,
but it was only passed as a prop. Now we expose the adapter on the
view object while keeping the Bokeh model accessible internally.

Changes in anywidget_component.ts _render_code():
- Store original Bokeh model before render
- Expose adapter as view.model for widgets using anywidget protocol
- Restore Bokeh model after render via finally() block

## Issue 2: Quak SyntaxError: Unexpected token ','
Quak's bundled ESM module failed to parse through es-module-shims.
Now we use native import() first for bundled URLs, avoiding the
shims parser which rejects certain modern JS syntax patterns.

Changes in reactive_esm.ts recompile():
- Try native import(url) for bundled URLs
- Fall back to importShim only if native import fails
- Skips es-module-shims parsing for pre-bundled code

Both fixes maintain backward compatibility and enable proper rendering
of DuckDB-based widgets (Mosaic, Quak).

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@MarcSkovMadsen
Copy link
Copy Markdown
Collaborator Author

MarcSkovMadsen commented Feb 21, 2026

I will leave this for now. I will wait for your feedback @philippjfr :

  • Is this the right architecture?
  • Is this the right python api?
  • Is it ok to change the anywidget_component.ts implementation to adapt it to an AnyWidget pane? Or should we create a seperate model?

Please explore by running

pixi run -e anywidget panel serve research/anywidget/examples/*.py

Try the altair and ipyaladin examples. They work for me.
Then try the higlass example. It loads with a blank screen and an error in the console.
The try the mosaic example. The scatter and bar buttons work for me. Next step would be to get "Interactive Params (synced from widget)" working. It might be in the examples python code.

Please fix a few issues and report back 😄

@philippjfr
Copy link
Copy Markdown
Member

Looks very promising, but it's a big PR and I've got a long two weeks coming up so can't promise quick review.

@MarcSkovMadsen
Copy link
Copy Markdown
Collaborator Author

MarcSkovMadsen commented Feb 23, 2026

A deep review is not needed. But an ok that this is moving in right direction would be helpful. Note most files are just temporary research files in research folder. They would not go into final PR.

@MarcSkovMadsen
Copy link
Copy Markdown
Collaborator Author

MarcSkovMadsen commented Mar 3, 2026

I've further enhanced this PR - many more anywidgets now working including lonboard which needed a widget manager and anywidgets that needed array buffer support.

I've simplified this PR by moving tests of dependent anywidgets to https://github.com/panel-extensions/panel-anywidget-gallery.

I've updated the initial post above to the current status.

There is still a lot to do:

  • Turn panel-any-widget gallery into a gallery similar to Marimos new Anywidget gallery https://try.anywidget.dev/.
  • Make panel-any-widget gallery a super repository for developing, testing and maintaining panels AnyWidget pane
  • Fix more AnyWidget pane issue
    • handle 10MB websocket limit by chunking to support for example anymap-ts.
    • review, test, fix iterations

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