Skip to content

Re-read the Dataframe virtual window whenever the row count changes - #13744

Merged
abidlabs merged 4 commits into
mainfrom
fix/issue-13611-dataframe-stream-not-rendering
Aug 13, 2026
Merged

Re-read the Dataframe virtual window whenever the row count changes#13744
abidlabs merged 4 commits into
mainfrom
fix/issue-13611-dataframe-stream-not-rendering

Conversation

@abidlabs

Copy link
Copy Markdown
Member

Fixes #13611, and the "appended row does not appear" symptom of #13272.

Root cause

createSvelteVirtualizer() recalculated the rendered window only on the initial 0 -> N row transition:

if (prev_count === 0 && current_count > 0) {
    virtualizer.measure();
}

Every later row count change calls setOptions(), which updates config but does not notify. virtualItems()'s only reactive dependency is the internal version counter, so without a notification Svelte never re-read it and the rendered window stayed frozen at whatever it was after the first row — while the underlying TanStack rows tracked the data correctly. That is why clicking the table "fixed" it: the click triggers an unrelated scroll/resize path that forces a recalculation.

Credit to @ujwalashet, who diagnosed this precisely on #13611, and @Urvity03, who investigated #13272 — both said they were blocked from opening a PR.

Fix

Bump version on any row count change, rather than calling measure():

if (current_count !== prev_count) {
    version += 1;
}

measure() also works (it notifies as a side effect), but it clears the whole item size cache, so every row gets re-measured on every row change. Since #13272 also reports "generally extremely poor performance" on this component, the notify-only path seemed like the better trade: it re-reads the window and keeps the measured row sizes.

What this does and does not cover in #13272

Covered: "When adding a row to the last row, the dataframe does not show that last row until I go fullscreen and back to re-render that last row." Same root cause, and there is a regression test for it.

Not covered, and I do not think it is the same bug: "Dataframe with values and interactive does not have an add row button" and "empty dataframe input, add row appears, but clicking it once makes it disappear." Both fall out of the gate on EmptyRowButton:

