refactor(utils): group utils/ files into topic folders (Phase L, part 1) (#689)

111 of 122 top-level src/utils/ files move into 16 topic folders (audio,
cache, cover, share, server, playback, playlist, deviceSync, waveform,
mix, format, export, changelog, ui, perf, componentHelpers). True
singletons with no cluster stay at the utils/ root.

Pure file-move: a path-aware codemod rewrote 539 relative-import
specifiers across 275 files; no logic touched. The hot-path coverage
gate list (.github/frontend-hot-path-files.txt) is updated to the new
paths for the 11 gated utils files — a mechanical consequence of the
move, not a CI change. tsc is green.
This commit is contained in:
Frank Stellmacher
2026-05-14 14:27:44 +02:00
committed by GitHub
parent 2409a1fec8
commit 7a7a9f5e6b
324 changed files with 551 additions and 551 deletions
+4 -4
View File
@@ -5,10 +5,10 @@ import type { NavigateFunction } from 'react-router-dom';
import { getSimilarSongs } from '../../api/subsonicArtists';
import { getMusicFolders } from '../../api/subsonicLibrary';
import { search as subsonicSearch } from '../../api/subsonicSearch';
import { shuffleArray } from '../../utils/shuffleArray';
import { songToTrack } from '../../utils/songToTrack';
import { showToast } from '../../utils/toast';
import { switchActiveServer } from '../../utils/switchActiveServer';
import { shuffleArray } from '../../utils/playback/shuffleArray';
import { songToTrack } from '../../utils/playback/songToTrack';
import { showToast } from '../../utils/ui/toast';
import { switchActiveServer } from '../../utils/server/switchActiveServer';
import i18n from '../../i18n';
import { usePlayerStore } from '../../store/playerStore';
import { useAuthStore } from '../../store/authStore';
+1 -1
View File
@@ -2,7 +2,7 @@ import React, { useCallback, useEffect, useRef } from 'react';
import type { SubsonicSong } from '../api/subsonicTypes';
import { useSelectionStore } from '../store/selectionStore';
import { useDragDrop } from '../contexts/DragDropContext';
import { songToTrack } from '../utils/songToTrack';
import { songToTrack } from '../utils/playback/songToTrack';
interface UseAlbumTrackListSelectionArgs {
songs: SubsonicSong[];
+1 -1
View File
@@ -5,7 +5,7 @@ import { invoke } from '@tauri-apps/api/core';
import { useTranslation } from 'react-i18next';
import { version as currentVersion } from '../../package.json';
import { IS_LINUX, IS_MACOS } from '../utils/platform';
import { SKIP_KEY, isNewer, pickAsset, type ReleaseData, type DlState } from '../utils/appUpdaterHelpers';
import { SKIP_KEY, isNewer, pickAsset, type ReleaseData, type DlState } from '../utils/componentHelpers/appUpdaterHelpers';
/** All update-modal state, the GitHub release probe and the download/relaunch
* handlers. The component owns only the early-return guard and the JSX. */
+1 -1
View File
@@ -1,7 +1,7 @@
import { useMemo } from 'react';
import type { SubsonicArtist } from '../api/subsonicTypes';
import { usePlayerStore } from '../store/playerStore';
import { ALL_SENTINEL, type ArtistListFlatRow } from '../utils/artistsHelpers';
import { ALL_SENTINEL, type ArtistListFlatRow } from '../utils/componentHelpers/artistsHelpers';
interface UseArtistsFilteringArgs {
artists: SubsonicArtist[];
+2 -2
View File
@@ -4,8 +4,8 @@ import { listen } from '@tauri-apps/api/event';
import type { TFunction } from 'i18next';
import { useAuthStore } from '../store/authStore';
import { IS_MACOS } from '../utils/platform';
import { sortAudioDeviceIds } from '../utils/audioDeviceLabels';
import { showToast } from '../utils/toast';
import { sortAudioDeviceIds } from '../utils/audio/audioDeviceLabels';
import { showToast } from '../utils/ui/toast';
interface UseAudioDevicesProbeResult {
audioDevices: string[];
+1 -1
View File
@@ -2,7 +2,7 @@ import { useState, useRef, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { invoke } from '@tauri-apps/api/core';
import { useEqStore } from '../store/eqStore';
import { parseFixedBandEqString, type AutoEqVariant, type AutoEqResult } from '../utils/autoEqParse';
import { parseFixedBandEqString, type AutoEqVariant, type AutoEqResult } from '../utils/audio/autoEqParse';
/** AutoEQ search/apply state for the Equalizer. Loads the entries index lazily
* when the section opens, filters client-side, and applies fetched profiles via
+2 -2
View File
@@ -1,8 +1,8 @@
import { useState, useEffect, useCallback, useRef, useMemo } from 'react';
import { useAuthStore } from '../store/authStore';
import { pingWithCredentials, scheduleInstantMixProbeForServer } from '../api/subsonic';
import { serverListDisplayLabel } from '../utils/serverDisplayName';
import { usePerfProbeFlags } from '../utils/perfFlags';
import { serverListDisplayLabel } from '../utils/server/serverDisplayName';
import { usePerfProbeFlags } from '../utils/perf/perfFlags';
export type ConnectionStatus = 'connected' | 'disconnected' | 'checking';
+1 -1
View File
@@ -3,7 +3,7 @@ import { setRating } from '../api/subsonicStarRating';
import type { SubsonicAlbum, SubsonicArtist } from '../api/subsonicTypes';
import type { Track } from '../store/playerStoreTypes';
import { useAuthStore } from '../store/authStore';
import { showToast } from '../utils/toast';
import { showToast } from '../utils/ui/toast';
type RatingKind = 'song' | 'album' | 'artist';
+1 -1
View File
@@ -6,7 +6,7 @@ import { search as searchSubsonic } from '../api/subsonicSearch';
import type {
SubsonicAlbum, SubsonicArtist, SubsonicPlaylist,
} from '../api/subsonicTypes';
import type { SourceTab } from '../utils/deviceSyncHelpers';
import type { SourceTab } from '../utils/deviceSync/deviceSyncHelpers';
export interface DeviceSyncBrowserResult {
playlists: SubsonicPlaylist[];
+1 -1
View File
@@ -2,7 +2,7 @@ import { useCallback, useEffect, useRef } from 'react';
import { invoke } from '@tauri-apps/api/core';
import type { TFunction } from 'i18next';
import { useDeviceSyncStore, type DeviceSyncSource } from '../store/deviceSyncStore';
import { showToast } from '../utils/toast';
import { showToast } from '../utils/ui/toast';
export interface DeviceSyncDeviceScanResult {
scanDevice: () => Promise<void>;
+1 -1
View File
@@ -1,6 +1,6 @@
import { useCallback, useEffect, useMemo, useState } from 'react';
import { invoke } from '@tauri-apps/api/core';
import type { RemovableDrive } from '../utils/deviceSyncHelpers';
import type { RemovableDrive } from '../utils/deviceSync/deviceSyncHelpers';
export interface DeviceSyncDrivesResult {
drives: RemovableDrive[];
+3 -3
View File
@@ -4,9 +4,9 @@ import { listen } from '@tauri-apps/api/event';
import type { TFunction } from 'i18next';
import { useDeviceSyncJobStore } from '../store/deviceSyncJobStore';
import { useDeviceSyncStore } from '../store/deviceSyncStore';
import { showToast } from '../utils/toast';
import { trackToSyncInfo } from '../utils/deviceSyncHelpers';
import { fetchTracksForSource } from '../utils/fetchTracksForSource';
import { showToast } from '../utils/ui/toast';
import { trackToSyncInfo } from '../utils/deviceSync/deviceSyncHelpers';
import { fetchTracksForSource } from '../utils/playback/fetchTracksForSource';
export function useDeviceSyncJobEvents(
t: TFunction,
+2 -2
View File
@@ -1,7 +1,7 @@
import { useEffect, useMemo, useState } from 'react';
import { invoke } from '@tauri-apps/api/core';
import { fetchTracksForSource } from '../utils/fetchTracksForSource';
import { trackToSyncInfo, type SyncStatus } from '../utils/deviceSyncHelpers';
import { fetchTracksForSource } from '../utils/playback/fetchTracksForSource';
import { trackToSyncInfo, type SyncStatus } from '../utils/deviceSync/deviceSyncHelpers';
import type { DeviceSyncSource } from '../store/deviceSyncStore';
export interface DeviceSyncSourceStatusesResult {
+1 -1
View File
@@ -4,7 +4,7 @@ import { usePlayerStore } from '../store/playerStore';
import {
entryToTrack, folderBrowserHasKeyModifiers, isFolderBrowserArrowKey,
type Column, type NavPos,
} from '../utils/folderBrowserHelpers';
} from '../utils/componentHelpers/folderBrowserHelpers';
interface Args {
columns: Column[];
+1 -1
View File
@@ -3,7 +3,7 @@ import { useLocation } from 'react-router-dom';
import { getMusicDirectory, getMusicIndexes } from '../api/subsonicLibrary';
import type { SubsonicDirectoryEntry } from '../api/subsonicTypes';
import type { Track } from '../store/playerStoreTypes';
import type { Column, NavPos } from '../utils/folderBrowserHelpers';
import type { Column, NavPos } from '../utils/componentHelpers/folderBrowserHelpers';
let persistedPlayingPathIds: string[] = [];
+1 -1
View File
@@ -1,5 +1,5 @@
import { useEffect, useRef, useState } from 'react';
import type { Column, NavPos } from '../utils/folderBrowserHelpers';
import type { Column, NavPos } from '../utils/componentHelpers/folderBrowserHelpers';
interface Args {
columns: Column[];
+1 -1
View File
@@ -1,5 +1,5 @@
import { useEffect, useState } from 'react';
import { extractCoverColors } from '../utils/dynamicColors';
import { extractCoverColors } from '../utils/ui/dynamicColors';
// Module-level cache: artKey → accent color string.
// Survives track changes so same-album songs reuse the extracted color instantly.
+1 -1
View File
@@ -10,7 +10,7 @@ import { fetchLyricsPlus, hasWordSync } from '../api/lyricsplus';
import { useAuthStore } from '../store/authStore';
import { useOfflineStore } from '../store/offlineStore';
import { useHotCacheStore } from '../store/hotCacheStore';
import { getCachedLyrics, putCachedLyrics, lyricsCacheKey } from '../utils/lyricsPersistentCache';
import { getCachedLyrics, putCachedLyrics, lyricsCacheKey } from '../utils/cache/lyricsPersistentCache';
export type LyricsSource = 'server' | 'lrclib' | 'netease' | 'embedded' | 'lyricsplus';
/**
+1 -1
View File
@@ -4,7 +4,7 @@ import { useAuthStore } from '../store/authStore';
import { IS_LINUX } from '../utils/platform';
import {
EXPANDED_SIZE, EXPANDED_MIN, readStoredExpandedHeight,
} from '../utils/miniPlayerHelpers';
} from '../utils/componentHelpers/miniPlayerHelpers';
/** Three window-bound setup effects bundled together:
* - Linux WebKitGTK smooth-scroll per-window (re-applies after auth hydrates
+1 -1
View File
@@ -7,7 +7,7 @@ import {
lastfmGetArtistStats, lastfmGetTrackInfo, lastfmIsConfigured,
type LastfmArtistStats, type LastfmTrackInfo,
} from '../api/lastfm';
import { makeCache } from '../utils/nowPlayingCache';
import { makeCache } from '../utils/cache/nowPlayingCache';
// Module-level TTL caches (shared across mounts)
const songMetaCache = makeCache<SubsonicSong | null>();
+1 -1
View File
@@ -1,5 +1,5 @@
import { getSong } from '../api/subsonicLibrary';
import { songToTrack } from '../utils/songToTrack';
import { songToTrack } from '../utils/playback/songToTrack';
import { useEffect, useRef } from 'react';
import { useOrbitStore } from '../store/orbitStore';
import { useAuthStore } from '../store/authStore';
+2 -2
View File
@@ -1,5 +1,5 @@
import { getSong } from '../api/subsonicLibrary';
import { songToTrack } from '../utils/songToTrack';
import { songToTrack } from '../utils/playback/songToTrack';
import { useEffect, useRef } from 'react';
import { useOrbitStore } from '../store/orbitStore';
import { usePlayerStore } from '../store/playerStore';
@@ -18,7 +18,7 @@ import {
type OrbitState,
type OrbitQueueItem,
} from '../api/orbit';
import { showToast } from '../utils/toast';
import { showToast } from '../utils/ui/toast';
import i18n from '../i18n';
import { pushOrbitEvent } from '../utils/orbitDiag';
+1 -1
View File
@@ -7,7 +7,7 @@ import {
evaluateOrbitSuggestGate,
OrbitSuggestBlockedError,
} from '../utils/orbit';
import { showToast } from '../utils/toast';
import { showToast } from '../utils/ui/toast';
/**
* Shared behaviour for song rows that in "normal mode" swallow a full list
+1 -1
View File
@@ -3,7 +3,7 @@ import type React from 'react';
import { getPlaylist } from '../api/subsonicPlaylists';
import type { SubsonicPlaylist } from '../api/subsonicTypes';
import { usePlaylistStore } from '../store/playlistStore';
import type { PendingSmartPlaylist } from '../utils/playlistsSmart';
import type { PendingSmartPlaylist } from '../utils/playlist/playlistsSmart';
/**
* Poll Navidrome every 10 s for each pending smart playlist until its
+1 -1
View File
@@ -1,6 +1,6 @@
import { useCallback } from 'react';
import type { Track } from '../store/playerStoreTypes';
import { enqueuePlaylistAll, playPlaylistAll, shufflePlaylistAll } from '../utils/playlistBulkPlayActions';
import { enqueuePlaylistAll, playPlaylistAll, shufflePlaylistAll } from '../utils/playlist/playlistBulkPlayActions';
export interface PlaylistBulkPlayCallbacksDeps {
songsLength: number;
+2 -2
View File
@@ -2,8 +2,8 @@ import { useMemo } from 'react';
import type { SubsonicSong } from '../api/subsonicTypes';
import type { Track } from '../store/playerStoreTypes';
import { usePlayerStore } from '../store/playerStore';
import { songToTrack } from '../utils/songToTrack';
import { getDisplayedSongs, type PlaylistSortDir, type PlaylistSortKey } from '../utils/playlistDisplayedSongs';
import { songToTrack } from '../utils/playback/songToTrack';
import { getDisplayedSongs, type PlaylistSortDir, type PlaylistSortKey } from '../utils/playlist/playlistDisplayedSongs';
export interface PlaylistDerivedOptions {
filterText: string;
+1 -1
View File
@@ -1,7 +1,7 @@
import React, { useEffect, useState } from 'react';
import type { SubsonicSong } from '../api/subsonicTypes';
import { useDragDrop } from '../contexts/DragDropContext';
import { runPlaylistReorderDrop } from '../utils/runPlaylistReorderDrop';
import { runPlaylistReorderDrop } from '../utils/playlist/runPlaylistReorderDrop';
export interface PlaylistDnDReorderDeps {
tracklistRef: React.RefObject<HTMLDivElement | null>;
+1 -1
View File
@@ -1,7 +1,7 @@
import type React from 'react';
import type { TFunction } from 'i18next';
import type { SubsonicPlaylist, SubsonicSong } from '../api/subsonicTypes';
import { showToast } from '../utils/toast';
import { showToast } from '../utils/ui/toast';
export interface PlaylistSongMutationsDeps {
songs: SubsonicSong[];
+1 -1
View File
@@ -1,7 +1,7 @@
import type { InternetRadioStation } from '../api/subsonicTypes';
import { useEffect, useRef, useState } from 'react';
import { invoke } from '@tauri-apps/api/core';
import { usePerfProbeFlags } from '../utils/perfFlags';
import { usePerfProbeFlags } from '../utils/perf/perfFlags';
import {
guessAzuraCastApiUrl,
normaliseAzuraCastHomepageUrl,
+2 -2
View File
@@ -6,12 +6,12 @@ import {
getSystemItemsForReorder,
isSidebarNavItemUserHideable,
type SidebarNavDropTarget,
} from '../utils/sidebarNavReorder';
} from '../utils/componentHelpers/sidebarNavReorder';
import {
SIDEBAR_NAV_LONG_PRESS_MOVE_CANCEL_PX,
SIDEBAR_NAV_LONG_PRESS_MS,
isPointerOutsideAsideSidebar,
} from '../utils/sidebarHelpers';
} from '../utils/componentHelpers/sidebarHelpers';
interface NavDndState {
section: 'library' | 'system';
+1 -1
View File
@@ -7,7 +7,7 @@ import {
NEW_RELEASES_UNREAD_SAMPLE_SIZE,
NEW_RELEASES_UNREAD_STORAGE_PREFIX,
mergeSeenNewReleaseIdsCap,
} from '../utils/sidebarHelpers';
} from '../utils/componentHelpers/sidebarHelpers';
interface Args {
serverId: string;
+1 -1
View File
@@ -1,6 +1,6 @@
import { useEffect, useState } from 'react';
import { invoke } from '@tauri-apps/api/core';
import { setPerfProbeTelemetryActive } from '../utils/perfTelemetry';
import { setPerfProbeTelemetryActive } from '../utils/perf/perfTelemetry';
interface PerfCpu {
app: number;
+1 -1
View File
@@ -2,7 +2,7 @@ import { useEffect, useState } from 'react';
import { filterSongsToActiveLibrary } from '../api/subsonicLibrary';
import { getPlaylist } from '../api/subsonicPlaylists';
import type { SubsonicPlaylist } from '../api/subsonicTypes';
import { isSmartPlaylistName } from '../utils/playlistsSmart';
import { isSmartPlaylistName } from '../utils/playlist/playlistsSmart';
/**
* Build the 2×2 cover collage for each smart playlist. Pulls each smart
+3 -3
View File
@@ -8,12 +8,12 @@ import {
type NdLibrary,
type NdUser,
} from '../api/navidromeAdmin';
import { showToast } from '../utils/toast';
import { showToast } from '../utils/ui/toast';
import {
copyTextToClipboard,
encodeServerMagicString,
} from '../utils/serverMagicString';
import { shortHostFromServerUrl } from '../utils/serverDisplayName';
} from '../utils/server/serverMagicString';
import { shortHostFromServerUrl } from '../utils/server/serverDisplayName';
import type { UserFormState } from '../components/settings/UserForm';
interface UseUserMgmtActionsArgs {
+3 -3
View File
@@ -1,12 +1,12 @@
import React, { useEffect } from 'react';
import type { SeekbarStyle } from '../store/authStoreTypes';
import type { AnimState } from '../utils/waveformSeekHelpers';
import type { AnimState } from '../utils/waveform/waveformSeekHelpers';
import {
ANIMATED_STYLES, BAR_COUNT, FLAT_WAVE_NORM, WAVE_MORPH_MS,
binsToHeights, easeOutCubic, heightsNearlyEqual,
makeFlatWaveHeights, makeHeights,
} from '../utils/waveformSeekHelpers';
import { drawSeekbar } from '../utils/waveformSeekRenderers';
} from '../utils/waveform/waveformSeekHelpers';
import { drawSeekbar } from '../utils/waveform/waveformSeekRenderers';
interface Args {
trackId: string | undefined;
+2 -2
View File
@@ -4,8 +4,8 @@ import { getPlaybackProgressSnapshot } from '../store/playbackProgress';
import {
ANIMATED_STYLES, AnimState, INTERPOLATION_PAINT_MIN_MS,
isBarQuantizedSeekStyle, quantizeProgressByBars,
} from '../utils/waveformSeekHelpers';
import { drawSeekbar } from '../utils/waveformSeekRenderers';
} from '../utils/waveform/waveformSeekHelpers';
import { drawSeekbar } from '../utils/waveform/waveformSeekRenderers';
interface Args {
duration: number;