Re-read the Dataframe virtual window whenever the row count changes - #13744
Conversation
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>
🪼 branch checks and previews
Install Gradio from this PR pip install https://huggingface.co/buckets/gradio/pypi-previews/resolve/4207dd8ca29f9d46d386ea15cf05fe5aa3503453/gradio-6.23.1-py3-none-any.whlInstall 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"; |
🦄 change detectedThis Pull Request includes changes to the following packages.
|
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.
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. |
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>
|
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). |
|
Thanks folks! |
Fixes #13611, and the "appended row does not appear" symptom of #13272.
Root cause
createSvelteVirtualizer()recalculated the rendered window only on the initial0 -> Nrow transition:Every later row count change calls
setOptions(), which updates config but does not notify.virtualItems()'s only reactive dependency is the internalversioncounter, 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
versionon any row count change, rather than callingmeasure():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
Two new tests, both failing on
main(expected 1 to be 2andexpected 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 rowtest 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-rowcount every 200 ms through the stream:(Worth flagging for anyone verifying this from a git worktree: symlinking
node_modulesfrom another checkout makes@gradio/*workspace imports resolve to that checkout, so the bundle builds without your patch even though vitest picks it up. A realpnpm installin the worktree is needed.)Minimal demo (not committed)
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
measure()and a notify-only fix, write the tests, and draft this description. Every changed line was reviewed and the verification above was run.🤖 Generated with Claude Code