refactor: component files export only components; lint fails on a warning

The 43 react-refresh/only-export-components warnings the gate let through meant an edit to a
provider (or AnchoredPopover, Toaster, plexDetailUi, …) could not be hot-swapped: Fast Refresh
reloads a module that also exports hooks or helpers.

- Each provider's contexts, the types its hooks return and the hooks themselves live in a sibling
  context module (navigationContext.ts, channelsContext.ts, …); the provider file keeps the
  component. loadAccountFilters moved next to the feed filters' defaults (feedFiltersInit.ts),
  following navigationInit/playlistsInit.
- Helpers that sat in component files have their own modules: welcomeMode, downloadTitle,
  plexPlayable, channelSync, ui/anchoredPopoverHooks, ui/pageToolbarSlot, ui/pagerContent,
  ui/toastLevels, ui/overlayOrder, ui/tableAlign, lib/plexDetail; the cells of the channel columns
  and the registry's page wrappers are components files of their own (channelCells, modulePages).
- usePersistedTab, an alias of useAccountPersistedState, is gone; its callers use the original.

npm run lint is now eslint --max-warnings 0, so a warning fails the gate like an error does.
This commit is contained in:
2026-09-17 02:59:09 +02:00
parent 569190480d
commit c3d88d9b20
158 changed files with 1600 additions and 1431 deletions
+13 -28
View File
@@ -1,8 +1,14 @@
import { createContext, useCallback, useContext, useMemo, useState, type ReactNode } from "react";
import { useCallback, useMemo, useState, type ReactNode } from "react";
import { LS, setAccountRaw } from "../lib/storage";
import { useUrlMirror } from "../lib/useUrlMirror";
import { useNavigation } from "./NavigationProvider";
import { useNavigation } from "./navigationContext";
import { readSelectedId } from "./playlistsInit";
import {
PlaylistsActionsContext,
PlaylistsStateContext,
type PlaylistsActions,
type PlaylistsState,
} from "./playlistsContext";
// Owns the Playlists module's shared local state — the rail's name filter and the selected playlist
// — lifted out of the App god component so the header search box, the PlaylistsRail, and the
@@ -14,29 +20,8 @@ import { readSelectedId } from "./playlistsInit";
// playlist is per-account persisted (LS.playlist) so a reload returns you to it. Neither is
// server-synced, and an account switch does a full reload, so init-from-storage is all the
// hydration these need — no per-account load effect (unlike the server-backed feed filters).
type PlaylistsState = {
search: string;
selectedId: number | null;
};
interface PlaylistsActions {
setSearch: (q: string) => void;
setSelectedId: (id: number | null) => void;
}
const StateContext = createContext<PlaylistsState>({ search: "", selectedId: null });
const ActionsContext = createContext<PlaylistsActions>({
setSearch: () => {},
setSelectedId: () => {},
});
export function usePlaylists(): PlaylistsState {
return useContext(StateContext);
}
export function usePlaylistsActions(): PlaylistsActions {
return useContext(ActionsContext);
}
//
// The contexts + hooks live in playlistsContext.ts.
export function PlaylistsProvider({ children }: { children: ReactNode }) {
const [search, setSearchState] = useState("");
@@ -61,8 +46,8 @@ export function PlaylistsProvider({ children }: { children: ReactNode }) {
);
return (
<ActionsContext.Provider value={actions}>
<StateContext.Provider value={state}>{children}</StateContext.Provider>
</ActionsContext.Provider>
<PlaylistsActionsContext.Provider value={actions}>
<PlaylistsStateContext.Provider value={state}>{children}</PlaylistsStateContext.Provider>
</PlaylistsActionsContext.Provider>
);
}