Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
252 commits
Select commit Hold shift + click to select a range
abe76c7
Merge branch 'dev' into dev
T4rk1n Mar 9, 2026
7c2329a
Merge branch 'dev' into dev
camdecoster Mar 26, 2026
60721ba
Feature: add partial pattern matching (partial=True) for callbacks
i-murray May 7, 2026
eb82fe1
Merge branch 'dev' into feature/partial-pattern-matching
i-murray May 7, 2026
b2df881
Fix offloadPatterns return value and isMultiValued call
i-murray May 7, 2026
2298888
Add State/Output integration tests; align naming with codebase conven…
i-murray May 8, 2026
f26d153
Rename _keys/keyStr to idKeys/idKeyStr in dependencies_ts.ts to match…
i-murray May 31, 2026
6e2a424
Merge branch 'dev' into feature/partial-pattern-matching
i-murray May 31, 2026
700c726
Support React 19
T4rk1n Jul 6, 2026
0548848
Make the jsx-runtime external backward compatible with older Dash
T4rk1n Jul 8, 2026
78a5736
Add filepath to extract-meta errors
T4rk1n Jul 10, 2026
f8f4e7c
improved extract-meta errors paths
T4rk1n Jul 10, 2026
a36444c
Fix error exposed by MCP server when chrome not available for kaleido
KoolADE85 Jul 22, 2026
1a8e356
Merge branch 'dev' into support-react-19
T4rk1n Jul 23, 2026
ca027f0
Merge pull request #3915 from plotly/bugfix/mcp-kaleido-error
KoolADE85 Jul 23, 2026
f02e3cb
Merge branch 'dev' into support-react-19
T4rk1n Jul 23, 2026
24ee225
Share the jsx-runtime external across webpack configs
T4rk1n Jul 23, 2026
e5d9e32
Merge pull request #3880 from plotly/support-react-19
T4rk1n Jul 23, 2026
ebbc084
Merge branch 'dev' into improve-extract-meta
T4rk1n Jul 23, 2026
6ea286e
Merge pull request #3895 from plotly/improve-extract-meta
T4rk1n Jul 24, 2026
dbddfe6
Fix multiple file drag-and-drop in dcc.Upload
AnnMarieW Jul 24, 2026
0d912b7
added changelog
AnnMarieW Jul 28, 2026
2ef4b25
Merge pull request #3916 from AnnMarieW/fix-upload-drag-drop
KoolADE85 Jul 28, 2026
d63253e
Fix number input spinner when only min is set
AnnMarieW Jul 28, 2026
c18e7fb
lint & changelog
AnnMarieW Jul 28, 2026
d8ed81f
Replace deprecated onKeyPress with onKeyDown
AnnMarieW Jul 28, 2026
0aeec68
Update components/dash-core-components/tests/integration/input/test_n…
AnnMarieW Jul 28, 2026
4302682
Added options to compress callback request body
datenzauberai Jul 29, 2026
3d3a9f7
Merge pull request #3922 from AnnMarieW/fix-input
T4rk1n Jul 29, 2026
2a72bf8
🩹 Fix sonar issues (and use more efficient Base64 encoder)
datenzauberai Jul 29, 2026
b533cec
Test on all backends (flask, quart, fastapi)
datenzauberai Jul 30, 2026
142121f
Set default threshold to 5 kB
datenzauberai Jul 30, 2026
0e812bc
Send as binary instead of Base64 encoded.
datenzauberai Jul 30, 2026
8cfca43
Update Dash HTML elements & attributes.
T4rk1n Aug 1, 2026
f3d1ec1
Use the proxied url as the Jupyter server url
Sanjays2402 Aug 2, 2026
3a46345
Merge pull request #3936 from Sanjays2402/fix/jupyter-dash-proxy
T4rk1n Aug 3, 2026
8fd588a
Use Compression Streams API instead of fflate
datenzauberai Aug 3, 2026
8526577
Decompress with a max_size to protect against zip bomb attacks
datenzauberai Aug 3, 2026
f45f6f6
Open FastAPI/Quart websocket only when a websocket callback needs it
T4rk1n Aug 4, 2026
c956234
Backfill PR and issue links in changelog entry
T4rk1n Aug 4, 2026
fc4ca45
Fix testing runner backend detection
cyphercodes Aug 6, 2026
dc33b46
Merge branch 'dev' into create-pull-request/patch
T4rk1n Aug 6, 2026
7ea5f92
Merge pull request #3762 from plotly/create-pull-request/patch
T4rk1n Aug 6, 2026
3d09e61
Merge branch 'dev' into fix/ws-always-on
T4rk1n Aug 6, 2026
8ee87c5
Merge pull request #3941 from plotly/fix/ws-always-on
T4rk1n Aug 7, 2026
74dbcd0
Add backend-agnostic shared storage (state manager + pub/sub)
T4rk1n Jul 30, 2026
324e619
End async subscriptions cleanly when the loop is shutting down
T4rk1n Jul 30, 2026
b1eb519
CHANGELOG: fill in PR number for shared storage entry
T4rk1n Jul 31, 2026
c51183c
Fix stale shared-storage comments (drop plan/pickle/wire wording)
T4rk1n Jul 31, 2026
b136598
Document shared storage in .ai/ARCHITECTURE.md
T4rk1n Jul 31, 2026
6bd42f9
Add Diskcache and Redis shared-storage backends
T4rk1n Jul 31, 2026
8f35ff1
Document the out-of-tree shared-storage backend seam
T4rk1n Jul 31, 2026
ab4a18b
Add dash[redis] extra for RedisSharedStorage
T4rk1n Jul 31, 2026
814a7ea
Fix mypy errors in shared-storage typing compliance
T4rk1n Jul 31, 2026
2cc9cd5
Expand shared-storage app integration tests across backends
T4rk1n Jul 31, 2026
38edb0e
Use dash_duo browser tests for shared-storage app integration
T4rk1n Jul 31, 2026
f2efcc7
Drop JSON codec fallback; msgspec is a hard dependency
T4rk1n Aug 3, 2026
adecacf
Lower default shared-storage buffer to 32
T4rk1n Aug 3, 2026
c695635
Add optional TTL to shared-storage key/value set
T4rk1n Aug 3, 2026
bd327e1
Add persistence modes to LocalSharedStorage
T4rk1n Aug 3, 2026
0df8e2b
Restructure shared-storage changelog entry into a sublist
T4rk1n Aug 3, 2026
42c2b89
Fix mypy int|None type error in ChunkedStore.put_batch flush loop
T4rk1n Aug 5, 2026
4b85ef9
Catch msgspec DecodeError by name when reading corrupt store files
T4rk1n Aug 5, 2026
fb1e5a0
Record operations applied by Patch()
Aaron-Wrote-This Jul 20, 2026
c4d4458
Patch() no longer reruns initial cbs for existing MATCH/ALL elements
Aaron-Wrote-This Jul 26, 2026
494d5ff
Fix persisted values being wiped from existing components during Patch()
Aaron-Wrote-This Jul 27, 2026
ec3eeb0
Prefer using optional chaining in patchAnalysis
Aaron-Wrote-This Aug 4, 2026
c9f9a24
Consolidate clientside and serverside patch apply logic
Aaron-Wrote-This Aug 4, 2026
c0ec8b6
Fix patch asymmetry when an Input is patched but the Output is unchanged
Aaron-Wrote-This Aug 8, 2026
082a7b5
Make plotly-cloud a default install dependency
T4rk1n Aug 11, 2026
a8bbf98
Merge pull request #3938 from Aaron-Wrote-This/bugfix/patch_reruns_in…
T4rk1n Aug 12, 2026
1e7cedd
Formatting
datenzauberai Aug 12, 2026
a43f50d
Avoid invocation that could trigger unwanted exception
datenzauberai Aug 12, 2026
f548d6e
Added change log entry.
datenzauberai Aug 12, 2026
2beeaa7
Formatting
datenzauberai Aug 12, 2026
4a9178a
Merge branch 'dev' into deps/plotly-cloud-default-install
T4rk1n Aug 12, 2026
ce29176
Use gzip instead of zlib in unit tests (Python 3.9 would fail otherwise)
datenzauberai Aug 13, 2026
df44578
Merge branch 'dev' into feature/partial-pattern-matching
i-murray Aug 13, 2026
9db36ac
Merge branch 'dev' into compress_payload
T4rk1n Aug 13, 2026
c06fbe7
Merge pull request #3925 from datenzauberai/compress_payload
T4rk1n Aug 13, 2026
9b74c34
Rename partial to partial_pattern
i-murray Aug 13, 2026
1c8e971
Merge branch 'dev' into deps/plotly-cloud-default-install
T4rk1n Aug 18, 2026
ad66355
Merge branch 'dev' into fix-3942-runner-backend-detection
T4rk1n Aug 18, 2026
c85b9b8
Merge branch 'dev' into feature/partial-pattern-matching
T4rk1n Aug 18, 2026
e9a7a7d
chore: Remove CODEOWNERS file
camdecoster Aug 18, 2026
e88f144
Merge pull request #3953 from plotly/cam/remove-codeowners-file
camdecoster Aug 18, 2026
96728ab
Merge branch 'dev' into deps/plotly-cloud-default-install
T4rk1n Aug 18, 2026
e99dd68
Unpin selenium to fix CI flakiness against current Chrome
T4rk1n Aug 18, 2026
1845c32
Migrate testing helpers off selenium APIs removed in 4.3+
T4rk1n Aug 18, 2026
982f2a3
Address runner backend review feedback
cyphercodes Aug 19, 2026
bec3549
Fix ActionChains offsets for selenium 4.3+ center-origin change
T4rk1n Aug 19, 2026
8722853
Read selenium TimeoutException message via .msg, not .args[0]
T4rk1n Aug 19, 2026
a5a474f
Stop Xvfb from hanging the Setup virtual display CI step
T4rk1n Aug 19, 2026
a96074b
Drop apt-get from Setup virtual display; Xvfb is preinstalled
T4rk1n Aug 19, 2026
bdf88c5
Optimize partial pattern callback lookups
i-murray Aug 19, 2026
d8812bf
Test partial pattern indexes and watched-key fast path
i-murray Aug 19, 2026
28873e0
Fix flaky callback-count tests with deterministic keystroke gating
T4rk1n Aug 19, 2026
f61ef47
Fix uncaught partial MATCH output key-order issue
i-murray Aug 19, 2026
3944e44
Merge branch 'dev' into dev
T4rk1n Aug 19, 2026
057eacb
Prevent hung tests from wedging CI: bound teardown join + per-test ti…
T4rk1n Aug 19, 2026
3d5ef08
Split partial pattern guards by input and output to avoid unnecessary…
i-murray Aug 19, 2026
35129c2
update deps
T4rk1n Aug 20, 2026
9f5b6d6
extract setup-virtual-display
T4rk1n Aug 20, 2026
cf32d18
Fix stale-element flake in tdrp004 by polling derived-prop cells
T4rk1n Aug 20, 2026
7090bd8
Fix flaky grbs007 clickData race; mark dvcv003 flaky
T4rk1n Aug 20, 2026
3dbf7fb
Fix rdcap003 race: poll for option re-render after click
T4rk1n Aug 20, 2026
2cbf447
Fix races in msmh003 and msps001 DCC tests
T4rk1n Aug 20, 2026
43ca43b
Trigger clean CI run for Percy snapshot upload
T4rk1n Aug 20, 2026
89ffecd
Fix arb008 stale-element-on-click race
T4rk1n Aug 20, 2026
e7cad53
Update CHANGELOG.md
T4rk1n Aug 21, 2026
ee1f4e0
Fix races in dveh002 (click count) and msps001 (dropdown intercept)
T4rk1n Aug 21, 2026
d0b6cbf
Harden empt001 clear and msps001 dropdownsingle selection
T4rk1n Aug 21, 2026
8e86f96
Force-exit background/async CI steps after pytest to stop shutdown hangs
T4rk1n Aug 21, 2026
36e699f
Fix ddso002 dropped-keystroke race in dropdown keyboard nav
T4rk1n Aug 21, 2026
fe9a029
Revert ddso002 key-delivery change (mark flaky); gate rdmo002 keystrokes
T4rk1n Aug 21, 2026
b5287df
Fix rdps008 read-after-click race on delete-column toggle
T4rk1n Aug 21, 2026
918ba3c
CI stability check (build 1 of 2)
T4rk1n Aug 21, 2026
0141dc1
Mark dropdown a11y keyboard-nav tests flaky (focus-timing)
T4rk1n Aug 21, 2026
481649c
Skip occupied ports when starting the threaded test server
T4rk1n Aug 21, 2026
388f934
Merge pull request #3955 from plotly/fix/unpin-selenium
T4rk1n Aug 21, 2026
a63cc21
Merge branch 'dev' into feature/partial-pattern-matching
i-murray Aug 22, 2026
0b6c571
Bump fast-uri from 3.0.6 to 3.1.6 in /@plotly/dash-component-plugins
dependabot[bot] Aug 23, 2026
7f7522f
Merge pull request #3958 from plotly/dependabot/npm_and_yarn/plotly/d…
T4rk1n Aug 24, 2026
89c27c2
Merge branch 'dev' into fix-3942-runner-backend-detection
T4rk1n Aug 24, 2026
3f74018
Merge branch 'dev' into deps/plotly-cloud-default-install
T4rk1n Aug 24, 2026
3d07313
Merge branch 'dev' into feature/partial-pattern-matching
T4rk1n Aug 24, 2026
2355a23
Merge branch 'dev' into dev
T4rk1n Aug 24, 2026
f6e4b0f
Merge pull request #3621 from antonymilne/dev
T4rk1n Aug 24, 2026
39dd0c0
Merge branch 'dev' into fix-3942-runner-backend-detection
T4rk1n Aug 24, 2026
5b8c688
Merge pull request #3944 from cyphercodes/fix-3942-runner-backend-det…
T4rk1n Aug 24, 2026
758c879
Merge branch 'dev' into feature/partial-pattern-matching
T4rk1n Aug 24, 2026
91dabfc
Update CHANGELOG.md
i-murray Aug 25, 2026
7782741
Merge pull request #3765 from i-murray/feature/partial-pattern-matching
T4rk1n Aug 25, 2026
4adb289
rewrite dcc.Markdown using typescript and updated markdown libs
KoolADE85 Aug 25, 2026
6be8b5c
cleanup
KoolADE85 Aug 25, 2026
ea10d93
Include xPixel and yPixel in hoverdata and clickdata
AnnMarieW Aug 27, 2026
a0b7cd9
Merge branch 'dev' into deps/plotly-cloud-default-install
T4rk1n Aug 27, 2026
68c28fe
Update CHANGELOG.md
T4rk1n Aug 27, 2026
9a0427e
Make callback-validation error assertions tolerate the injected plotl…
T4rk1n Aug 27, 2026
574f67c
fix test
AnnMarieW Aug 28, 2026
971fef5
Added changelog
AnnMarieW Aug 28, 2026
c947811
Refresh CLAUDE.md: trim stale sections, add writing style and QA gate
T4rk1n Aug 28, 2026
c2774d1
use callback/memo for renderMarkdown function; name some default exports
KoolADE85 Aug 28, 2026
fd29bb7
Merge branch 'dev' into bugfix/3951-markdown-rewrite
KoolADE85 Aug 28, 2026
1dd02e1
Merge pull request #3963 from plotly/docs/claude-md-refresh
T4rk1n Aug 28, 2026
2a1afb2
Merge branch 'dev' into bugfix/3951-markdown-rewrite
KoolADE85 Aug 28, 2026
5bad579
Merge pull request #3960 from plotly/bugfix/3951-markdown-rewrite
KoolADE85 Aug 28, 2026
3127878
added remark-gfm plugin
AnnMarieW Aug 29, 2026
42910bf
Merge branch 'dev' into update-clickanywhere-hoveranywhere
T4rk1n Aug 31, 2026
c2fc14f
Merge pull request #3961 from AnnMarieW/update-clickanywhere-hoverany…
T4rk1n Aug 31, 2026
4e7ef32
added test
AnnMarieW Aug 31, 2026
cfa1c5d
Bump the pip-dependencies group across 1 directory with 12 updates
dependabot[bot] Aug 31, 2026
8e9319d
Merge branch 'dev' into fix-dcc-Markdown-tables
AnnMarieW Aug 31, 2026
5c3a66e
Merge pull request #3965 from AnnMarieW/fix-dcc-Markdown-tables
KoolADE85 Aug 31, 2026
3bba0e6
Only remount parent-passed components when their identity changed
T4rk1n Jul 7, 2026
84bc271
fix component as props on updates
T4rk1n Jul 15, 2026
c9e5dcd
Add dash.remount to force-reset a component from a callback
T4rk1n Jul 15, 2026
52ba248
Keep initial state set on mount from being wiped on first render (#3929)
T4rk1n Aug 10, 2026
7d96f60
Fix O(total) Patch().append() slowdown into a growing container
T4rk1n Aug 12, 2026
84e86c3
Apply prettier formatting to renderer append-perf fix
T4rk1n Aug 12, 2026
7ee04de
Extend Patch append perf fix to nested and non-tail locations
T4rk1n Aug 18, 2026
acf4371
Update CHANGELOG.md
T4rk1n Aug 28, 2026
3d1d158
Remove verbose comments flagged in review
T4rk1n Aug 28, 2026
9ba6c6c
Add platform performance benchmark harness, CI job, and profiling docs
T4rk1n Aug 18, 2026
6fd5ae0
Make pattern-matching (MATCH/ALL) path resolution O(1) instead of O(n)
T4rk1n Aug 18, 2026
b50967f
Cut ramda currying overhead in the per-node layout crawl
T4rk1n Aug 18, 2026
250e591
Make the benchmark baseline gate machine-independent
T4rk1n Aug 27, 2026
4f50579
Poll for settled focus in DatePickerSingle a11y keyboard tests
T4rk1n Aug 28, 2026
f9c8d1c
Skip baseline-ratio gate for sub-millisecond benchmark metrics
T4rk1n Aug 28, 2026
9003cd2
Add workflow_dispatch to regenerate the benchmark baseline on CI
T4rk1n Aug 28, 2026
f055ab7
Merge pull request #3954 from plotly/perf/benchmark-harness
T4rk1n Aug 31, 2026
8bb72af
Pin black and pylint to last Python 3.9-compatible versions
T4rk1n Aug 31, 2026
00221c7
Merge origin/dev into feat/shared_storage
T4rk1n Aug 31, 2026
f4d47ac
Revert black and pylint to dev versions
T4rk1n Aug 31, 2026
fb9a6da
Remove unused global statements flagged by flake8 F824
T4rk1n Sep 1, 2026
5630435
Regenerate benchmark baseline on CI hardware
github-actions[bot] Sep 1, 2026
407ac3a
Merge pull request #3967 from plotly/benchmarks/regenerate-baseline-3…
T4rk1n Sep 1, 2026
6308b12
Merge remote-tracking branch 'origin/dev' into feat/shared_storage
T4rk1n Sep 1, 2026
ce7530c
Add streaming callbacks (@callback generator functions)
T4rk1n Jul 31, 2026
e33a79b
Multiplex HTTP streams over shared storage
T4rk1n Jul 31, 2026
6ff0621
Streaming: verify connections, stop on shutdown, reset stale cursor
T4rk1n Aug 31, 2026
5d17895
trigger PR recheck
T4rk1n Sep 1, 2026
55c2891
Fix FastAPI startup crash: replace removed add_event_handler
T4rk1n Sep 1, 2026
7e491ce
Fix Ctrl+C hang with active streaming downlink on Flask
T4rk1n Sep 1, 2026
bcbbbca
Poll at 0.5s in keepalive generator so shutdown is noticed quickly
T4rk1n Sep 1, 2026
f01fbbf
Move local imports to top level, add shutdown tests
T4rk1n Sep 1, 2026
0366d91
Check shutdown flag in async keepalive generator too
T4rk1n Sep 1, 2026
d0ee3d7
Move stream shutdown signal handler to _stream_hub
T4rk1n Sep 1, 2026
698adf1
Handle KeyboardInterrupt in FastAPI subprocess runner
T4rk1n Sep 1, 2026
8e85ea1
Call shutdown_active_streams in Quart signal handler
T4rk1n Sep 2, 2026
5d09728
Fall back to inline NDJSON when multiplexed uplink returns 403
T4rk1n Sep 2, 2026
ba62035
Suppress traceback on repeated Ctrl+C during FastAPI shutdown
T4rk1n Sep 2, 2026
9fa8ce4
Treat mid-stream connection drop as graceful when frames were received
T4rk1n Sep 2, 2026
150598e
Clear _shutdown flag on server startup across all backends
T4rk1n Sep 2, 2026
32f9542
Fix Ctrl+C and restart handling for streaming callbacks on FastAPI
T4rk1n Sep 2, 2026
8c89aab
Merge branch 'dev' into dependabot/pip/pip-dependencies-ed78e3790b
T4rk1n Sep 2, 2026
de61940
chore: Bump fast-uri for security updates
camdecoster Sep 3, 2026
279dc41
Merge pull request #3970 from plotly/cam/update-fast-uri
camdecoster Sep 4, 2026
908942f
Bump browserslist in /@plotly/dash-component-plugins
dependabot[bot] Sep 4, 2026
10ead7a
Merge pull request #3969 from plotly/dependabot/npm_and_yarn/plotly/d…
T4rk1n Sep 4, 2026
02b8f2b
Merge branch 'dev' into dependabot/pip/pip-dependencies-ed78e3790b
T4rk1n Sep 4, 2026
731afc9
Merge pull request #3966 from plotly/dependabot/pip/pip-dependencies-…
T4rk1n Sep 4, 2026
50149d5
Start streams promptly on sync WSGI workers, stop Quart streams on Ct…
T4rk1n Sep 4, 2026
c2470ec
Merge remote-tracking branch 'origin/dev' into feat/shared_storage
T4rk1n Sep 4, 2026
c1691c5
Merge branch 'feat/shared_storage' into feat/streaming
T4rk1n Sep 4, 2026
c2ef59b
Fix dev CI after the pip dependency bump
T4rk1n Sep 4, 2026
5bf95fb
Run every CI job when shared dependency or config files change
T4rk1n Sep 4, 2026
9f4ecb0
Add partial path reads to WebSocket get_prop
CNFeffery Sep 7, 2026
94f23e5
Add tests for partial WebSocket prop reads
CNFeffery Sep 7, 2026
55e21f8
Document partial WebSocket prop reads in architecture guide
CNFeffery Sep 7, 2026
64f61e5
Update CHANGELOG.md
CNFeffery Sep 7, 2026
8e4b1c3
Fix websocket get_prop path typing and validation
CNFeffery Sep 7, 2026
f0e20e3
Refactor partial WebSocket prop reads
CNFeffery Sep 7, 2026
c0cc4db
Merge pull request #3972 from plotly/fix/dev-ci-py39-flake8
T4rk1n Sep 8, 2026
3a0524b
Merge branch 'dev' into ci/shared-paths-run-all-jobs
T4rk1n Sep 8, 2026
e176a05
Pin mimesis to 11.1.0 on Python 3.9
T4rk1n Sep 8, 2026
a8168bb
Merge pull request #3973 from plotly/ci/shared-paths-run-all-jobs
T4rk1n Sep 8, 2026
91bac77
Merge remote-tracking branch 'origin/dev' into feat/shared_storage
T4rk1n Sep 8, 2026
2d97ce5
Merge branch 'feat/shared_storage' into feat/streaming
T4rk1n Sep 8, 2026
3b470aa
Merge branch 'dev' into feat/3975-get-prop-path
CNFeffery Sep 9, 2026
0a88259
Merge pull request #3977 from CNFeffery/feat/3975-get-prop-path
T4rk1n Sep 11, 2026
42d618f
Share one streaming downlink per browser and scale it on both WSGI an…
T4rk1n Sep 15, 2026
b35f721
Merge remote-tracking branch 'origin/dev' into feat/shared_storage
T4rk1n Sep 15, 2026
ea2c1b0
Merge branch 'feat/shared_storage' into feat/streaming
T4rk1n Sep 15, 2026
2324e25
Fix mypy typing errors in shared-storage async client poll
T4rk1n Sep 15, 2026
e68d172
Refresh JS dependencies within range, hold breaking majors
T4rk1n Sep 16, 2026
65ccc99
Fix lockfiles for CI npm ci and hold d3-format
T4rk1n Sep 16, 2026
b9fa56a
Modify before-request hooks to handle results for fastapi
BSd3v Sep 16, 2026
b377434
adding changelog
BSd3v Sep 16, 2026
d08abba
Merge pull request #3986 from BSd3v/patch-2
T4rk1n Sep 16, 2026
f30f0b0
Merge branch 'dev' into deps/consolidated-updates
T4rk1n Sep 17, 2026
5f64859
Merge remote-tracking branch 'origin/dev' into feat/shared_storage
T4rk1n Sep 18, 2026
9b807f1
Merge branch 'feat/shared_storage' into feat/streaming
T4rk1n Sep 18, 2026
bb0356a
Merge pull request #3931 from plotly/feat/streaming
T4rk1n Sep 18, 2026
408098a
Merge branch 'dev' into deps/plotly-cloud-default-install
T4rk1n Sep 18, 2026
a444639
Merge pull request #3947 from plotly/deps/plotly-cloud-default-install
T4rk1n Sep 18, 2026
be3c345
Trim Unreleased CHANGELOG entries to <=2 lines
T4rk1n Sep 21, 2026
a794780
Bump the pip-dependencies group with 4 updates
dependabot[bot] Sep 21, 2026
8136ead
Merge pull request #3989 from plotly/changelog/trim-unreleased-entries
T4rk1n Sep 21, 2026
1fc7c8d
Hold black/pylint pins; keep selenium and redis bumps
T4rk1n Sep 21, 2026
6451d40
Merge branch 'dev' into deps/consolidated-updates
T4rk1n Sep 21, 2026
86262d1
Merge pull request #3985 from plotly/deps/consolidated-updates
T4rk1n Sep 21, 2026
221d7ac
Merge branch 'dev' into deps/pip-group-py39-hold-linters
T4rk1n Sep 21, 2026
c426abd
Merge pull request #3991 from plotly/deps/pip-group-py39-hold-linters
T4rk1n Sep 21, 2026
c11410e
version 4.5.0rc0
T4rk1n Sep 21, 2026
50e0740
Add missing PR references to CHANGELOG (#3881)
T4rk1n Sep 21, 2026
73e99ef
Bump npm package versions for 4.5.0 release
T4rk1n Sep 21, 2026
d944f03
Merge pull request #4002 from plotly/release/4.5.0rc0
T4rk1n Sep 21, 2026
d75e862
Merge remote-tracking branch 'origin/dev' into master-4.5.0rc0
T4rk1n Sep 21, 2026
b04dec2
Version 4.5.0rc0 build artifacts
T4rk1n Sep 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
440 changes: 436 additions & 4 deletions .ai/ARCHITECTURE.md

Large diffs are not rendered by default.

200 changes: 200 additions & 0 deletions .ai/PERFORMANCE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
# Performance: benchmarks, profiling, and findings

Dash's user-visible speed lives almost entirely in the **renderer** (client-side
hydration, callback dispatch, Patch application). This doc covers the benchmark
harness that measures it, how to profile a slow scenario down to the hot
function, and the findings so far.

## The harness (`benchmarks/`)

A standalone harness - **not** part of the pytest suite, on purpose: timing is
noisy, so it uses generous thresholds and *reports* rather than flaking the test
matrix (see "CI job" below).

| file | role |
|--|--|
| `benchmarks/scenarios.py` | the scenarios: each has a `build(params) -> Dash` app and a `drive(b, params) -> {metric: ms}` interaction, plus warn/fail thresholds |
| `benchmarks/bench_app.py` | serves one scenario as a real app in its own process (`debug=False`, i.e. the **production** `min.js` bundle) |
| `benchmarks/run.py` | the runner + CPU profiler + threshold gating + markdown report |
| `benchmarks/baseline.json` | committed reference numbers the CI job compares against |

Each scenario runs in its own `bench_app` subprocess driven by a headless
Chrome. Timings are taken with `performance.now()` **inside the page** (not
Python-side), so they measure real client work - server round-trip + patch
apply + React render - without selenium's per-poll latency. Per scenario we drop
`warmup` runs then report **median / p90 / max** plus a **growth** ratio
(late-third ÷ early-third per-op time): `~1` is flat, a large value means the
per-op cost scales with accumulated state - an O(total) smell.

### Running locally

```bash
# prerequisites: production renderer bundle must be current
npm run build # (or: cd dash/dash-renderer && renderer build)

# all scenarios -> results.json + a printed markdown table
python -m benchmarks.run --out benchmarks/results.json

# a subset
python -m benchmarks.run --scenario patch_append_nested wildcard_all_resolve

# gate against the committed baseline (what CI runs); exit code 1 on a hard fail
python -m benchmarks.run --baseline benchmarks/baseline.json --summary-md summary.md
```

### Updating the baseline

`baseline.json` holds absolute ms, but you do **not** need to regenerate it on a
CI-class machine: the baseline-ratio gate divides out a per-run *machine scale*
(see "Machine-independent gating" below), so a baseline captured on your laptop
compares correctly against numbers measured on a slower CI runner. Regenerate it
when scenarios change or an intended optimization lands:

```bash
python -m benchmarks.run --out benchmarks/baseline.json
```

Commit the new `baseline.json` in the same PR, and say why in the message.

To capture the baseline on CI hardware instead (so the machine scale is ~1.0x
for subsequent PRs), run the **Performance Benchmarks** workflow via
`workflow_dispatch` with `regenerate_baseline` checked, on the branch you want
to refresh (usually the default branch, right after a merge). It measures a
fresh baseline on the runner and opens a PR updating `benchmarks/baseline.json`.

### Machine-independent gating

The same code runs ~2-4x slower on a shared CI runner than on a dev machine, and
even two `ubuntu-latest` runners vary run-to-run - so comparing raw ms against a
committed baseline flakes. To avoid that, the runner measures a fixed
**calibration scenario** (`initial_render_small`) in the same run and computes a
**machine scale** = `this-run calibration median ÷ baseline calibration median`.
The baseline-ratio check then divides each scenario's raw ratio by that scale,
so a uniformly N× slower machine reports ~1.0× (no regression) while a *genuine*
regression still shows through. The scale is printed in the summary/PR comment.

The absolute `warn_ms` / `fail_ms` ceilings in `scenarios.py` are **not** scaled
- they stay generous order-of-magnitude guards and double as the backstop for a
global slowdown that would also drag the calibration workload (and so hide
inside the scale). If a run doesn't include `initial_render_small` (e.g. a
`--scenario` subset), gating falls back to a raw-ms comparison and says so.

