chore(feed-ui): Phase 2 #2 frontend cleanup — hoist, drop wrapper, dedup caches

- Hoist withOverrides(loaded) into `merged` (was mapped twice per render for
  items and playerQueue).
- Remove the needless always-true {(<>…</>)} expression wrapper around the
  Source selector.
- Extract dropFeedCaches() — the feed/feed-count/facets removeQueries trio was
  duplicated across the back and clear-now handlers.

Behavior-neutral. tsc green.
This commit is contained in:
npeter83 2026-07-11 17:37:52 +02:00
parent 505f0e5650
commit 3cbb420ece

View file

@ -310,12 +310,21 @@ export default function Feed({
list
.map((v) => (overrides[v.id] ? { ...v, status: overrides[v.id] } : v))
.map((v) => (savedOverrides[v.id] !== undefined ? { ...v, saved: savedOverrides[v.id] } : v));
const items: Video[] = withOverrides(loaded).filter((v) => matchesView(v.status, filters.show));
const merged: Video[] = withOverrides(loaded);
const items: Video[] = merged.filter((v) => matchesView(v.status, filters.show));
// The in-app player's queue (auto-advance / loop / prev-next). It's the filtered feed, but the
// watch-state view filter is NOT applied — so marking the current video watched keeps it in the
// sequence (no reindex/reload mid-play), matching "watched doesn't affect the list". A video that
// goes hidden still drops out ("visible affects").
const playerQueue: Video[] = withOverrides(loaded).filter((v) => v.status !== "hidden");
const playerQueue: Video[] = merged.filter((v) => v.status !== "hidden");
// A live search ingests new catalog videos, so the disabled feed queries still hold their stale
// pre-search cache. Drop them so the feed re-fetches fresh on return.
const dropFeedCaches = () => {
qc.removeQueries({ queryKey: ["feed"] });
qc.removeQueries({ queryKey: ["feed-count"] });
qc.removeQueries({ queryKey: ["facets"] });
};
// --- Live YouTube search mode -------------------------------------------------------------
// A separate data source rendered in the same cards/player. No watch-state view filter (the
@ -341,16 +350,11 @@ export default function Feed({
<div className="flex items-center gap-2 flex-wrap">
<button
onClick={() => {
// The search just ingested new catalog videos, so the normal feed (which was
// 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" (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"] });
qc.removeQueries({ queryKey: ["facets"] });
dropFeedCaches();
}}
className="inline-flex items-center gap-1.5 text-sm text-muted hover:text-accent transition shrink-0"
>
@ -394,9 +398,7 @@ export default function Feed({
.catch(() => {});
setFilters({ ...filters, librarySource: "organic", sort: "newest" });
onExitYtSearch();
qc.removeQueries({ queryKey: ["feed"] });
qc.removeQueries({ queryKey: ["feed-count"] });
qc.removeQueries({ queryKey: ["facets"] });
dropFeedCaches();
qc.removeQueries({ queryKey: ["yt-search"] });
}}
className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full border border-border text-muted hover:text-danger hover:border-danger transition"
@ -541,29 +543,25 @@ export default function Feed({
</button>
);
})}
{(
<>
<span className="mx-1 h-5 w-px bg-border" aria-hidden="true" />
<label className="inline-flex items-center gap-1.5 text-xs text-muted">
<span>{t("feed.source.label")}</span>
<select
value={filters.librarySource ?? "organic"}
onChange={(e) =>
setFilters({
...filters,
librarySource: e.target.value as FeedFilters["librarySource"],
})
}
title={t("feed.source.tip")}
className="bg-card border border-border rounded-lg px-2 py-1 text-xs outline-none focus:border-accent"
>
<option value="organic">{t("feed.source.organic")}</option>
<option value="all">{t("feed.source.all")}</option>
<option value="search">{t("feed.source.only")}</option>
</select>
</label>
</>
)}
<span className="mx-1 h-5 w-px bg-border" aria-hidden="true" />
<label className="inline-flex items-center gap-1.5 text-xs text-muted">
<span>{t("feed.source.label")}</span>
<select
value={filters.librarySource ?? "organic"}
onChange={(e) =>
setFilters({
...filters,
librarySource: e.target.value as FeedFilters["librarySource"],
})
}
title={t("feed.source.tip")}
className="bg-card border border-border rounded-lg px-2 py-1 text-xs outline-none focus:border-accent"
>
<option value="organic">{t("feed.source.organic")}</option>
<option value="all">{t("feed.source.all")}</option>
<option value="search">{t("feed.source.only")}</option>
</select>
</label>
<span className="mx-1 h-5 w-px bg-border" aria-hidden="true" />
<span className="text-xs text-muted whitespace-nowrap">
{countQuery.data