Only remount a plot when its payload changes - #13678
Open
hysts wants to merge 2 commits into
Open
Conversation
Plot.svelte wraps the plot in {#key key} and incremented key on every
effect run. The effect depends on the value prop, and gr.Chatbot rebuilds
its message objects on every streaming update, so the plot arrived as a
fresh object holding the same payload about 30 times per streamed reply
and each one forced a full remount.
For bokeh that is visible as flickering and as the view jumping between
the top and the bottom of the conversation: re-embedding clears the
container, so the message height collapses to 0 and grows back on every
update.
The remount is deliberate (#9781), so gate it instead of removing it.
PlotData is fully described by type, plot and altair's chart, all
strings, so comparing those three tells a real plot change from a
re-created object. The plot type components already track value in place
through $derived, so nothing else needed to move.
Measured on the issue's app over one plain-text turn after a bokeh plot
was displayed: 33 remounts and 493 px away from the bottom before, 0
remounts and 26 px after.
Collaborator
🪼 branch checks and previews
Install Gradio from this PR pip install https://huggingface.co/buckets/gradio/pypi-previews/resolve/85456576c9a5ff442ab11210607bfceede1fb77b/gradio-6.20.0-py3-none-any.whlInstall Gradio Python Client from this PR pip install "gradio-client @ git+https://github.com/gradio-app/gradio@85456576c9a5ff442ab11210607bfceede1fb77b#subdirectory=client/python"Import Gradio JS Client from this PR via CDN import { Client } from "https://huggingface.co/buckets/gradio/npm-previews/resolve/85456576c9a5ff442ab11210607bfceede1fb77b/browser.js"; |
Collaborator
🦄 change detectedThis Pull Request includes changes to the following packages.
|
Contributor
There was a problem hiding this comment.
Pull request overview
This PR fixes a Gradio frontend regression where plots embedded in gr.Chatbot messages were being unnecessarily remounted during streaming updates, causing visible flicker and scroll jumping (notably with Bokeh). It keeps the intentional “remount on plot change” behavior (added for cleaner plot updates) but gates it so remounts only occur when the plot payload meaningfully changes.
Changes:
- Updates
js/plot/shared/Plot.svelteto advance the{#key ...}remount key only when the plot payload (type,plot, or Altair’schart) changes, rather than whenever thevalueprop’s object identity changes. - Adds regression tests ensuring identical payloads delivered via new objects do not remount, while genuine payload changes still do remount.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| js/plot/shared/Plot.svelte | Prevents unnecessary plot remounts by comparing payload fields instead of prop identity, avoiding flicker/scroll jumps during chatbot streaming. |
| js/plot/Plot.test.ts | Adds targeted tests to prevent regressions: no remount for unchanged payloads, but remount preserved for actual payload changes. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
hysts
marked this pull request as ready for review
July 28, 2026 10:22
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
A
gr.Plotinside agr.Chatbotmessage was torn down and rebuilt on every streaming update of the chat history. With a bokeh plot that shows up as flickering and as the view flipping between the top and the bottom of the conversation while the reply streams: re-embedding clears the container, so the message height collapses to 0 and grows back on every update. It does land at the bottom once the reply finishes, but everything in between is unusable.js/plot/shared/Plot.sveltewraps the plot in{#key key}and its$effectincrementedkeyon every run. The effect depends on thevalueprop, andgr.Chatbotrebuilds its message objects on every update, sovaluearrived as a fresh object holding the same payload roughly 30 times per streamed reply and each one forced a full remount.The remount is deliberate (added in #9781 so a plot change renders cleanly), so this keeps it and only gates it:
keynow advances when the payload changes (type,plot, or altair'schart) rather than when the prop's object identity changes.PlotDatais fully described by those fields, and the plot type components already trackvaluein place through$derived, so nothing else had to move.Measured with the issue's app on a bokeh plot, over one plain-text turn (31 streaming yields) after the plot was displayed:
Plotly and matplotlib also went from 33 remounts to 0. They kept their height across the remount, so they never showed the scroll jump, only the wasted re-render.
Closes: #10107
Testing
Two tests in
js/plot/Plot.test.ts: an unchanged payload delivered as a new object keeps the same plot element, and a changed payload still replaces it (that second one guards #9781's remount). The first test fails onmainand passes with this change.The rest of the frontend unit suite passes locally (77 files, 2137 tests), as do the chatbot tests.
gr.Plotrenders through only two paths, standalone viajs/plot/Index.svelteand inside the chatbot viajs/chatbot/shared/Component.svelte, and both are covered above. Standalone plots see no behaviour change beyond skipping a redundant re-render when the backend re-sends an identical plot. Thechangeevent still fires exactly as before; it was never tied to the remount.Two things this deliberately leaves alone. The chatbot's autoscroll still uses a fixed
tick()+ 300 ms timeout, which is fine once the plot stops collapsing its height (the 26 px above is normal lag while content grows). And a wide bokeh figure still overflows the chat bubble, which hannahblair noted on the issue; that is a layout question separate from the scroll reset.Before / after Spaces
Same app on both, so the only difference is which gradio it installs. Send
plot, then send anything else and watch the chat area while the reply streams.Driving both with the same Playwright probe: 80 plot remounts and up to 600 px away from the bottom on the before Space, 0 remounts and 26 px on the after Space.
AI Disclosure
We encourage the use of AI tooling in creating PRs, but the any non-trivial use of AI needs be disclosed. E.g. if you used Claude to write a first draft, you should mention that. Trivial tab-completion doesn't need to be disclosed. You should self-review all PRs, especially if they were generated with AI.
🎯 PRs Should Target Issues
Before your create a PR, please check to see if there is an existing issue for this change. If not, please create an issue before you create this PR, unless the fix is very small.
Not adhering to this guideline will result in the PR being closed.
Testing and Formatting Your Code
PRs will only be merged if tests pass on CI. We recommend at least running the backend tests locally, please set up your Gradio environment locally and run the backed tests:
bash scripts/run_backend_tests.shPlease run these bash scripts to automatically format your code:
bash scripts/format_backend.sh, and (if you made any changes to non-Python files)bash scripts/format_frontend.sh