## CI job (`.github/workflows/benchmarks.yml`)

Runs on PRs that touch `dash/`, `benchmarks/`, or components. It builds the
production bundle, runs the harness against `baseline.json`, and:

- **hard-fails** the job only on an order-of-magnitude regression - a metric
over its absolute `fail_ms`, or `> 2x` the baseline p90 *after normalizing out
machine speed* (see "Machine-independent gating" above);
- **warns** (without failing) on a smaller drift - over `warn_ms`, or `> 1.3x`
the normalized baseline - and always upserts a single sticky **PR comment**
with the table (and the machine scale) so the numbers are visible on every run;
- uploads `results.json` + `summary.md` as artifacts.

Thresholds live per-scenario in `scenarios.py` (`warn_ms` / `fail_ms`, keyed by
metric). Keep them generous: this is a smoke alarm, not a microbenchmark.

## Profiling a slow scenario

The runner can capture a **Chrome DevTools CPU profile** of a scenario and print
the hottest functions:

```bash
python -m benchmarks.run --profile wildcard_all_resolve
# -> benchmarks/profile.cpuprofile (load in Chrome DevTools > Performance,
# or in VS Code) + a printed "hottest functions" table
```

Profile mode serves the **dev** bundle (`dev_tools_serve_dev_bundles`, without
the rest of the dev tools) so the profile has **readable function names** -
the production bundle is minified to one-letter names. It warms up, then samples
`repeats` interactions at a 50µs sampling interval, aggregating self-time per
function by hit count.

