Skip to content

fix(dashboard): only the visible page auto-refreshes (#1419) - #1474

Merged
alfredo1996 merged 2 commits into
release/1.5from
fix/issue-1419-gate-refresh-on-active
Aug 6, 2026
Merged

fix(dashboard): only the visible page auto-refreshes (#1419)#1474
alfredo1996 merged 2 commits into
release/1.5from
fix/issue-1419-gate-refresh-on-active

Conversation

@alfredo1996

@alfredo1996 alfredo1996 commented Aug 5, 2026

Copy link
Copy Markdown
Owner

Refs #1419part one of two. Not Closes: the unbounded mount cache is deliberately left out, see Scope.

The problem

Every dashboard page a user had ever visited kept auto-refreshing. Query load scaled with browsing history rather than with what was on screen. Sitting on the same page, with the identical 18 visible widgets:

Run Tiles mounted Visible /api/query POSTs in 40s
only page 1 ever opened 18 18 16
after visiting 6 pages, then back to page 1 86 18 77

4.81× the database traffic for the same view. Tour all 20 pages of Chart Reference and leave the tab open and ~230 widgets keep polling.

This is not a rendering defect — it is load the product generates against the customer's database for no user-visible benefit, and it is refresh-tier work, the exact category the query scheduler exists to shed. One user exploring a large dashboard could crowd out interactive queries for everyone else on that connector.

The fix

One condition. isActive was already computed, already used two lines above for the hidden class and one line below to gate onLayoutChange — it just never gated the refresh.

-refetchInterval={editMode ? false : viewRefetchInterval}
+refetchInterval={editMode || !isActive ? false : viewRefetchInterval}

Pages stay mounted, which is deliberate: tab switches stay instant and don't re-query. They simply stop polling while hidden. On return, the page refreshes on the next tick, and within the 5-minute staleTime it renders from cache with no network call at all.

Scope: part two is separate

The issue's second defect — the mount cache that never evicts (131 tiles, 87 canvases, 146.5 MB peak heap after 11 tabs) — is not here. It is a behaviour change with a real state-loss edge case: widget-local React state does not survive unmount, so a user mid-way through a Form widget would lose their typed input to a tab switch. Layout edits and parameter values are safe (workspace state and Zustand stores respectively), but form input is not.

That deserves its own review and its own E2E. This half removes the entire 4.8× on its own and carries essentially no risk, so it should not wait.

The E2E was verified in both directions

Against the unfixed build it fails, on the initial run and the retry:

Error: hidden pages are still polling: 7 queries after touring vs 2 on a fresh load

With the fix it passes. That check mattered, because the first version of this test passed against the bug:

All three pages queried the same thing, and use-widget-query keys on [connectionId, database, query, params, staleTime]no widget id. TanStack Query collapsed the three widgets into one cache entry with one refetch timer, so mounting three pages produced one network query whether or not the fix was present.

The fixture now gives each page a distinct query, and the reason is commented there so nobody "simplifies" it back.

Worth knowing independently: identical widgets anywhere on a dashboard share one query and one refresh. That softens this bug for dashboards that repeat a query across pages — the 4.81× measurement holds because Chart Reference's pages genuinely differ.

Tests

  • Unit — 4 cases on DashboardWorkspace: only the active page carries the interval; returning to page 1 leaves exactly one poller among three mounted pages; the count never depends on how many pages were toured; edit mode stays off throughout. The first three fail today, reporting ['30000','30000','30000'].
  • E2E — counts /api/query POSTs over a fixed window on page 1, tours the other pages, returns, and counts again over an identical window. Same visible view, same count.

Verification

app unit 64/64 in the affected file · targeted E2E green (27s) · typecheck + lint clean

Note

e2e/global-setup.ts skips next build whenever .next/BUILD_ID exists, so a cached build from another branch is used silently. Every E2E run above needed an explicit rm .next/BUILD_ID first — otherwise it tests stale code and reports a meaningless pass. Worth its own fix; the auto-detect should key on the current commit rather than on a file existing.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Dashboard auto-refresh now runs only on the currently visible page.
    • Previously visited pages stop polling when hidden, reducing unnecessary refresh activity.
    • Auto-refresh remains disabled while editing a dashboard.
  • Tests

    • Added coverage for page visibility, navigation, edit mode, and multi-page refresh behavior.

Every page a user had visited kept polling, so query load scaled with
browsing history instead of with what was on screen. Measured on Chart
Reference, same page and same 18 visible widgets both times:

  only page 1 opened          18 tiles   16 POSTs / 40s
  after touring 6 pages       86 tiles   77 POSTs / 40s

4.81x the database traffic for an identical view. That load is
refresh-tier work against the customer's database — the exact category
the scheduler exists to shed — so one user browsing a large dashboard
could crowd out interactive queries for everyone on that connector.

`isActive` was already computed, already used two lines above for the
`hidden` class and one line below to gate `onLayoutChange`. It just
never gated the refresh.

Pages stay mounted, which is deliberate: tab switches remain instant and
do not re-query. They simply stop polling while hidden.

Scope: this is part one of the issue. The unbounded mount cache — 131
tiles and 87 canvases after 11 tabs, 146.5 MB peak heap — is a separate
behaviour change with a state-loss edge case around unsaved form input,
and is tracked separately.

The E2E was verified in both directions. Against the unfixed build it
fails with "7 queries after touring vs 2 on a fresh load", on the initial
run and the retry; with the fix it passes. An earlier version of it
passed either way: all three pages shared one query, and use-widget-query
keys on (connection, database, query, params, staleTime) with no widget
id, so TanStack collapsed them into a single fetch. The pages now query
distinct data, and the reason is commented in the fixture.

Refs #1419

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@alfredo1996 alfredo1996 added bug Something isn't working performance Performance improvement pkg:app Next.js application package area:query-exec Query execution & safety area:dashboard Dashboard management priority:P1 Ship-but-fix before release labels Aug 5, 2026
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: df11c3d8-0077-475d-86fb-e3a13811d2f4

📥 Commits

Reviewing files that changed from the base of the PR and between 4996f4b and d6791a8.

📒 Files selected for processing (1)
  • CHANGELOG.md

Walkthrough

Dashboard refresh polling now runs only for the active visible page. Mounted hidden pages stop polling, while edit mode disables polling for all pages. Unit and end-to-end tests cover navigation, visibility, and query request counts.

Changes

Hidden page polling

Layer / File(s) Summary
Active-page polling control
app/src/components/dashboard-workspace.tsx
DashboardContainer receives viewRefetchInterval only for the active page in view mode. Hidden pages and edit mode receive no polling interval.
Polling regression coverage
app/src/components/__tests__/dashboard-workspace.test.tsx, app/e2e/hidden-page-refresh.spec.ts, CHANGELOG.md
Tests verify polling behavior across mounted pages, navigation, and edit mode. The end-to-end test measures /api/query requests. The changelog records the change.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related issues

  • alfredo1996/neoboard#1475 — Proposes evicting old mounted dashboard pages, which relates to the same visited-page mounting behavior changed here.

Possibly related PRs

Suggested labels: testing

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: only the visible dashboard page auto-refreshes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/issue-1419-gate-refresh-on-active

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

…-gate-refresh-on-active

# Conflicts:
#	CHANGELOG.md
@alfredo1996
alfredo1996 merged commit 5bcf05e into release/1.5 Aug 6, 2026
1 check was pending
@alfredo1996
alfredo1996 deleted the fix/issue-1419-gate-refresh-on-active branch August 6, 2026 08:40
@sonarqubecloud

sonarqubecloud Bot commented Aug 6, 2026

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:dashboard Dashboard management area:query-exec Query execution & safety bug Something isn't working performance Performance improvement pkg:app Next.js application package priority:P1 Ship-but-fix before release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants