Skip to content

fix(web): pause plan, heating, settings, and card polls when hidden - #1177

Open
frahlg wants to merge 1 commit into
masterfrom
fix/hidden-tab-polls
Open

fix(web): pause plan, heating, settings, and card polls when hidden#1177
frahlg wants to merge 1 commit into
masterfrom
fix/hidden-tab-polls

Conversation

@frahlg

@frahlg frahlg commented Sep 8, 2026

Copy link
Copy Markdown
Member

Closes #1141.

Status polling in app.js already respects document.hidden. Plan, heating, dashboard cards, loadpoints/twins, and the Settings EV/System tabs did not. They kept hitting the box (plan's 30s /api/config being the worst) after the tab was backgrounded, and Settings timers kept firing after the modal closed.

What changed

Same pattern as syncStatusPolling in app.js: on visibilitychange, clear the timer while hidden; on return, one fetch and exactly one timer.

  • Plan (web/plan.js) — 30s plan bundle and 5s strategy hint
  • Heating (web/heating.js) — 30s live refresh; a queued overlap does not catch up while hidden
  • History / savings / price cards — 5 min polls
  • Loadpoints and twins — 10s advanced-mode polls
  • Settings EV and System — 5s status polls, cleared when the modal gets hidden
  • Energy-flow particles — skip requestAnimationFrame while hidden (helps perf(web): stop dashboard render loops when their view is hidden #881 paint; CSS dash animation is unchanged)

Resume is one fetch so the first visible frame is current. If document.hidden is missing, polling continues.

Left for other PRs

#881 stays open for remaining energy-flow/chart paint (CSS dash animation, chart 30fps loop already pauses in app.js). This PR only paused the particle rAF and the network polls.

Tests

web/hidden-tab-polls.test.mjs drives heating (pause / single-flight / resume-one-fetch, dormant hidden start) and the EV settings timer (hidden + close). Source matches cover the other pollers.

node --test web/ — 587 pass. (npm test on Node 20 does not expand the quoted glob in package.json; CI Node is new enough.)

Visual check

AGENTS.md: a human must inspect the rendered UI. This environment cannot do a real browser pass. Please confirm:

  1. Background the tab: no further plan/heating/card/settings network polls in the inspector.
  2. Return: one refresh, UI current, then the normal cadence.
  3. Open Settings → EV or System, close the modal: the 5s timer stops.
  4. Energy-flow particles hold still while hidden and resume without a burst.

Note

Low Risk
Client-side polling and animation only; behavior is additive pause/resume with tests, with no auth or data-path changes.

Overview
Background tabs and closed Settings modals no longer keep hammering the box with timers that app.js status polling already respected.

Visibility (document.hidden) — Plan (30s bundle + 5s strategy hint), heating (30s), history/savings/price cards (~5 min), and advanced-mode loadpoints/twins (10s) now listen for visibilitychange, clear intervals while hidden, and on return run one fetch then a single interval. Heating also skips a queued post-refresh catch-up if the tab went hidden mid-flight.

Settings EV & System — 5s status polls run only while the settings modal is open and the document is visible; a MutationObserver on the modal’s hidden class stops the timer when the modal closes.

Energy-flow diagram — Particle requestAnimationFrame loops stop when the tab is hidden and resume when shown (CSS dash animation unchanged).

New web/hidden-tab-polls.test.mjs exercises heating and EV settings behavior; source assertions cover the other pollers.

Reviewed by Cursor Bugbot for commit 64dfd25. Bugbot is set up for automated code reviews on this repo. Configure here.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 8, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-08T09:44:13.593517Z 4450d42 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@cursor

cursor Bot commented Sep 8, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_26a075e4-948f-41e5-8fc8-19f95a08bcd2)

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4450d42710

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread web/settings/tabs/ev.js
Comment on lines +161 to +162
if (!window._evStatusTimer) {
window._evStatusTimer = setInterval(function () {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Restart the EV timer after rerendering the tab

When the provider selector calls ctx.renderTab("ev"), the existing timer is still active, so this condition preserves an interval whose callback closes over the previous refresh function and its now-detached el. The newly rendered badge gets the immediate refresh but no subsequent interval updates, leaving its charger status stale until the tab is left for at least one timer tick or the modal is reopened; clear the existing timer whenever after installs the new callback.

Useful? React with 👍 / 👎.

Comment thread web/plan.js
}
return;
}
pollPlan();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Coalesce the plan refresh when visibility returns

If the tab is hidden while a plan fetchAll() is in flight and restored before it settles, this unconditional call starts another six-endpoint batch because fetchAll has no in-flight or revision guard. The two batches can both render, allowing the later-settling older refresh to overwrite the catch-up result while also doubling load on the box; defer the catch-up until the active refresh settles or abort/sequence the old request.

Useful? React with 👍 / 👎.

Plan, heating, settings, history/savings/price cards, loadpoints and
twins now follow the same visibilitychange rule as status polling:
clear the timer while document.hidden, then one fetch and one timer
on return. Settings EV and System timers also stop when the modal
closes. Energy-flow particles skip rAF while hidden.

Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
@frahlg
frahlg force-pushed the fix/hidden-tab-polls branch from 4450d42 to 64dfd25 Compare September 8, 2026 11:20
@cursor

cursor Bot commented Sep 8, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_a6baaade-b01b-4750-8cbe-8bf23bb5402d)

@miravoss26 miravoss26 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Consistent visibility-pause pattern applied across plan, heating, history/savings/price cards, loadpoints/twins, Settings EV/System, and the energy-flow particle rAF loop: clear timers on visibilitychange → hidden, one fetch + one fresh timer on → visible. Heating's queued-refresh-while-hidden edge case is handled explicitly (waiters resolve without a stale catch-up fetch). Settings EV/System additionally stop via a MutationObserver on the modal's hidden class, which is the right hook since those timers are scoped to the modal being open, not just tab visibility.

Spot-checked ftw-energy-flow.js, ftw-history-card.js, ftw-price-chart.js, ftw-savings-card.js, and heating.js — the _syncPolling/syncHeatingPolling refactors are equivalent to the old _restartPolling plus the new hidden-check, no dropped isConnected guards. hidden-tab-polls.test.mjs (269 new lines) exercises heating and EV-settings with real timer/DOM mocks, not just source-pattern assertions. All CI green, no security surface here (client-side polling/animation only).

Safe to merge from my read.

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.

perf(web): pause plan, heating, settings, and card polls when the tab is hidden

2 participants