diff --git a/backend/alembic/versions/0073_saved_view_source.py b/backend/alembic/versions/0073_saved_view_source.py index c2aa8843..6b2a9061 100644 --- a/backend/alembic/versions/0073_saved_view_source.py +++ b/backend/alembic/versions/0073_saved_view_source.py @@ -29,6 +29,11 @@ def upgrade() -> None: def downgrade() -> None: + # LOSSY / effectively forward-only: upgrade lets a user hold a feed view and a plex view of the same + # name, so re-creating the narrow (user_id, name) constraint can raise IntegrityError on such a pair. + # De-dup by dropping the non-feed rows first (they can't exist in the pre-0073 schema anyway) so the + # narrow constraint re-applies cleanly for the drift test's downgrade→re-upgrade cycle. + op.execute("DELETE FROM saved_views WHERE source <> 'feed'") op.drop_constraint("uq_user_source_saved_view", "saved_views", type_="unique") op.create_unique_constraint("uq_user_saved_view", "saved_views", ["user_id", "name"]) op.drop_column("saved_views", "source") diff --git a/frontend/src/components/PlexDiscover.tsx b/frontend/src/components/PlexDiscover.tsx index 4137cddf..e1c1d6a1 100644 --- a/frontend/src/components/PlexDiscover.tsx +++ b/frontend/src/components/PlexDiscover.tsx @@ -4,6 +4,7 @@ import { useQuery, useQueryClient } from "@tanstack/react-query"; import { RotateCcw } from "lucide-react"; import { qk } from "../lib/queryKeys"; import { api, type PlexCard, type PlexDiscover as PlexDiscoverData } from "../lib/api"; +import { hueOf, hueTileGradient } from "../lib/hueTile"; import { plexLang } from "../i18n"; import { toggleWatchState } from "../lib/plexWatch"; import { usePlex } from "./PlexProvider"; @@ -18,14 +19,6 @@ import { LoadingState, StateMessage } from "./QueryState"; // info / watched-toggle handlers come from the shell (PlexBrowse) so a rail card behaves exactly like // a grid card; the watched-toggle optimistically flips THIS surface's cache too. -// A stable, pleasant hue per genre string (no art to collage yet — colored tiles stand in). Hashing -// keeps a genre the same color across renders/sessions without storing anything. -function genreHue(name: string): number { - let h = 0; - for (let i = 0; i < name.length; i++) h = (h * 31 + name.charCodeAt(i)) % 360; - return h; -} - function CardRail({ title, ariaLabel, @@ -99,7 +92,7 @@ export default function PlexDiscover({ }); // Surprise-me is its own query so the ↻ button can redraw it without refetching the whole surface. const surpriseQ = useQuery({ - queryKey: qk.plexSurprise(scope, filters), + queryKey: qk.plexSurprise(scope, lang, filters), queryFn: () => api.plexSurprise(scope, undefined, filters, lang), staleTime: 0, }); @@ -124,7 +117,7 @@ export default function PlexDiscover({ : old, ); qc.setQueryData<{ scope: string; items: PlexCard[] }>( - qk.plexSurprise(scope, filters), + qk.plexSurprise(scope, lang, filters), (old) => (old ? { ...old, items: flip(old.items) } : old), ); }, @@ -207,16 +200,14 @@ export default function PlexDiscover({