Workflow: run the timing suite -> find a scenario with a high absolute time or
high `growth` -> `--profile` it -> read the hot functions -> the frame's
`file.dev.js:line` points straight into `dash/dash-renderer/src`.

## Findings

Numbers below are from `ubuntu-latest`-class hardware, production bundle, React
18. They move with the machine; trust the **shape** (flat vs growing, and which
function dominates), not the absolute ms.

### Snapshot (median per-op)

| scenario | median | growth | reading |
|--|--:|--:|--|
| initial_render_small (200 rows) | ~45 ms | 1.0x | fine |
| initial_render_large (3000 rows) | ~240 ms | 1.0x | linear in node count, expected |
| deep_nesting (120 deep) | ~27 ms | 1.0x | fine |
| patch_append_toplevel | ~52 ms | ~2x | flat enough; residual is shared O(total) traversal |
| patch_append_nested | ~54 ms | ~2.8x | same; the [[nested append fix]] keeps re-hydration O(appended) |
| patch_scalar_update_large (3000) | ~80 ms | ~0.8x | flat - in-place value change |
| callback_fanout (1 -> 300) | ~45 ms | 1.0x | fine |
| callback_chain (100 deep) | ~440 ms | 1.0x | ~100 sequential dispatches; inherent |
| wildcard_all_resolve (ALL over 400) | ~140 ms | 1.0x | was ~580 ms (O(n²)); now O(n), see below |
| full_children_replace (contrast) | ~850 ms | ~18x | O(total) every click *by design* - why Patch exists |

