From cec481741b59a3a63ee502346c71a30330995015 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Fromme?= Date: Fri, 20 Feb 2026 17:24:20 +0100 Subject: [PATCH] fix: prevent race conditions in htmx boost response handling --- assets/loading.js | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/assets/loading.js b/assets/loading.js index 62e5650..40ee364 100644 --- a/assets/loading.js +++ b/assets/loading.js @@ -6,6 +6,7 @@ const DEBOUNCE_MS = 200 let debounceTimeout = null let historyRestoreGracePeriod = false let savedScrollPosition = null +let activeRequests = 0 function getIndicator() { return document.getElementById(INDICATOR_ID) @@ -35,6 +36,8 @@ function handleBeforeRequest(event) { return } + activeRequests++ + // Save scroll position of the target container before HTMX request const target = event.detail?.target || document.getElementById('main-content') if (target && typeof target.scrollTop === 'number') { @@ -52,6 +55,19 @@ function handleBeforeRequest(event) { }, DEBOUNCE_MS) } +function handleRequestEnd() { + // Counter may already be 0 if a force-reset (history save, page show) fired + // before this afterRequest — ignore the stale completion in that case + if (0 === activeRequests) { + return + } + + activeRequests-- + if (0 === activeRequests) { + hide() + } +} + function handleAfterSwap() { // Restore scroll position after HTMX swaps content if (savedScrollPosition !== null && savedScrollPosition.element) { @@ -61,6 +77,7 @@ function handleAfterSwap() { } function handleHistoryRestore() { + activeRequests = 0 hide() // Set grace period to ignore change events triggered by history restore @@ -71,24 +88,29 @@ function handleHistoryRestore() { } function handleTimeout() { - hide() + // Don't decrement here — htmx:afterRequest always fires alongside htmx:timeout + // and handles the counter. This handler only shows the alert. alert('Die Anfrage hat zu lange gedauert. Bitte versuchen Sie es erneut. Falls das Problem weiterhin besteht, kontaktieren Sie bitte unseren Support.') } function handleBeforeHistorySave() { // Hide indicator before HTMX saves the page to history cache + activeRequests = 0 + hide() +} + +function handlePageShow() { + activeRequests = 0 hide() } // Initialize event listeners document.body.addEventListener('htmx:beforeRequest', handleBeforeRequest) -document.body.addEventListener('htmx:afterRequest', hide) +document.body.addEventListener('htmx:afterRequest', handleRequestEnd) document.body.addEventListener('htmx:afterSwap', handleAfterSwap) document.body.addEventListener('htmx:timeout', handleTimeout) document.body.addEventListener('htmx:historyRestore', handleHistoryRestore) -document.body.addEventListener('htmx:sendError', hide) -document.body.addEventListener('htmx:responseError', hide) document.body.addEventListener('htmx:beforeHistorySave', handleBeforeHistorySave) -window.addEventListener('pageshow', hide) -window.addEventListener('popstate', handleHistoryRestore) \ No newline at end of file +window.addEventListener('pageshow', handlePageShow) +window.addEventListener('popstate', handleHistoryRestore)