From 2f083feb768944a84664ebdee72844e71b4b4379 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 26 Jul 2026 18:40:50 +0000 Subject: [PATCH] Gate widget pollers on their Widgets-tab enabled flag, not just sidebar visibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Disabling Weather/Stocks/PoE2 in the Widgets tab previously only hid the sidebar box — the backend kept polling on schedule regardless. scheduler.ts now skips both the interval tick and the startup-immediate poll for any widget currently disabled, so there's no outbound call for something nobody's displaying. Re-enabling a widget (via PATCH /api/admin/settings) triggers an immediate poll instead of waiting out its normal cadence, mirroring the existing weather-location-change behavior. Verified live: booted with poe2 disabled and confirmed no poll fired at startup (previously always immediate), then re-enabled via PATCH and confirmed the poll fired immediately. --- backend/src/api/admin.ts | 16 ++++++++++++++++ backend/src/queue/scheduler.ts | 21 +++++++++++++++++---- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/backend/src/api/admin.ts b/backend/src/api/admin.ts index 8792ef5..cf2b1f3 100644 --- a/backend/src/api/admin.ts +++ b/backend/src/api/admin.ts @@ -38,12 +38,28 @@ export async function registerAdminRoutes(app: FastifyInstance) { categoriesDb.setCategoryOrder(body.categoryPriority); delete body.categoryPriority; } + const before = settingsDb.getSettings(); const settings = withStorageUsed(settingsDb.updateSettings(body)); if (body.weather) { // Poll immediately rather than waiting for the next scheduler tick (up to 45 // minutes) — the admin just changed the location/unit and expects to see it reflected. pollWeatherNow().catch((err) => logger.error('weather', `Immediate poll failed: ${err.message}`)); } + if (body.widgets) { + // Re-enabling a widget (see the Widgets tab) should show fresh data right away + // instead of waiting out its normal cadence (up to 45m/15m/1h) — scheduler.ts + // skips polling entirely while a widget is disabled, so there's nothing recent + // to fall back on otherwise. + if (body.widgets.weather && !before.widgets.weather) { + pollWeatherNow().catch((err) => logger.error('weather', `Immediate poll failed: ${err.message}`)); + } + if (body.widgets.stocks && !before.widgets.stocks) { + pollStocksNow().catch((err) => logger.error('stocks', `Immediate poll failed: ${err.message}`)); + } + if (body.widgets.poe2 && !before.widgets.poe2) { + pollPoe2Now().catch((err) => logger.error('poe2', `Immediate poll failed: ${err.message}`)); + } + } return { ...settings, categoryPriority: categoriesDb.listCategories() }; }); diff --git a/backend/src/queue/scheduler.ts b/backend/src/queue/scheduler.ts index 39d9f56..76dd754 100644 --- a/backend/src/queue/scheduler.ts +++ b/backend/src/queue/scheduler.ts @@ -69,19 +69,32 @@ export function startScheduler() { // Immediate first call for all three — unlike RSS sources (whose "due" check makes a // brand-new source eligible on the very next 1-minute tick), weather/stocks/poe2 have // no such shortcut; without this the sidebar is empty for up to 45/15/60 minutes after - // every restart. - pollWeatherNow().catch((err) => logger.error('weather', `Initial poll failed: ${err.message}`)); + // every restart. Each is also gated on its Widgets-tab enabled flag (see + // admin/settings' consolidated Widgets tab) — disabling a widget stops these external + // calls entirely rather than just hiding the sidebar box, so there's no pointless + // polling for something nobody's looking at. Re-enabling it triggers an immediate + // poll instead (see admin.ts's PATCH /api/admin/settings), same as this initial call. + if (settingsDb.getSettings().widgets.weather) { + pollWeatherNow().catch((err) => logger.error('weather', `Initial poll failed: ${err.message}`)); + } setInterval(() => { + if (!settingsDb.getSettings().widgets.weather) return; pollWeatherNow().catch((err) => logger.error('weather', `Poll tick failed: ${err.message}`)); }, WEATHER_TICK_MS); - pollStocksNow().catch((err) => logger.error('stocks', `Initial poll failed: ${err.message}`)); + if (settingsDb.getSettings().widgets.stocks) { + pollStocksNow().catch((err) => logger.error('stocks', `Initial poll failed: ${err.message}`)); + } setInterval(() => { + if (!settingsDb.getSettings().widgets.stocks) return; pollStocksNow().catch((err) => logger.error('stocks', `Poll tick failed: ${err.message}`)); }, STOCKS_TICK_MS); - pollPoe2Now().catch((err) => logger.error('poe2', `Initial poll failed: ${err.message}`)); + if (settingsDb.getSettings().widgets.poe2) { + pollPoe2Now().catch((err) => logger.error('poe2', `Initial poll failed: ${err.message}`)); + } setInterval(() => { + if (!settingsDb.getSettings().widgets.poe2) return; pollPoe2Now().catch((err) => logger.error('poe2', `Poll tick failed: ${err.message}`)); }, POE2_TICK_MS);