fix(plex): genre facet offers only co-occurring genres in AND mode

The adaptive genre facet always self-excluded the genre filter ({**p,"genres":
None}). That's right for "any"/OR mode — each offered genre widens the result —
but wrong for "all"/AND mode, where adding a genre narrows: it kept showing
genres that don't co-occur with the current selection, so clicking them ANDed the
result to zero (dead-end chips). Now self-exclude only in "any" mode; in "all"
mode keep the filter applied so the facet returns only genres present on the
current result set.

Guard the empty-AND case: a zero-match combination would return facets.genres=[],
and the sidebar hides the whole genre section (chips + Any/All toggle) when it's
empty — trapping the user with no per-chip way to undo the selection. Always emit
the actively-selected genres (count 0 when dropped) so they stay visible and
removable.
This commit is contained in:
npeter83 2026-07-11 03:41:17 +02:00
parent 5759deac20
commit 4423e8464e

View file

@ -672,10 +672,15 @@ def facets(
def per_table(model, ids, kind_movie: bool) -> None:
if not ids:
return
# Genres — exclude the genre filter itself so you can keep adding genres.
# Genres — in "any"/OR mode, exclude the genre filter itself so each offered genre is a valid
# OR-addition that only widens (keep adding). In "all"/AND mode, KEEP the filter applied so we
# offer only genres that CO-OCCUR on the current result set — a non-co-occurring genre would AND
# the result down to zero, i.e. a dead-end chip. (The already-selected genres still appear, since
# every matching title carries them, so they stay togglable.)
genre_pp = p if p.get("genre_mode") == "all" else {**p, "genres": None}
expanded = wsq(
db.query(func.jsonb_array_elements_text(model.genres).label("g")),
model, ids, {**p, "genres": None}, kind_movie, True,
model, ids, genre_pp, kind_movie, True,
).subquery()
for g, c in db.query(expanded.c.g, func.count()).group_by(expanded.c.g).all():
genre_counts[g] = genre_counts.get(g, 0) + c
@ -711,6 +716,12 @@ def facets(
per_table(PlexItem, movie_ids, True)
per_table(PlexShow, show_ids, False)
# Always surface the actively-selected genres (even a zero-match AND combo, where they'd otherwise
# be absent): the sidebar renders genres solely from this list and hides the whole genre section —
# chips AND the Any/All toggle — when it's empty, which would trap the user with no per-chip way to
# undo a zero-result selection. Count 0 is fine (genre chips don't display counts).
for g in _csv(genres):
genre_counts.setdefault(g, 0)
return {
"genres": [{"value": g, "count": c} for g, c in sorted(genre_counts.items(), key=lambda kv: kv[0])],
"content_ratings": [{"value": cr, "count": c} for cr, c in sorted(cr_counts.items(), key=lambda kv: -kv[1])],