### 1. Wildcard (ALL / MATCH) resolution — was O(n²), now O(n) [fixed]

Profiling `wildcard_all_resolve` (one input change, `Output({...: ALL})` over
400 components) originally put ~45% of the time in ramda `_equals` +
`_functionName`. Root cause: `getPath` for a pattern-matching (dict) id did a
**linear** `find(propEq(values, 'values'), keyPaths)` over every component
sharing that id shape, and `propEq` is a **deep-equality** on the id-values
array. During an `ALL` resolution `getPath` is called once per resolved
component, so it was O(N) lookups × O(N) scan × O(k) equals ≈ **O(N²·k)**.

**Fix (`dash/dash-renderer/src/actions/paths.js`):** the paths table now
carries an `objIndex` - `{[keyStr]: {[valuesKey]: path}}`, where `valuesKey` is
`JSON.stringify(values)` - so `getPath` is an O(1) map lookup. `objs` stays an
ordered array because pattern matching (`resolveDeps`, `getAllPMCIds`) walks it
in order; `objIndex` is only for exact lookups. It's maintained inline in
`computePaths` (copy-on-write per keyStr, so re-resolving one chunk doesn't
rebuild the index for unrelated components) and extended incrementally in
`appendPaths`; a table without an index (empty initial state, test fixture)
makes `getPath` fall back to the linear scan, so the two never disagree.
Result: `wildcard_all_resolve` went **~580 ms → ~140 ms (≈4x)**, and the
`_equals`/`_functionName` frames left the profile. What remains is O(N) - one
`assocPath` per resolved output - which is inherent to writing N updates.

