feat(search): live YouTube search UI

Surface live YouTube search in the existing feed, triggered explicitly so the
expensive API call is never per-keystroke.

- Header: the search box still filters the local catalog as you type; Enter or a
  YouTube button escalates the term to a live search (hidden for the demo account).
- Feed: a dedicated infinite query renders results in the same VirtualFeed cards +
  in-app player, under a banner with a back button and a quota note. No auto-paginate
  (each page spends 100 units) — an explicit 'Load more (uses quota)' button instead;
  quota/limit errors (incl. 429) shown inline. The empty local feed offers a
  'Search YouTube for <q>' CTA.
- Library: a 'Search-discovered' toggle reveals search-ingested videos (hidden by
  default); sent as exclude_search_discovered.
- Admin: search_daily_limit_per_user config field; new videos_search quota label.
- All new strings translated in HU/EN/DE.
This commit is contained in:
npeter83 2026-06-29 02:01:44 +02:00
parent 9b1bdb6b42
commit 546be57963
16 changed files with 240 additions and 15 deletions

View file

@ -19,6 +19,7 @@
"smtp_password": { "label": "SMTP password", "hint": "App password. Stored encrypted; write-only — it's never shown back." },
"quota_daily_budget": { "label": "Daily quota budget", "hint": "Total YouTube Data API units to spend per day (free tier is 10,000). Default 9000 leaves headroom." },
"backfill_quota_reserve": { "label": "Backfill quota reserve", "hint": "Units kept in reserve so scheduled backfill never starves interactive syncs / enrichment." },
"search_daily_limit_per_user": { "label": "Live search — daily limit per user", "hint": "Max live YouTube searches per user per day. Each search costs 100 units, so the shared budget only affords ~80-90/day total. Set 0 to disable live search." },
"backfill_recent_max_videos": { "label": "Recent backfill — max videos", "hint": "Newest videos fetched per channel on the first pass." },
"backfill_recent_max_days": { "label": "Recent backfill — max age (days)", "hint": "How far back the first pass reaches per channel." },
"shorts_probe_max_seconds": { "label": "Shorts probe — max duration (s)", "hint": "Only videos at or below this length are probed as possible Shorts." },