From 7415485b9a8bdb342fce8669169be2c98bb70e26 Mon Sep 17 00:00:00 2001 From: Broque Thomas <26755000+Nezreka@users.noreply.github.com> Date: Fri, 17 Apr 2026 14:52:18 -0700 Subject: [PATCH] Harden download modal polling against premature completion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Include completed batches in poll cycle so late task updates still render (prevents modal freezing when batch completes before all rows update) - Require server to report no active tasks before client-side completion fires (prevents phase=complete from prematurely ending UI) - Apply same fix to backoff poller and WebSocket resubscription Note: modal stalling issue persists — setInterval stops firing after ~12 cycles for unknown reasons. Needs deeper browser-level debugging. --- webui/static/script.js | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/webui/static/script.js b/webui/static/script.js index 61e98809..b8adeb6f 100644 --- a/webui/static/script.js +++ b/webui/static/script.js @@ -465,7 +465,7 @@ function resubscribeDownloadBatches() { if (!socket || !socketConnected) return; const activeBatchIds = []; Object.entries(activeDownloadProcesses).forEach(([playlistId, process]) => { - if (process.batchId && process.status === 'running') { + if (process.batchId && (process.status === 'running' || process.status === 'complete')) { activeBatchIds.push(process.batchId); } }); @@ -15075,7 +15075,9 @@ function startGlobalDownloadPolling() { let hasOpenWishlistModal = false; Object.entries(activeDownloadProcesses).forEach(([playlistId, process]) => { - if (process.batchId && process.status === 'running') { + // Include running AND recently-completed batches — ensures late task + // status updates still reach the modal so rows don't freeze mid-download + if (process.batchId && (process.status === 'running' || process.status === 'complete')) { activeBatchIds.push(process.batchId); batchToPlaylistMap[process.batchId] = playlistId; } @@ -15187,7 +15189,7 @@ function startGlobalDownloadPollingWithInterval(interval) { let hasOpenWishlistModal = false; Object.entries(activeDownloadProcesses).forEach(([playlistId, process]) => { - if (process.batchId && process.status === 'running') { + if (process.batchId && (process.status === 'running' || process.status === 'complete')) { activeBatchIds.push(process.batchId); batchToPlaylistMap[process.batchId] = playlistId; } @@ -15661,9 +15663,14 @@ function processModalStatusUpdate(playlistId, data) { autoSavePlaylistM3U(playlistId); } - // CLIENT-SIDE COMPLETION: If all tracks are finished (completed or failed), complete the modal - const allTracksFinished = totalFinished >= missingCount && missingCount > 0; - if (allTracksFinished && process.status !== 'complete') { + // CLIENT-SIDE COMPLETION: Only complete when ALL task rows in the UI reflect a terminal state. + // Using totalFinished (derived from DOM updates in THIS render pass) prevents premature + // completion when the server sends phase='complete' before all rows have been updated. + const allTracksFinished = totalFinished >= missingCount && missingCount > 0 && totalFinished > 0; + // Extra guard: require the server to also report no active tasks + const serverHasActiveWork = (data.tasks || []).some(t => + ['downloading', 'searching', 'queued', 'pending', 'post_processing'].includes(t.status)); + if (allTracksFinished && !serverHasActiveWork && process.status !== 'complete') { console.log(`🎯 [Client Completion] All ${totalFinished}/${missingCount} tracks finished - completing modal locally`); // Hide cancel button and mark as complete