### 2. Patch append (post-fix) has no single hotspot

After the [[nested append fix]], profiling `patch_append_nested` shows the cost
spread across ramda `_path`/curry internals, React reconciliation, and redux
`useSelector` snapshots - the inherent O(total) *cheap* traversal (persistence
walk + callback crawl + element mapping), not the O(total) *re-hydration* that
the fix removed. There is no dominant frame to cut; flattening it further means
making those three traversals skip byref-unchanged subtrees (a bigger change).

### 3. Ramda currying overhead in the per-node hot loops [fixed]

Across every scenario the profiles showed ~10-15% in ramda's curry machinery -
`f1`/`f2`/`f3` (the arity dispatchers), `_isPlaceholder`, and curried `path`/
`pathOr`. It came from `crawlLayout` (utils.js), which runs the callback on
*every* component on *every* path recompute and callback gather, calling
curried `path(['props','children'], obj)` / `pathOr(...)` per node, plus the
`path(['props','id'], child)` in each crawl callback (`paths.js`,
`dependencies.js`). Replacing those with direct property access (`obj.props &&
obj.props.children`, etc.) and native array `concat` on the hot common path -
leaving the rare declared-`childrenProps` branch alone - cut `patch_append` by
~16% and initial render / wildcard by ~3-4% in a same-machine A/B. Low risk:
the crawled nodes are always plain component objects, so direct access is
equivalent to the curried `path`, just without the dispatch and placeholder
checks. When touching these traversals, prefer direct access over curried
ramda - the per-node multiplier makes it matter.

