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.
This commit is contained in:
2026-10-27 22:43:17 +01:00
parent e2b725dba7
commit 66e427dfa4
2 changed files with 24 additions and 5 deletions
@@ -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);
@@ -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"));