From 8b19faaed1ce1a5b82032c0985d7a743a917d805 Mon Sep 17 00:00:00 2001 From: npeter83 Date: Mon, 29 Jun 2026 02:11:53 +0200 Subject: [PATCH] feat(search): Library provenance filter (organic / all / search-only) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the binary 'show search-discovered' toggle with a 3-way Source selector in the Library toolbar, so users can also see ONLY search-discovered videos — not just hide or mix them. Backend: feed param library_source = organic (default, hides via_search) | all (both) | search (only via_search), applied in scope=all. Strings in HU/EN/DE. --- backend/app/routes/feed.py | 21 ++++++++++------- frontend/src/components/Feed.tsx | 32 +++++++++++++++----------- frontend/src/i18n/locales/de/feed.json | 9 ++++++-- frontend/src/i18n/locales/en/feed.json | 9 ++++++-- frontend/src/i18n/locales/hu/feed.json | 9 ++++++-- frontend/src/lib/api.ts | 10 ++++---- 6 files changed, 57 insertions(+), 33 deletions(-) diff --git a/backend/app/routes/feed.py b/backend/app/routes/feed.py index 7151960..d9f405b 100644 --- a/backend/app/routes/feed.py +++ b/backend/app/routes/feed.py @@ -124,7 +124,7 @@ def _filtered_query( include_live: bool, show: str, scope: str = "my", - exclude_search_discovered: bool = True, + library_source: str = "organic", exclude_tag_category: str | None = None, ) -> tuple[Select, object]: """Build the feed query (joins + all WHERE filters), shared by /feed and /feed/count. @@ -172,11 +172,16 @@ def _filtered_query( # either way they shouldn't show in any feed view meanwhile. query = query.where(Video.unavailable_since.is_(None)) - # Hide videos that only entered the catalog via a live YouTube search (search-discovered - # "noise") from the Library by default. Only meaningful in "all" scope: "my" already - # restricts to subscribed channels, where a once-searched video is legitimately the user's. - if scope == "all" and exclude_search_discovered: - query = query.where(Video.via_search.is_(False)) + # Provenance filter for the Library: search-discovered videos are "noise" hidden by + # default ("organic"), can be mixed in ("all"), or shown exclusively ("search"). Only + # meaningful in "all" scope: "my" already restricts to subscribed channels, where a + # once-searched video is legitimately the user's. + if scope == "all": + if library_source == "organic": + query = query.where(Video.via_search.is_(False)) + elif library_source == "search": + query = query.where(Video.via_search.is_(True)) + # "all" → both organic and search-discovered videos if channel_id: query = query.where(Video.channel_id == channel_id) @@ -278,7 +283,7 @@ def _feed_params( include_live: bool = False, show: str = "unwatched", scope: str = "my", - exclude_search_discovered: bool = True, + library_source: str = "organic", ) -> dict: return { "tags": tags, @@ -295,7 +300,7 @@ def _feed_params( "include_live": include_live, "show": show, "scope": scope, - "exclude_search_discovered": exclude_search_discovered, + "library_source": library_source, } diff --git a/frontend/src/components/Feed.tsx b/frontend/src/components/Feed.tsx index 06e473c..ac289ea 100644 --- a/frontend/src/components/Feed.tsx +++ b/frontend/src/components/Feed.tsx @@ -417,20 +417,24 @@ export default function Feed({ {filters.scope === "all" && ( <>