Skip to content

Web viewer: a page that was loading when the tab was hidden or auto-refresh paused now refreshes normally on return (#4774) - #4781

Merged
erikdarlingdata merged 2 commits into
devfrom
fix/4774-web-render-settle
Sep 29, 2026
Merged

erikdarlingdata merged 2 commits into
devfrom
fix/4774-web-render-settle

Conversation

@erikdarlingdata

@erikdarlingdata erikdarlingdata commented Sep 29, 2026 •

Copy link
Copy Markdown
Owner

Fixes #4774.

Why

In the Darling web viewer, a page render sets pageRendering and stamps its start. The only call that settles it, in schedulerTick, sat below the hidden-or-paused return. So a render whose reads finished while the tab was hidden, or while auto-refresh was paused, stayed open:

  • The page was never due again, because pageIsDue needs !pageRendering, and returning to the tab only refreshes the shell.
  • When the render finally settled, at the first visible unpaused tick, its duration ran from the old start, so it counted the whole span. The back-off multiplies it by 4 (capped at 15 minutes), the hint reads "(slow page)", and the status bar's "Updated" stamp, which comes from the shell's fleet read, looks fresh above panels that are not.

I reproduced it on the unmodified scheduler by running the real app.js and refresh-policy.js on a virtual clock. A render whose reads took 2 seconds, with the tab hidden for 10 minutes, measured 601,000 ms, put the next refresh 15 minutes out, showed "(slow page)" and never started a new render. A tab opened in the background, and a pause flag cleared from another tab, gave the same result.

What changes

  • Darling/PerformanceMonitor.Darling.Service/wwwroot/js/app.js: schedulerTick now reads the clock and runs the existing settle check (if (pageRendering && !hasInFlightReads()) settlePageRender(now);) first, then the hidden-or-paused return. The return's line is unchanged (WebFetchLayerTests pins its text). A render now ends at the first tick that sees no reads in flight, whether or not the tab is hidden or auto-refresh is paused, and the next refresh is scheduled from that moment. The back-off policy is unchanged.
  • The settle now also runs when the session has expired. That is harmless: refresh() and route() return early in that state.
  • A browser may slow the tick in a hidden tab, so the end is stamped at the first tick after the reads finish, not at the exact millisecond. That is still the reads' own end and not the end of the hidden span.
  • Darling/Darling.Tests/web-scheduler-harness.mjs runs the shipped app.js scheduler on a virtual clock. It removes app.js's import lines and its closing start() call, replaces the imports with stand-ins (page renders open a read that stays out for a set time, and hasInFlightReads() counts them), and uses the real refresh-policy.js. If app.js's layout changes so it cannot separate those lines, it throws instead of passing.
  • Darling/Darling.Tests/WebRenderSettleTests.cs runs seven checks. Six run scenarios under Node and skip silently when Node is not installed, the way WebAutoRefreshPolicyTests does. The seventh reads the source and pins that the settle call comes before the hidden-or-paused return, so a machine without Node still holds the fix.

Test plan

Scenario numbers, from the harness (the render's reads take 2 s; the interval is 60 s):

Scenario Before the fix After the fix
Tab hidden 10 minutes with the reads out 601,000 ms render, next refresh 15 min out, "(slow page)", 1 render 2,000 ms render, page renders again on return, next refresh 60 s after the settle, 2 renders
Tab opened in the background, shown 10 minutes later same as above same as the row above
Paused with the button for 10 minutes render left open during the pause; resume forces one refresh, so the end state was already normal render settled at 2,000 ms during the pause; end state unchanged
Pause flag cleared from another tab after 10 minutes 601,000 ms render, 15 min back-off, "(slow page)", 1 render 2,000 ms render, new render 1 s after the flag clears
Tab hidden 20 s, then back the render settled at the 21 s tick and read 21,000 ms, so the second render came at 81 s second render at 62 s (2 s settle + 60 s)
Render still loading, 45 s, no hide or pause 45,000 ms render, next refresh 180 s out, "(slow page)", not started twice identical
  • WebRenderSettleTests against the unmodified app.js: 6 of 7 fail, and the still-loading check passes (it pins behavior that must not change). After the fix all 7 pass.
  • Related classes together (WebRenderSettleTests, WebFetchLayerTests, WebAutoRefreshPolicyTests, RepoFileAdoptionTests, RepoFileResolutionEquivalenceTests, DocCommentHygieneTests): Total 127, Failed 0.
  • Build of Darling.Tests: 0 warnings, 0 errors.
  • Full Darling.Tests suite once, after merging origin/dev, with no PostgreSQL rig: Total 17205, Failed 0, Skipped 1123 (live tests that need a rig), Not Run 1.
  • Live PostgreSQL tests: not run (no rig), and this change touches no server code.
  • Not tried in a real browser: the checks run the shipped scheduler code under Node, not in Chrome or Edge.

Note on the pause case: the "Paused with the button" scenario already ended normally before the fix, because resuming forces a page refresh that restamps the render start. What the fix changes there is that the render is settled during the pause instead of staying open. The case that failed end to end for a pause is the flag being cleared by another tab (the flag lives in localStorage), which has no click to force a refresh.

CHANGELOG

SECTION: Fixed
ENTRY:

…to-refresh is paused settles at its own end (#4774)

The settle check sat below the hidden-or-paused return in the scheduler tick, so a render whose reads finished during such a span stayed open until the first visible, unpaused tick. Its duration then counted the whole span, the back-off pushed the next refresh out to 15 minutes with "(slow page)", and the page was never due. The check now runs first on every tick, so the render ends when its reads do and the next refresh is scheduled from that moment.

The new tests run the real app.js scheduler and refresh-policy.js under Node on a virtual clock.
@erikdarlingdata
erikdarlingdata marked this pull request as ready for review September 29, 2026 09:56
@erikdarlingdata
erikdarlingdata merged commit 3efc0db into dev Sep 29, 2026
17 of 18 checks passed
@erikdarlingdata
erikdarlingdata deleted the fix/4774-web-render-settle branch September 29, 2026 10:18
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