refactor(hooks): move cross-cutting hooks into lib/hooks

The five residual src/hooks/ hooks (useConnectionStatus, useCardGridMetrics,
useTracklistColumns, useNavidromeAdminRole, useAnalysisPerfListener) are all
domain-agnostic — none imports @/features — so they join the other shared hooks
in src/lib/hooks/ (with their two colocated tests). Consumers repointed;
relative-up imports in the moved files become the @/ alias. src/hooks/ is removed.

Pure move + import updates, no behavior change. The layering baseline gains five
lib->store entries (the three hooks that read authStore/devOfflineBrowseStore are
only now caught under lib/) — same documented debt class as the api move.
This commit is contained in:
Psychotoxical
2026-07-01 14:38:23 +02:00
parent 25055dc79a
commit f2cf8a08df
38 changed files with 120 additions and 35 deletions
+85
View File
@@ -533,6 +533,91 @@
"name": "lib-is-the-floor"
}
},
{
"type": "dependency",
"from": "src/lib/hooks/useCardGridMetrics.ts",
"to": "src/store/authStore.ts",
"unresolvedTo": "@/store/authStore",
"dependencyTypes": [
"aliased",
"aliased-tsconfig",
"aliased-tsconfig-paths",
"local",
"import"
],
"rule": {
"severity": "error",
"name": "lib-is-the-floor"
}
},
{
"type": "dependency",
"from": "src/lib/hooks/useCardGridMetrics.ts",
"to": "src/store/authStoreHelpers.ts",
"unresolvedTo": "@/store/authStoreHelpers",
"dependencyTypes": [
"aliased",
"aliased-tsconfig",
"aliased-tsconfig-paths",
"local",
"import"
],
"rule": {
"severity": "error",
"name": "lib-is-the-floor"
}
},
{
"type": "dependency",
"from": "src/lib/hooks/useConnectionStatus.ts",
"to": "src/store/authStore.ts",
"unresolvedTo": "@/store/authStore",
"dependencyTypes": [
"aliased",
"aliased-tsconfig",
"aliased-tsconfig-paths",
"local",
"import"
],
"rule": {
"severity": "error",
"name": "lib-is-the-floor"
}
},
{
"type": "dependency",
"from": "src/lib/hooks/useConnectionStatus.ts",
"to": "src/store/devOfflineBrowseStore.ts",
"unresolvedTo": "@/store/devOfflineBrowseStore",
"dependencyTypes": [
"aliased",
"aliased-tsconfig",
"aliased-tsconfig-paths",
"local",
"import"
],
"rule": {
"severity": "error",
"name": "lib-is-the-floor"
}
},
{
"type": "dependency",
"from": "src/lib/hooks/useNavidromeAdminRole.ts",
"to": "src/store/authStore.ts",
"unresolvedTo": "@/store/authStore",
"dependencyTypes": [
"aliased",
"aliased-tsconfig",
"aliased-tsconfig-paths",
"local",
"import"
],
"rule": {
"severity": "error",
"name": "lib-is-the-floor"
}
},
{
"type": "dependency",
"from": "src/lib/library/hooks/useLibraryAnalysisBackfill.ts",
+1 -1
View File
@@ -55,7 +55,7 @@ import { useOfflineAutoNav } from '@/features/offline';
import { useOfflineLibraryFilterSuspend } from '@/features/offline';
import { AppShellQueueResizerSeam } from '@/app/AppShellQueueResizerSeam';
import { IS_LINUX, IS_MACOS } from '@/lib/util/platform';
import { useConnectionStatus } from '../hooks/useConnectionStatus';
import { useConnectionStatus } from '@/lib/hooks/useConnectionStatus';
import { useIdlePlayQueuePull } from '@/app/hooks/useIdlePlayQueuePull';
import { useAuthStore } from '../store/authStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
+1 -1
View File
@@ -4,7 +4,7 @@ import { createPortal } from 'react-dom';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { Check, ChevronDown, RefreshCw } from 'lucide-react';
import type { ConnectionStatus } from '@/hooks/useConnectionStatus';
import type { ConnectionStatus } from '@/lib/hooks/useConnectionStatus';
import { usePlayQueueSyncLedState } from '@/app/hooks/usePlayQueueSyncLedState';
import type { ServerProfile } from '@/store/authStoreTypes';
import { useAuthStore } from '@/store/authStore';
+1 -1
View File
@@ -30,7 +30,7 @@ import {
resolveOverlayVisibility,
usePerfOverlayMode,
} from '@/lib/perf/perfOverlayMode';
import { useAnalysisPerfListener } from '@/hooks/useAnalysisPerfListener';
import { useAnalysisPerfListener } from '@/lib/hooks/useAnalysisPerfListener';
import { useCoverPerfListener } from '@/cover/useCoverPerfListener';
import { getCoverCachedPerMinute, getCoverUiPerMinute } from '@/lib/perf/coverPerfStore';
+1 -1
View File
@@ -11,7 +11,7 @@ import {
markPlaybackIdle,
} from '@/features/playback/store/queuePlaybackIdle';
import { hasPendingQueueSync } from '@/features/playback/store/queueSync';
import type { ConnectionStatus } from '@/hooks/useConnectionStatus';
import type { ConnectionStatus } from '@/lib/hooks/useConnectionStatus';
import { canAutoIdlePlayQueuePull } from '@/app/hooks/usePlayQueueSyncLedState';
const IDLE_THRESHOLD_MS = 30_000;
+1 -1
View File
@@ -1,6 +1,6 @@
import { useCallback, useEffect, useMemo, useState, useSyncExternalStore } from 'react';
import { useTranslation } from 'react-i18next';
import type { ConnectionStatus } from '@/hooks/useConnectionStatus';
import type { ConnectionStatus } from '@/lib/hooks/useConnectionStatus';
import { pullPlayQueueFromActiveServer } from '@/features/playback/store/applyServerPlayQueue';
import { useAuthStore } from '@/store/authStore';
import { useOrbitStore } from '@/features/orbit';
@@ -1,7 +1,7 @@
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
import type { Track } from '@/lib/media/trackTypes';
import React, { useMemo, useState, useEffect } from 'react';
import { useTracklistColumns } from '@/hooks/useTracklistColumns';
import { useTracklistColumns } from '@/lib/hooks/useTracklistColumns';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import { useTranslation } from 'react-i18next';
import { useIsMobile } from '@/lib/hooks/useIsMobile';
+1 -1
View File
@@ -2,7 +2,7 @@ import React from 'react';
import { AudioLines, ChevronRight, Heart, Play, Square } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import type { ColDef } from '@/hooks/useTracklistColumns';
import type { ColDef } from '@/lib/hooks/useTracklistColumns';
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
import type { Track } from '@/lib/media/trackTypes';
import { songToTrack } from '@/lib/media/songToTrack';
@@ -1,6 +1,6 @@
import React from 'react';
import type { TFunction } from 'i18next';
import type { ColDef } from '@/hooks/useTracklistColumns';
import type { ColDef } from '@/lib/hooks/useTracklistColumns';
import { CENTERED_COLS, isSortable, type ColKey, type SortKey } from '@/features/album/utils/albumTrackListHelpers';
interface Props {
@@ -1,4 +1,4 @@
import type { ColDef } from '@/hooks/useTracklistColumns';
import type { ColDef } from '@/lib/hooks/useTracklistColumns';
export function codecLabel(song: { suffix?: string; bitRate?: number }, showBitrate: boolean): string {
const parts: string[] = [];
@@ -6,7 +6,7 @@ import type {
SubsonicAlbum, SubsonicArtist, SubsonicArtistInfo, SubsonicSong,
} from '@/lib/api/subsonicTypes';
import { useAuthStore } from '@/store/authStore';
import { useConnectionStatus } from '@/hooks/useConnectionStatus';
import { useConnectionStatus } from '@/lib/hooks/useConnectionStatus';
import { loadArtistFromLibraryIndex } from '@/features/offline';
import { useOfflineBrowseContext } from '@/features/offline';
import { loadArtistFromLocalPlayback, offlineLocalBrowseEnabled } from '@/features/offline';
@@ -1,7 +1,7 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { AudioLines, ChevronRight, Play, Square, X } from 'lucide-react';
import type { ColDef } from '@/hooks/useTracklistColumns';
import type { ColDef } from '@/lib/hooks/useTracklistColumns';
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
import { codecLabel } from '@/lib/format/playlistDetailHelpers';
import { formatLastSeen } from '@/lib/format/userMgmtHelpers';
@@ -4,7 +4,7 @@ import FavoriteSongRow, { type FavoriteSongRowCallbacks } from '@/features/favor
import { TracklistColumnPicker } from '@/ui/TracklistColumnPicker';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import type { ColDef } from '@/hooks/useTracklistColumns';
import type { ColDef } from '@/lib/hooks/useTracklistColumns';
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import { previewInputFromSong, usePreviewStore } from '@/features/playback/store/previewStore';
@@ -7,7 +7,7 @@ import type {
import { useAuthStore } from '@/store/authStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import type { TopFavoriteArtist } from '@/features/favorites/components/TopFavoriteArtists';
import { useConnectionStatus } from '@/hooks/useConnectionStatus';
import { useConnectionStatus } from '@/lib/hooks/useConnectionStatus';
import { isActiveServerReachable } from '@/lib/network/activeServerReachability';
import { useOfflineBrowseContext } from '@/features/offline';
import { useOfflineBrowseReloadToken } from '@/features/offline';
+1 -1
View File
@@ -1,6 +1,6 @@
import { queueSongStar, queueSongRating } from '@/features/playback/store/pendingStarSync';
import React, { useEffect, useMemo, useState } from 'react';
import { useTracklistColumns, type ColDef } from '@/hooks/useTracklistColumns';
import { useTracklistColumns, type ColDef } from '@/lib/hooks/useTracklistColumns';
import { TopFavoriteArtistsRow } from '@/features/favorites/components/TopFavoriteArtists';
import { RadioStationRow } from '@/features/favorites/components/RadioFavorites';
import FavoritesSongsSectionHeader from '@/features/favorites/components/FavoritesSongsSectionHeader';
+1 -1
View File
@@ -28,7 +28,7 @@ import {
writeHomeFeedCache,
type HomeFeedSnapshot,
} from '@/features/home/store/homeFeedCache';
import { useConnectionStatus } from '@/hooks/useConnectionStatus';
import { useConnectionStatus } from '@/lib/hooks/useConnectionStatus';
import { useOfflineBrowseContext } from '@/features/offline';
import { useOfflineBrowseReloadToken } from '@/features/offline';
import { useDevOfflineBrowseStore } from '@/features/offline';
@@ -7,7 +7,7 @@ import type { ArtistStats, TrackStats } from '@/music-network';
import { getMusicNetworkRuntimeOrNull } from '@/music-network';
import { makeCache } from '@/lib/cache/nowPlayingCache';
import { shouldAttemptSubsonicForServer } from '@/lib/network/subsonicNetworkGuard';
import { useConnectionStatus } from '@/hooks/useConnectionStatus';
import { useConnectionStatus } from '@/lib/hooks/useConnectionStatus';
// Module-level TTL caches (shared across mounts)
const songMetaCache = makeCache<SubsonicSong | null>();
@@ -1,6 +1,6 @@
import { useAuthStore } from '@/store/authStore';
import { useOfflineStore } from '@/features/offline/store/offlineStore';
import { useConnectionStatus } from '@/hooks/useConnectionStatus';
import { useConnectionStatus } from '@/lib/hooks/useConnectionStatus';
import { usePlayerStatsRecordingEnabled } from '@/features/stats';
import { hasOfflineBrowsingContent } from '@/features/offline/utils/favoritesOfflineBrowse';
import { useOfflineBrowseActive } from '@/features/offline/utils/offlineBrowseMode';
@@ -1,4 +1,4 @@
import type { ConnectionStatus } from '@/hooks/useConnectionStatus';
import type { ConnectionStatus } from '@/lib/hooks/useConnectionStatus';
import type { OfflineAlbumMeta } from '@/features/offline/store/offlineStore';
import { favoritesOfflineBrowseEnabled } from '@/features/offline/utils/favoritesOfflineBrowse';
import { hasOfflineBrowseCapability } from '@/features/offline/utils/offlineBrowseRouting';
@@ -2,7 +2,7 @@ import {
isDevOfflineBrowseForced,
useDevOfflineBrowseStore,
} from '@/store/devOfflineBrowseStore';
import { useConnectionStatus } from '@/hooks/useConnectionStatus';
import { useConnectionStatus } from '@/lib/hooks/useConnectionStatus';
import { isActiveServerReachable } from '@/lib/network/activeServerReachability';
/** True when browse/detail pages should use local-bytes-only data sources. */
@@ -1,7 +1,7 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { AudioLines, ChevronRight, Heart, Play, Square, Trash2 } from 'lucide-react';
import type { ColDef } from '@/hooks/useTracklistColumns';
import type { ColDef } from '@/lib/hooks/useTracklistColumns';
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
import { codecLabel } from '@/lib/format/playlistDetailHelpers';
import { formatLastSeen } from '@/lib/format/userMgmtHelpers';
@@ -2,7 +2,7 @@ import React from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { ChevronRight, Heart, Play, Plus, RefreshCw, Square } from 'lucide-react';
import type { ColDef } from '@/hooks/useTracklistColumns';
import type { ColDef } from '@/lib/hooks/useTracklistColumns';
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import { usePreviewStore } from '@/features/playback/store/previewStore';
@@ -9,7 +9,7 @@ import { useNavigate } from 'react-router-dom';
import {
ListPlus, Search, Trash2, X,
} from 'lucide-react';
import type { ColDef } from '@/hooks/useTracklistColumns';
import type { ColDef } from '@/lib/hooks/useTracklistColumns';
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
import type { Track } from '@/lib/media/trackTypes';
import { usePlayerStore } from '@/features/playback/store/playerStore';
@@ -2,7 +2,7 @@ import { updatePlaylist } from '@/lib/api/subsonicPlaylists';
import type { SubsonicPlaylist, SubsonicSong } from '@/lib/api/subsonicTypes';
import React, { useEffect, useState, useCallback, useMemo } from 'react';
import { useParams, useNavigate, useLocation } from 'react-router-dom';
import { useTracklistColumns, type ColDef } from '@/hooks/useTracklistColumns';
import { useTracklistColumns, type ColDef } from '@/lib/hooks/useTracklistColumns';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import { useShallow } from 'zustand/react/shallow';
import { usePlaylistStore } from '@/features/playlist/store/playlistStore';
+1 -1
View File
@@ -15,7 +15,7 @@ import RadioEditModal from '@/features/radio/components/RadioEditModal';
import RadioDirectoryModal from '@/features/radio/components/RadioDirectoryModal';
import { usePerfProbeFlags } from '@/lib/perf/perfFlags';
import { VirtualCardGrid } from '@/ui/VirtualCardGrid';
import { useNavidromeAdminRole, canManageNavidromeRadio } from '@/hooks/useNavidromeAdminRole';
import { useNavidromeAdminRole, canManageNavidromeRadio } from '@/lib/hooks/useNavidromeAdminRole';
export default function InternetRadio() {
const { t } = useTranslation();
@@ -1,7 +1,7 @@
import { useAuthStore } from '@/store/authStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import { useConnectionStatus } from '@/hooks/useConnectionStatus';
import { useNavidromeAdminRole } from '@/hooks/useNavidromeAdminRole';
import { useConnectionStatus } from '@/lib/hooks/useConnectionStatus';
import { useNavidromeAdminRole } from '@/lib/hooks/useNavidromeAdminRole';
import { serverListDisplayLabel } from '@/lib/server/serverDisplayName';
import { findServerByIdOrIndexKey } from '@/lib/server/serverLookup';
import { PerfProbeMetricSection } from '@/features/sidebar/components/perfProbe/PerfProbeMetricCard';
@@ -1,5 +1,5 @@
import { useAuthStore } from '@/store/authStore';
import type { NavidromeAdminRole } from '@/hooks/useNavidromeAdminRole';
import type { NavidromeAdminRole } from '@/lib/hooks/useNavidromeAdminRole';
import { isNavidromeServer, formatServerSoftware } from '@/lib/server/subsonicServerIdentity';
import { FEATURE_AUDIOMUSE_SIMILAR_TRACKS, OP_SIMILAR_TRACKS } from '@/serverCapabilities/catalog';
import {
@@ -2,7 +2,7 @@ import { useEffect, useState } from 'react';
import { acquirePerfLivePoll, patchPerfLiveAnalysis } from '@/lib/perf/perfLiveStore';
import { setPerfProbeTelemetryActive } from '@/lib/perf/perfTelemetry';
import { useAnalysisPerfLast } from '@/lib/perf/analysisPerfStore';
import { useAnalysisPerfListener } from '@/hooks/useAnalysisPerfListener';
import { useAnalysisPerfListener } from '@/lib/hooks/useAnalysisPerfListener';
import { useCoverPerfListener, useCoverUiThroughputPoll } from '@/cover/useCoverPerfListener';
import {
getPerfProbeFlags,
@@ -1,6 +1,6 @@
import { useLayoutEffect, useState, type RefObject } from 'react';
import { useAuthStore } from '../store/authStore';
import { clampLibraryGridMaxColumns } from '../store/authStoreHelpers';
import { useAuthStore } from '@/store/authStore';
import { clampLibraryGridMaxColumns } from '@/store/authStoreHelpers';
import {
type CardGridRowHeightVariant,
computeCardGridColumnCount,
@@ -1,5 +1,5 @@
import { useState, useEffect, useCallback, useRef, useMemo, useSyncExternalStore } from 'react';
import { useAuthStore } from '../store/authStore';
import { useAuthStore } from '@/store/authStore';
import { scheduleInstantMixProbeForServer } from '@/lib/api/subsonic';
import { serverListDisplayLabel } from '@/lib/server/serverDisplayName';
import {
@@ -1,6 +1,6 @@
import { useEffect, useState } from 'react';
import { ndLogin } from '@/lib/api/navidromeAdmin';
import { useAuthStore } from '../store/authStore';
import { useAuthStore } from '@/store/authStore';
import { isNavidromeServer } from '@/lib/server/subsonicServerIdentity';
export type NavidromeAdminRole = 'idle' | 'checking' | 'admin' | 'user' | 'na' | 'error';
+1 -1
View File
@@ -5,7 +5,7 @@ import userEvent from '@testing-library/user-event';
import type { TFunction } from 'i18next';
import { renderWithProviders } from '@/test/helpers/renderWithProviders';
import { TracklistColumnPicker } from '@/ui/TracklistColumnPicker';
import type { ColDef } from '@/hooks/useTracklistColumns';
import type { ColDef } from '@/lib/hooks/useTracklistColumns';
const COLUMNS: readonly ColDef[] = [
{ key: 'title', i18nKey: 'title', minWidth: 100, defaultWidth: 200, required: true },
+1 -1
View File
@@ -2,7 +2,7 @@ import React, { useEffect, useLayoutEffect, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import { Check, ChevronDown, RotateCcw } from 'lucide-react';
import type { TFunction } from 'i18next';
import type { ColDef } from '@/hooks/useTracklistColumns';
import type { ColDef } from '@/lib/hooks/useTracklistColumns';
interface Props {
/** Every column (required ones are filtered out of the menu). */
+1 -1
View File
@@ -4,7 +4,7 @@ import { useWarmGridCovers } from '@/cover/useWarmGridCovers';
import { useVirtualizer } from '@tanstack/react-virtual';
import { APP_MAIN_SCROLL_VIEWPORT_ID } from '@/constants/appScroll';
import { useElementClientHeightById } from '@/lib/hooks/useResizeClientHeight';
import { useCardGridMetrics } from '@/hooks/useCardGridMetrics';
import { useCardGridMetrics } from '@/lib/hooks/useCardGridMetrics';
import { useRemeasureGridVirtualizer } from '@/lib/hooks/useRemeasureGridVirtualizer';
import { useVirtualizerScrollMargin } from '@/lib/hooks/useVirtualizerScrollMargin';
import type { CardGridRowHeightVariant } from '@/lib/util/cardGridLayout';