{#if values.length === 0 && editable && row_count[1] === "dynamic"}

The button is deliberately only offered while the table is empty, so it vanishes as soon as the first row exists. Making it a persistent affordance is a UX decision rather than a fix, so I have left #13272 open for that part.

Verification

$ CI=1 npx vitest run --config .config/vitest.config.ts js/dataframe/
Test Files  4 passed (4)
     Tests  124 passed | 2 todo (126)

Two new tests, both failing on main (expected 1 to be 2 and expected 3 to be 4): one streams five rows one at a time and asserts every row renders at each step, one appends a row to the end.

I also fixed a dead selector while I was in there: the existing add row button appends a new row test queried .empty-row-button, which never matches — the class is .add-row-button — and the assertion was nested inside that lookup, so the test passed while asserting nothing. It now renders an empty table (the only state where the button is offered), asserts the button exists, and asserts the click appends a row.

End-to-end in a real browser, driving the demo below with Playwright and sampling the rendered .virtual-row count every 200 ms through the stream:

before:  0,0,1,1,1,1,1,1,1,1,1,...   -> 1 of 5 rows, verdict "❌ FAIL"
after:   0,0,1,1,1,2,2,3,3,3,4,4,4,5,5,...  -> 5 of 5 rows, verdict "✅ PASS"

(Worth flagging for anyone verifying this from a git worktree: symlinking node_modules from another checkout makes @gradio/* workspace imports resolve to that checkout, so the bundle builds without your patch even though vitest picks it up. A real pnpm install in the worktree is needed.)

Minimal demo (not committed)

import time
import gradio as gr
import pandas as pd


def stream_dataframe(n_steps: int = 5):
    rows = [{"Step": i + 1, "Value": f"Result {i + 1}"} for i in range(n_steps)]
    for i in range(n_steps):
        time.sleep(0.5)
        yield pd.DataFrame(rows[: i + 1])


with gr.Blocks() as demo:
    result_table = gr.Dataframe(row_count=(1, "dynamic"), interactive=True, label="Results")
    gr.Button("Run").click(stream_dataframe, outputs=[result_table])

demo.launch()

Only the first row appears until you click the table. The demo file was kept untracked and is not part of this PR.

AI Disclosure

  • I used AI to reproduce the bug, confirm the reported diagnosis against the code, choose between measure() and a notify-only fix, write the tests, and draft this description. Every changed line was reviewed and the verification above was run.
  • I did not use AI

🤖 Generated with Claude Code

The virtualized table only recalculated its rendered window on the initial
0 -> N row transition. Every later row count change called `setOptions()`, which
does not notify, so `virtualItems()` -- whose only reactive dependency is the
internal `version` counter -- was never re-read and the rendered window stayed
frozen at whatever it was after the first row. Streamed dataframes showed only
their first row, and a row appended to the end stayed invisible, until an
unrelated scroll or resize happened to force a recalculation.

Bumping `version` on any row count change re-reads the window while keeping the
measured row sizes that `measure()` would discard.

Also fixes a dead selector in the add-row test: it queried `.empty-row-button`,
which never matches (the class is `.add-row-button`), and the assertion sat
inside that lookup, so the test silently asserted nothing.

Fixes #13611

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gradio-pr-bot

gradio-pr-bot commented Aug 12, 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/4207dd8ca29f9d46d386ea15cf05fe5aa3503453/gradio-6.23.1-py3-none-any.whl

Install Gradio Python Client from this PR

pip install "gradio-client @ git+https://github.com/gradio-app/gradio@4207dd8ca29f9d46d386ea15cf05fe5aa3503453#subdirectory=client/python"

Import Gradio JS Client from this PR via CDN

import { Client } from "https://huggingface.co/buckets/gradio/npm-previews/resolve/4207dd8ca29f9d46d386ea15cf05fe5aa3503453/browser.js";

@gradio-pr-bot

gradio-pr-bot commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

🦄 change detected

This Pull Request includes changes to the following packages.

Package Version
@gradio/dataframe patch
gradio patch

  • Re-read the Dataframe virtual window whenever the row count 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.

@abidlabs

abidlabs commented Aug 12, 2026

Copy link
Copy Markdown
Member Author

Before / after Spaces

Both run the same plain demo: a calculation that yields one more dataframe row every half second, for 5 rows. Press Run and watch the table.

before after
rows visible when the run finishes 1 5
the rest appear if you click the table yes n/a, they are already there

I drove both with Playwright to check them before posting: 1 row rendered on the before Space, 5 on the after.

One reviewer note: UI Tests (Chromatic) flags baselines needing acceptance. That is expected — rows that previously did not render now do, so Dataframe stories legitimately differ — but accepting the baselines is a review decision, so I have left them alone.

abidlabs and others added 3 commits August 11, 2026 23:06
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Leaves the change as the one-line behaviour fix.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@abidlabs
abidlabs requested review from dawoodkhan82 and hysts August 12, 2026 06:18
@abidlabs
abidlabs marked this pull request as ready for review August 12, 2026 06:26

@hysts hysts left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks @abidlabs ! LGTM!

@ujwalashet

Copy link
Copy Markdown

Glad this was useful — thanks for the credit @abidlabs! Nice fix using the version counter instead of measure(), that's a better trade-off than what I had in mind (I hadn't clocked that measure() would also wipe the whole size cache on every change).

@abidlabs

Copy link
Copy Markdown
Member Author

Thanks folks!

@abidlabs
abidlabs merged commit ca9dec0 into main Aug 13, 2026
29 of 30 checks passed
@abidlabs
abidlabs deleted the fix/issue-13611-dataframe-stream-not-rendering branch August 13, 2026 16:19
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.

Dataframe stream not rendering

4 participants