handleAction(async () => {
- const { showToast } = await import('@/utils/ui/toast');
+ const { showToast } = await import('@/lib/dom/toast');
const { deletePlaylist } = await import('@/lib/api/subsonicPlaylists');
const { removeId } = usePlaylistStore.getState();
try {
@@ -112,7 +112,7 @@ export default function PlaylistContextItems(props: ContextMenuItemsProps) {
)}
{offlinePolicy.canEditPlaylist && (
handleAction(async () => {
- const { showToast } = await import('@/utils/ui/toast');
+ const { showToast } = await import('@/lib/dom/toast');
const { deletePlaylist } = await import('@/lib/api/subsonicPlaylists');
const { removeId } = usePlaylistStore.getState();
const deletedIds: string[] = [];
diff --git a/src/features/contextMenu/components/PlaylistToPlaylistSubmenus.tsx b/src/features/contextMenu/components/PlaylistToPlaylistSubmenus.tsx
index 573a2f46..a99bd068 100644
--- a/src/features/contextMenu/components/PlaylistToPlaylistSubmenus.tsx
+++ b/src/features/contextMenu/components/PlaylistToPlaylistSubmenus.tsx
@@ -2,7 +2,7 @@ import React, { useEffect, useLayoutEffect, useMemo, useRef, useState } from 're
import { useTranslation } from 'react-i18next';
import { ListMusic, Plus } from 'lucide-react';
import { usePlaylistStore } from '@/features/playlist';
-import { showToast } from '@/utils/ui/toast';
+import { showToast } from '@/lib/dom/toast';
import { isSmartPlaylistName } from '@/utils/componentHelpers/contextMenuHelpers';
interface SingleProps {
diff --git a/src/features/contextMenu/components/SongContextItems.tsx b/src/features/contextMenu/components/SongContextItems.tsx
index 89e8a6c1..fddf1cd0 100644
--- a/src/features/contextMenu/components/SongContextItems.tsx
+++ b/src/features/contextMenu/components/SongContextItems.tsx
@@ -9,7 +9,7 @@ import type { Track } from '@/lib/media/trackTypes';
import { useAuthStore } from '@/store/authStore';
import { usePlaylistStore } from '@/features/playlist';
import { songToTrack } from '@/lib/media/songToTrack';
-import { showToast } from '@/utils/ui/toast';
+import { showToast } from '@/lib/dom/toast';
import { suggestOrbitTrack, hostEnqueueToOrbit, evaluateOrbitSuggestGate, OrbitSuggestBlockedError } from '@/features/orbit';
import { renderPresetIcon } from '@/music-network';
import StarRating from '@/ui/StarRating';
@@ -179,7 +179,7 @@ export default function SongContextItems(props: ContextMenuItemsProps) {
{offlinePolicy.canEditPlaylist && playlistId && playlistSongIndex !== undefined && (
handleAction(async () => {
const { updatePlaylist } = await import('@/lib/api/subsonicPlaylists');
- const { showToast } = await import('@/utils/ui/toast');
+ const { showToast } = await import('@/lib/dom/toast');
const touchPlaylist = usePlaylistStore.getState().touchPlaylist;
try {
const serverId = resolveMediaServerId();
diff --git a/src/features/contextMenu/hooks/useContextMenuRating.ts b/src/features/contextMenu/hooks/useContextMenuRating.ts
index e90b5a7b..1e54f375 100644
--- a/src/features/contextMenu/hooks/useContextMenuRating.ts
+++ b/src/features/contextMenu/hooks/useContextMenuRating.ts
@@ -4,7 +4,7 @@ import { queueSongRating } from '@/features/playback/store/pendingStarSync';
import type { SubsonicAlbum, SubsonicArtist } from '@/lib/api/subsonicTypes';
import type { Track } from '@/lib/media/trackTypes';
import { useAuthStore } from '@/store/authStore';
-import { showToast } from '@/utils/ui/toast';
+import { showToast } from '@/lib/dom/toast';
type RatingKind = 'song' | 'album' | 'artist';
diff --git a/src/features/deviceSync/hooks/useDeviceSyncDeviceScan.ts b/src/features/deviceSync/hooks/useDeviceSyncDeviceScan.ts
index f575edf1..38c67ab6 100644
--- a/src/features/deviceSync/hooks/useDeviceSyncDeviceScan.ts
+++ b/src/features/deviceSync/hooks/useDeviceSyncDeviceScan.ts
@@ -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 '@/features/deviceSync/store/deviceSyncStore';
-import { showToast } from '@/utils/ui/toast';
+import { showToast } from '@/lib/dom/toast';
export interface DeviceSyncDeviceScanResult {
scanDevice: () => Promise;
diff --git a/src/features/deviceSync/hooks/useDeviceSyncJobEvents.ts b/src/features/deviceSync/hooks/useDeviceSyncJobEvents.ts
index f5653c94..8013fe0d 100644
--- a/src/features/deviceSync/hooks/useDeviceSyncJobEvents.ts
+++ b/src/features/deviceSync/hooks/useDeviceSyncJobEvents.ts
@@ -4,7 +4,7 @@ import { listen } from '@tauri-apps/api/event';
import type { TFunction } from 'i18next';
import { useDeviceSyncJobStore } from '@/features/deviceSync/store/deviceSyncJobStore';
import { useDeviceSyncStore } from '@/features/deviceSync/store/deviceSyncStore';
-import { showToast } from '@/utils/ui/toast';
+import { showToast } from '@/lib/dom/toast';
import { trackToSyncInfo } from '@/features/deviceSync/utils/deviceSyncHelpers';
import { fetchTracksForSource } from '@/features/playback/utils/playback/fetchTracksForSource';
diff --git a/src/features/deviceSync/utils/runDeviceSyncChooseFolder.ts b/src/features/deviceSync/utils/runDeviceSyncChooseFolder.ts
index e6296774..e782838a 100644
--- a/src/features/deviceSync/utils/runDeviceSyncChooseFolder.ts
+++ b/src/features/deviceSync/utils/runDeviceSyncChooseFolder.ts
@@ -2,7 +2,7 @@ import { invoke } from '@tauri-apps/api/core';
import { open as openDialog } from '@tauri-apps/plugin-dialog';
import type { TFunction } from 'i18next';
import { useDeviceSyncStore, type DeviceSyncSource } from '@/features/deviceSync/store/deviceSyncStore';
-import { showToast } from '@/utils/ui/toast';
+import { showToast } from '@/lib/dom/toast';
export interface RunDeviceSyncChooseFolderDeps {
t: TFunction;
diff --git a/src/features/deviceSync/utils/runDeviceSyncExecution.ts b/src/features/deviceSync/utils/runDeviceSyncExecution.ts
index 78bbc3a0..dc394d98 100644
--- a/src/features/deviceSync/utils/runDeviceSyncExecution.ts
+++ b/src/features/deviceSync/utils/runDeviceSyncExecution.ts
@@ -4,7 +4,7 @@ import { buildDownloadUrl } from '@/lib/api/subsonicStreamUrl';
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
import { useDeviceSyncStore, type DeviceSyncSource } from '@/features/deviceSync/store/deviceSyncStore';
import { useDeviceSyncJobStore } from '@/features/deviceSync/store/deviceSyncJobStore';
-import { showToast } from '@/utils/ui/toast';
+import { showToast } from '@/lib/dom/toast';
import { trackToSyncInfo, uuid } from '@/features/deviceSync/utils/deviceSyncHelpers';
import { fetchTracksForSource } from '@/features/playback/utils/playback/fetchTracksForSource';
diff --git a/src/features/fullscreenPlayer/components/FsLyricsApple.tsx b/src/features/fullscreenPlayer/components/FsLyricsApple.tsx
index 717f1aaa..848a8cca 100644
--- a/src/features/fullscreenPlayer/components/FsLyricsApple.tsx
+++ b/src/features/fullscreenPlayer/components/FsLyricsApple.tsx
@@ -6,7 +6,7 @@ import { useWordLyricsSync } from '@/features/lyrics';
import { getPlaybackProgressSnapshot, subscribePlaybackProgress } from '@/features/playback/store/playbackProgress';
import type { LrcLine } from '@/features/lyrics';
import type { Track } from '@/lib/media/trackTypes';
-import { EaseScroller, targetForFraction } from '@/utils/ui/easeScroll';
+import { EaseScroller, targetForFraction } from '@/lib/dom/easeScroll';
// Fullscreen synced lyrics.
// Full-screen scrollable list. The active line auto-scrolls following the
diff --git a/src/features/genre/pages/GenreDetail.tsx b/src/features/genre/pages/GenreDetail.tsx
index 9676b3a8..8e2e1a7e 100644
--- a/src/features/genre/pages/GenreDetail.tsx
+++ b/src/features/genre/pages/GenreDetail.tsx
@@ -29,7 +29,7 @@ import {
readAlbumBrowseRestore,
readAlbumDetailReturnTo,
} from '@/utils/navigation/albumDetailNavigation';
-import { usePerfProbeFlags } from '@/utils/perf/perfFlags';
+import { usePerfProbeFlags } from '@/lib/perf/perfFlags';
import { runBulkEnqueue, runBulkPlayAll, runBulkShuffle } from '@/features/playback/utils/playback/runBulkPlay';
export default function GenreDetail() {
diff --git a/src/features/home/components/Hero.tsx b/src/features/home/components/Hero.tsx
index 6d0ceca2..3ada7ca6 100644
--- a/src/features/home/components/Hero.tsx
+++ b/src/features/home/components/Hero.tsx
@@ -18,7 +18,7 @@ import { useWindowVisibility } from '@/lib/hooks/useWindowVisibility';
import { useAuthStore } from '@/store/authStore';
import { useThemeStore } from '@/store/themeStore';
import { filterAlbumsByMixRatings, getMixMinRatingsConfigFromAuth } from '@/utils/mix/mixRatingFilter';
-import { usePerfProbeFlags } from '@/utils/perf/perfFlags';
+import { usePerfProbeFlags } from '@/lib/perf/perfFlags';
import { playAlbum, playAlbumShuffled } from '@/features/playback/utils/playback/playAlbum';
import { useLongPressAction } from '@/lib/hooks/useLongPressAction';
import { LongPressWaveOverlay } from '@/ui/LongPressWaveOverlay';
diff --git a/src/features/home/components/SongRail.tsx b/src/features/home/components/SongRail.tsx
index fc035bb5..2acef919 100644
--- a/src/features/home/components/SongRail.tsx
+++ b/src/features/home/components/SongRail.tsx
@@ -2,7 +2,7 @@ import type { SubsonicSong } from '@/lib/api/subsonicTypes';
import React, { useRef, useState, useEffect, useMemo } from 'react';
import { ChevronLeft, ChevronRight, RefreshCw } from 'lucide-react';
import SongCard from '@/features/home/components/SongCard';
-import { usePerfProbeFlags } from '@/utils/perf/perfFlags';
+import { usePerfProbeFlags } from '@/lib/perf/perfFlags';
import { dedupeById } from '@/lib/util/dedupeById';
interface Props {
diff --git a/src/features/home/pages/Home.tsx b/src/features/home/pages/Home.tsx
index 72ec8f82..baff9532 100644
--- a/src/features/home/pages/Home.tsx
+++ b/src/features/home/pages/Home.tsx
@@ -14,8 +14,8 @@ import { ChevronRight } from 'lucide-react';
import { useHomeStore } from '@/features/home/store/homeStore';
import { useAuthStore } from '@/store/authStore';
import { filterAlbumsByMixRatings, getMixMinRatingsConfigFromAuth } from '@/utils/mix/mixRatingFilter';
-import { usePerfProbeFlags } from '@/utils/perf/perfFlags';
-import { bumpPerfCounter } from '@/utils/perf/perfTelemetry';
+import { usePerfProbeFlags } from '@/lib/perf/perfFlags';
+import { bumpPerfCounter } from '@/lib/perf/perfTelemetry';
import { dedupeById } from '@/lib/util/dedupeById';
import { shuffleArray } from '@/lib/util/shuffleArray';
import { useLibraryCoverPrefetch } from '@/cover/useLibraryCoverPrefetch';
diff --git a/src/features/lyrics/components/LyricsPane.tsx b/src/features/lyrics/components/LyricsPane.tsx
index 95b3f423..04fdf1d6 100644
--- a/src/features/lyrics/components/LyricsPane.tsx
+++ b/src/features/lyrics/components/LyricsPane.tsx
@@ -7,7 +7,7 @@ import { useLyrics, type WordLyricsLine } from '@/features/lyrics/hooks/useLyric
import { useAuthStore } from '@/store/authStore';
import { useTranslation } from 'react-i18next';
import type { Track } from '@/lib/media/trackTypes';
-import { EaseScroller, targetForFraction } from '@/utils/ui/easeScroll';
+import { EaseScroller, targetForFraction } from '@/lib/dom/easeScroll';
import OverlayScrollArea from '@/ui/OverlayScrollArea';
interface Props {
diff --git a/src/features/offline/pages/OfflineLibrary.tsx b/src/features/offline/pages/OfflineLibrary.tsx
index d025daac..0a458718 100644
--- a/src/features/offline/pages/OfflineLibrary.tsx
+++ b/src/features/offline/pages/OfflineLibrary.tsx
@@ -9,7 +9,7 @@ import { useAuthStore } from '@/store/authStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import { AlbumCoverArtImage } from '@/cover/AlbumCoverArtImage';
import { coverServerScopeForServerId } from '@/cover/serverScope';
-import { usePerfProbeFlags } from '@/utils/perf/perfFlags';
+import { usePerfProbeFlags } from '@/lib/perf/perfFlags';
import { GRID_COVER_WARM_LIMIT } from '@/cover/layoutSizes';
import { VirtualCardGrid } from '@/ui/VirtualCardGrid';
import { OfflineLibraryDiskStat } from '@/features/offline/components/OfflineLibraryDiskStat';
@@ -32,7 +32,7 @@ import {
type OfflineCoverQuadCell,
type OfflineLibraryCard,
} from '@/features/offline/utils/offlineLibraryHelpers';
-import { showToast } from '@/utils/ui/toast';
+import { showToast } from '@/lib/dom/toast';
import { shuffleArray } from '@/lib/util/shuffleArray';
import { getMediaDir } from '@/lib/media/mediaDir';
import { canonicalQueueServerKey, resolveIndexKey } from '@/utils/server/serverIndexKey';
diff --git a/src/features/offline/store/offlineStore.ts b/src/features/offline/store/offlineStore.ts
index 1c7f5e6a..a9b47aa8 100644
--- a/src/features/offline/store/offlineStore.ts
+++ b/src/features/offline/store/offlineStore.ts
@@ -7,7 +7,7 @@ import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import { invoke } from '@tauri-apps/api/core';
import { useAuthStore } from '@/store/authStore';
-import { showToast } from '@/utils/ui/toast';
+import { showToast } from '@/lib/dom/toast';
import { useOfflineJobStore, cancelledDownloads } from '@/features/offline/store/offlineJobStore';
import { useLocalPlaybackStore, type PinSource } from '@/store/localPlaybackStore';
import { getMediaDir } from '@/lib/media/mediaDir';
diff --git a/src/features/orbit/components/OrbitDiagnosticsPopover.tsx b/src/features/orbit/components/OrbitDiagnosticsPopover.tsx
index 82abfe1c..876109a1 100644
--- a/src/features/orbit/components/OrbitDiagnosticsPopover.tsx
+++ b/src/features/orbit/components/OrbitDiagnosticsPopover.tsx
@@ -4,7 +4,7 @@ import { Copy, Trash2 } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { useOrbitStore } from '@/features/orbit/store/orbitStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
-import { showToast } from '@/utils/ui/toast';
+import { showToast } from '@/lib/dom/toast';
import { computeOrbitDriftMs } from '@/features/orbit/utils/orbit';
import {
clearOrbitEvents,
diff --git a/src/features/orbit/components/OrbitJoinModal.tsx b/src/features/orbit/components/OrbitJoinModal.tsx
index 746272d1..2354e9a2 100644
--- a/src/features/orbit/components/OrbitJoinModal.tsx
+++ b/src/features/orbit/components/OrbitJoinModal.tsx
@@ -11,7 +11,7 @@ import {
} from '@/features/orbit/utils/orbit';
import { switchActiveServer } from '@/utils/server/switchActiveServer';
import { useOrbitAccountPickerStore } from '@/features/orbit/store/orbitAccountPickerStore';
-import { showToast } from '@/utils/ui/toast';
+import { showToast } from '@/lib/dom/toast';
interface Props {
onClose: () => void;
diff --git a/src/features/orbit/components/OrbitSettingsPopover.tsx b/src/features/orbit/components/OrbitSettingsPopover.tsx
index 749d5eff..c555fd71 100644
--- a/src/features/orbit/components/OrbitSettingsPopover.tsx
+++ b/src/features/orbit/components/OrbitSettingsPopover.tsx
@@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next';
import { useOrbitStore } from '@/features/orbit/store/orbitStore';
import { updateOrbitSettings, triggerOrbitShuffleNow } from '@/features/orbit/utils/orbit';
import { ORBIT_DEFAULT_SETTINGS, ORBIT_SHUFFLE_INTERVAL_PRESETS_MIN, type OrbitShuffleIntervalMin } from '@/features/orbit/api/orbit';
-import { showToast } from '@/utils/ui/toast';
+import { showToast } from '@/lib/dom/toast';
interface Props {
anchorRef: React.RefObject;
diff --git a/src/features/orbit/hooks/useOrbitGuest.ts b/src/features/orbit/hooks/useOrbitGuest.ts
index fd9ce31b..7af2e033 100644
--- a/src/features/orbit/hooks/useOrbitGuest.ts
+++ b/src/features/orbit/hooks/useOrbitGuest.ts
@@ -14,7 +14,7 @@ import {
forgetPendingSuggestion,
resetPendingResendState,
} from '@/features/orbit/utils/orbit';
-import { showToast } from '@/utils/ui/toast';
+import { showToast } from '@/lib/dom/toast';
import i18n from '@/lib/i18n';
import { estimateLivePosition, type OrbitState } from '@/features/orbit/api/orbit';
import { pushOrbitEvent } from '@/features/orbit/utils/orbitDiag';
diff --git a/src/features/orbit/hooks/useOrbitHost.ts b/src/features/orbit/hooks/useOrbitHost.ts
index 4d984eb6..632a044f 100644
--- a/src/features/orbit/hooks/useOrbitHost.ts
+++ b/src/features/orbit/hooks/useOrbitHost.ts
@@ -19,7 +19,7 @@ import {
type OrbitState,
type OrbitQueueItem,
} from '@/features/orbit/api/orbit';
-import { showToast } from '@/utils/ui/toast';
+import { showToast } from '@/lib/dom/toast';
import i18n from '@/lib/i18n';
import { pushOrbitEvent } from '@/features/orbit/utils/orbitDiag';
import { useOrbitOutboxHeartbeat } from '@/features/orbit/hooks/useOrbitOutboxHeartbeat';
diff --git a/src/features/orbit/hooks/useOrbitSongRowBehavior.ts b/src/features/orbit/hooks/useOrbitSongRowBehavior.ts
index 982f9755..6962d745 100644
--- a/src/features/orbit/hooks/useOrbitSongRowBehavior.ts
+++ b/src/features/orbit/hooks/useOrbitSongRowBehavior.ts
@@ -7,7 +7,7 @@ import {
evaluateOrbitSuggestGate,
OrbitSuggestBlockedError,
} from '@/features/orbit/utils/orbit';
-import { showToast } from '@/utils/ui/toast';
+import { showToast } from '@/lib/dom/toast';
/**
* Shared behaviour for song rows that in "normal mode" swallow a full list
diff --git a/src/features/playback/components/PlayerBar.tsx b/src/features/playback/components/PlayerBar.tsx
index c5202c34..6c174e47 100644
--- a/src/features/playback/components/PlayerBar.tsx
+++ b/src/features/playback/components/PlayerBar.tsx
@@ -21,7 +21,7 @@ import { usePlaybackDelayPress } from '@/features/playback/hooks/usePlaybackDela
import PlaybackDelayModal from '@/features/playback/components/PlaybackDelayModal';
import { usePlaybackScheduleRemaining } from '@/features/playback/utils/playbackScheduleFormat';
import { usePreviewStore } from '@/features/playback/store/previewStore';
-import { usePerfProbeFlags } from '@/utils/perf/perfFlags';
+import { usePerfProbeFlags } from '@/lib/perf/perfFlags';
import { coerceOpenArtistRefs } from '@/lib/api/openArtistRefs';
import { resolveTrackArtistRefs } from '@/features/playback/utils/playback/trackArtistRefs';
import { PlayerTrackInfo } from '@/features/playback/components/playerBar/PlayerTrackInfo';
diff --git a/src/features/playback/hooks/useAudioDevicesProbe.ts b/src/features/playback/hooks/useAudioDevicesProbe.ts
index a2288234..11a0e3c5 100644
--- a/src/features/playback/hooks/useAudioDevicesProbe.ts
+++ b/src/features/playback/hooks/useAudioDevicesProbe.ts
@@ -5,7 +5,7 @@ import type { TFunction } from 'i18next';
import { useAuthStore } from '@/store/authStore';
import { IS_MACOS } from '@/lib/util/platform';
import { sortAudioDeviceIds } from '@/features/playback/utils/audio/audioDeviceLabels';
-import { showToast } from '@/utils/ui/toast';
+import { showToast } from '@/lib/dom/toast';
interface UseAudioDevicesProbeResult {
audioDevices: string[];
diff --git a/src/features/playback/store/audioEventHandlers.ts b/src/features/playback/store/audioEventHandlers.ts
index 34d3db89..664bb4b1 100644
--- a/src/features/playback/store/audioEventHandlers.ts
+++ b/src/features/playback/store/audioEventHandlers.ts
@@ -17,8 +17,8 @@ import {
playListenSessionOpen,
} from '@/features/playback/store/playListenSession';
import { appendTimelineLeaveTrack } from '@/features/playback/store/timelineSessionHistory';
-import { getPerfProbeFlags } from '@/utils/perf/perfFlags';
-import { bumpPerfCounter } from '@/utils/perf/perfTelemetry';
+import { getPerfProbeFlags } from '@/lib/perf/perfFlags';
+import { bumpPerfCounter } from '@/lib/perf/perfTelemetry';
import {
getPlaybackCacheServerKey,
getPlaybackIndexKey,
@@ -29,7 +29,7 @@ import {
import { resolvePlaybackUrl } from '@/features/playback/utils/playback/resolvePlaybackUrl';
import { resolveReplayGainDb } from '@/features/playback/utils/audio/resolveReplayGainDb';
import { audioPlayHiResBlendArgs } from '@/lib/audio/hiResCrossfadeResample';
-import { showToast } from '@/utils/ui/toast';
+import { showToast } from '@/lib/dom/toast';
import { useAuthStore } from '@/store/authStore';
import { getPlayGeneration, setIsAudioPaused } from '@/features/playback/store/engineState';
import {
diff --git a/src/features/playback/store/miscActions.ts b/src/features/playback/store/miscActions.ts
index 305b3d84..188f1332 100644
--- a/src/features/playback/store/miscActions.ts
+++ b/src/features/playback/store/miscActions.ts
@@ -1,7 +1,7 @@
import { applyServerPlayQueue } from '@/features/playback/store/applyServerPlayQueue';
import { invoke } from '@tauri-apps/api/core';
import i18n from '@/lib/i18n';
-import { showToast } from '@/utils/ui/toast';
+import { showToast } from '@/lib/dom/toast';
import { useAuthStore } from '@/store/authStore';
import {
bumpPlayGeneration,
diff --git a/src/features/playback/store/radioPlayer.test.ts b/src/features/playback/store/radioPlayer.test.ts
index 13b47005..a8f06f0f 100644
--- a/src/features/playback/store/radioPlayer.test.ts
+++ b/src/features/playback/store/radioPlayer.test.ts
@@ -15,7 +15,7 @@ const hoisted = vi.hoisted(() => {
};
});
-vi.mock('@/utils/ui/toast', () => ({ showToast: hoisted.showToastMock }));
+vi.mock('@/lib/dom/toast', () => ({ showToast: hoisted.showToastMock }));
vi.mock('@/features/playback/store/playerStore', () => ({
usePlayerStore: {
getState: hoisted.playerStateGet,
diff --git a/src/features/playback/store/radioPlayer.ts b/src/features/playback/store/radioPlayer.ts
index 8314cde7..08e7fcd6 100644
--- a/src/features/playback/store/radioPlayer.ts
+++ b/src/features/playback/store/radioPlayer.ts
@@ -1,4 +1,4 @@
-import { showToast } from '@/utils/ui/toast';
+import { showToast } from '@/lib/dom/toast';
import { usePlayerStore } from '@/features/playback/store/playerStore';
/**
diff --git a/src/features/playlist/components/CsvImportReportModal.tsx b/src/features/playlist/components/CsvImportReportModal.tsx
index 11e32776..3308595b 100644
--- a/src/features/playlist/components/CsvImportReportModal.tsx
+++ b/src/features/playlist/components/CsvImportReportModal.tsx
@@ -2,7 +2,7 @@ import React from 'react';
import { createPortal } from 'react-dom';
import { useTranslation } from 'react-i18next';
import { Download, X } from 'lucide-react';
-import { showToast } from '@/utils/ui/toast';
+import { showToast } from '@/lib/dom/toast';
import type { SpotifyCsvTrack } from '@/features/playlist/utils/spotifyCsvImport';
interface CsvReportModalProps {
diff --git a/src/features/playlist/hooks/usePlaylistSongMutations.ts b/src/features/playlist/hooks/usePlaylistSongMutations.ts
index bc52457b..a5d9cb2d 100644
--- a/src/features/playlist/hooks/usePlaylistSongMutations.ts
+++ b/src/features/playlist/hooks/usePlaylistSongMutations.ts
@@ -1,7 +1,7 @@
import type React from 'react';
import type { TFunction } from 'i18next';
import type { SubsonicPlaylist, SubsonicSong } from '@/lib/api/subsonicTypes';
-import { showToast } from '@/utils/ui/toast';
+import { showToast } from '@/lib/dom/toast';
export interface PlaylistSongMutationsDeps {
songs: SubsonicSong[];
diff --git a/src/features/playlist/pages/Playlists.tsx b/src/features/playlist/pages/Playlists.tsx
index b9a68941..4d1b7455 100644
--- a/src/features/playlist/pages/Playlists.tsx
+++ b/src/features/playlist/pages/Playlists.tsx
@@ -25,7 +25,7 @@ import {
import PlaylistsSmartEditor from '@/features/playlist/components/PlaylistsSmartEditor';
import PlaylistsHeader from '@/features/playlist/components/PlaylistsHeader';
import PlaylistCard from '@/features/playlist/components/PlaylistCard';
-import { usePerfProbeFlags } from '@/utils/perf/perfFlags';
+import { usePerfProbeFlags } from '@/lib/perf/perfFlags';
import { VirtualCardGrid } from '@/ui/VirtualCardGrid';
import { useOfflineBrowseContext } from '@/features/offline';
import { offlineActionPolicy } from '@/features/offline';
diff --git a/src/features/playlist/utils/runPlaylistCsvImport.ts b/src/features/playlist/utils/runPlaylistCsvImport.ts
index 87244f5d..34bb8b7f 100644
--- a/src/features/playlist/utils/runPlaylistCsvImport.ts
+++ b/src/features/playlist/utils/runPlaylistCsvImport.ts
@@ -3,7 +3,7 @@ import { open as openDialog } from '@tauri-apps/plugin-dialog';
import { readTextFile } from '@tauri-apps/plugin-fs';
import { search } from '@/lib/api/subsonicSearch';
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
-import { showToast } from '@/utils/ui/toast';
+import { showToast } from '@/lib/dom/toast';
import { parseSpotifyCsv, type SpotifyCsvTrack } from '@/features/playlist/utils/spotifyCsvImport';
import {
cleanTrackTitle,
diff --git a/src/features/playlist/utils/runPlaylistSaveMeta.ts b/src/features/playlist/utils/runPlaylistSaveMeta.ts
index c1f24244..e5354701 100644
--- a/src/features/playlist/utils/runPlaylistSaveMeta.ts
+++ b/src/features/playlist/utils/runPlaylistSaveMeta.ts
@@ -1,7 +1,7 @@
import type { TFunction } from 'i18next';
import { getPlaylist, updatePlaylistMeta, uploadPlaylistCoverArt } from '@/lib/api/subsonicPlaylists';
import type { SubsonicPlaylist } from '@/lib/api/subsonicTypes';
-import { showToast } from '@/utils/ui/toast';
+import { showToast } from '@/lib/dom/toast';
export interface RunPlaylistSaveMetaDeps {
id: string;
diff --git a/src/features/playlist/utils/runPlaylistsActions.ts b/src/features/playlist/utils/runPlaylistsActions.ts
index e2b352f8..ed8203f4 100644
--- a/src/features/playlist/utils/runPlaylistsActions.ts
+++ b/src/features/playlist/utils/runPlaylistsActions.ts
@@ -3,7 +3,7 @@ import type { TFunction } from 'i18next';
import { deletePlaylist, getPlaylist, updatePlaylist } from '@/lib/api/subsonicPlaylists';
import type { SubsonicPlaylist } from '@/lib/api/subsonicTypes';
import { usePlaylistStore } from '@/features/playlist/store/playlistStore';
-import { showToast } from '@/utils/ui/toast';
+import { showToast } from '@/lib/dom/toast';
export interface RunPlaylistDeleteDeps {
e: React.MouseEvent;
diff --git a/src/features/playlist/utils/runPlaylistsOpenSmartEditor.ts b/src/features/playlist/utils/runPlaylistsOpenSmartEditor.ts
index 7f7422d1..06b55bb6 100644
--- a/src/features/playlist/utils/runPlaylistsOpenSmartEditor.ts
+++ b/src/features/playlist/utils/runPlaylistsOpenSmartEditor.ts
@@ -6,7 +6,7 @@ import {
defaultSmartFilters, displayPlaylistName, isSmartPlaylistName,
parseSmartRulesToFilters, type SmartFilters,
} from '@/features/playlist/utils/playlistsSmart';
-import { showToast } from '@/utils/ui/toast';
+import { showToast } from '@/lib/dom/toast';
export interface RunPlaylistsOpenSmartEditorDeps {
pl: SubsonicPlaylist;
diff --git a/src/features/playlist/utils/runPlaylistsSaveSmart.ts b/src/features/playlist/utils/runPlaylistsSaveSmart.ts
index 988dd4c7..591f248e 100644
--- a/src/features/playlist/utils/runPlaylistsSaveSmart.ts
+++ b/src/features/playlist/utils/runPlaylistsSaveSmart.ts
@@ -7,7 +7,7 @@ import {
buildSmartRulesPayload, defaultSmartFilters, SMART_PREFIX,
type PendingSmartPlaylist, type SmartFilters,
} from '@/features/playlist/utils/playlistsSmart';
-import { showToast } from '@/utils/ui/toast';
+import { showToast } from '@/lib/dom/toast';
export interface RunPlaylistsSaveSmartDeps {
isNavidromeServer: boolean;
diff --git a/src/features/queue/components/QueuePanel.tsx b/src/features/queue/components/QueuePanel.tsx
index 2ce5cb4d..1e2aabce 100644
--- a/src/features/queue/components/QueuePanel.tsx
+++ b/src/features/queue/components/QueuePanel.tsx
@@ -15,7 +15,7 @@ import { useAuthStore } from '@/store/authStore';
import { encodeSharePayload } from '@/utils/share/shareLink';
import { serverShareBaseUrl } from '@/utils/server/serverEndpoint';
import { copyTextToClipboard } from '@/utils/server/serverMagicString';
-import { showToast } from '@/utils/ui/toast';
+import { showToast } from '@/lib/dom/toast';
import { useThemeStore } from '@/store/themeStore';
import { useLyricsStore } from '@/store/lyricsStore';
import { LyricsPane } from '@/features/lyrics';
diff --git a/src/features/radio/components/RadioDirectoryModal.tsx b/src/features/radio/components/RadioDirectoryModal.tsx
index de37adcc..431df671 100644
--- a/src/features/radio/components/RadioDirectoryModal.tsx
+++ b/src/features/radio/components/RadioDirectoryModal.tsx
@@ -9,7 +9,7 @@ import {
import {
type InternetRadioStation, type RadioBrowserStation, RADIO_PAGE_SIZE,
} from '@/lib/api/subsonicTypes';
-import { showToast } from '@/utils/ui/toast';
+import { showToast } from '@/lib/dom/toast';
interface RadioDirectoryModalProps {
onClose: () => void;
diff --git a/src/features/radio/hooks/useRadioMetadata.ts b/src/features/radio/hooks/useRadioMetadata.ts
index 5d94ea29..5b2b9a01 100644
--- a/src/features/radio/hooks/useRadioMetadata.ts
+++ b/src/features/radio/hooks/useRadioMetadata.ts
@@ -1,7 +1,7 @@
import type { InternetRadioStation } from '@/lib/api/subsonicTypes';
import { useEffect, useRef, useState } from 'react';
import { invoke } from '@tauri-apps/api/core';
-import { usePerfProbeFlags } from '@/utils/perf/perfFlags';
+import { usePerfProbeFlags } from '@/lib/perf/perfFlags';
import {
guessAzuraCastApiUrl,
normaliseAzuraCastHomepageUrl,
diff --git a/src/features/radio/pages/InternetRadio.tsx b/src/features/radio/pages/InternetRadio.tsx
index 6ad1e26a..b2e9b56e 100644
--- a/src/features/radio/pages/InternetRadio.tsx
+++ b/src/features/radio/pages/InternetRadio.tsx
@@ -7,13 +7,13 @@ import { setRadioVolume } from '@/features/playback/store/radioPlayer';
import { fadeOut } from '@/features/playback/utils/playback/fadeOut';
import { invalidateCoverArt } from '@/utils/imageCache';
import { useTranslation } from 'react-i18next';
-import { showToast } from '@/utils/ui/toast';
+import { showToast } from '@/lib/dom/toast';
import RadioToolbar from '@/features/radio/components/RadioToolbar';
import AlphabetFilterBar from '@/features/radio/components/AlphabetFilterBar';
import RadioCard from '@/features/radio/components/RadioCard';
import RadioEditModal from '@/features/radio/components/RadioEditModal';
import RadioDirectoryModal from '@/features/radio/components/RadioDirectoryModal';
-import { usePerfProbeFlags } from '@/utils/perf/perfFlags';
+import { usePerfProbeFlags } from '@/lib/perf/perfFlags';
import { VirtualCardGrid } from '@/ui/VirtualCardGrid';
import { useNavidromeAdminRole, canManageNavidromeRadio } from '@/hooks/useNavidromeAdminRole';
diff --git a/src/features/randomMix/utils/luckyMix.ts b/src/features/randomMix/utils/luckyMix.ts
index 1622b23f..73d8021b 100644
--- a/src/features/randomMix/utils/luckyMix.ts
+++ b/src/features/randomMix/utils/luckyMix.ts
@@ -10,7 +10,7 @@ import { pushQueueUndoFromGetter } from '@/features/playback/store/queueUndo';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import { useLuckyMixStore } from '@/features/randomMix/store/luckyMixStore';
import { isLuckyMixAvailable } from '@/features/randomMix/hooks/useLuckyMixAvailable';
-import { showToast } from '@/utils/ui/toast';
+import { showToast } from '@/lib/dom/toast';
import {
bindQueueServerForPlayback,
playbackServerDiffersFromActive,
diff --git a/src/features/search/components/LiveSearch.tsx b/src/features/search/components/LiveSearch.tsx
index 841c6cd9..28478c1b 100644
--- a/src/features/search/components/LiveSearch.tsx
+++ b/src/features/search/components/LiveSearch.tsx
@@ -36,7 +36,7 @@ import { ArtistCoverArtImage } from '@/cover/ArtistCoverArtImage';
import { CoverArtImage } from '@/cover/CoverArtImage';
import { COVER_DENSE_SEARCH_CSS_PX } from '@/cover/layoutSizes';
import { albumCoverRef } from '@/cover/ref';
-import { showToast } from '@/utils/ui/toast';
+import { showToast } from '@/lib/dom/toast';
import { useShareSearch } from '@/features/search/hooks/useShareSearch';
import ShareSearchResults from '@/features/search/components/ShareSearchResults';
import {
diff --git a/src/features/search/components/MobileSearchOverlay.tsx b/src/features/search/components/MobileSearchOverlay.tsx
index 114190ae..916f4464 100644
--- a/src/features/search/components/MobileSearchOverlay.tsx
+++ b/src/features/search/components/MobileSearchOverlay.tsx
@@ -15,7 +15,7 @@ import { AlbumCoverArtImage } from '@/cover/AlbumCoverArtImage';
import { ArtistCoverArtImage } from '@/cover/ArtistCoverArtImage';
import { CoverArtImage } from '@/cover/CoverArtImage';
import { albumCoverRefForSong } from '@/cover/ref';
-import { showToast } from '@/utils/ui/toast';
+import { showToast } from '@/lib/dom/toast';
import { albumArtistDisplayName } from '@/features/album';
import { useShareSearch } from '@/features/search/hooks/useShareSearch';
import ShareSearchResults from '@/features/search/components/ShareSearchResults';
diff --git a/src/features/search/components/TracksPageChrome.tsx b/src/features/search/components/TracksPageChrome.tsx
index 067efc51..c7c92fa0 100644
--- a/src/features/search/components/TracksPageChrome.tsx
+++ b/src/features/search/components/TracksPageChrome.tsx
@@ -10,7 +10,7 @@ import { usePlayerStore } from '@/features/playback/store/playerStore';
import { SongRail } from '@/features/home';
import { playSongNow } from '@/features/playback/utils/playback/playSong';
import { ndListSongs, ndInvalidateSongsCache } from '@/lib/api/navidromeBrowse';
-import { usePerfProbeFlags } from '@/utils/perf/perfFlags';
+import { usePerfProbeFlags } from '@/lib/perf/perfFlags';
import { useNavigateToAlbum } from '@/features/album';
import { useNavigateToArtist } from '@/features/artist';
import { OpenArtistRefInline } from '@/features/artist';
diff --git a/src/features/search/pages/SearchBrowsePage.tsx b/src/features/search/pages/SearchBrowsePage.tsx
index 1a210918..d2fa8861 100644
--- a/src/features/search/pages/SearchBrowsePage.tsx
+++ b/src/features/search/pages/SearchBrowsePage.tsx
@@ -57,7 +57,7 @@ import {
} from '@/lib/library/browseTextSearch';
import { useLibraryIndexStore } from '@/store/libraryIndexStore';
import { MOOD_GROUP_IDS } from '@/config/moodGroups';
-import { usePerfProbeFlags } from '@/utils/perf/perfFlags';
+import { usePerfProbeFlags } from '@/lib/perf/perfFlags';
import { useSongBrowseList, type SongBrowseListRestore } from '@/features/search/hooks/useSongBrowseList';
import TracksPageChrome from '@/features/search/components/TracksPageChrome';
import SongBrowseSection from '@/features/search/components/SongBrowseSection';
diff --git a/src/features/settings/components/AddServerForm.test.tsx b/src/features/settings/components/AddServerForm.test.tsx
index 4051e2af..6a738475 100644
--- a/src/features/settings/components/AddServerForm.test.tsx
+++ b/src/features/settings/components/AddServerForm.test.tsx
@@ -11,11 +11,11 @@ vi.mock('@/api/network', () => ({
}));
// showToast mocked so we can assert two-LAN validation surfaced the error.
-vi.mock('@/utils/ui/toast', () => ({
+vi.mock('@/lib/dom/toast', () => ({
showToast: vi.fn(),
}));
-import { showToast } from '@/utils/ui/toast';
+import { showToast } from '@/lib/dom/toast';
describe('AddServerForm — dual-address behaviour', () => {
beforeEach(() => {
diff --git a/src/features/settings/components/AddServerForm.tsx b/src/features/settings/components/AddServerForm.tsx
index 7be144cd..ff3bd98e 100644
--- a/src/features/settings/components/AddServerForm.tsx
+++ b/src/features/settings/components/AddServerForm.tsx
@@ -1,7 +1,7 @@
import React, { useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import type { CustomHeaderEntry, CustomHeadersApplyTo, ServerProfile } from '@/store/authStoreTypes';
-import { showToast } from '@/utils/ui/toast';
+import { showToast } from '@/lib/dom/toast';
import {
DEFAULT_CUSTOM_HEADERS_APPLY_TO,
serverCustomHeadersFromForm,
diff --git a/src/features/settings/components/AnalyticsStrategySection.tsx b/src/features/settings/components/AnalyticsStrategySection.tsx
index 48db3573..e5287a85 100644
--- a/src/features/settings/components/AnalyticsStrategySection.tsx
+++ b/src/features/settings/components/AnalyticsStrategySection.tsx
@@ -23,7 +23,7 @@ import { libraryGetTracksBatch, type LibraryTrackDto, type TrackRefDto } from '@
import { buildStreamUrlForServer } from '@/lib/api/subsonicStreamUrl';
import { serverListDisplayLabel } from '@/utils/server/serverDisplayName';
import { serverIndexKeyForProfile } from '@/utils/server/serverIndexKey';
-import { showToast } from '@/utils/ui/toast';
+import { showToast } from '@/lib/dom/toast';
import {
ANALYTICS_STRATEGIES,
ADVANCED_PARALLELISM_MAX,
diff --git a/src/features/settings/components/BackupSection.tsx b/src/features/settings/components/BackupSection.tsx
index 297c1b7e..3bcfab68 100644
--- a/src/features/settings/components/BackupSection.tsx
+++ b/src/features/settings/components/BackupSection.tsx
@@ -10,7 +10,7 @@ import {
pickBackupExportPath,
pickBackupImportPath,
} from '@/features/settings/utils/backup';
-import { showToast } from '@/utils/ui/toast';
+import { showToast } from '@/lib/dom/toast';
type BackupMode = 'full' | 'library' | 'config';
type BackupAction = 'export' | 'import';
diff --git a/src/features/settings/components/CoverCacheStrategySection.tsx b/src/features/settings/components/CoverCacheStrategySection.tsx
index ee5d3e61..5095e188 100644
--- a/src/features/settings/components/CoverCacheStrategySection.tsx
+++ b/src/features/settings/components/CoverCacheStrategySection.tsx
@@ -16,7 +16,7 @@ import {
import { clearDiskSrcCacheForServer } from '@/cover/diskSrcCache';
import { serverListDisplayLabel } from '@/utils/server/serverDisplayName';
import { serverIndexKeyForProfile } from '@/utils/server/serverIndexKey';
-import { showToast } from '@/utils/ui/toast';
+import { showToast } from '@/lib/dom/toast';
import { formatBytes } from '@/lib/format/formatBytes';
import { clearImageCache, getImageCacheSize } from '@/utils/imageCache';
import { wakeLibraryCoverBackfill } from '@/lib/library/coverBackfillWake';
diff --git a/src/features/settings/components/InstalledThemes.tsx b/src/features/settings/components/InstalledThemes.tsx
index c79aafed..0dcd8be0 100644
--- a/src/features/settings/components/InstalledThemes.tsx
+++ b/src/features/settings/components/InstalledThemes.tsx
@@ -7,7 +7,7 @@ import { uninstallTheme } from '@/lib/themes/uninstallTheme';
import { installThemeFromRegistry } from '@/lib/themes/installThemeFromRegistry';
import { useThemeUpdates } from '@/features/settings/hooks/useThemeUpdates';
import { useThemeAnimationRisk } from '@/features/settings/hooks/useThemeAnimationRisk';
-import { showToast } from '@/utils/ui/toast';
+import { showToast } from '@/lib/dom/toast';
import { AnimatedThemeBadge } from '@/features/settings/components/AnimatedThemeBadge';
import { FIXED_THEMES } from '@/lib/themes/fixedThemes';
diff --git a/src/features/settings/components/ServersTab.tsx b/src/features/settings/components/ServersTab.tsx
index 05816375..a75dba86 100644
--- a/src/features/settings/components/ServersTab.tsx
+++ b/src/features/settings/components/ServersTab.tsx
@@ -28,7 +28,7 @@ import {
runIndexKeyRemigration,
} from '@/utils/server/serverUrlRemigration';
import { useConfirmModalStore } from '@/store/confirmModalStore';
-import { showToast } from '@/utils/ui/toast';
+import { showToast } from '@/lib/dom/toast';
import { FEATURE_AUDIOMUSE_SIMILAR_TRACKS } from '@/serverCapabilities/catalog';
import { isFeatureActiveForServer, resolveFeatureForServer } from '@/serverCapabilities/storeView';
import type { ResolvedCapability } from '@/serverCapabilities/types';
diff --git a/src/features/settings/components/SystemTab.tsx b/src/features/settings/components/SystemTab.tsx
index 962530a5..c60c4cb1 100644
--- a/src/features/settings/components/SystemTab.tsx
+++ b/src/features/settings/components/SystemTab.tsx
@@ -10,7 +10,7 @@ import i18n from '@/lib/i18n';
import { useAuthStore } from '@/store/authStore';
import type { ClockFormat, LinuxWaylandTextRenderProfile, LoggingMode } from '@/store/authStoreTypes';
import { IS_LINUX } from '@/lib/util/platform';
-import { showToast } from '@/utils/ui/toast';
+import { showToast } from '@/lib/dom/toast';
import { AboutPsysonicBrandHeader } from '@/features/settings/components/AboutPsysonicLol';
import CustomSelect from '@/ui/CustomSelect';
import LicensesPanel from '@/features/settings/components/LicensesPanel';
diff --git a/src/features/settings/components/ThemeImportSection.tsx b/src/features/settings/components/ThemeImportSection.tsx
index e2603ddb..e75b2dba 100644
--- a/src/features/settings/components/ThemeImportSection.tsx
+++ b/src/features/settings/components/ThemeImportSection.tsx
@@ -5,7 +5,7 @@ import { invoke } from '@tauri-apps/api/core';
import { open } from '@tauri-apps/plugin-dialog';
import { useInstalledThemesStore } from '@/store/installedThemesStore';
import { validateThemePackage, type ValidatedTheme } from '@/lib/themes/validateThemePackage';
-import { showToast } from '@/utils/ui/toast';
+import { showToast } from '@/lib/dom/toast';
import ConfirmModal from '@/components/ConfirmModal';
/**
diff --git a/src/features/settings/components/UserForm.tsx b/src/features/settings/components/UserForm.tsx
index 4e329d6d..86a2708d 100644
--- a/src/features/settings/components/UserForm.tsx
+++ b/src/features/settings/components/UserForm.tsx
@@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Shield, Wand2 } from 'lucide-react';
import { ndUpdateUser, type NdLibrary, type NdUser } from '@/lib/api/navidromeAdmin';
-import { showToast } from '@/utils/ui/toast';
+import { showToast } from '@/lib/dom/toast';
import {
copyTextToClipboard,
encodeServerMagicString,
diff --git a/src/features/settings/components/musicNetwork/MusicNetworkSection.tsx b/src/features/settings/components/musicNetwork/MusicNetworkSection.tsx
index 85803cc9..80ce6766 100644
--- a/src/features/settings/components/musicNetwork/MusicNetworkSection.tsx
+++ b/src/features/settings/components/musicNetwork/MusicNetworkSection.tsx
@@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next';
import { Share2 } from 'lucide-react';
import SettingsSubSection from '@/features/settings/components/SettingsSubSection';
import { SettingsGroup } from '@/features/settings/components/SettingsGroup';
-import { showToast } from '@/utils/ui/toast';
+import { showToast } from '@/lib/dom/toast';
import { useAuthStore } from '@/store/authStore';
import {
errorI18nKey,
diff --git a/src/features/settings/components/userMgmt/MagicStringModal.tsx b/src/features/settings/components/userMgmt/MagicStringModal.tsx
index 53bd1276..04006fab 100644
--- a/src/features/settings/components/userMgmt/MagicStringModal.tsx
+++ b/src/features/settings/components/userMgmt/MagicStringModal.tsx
@@ -3,7 +3,7 @@ import { createPortal } from 'react-dom';
import { X } from 'lucide-react';
import type { TFunction } from 'i18next';
import { ndUpdateUser, type NdUser } from '@/lib/api/navidromeAdmin';
-import { showToast } from '@/utils/ui/toast';
+import { showToast } from '@/lib/dom/toast';
import {
copyTextToClipboard,
encodeServerMagicString,
diff --git a/src/features/settings/hooks/useUserMgmtActions.ts b/src/features/settings/hooks/useUserMgmtActions.ts
index 9e333729..dd9ca800 100644
--- a/src/features/settings/hooks/useUserMgmtActions.ts
+++ b/src/features/settings/hooks/useUserMgmtActions.ts
@@ -8,7 +8,7 @@ import {
type NdLibrary,
type NdUser,
} from '@/lib/api/navidromeAdmin';
-import { showToast } from '@/utils/ui/toast';
+import { showToast } from '@/lib/dom/toast';
import {
copyTextToClipboard,
encodeServerMagicString,
diff --git a/src/features/sidebar/components/Sidebar.tsx b/src/features/sidebar/components/Sidebar.tsx
index 4172d2c6..929ad48e 100644
--- a/src/features/sidebar/components/Sidebar.tsx
+++ b/src/features/sidebar/components/Sidebar.tsx
@@ -18,7 +18,7 @@ import {
getSystemItemsForReorder,
} from '@/features/sidebar/utils/sidebarNavReorder';
import { useLuckyMixAvailable } from '@/features/randomMix';
-import { usePerfProbeFlags } from '@/utils/perf/perfFlags';
+import { usePerfProbeFlags } from '@/lib/perf/perfFlags';
import { useSidebarNewReleasesUnread } from '@/features/sidebar/hooks/useSidebarNewReleasesUnread';
import { useSidebarNavDnd } from '@/features/sidebar/hooks/useSidebarNavDnd';
import { useSidebarLibraryDropdown } from '@/features/sidebar/hooks/useSidebarLibraryDropdown';
diff --git a/src/features/sidebar/components/SidebarPerfProbeModal.tsx b/src/features/sidebar/components/SidebarPerfProbeModal.tsx
index e10cddd1..19df0eab 100644
--- a/src/features/sidebar/components/SidebarPerfProbeModal.tsx
+++ b/src/features/sidebar/components/SidebarPerfProbeModal.tsx
@@ -6,10 +6,10 @@ import SidebarPerfProbeTogglesTab from '@/features/sidebar/components/perfProbe/
import SidebarPerfProbeTuningTab from '@/features/sidebar/components/perfProbe/SidebarPerfProbeTuningTab';
import SidebarPerfProbeLogsTab from '@/features/sidebar/components/perfProbe/SidebarPerfProbeLogsTab';
import SidebarPerfProbeConnectionsTab from '@/features/sidebar/components/perfProbe/SidebarPerfProbeConnectionsTab';
-import { resetPerfProbeFlags, type PerfProbeFlags } from '@/utils/perf/perfFlags';
-import { clearPerfLiveOverlayPins } from '@/utils/perf/perfOverlayPins';
-import { resetPerfOverlayAppearance } from '@/utils/perf/perfOverlayAppearance';
-import { resetPerfOverlayMode } from '@/utils/perf/perfOverlayMode';
+import { resetPerfProbeFlags, type PerfProbeFlags } from '@/lib/perf/perfFlags';
+import { clearPerfLiveOverlayPins } from '@/lib/perf/perfOverlayPins';
+import { resetPerfOverlayAppearance } from '@/lib/perf/perfOverlayAppearance';
+import { resetPerfOverlayMode } from '@/lib/perf/perfOverlayMode';
type TabId = 'monitor' | 'connections' | 'toggles' | 'tuning' | 'logs';
diff --git a/src/features/sidebar/components/perfProbe/PerfLivePollControls.tsx b/src/features/sidebar/components/perfProbe/PerfLivePollControls.tsx
index a71aa8a5..863e5357 100644
--- a/src/features/sidebar/components/perfProbe/PerfLivePollControls.tsx
+++ b/src/features/sidebar/components/perfProbe/PerfLivePollControls.tsx
@@ -1,4 +1,4 @@
-import { perfLiveCpuSnapshotSupported } from '@/utils/perf/perfLiveCpuSnapshot';
+import { perfLiveCpuSnapshotSupported } from '@/lib/perf/perfLiveCpuSnapshot';
import {
PERF_LIVE_POLL_MS_MAX,
PERF_LIVE_POLL_MS_MIN,
@@ -7,7 +7,7 @@ import {
setPerfLivePollIntervalMs,
usePerfLiveIncludeThreadGroups,
usePerfLivePollIntervalMs,
-} from '@/utils/perf/perfLivePollSettings';
+} from '@/lib/perf/perfLivePollSettings';
export default function PerfLivePollControls() {
const pollMs = usePerfLivePollIntervalMs();
diff --git a/src/features/sidebar/components/perfProbe/PerfOverlayAppearanceControls.tsx b/src/features/sidebar/components/perfProbe/PerfOverlayAppearanceControls.tsx
index aed1aca5..0ef67900 100644
--- a/src/features/sidebar/components/perfProbe/PerfOverlayAppearanceControls.tsx
+++ b/src/features/sidebar/components/perfProbe/PerfOverlayAppearanceControls.tsx
@@ -3,7 +3,7 @@ import {
setPerfOverlayCorner,
setPerfOverlayOpacity,
usePerfOverlayAppearance,
-} from '@/utils/perf/perfOverlayAppearance';
+} from '@/lib/perf/perfOverlayAppearance';
export default function PerfOverlayAppearanceControls() {
const { corner, opacity } = usePerfOverlayAppearance();
diff --git a/src/features/sidebar/components/perfProbe/PerfOverlayModeControls.tsx b/src/features/sidebar/components/perfProbe/PerfOverlayModeControls.tsx
index ee39832e..9e27e81e 100644
--- a/src/features/sidebar/components/perfProbe/PerfOverlayModeControls.tsx
+++ b/src/features/sidebar/components/perfProbe/PerfOverlayModeControls.tsx
@@ -2,7 +2,7 @@ import {
PERF_OVERLAY_MODE_OPTIONS,
setPerfOverlayMode,
usePerfOverlayMode,
-} from '@/utils/perf/perfOverlayMode';
+} from '@/lib/perf/perfOverlayMode';
export default function PerfOverlayModeControls() {
const mode = usePerfOverlayMode();
diff --git a/src/features/sidebar/components/perfProbe/SidebarPerfProbeLogsTab.tsx b/src/features/sidebar/components/perfProbe/SidebarPerfProbeLogsTab.tsx
index c55617ab..09cc4139 100644
--- a/src/features/sidebar/components/perfProbe/SidebarPerfProbeLogsTab.tsx
+++ b/src/features/sidebar/components/perfProbe/SidebarPerfProbeLogsTab.tsx
@@ -16,8 +16,8 @@ import { invoke } from '@tauri-apps/api/core';
import { useAuthStore } from '@/store/authStore';
import type { LoggingMode } from '@/store/authStoreTypes';
import CustomSelect from '@/ui/CustomSelect';
-import { filterLogLines } from '@/utils/perf/filterLogLines';
-import { sanitizeLogLine } from '@/utils/perf/sanitizeLogLine';
+import { filterLogLines } from '@/lib/perf/filterLogLines';
+import { sanitizeLogLine } from '@/lib/perf/sanitizeLogLine';
function formatLogLinesText(lines: RuntimeLogLine[]): string {
return lines.map(line => line.text).join('\n');
diff --git a/src/features/sidebar/components/perfProbe/SidebarPerfProbeMonitorTab.tsx b/src/features/sidebar/components/perfProbe/SidebarPerfProbeMonitorTab.tsx
index 6549f5d3..aec352c1 100644
--- a/src/features/sidebar/components/perfProbe/SidebarPerfProbeMonitorTab.tsx
+++ b/src/features/sidebar/components/perfProbe/SidebarPerfProbeMonitorTab.tsx
@@ -1,13 +1,13 @@
import { useMemo, useRef } from 'react';
-import { isPerfLivePollWaitingForCpu, usePerfLiveSnapshot } from '@/utils/perf/perfLiveStore';
-import { usePerfLiveIncludeThreadGroups } from '@/utils/perf/perfLivePollSettings';
+import { isPerfLivePollWaitingForCpu, usePerfLiveSnapshot } from '@/lib/perf/perfLiveStore';
+import { usePerfLiveIncludeThreadGroups } from '@/lib/perf/perfLivePollSettings';
import {
togglePerfLiveOverlayPin,
togglePipelineOverlayPin,
usePerfLiveOverlayPins,
usePipelineOverlayPinned,
type PerfLiveOverlayPinId,
-} from '@/utils/perf/perfOverlayPins';
+} from '@/lib/perf/perfOverlayPins';
import PerfProbeMetricCard, { PerfProbeMetricSection } from '@/features/sidebar/components/perfProbe/PerfProbeMetricCard';
import PerfOverlayAppearanceControls from '@/features/sidebar/components/perfProbe/PerfOverlayAppearanceControls';
import PerfOverlayModeControls from '@/features/sidebar/components/perfProbe/PerfOverlayModeControls';
diff --git a/src/features/sidebar/components/perfProbe/SidebarPerfProbeTogglesTab.tsx b/src/features/sidebar/components/perfProbe/SidebarPerfProbeTogglesTab.tsx
index 9f3357b4..a4dffd79 100644
--- a/src/features/sidebar/components/perfProbe/SidebarPerfProbeTogglesTab.tsx
+++ b/src/features/sidebar/components/perfProbe/SidebarPerfProbeTogglesTab.tsx
@@ -1,12 +1,12 @@
import { useState, type CSSProperties } from 'react';
import { ChevronRight } from 'lucide-react';
-import { setPerfProbeFlag, type PerfProbeFlags } from '@/utils/perf/perfFlags';
-import type { PerfToggleEngineLeaf, PerfToggleLeaf, PerfToggleNode } from '@/utils/perf/perfProbeToggleTree';
+import { setPerfProbeFlag, type PerfProbeFlags } from '@/lib/perf/perfFlags';
+import type { PerfToggleEngineLeaf, PerfToggleLeaf, PerfToggleNode } from '@/lib/perf/perfProbeToggleTree';
import {
isPerfToggleEngineLeaf,
isPerfToggleGroup,
PERF_PROBE_TOGGLE_TREE,
-} from '@/utils/perf/perfProbeToggleTree';
+} from '@/lib/perf/perfProbeToggleTree';
interface EngineProps {
hotCacheEnabled: boolean;
diff --git a/src/features/sidebar/hooks/useSidebarPerfProbe.ts b/src/features/sidebar/hooks/useSidebarPerfProbe.ts
index 31e8f3c3..0eaeb1d3 100644
--- a/src/features/sidebar/hooks/useSidebarPerfProbe.ts
+++ b/src/features/sidebar/hooks/useSidebarPerfProbe.ts
@@ -1,14 +1,14 @@
import { useEffect, useState } from 'react';
-import { acquirePerfLivePoll, patchPerfLiveAnalysis } from '@/utils/perf/perfLiveStore';
-import { setPerfProbeTelemetryActive } from '@/utils/perf/perfTelemetry';
-import { useAnalysisPerfLast } from '@/utils/perf/analysisPerfStore';
+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 { useCoverPerfListener, useCoverUiThroughputPoll } from '@/cover/useCoverPerfListener';
import {
getPerfProbeFlags,
subscribePerfProbeFlags,
-} from '@/utils/perf/perfFlags';
-import { hasAnyLiveMetricPollNeed, usePerfLiveOverlayPins } from '@/utils/perf/perfOverlayPins';
+} from '@/lib/perf/perfFlags';
+import { hasAnyLiveMetricPollNeed, usePerfLiveOverlayPins } from '@/lib/perf/perfOverlayPins';
import { useSyncExternalStore } from 'react';
interface Result {
diff --git a/src/features/stats/components/StatsExportModal.tsx b/src/features/stats/components/StatsExportModal.tsx
index 7a5b2336..63de3348 100644
--- a/src/features/stats/components/StatsExportModal.tsx
+++ b/src/features/stats/components/StatsExportModal.tsx
@@ -6,7 +6,7 @@ import { useTranslation } from 'react-i18next';
import { X } from 'lucide-react';
import { save } from '@tauri-apps/plugin-dialog';
import { writeFile } from '@tauri-apps/plugin-fs';
-import { showToast } from '@/utils/ui/toast';
+import { showToast } from '@/lib/dom/toast';
import {
exportAlbumCardBlob,
renderAlbumCardCanvas,
diff --git a/src/features/waveform/utils/waveformSeekRenderers.ts b/src/features/waveform/utils/waveformSeekRenderers.ts
index 98a380f9..86e96f6d 100644
--- a/src/features/waveform/utils/waveformSeekRenderers.ts
+++ b/src/features/waveform/utils/waveformSeekRenderers.ts
@@ -1,5 +1,5 @@
import type { SeekbarStyle } from '@/store/authStoreTypes';
-import { bumpPerfCounter } from '@/utils/perf/perfTelemetry';
+import { bumpPerfCounter } from '@/lib/perf/perfTelemetry';
import { AnimState, makeAnimState } from '@/features/waveform/utils/waveformSeekHelpers';
import {
drawBar, drawLineDot, drawNeon, drawSegmented, drawThick, drawWaveform,
diff --git a/src/hooks/useAnalysisPerfListener.ts b/src/hooks/useAnalysisPerfListener.ts
index 8a9e147a..036dc9fc 100644
--- a/src/hooks/useAnalysisPerfListener.ts
+++ b/src/hooks/useAnalysisPerfListener.ts
@@ -1,6 +1,6 @@
import { useEffect } from 'react';
import { listen } from '@tauri-apps/api/event';
-import { recordAnalysisTrackPerf } from '../utils/perf/analysisPerfStore';
+import { recordAnalysisTrackPerf } from '@/lib/perf/analysisPerfStore';
type AnalysisTrackPerfPayload = {
trackId: string;
diff --git a/src/hooks/useConnectionStatus.test.ts b/src/hooks/useConnectionStatus.test.ts
index c8495f86..7f73a146 100644
--- a/src/hooks/useConnectionStatus.test.ts
+++ b/src/hooks/useConnectionStatus.test.ts
@@ -13,7 +13,7 @@ vi.mock('@/lib/api/subsonic', () => ({
scheduleInstantMixProbeForServer: vi.fn(),
}));
-vi.mock('@/utils/perf/perfFlags', () => ({
+vi.mock('@/lib/perf/perfFlags', () => ({
usePerfProbeFlags: () => ({ disableBackgroundPolling: false }),
}));
diff --git a/src/hooks/useConnectionStatus.ts b/src/hooks/useConnectionStatus.ts
index dba00c8d..4342c0e1 100644
--- a/src/hooks/useConnectionStatus.ts
+++ b/src/hooks/useConnectionStatus.ts
@@ -15,7 +15,7 @@ import {
subscribeConnectionStatus,
type ConnectionStatus,
} from '../utils/network/activeServerReachability';
-import { usePerfProbeFlags } from '../utils/perf/perfFlags';
+import { usePerfProbeFlags } from '@/lib/perf/perfFlags';
import {
isDevOfflineBrowseForced,
useDevOfflineBrowseStore,
diff --git a/src/utils/ui/dragGhost.ts b/src/lib/dom/dragGhost.ts
similarity index 100%
rename from src/utils/ui/dragGhost.ts
rename to src/lib/dom/dragGhost.ts
diff --git a/src/utils/ui/dynamicColors.dom.test.ts b/src/lib/dom/dynamicColors.dom.test.ts
similarity index 99%
rename from src/utils/ui/dynamicColors.dom.test.ts
rename to src/lib/dom/dynamicColors.dom.test.ts
index 94631fc7..0c0d1d69 100644
--- a/src/utils/ui/dynamicColors.dom.test.ts
+++ b/src/lib/dom/dynamicColors.dom.test.ts
@@ -9,7 +9,7 @@
* Pure-math helpers live in `dynamicColors.test.ts`.
*/
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
-import { extractCoverColors } from './dynamicColors';
+import { extractCoverColors } from '@/lib/dom/dynamicColors';
type ImageBehavior = 'load' | 'error';
diff --git a/src/utils/ui/dynamicColors.test.ts b/src/lib/dom/dynamicColors.test.ts
similarity index 99%
rename from src/utils/ui/dynamicColors.test.ts
rename to src/lib/dom/dynamicColors.test.ts
index 95f9b510..aab6292e 100644
--- a/src/utils/ui/dynamicColors.test.ts
+++ b/src/lib/dom/dynamicColors.test.ts
@@ -6,7 +6,7 @@ import {
rgbToHsl,
hslToRgb,
ensureContrast,
-} from './dynamicColors';
+} from '@/lib/dom/dynamicColors';
// ─── srgbToLinear ─────────────────────────────────────────────────────────────
diff --git a/src/utils/ui/dynamicColors.ts b/src/lib/dom/dynamicColors.ts
similarity index 100%
rename from src/utils/ui/dynamicColors.ts
rename to src/lib/dom/dynamicColors.ts
diff --git a/src/utils/ui/easeScroll.ts b/src/lib/dom/easeScroll.ts
similarity index 100%
rename from src/utils/ui/easeScroll.ts
rename to src/lib/dom/easeScroll.ts
diff --git a/src/utils/ui/overlayScrollbarMetrics.ts b/src/lib/dom/overlayScrollbarMetrics.ts
similarity index 100%
rename from src/utils/ui/overlayScrollbarMetrics.ts
rename to src/lib/dom/overlayScrollbarMetrics.ts
diff --git a/src/utils/ui/overlayScrollbarThumb.ts b/src/lib/dom/overlayScrollbarThumb.ts
similarity index 96%
rename from src/utils/ui/overlayScrollbarThumb.ts
rename to src/lib/dom/overlayScrollbarThumb.ts
index 04bf3b2e..f32fabba 100644
--- a/src/utils/ui/overlayScrollbarThumb.ts
+++ b/src/lib/dom/overlayScrollbarThumb.ts
@@ -1,5 +1,5 @@
import type { PointerEvent as ReactPointerEvent } from 'react';
-import { computeOverlayScrollbarThumbMeta } from './overlayScrollbarMetrics';
+import { computeOverlayScrollbarThumbMeta } from '@/lib/dom/overlayScrollbarMetrics';
/**
* Drag the overlay scrollbar thumb (native bar is hidden). Maps pointer delta
diff --git a/src/utils/ui/resolveIntersectionScrollRoot.test.ts b/src/lib/dom/resolveIntersectionScrollRoot.test.ts
similarity index 92%
rename from src/utils/ui/resolveIntersectionScrollRoot.test.ts
rename to src/lib/dom/resolveIntersectionScrollRoot.test.ts
index 90a7aa52..9c3788b8 100644
--- a/src/utils/ui/resolveIntersectionScrollRoot.test.ts
+++ b/src/lib/dom/resolveIntersectionScrollRoot.test.ts
@@ -1,7 +1,7 @@
// @vitest-environment jsdom
import { describe, expect, it, beforeEach } from 'vitest';
-import { APP_MAIN_SCROLL_VIEWPORT_ID } from '../../constants/appScroll';
-import { resolveIntersectionScrollRoot } from './resolveIntersectionScrollRoot';
+import { APP_MAIN_SCROLL_VIEWPORT_ID } from '@/constants/appScroll';
+import { resolveIntersectionScrollRoot } from '@/lib/dom/resolveIntersectionScrollRoot';
describe('resolveIntersectionScrollRoot', () => {
beforeEach(() => {
diff --git a/src/utils/ui/resolveIntersectionScrollRoot.ts b/src/lib/dom/resolveIntersectionScrollRoot.ts
similarity index 94%
rename from src/utils/ui/resolveIntersectionScrollRoot.ts
rename to src/lib/dom/resolveIntersectionScrollRoot.ts
index 3db5c416..c0d54f3d 100644
--- a/src/utils/ui/resolveIntersectionScrollRoot.ts
+++ b/src/lib/dom/resolveIntersectionScrollRoot.ts
@@ -1,4 +1,4 @@
-import { APP_MAIN_SCROLL_VIEWPORT_ID } from '../../constants/appScroll';
+import { APP_MAIN_SCROLL_VIEWPORT_ID } from '@/constants/appScroll';
/**
* Scroll container for `IntersectionObserver` priority scoring on cover art.
diff --git a/src/utils/ui/toast.ts b/src/lib/dom/toast.ts
similarity index 100%
rename from src/utils/ui/toast.ts
rename to src/lib/dom/toast.ts
diff --git a/src/lib/library/hooks/useLibraryIndexSync.ts b/src/lib/library/hooks/useLibraryIndexSync.ts
index 974e65ae..3a4194b4 100644
--- a/src/lib/library/hooks/useLibraryIndexSync.ts
+++ b/src/lib/library/hooks/useLibraryIndexSync.ts
@@ -2,7 +2,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useAuthStore } from '@/store/authStore';
import { useLibraryIndexStore } from '@/store/libraryIndexStore';
-import { showToast } from '@/utils/ui/toast';
+import { showToast } from '@/lib/dom/toast';
import { resolveIndexKey, serverIndexKeyForProfile } from '@/utils/server/serverIndexKey';
import {
libraryGetStatus,
diff --git a/src/utils/perf/analysisPerfStore.test.ts b/src/lib/perf/analysisPerfStore.test.ts
similarity index 97%
rename from src/utils/perf/analysisPerfStore.test.ts
rename to src/lib/perf/analysisPerfStore.test.ts
index 48f59e2d..2f479b3c 100644
--- a/src/utils/perf/analysisPerfStore.test.ts
+++ b/src/lib/perf/analysisPerfStore.test.ts
@@ -3,7 +3,7 @@ import {
getAnalysisTracksPerMinute,
recordAnalysisTrackPerf,
resetAnalysisPerfStateForTest,
-} from './analysisPerfStore';
+} from '@/lib/perf/analysisPerfStore';
beforeEach(() => {
resetAnalysisPerfStateForTest();
diff --git a/src/utils/perf/analysisPerfStore.ts b/src/lib/perf/analysisPerfStore.ts
similarity index 100%
rename from src/utils/perf/analysisPerfStore.ts
rename to src/lib/perf/analysisPerfStore.ts
diff --git a/src/utils/perf/coverPerfStore.test.ts b/src/lib/perf/coverPerfStore.test.ts
similarity index 98%
rename from src/utils/perf/coverPerfStore.test.ts
rename to src/lib/perf/coverPerfStore.test.ts
index 2d99e21d..a5fac854 100644
--- a/src/utils/perf/coverPerfStore.test.ts
+++ b/src/lib/perf/coverPerfStore.test.ts
@@ -6,7 +6,7 @@ import {
recordCoverProgress,
recordCoverUiTotal,
resetCoverPerfStateForTest,
-} from './coverPerfStore';
+} from '@/lib/perf/coverPerfStore';
beforeEach(() => {
resetCoverPerfStateForTest();
diff --git a/src/utils/perf/coverPerfStore.ts b/src/lib/perf/coverPerfStore.ts
similarity index 100%
rename from src/utils/perf/coverPerfStore.ts
rename to src/lib/perf/coverPerfStore.ts
diff --git a/src/utils/perf/filterLogLines.test.ts b/src/lib/perf/filterLogLines.test.ts
similarity index 96%
rename from src/utils/perf/filterLogLines.test.ts
rename to src/lib/perf/filterLogLines.test.ts
index 2bb304fd..46a869d8 100644
--- a/src/utils/perf/filterLogLines.test.ts
+++ b/src/lib/perf/filterLogLines.test.ts
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest';
-import { filterLogLines, parseLogFilter } from './filterLogLines';
+import { filterLogLines, parseLogFilter } from '@/lib/perf/filterLogLines';
const L = (text: string) => ({ text });
const lines = [
diff --git a/src/utils/perf/filterLogLines.ts b/src/lib/perf/filterLogLines.ts
similarity index 100%
rename from src/utils/perf/filterLogLines.ts
rename to src/lib/perf/filterLogLines.ts
diff --git a/src/utils/perf/formatAnalysisQueueStats.test.ts b/src/lib/perf/formatAnalysisQueueStats.test.ts
similarity index 97%
rename from src/utils/perf/formatAnalysisQueueStats.test.ts
rename to src/lib/perf/formatAnalysisQueueStats.test.ts
index 2a845549..3944ef50 100644
--- a/src/utils/perf/formatAnalysisQueueStats.test.ts
+++ b/src/lib/perf/formatAnalysisQueueStats.test.ts
@@ -2,7 +2,7 @@ import { describe, it, expect } from 'vitest';
import {
formatAnalysisPipelineQueueOverlay,
formatAnalysisTierQueue,
-} from './formatAnalysisQueueStats';
+} from '@/lib/perf/formatAnalysisQueueStats';
describe('formatAnalysisQueueStats', () => {
it('formats tier queue as total(high,middle,low)', () => {
diff --git a/src/utils/perf/formatAnalysisQueueStats.ts b/src/lib/perf/formatAnalysisQueueStats.ts
similarity index 95%
rename from src/utils/perf/formatAnalysisQueueStats.ts
rename to src/lib/perf/formatAnalysisQueueStats.ts
index a0911ed2..45d2c1c3 100644
--- a/src/utils/perf/formatAnalysisQueueStats.ts
+++ b/src/lib/perf/formatAnalysisQueueStats.ts
@@ -1,4 +1,4 @@
-import type { AnalysisPipelineQueueStatsDto } from '../../api/analysis';
+import type { AnalysisPipelineQueueStatsDto } from '@/api/analysis';
export function formatAnalysisTierQueue(
total: number,
diff --git a/src/utils/perf/formatCoverPipelineQueueOverlay.test.ts b/src/lib/perf/formatCoverPipelineQueueOverlay.test.ts
similarity index 94%
rename from src/utils/perf/formatCoverPipelineQueueOverlay.test.ts
rename to src/lib/perf/formatCoverPipelineQueueOverlay.test.ts
index 1ae851a3..a714cb8b 100644
--- a/src/utils/perf/formatCoverPipelineQueueOverlay.test.ts
+++ b/src/lib/perf/formatCoverPipelineQueueOverlay.test.ts
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest';
-import { formatCoverPipelineQueueOverlay } from './formatCoverPipelineQueueOverlay';
+import { formatCoverPipelineQueueOverlay } from '@/lib/perf/formatCoverPipelineQueueOverlay';
describe('formatCoverPipelineQueueOverlay', () => {
it('formats ensure tiers, rust pools, and optional peek backlog', () => {
diff --git a/src/utils/perf/formatCoverPipelineQueueOverlay.ts b/src/lib/perf/formatCoverPipelineQueueOverlay.ts
similarity index 80%
rename from src/utils/perf/formatCoverPipelineQueueOverlay.ts
rename to src/lib/perf/formatCoverPipelineQueueOverlay.ts
index 18e3e3dc..bd9a0561 100644
--- a/src/utils/perf/formatCoverPipelineQueueOverlay.ts
+++ b/src/lib/perf/formatCoverPipelineQueueOverlay.ts
@@ -1,7 +1,7 @@
-import type { CoverPipelineQueueStatsDto } from '../../api/coverCache';
-import type { CoverEnsureQueueStats } from '../../cover/ensureQueue';
-import type { CoverPeekQueueStats } from '../../cover/peekQueue';
-import { formatAnalysisTierQueue } from './formatAnalysisQueueStats';
+import type { CoverPipelineQueueStatsDto } from '@/api/coverCache';
+import type { CoverEnsureQueueStats } from '@/cover/ensureQueue';
+import type { CoverPeekQueueStats } from '@/cover/peekQueue';
+import { formatAnalysisTierQueue } from '@/lib/perf/formatAnalysisQueueStats';
export type CoverPipelineOverlayInput = {
rust: CoverPipelineQueueStatsDto;
diff --git a/src/utils/perf/formatLiveOverlayItems.ts b/src/lib/perf/formatLiveOverlayItems.ts
similarity index 98%
rename from src/utils/perf/formatLiveOverlayItems.ts
rename to src/lib/perf/formatLiveOverlayItems.ts
index 284b3202..49737aed 100644
--- a/src/utils/perf/formatLiveOverlayItems.ts
+++ b/src/lib/perf/formatLiveOverlayItems.ts
@@ -1,4 +1,4 @@
-import type { PerfLiveSnapshot } from './perfLiveStore';
+import type { PerfLiveSnapshot } from '@/lib/perf/perfLiveStore';
export type LiveOverlayItemKind = 'cpu' | 'memory' | 'rate' | 'analysis' | 'cover';
diff --git a/src/utils/perf/perfFlags.ts b/src/lib/perf/perfFlags.ts
similarity index 100%
rename from src/utils/perf/perfFlags.ts
rename to src/lib/perf/perfFlags.ts
diff --git a/src/utils/perf/perfLiveCpuSnapshot.ts b/src/lib/perf/perfLiveCpuSnapshot.ts
similarity index 100%
rename from src/utils/perf/perfLiveCpuSnapshot.ts
rename to src/lib/perf/perfLiveCpuSnapshot.ts
diff --git a/src/utils/perf/perfLiveHistory.ts b/src/lib/perf/perfLiveHistory.ts
similarity index 97%
rename from src/utils/perf/perfLiveHistory.ts
rename to src/lib/perf/perfLiveHistory.ts
index 2a055b38..55022db6 100644
--- a/src/utils/perf/perfLiveHistory.ts
+++ b/src/lib/perf/perfLiveHistory.ts
@@ -2,8 +2,8 @@ import { useSyncExternalStore } from 'react';
import {
isLiveHistoryPin,
liveOverlayItemValue,
-} from './formatLiveOverlayItems';
-import type { PerfLiveSnapshot } from './perfLiveStore';
+} from '@/lib/perf/formatLiveOverlayItems';
+import type { PerfLiveSnapshot } from '@/lib/perf/perfLiveStore';
const HISTORY_MS = 60_000;
const EMPTY_VALUES: readonly number[] = [];
diff --git a/src/utils/perf/perfLivePollSettings.ts b/src/lib/perf/perfLivePollSettings.ts
similarity index 100%
rename from src/utils/perf/perfLivePollSettings.ts
rename to src/lib/perf/perfLivePollSettings.ts
diff --git a/src/utils/perf/perfLiveStore.ts b/src/lib/perf/perfLiveStore.ts
similarity index 97%
rename from src/utils/perf/perfLiveStore.ts
rename to src/lib/perf/perfLiveStore.ts
index 3b26a179..6c330cb0 100644
--- a/src/utils/perf/perfLiveStore.ts
+++ b/src/lib/perf/perfLiveStore.ts
@@ -1,15 +1,15 @@
import { useSyncExternalStore } from 'react';
import { invoke } from '@tauri-apps/api/core';
-import { clearPerfLiveHistory, syncPerfLiveHistoryFromPoll } from './perfLiveHistory';
-import { getAnalysisTracksPerMinute } from './analysisPerfStore';
-import { getCoverCachedPerMinute, getCoverUiPerMinute, getCoverPerfState } from './coverPerfStore';
-import { perfLiveCpuSnapshotSupported } from './perfLiveCpuSnapshot';
-import { getPerfLiveOverlayPins } from './perfOverlayPins';
+import { clearPerfLiveHistory, syncPerfLiveHistoryFromPoll } from '@/lib/perf/perfLiveHistory';
+import { getAnalysisTracksPerMinute } from '@/lib/perf/analysisPerfStore';
+import { getCoverCachedPerMinute, getCoverUiPerMinute, getCoverPerfState } from '@/lib/perf/coverPerfStore';
+import { perfLiveCpuSnapshotSupported } from '@/lib/perf/perfLiveCpuSnapshot';
+import { getPerfLiveOverlayPins } from '@/lib/perf/perfOverlayPins';
import {
buildPerfCpuSnapshotRequest,
getPerfLivePollIntervalMs,
registerPerfLivePollScheduleBump,
-} from './perfLivePollSettings';
+} from '@/lib/perf/perfLivePollSettings';
export type PerfProcessMemory = {
label: string;
diff --git a/src/utils/perf/perfOverlayAppearance.ts b/src/lib/perf/perfOverlayAppearance.ts
similarity index 100%
rename from src/utils/perf/perfOverlayAppearance.ts
rename to src/lib/perf/perfOverlayAppearance.ts
diff --git a/src/utils/perf/perfOverlayMode.ts b/src/lib/perf/perfOverlayMode.ts
similarity index 98%
rename from src/utils/perf/perfOverlayMode.ts
rename to src/lib/perf/perfOverlayMode.ts
index 04e9b1d9..591cab8a 100644
--- a/src/utils/perf/perfOverlayMode.ts
+++ b/src/lib/perf/perfOverlayMode.ts
@@ -1,5 +1,5 @@
import { useSyncExternalStore } from 'react';
-import type { PerfProbeFlags } from './perfFlags';
+import type { PerfProbeFlags } from '@/lib/perf/perfFlags';
export type PerfOverlayMode = 'off' | 'fps' | 'pinned';
diff --git a/src/utils/perf/perfOverlayPins.ts b/src/lib/perf/perfOverlayPins.ts
similarity index 93%
rename from src/utils/perf/perfOverlayPins.ts
rename to src/lib/perf/perfOverlayPins.ts
index 6841d1bf..57ccc5ce 100644
--- a/src/utils/perf/perfOverlayPins.ts
+++ b/src/lib/perf/perfOverlayPins.ts
@@ -1,8 +1,8 @@
import { useSyncExternalStore } from 'react';
-import { clearPerfLiveHistory } from './perfLiveHistory';
-import { perfLiveCpuSnapshotSupported } from './perfLiveCpuSnapshot';
-import { getPerfOverlayMode } from './perfOverlayMode';
-import { getPerfProbeFlags, setPerfProbeFlag, subscribePerfProbeFlags } from './perfFlags';
+import { clearPerfLiveHistory } from '@/lib/perf/perfLiveHistory';
+import { perfLiveCpuSnapshotSupported } from '@/lib/perf/perfLiveCpuSnapshot';
+import { getPerfOverlayMode } from '@/lib/perf/perfOverlayMode';
+import { getPerfProbeFlags, setPerfProbeFlag, subscribePerfProbeFlags } from '@/lib/perf/perfFlags';
const STORAGE_KEY = 'psysonic_perf_overlay_pins_v1';
diff --git a/src/utils/perf/perfProbeToggleTree.ts b/src/lib/perf/perfProbeToggleTree.ts
similarity index 99%
rename from src/utils/perf/perfProbeToggleTree.ts
rename to src/lib/perf/perfProbeToggleTree.ts
index dad4fb33..c5c4282e 100644
--- a/src/utils/perf/perfProbeToggleTree.ts
+++ b/src/lib/perf/perfProbeToggleTree.ts
@@ -1,4 +1,4 @@
-import type { PerfProbeFlags } from './perfFlags';
+import type { PerfProbeFlags } from '@/lib/perf/perfFlags';
export type PerfToggleLeaf = {
id: string;
diff --git a/src/utils/perf/perfTelemetry.ts b/src/lib/perf/perfTelemetry.ts
similarity index 100%
rename from src/utils/perf/perfTelemetry.ts
rename to src/lib/perf/perfTelemetry.ts
diff --git a/src/utils/perf/sanitizeLogLine.test.ts b/src/lib/perf/sanitizeLogLine.test.ts
similarity index 97%
rename from src/utils/perf/sanitizeLogLine.test.ts
rename to src/lib/perf/sanitizeLogLine.test.ts
index 33482c27..c76e286d 100644
--- a/src/utils/perf/sanitizeLogLine.test.ts
+++ b/src/lib/perf/sanitizeLogLine.test.ts
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest';
-import { sanitizeLogLine } from './sanitizeLogLine';
+import { sanitizeLogLine } from '@/lib/perf/sanitizeLogLine';
describe('sanitizeLogLine', () => {
it('redacts Subsonic wire-auth query params', () => {
diff --git a/src/utils/perf/sanitizeLogLine.ts b/src/lib/perf/sanitizeLogLine.ts
similarity index 100%
rename from src/utils/perf/sanitizeLogLine.ts
rename to src/lib/perf/sanitizeLogLine.ts
diff --git a/src/ui/CachedImage.tsx b/src/ui/CachedImage.tsx
index a1844cac..23ccea06 100644
--- a/src/ui/CachedImage.tsx
+++ b/src/ui/CachedImage.tsx
@@ -1,6 +1,6 @@
import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
import { acquireUrl, getCachedBlob, releaseUrl, subscribeCoverUpgraded } from '@/utils/imageCache';
-import { resolveIntersectionScrollRoot } from '@/utils/ui/resolveIntersectionScrollRoot';
+import { resolveIntersectionScrollRoot } from '@/lib/dom/resolveIntersectionScrollRoot';
interface CachedImageProps extends React.ImgHTMLAttributes {
src: string;
diff --git a/src/ui/MarqueeText.tsx b/src/ui/MarqueeText.tsx
index 83113041..a158f024 100644
--- a/src/ui/MarqueeText.tsx
+++ b/src/ui/MarqueeText.tsx
@@ -1,5 +1,5 @@
import React, { useRef, useState, useEffect, useCallback } from 'react';
-import { usePerfProbeFlags } from '@/utils/perf/perfFlags';
+import { usePerfProbeFlags } from '@/lib/perf/perfFlags';
interface Props {
text: string;
diff --git a/src/ui/OverlayScrollArea.tsx b/src/ui/OverlayScrollArea.tsx
index 757b35e4..50b52046 100644
--- a/src/ui/OverlayScrollArea.tsx
+++ b/src/ui/OverlayScrollArea.tsx
@@ -1,7 +1,7 @@
import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
-import { computeOverlayScrollbarThumbMeta } from '@/utils/ui/overlayScrollbarMetrics';
-import { bindOverlayScrollbarThumbDrag } from '@/utils/ui/overlayScrollbarThumb';
-import { usePerfProbeFlags } from '@/utils/perf/perfFlags';
+import { computeOverlayScrollbarThumbMeta } from '@/lib/dom/overlayScrollbarMetrics';
+import { bindOverlayScrollbarThumbDrag } from '@/lib/dom/overlayScrollbarThumb';
+import { usePerfProbeFlags } from '@/lib/perf/perfFlags';
export type OverlayScrollRailInset = 'none' | 'mini' | 'panel';
diff --git a/src/utils/componentHelpers/contextMenuActions.ts b/src/utils/componentHelpers/contextMenuActions.ts
index 6785663b..d1ce9cf9 100644
--- a/src/utils/componentHelpers/contextMenuActions.ts
+++ b/src/utils/componentHelpers/contextMenuActions.ts
@@ -13,7 +13,7 @@ import type { EntityShareKind } from '../share/shareLink';
import { copyEntityShareLink } from '../share/copyEntityShareLink';
import { sanitizeFilename, shuffleArray } from './contextMenuHelpers';
import { songToTrack } from '@/lib/media/songToTrack';
-import { showToast } from '../ui/toast';
+import { showToast } from '@/lib/dom/toast';
export async function copyShareLink(
kind: EntityShareKind,
diff --git a/src/utils/share/applySharePaste.ts b/src/utils/share/applySharePaste.ts
index 3f2477d5..5442513b 100644
--- a/src/utils/share/applySharePaste.ts
+++ b/src/utils/share/applySharePaste.ts
@@ -8,7 +8,7 @@ import { useAuthStore } from '../../store/authStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import { navigateToAlbumDetail } from '../navigation/albumDetailNavigation';
import { findServerIdForShareUrl, type EntitySharePayloadV1 } from './shareLink';
-import { showToast } from '../ui/toast';
+import { showToast } from '@/lib/dom/toast';
const RESOLVE_QUEUE_CHUNK = 12;
diff --git a/src/utils/share/enqueueShareSearchPayload.test.ts b/src/utils/share/enqueueShareSearchPayload.test.ts
index d033862b..71142993 100644
--- a/src/utils/share/enqueueShareSearchPayload.test.ts
+++ b/src/utils/share/enqueueShareSearchPayload.test.ts
@@ -57,7 +57,7 @@ vi.mock('@/features/orbit', () => ({
orbitBulkGuard: mocks.orbitBulkGuard,
}));
-vi.mock('../ui/toast', () => ({
+vi.mock('@/lib/dom/toast', () => ({
showToast: mocks.showToast,
}));
diff --git a/src/utils/share/enqueueShareSearchPayload.ts b/src/utils/share/enqueueShareSearchPayload.ts
index b2a76a64..08835240 100644
--- a/src/utils/share/enqueueShareSearchPayload.ts
+++ b/src/utils/share/enqueueShareSearchPayload.ts
@@ -22,7 +22,7 @@ import type {
ComposerShareSearchPayload,
QueueableShareSearchPayload,
} from './shareSearch';
-import { showToast } from '../ui/toast';
+import { showToast } from '@/lib/dom/toast';
const RESOLVE_QUEUE_CHUNK = 12;