Skip to content

fix: keep spinner up until client-drawn widgets have painted - #1

Open
ivokwee wants to merge 1 commit into
mainfrom
fix/spinner-wait-for-widget-paint
Open

fix: keep spinner up until client-drawn widgets have painted#1
ivokwee wants to merge 1 commit into
mainfrom
fix/spinner-wait-for-widget-paint

Conversation

@ivokwee

@ivokwee ivokwee commented Aug 16, 2026

Copy link
Copy Markdown
Member

Problem

spinner.js hides the spinner on shiny:value. That event fires when the value reaches the browser — which for a client-drawn widget is before it draws anything. For plotly and iheatmapr the value is the drawing instruction, so the spinner disappears and the container sits blank for the entire client-side draw.

For a large scatter or heatmap that draw is the slow part. It is also the whole of the wait whenever a plot is redrawn without a server round-trip.

Noticed in Omics Playground, where every PlotModuleUI() plot goes through useSpinner(), so it affects all plotly cards app-wide.

Fix

For plotly/iheatmapr outputs only, keep the spinner up until the output element actually contains an svg/canvas, polled on requestAnimationFrame. Capped at ~10s so a widget that never paints (empty data, a render error swallowed by the library) cannot leave the spinner running forever.

Everything else — tables, htmlOutput, server-rendered plot images — is complete at shiny:value and takes the original immediate-hide path, unchanged.

showSpinner/hideSpinner are untouched, so the visibility handling from 7efb55b still applies: the container keeps its layout box while hidden, which plotly needs in order to measure itself while drawing.

Version bump

addBigLoaderDeps() builds the htmlDependency from packageVersion(), and that version is part of the served asset path. Bumped 0.0.2 → 0.0.3 so browsers don't serve a cached spinner.js.

Testing

Verified in Omics Playground (components/app_qsee): opening a board with a heavy plotly figure now shows a continuous spinner through both the server compute and the client draw, with no blank gap before the plot appears. Tables and non-widget outputs behave as before.

@ESCRI11 — you wrote this package, so you're the right reviewer. The main thing worth your eye is the widget-detection list in isClientDrawnWidget(): I covered plotly and iheatmapr because those are what Playground uses. If there are other client-drawn widget types you'd expect bigLoaders to handle (DT, visNetwork, ggiraph), say the word and I'll extend it.

🤖 Generated with Claude Code

'shiny:value' fires when a value reaches the browser, not when the widget
has drawn it. For plotly and iheatmapr the value *is* the drawing
instruction, so hiding the spinner on that event leaves the container
blank for the entire client-side draw -- which for a large scatter or
heatmap is the slow part, and is the whole of the wait when a plot is
redrawn without a server round-trip.

Keep the spinner visible for those widget types until the output element
actually contains an svg/canvas, capped at ~10s so a widget that never
paints cannot leave it spinning. Tables, HTML and server-rendered plot
images are complete at 'shiny:value' and are unaffected.

Version bumped so the htmlDependency path changes and browsers do not
serve a cached spinner.js.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ivokwee
ivokwee requested a review from ESCRI11 August 16, 2026 02:38
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.

1 participant