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