### 4. Full children replacement is the O(total) baseline

`full_children_replace` grows ~18x across a run and is ~15x slower than the
equivalent `Patch().extend()`. This is expected and is the reason `Patch`
exists for growing containers; it is kept as a contrast with loose thresholds.

### 5. Layouts deeper than ~250 nested components fail to serialize

`/_dash-layout` raises "Recursion limit reached" (the JSON encoder's recursion
limit) for a component tree nested deeper than ~254. The `deep_nesting`
scenario is capped at 120 to stay clear. Worth remembering before recommending
deeply-recursive layouts.
3 changes: 0 additions & 3 deletions .github/CODEOWNERS

This file was deleted.

12 changes: 12 additions & 0 deletions .github/actions/setup-virtual-display/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
name: 'Setup virtual display'
description: 'Start an Xvfb virtual display and export DISPLAY for headed browser tests'

runs:
using: 'composite'
steps:
- name: Setup virtual display
shell: bash
run: |
sudo Xvfb :99 -ac -screen 0 1280x1024x24 >/dev/null 2>&1 &
disown
echo "DISPLAY=:99" >> $GITHUB_ENV
51 changes: 51 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,20 @@ updates:
- dependency-name: "cookie"
- dependency-name: "webpack*"
- dependency-name: "typescript"
ignore:
# typescript 6+ (and the native tsc 7) fail the renderer type-check. Stay on 5.x.
- dependency-name: "typescript"
versions: [">=6.0.0"]
# Babel 8 / babel-loader 10 break the build (the class-properties plugin
# peer-deps on @babel/core ^7). Stay on the 7.x / 9.x line.
- dependency-name: "@babel/*"
versions: [">=8.0.0"]
- dependency-name: "babel-loader"
versions: [">=10.0.0"]
# @types/ramda is exact-pinned to 0.29.x to match the types-ramda override
# in package.json (each @types/ramda minor targets a matching types-ramda).
- dependency-name: "@types/ramda"
versions: [">=0.30.0"]

