diff --git a/backend/app/routes/downloads.py b/backend/app/routes/downloads.py index 28a8bb6..71153d5 100644 --- a/backend/app/routes/downloads.py +++ b/backend/app/routes/downloads.py @@ -752,7 +752,7 @@ def list_links( .scalars() .all() ) - return [linksmod.owner_view(l) for l in rows] + return [linksmod.owner_view(row) for row in rows] @router.post("/{job_id}/links") diff --git a/backend/app/routes/plex.py b/backend/app/routes/plex.py index 9c14e95..61cdd5a 100644 --- a/backend/app/routes/plex.py +++ b/backend/app/routes/plex.py @@ -1346,11 +1346,15 @@ def _vtt_ts_to_s(ts: str) -> float: def _vtt_s_to_ts(x: float) -> str: x = max(0.0, x) - h = int(x // 3600); x -= h * 3600 - m = int(x // 60); x -= m * 60 - s = int(x); ms = int(round((x - s) * 1000)) + h = int(x // 3600) + x -= h * 3600 + m = int(x // 60) + x -= m * 60 + s = int(x) + ms = int(round((x - s) * 1000)) if ms >= 1000: - s += 1; ms -= 1000 + s += 1 + ms -= 1000 return f"{h:02d}:{m:02d}:{s:02d}.{ms:03d}" diff --git a/backend/app/sync/subscriptions.py b/backend/app/sync/subscriptions.py index c0f643a..a007296 100644 --- a/backend/app/sync/subscriptions.py +++ b/backend/app/sync/subscriptions.py @@ -34,9 +34,9 @@ def _external_links(branding: dict) -> list | None: channel = branding.get("channel", {}) links = channel.get("links") or [] out = [ - {"title": l.get("title"), "url": l.get("url")} - for l in links - if isinstance(l, dict) and l.get("url") + {"title": item.get("title"), "url": item.get("url")} + for item in links + if isinstance(item, dict) and item.get("url") ] return out or None diff --git a/backend/ruff.toml b/backend/ruff.toml new file mode 100644 index 0000000..4706ea0 --- /dev/null +++ b/backend/ruff.toml @@ -0,0 +1,6 @@ +# Ruff config for the Siftlode backend. Keeps ruff's default rule set (E4/E7/E9 + F = the +# high-signal pyflakes/pycodestyle checks that catch real dead code + bugs), but ignores E402: +# a handful of modules (e.g. main.py) deliberately run setup — logging config, sys.path — before +# importing route modules, so "module import not at top of file" is intentional there, not cruft. +[lint] +ignore = ["E402"] diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 325008b..6d64895 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -831,7 +831,6 @@ export default function App() { ) : page === "plex" && meQuery.data!.plex_enabled ? ( setPlexQ("")} scope={plexScope} setScope={setPlexScope} show={plexShowFilter} diff --git a/frontend/src/components/PlexBrowse.tsx b/frontend/src/components/PlexBrowse.tsx index 9696148..4409b5a 100644 --- a/frontend/src/components/PlexBrowse.tsx +++ b/frontend/src/components/PlexBrowse.tsx @@ -54,7 +54,6 @@ type Sub = type Props = { q: string; - onClearSearch: () => void; scope: string; // movie | show | both (unified cross-library scope) setScope: (v: string) => void; show: string; @@ -70,7 +69,6 @@ const PAGE = 40; export default function PlexBrowse({ q, - onClearSearch, scope, setScope, show, diff --git a/frontend/src/components/SavedViewsWidget.tsx b/frontend/src/components/SavedViewsWidget.tsx index 200d777..f5a1ff8 100644 --- a/frontend/src/components/SavedViewsWidget.tsx +++ b/frontend/src/components/SavedViewsWidget.tsx @@ -20,7 +20,7 @@ import { CSS } from "@dnd-kit/utilities"; import { api, getActiveAccount, type FeedFilters, type SavedView } from "../lib/api"; import { filtersToParams, shareUrl } from "../lib/urlState"; import { notify } from "../lib/notifications"; -import { accountKey, LS, readJSON, writeJSON } from "../lib/storage"; +import { accountKey, LS, writeJSON } from "../lib/storage"; import { useConfirm } from "./ConfirmProvider"; // Compact, order-independent signature of a filter set (reuses the share serializer, which @@ -28,14 +28,6 @@ import { useConfirm } from "./ConfirmProvider"; // saved view that matches the feed's current filters. const keyOf = (f: FeedFilters) => filtersToParams(f).toString(); -/** The default view's filters, or null. Read synchronously on load (App.loadAccountFilters) from a - * per-account localStorage mirror the widget keeps in sync — avoids a flash before the query loads. - * Per-account so one account's default view never seeds another account's feed. */ -export function loadDefaultViewFilters(): FeedFilters | null { - const key = accountKey(LS.defaultViewFilters, getActiveAccount()); - return key ? readJSON(key, null) : null; -} - function syncDefaultMirror(views: SavedView[]): void { const key = accountKey(LS.defaultViewFilters, getActiveAccount()); if (!key) return; diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx index 6368c22..b6dc281 100644 --- a/frontend/src/components/Sidebar.tsx +++ b/frontend/src/components/Sidebar.tsx @@ -39,29 +39,11 @@ import { import { shareUrl } from "../lib/urlState"; import { notify } from "../lib/notifications"; import { useDebounced } from "../lib/useDebounced"; -import { Switch } from "./ui/form"; import TagManager from "./TagManager"; import SavedViewsWidget from "./SavedViewsWidget"; import CollapsedFilterRail from "./CollapsedFilterRail"; // Filter ids; display labels resolved at render time via t("sidebar.sort.") etc. -const SORT_IDS = [ - "newest", - "oldest", - "views", - "duration_desc", - "duration_asc", - "title", - "subscribers", - "priority", - "shuffle", -]; - -const SHOW_IDS = ["unwatched", "in_progress", "all", "watched", "hidden"]; - -// Fresh shuffle token for the "surprise me" sort. -const rollSeed = () => Math.floor(Math.random() * 1_000_000_000); - const DATE_PRESETS: { days: number; key: string }[] = [ { days: 1, key: "24h" }, { days: 7, key: "1week" }, @@ -697,19 +679,3 @@ function WidgetCard({ ); } -function Toggle({ - label, - checked, - onChange, -}: { - label: string; - checked: boolean; - onChange: (v: boolean) => void; -}) { - return ( - - ); -} diff --git a/frontend/src/components/VideoEditor.tsx b/frontend/src/components/VideoEditor.tsx index 3f52c70..4a103b5 100644 --- a/frontend/src/components/VideoEditor.tsx +++ b/frontend/src/components/VideoEditor.tsx @@ -366,7 +366,7 @@ export default function VideoEditor({ job, onClose }: { job: DownloadJob; onClos )} {/* segment tint: dropped = dimmed + hatched */} - {segments.map((s, i) => ( + {segments.map((s) => (
(n.id === id ? { ...n, dismissed: true } : n)); diff --git a/frontend/src/lib/storage.ts b/frontend/src/lib/storage.ts index 1102d3b..52d4cca 100644 --- a/frontend/src/lib/storage.ts +++ b/frontend/src/lib/storage.ts @@ -129,29 +129,11 @@ export function writeJSON(key: string, value: unknown): void { // --- reactive persisted string state ---------------------------------------------------- -/** A `useState` whose string value is mirrored to localStorage, so it survives a reload (F5). - * Validation is deferred to the caller (clamp at render) so it can be used before an - * async-loaded option list is known, keeping hook order stable. Generalizes the per-component - * "persisted active tab" pattern (Settings, Stats, admin pages, the channel filter…). */ -function usePersistedState( - key: string, - fallback = "", -): [string, (value: string) => void] { - const [value, setValue] = useState(() => localStorage.getItem(key) ?? fallback); - const set = (next: string) => { - setValue(next); - try { - localStorage.setItem(key, next); - } catch { - /* ignore */ - } - }; - return [value, set]; -} - -/** Like usePersistedState but scoped to the current account (see accountKey), so one account's - * persisted tab/view position doesn't carry over to another in the same browser. These hooks run - * in components that only mount once signed in, so the account is known. */ +/** A `useState` whose string value is mirrored to localStorage, scoped to the current account (see + * accountKey), so one account's persisted tab/view position doesn't carry over to another in the + * same browser. Survives reload (F5); validation is deferred to the caller (clamp at render) so it + * can be used before an async-loaded option list is known, keeping hook order stable. These hooks + * run in components that only mount once signed in, so the account is known. */ export function useAccountPersistedState( base: string, fallback = "", diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index 79a2287..83b5c91 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -12,8 +12,8 @@ "noEmit": true, "jsx": "react-jsx", "strict": true, - "noUnusedLocals": false, - "noUnusedParameters": false + "noUnusedLocals": true, + "noUnusedParameters": true }, "include": ["src"] }