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).