# Components - dash-core-components
- package-ecosystem: "npm"
Expand Down Expand Up @@ -92,6 +106,18 @@ updates:
# the react-docgen 5 API) and must never be updated.
ignore:
- dependency-name: "react-docgen"
# react-window 2.x is a full API rewrite; the components use the 1.x API.
- dependency-name: "react-window"
versions: [">=2.0.0"]
# typescript 6+ (and the native tsc 7) break the type-check. Stay on 5.x.
- dependency-name: "typescript"
versions: [">=6.0.0"]
# Babel 8 / babel-loader 10 break the build (the class-properties plugin
# peer-deps on @babel/core ^7). Stay on the 7.x / 9.x line.
- dependency-name: "@babel/*"
versions: [">=8.0.0"]
- dependency-name: "babel-loader"
versions: [">=10.0.0"]

# Components - dash-html-components
- package-ecosystem: "npm"
Expand Down Expand Up @@ -120,6 +146,12 @@ updates:
# the react-docgen 5 API) and must never be updated.
ignore:
- dependency-name: "react-docgen"
# Babel 8 / babel-loader 10 break the build (the class-properties plugin
# peer-deps on @babel/core ^7). Stay on the 7.x / 9.x line.
- dependency-name: "@babel/*"
versions: [">=8.0.0"]
- dependency-name: "babel-loader"
versions: [">=10.0.0"]

