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
+2 -2
View File
@@ -1,6 +1,6 @@
import { reportNowPlaying } from '../api/subsonicScrobble';
import { invoke } from '@tauri-apps/api/core';
import { getPlaybackSourceKind } from '../utils/resolvePlaybackUrl';
import { getPlaybackSourceKind } from '../utils/playback/resolvePlaybackUrl';
import { useAuthStore } from './authStore';
import {
bumpPlayGeneration,
@@ -10,7 +10,7 @@ import {
import { clearPreloadingIds } from './gaplessPreloadState';
import { deriveNormalizationSnapshot } from './normalizationSnapshot';
import type { PlayerState } from './playerStoreTypes';
import { sameQueueTrackId, shallowCloneQueueTracks } from '../utils/queueIdentity';
import { sameQueueTrackId, shallowCloneQueueTracks } from '../utils/playback/queueIdentity';
import { queueUndoRestoreAudioEngine } from './queueUndoAudioRestore';
import {
setPendingQueueListScrollTop,
+6 -6
View File
@@ -2,12 +2,12 @@ import { reportNowPlaying, scrobbleSong } from '../api/subsonicScrobble';
import type { Track } from './playerStoreTypes';
import { invoke } from '@tauri-apps/api/core';
import { lastfmGetTrackLoved, lastfmScrobble, lastfmUpdateNowPlaying } from '../api/lastfm';
import { setDeferHotCachePrefetch } from '../utils/hotCacheGate';
import { getPerfProbeFlags } from '../utils/perfFlags';
import { bumpPerfCounter } from '../utils/perfTelemetry';
import { resolvePlaybackUrl } from '../utils/resolvePlaybackUrl';
import { resolveReplayGainDb } from '../utils/resolveReplayGainDb';
import { showToast } from '../utils/toast';
import { setDeferHotCachePrefetch } from '../utils/cache/hotCacheGate';
import { getPerfProbeFlags } from '../utils/perf/perfFlags';
import { bumpPerfCounter } from '../utils/perf/perfTelemetry';
import { resolvePlaybackUrl } from '../utils/playback/resolvePlaybackUrl';
import { resolveReplayGainDb } from '../utils/audio/resolveReplayGainDb';
import { showToast } from '../utils/ui/toast';
import { useAuthStore } from './authStore';
import { getPlayGeneration, setIsAudioPaused } from './engineState';
import {
@@ -1,7 +1,7 @@
import { listen } from '@tauri-apps/api/event';
import { streamUrlTrackId } from '../../utils/resolvePlaybackUrl';
import { normalizationAlmostEqual } from '../../utils/normalizationCompare';
import { normalizeAnalysisTrackId } from '../../utils/queueIdentity';
import { streamUrlTrackId } from '../../utils/playback/resolvePlaybackUrl';
import { normalizationAlmostEqual } from '../../utils/audio/normalizationCompare';
import { normalizeAnalysisTrackId } from '../../utils/playback/queueIdentity';
import {
handleAudioEnded,
handleAudioError,
@@ -1,5 +1,5 @@
import { invoke } from '@tauri-apps/api/core';
import { effectiveLoudnessPreAnalysisAttenuationDb } from '../../utils/loudnessPreAnalysisSlider';
import { effectiveLoudnessPreAnalysisAttenuationDb } from '../../utils/audio/loudnessPreAnalysisSlider';
import { useAuthStore } from '../authStore';
import { onAnalysisStorageChanged } from '../analysisSync';
import { emitNormalizationDebug } from '../normalizationDebug';
@@ -1,5 +1,5 @@
import { invoke } from '@tauri-apps/api/core';
import { effectiveLoudnessPreAnalysisAttenuationDb } from '../../utils/loudnessPreAnalysisSlider';
import { effectiveLoudnessPreAnalysisAttenuationDb } from '../../utils/audio/loudnessPreAnalysisSlider';
import { useAuthStore } from '../authStore';
import { emitNormalizationDebug } from '../normalizationDebug';
import { invokeAudioSetNormalizationDeduped } from '../normalizationIpcDedupe';
+1 -1
View File
@@ -1,4 +1,4 @@
import { clampStoredLoudnessPreAnalysisAttenuationRefDb } from '../utils/loudnessPreAnalysisSlider';
import { clampStoredLoudnessPreAnalysisAttenuationRefDb } from '../utils/audio/loudnessPreAnalysisSlider';
import { DEFAULT_LOUDNESS_PRE_ANALYSIS_ATTENUATION_DB } from './authStoreDefaults';
import { usePlayerStore } from './playerStore';
import type { AuthState } from './authStoreTypes';
+1 -1
View File
@@ -1,4 +1,4 @@
import { isNavidromeAudiomuseSoftwareEligible } from '../utils/subsonicServerIdentity';
import { isNavidromeAudiomuseSoftwareEligible } from '../utils/server/subsonicServerIdentity';
import type { AuthState } from './authStoreTypes';
type SetState = (
+1 -1
View File
@@ -1,4 +1,4 @@
import { clampStoredLoudnessPreAnalysisAttenuationRefDb } from '../utils/loudnessPreAnalysisSlider';
import { clampStoredLoudnessPreAnalysisAttenuationRefDb } from '../utils/audio/loudnessPreAnalysisSlider';
import {
DEFAULT_LOUDNESS_PRE_ANALYSIS_ATTENUATION_DB,
LOUDNESS_LUFS_PRESETS,
+1 -1
View File
@@ -2,7 +2,7 @@ import { IS_LINUX } from '../utils/platform';
import {
LOUDNESS_PRE_ANALYSIS_REF_TARGET_LUFS,
clampStoredLoudnessPreAnalysisAttenuationRefDb,
} from '../utils/loudnessPreAnalysisSlider';
} from '../utils/audio/loudnessPreAnalysisSlider';
import { DEFAULT_LOUDNESS_PRE_ANALYSIS_ATTENUATION_DB } from './authStoreDefaults';
import {
clampMixFilterMinStars,
+1 -1
View File
@@ -2,7 +2,7 @@ import type { EntityRatingSupportLevel } from '../api/subsonicTypes';
import type {
InstantMixProbeResult,
SubsonicServerIdentity,
} from '../utils/subsonicServerIdentity';
} from '../utils/server/subsonicServerIdentity';
export interface ServerProfile {
id: string;
+1 -1
View File
@@ -2,7 +2,7 @@ import type { Track } from './playerStoreTypes';
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import { invoke } from '@tauri-apps/api/core';
import { isHotCachePreviousTrackUnderGrace } from '../utils/hotCacheGate';
import { isHotCachePreviousTrackUnderGrace } from '../utils/cache/hotCacheGate';
import { emitAnalysisStorageChanged } from './analysisSync';
import { useAuthStore } from './authStore';
+1 -1
View File
@@ -39,7 +39,7 @@ const hoisted = vi.hoisted(() => {
vi.mock('@tauri-apps/api/core', () => ({ invoke: hoisted.invokeMock }));
vi.mock('../api/subsonicStreamUrl', () => ({ buildStreamUrl: hoisted.buildStreamUrlMock }));
vi.mock('../utils/redactSubsonicUrl', () => ({ redactSubsonicUrlForLog: hoisted.redactMock }));
vi.mock('../utils/server/redactSubsonicUrl', () => ({ redactSubsonicUrlForLog: hoisted.redactMock }));
vi.mock('./authStore', () => ({ useAuthStore: { getState: () => hoisted.auth } }));
vi.mock('./playerStore', () => ({
usePlayerStore: {
+1 -1
View File
@@ -1,6 +1,6 @@
import { buildStreamUrl } from '../api/subsonicStreamUrl';
import { invoke } from '@tauri-apps/api/core';
import { redactSubsonicUrlForLog } from '../utils/redactSubsonicUrl';
import { redactSubsonicUrlForLog } from '../utils/server/redactSubsonicUrl';
import { useAuthStore } from './authStore';
import { usePlayerStore } from './playerStore';
import { emitNormalizationDebug } from './normalizationDebug';
+2 -2
View File
@@ -1,8 +1,8 @@
import { getPlayQueue } from '../api/subsonicPlayQueue';
import { invoke } from '@tauri-apps/api/core';
import i18n from '../i18n';
import { songToTrack } from '../utils/songToTrack';
import { showToast } from '../utils/toast';
import { songToTrack } from '../utils/playback/songToTrack';
import { showToast } from '../utils/ui/toast';
import { useAuthStore } from './authStore';
import {
bumpPlayGeneration,
+2 -2
View File
@@ -1,7 +1,7 @@
import { getSimilarSongs2, getTopSongs } from '../api/subsonicArtists';
import { invoke } from '@tauri-apps/api/core';
import { buildInfiniteQueueCandidates } from '../utils/buildInfiniteQueueCandidates';
import { songToTrack } from '../utils/songToTrack';
import { buildInfiniteQueueCandidates } from '../utils/playback/buildInfiniteQueueCandidates';
import { songToTrack } from '../utils/playback/songToTrack';
import { useAuthStore } from './authStore';
import { setIsAudioPaused } from './engineState';
import {
+1 -1
View File
@@ -17,7 +17,7 @@ const { authState, invokeMock } = vi.hoisted(() => ({
vi.mock('@tauri-apps/api/core', () => ({ invoke: invokeMock }));
vi.mock('./authStore', () => ({ useAuthStore: { getState: () => authState } }));
vi.mock('../utils/loudnessPreAnalysisSlider', () => ({
vi.mock('../utils/audio/loudnessPreAnalysisSlider', () => ({
effectiveLoudnessPreAnalysisAttenuationDb: (attenuation: number) => attenuation,
}));
+1 -1
View File
@@ -1,6 +1,6 @@
import { invoke } from '@tauri-apps/api/core';
import { useAuthStore } from './authStore';
import { effectiveLoudnessPreAnalysisAttenuationDb } from '../utils/loudnessPreAnalysisSlider';
import { effectiveLoudnessPreAnalysisAttenuationDb } from '../utils/audio/loudnessPreAnalysisSlider';
/**
* Two IPC entry points to the Rust normalization pipeline that get hammered
+4 -4
View File
@@ -21,7 +21,7 @@ vi.mock('./authStore', () => ({
useAuthStore: { getState: () => authState },
}));
vi.mock('../utils/resolveReplayGainDb', () => ({
vi.mock('../utils/audio/resolveReplayGainDb', () => ({
resolveReplayGainDb: vi.fn(
(track: Track) => (track as Track & { _testGain?: number | null })._testGain ?? null,
),
@@ -99,7 +99,7 @@ describe("engine='replaygain'", () => {
});
it('passes neighbour-track context for album-mode resolution', async () => {
const { resolveReplayGainDb } = await import('../utils/resolveReplayGainDb');
const { resolveReplayGainDb } = await import('../utils/audio/resolveReplayGainDb');
const spy = vi.mocked(resolveReplayGainDb);
spy.mockClear();
authState.normalizationEngine = 'replaygain';
@@ -111,7 +111,7 @@ describe("engine='replaygain'", () => {
});
it('passes null for prev when queueIndex is 0', async () => {
const { resolveReplayGainDb } = await import('../utils/resolveReplayGainDb');
const { resolveReplayGainDb } = await import('../utils/audio/resolveReplayGainDb');
const spy = vi.mocked(resolveReplayGainDb);
spy.mockClear();
authState.normalizationEngine = 'replaygain';
@@ -123,7 +123,7 @@ describe("engine='replaygain'", () => {
});
it('passes null for next when queueIndex is the last slot', async () => {
const { resolveReplayGainDb } = await import('../utils/resolveReplayGainDb');
const { resolveReplayGainDb } = await import('../utils/audio/resolveReplayGainDb');
const spy = vi.mocked(resolveReplayGainDb);
spy.mockClear();
authState.normalizationEngine = 'replaygain';
+1 -1
View File
@@ -1,6 +1,6 @@
import type { PlayerState, Track } from './playerStoreTypes';
import { useAuthStore } from './authStore';
import { resolveReplayGainDb } from '../utils/resolveReplayGainDb';
import { resolveReplayGainDb } from '../utils/audio/resolveReplayGainDb';
/**
* Compute the normalization fields that should land in the next state commit
* when the runtime switches tracks or rewrites the queue. Three branches:
+1 -1
View File
@@ -6,7 +6,7 @@ import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import { invoke } from '@tauri-apps/api/core';
import { useAuthStore } from './authStore';
import { showToast } from '../utils/toast';
import { showToast } from '../utils/ui/toast';
import { useOfflineJobStore, cancelledDownloads } from './offlineJobStore';
import { emitAnalysisStorageChanged } from './analysisSync';
+4 -4
View File
@@ -1,11 +1,11 @@
import { reportNowPlaying } from '../api/subsonicScrobble';
import { invoke } from '@tauri-apps/api/core';
import { lastfmGetTrackLoved, lastfmUpdateNowPlaying } from '../api/lastfm';
import { setDeferHotCachePrefetch } from '../utils/hotCacheGate';
import { setDeferHotCachePrefetch } from '../utils/cache/hotCacheGate';
import { orbitBulkGuard } from '../utils/orbitBulkGuard';
import { sameQueueTrackId } from '../utils/queueIdentity';
import { resolvePlaybackUrl } from '../utils/resolvePlaybackUrl';
import { resolveReplayGainDb } from '../utils/resolveReplayGainDb';
import { sameQueueTrackId } from '../utils/playback/queueIdentity';
import { resolvePlaybackUrl } from '../utils/playback/resolvePlaybackUrl';
import { resolveReplayGainDb } from '../utils/audio/resolveReplayGainDb';
import { useAuthStore } from './authStore';
import {
bumpPlayGeneration,
+1 -1
View File
@@ -21,7 +21,7 @@ vi.mock('./offlineStore', () => ({
},
}));
vi.mock('../utils/resolvePlaybackUrl', () => ({
vi.mock('../utils/playback/resolvePlaybackUrl', () => ({
resolvePlaybackUrl: vi.fn((trackId: string, serverId: string) => {
if (offlineStoreState.localUrlByKey.has(`${serverId}:${trackId}`)) {
return `psysonic-local://${serverId}/${trackId}`;
+2 -2
View File
@@ -1,5 +1,5 @@
import { resolvePlaybackUrl, type PlaybackSourceKind } from '../utils/resolvePlaybackUrl';
import { sameQueueTrackId } from '../utils/queueIdentity';
import { resolvePlaybackUrl, type PlaybackSourceKind } from '../utils/playback/resolvePlaybackUrl';
import { sameQueueTrackId } from '../utils/playback/queueIdentity';
import { useOfflineStore } from './offlineStore';
/**
+1 -1
View File
@@ -1,5 +1,5 @@
import type { InternetRadioStation } from '../api/subsonicTypes';
import type { PlaybackSourceKind } from '../utils/resolvePlaybackUrl';
import type { PlaybackSourceKind } from '../utils/playback/resolvePlaybackUrl';
export interface Track {
id: string;
+3 -3
View File
@@ -37,9 +37,9 @@ const hoisted = vi.hoisted(() => {
});
vi.mock('@tauri-apps/api/core', () => ({ invoke: hoisted.invokeMock }));
vi.mock('../utils/hotCacheGate', () => ({ setDeferHotCachePrefetch: hoisted.setDeferHotCachePrefetchMock }));
vi.mock('../utils/resolvePlaybackUrl', () => ({ resolvePlaybackUrl: hoisted.resolvePlaybackUrlMock }));
vi.mock('../utils/resolveReplayGainDb', () => ({ resolveReplayGainDb: hoisted.resolveReplayGainDbMock }));
vi.mock('../utils/cache/hotCacheGate', () => ({ setDeferHotCachePrefetch: hoisted.setDeferHotCachePrefetchMock }));
vi.mock('../utils/playback/resolvePlaybackUrl', () => ({ resolvePlaybackUrl: hoisted.resolvePlaybackUrlMock }));
vi.mock('../utils/audio/resolveReplayGainDb', () => ({ resolveReplayGainDb: hoisted.resolveReplayGainDbMock }));
vi.mock('./authStore', () => ({ useAuthStore: { getState: () => hoisted.auth } }));
vi.mock('./engineState', () => ({
getPlayGeneration: hoisted.getPlayGeneration,
+3 -3
View File
@@ -1,8 +1,8 @@
import type { Track } from './playerStoreTypes';
import { invoke } from '@tauri-apps/api/core';
import { setDeferHotCachePrefetch } from '../utils/hotCacheGate';
import { resolvePlaybackUrl } from '../utils/resolvePlaybackUrl';
import { resolveReplayGainDb } from '../utils/resolveReplayGainDb';
import { setDeferHotCachePrefetch } from '../utils/cache/hotCacheGate';
import { resolvePlaybackUrl } from '../utils/playback/resolvePlaybackUrl';
import { resolveReplayGainDb } from '../utils/audio/resolveReplayGainDb';
import { useAuthStore } from './authStore';
import { getPlayGeneration, setIsAudioPaused } from './engineState';
import { touchHotCacheOnPlayback } from './hotCacheTouch';
+1 -1
View File
@@ -15,7 +15,7 @@ const hoisted = vi.hoisted(() => {
};
});
vi.mock('../utils/toast', () => ({ showToast: hoisted.showToastMock }));
vi.mock('../utils/ui/toast', () => ({ showToast: hoisted.showToastMock }));
vi.mock('./playerStore', () => ({
usePlayerStore: {
getState: hoisted.playerStateGet,
+1 -1
View File
@@ -1,4 +1,4 @@
import { showToast } from '../utils/toast';
import { showToast } from '../utils/ui/toast';
import { usePlayerStore } from './playerStore';
/**
+4 -4
View File
@@ -1,10 +1,10 @@
import { getSong } from '../api/subsonicLibrary';
import { invoke } from '@tauri-apps/api/core';
import { estimateLivePosition } from '../api/orbit';
import { setDeferHotCachePrefetch } from '../utils/hotCacheGate';
import { resolvePlaybackUrl } from '../utils/resolvePlaybackUrl';
import { resolveReplayGainDb } from '../utils/resolveReplayGainDb';
import { songToTrack } from '../utils/songToTrack';
import { setDeferHotCachePrefetch } from '../utils/cache/hotCacheGate';
import { resolvePlaybackUrl } from '../utils/playback/resolvePlaybackUrl';
import { resolveReplayGainDb } from '../utils/audio/resolveReplayGainDb';
import { songToTrack } from '../utils/playback/songToTrack';
import { useAuthStore } from './authStore';
import {
bumpPlayGeneration,
+1 -1
View File
@@ -1,5 +1,5 @@
import { invoke } from '@tauri-apps/api/core';
import { isRecoverableSeekError } from '../utils/seekErrors';
import { isRecoverableSeekError } from '../utils/audio/seekErrors';
import { useAuthStore } from './authStore';
import { shouldRebindPlaybackToHotCache } from './playbackUrlRouting';
import type { PlayerState } from './playerStoreTypes';
+1 -1
View File
@@ -19,7 +19,7 @@ const hoisted = vi.hoisted(() => {
});
vi.mock('@tauri-apps/api/core', () => ({ invoke: hoisted.invokeMock }));
vi.mock('../utils/seekErrors', () => ({ isRecoverableSeekError: hoisted.isRecoverableSeekErrorMock }));
vi.mock('../utils/audio/seekErrors', () => ({ isRecoverableSeekError: hoisted.isRecoverableSeekErrorMock }));
vi.mock('./seekTargetState', () => ({ setSeekTarget: hoisted.setSeekTargetMock }));
vi.mock('./playerStore', () => ({
usePlayerStore: { getState: hoisted.playerStateGet },
+1 -1
View File
@@ -1,5 +1,5 @@
import { invoke } from '@tauri-apps/api/core';
import { isRecoverableSeekError } from '../utils/seekErrors';
import { isRecoverableSeekError } from '../utils/audio/seekErrors';
import { usePlayerStore } from './playerStore';
import { setSeekTarget } from './seekTargetState';
+3 -3
View File
@@ -1,6 +1,6 @@
import { effectiveLoudnessPreAnalysisAttenuationDb } from '../utils/loudnessPreAnalysisSlider';
import { loudnessGainPlaceholderUntilCacheDb } from '../utils/loudnessPlaceholder';
import { resolveReplayGainDb } from '../utils/resolveReplayGainDb';
import { effectiveLoudnessPreAnalysisAttenuationDb } from '../utils/audio/loudnessPreAnalysisSlider';
import { loudnessGainPlaceholderUntilCacheDb } from '../utils/audio/loudnessPlaceholder';
import { resolveReplayGainDb } from '../utils/audio/resolveReplayGainDb';
import { useAuthStore } from './authStore';
import {
getCachedLoudnessGain,
+1 -1
View File
@@ -22,7 +22,7 @@ const hoisted = vi.hoisted(() => ({
}));
vi.mock('@tauri-apps/api/core', () => ({ invoke: hoisted.invokeMock }));
vi.mock('../utils/waveformParse', () => ({ coerceWaveformBins: hoisted.coerceWaveformBinsMock }));
vi.mock('../utils/waveform/waveformParse', () => ({ coerceWaveformBins: hoisted.coerceWaveformBinsMock }));
vi.mock('./playerStore', () => ({
usePlayerStore: {
getState: () => hoisted.playerSnapshot,
+1 -1
View File
@@ -1,5 +1,5 @@
import { invoke } from '@tauri-apps/api/core';
import { coerceWaveformBins } from '../utils/waveformParse';
import { coerceWaveformBins } from '../utils/waveform/waveformParse';
import { usePlayerStore } from './playerStore';
import { getWaveformRefreshGen } from './waveformRefreshGen';