From 4423e8464ec98a3373a341f44ed5b8de87ccf3d1 Mon Sep 17 00:00:00 2001 From: npeter83 Date: Sat, 11 Jul 2026 03:41:17 +0200 Subject: [PATCH] fix(plex): genre facet offers only co-occurring genres in AND mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- backend/app/routes/plex.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/backend/app/routes/plex.py b/backend/app/routes/plex.py index db007b5..af1a177 100644 --- a/backend/app/routes/plex.py +++ b/backend/app/routes/plex.py @@ -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])],