From 908fb8650ce1f295b6047fe95a75ec4aa235b7dc Mon Sep 17 00:00:00 2001 From: Peter Stone Date: Mon, 16 Mar 2026 00:41:44 +0000 Subject: fix: restore running tab rendering and throttle history fetch - poll() now calls renderActiveTab(cache) on early-return so switching tabs always renders immediately instead of leaving the panel blank - renderRunningView unchanged check now requires running.length > 0, fixing the empty-state message never appearing when no tasks run - Extract renderActiveTab() to avoid duplicating the tab switch logic - Throttle execution history fetch to once per 60s (was every poll) Co-Authored-By: Claude Sonnet 4.6 --- web/app.js | 79 ++++++++++++++++++++++++++++++++++---------------------------- 1 file changed, 44 insertions(+), 35 deletions(-) diff --git a/web/app.js b/web/app.js index a661fe7..a2b0ea9 100644 --- a/web/app.js +++ b/web/app.js @@ -1079,6 +1079,7 @@ let taskCache = new Map(); let lastServerUpdate = null; let pollTimeout = null; let lastUserInteraction = Date.now(); +let lastHistoryFetch = 0; function getActiveTab() { const active = document.querySelector('.tab.active'); @@ -1096,13 +1097,52 @@ async function fetchHealth() { return res.json(); } +function renderActiveTab(allTasks) { + const activeTab = getActiveTab(); + switch (activeTab) { + case 'queue': + renderQueuePanel(allTasks); + break; + case 'interrupted': + renderInterruptedPanel(allTasks); + break; + case 'ready': + renderReadyPanel(allTasks); + break; + case 'running': + renderRunningView(allTasks); + if (Date.now() - lastHistoryFetch > 60_000) { + lastHistoryFetch = Date.now(); + fetchRecentExecutions(BASE_PATH, fetch) + .then(execs => renderRunningHistory(execs)) + .catch(() => { + const histEl = document.querySelector('.running-history'); + if (histEl) histEl.innerHTML = '

Could not load execution history.

'; + }); + } + break; + case 'all': + renderAllPanel(allTasks); + break; + case 'stats': + fetchRecentExecutions(BASE_PATH, fetch) + .then(execs => renderStatsPanel(allTasks, execs)) + .catch(() => {}); + break; + case 'settings': + renderSettingsPanel(); + break; + } +} + async function poll() { try { const health = await fetchHealth(); const serverUpdate = health.last_updated; - // If server says nothing changed, we skip fetching tasks. + // If server says nothing changed, skip fetching but still render (e.g. tab was just switched). if (lastServerUpdate && serverUpdate <= lastServerUpdate && taskCache.size > 0) { + renderActiveTab(Array.from(taskCache.values())); return; } @@ -1118,39 +1158,7 @@ async function poll() { const allTasks = Array.from(taskCache.values()); updateTabBadges(allTasks); - - const activeTab = getActiveTab(); - switch (activeTab) { - case 'queue': - renderQueuePanel(allTasks); - break; - case 'interrupted': - renderInterruptedPanel(allTasks); - break; - case 'ready': - renderReadyPanel(allTasks); - break; - case 'running': - renderRunningView(allTasks); - fetchRecentExecutions(BASE_PATH, fetch) - .then(execs => renderRunningHistory(execs)) - .catch(() => { - const histEl = document.querySelector('.running-history'); - if (histEl) histEl.innerHTML = '

Could not load execution history.

'; - }); - break; - case 'all': - renderAllPanel(allTasks); - break; - case 'stats': - fetchRecentExecutions(BASE_PATH, fetch) - .then(execs => renderStatsPanel(allTasks, execs)) - .catch(() => {}); - break; - case 'settings': - renderSettingsPanel(); - break; - } + renderActiveTab(allTasks); } catch (err) { console.error('Polling failed:', err); const panel = document.querySelector('[data-panel="queue"] .panel-task-list'); @@ -2106,7 +2114,8 @@ function renderRunningView(tasks) { // Update elapsed spans in place if the same tasks are still running. const existingCards = currentEl.querySelectorAll('[data-task-id]'); const existingIds = new Set([...existingCards].map(c => c.dataset.taskId)); - const unchanged = running.length === existingCards.length && + const unchanged = running.length > 0 && + running.length === existingCards.length && running.every(t => existingIds.has(t.id)); if (unchanged) { -- cgit v1.2.3