diff --git a/frontend/src/components/NavigationProvider.dom.test.tsx b/frontend/src/components/NavigationProvider.dom.test.tsx index 0d19f2ce..fe29cf5d 100644 --- a/frontend/src/components/NavigationProvider.dom.test.tsx +++ b/frontend/src/components/NavigationProvider.dom.test.tsx @@ -11,6 +11,7 @@ // bookkeeping. import { describe, it, expect, afterEach, beforeEach, vi } from "vitest"; import { renderHook, act, cleanup } from "@testing-library/react"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import type { ReactNode } from "react"; // Partial: the provider reads `t` AND `i18n.language` (both feed the document-title effect), and the @@ -28,8 +29,14 @@ import { NavigationProvider } from "./NavigationProvider"; import { useNavigation, useNavigationActions } from "./navigationContext"; import { useBackToClose } from "../lib/history"; +import { qk } from "../lib/queryKeys"; + +// The provider reads the signed-in account (the tab title) from the `me` cache. +let qc = new QueryClient(); const wrapper = ({ children }: { children: ReactNode }) => ( - {children} + + {children} + ); const render = () => renderHook(() => ({ ...useNavigation(), ...useNavigationActions() }), { wrapper }); @@ -54,6 +61,8 @@ async function openOverlay() { } beforeEach(() => { + qc = new QueryClient(); + qc.setQueryData(qk.me(), { id: 1 }); localStorage.clear(); sessionStorage.setItem("siftlode.activeAccount", "1"); // account-scoped page memory window.history.replaceState({ sfPage: "feed" }, "", "/"); @@ -65,6 +74,22 @@ afterEach(() => { vi.restoreAllMocks(); }); +describe("NavigationProvider · the tab title", () => { + // Prod UAT 2026-09-19: signed out, the tab read the Home page's question over the sign-in form. + it("is the brand alone while nobody is signed in, the page's name once someone is", async () => { + window.history.replaceState({ sfPage: "home" }, "", "/?page=home"); + qc.setQueryData(qk.me(), null); + render(); + await settle(); + expect(document.title).toBe("Siftlode"); + + act(() => qc.setQueryData(qk.me(), { id: 1 })); + await settle(); + expect(document.title).not.toBe("Siftlode"); + expect(document.title.endsWith(" · Siftlode")).toBe(true); + }); +}); + describe("NavigationProvider · setPage's same-page guard", () => { it("pushes an entry for a real page change", () => { const { result } = render(); diff --git a/frontend/src/components/NavigationProvider.tsx b/frontend/src/components/NavigationProvider.tsx index 82faf9a8..b196bcce 100644 --- a/frontend/src/components/NavigationProvider.tsx +++ b/frontend/src/components/NavigationProvider.tsx @@ -1,5 +1,6 @@ import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from "react"; import { useTranslation } from "react-i18next"; +import { useMe } from "../lib/useMe"; import { urlWithParams, type Page } from "../lib/urlState"; import { LOGS_KEYS } from "../lib/logsUrlState"; import { pageTitleKey } from "../lib/pageMeta"; @@ -154,16 +155,20 @@ export function NavigationProvider({ children }: { children: ReactNode }) { }, [page]); // Reflect the current module in the browser tab title. Feed = brand only; an open channel page - // shows the channel name. + // shows the channel name. Signed out, the page on screen is the public landing whatever `page` + // says — the tab read "What should I watch?" over the sign-in form (prod UAT 2026-09-19). + const signedIn = useMe() != null; useEffect(() => { const brand = "Siftlode"; - const label = channelView - ? channelView.name || t("header.channelManager") - : page === "feed" - ? null - : t(pageTitleKey(page)); + const label = !signedIn + ? null + : channelView + ? channelView.name || t("header.channelManager") + : page === "feed" + ? null + : t(pageTitleKey(page)); document.title = label ? `${label} · ${brand}` : brand; - }, [page, channelView, i18n.language, t]); + }, [signedIn, page, channelView, i18n.language, t]); // In-app Back/Forward: stamp the initial entry with the current page (mirrored to the address bar), // then sync state from history.state on popstate — the browser restores each entry's URL for us, so diff --git a/frontend/src/components/legal/LegalLayout.tsx b/frontend/src/components/legal/LegalLayout.tsx index 5b0d30bd..e34b163a 100644 --- a/frontend/src/components/legal/LegalLayout.tsx +++ b/frontend/src/components/legal/LegalLayout.tsx @@ -71,6 +71,11 @@ export default function LegalLayout({ title, children }: { title: string; childr const id = decodeURIComponent(window.location.hash.slice(1)); if (id) document.getElementById(id)?.scrollIntoView(); }, []); + // The tab names the document, like every page of the app — a bookmark or a tab strip read just + // "Siftlode" (prod UAT 2026-09-19). + useEffect(() => { + document.title = `${title} · Siftlode`; + }, [title]); return (
diff --git a/frontend/src/i18n/locales/en/downloads.json b/frontend/src/i18n/locales/en/downloads.json index 1e750cba..adc16396 100644 --- a/frontend/src/i18n/locales/en/downloads.json +++ b/frontend/src/i18n/locales/en/downloads.json @@ -18,7 +18,7 @@ "page": { "title": "Downloads", "subtitle": "Download videos to the server, then save them to your device.", - "addUrl": "Paste a YouTube link or video id…", + "addUrl": "Paste a video link (YouTube, Facebook, …) or a YouTube video id…", "add": "Add", "paste": "Paste link from clipboard", "upload": "Upload", diff --git a/frontend/src/i18n/locales/hu/downloads.json b/frontend/src/i18n/locales/hu/downloads.json index 7c1ec57e..00362e97 100644 --- a/frontend/src/i18n/locales/hu/downloads.json +++ b/frontend/src/i18n/locales/hu/downloads.json @@ -18,7 +18,7 @@ "page": { "title": "Letöltések", "subtitle": "Töltsd le a videókat a szerverre, majd mentsd a saját gépedre.", - "addUrl": "Illessz be egy YouTube linket vagy videó azonosítót…", + "addUrl": "Illessz be egy videólinket (YouTube, Facebook, …) vagy YouTube-videóazonosítót…", "add": "Hozzáad", "paste": "Link beillesztése a vágólapról", "upload": "Feltöltés",