From 003aac8769560e12151b93968e3ff8264343cd1e Mon Sep 17 00:00:00 2001 From: Mike Mulhearn Date: Wed, 26 Nov 2025 23:00:45 -0800 Subject: [PATCH 1/2] fix: Fix slides from getting stuck on live photos caused by polling loop --- frontend/src/ts/kiosk.ts | 55 +++++++++++++++++++++++ frontend/src/ts/polling.ts | 5 +++ internal/templates/views/views_home.templ | 1 - 3 files changed, 60 insertions(+), 1 deletion(-) diff --git a/frontend/src/ts/kiosk.ts b/frontend/src/ts/kiosk.ts index 91bdcd59..7aa5460d 100644 --- a/frontend/src/ts/kiosk.ts +++ b/frontend/src/ts/kiosk.ts @@ -137,6 +137,21 @@ async function init(): Promise { const MILLISECONDS_PER_SECOND = 1000; const TIMEOUT_GRACE_FACTOR = 3; + document.body.addEventListener( + "htmx:afterRequest", + ((e: Event) => { + const detail = (e as any).detail; + const path = detail?.pathInfo?.requestPath; + const status = detail?.xhr?.status; + + console.debug("[DEBUG] htmx:afterRequest", { + path, + status, + successful: detail?.successful, + }); + }) as EventListener, + ); + if (kioskData.httpTimeout <= 0) { htmx.config.timeout = 0; } else { @@ -288,6 +303,20 @@ function addEventListeners(): void { htmx.addClass(offlineSVG, "offline"); } }); + + // Slideshow polling control. Fires after every AJAX request. + // Only (re)start polling when a new asset has been loaded + htmx.on("htmx:afterRequest", (e: HTMXEvent) => { + const path = e.detail?.pathInfo?.requestPath || ""; + + // Debug so we can see exactly when this runs + console.debug("[DEBUG] kiosk polling afterRequest", { path }); + + // Only restart polling for asset endpoints (new slide) + if (path.startsWith("/asset/")) { + startPolling(); + } + }); htmx.on("htmx:timeout", (e: HTMXEvent) => { let currentTimeout = timeouts[e.detail.pathInfo.requestPath]; @@ -416,6 +445,15 @@ async function cleanupFrames(): Promise { * @throws {Error} If request lock is already set */ function setRequestLock(e: HTMXEvent): void { + const path = e.detail?.pathInfo?.requestPath || ""; + + console.debug("[DEBUG] setRequestLock for asset request", { path }); + + // Only lock and pause polling for asset requests (new slide) + if (!path.startsWith("/asset/")) { + return; + } + if (requestInFlight) { e.preventDefault(); return; @@ -441,6 +479,22 @@ function releaseRequestLock(): void { requestInFlight = false; } +/** + * Only starts polling for asset events after a request completes + * @description Request event handling function that: + * - Starts polling only if there is an asset change event + * - Ignores polling events due to live photo failures + */ +function afterRequest(e: HTMXEvent): void { + const path = e.detail?.pathInfo?.requestPath || ""; + + // Only restart polling for asset endpoints + if (path.startsWith("/asset/")) { + startPolling(); + } + +} + /** * Checks if there are enough history entries to navigate back * @param {HTMXEvent} e Event object for the history navigation request @@ -551,4 +605,5 @@ export { clientData, videoHandler, sleepMode, + afterRequest, }; diff --git a/frontend/src/ts/polling.ts b/frontend/src/ts/polling.ts index 00481a92..21060d97 100644 --- a/frontend/src/ts/polling.ts +++ b/frontend/src/ts/polling.ts @@ -126,6 +126,11 @@ class PollingController { * Starts the polling process */ startPolling = () => { + console.debug("[DEBUG] startPolling() called", { + pollInterval: this.pollInterval, + timestamp: performance.now(), + }); + this.progressBarElement?.classList.remove("progress--bar-paused"); this.menuElement?.classList.add("navigation-hidden"); this.lastPollTime = performance.now(); diff --git a/internal/templates/views/views_home.templ b/internal/templates/views/views_home.templ index 5ecb73f5..7b7ecde2 100644 --- a/internal/templates/views/views_home.templ +++ b/internal/templates/views/views_home.templ @@ -34,7 +34,6 @@ templ Home(viewData common.ViewData, secret string) { hx-include=".kiosk-history--entry" hx-trigger={ triggers(viewData.DisableNavigation) } hx-on::before-send="kiosk.setRequestLock(event)" - hx-on::after-request="kiosk.startPolling()" hx-on::after-swap="kiosk.releaseRequestLock(), kiosk.cleanupFrames()" > @partials.Spinner() From aae9cbba39cf5de9881e02092accfd976cc723dc Mon Sep 17 00:00:00 2001 From: Mike Mulhearn Date: Wed, 26 Nov 2025 23:37:42 -0800 Subject: [PATCH 2/2] remove debug logging and template request handling --- frontend/src/ts/kiosk.ts | 37 ------------------------------------- 1 file changed, 37 deletions(-) diff --git a/frontend/src/ts/kiosk.ts b/frontend/src/ts/kiosk.ts index 7aa5460d..441cdd74 100644 --- a/frontend/src/ts/kiosk.ts +++ b/frontend/src/ts/kiosk.ts @@ -137,21 +137,6 @@ async function init(): Promise { const MILLISECONDS_PER_SECOND = 1000; const TIMEOUT_GRACE_FACTOR = 3; - document.body.addEventListener( - "htmx:afterRequest", - ((e: Event) => { - const detail = (e as any).detail; - const path = detail?.pathInfo?.requestPath; - const status = detail?.xhr?.status; - - console.debug("[DEBUG] htmx:afterRequest", { - path, - status, - successful: detail?.successful, - }); - }) as EventListener, - ); - if (kioskData.httpTimeout <= 0) { htmx.config.timeout = 0; } else { @@ -309,9 +294,6 @@ function addEventListeners(): void { htmx.on("htmx:afterRequest", (e: HTMXEvent) => { const path = e.detail?.pathInfo?.requestPath || ""; - // Debug so we can see exactly when this runs - console.debug("[DEBUG] kiosk polling afterRequest", { path }); - // Only restart polling for asset endpoints (new slide) if (path.startsWith("/asset/")) { startPolling(); @@ -447,8 +429,6 @@ async function cleanupFrames(): Promise { function setRequestLock(e: HTMXEvent): void { const path = e.detail?.pathInfo?.requestPath || ""; - console.debug("[DEBUG] setRequestLock for asset request", { path }); - // Only lock and pause polling for asset requests (new slide) if (!path.startsWith("/asset/")) { return; @@ -479,22 +459,6 @@ function releaseRequestLock(): void { requestInFlight = false; } -/** - * Only starts polling for asset events after a request completes - * @description Request event handling function that: - * - Starts polling only if there is an asset change event - * - Ignores polling events due to live photo failures - */ -function afterRequest(e: HTMXEvent): void { - const path = e.detail?.pathInfo?.requestPath || ""; - - // Only restart polling for asset endpoints - if (path.startsWith("/asset/")) { - startPolling(); - } - -} - /** * Checks if there are enough history entries to navigate back * @param {HTMXEvent} e Event object for the history navigation request @@ -605,5 +569,4 @@ export { clientData, videoHandler, sleepMode, - afterRequest, };