From b605c28b32ebd9990e11d1c9780ef83a26fab21f Mon Sep 17 00:00:00 2001 From: npeter83 Date: Mon, 17 Aug 2026 22:18:07 +0200 Subject: [PATCH] =?UTF-8?q?release:=200.66.0=20=E2=80=94=20E5=20Plex=20epi?= =?UTF-8?q?c=20(scoped=20search,=20HU=20localization,=20toolbar/header)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ships the E5 sprint stack (S1–S7): field-scoped Plex search + typeahead, cast & crew pages, the HU-overlay Hungarian metadata + language-aware serving, the IMDb/audience rating split, the toolbar relocation, and the narrow/mobile header fix — plus the underlying Plex-module refactors (feedquery/plex.cards/plex.query extraction, db.query→2.0 select). - VERSION 0.65.2 → 0.66.0 - releaseNotes.ts: 0.66.0 user-facing entry - prettier-format the E5-touched frontend files (release hygiene) Merged main (0.65.2 download-gc hotfix) in first; no code conflicts (E5 is Plex-only, the hotfix was notifications/downloads). --- VERSION | 2 +- frontend/src/components/PlexBrowse.tsx | 10 ++++++++-- .../src/components/PlexSearchBox.dom.test.tsx | 6 +++++- frontend/src/components/PlexSearchBox.tsx | 13 +++---------- frontend/src/components/SearchBar.dom.test.tsx | 4 +--- frontend/src/lib/api/plex.ts | 7 ++++++- frontend/src/lib/plexSearch.ts | 12 ++---------- frontend/src/lib/releaseNotes.ts | 16 ++++++++++++++++ 8 files changed, 42 insertions(+), 28 deletions(-) diff --git a/VERSION b/VERSION index a9ca1dcc..367d4880 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.65.2 \ No newline at end of file +0.66.0 \ No newline at end of file diff --git a/frontend/src/components/PlexBrowse.tsx b/frontend/src/components/PlexBrowse.tsx index a2c38c52..7c8440fc 100644 --- a/frontend/src/components/PlexBrowse.tsx +++ b/frontend/src/components/PlexBrowse.tsx @@ -1327,7 +1327,10 @@ function PlexShowView({ const qc = useQueryClient(); const isAdmin = qc.getQueryData<{ role?: string }>(qk.me())?.role === "admin"; const lang = plexLang(); - const q = useQuery({ queryKey: qk.plexShow(showId, lang), queryFn: () => api.plexShow(showId, lang) }); + const q = useQuery({ + queryKey: qk.plexShow(showId, lang), + queryFn: () => api.plexShow(showId, lang), + }); const d = q.data; const [addTarget, setAddTarget] = useState(null); const [collOpen, setCollOpen] = useState(false); @@ -1565,7 +1568,10 @@ function PlexSeasonView({ const { t } = useTranslation(); const qc = useQueryClient(); const lang = plexLang(); - const q = useQuery({ queryKey: qk.plexShow(showId, lang), queryFn: () => api.plexShow(showId, lang) }); + const q = useQuery({ + queryKey: qk.plexShow(showId, lang), + queryFn: () => api.plexShow(showId, lang), + }); const d = q.data; const se = d?.seasons.find((s) => s.id === seasonId); const [addTarget, setAddTarget] = useState(null); diff --git a/frontend/src/components/PlexSearchBox.dom.test.tsx b/frontend/src/components/PlexSearchBox.dom.test.tsx index 4751a63f..e7a3163a 100644 --- a/frontend/src/components/PlexSearchBox.dom.test.tsx +++ b/frontend/src/components/PlexSearchBox.dom.test.tsx @@ -19,7 +19,11 @@ vi.mock("react-i18next", async (importOriginal) => { vi.mock("../lib/useDebounced", () => ({ useDebounced: (v: unknown) => v })); // Controllable provider — PlexSearchBox reads q/scope/field and calls setQ/setField/setJumpTo. -const plexState = { scope: "both", field: "all", q: "" } as { scope: string; field: string; q: string }; +const plexState = { scope: "both", field: "all", q: "" } as { + scope: string; + field: string; + q: string; +}; // setQ writes back to plexState.q so a typed value flows through to `dq` on the next render (the real // provider persists it) — lets the reopen test assert a fresh query fires on retype, not just that // the panel re-appears from cache. diff --git a/frontend/src/components/PlexSearchBox.tsx b/frontend/src/components/PlexSearchBox.tsx index c6fc3e8f..69905c6a 100644 --- a/frontend/src/components/PlexSearchBox.tsx +++ b/frontend/src/components/PlexSearchBox.tsx @@ -8,11 +8,7 @@ import { usePlex, usePlexActions } from "./PlexProvider"; import { api } from "../lib/api"; import { qk } from "../lib/queryKeys"; import { useDebounced } from "../lib/useDebounced"; -import { - availablePlexFields, - healPlexField, - plexFieldLabelKey, -} from "../lib/plexSearch"; +import { availablePlexFields, healPlexField, plexFieldLabelKey } from "../lib/plexSearch"; // The Plex header search box: the shared SearchBar enriched with the S2 search scope-pill (a // GitHub-style field selector inside the box) and an instant typeahead of the top matches attached @@ -46,8 +42,7 @@ export default function PlexSearchBox() { const suggestQ = useQuery({ queryKey: qk.plexSuggest(scope, dq, activeField), enabled: suggestEnabled, - queryFn: () => - api.plexLibrary({ scope, q: dq, field: activeField, limit: SUGGEST_LIMIT }), + queryFn: () => api.plexLibrary({ scope, q: dq, field: activeField, limit: SUGGEST_LIMIT }), staleTime: 30_000, }); const suggestions = suggestEnabled ? (suggestQ.data?.items ?? []) : []; @@ -58,9 +53,7 @@ export default function PlexSearchBox() { // Keep the highlighted option scrolled into view during arrow-key navigation. useEffect(() => { if (hi < 0) return; - listRef.current - ?.querySelector(`[data-idx="${hi}"]`) - ?.scrollIntoView({ block: "nearest" }); + listRef.current?.querySelector(`[data-idx="${hi}"]`)?.scrollIntoView({ block: "nearest" }); }, [hi]); const pick = (i: number) => { diff --git a/frontend/src/components/SearchBar.dom.test.tsx b/frontend/src/components/SearchBar.dom.test.tsx index 77d6254f..8c53178a 100644 --- a/frontend/src/components/SearchBar.dom.test.tsx +++ b/frontend/src/components/SearchBar.dom.test.tsx @@ -15,9 +15,7 @@ vi.mock("react-i18next", async (importOriginal) => { describe("SearchBar responsive wrapper (E5 S7)", () => { it("carries the container-query classes that make it wrap instead of collapsing", () => { - const { container } = render( - {}} placeholder="Search…" />, - ); + const { container } = render( {}} placeholder="Search…" />); const cls = (container.firstElementChild as HTMLElement).className; // A usable min-width floor once the column has room → the box wraps via flex-wrap, never ~0: expect(cls).toContain("@min-[420px]/appmain:min-w-[15rem]"); diff --git a/frontend/src/lib/api/plex.ts b/frontend/src/lib/api/plex.ts index b411ca67..9257b02f 100644 --- a/frontend/src/lib/api/plex.ts +++ b/frontend/src/lib/api/plex.ts @@ -307,7 +307,12 @@ export const plexApi = { }, // Facets ADAPT to the active filters + watch-state (faceted search) — pass them so each group // narrows the others and the bounds match the visible result set. `lang` picks the genre vocabulary. - plexFacets: (scope: string, f?: PlexFilters, show?: string, lang?: string): Promise => { + plexFacets: ( + scope: string, + f?: PlexFilters, + show?: string, + lang?: string, + ): Promise => { const u = new URLSearchParams({ scope }); if (show && show !== "all") u.set("show", show); if (lang === "hu") u.set("lang", "hu"); diff --git a/frontend/src/lib/plexSearch.ts b/frontend/src/lib/plexSearch.ts index a849873c..7c027124 100644 --- a/frontend/src/lib/plexSearch.ts +++ b/frontend/src/lib/plexSearch.ts @@ -4,13 +4,7 @@ export type PlexSearchField = "all" | "title" | "people" | "summary" | "episode"; -const PLEX_SEARCH_FIELDS: PlexSearchField[] = [ - "all", - "title", - "people", - "summary", - "episode", -]; +const PLEX_SEARCH_FIELDS: PlexSearchField[] = ["all", "title", "people", "summary", "episode"]; // i18n label key per scope (see plex.searchField.* in the locale files). export const plexFieldLabelKey = (f: PlexSearchField): string => `plex.searchField.${f}`; @@ -18,9 +12,7 @@ export const plexFieldLabelKey = (f: PlexSearchField): string => `plex.searchFie // Episodes only exist under show libraries, so the Episode scope is offered only when shows are in // scope (show | both) — mirroring the backend, which returns nothing for episode+movie-only. export function availablePlexFields(scope: string): PlexSearchField[] { - return scope === "movie" - ? PLEX_SEARCH_FIELDS.filter((f) => f !== "episode") - : PLEX_SEARCH_FIELDS; + return scope === "movie" ? PLEX_SEARCH_FIELDS.filter((f) => f !== "episode") : PLEX_SEARCH_FIELDS; } // Heal a stored/selected field against the current scope: a movie-only scope can't hold Episode, so diff --git a/frontend/src/lib/releaseNotes.ts b/frontend/src/lib/releaseNotes.ts index c94eab55..2ce2cdb4 100644 --- a/frontend/src/lib/releaseNotes.ts +++ b/frontend/src/lib/releaseNotes.ts @@ -14,6 +14,22 @@ export interface ReleaseEntry { } export const RELEASE_NOTES: ReleaseEntry[] = [ + { + version: "0.66.0", + date: "2026-08-17", + summary: + "A big Plex update: Hungarian metadata, scoped search with instant suggestions, cast & crew pages, and a tidier toolbar that finally works on a phone.", + features: [ + "Hungarian Plex library: with the app in Hungarian, film and show titles, summaries and genres appear in Hungarian (falling back to English where a title has no Hungarian version). Search and the genre filter work in the Hungarian vocabulary too, and switching language clears a genre selection so it can't silently hide everything.", + "Scoped Plex search: a pill in the search box narrows a search to Titles, Cast, Descriptions or Episodes, and a live list of matching titles drops down as you type — jumping straight to a film's page or a show's episodes.", + "Cast & crew pages: click an actor or director anywhere in Plex to see their filmography across your libraries, or filter the library by that person.", + "Ratings now separate an IMDb score (shown with a link to IMDb) from Plex's own audience score, so a title with no IMDb rating still shows its audience score instead of nothing.", + "Plex toolbar tidy-up: the movie/show scope sits next to the search box and the sort control sits above the cards (like the feed), and the header no longer collapses or overlaps its own controls on a narrow window or a phone.", + ], + chores: [ + "Large internal refactor of the Plex module (search/card/query builders split out, the feed read-model extracted, the database queries modernized) backed by characterization tests, plus a full-codebase review. No behaviour change intended.", + ], + }, { version: "0.65.2", date: "2026-08-16",