chore: Phase 1 hygiene — drop unused imports/exports/types (behavior-neutral)
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.
This commit is contained in:
parent
bf8d4b94a0
commit
c2a2c98f16
19 changed files with 26 additions and 42 deletions
|
|
@ -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<T> =
|
||||
type ColumnFilter<T> =
|
||||
| { 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 };
|
||||
|
|
|
|||
|
|
@ -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<string | null> {
|
|||
return contactPromise;
|
||||
}
|
||||
|
||||
export function useOperatorContact(): string | null {
|
||||
function useOperatorContact(): string | null {
|
||||
const [contact, setContact] = useState<string | null>(null);
|
||||
useEffect(() => {
|
||||
let alive = true;
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<Tooltip hint={hint ?? ""}>
|
||||
<span
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ export const LANGUAGES = [
|
|||
] as const;
|
||||
export type LangCode = (typeof LANGUAGES)[number]["code"];
|
||||
const SUPPORTED = LANGUAGES.map((l) => l.code) as readonly string[];
|
||||
export const LANG_KEY = LS.lang;
|
||||
const LANG_KEY = LS.lang;
|
||||
|
||||
// Auto-load every locale file (locales/<lang>/<area>.json) and merge each <area> 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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 <ErrorDialog> mounted at the app root renders the current one.
|
||||
export interface AppError {
|
||||
interface AppError {
|
||||
title: string;
|
||||
message: string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ export async function partnerPub(partnerId: number): Promise<string> {
|
|||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<WidgetId, string> = {
|
||||
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[];
|
||||
|
|
|
|||
|
|
@ -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] {
|
||||
|
|
|
|||
|
|
@ -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 }[] = [
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue