From c2a2c98f16c9cb3e9019b598b6bbd081e7a55b2d Mon Sep 17 00:00:00 2001 From: npeter83 Date: Sat, 11 Jul 2026 04:47:08 +0200 Subject: [PATCH] =?UTF-8?q?chore:=20Phase=201=20hygiene=20=E2=80=94=20drop?= =?UTF-8?q?=20unused=20imports/exports/types=20(behavior-neutral)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Machine-baseline harvest (ruff + knip), all tsc/parse-green, no runtime change: - backend: remove 3 unused imports (channels/playlists/youtube) via ruff; drop the unused `job` binding in unshare_download (keep the _own_job ownership guard call). - frontend: remove `export` from 23 internally-used-only symbols (knip "unused exports") to shrink the public surface; delete 2 genuinely-dead declarations (PlexBrowseResult — leftover from the removed /browse route; WIDGET_TITLES — hardcoded English titles superseded by i18n). Held back for a decision (unused here = possibly-unwired, NOT dead — flagged, not removed): e2ee.lock()/clearDevice() (security primitives never wired to logout / a "forget device" feature) and loadDefaultViewFilters (App reimplements it inline — a DRY issue). See siftlode-ops/CODE-HYGIENE.md. --- backend/app/routes/channels.py | 2 +- backend/app/routes/downloads.py | 2 +- backend/app/sync/playlists.py | 2 +- backend/app/youtube/client.py | 1 - frontend/src/components/DataTable.tsx | 2 +- frontend/src/components/legal/LegalLayout.tsx | 4 ++-- frontend/src/components/ui/form.tsx | 2 +- frontend/src/i18n/index.ts | 4 ++-- frontend/src/lib/adminUsersTab.ts | 2 +- frontend/src/lib/api.ts | 15 ++++----------- frontend/src/lib/errorDialog.ts | 2 +- frontend/src/lib/messagesSocket.ts | 2 +- frontend/src/lib/messaging.ts | 2 +- frontend/src/lib/notifications.ts | 6 +++--- frontend/src/lib/onboarding.ts | 4 ++-- frontend/src/lib/sidebarLayout.ts | 10 +--------- frontend/src/lib/storage.ts | 2 +- frontend/src/lib/theme.ts | 2 +- frontend/src/lib/urlState.ts | 2 +- 19 files changed, 26 insertions(+), 42 deletions(-) diff --git a/backend/app/routes/channels.py b/backend/app/routes/channels.py index 4c47b71..c7dcdb7 100644 --- a/backend/app/routes/channels.py +++ b/backend/app/routes/channels.py @@ -5,7 +5,7 @@ import logging from datetime import datetime, timezone from fastapi import APIRouter, Depends, HTTPException -from sqlalchemy import and_, func, select, update +from sqlalchemy import func, select, update from sqlalchemy.orm import Session from app import quota, sysconfig diff --git a/backend/app/routes/downloads.py b/backend/app/routes/downloads.py index ee5490e..153f00c 100644 --- a/backend/app/routes/downloads.py +++ b/backend/app/routes/downloads.py @@ -675,7 +675,7 @@ def unshare_download( user: User = Depends(require_human), db: Session = Depends(get_db), ) -> dict: - job = _own_job(db, user, job_id) + _own_job(db, user, job_id) # ownership guard (404s if not the caller's job) recipient = db.execute( select(User).where(func.lower(User.email) == email.strip().lower()) ).scalar_one_or_none() diff --git a/backend/app/sync/playlists.py b/backend/app/sync/playlists.py index 547dc55..ca82b27 100644 --- a/backend/app/sync/playlists.py +++ b/backend/app/sync/playlists.py @@ -13,7 +13,7 @@ from sqlalchemy import delete, select from sqlalchemy.orm import Session from app import progress, quota -from app.auth import has_read_scope, has_write_scope +from app.auth import has_read_scope from app.models import Channel, OAuthToken, Playlist, PlaylistItem, User, Video from app.sync.videos import apply_video_details, parse_dt from app.youtube.client import YouTubeClient, YouTubeError diff --git a/backend/app/youtube/client.py b/backend/app/youtube/client.py index 9695a55..2c32405 100644 --- a/backend/app/youtube/client.py +++ b/backend/app/youtube/client.py @@ -12,7 +12,6 @@ from datetime import datetime, timedelta, timezone import httpx from app import quota, sysconfig -from app.config import settings from app.models import User from app.security import decrypt diff --git a/frontend/src/components/DataTable.tsx b/frontend/src/components/DataTable.tsx index 8b9404c..88b4d67 100644 --- a/frontend/src/components/DataTable.tsx +++ b/frontend/src/components/DataTable.tsx @@ -9,7 +9,7 @@ import { useDismiss } from "../lib/useDismiss"; // other modules (e.g. the playlist manager) can reuse it. Below `md` it falls back to a card // list built from the same column definitions. -export type ColumnFilter = +type ColumnFilter = | { kind: "text"; get: (row: T) => string } | { kind: "select"; options: { value: string; label: string }[]; test: (row: T, value: string) => boolean } | { kind: "multi"; options: { value: string; label: string }[]; test: (row: T, values: string[]) => boolean }; diff --git a/frontend/src/components/legal/LegalLayout.tsx b/frontend/src/components/legal/LegalLayout.tsx index bcd1dd5..73af941 100644 --- a/frontend/src/components/legal/LegalLayout.tsx +++ b/frontend/src/components/legal/LegalLayout.tsx @@ -4,7 +4,7 @@ // with no react-query provider: the operator contact is fetched with a plain fetch below. import { useEffect, useState } from "react"; -export const LAST_UPDATED = "June 14, 2026"; +const LAST_UPDATED = "June 14, 2026"; // Contact shown on the legal pages: the instance operator's configured admin email, from the // public /auth/config. One shared fetch across the page; null when the operator set none. @@ -19,7 +19,7 @@ function fetchOperatorContact(): Promise { return contactPromise; } -export function useOperatorContact(): string | null { +function useOperatorContact(): string | null { const [contact, setContact] = useState(null); useEffect(() => { let alive = true; diff --git a/frontend/src/components/ui/form.tsx b/frontend/src/components/ui/form.tsx index 9c6dc36..eef1c8d 100644 --- a/frontend/src/components/ui/form.tsx +++ b/frontend/src/components/ui/form.tsx @@ -59,7 +59,7 @@ export function Section({ /** A label that reveals an explanatory caption on hover (dotted underline) when `hint` is set. * No-op styling when there's no hint, so it's safe to use unconditionally. */ -export function HintLabel({ hint, children }: { hint?: string; children: ReactNode }) { +function HintLabel({ hint, children }: { hint?: string; children: ReactNode }) { return ( l.code) as readonly string[]; -export const LANG_KEY = LS.lang; +const LANG_KEY = LS.lang; // Auto-load every locale file (locales//.json) and merge each under one // `translation` namespace, so components use t("area.key") and adding an area needs no wiring. @@ -33,7 +33,7 @@ export function isSupported(code: string | null | undefined): code is LangCode { // Initial language before the server preference is known: stored choice, else the browser // language, else English. (After login, App adopts the server-persisted preference.) -export function detectInitialLang(): LangCode { +function detectInitialLang(): LangCode { const stored = localStorage.getItem(LANG_KEY); if (isSupported(stored)) return stored; const nav = (navigator.language || "en").slice(0, 2).toLowerCase(); diff --git a/frontend/src/lib/adminUsersTab.ts b/frontend/src/lib/adminUsersTab.ts index 6bf817d..09ce140 100644 --- a/frontend/src/lib/adminUsersTab.ts +++ b/frontend/src/lib/adminUsersTab.ts @@ -8,7 +8,7 @@ import { LS, setAccountRaw } from "./storage"; // WITHOUT statically importing the — now lazy-loaded — admin page, which would otherwise pull // it back into the main bundle and defeat the code-split. export const ADMIN_USERS_TAB_KEY = LS.adminUsersTab; -export const ADMIN_USERS_ACCESS_TAB = "access"; +const ADMIN_USERS_ACCESS_TAB = "access"; export function focusAccessRequestsTab(): void { setAccountRaw(ADMIN_USERS_TAB_KEY, ADMIN_USERS_ACCESS_TAB); diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index 719eb4b..8b4c534 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -78,7 +78,7 @@ export interface VideoDetail { duration_seconds: number | null; } -export interface ChannelLink { +interface ChannelLink { title: string | null; url: string; } @@ -611,7 +611,7 @@ export interface SystemConfigData { } // Admin: result of testing the Plex server connection (also drives the library-picker). -export interface PlexSection { +interface PlexSection { key: string; title: string; type: string; // "movie" | "show" @@ -656,13 +656,6 @@ export interface PlexCard { show_id?: string | null; // episode: the show's rating_key (for grouping a playlist by show/season) summary?: string | null; // episode } -export interface PlexBrowseResult { - kind: "movie" | "show"; - total: number; - offset: number; - limit: number; - items: PlexCard[]; -} // Unified cross-library browse (movies + shows mixed). `episodes` is populated only on a search that // also matches episodes (the grouped "Episodes" section). export interface PlexUnifiedResult { @@ -823,7 +816,7 @@ export function plexFilterCount(f: PlexFilters): number { // Serialize the shared PlexFilters metadata fields onto a query string. Used by both the library grid // and the facets request so the two endpoints always agree on the filter set (paging/sort/sort_dir are // each caller's own concern and stay out of here). -export function appendPlexFilters(u: URLSearchParams, f: PlexFilters): void { +function appendPlexFilters(u: URLSearchParams, f: PlexFilters): void { if (f.genres?.length) u.set("genres", f.genres.join(",")); if (f.genreMode === "all") u.set("genre_mode", "all"); if (f.contentRatings?.length) u.set("content_ratings", f.contentRatings.join(",")); @@ -903,7 +896,7 @@ export interface DownloadProfile { sort_order: number; } -export interface DownloadAsset { +interface DownloadAsset { id: number; status: string; title: string | null; diff --git a/frontend/src/lib/errorDialog.ts b/frontend/src/lib/errorDialog.ts index 70f5a4c..f49f0dc 100644 --- a/frontend/src/lib/errorDialog.ts +++ b/frontend/src/lib/errorDialog.ts @@ -5,7 +5,7 @@ import { createStore } from "./store"; // server can't carry out an action (a definitive 4xx/5xx response). Distinct from toasts // (transient, e.g. connection blips) and from silent handling. Module-level so the non-React // api layer can raise it; an mounted at the app root renders the current one. -export interface AppError { +interface AppError { title: string; message: string; } diff --git a/frontend/src/lib/messagesSocket.ts b/frontend/src/lib/messagesSocket.ts index 73acd5e..7a03148 100644 --- a/frontend/src/lib/messagesSocket.ts +++ b/frontend/src/lib/messagesSocket.ts @@ -5,7 +5,7 @@ import { getActiveAccount, type Message, type MessageUser } from "./api"; // A pushed message plus both parties, so the dock can open/flash the right window. -export interface IncomingMessage { +interface IncomingMessage { message: Message; from?: MessageUser; to?: MessageUser; diff --git a/frontend/src/lib/messaging.ts b/frontend/src/lib/messaging.ts index 42e9bc7..ee4e1bd 100644 --- a/frontend/src/lib/messaging.ts +++ b/frontend/src/lib/messaging.ts @@ -17,7 +17,7 @@ export async function partnerPub(partnerId: number): Promise { } // Live "is secure messaging unlocked on this device" — shared so the page and the dock agree. -export function useUnlocked(): boolean { +function useUnlocked(): boolean { return useSyncExternalStore(subscribeUnlock, isUnlocked, isUnlocked); } diff --git a/frontend/src/lib/notifications.ts b/frontend/src/lib/notifications.ts index 07e4a30..316f85c 100644 --- a/frontend/src/lib/notifications.ts +++ b/frontend/src/lib/notifications.ts @@ -6,7 +6,7 @@ import { LS, readAccount, readAccountMerged, writeAccount } from "./storage"; export type NotifLevel = "info" | "success" | "warning" | "error" | "fatal"; -export interface NotifAction { +interface NotifAction { label: string; onClick: () => void; } @@ -34,7 +34,7 @@ export type ChannelSubscribedMeta = { }; // Admin nudge that pending access requests are waiting — carries a durable inbox link to the // Users page (where Access requests live). No payload; the panel provides the navigation. -export type AccessRequestsMeta = { +type AccessRequestsMeta = { kind: "access-requests"; }; export type NotifMeta = @@ -224,7 +224,7 @@ export function notify(input: NotifyInput): number { } /** Back-compat: a simple info toast with an optional inline action (e.g. Undo). */ -export function toast(message: string, action?: NotifAction): number { +function toast(message: string, action?: NotifAction): number { return notify({ message, action }); } diff --git a/frontend/src/lib/onboarding.ts b/frontend/src/lib/onboarding.ts index b30a6f8..d7664a5 100644 --- a/frontend/src/lib/onboarding.ts +++ b/frontend/src/lib/onboarding.ts @@ -14,10 +14,10 @@ import { getAccountRaw, LS, setAccountRaw } from "./storage"; -export const ONBOARD_ACTIVE = "siftlode.onboarding.active"; // sessionStorage (not in LS, which is localStorage) +const ONBOARD_ACTIVE = "siftlode.onboarding.active"; // sessionStorage (not in LS, which is localStorage) // Per-account (see accountKey): a fresh account should still get the onboarding nudge even if a // different account in the same browser dismissed it. -export const ONBOARD_DISMISSED = LS.onboardingDismissed; +const ONBOARD_DISMISSED = LS.onboardingDismissed; export function shouldAutoOpenOnboarding(me: { can_read: boolean; diff --git a/frontend/src/lib/sidebarLayout.ts b/frontend/src/lib/sidebarLayout.ts index 3e0d630..146c7b1 100644 --- a/frontend/src/lib/sidebarLayout.ts +++ b/frontend/src/lib/sidebarLayout.ts @@ -8,15 +8,7 @@ import { LS, readAccount, writeAccount } from "./storage"; export type WidgetId = "savedviews" | "date" | "language" | "topic" | "tags"; -export const ALL_WIDGETS: WidgetId[] = ["savedviews", "date", "tags", "language", "topic"]; - -export const WIDGET_TITLES: Record = { - savedviews: "Saved views", - date: "Upload date", - language: "Language", - topic: "Topic", - tags: "Tags", -}; +const ALL_WIDGETS: WidgetId[] = ["savedviews", "date", "tags", "language", "topic"]; export interface SidebarLayout { order: WidgetId[]; diff --git a/frontend/src/lib/storage.ts b/frontend/src/lib/storage.ts index aac8340..b7babae 100644 --- a/frontend/src/lib/storage.ts +++ b/frontend/src/lib/storage.ts @@ -129,7 +129,7 @@ export function writeJSON(key: string, value: unknown): void { * 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…). */ -export function usePersistedState( +function usePersistedState( key: string, fallback = "", ): [string, (value: string) => void] { diff --git a/frontend/src/lib/theme.ts b/frontend/src/lib/theme.ts index 5104119..db8caac 100644 --- a/frontend/src/lib/theme.ts +++ b/frontend/src/lib/theme.ts @@ -1,6 +1,6 @@ import { LS, readAccountMerged, writeAccount } from "./storage"; -export type Mode = "dark" | "light"; +type Mode = "dark" | "light"; export type Scheme = "midnight" | "forest" | "slate" | "youtube"; export const SCHEMES: { id: Scheme; name: string; swatch: string }[] = [ diff --git a/frontend/src/lib/urlState.ts b/frontend/src/lib/urlState.ts index 3cfec21..5653dd8 100644 --- a/frontend/src/lib/urlState.ts +++ b/frontend/src/lib/urlState.ts @@ -89,7 +89,7 @@ export function hasFilterParams(params: URLSearchParams): boolean { // The single source of truth for valid page ids; `Page` and the runtime validator both // derive from it, so adding a page is a one-line change (no parallel allowlists to keep in // sync). "feed" is the default/fallback. -export const PAGES = [ +const PAGES = [ "feed", "channels", "stats",