From 795a99ae2b055895e986bcd3904cc3635cfcff97 Mon Sep 17 00:00:00 2001 From: James Brechtel Date: Wed, 20 May 2026 11:54:06 -0400 Subject: [PATCH] fix: deduplicate SSE events and debounce client reload --- src/Roux/Html.hs | 15 +++++++++++++-- src/Roux/Server.hs | 13 ++++++++++++- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/Roux/Html.hs b/src/Roux/Html.hs index 43e59af..efd4e35 100644 --- a/src/Roux/Html.hs +++ b/src/Roux/Html.hs @@ -344,8 +344,13 @@ sseRecipeJs = , "if (!el) return;" , "var currentRecipe = el.textContent;" , "var es = new EventSource('/events');" + , "var lastReload = 0;" , "es.addEventListener('recipe-changed', function(e) {" - , " if (e.data === currentRecipe) location.reload();" + , " if (e.data !== currentRecipe) return;" + , " var now = Date.now();" + , " if (now - lastReload < 2000) return;" + , " lastReload = now;" + , " location.reload();" , "});" , "})();" ] @@ -358,7 +363,13 @@ sseIndexJs = , "'use strict';" , "if (!window.EventSource) return;" , "var es = new EventSource('/events');" - , "es.addEventListener('recipe-changed', function() { location.reload(); });" + , "var lastReload = 0;" + , "es.addEventListener('recipe-changed', function() {" + , " var now = Date.now();" + , " if (now - lastReload < 2000) return;" + , " lastReload = now;" + , " location.reload();" + , "});" , "})();" ] diff --git a/src/Roux/Server.hs b/src/Roux/Server.hs index f344c30..45e0861 100644 --- a/src/Roux/Server.hs +++ b/src/Roux/Server.hs @@ -115,7 +115,18 @@ sseHandler changeLog _request respond = do sent <- readIORef lastSent when (version > sent) $ do let newEntries = takeWhile (\(v, _) -> v > sent) entries - forM_ newEntries $ \(_v, path) -> do + -- Entries are newest-first. Deduplicate by filename, + -- keeping the newest (first encountered) entry per file. + let deduped = + foldl' + ( \acc e@(_, p) -> + if any ((== p) . snd) acc + then acc + else e : acc + ) + [] + newEntries + forM_ deduped $ \(_v, path) -> do write ( lazyByteString $ LB.fromStrict $