diff --git a/frontend/src/components/Feed.tsx b/frontend/src/components/Feed.tsx index 13ac40a..7205633 100644 --- a/frontend/src/components/Feed.tsx +++ b/frontend/src/components/Feed.tsx @@ -137,19 +137,15 @@ export default function Feed({ retry: false, }); - // When a search term first appears, rank the local feed by relevance (YouTube-like); when - // it's cleared, fall back to the default newest sort. Only fires on the empty↔non-empty - // transition, so it never overrides a sort the user picks while a query stays active. + // Switching to the relevance sort when a search starts happens atomically in the header's + // input onChange (race-free with the query update). Here we only handle the reverse: when + // the term is cleared, fall back to the default sort — relevance has no dropdown option and + // doesn't rank without a query. const qEmpty = !filters.q.trim(); - const prevQEmptyRef = useRef(qEmpty); useEffect(() => { - const sortKeyNow = parseSort(filters.sort).key; - if (prevQEmptyRef.current && !qEmpty && sortKeyNow !== "relevance") { - setFilters({ ...filters, sort: "relevance" }); - } else if (!prevQEmptyRef.current && qEmpty && sortKeyNow === "relevance") { + if (qEmpty && parseSort(filters.sort).key === "relevance") { setFilters({ ...filters, sort: buildSort("date", "desc") }); } - prevQEmptyRef.current = qEmpty; // eslint-disable-next-line react-hooks/exhaustive-deps }, [qEmpty]); @@ -316,8 +312,8 @@ export default function Feed({ // disabled and still holds its pre-search cache) is stale. Drop those caches so // it re-fetches fresh on return instead of showing the old (often empty) result. // Surface the just-searched videos in the feed you return to: switch the Source - // filter to "search" so your own search finds show (filtered by the kept term). - setFilters({ ...filters, librarySource: "search" }); + // filter to "search" (your own search finds) and rank them by relevance. + setFilters({ ...filters, librarySource: "search", sort: "relevance" }); onExitYtSearch(); qc.removeQueries({ queryKey: ["feed"] }); qc.removeQueries({ queryKey: ["feed-count"] }); diff --git a/frontend/src/components/Header.tsx b/frontend/src/components/Header.tsx index c1d8a9c..5291e92 100644 --- a/frontend/src/components/Header.tsx +++ b/frontend/src/components/Header.tsx @@ -67,7 +67,14 @@ export default function Header({ setFilters({ ...filters, q: e.target.value })} + onChange={(e) => { + const q = e.target.value; + // When a search first appears, rank the feed by relevance — set atomically with + // the query (race-free vs. per-keystroke updates). Only overrides the default + // "newest" sort; a custom sort the user chose is left alone. Cleared in Feed. + const startSearch = !filters.q.trim() && !!q.trim() && filters.sort === "newest"; + setFilters({ ...filters, q, ...(startSearch ? { sort: "relevance" } : {}) }); + }} onKeyDown={(e) => { // Enter runs a live YouTube search for the typed term (the box itself filters // the local catalog as you type; Enter escalates to the YouTube API).