Skip to content

Only remount a plot when its payload changes - #13678

Open
hysts wants to merge 2 commits into
mainfrom
fix/chatbot-scroll-reset-on-plot
Open

Only remount a plot when its payload changes#13678
hysts wants to merge 2 commits into
mainfrom
fix/chatbot-scroll-reset-on-plot

Conversation

@hysts

@hysts hysts commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

Description

A gr.Plot inside a gr.Chatbot message 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.svelte wraps the plot in {#key key} and its $effect incremented key on every run. The effect depends on the value prop, and gr.Chatbot rebuilds its message objects on every update, so value arrived 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: key now advances when the payload changes (type, plot, or altair's chart) rather than when the prop's object identity changes. PlotData is fully described by those fields, and the plot type components already track value in 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:

plot remounts max distance from bottom
before 33 493 px
after 0 26 px (transient, ends at 0)

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 on main and passes with this change.

The rest of the frontend unit suite passes locally (77 files, 2137 tests), as do the chatbot tests.

gr.Plot renders through only two paths, standalone via js/plot/Index.svelte and inside the chatbot via js/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. The change event 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.

  • I used AI to... investigate the root cause and implement the fix.
  • I did not use 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

  1. 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.sh

  2. Please 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

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.
@hysts hysts self-assigned this Jul 28, 2026
@hysts
hysts requested a review from Copilot July 28, 2026 09:48
@gradio-pr-bot

gradio-pr-bot commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

🪼 branch checks and previews

Name Status URL
Spaces ready! Spaces preview
Storybook ready! Storybook preview
🦄 Changes detected! Details

Install Gradio from this PR

pip install https://huggingface.co/buckets/gradio/pypi-previews/resolve/85456576c9a5ff442ab11210607bfceede1fb77b/gradio-6.20.0-py3-none-any.whl

Install 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";

@gradio-pr-bot

Copy link
Copy Markdown
Collaborator

🦄 change detected

This Pull Request includes changes to the following packages.

Package Version
@gradio/plot patch
gradio patch

  • Only remount a plot when its payload changes

Something isn't right?

  • Maintainers can change the version label to modify the version bump.
  • If the bot has failed to detect any changes, or if this pull request needs to update multiple packages to different versions or requires a more comprehensive changelog entry, maintainers can update the changelog file directly.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.svelte to advance the {#key ...} remount key only when the plot payload (type, plot, or Altair’s chart) changes, rather than whenever the value prop’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
hysts marked this pull request as ready for review July 28, 2026 10:22
@hysts
hysts requested review from abidlabs and dawoodkhan82 July 28, 2026 10:22
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.

Chatbot scroll resetting when displaying graphs

3 participants