feat(search): add Media/Audiobooks/All scope with remembered per-user default

Search previously mixed audiobooks into movie/series results with no way to
separate them beyond single-type filters.

Backend: accept a new "video" group media scope (movies + series) anywhere a
media_scope is valid, expanded centrally via MediaScopeItemTypes into the
search item-type list, browse comma-list Type filter, and a type = ANY(...)
condition in the query executor. Register a user-scoped search.media_scope
setting (all|video|audiobook, default video).

Frontend: Media / Audiobooks / All chips on the search results page that
filter results and persist the choice as the user's default; the global
search typeahead follows the same preference. An explicit URL ?type= always
wins (with type=all as an unscoped sentinel), and the filter-bar dropdown
gains a Movies & Series option.

The API surface is additive, so Android/Apple clients are unaffected until
they adopt the new scope.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Quick
2026-06-09 15:46:11 -04:00
co-authored by Claude Fable 5
parent adbdd86ac5
commit 2fe2c918f4
16 changed files with 424 additions and 49 deletions
+3 -2
View File
@@ -1174,7 +1174,7 @@ export interface QuerySort {
export interface QueryDefinition {
library_ids: number[];
media_scope?: "movie" | "series" | "episode" | "audiobook";
media_scope?: "movie" | "series" | "episode" | "audiobook" | "video";
match: "all" | "any";
groups: QueryGroup[];
sort: QuerySort;
@@ -3129,7 +3129,8 @@ export function normalizeQueryDefinition(value?: QueryDefinitionInput | null): Q
value?.media_scope === "movie" ||
value?.media_scope === "series" ||
value?.media_scope === "episode" ||
value?.media_scope === "audiobook"
value?.media_scope === "audiobook" ||
value?.media_scope === "video"
? value.media_scope
: undefined,
match: value?.match === "any" ? "any" : "all",