Changelog
Mirrored from the upstream CHANGELOG.md. Last synced: 2026-05-21. Resynced on each Weaverlet release.
0.3.1 (2026-04-30)
Patch release. Fixes the weaverlet[jupyter] extra and WeaverletApp(jupyter_mode=True) path, both of which were broken on arrival in 0.3.0:
pyproject.tomldeclaredjupyter = ["dash[jupyter]>=4.1"], but Dash 4.x has nojupyterextra (declared extras:async,ci,dev,testing,celery,diskcache,compress,cloud,ag-grid). Installingweaverlet[jupyter]was a silent no-op.WeaverletApp(jupyter_mode=True)didfrom jupyter_dash import JupyterDash, but the standalonejupyter_dashpackage was archived in 2022 and Dash 4 absorbed Jupyter integration into core (asdash.jupyter_dash, exposed as a singleton instance). The import always failed, even after installing the (non-existent) extra.
Migration
Dash 4 ships built-in Jupyter support that doesn't need a separate JupyterDash class anymore. Construct WeaverletApp normally and pass jupyter_mode='inline' (or 'tab' / 'external') to .app.run():
wapp = WeaverletApp(root_component=...)
wapp.app.run(jupyter_mode='inline')
No extras required.
Breaking changes (vs 0.3.0)
weaverlet[jupyter]extra removed. It never installed anything useful;pip install weaverlet[jupyter]will now error out, but the migration path above doesn't need any extra at all.WeaverletApp(jupyter_mode=True)raisesTypeErrorwith a migration message pointing atapp.run(jupyter_mode=...). The defaultjupyter_mode=False(or omitting it) is unchanged.
0.3.0 (2026-04-29)
First major refresh since 0.2.0 (2024). Modernizes the dependency stack and adds WebGL-safe routing while keeping the public API backwards-compatible where it matters.
Breaking changes
- Drop Python < 3.12. The package no longer installs on older interpreters.
- Drop hard pins on Flask, werkzeug, dash, dash_extensions, jupyter-dash. The triple-pin in 0.2.0 made Weaverlet unresolvable in any modern environment. Dependencies are now declared as open ranges (
dash>=4.1.0,<5.0.0,dash-extensions>=1.0.0,<3.0.0); Flask/werkzeug are resolved transitively. jupyter-dashis now optional. Install withpip install weaverlet[jupyter]if you passWeaverletApp(..., jupyter_mode=True). Otherwise importingweaverletno longer pulls injupyter-dash.- Build system switched from
setup.py(setuptools) topyproject.toml(PEP 621 / hatchling). Existing install commands (pip install weaverlet,pip install -e .) continue to work. Identifierid format changed fromhex_id-name-attrtoClassName_attr_hexInstanceId. IDs are opaque and only used symbolically asself.<identifier>, so user code is unaffected. But any code that compared the string form of an id will break.get_page_root()removed. The old per-router tree walk that assigned page roots to each route's children was unused and added complexity. If you relied on it, file an issue.StoreComponentrequires the dash-extensions multiplexer/group to register multiple callbacks against the sameOutput. This worked in 0.2 via dash-extensions 0.0.65; in 1.x/2.x it is provided byDashProxy(which Weaverlet now uses by default). No action required if you useWeaverletApp; raw Dash users need to pick upDashProxythemselves.
New features
SimpleRouterComponent(keep_mounted=True, preserve_path=...)for WebGL-safe multipage. Whenkeep_mounted=True, every route is rendered at startup and the router togglesstyleinstead of unmounting (preserving the canvas state ofdash-sylvereye(PixiJS),dash-leaflet, and Plotly WebGL backends across navigation. Thepreserve_pathroute usesvisibility:hidden(canvas survives even WebGL-strict libraries); other routes usedisplay:none. Default iskeep_mounted=False(unchanged behavior) drop-in replacement for 0.2 routers).AuthRouterComponent(keep_mounted=True, preserve_path=...): same state-preservation feature for the auth-gated router. Routes are pre-rendered with placeholder values (user=None,protected_route=""); a separate callback re-renders the login wrapper's children with the actualprotected_routewhenever an unauthenticated visit hits a login-required route, so the legacy login-redirect pattern keeps working in keep_mounted mode. Apps that need the liveuservalue should readflask.session[user_session_key]from inside their callbacks rather than capturinguserfromget_layout.- Aliasing under
keep_mounted=True. Two paths mapping to the same component instance (e.g./and/homeboth pointing to onepage_a) no longer raisesDuplicateIdError; both paths share a single canonical wrapper. The same generalization underlies the shared-not_foundhandling. - Dynamic 404. When
keep_mounted=Trueand the URL doesn't match any route, the not-found wrapper's children are re-rendered with the live pathname. So a template that interpolates the current pathname displays the actual URL the user typed instead of the empty-string placeholder used at startup. - WebGL-safe shared
not_found. Ifnot_found_page_componentis the same instance as one of the routes, the router reuses that route's wrapper instead of mounting a duplicate (no moreDuplicateIdError). WeaverletAppusesdash_extensions.enrich.DashProxyunder the hood, which keepsdash_extensions.javascript.assign()functions registered inwindow.dashExtensions.default.*. This unblocksdash-leafletstyle handlers and similar uses without any extra wiring.assets_folderauto-resolves to the directory of the user's main script, sodashExtensions_default.js(and any user-authored asset) is served by Dash without configuration. After resolving,WeaverletAppalso re-dumps thedash_extensions.javascript.assign()namespace into that directory. Important becauseassign()writes to a CWD-relative path by default, which doesn't match Dash's served path when the script is run from a different working directory (common withuv run, IDEs, and test runners). Without this re-dump,dash-leafletstyle handlers and similar uses fail with "No match for function0". Override withWeaverletApp(..., assets_folder="...")if needed.WeaverletAppexposestitle,external_stylesheets,assets_folderas named keyword arguments. Other Dash kwargs continue to flow through via**dash_kwargs.- Routed
get_layout()is permissive about its signature. Components no longer have to declareget_layout(self, pathname, hash, href, search)to be routable. Declare just the kwargs you need ((self),(self, pathname),(self, pathname, hash, href, search, user), etc.) and the router will pass only what your signature accepts. Implemented viaweaverlet.base._call_with_router_kwargs. weaverlet.__version__exposes the version string.- Top-level re-exports:
from weaverlet import WeaverletComponent, Identifier, WeaverletApp, SimpleRouterComponent, ...now works directly, no need to drill intoweaverlet.base/weaverlet.components.
Internal
- Component children are now discovered by introspecting
__dict__on each walk (with cycle detection viavisited), instead of pre-computing_childrenonce and walking it four times. TheComponentsList,ComponentsDict, andComponentsOrderedDictABCs are still recognized for collection-style children. - All component modules migrated from
import dash_html_components as html/import dash_core_components as dcctofrom dash import html, dcc. ServersideOutput(dash-extensions 0.0.65) is gone; the helperServersideSignalOutput()now returns a plainOutputand callers wrap the return value withServerside(...): the dash-extensions 1.x/2.x pattern.Serversideis re-exported fromweaverlet.DetatchedComponentRefrenamed toDetachedComponentRef(typo fix); the original name is preserved as an alias.
Compat
The following public names continue to import and behave the same way as in 0.2.0: WeaverletComponent, Identifier, WeaverletApp, RouterComponent, SimpleRouterComponent, AuthRouterComponent, StoreComponent, StoreComponentOp, SignalComponent, DivSignalComponent, EmptyLayoutComponent, RedirectComponent, WeaverletException, SignalInput, SignalOutput, SignalTrigger, SignalState, SignalGroup, ServersideSignalOutput, ComponentsDict, ComponentsList, ComponentsOrderedDict, DetatchedComponentRef, DEFAULT_COMPONENT_NAME.