# Components - dash-table
- package-ecosystem: "npm"
Expand Down Expand Up @@ -152,6 +184,15 @@ updates:
# the react-docgen 5 API) and must never be updated.
ignore:
- dependency-name: "react-docgen"
# typescript 6+ (and the native tsc 7) break the type-check. Stay on 5.x.
- dependency-name: "typescript"
versions: [">=6.0.0"]
# Babel 8 / babel-loader 10 break the build (the class-properties plugin
# peer-deps on @babel/core ^7). Stay on the 7.x / 9.x line.
- dependency-name: "@babel/*"
versions: [">=8.0.0"]
- dependency-name: "babel-loader"
versions: [">=10.0.0"]

# Python dependencies
- package-ecosystem: "pip"
Expand All @@ -176,6 +217,16 @@ updates:
- "*"
# jupyterlab is pinned <4.0.0: jupyterlab 4.x ships jlpm as Yarn Berry, which
# cannot consume @plotly/dash-jupyterlab's Yarn 1 lockfile and breaks the build.
#
# black >=25.12.0 and pylint >=4.0.0 require Python >=3.10 and cannot install
# on the 3.9 CI jobs (Dash still supports Python 3.9). Upgrading either past
# its last 3.9-compatible release (black 25.11.0, pylint 3.3.9) also forces a
# repo-wide reformat / lint-fix pass, so linter bumps are done deliberately,
# not via Dependabot.
ignore:
- dependency-name: "jupyterlab"
versions: [">=4.0.0"]
- dependency-name: "black"
versions: [">=25.12.0"]
- dependency-name: "pylint"
versions: [">=4.0.0"]
Loading
Loading