From 66e427dfa4a7eeb54addd4ff2be70ca99e23b1e5 Mon Sep 17 00:00:00 2001 From: npeter83 Date: Tue, 27 Oct 2026 22:43:17 +0100 Subject: [PATCH] fix(nav): a search the address opened marks its entry, so a channel over it keeps it A search reached by a reload or a link had no _yt mark on its entry, so a channel opened from its results carried none, and a landing back on that channel (an overlay over it closing) ended the search under it. --- .../features/navigation/NavigationProvider.tsx | 13 ++++++++----- .../NavigationProvider.ytSearch.dom.test.tsx | 16 ++++++++++++++++ 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/frontend/src/features/navigation/NavigationProvider.tsx b/frontend/src/features/navigation/NavigationProvider.tsx index 810497c8..25d6ea88 100644 --- a/frontend/src/features/navigation/NavigationProvider.tsx +++ b/frontend/src/features/navigation/NavigationProvider.tsx @@ -296,9 +296,10 @@ export function NavigationProvider({ children }: { children: ReactNode }) { // then sync state from history.state on popstate — the browser restores each entry's URL for us, so // the popstate handler only needs to read history.state. useEffect(() => { - // The entry's `_yt` mark stays while the address still names its search (a reload of it); a mark - // left without one (an older build's entry) goes. _chan/_chanName are always kept so a channel - // page survives F5. + // The entry of a search the address names carries its `_yt` mark, whichever way it opened (sent + // from the box, a reload, a link): a channel opened from its results reads the search under it + // from that mark. A mark left without one (an older build's entry) goes. _chan/_chanName are + // always kept so a channel page survives F5. const st = window.history.state || {}; // Mirror the resolved initial page to the address bar (the feed too) so a page reached from the // stored landing pref — not just from the URL — is itself bookmarkable. urlWithParams keeps the @@ -316,8 +317,10 @@ export function NavigationProvider({ children }: { children: ReactNode }) { ...retiredParams(linked ?? (resolvePage(remembered) === mountPage ? remembered : null)), page: mountPage, }); - if (st._yt && ytSearchFrom(new URLSearchParams(window.location.search)) != null) { - window.history.replaceState({ ...st, sfPage: mountPage }, "", url); + const named = + mountPage === "feed" ? ytSearchFrom(new URLSearchParams(window.location.search)) : null; + if (named != null) { + window.history.replaceState({ ...st, _yt: named, sfPage: mountPage }, "", url); } else { const { _yt: _staleYt, _ytScrape: _staleScrape, ...rest } = st; window.history.replaceState({ ...rest, sfPage: mountPage }, "", url); diff --git a/frontend/src/features/navigation/NavigationProvider.ytSearch.dom.test.tsx b/frontend/src/features/navigation/NavigationProvider.ytSearch.dom.test.tsx index fd610561..cc41603b 100644 --- a/frontend/src/features/navigation/NavigationProvider.ytSearch.dom.test.tsx +++ b/frontend/src/features/navigation/NavigationProvider.ytSearch.dom.test.tsx @@ -138,6 +138,12 @@ describe("NavigationProvider · the YouTube search in the address", () => { expect(window.history.state?._yt).toBe("lofi"); }); + it("marks the entry of a search the address opened (a reload, a link)", () => { + window.history.replaceState({ sfPage: "feed" }, "", "/?page=feed&yt=lofi"); + render(); + expect(window.history.state?._yt).toBe("lofi"); + }); + it("drops an older build's search mark the address does not name", () => { window.history.replaceState( { sfPage: "feed", _yt: "lofi", _ytScrape: true }, @@ -255,6 +261,16 @@ describe("NavigationProvider · the YouTube search in the address", () => { expect(result.current.ytSearchAsked).toBe(true); }); + it("a landing on a channel opened from a search the address opened keeps the search", () => { + window.history.replaceState({ sfPage: "feed" }, "", "/?page=feed&yt=lofi"); + const { result } = render(); + act(() => result.current.openChannel("UC1")); + act(() => { + window.dispatchEvent(new PopStateEvent("popstate", { state: window.history.state })); + }); + expect(result.current.ytSearch).toBe("lofi"); + }); + it("a channel closed in place over the search names the search again", () => { const { result } = render(); act(() => result.current.enterYtSearch("lofi"));