From 3d8015f3a9c979b8fd4278a1cd060561947642b3 Mon Sep 17 00:00:00 2001 From: npeter83 Date: Wed, 1 Jul 2026 01:42:00 +0200 Subject: [PATCH] fix(history): stop the next overlay's first Back being swallowed after a button/ESC close MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit useBackToClose closes an overlay programmatically (X button or ESC) by setting the module-global suppressPop=true and calling history.back(), so the remaining overlays' popstate handlers ignore that synthetic pop. But the flag was only ever reset by one of those handlers — when the closed overlay had no overlay underneath it (the common single-player case), nothing consumed it and suppressPop leaked true. The next overlay's first real Back then hit the stale true, reset it and returned early without closing — the Back was silently swallowed. Symptom in the YouTube-search flow: close the player with the X button, open another, press Back once -> nothing happens; a second Back closes it AND discards the search results, dumping you on the normal feed. Fix: register a one-shot popstate listener alongside suppressPop=true so the flag is cleared on that very pop even when no overlay remains to consume it. Nesting is unaffected (an underlying overlay still short- circuits first and the one-shot is a redundant no-op). --- frontend/src/lib/history.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/frontend/src/lib/history.ts b/frontend/src/lib/history.ts index 46362c8..270fff2 100644 --- a/frontend/src/lib/history.ts +++ b/frontend/src/lib/history.ts @@ -66,9 +66,17 @@ export function useBackToClose(onClose: () => void): void { const idx = overlayStack.lastIndexOf(token); if (idx !== -1) { // Closed programmatically (not via Back): drop our own history entry, and tell the - // remaining overlays' handlers to ignore the resulting popstate. + // remaining overlays' handlers to ignore the resulting popstate. A one-shot listener + // clears the flag on that very popstate even when NO overlay remains underneath to + // consume it — otherwise suppressPop leaks `true` and silently swallows the NEXT + // overlay's first Back (reopen-after-button-close → first Back does nothing). overlayStack.splice(idx, 1); suppressPop = true; + const clearSuppress = () => { + suppressPop = false; + window.removeEventListener("popstate", clearSuppress); + }; + window.addEventListener("popstate", clearSuppress); window.history.back(); } };