diff --git a/src/App.tsx b/src/App.tsx index 55819b8d..bff35da0 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,6 +1,6 @@ import { useEffect } from 'react'; import { useAuthStore } from './store/authStore'; -import { usePlayerStore } from './store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { useLyricsStore } from './store/lyricsStore'; import { useThemeStore } from './store/themeStore'; import { useInstalledThemesStore } from './store/installedThemesStore'; diff --git a/src/api/coverCache.test.ts b/src/api/coverCache.test.ts index e24aa4d8..408a771f 100644 --- a/src/api/coverCache.test.ts +++ b/src/api/coverCache.test.ts @@ -1,6 +1,6 @@ import { beforeEach, describe, expect, it } from 'vitest'; import { useAuthStore } from '../store/authStore'; -import { usePlayerStore } from '../store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { makeTrack } from '../test/helpers/factories'; import { resetAllStores } from '../test/helpers/storeReset'; import { invokeMock, onInvoke } from '../test/mocks/tauri'; diff --git a/src/api/coverCache.ts b/src/api/coverCache.ts index 7d2d21db..738044e5 100644 --- a/src/api/coverCache.ts +++ b/src/api/coverCache.ts @@ -4,7 +4,7 @@ import { useThemeStore } from '../store/themeStore'; import { coverIndexKeyFromRef, coverStorageKeyFromRef } from '../cover/storageKeys'; import { connectBaseUrlForServer } from '../utils/server/serverEndpoint'; import { serverIndexKeyForProfile } from '../utils/server/serverIndexKey'; -import { getPlaybackServerId } from '../utils/playback/playbackServer'; +import { getPlaybackServerId } from '@/features/playback/utils/playback/playbackServer'; import { restBaseFromUrl } from '@/lib/api/subsonicClient'; import type { CoverArtRef, CoverArtTier } from '../cover/types'; diff --git a/src/app/AppShell.tsx b/src/app/AppShell.tsx index bacc180e..1eadb11e 100644 --- a/src/app/AppShell.tsx +++ b/src/app/AppShell.tsx @@ -1,6 +1,6 @@ import React, { Suspense, useCallback, useEffect, useRef, useState } from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; -import { ensurePlaybackServerActive } from '../utils/playback/playbackServer'; +import { ensurePlaybackServerActive } from '@/features/playback/utils/playback/playbackServer'; import { navigatePathWithAlbumReturnTo, shouldSkipMainScrollResetOnRouteChange } from '../utils/navigation/albumDetailNavigation'; import { getCurrentWebview } from '@tauri-apps/api/webview'; import { PanelRight } from 'lucide-react'; @@ -58,13 +58,13 @@ import { IS_LINUX, IS_MACOS } from '@/lib/util/platform'; import { useConnectionStatus } from '../hooks/useConnectionStatus'; import { useIdlePlayQueuePull } from '../hooks/useIdlePlayQueuePull'; import { useAuthStore } from '../store/authStore'; -import { usePlayerStore } from '../store/playerStore'; -import '../store/previewPlayerVolumeSync'; -import '../store/queueResolverBridge'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; +import '@/features/playback/store/previewPlayerVolumeSync'; +import '@/features/playback/store/queueResolverBridge'; import { useThemeStore } from '../store/themeStore'; import { useFontStore } from '../store/fontStore'; import { useEqStore } from '../store/eqStore'; -import { usePlaybackRateStore } from '../store/playbackRateStore'; +import { usePlaybackRateStore } from '@/features/playback/store/playbackRateStore'; import { usePlaybackRateOrbitSync } from '@/features/orbit'; import { usePerfProbeFlags } from '../utils/perf/perfFlags'; import { diff --git a/src/app/MainApp.tsx b/src/app/MainApp.tsx index d42fb1b8..41ceddd2 100644 --- a/src/app/MainApp.tsx +++ b/src/app/MainApp.tsx @@ -1,5 +1,5 @@ -import { initAudioListeners } from '../store/initAudioListeners'; -import '../store/playbackEngineBridgeRegister'; // installs the playback-engine bridge at boot +import { initAudioListeners } from '@/features/playback/store/initAudioListeners'; +import '@/features/playback/store/playbackEngineBridgeRegister'; // installs the playback-engine bridge at boot import { lazy, Suspense, useEffect, useState } from 'react'; import { BrowserRouter, Route, Routes } from 'react-router-dom'; import { invoke } from '@tauri-apps/api/core'; diff --git a/src/app/bootstrap.test.ts b/src/app/bootstrap.test.ts index f22e82d9..9f71fc51 100644 --- a/src/app/bootstrap.test.ts +++ b/src/app/bootstrap.test.ts @@ -6,14 +6,14 @@ * payload) and the failure-tolerant path (no throw when invoke or storage * misbehaves). */ -import { installQueueUndoHotkey } from '../store/queueUndoHotkey'; +import { installQueueUndoHotkey } from '@/features/playback/store/queueUndoHotkey'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; vi.mock('@tauri-apps/api/core', () => ({ invoke: vi.fn(), })); -vi.mock('../store/queueUndoHotkey', () => ({ +vi.mock('@/features/playback/store/queueUndoHotkey', () => ({ installQueueUndoHotkey: vi.fn(), })); diff --git a/src/app/bootstrap.ts b/src/app/bootstrap.ts index 3db7cded..01f94b6a 100644 --- a/src/app/bootstrap.ts +++ b/src/app/bootstrap.ts @@ -1,4 +1,4 @@ -import { installQueueUndoHotkey } from '../store/queueUndoHotkey'; +import { installQueueUndoHotkey } from '@/features/playback/store/queueUndoHotkey'; import { configureStartupSplash } from './startupSplash'; import { setupMusicNetworkRuntime } from './musicNetworkBridge'; import { invoke } from '@tauri-apps/api/core'; diff --git a/src/components/BecauseYouLikeRail.tsx b/src/components/BecauseYouLikeRail.tsx index d7c45e84..c4ab94ca 100644 --- a/src/components/BecauseYouLikeRail.tsx +++ b/src/components/BecauseYouLikeRail.tsx @@ -2,8 +2,8 @@ import { getArtist, getArtistInfo } from '@/lib/api/subsonicArtists'; import { filterAlbumsToActiveLibrary } from '@/lib/api/subsonicLibrary'; import { resolveAlbum, resolveMediaServerId } from '@/features/offline'; import type { SubsonicAlbum } from '@/lib/api/subsonicTypes'; -import { songToTrack } from '../utils/playback/songToTrack'; -import { shuffleArray } from '../utils/playback/shuffleArray'; +import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; +import { shuffleArray } from '@/features/playback/utils/playback/shuffleArray'; import React, { memo, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; @@ -18,9 +18,9 @@ import { writeBecauseYouLikeCache, type BecauseYouLikeAnchor, } from '../store/becauseYouLikeCache'; -import { usePlayerStore } from '../store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { useAuthStore } from '../store/authStore'; -import { playAlbum, playAlbumShuffled } from '../utils/playback/playAlbum'; +import { playAlbum, playAlbumShuffled } from '@/features/playback/utils/playback/playAlbum'; import { useLongPressAction } from '@/lib/hooks/useLongPressAction'; import { LongPressWaveOverlay } from './LongPressWaveOverlay'; import { formatHumanHoursMinutes } from '@/lib/format/formatHumanDuration'; diff --git a/src/components/BottomNav.tsx b/src/components/BottomNav.tsx index 6cb0f627..0be480e2 100644 --- a/src/components/BottomNav.tsx +++ b/src/components/BottomNav.tsx @@ -2,7 +2,7 @@ import { useState } from 'react'; import { NavLink } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { Disc3, Search, Music4, AudioLines, MoreHorizontal } from 'lucide-react'; -import { usePlayerStore } from '../store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { MobileSearchOverlay } from '@/features/search'; import MobileMoreOverlay from './MobileMoreOverlay'; diff --git a/src/components/ContextMenu.test.tsx b/src/components/ContextMenu.test.tsx index 71bbaa4a..9577590c 100644 --- a/src/components/ContextMenu.test.tsx +++ b/src/components/ContextMenu.test.tsx @@ -53,7 +53,7 @@ vi.mock('@/features/offline/hooks/useOfflineBrowseContext', () => ({ import ContextMenu from './ContextMenu'; import { renderWithProviders } from '@/test/helpers/renderWithProviders'; -import { usePlayerStore } from '@/store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { useAuthStore } from '@/store/authStore'; import { resetAllStores } from '@/test/helpers/storeReset'; import { makeTrack, makeServer, seedQueue } from '@/test/helpers/factories'; diff --git a/src/components/ContextMenu.tsx b/src/components/ContextMenu.tsx index bbde22de..f8a6f8e2 100644 --- a/src/components/ContextMenu.tsx +++ b/src/components/ContextMenu.tsx @@ -1,7 +1,7 @@ import React, { useCallback, useEffect, useRef, useState } from 'react'; -import type { Track } from '../store/playerStoreTypes'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; import { useOrbitStore } from '@/features/orbit'; -import { usePlayerStore } from '../store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { useShallow } from 'zustand/react/shallow'; import { useAuthStore } from '../store/authStore'; import { useTranslation } from 'react-i18next'; diff --git a/src/components/Equalizer.tsx b/src/components/Equalizer.tsx index 60dc8dbd..87cddc51 100644 --- a/src/components/Equalizer.tsx +++ b/src/components/Equalizer.tsx @@ -4,7 +4,7 @@ import { Save, Trash2, RotateCcw } from 'lucide-react'; import CustomSelect from '@/ui/CustomSelect'; import { useEqStore, EQ_BANDS, BUILTIN_PRESETS } from '../store/eqStore'; import { useThemeStore } from '../store/themeStore'; -import { drawCurve } from '../utils/audio/eqCurve'; +import { drawCurve } from '@/features/playback/utils/audio/eqCurve'; import VerticalFader from './equalizer/VerticalFader'; import AutoEqSection from './equalizer/AutoEqSection'; diff --git a/src/components/Hero.tsx b/src/components/Hero.tsx index 3b018b46..878fe345 100644 --- a/src/components/Hero.tsx +++ b/src/components/Hero.tsx @@ -1,7 +1,7 @@ import { getRandomAlbums } from '@/lib/api/subsonicLibrary'; import { resolveAlbum, resolveMediaServerId } from '@/features/offline'; import type { SubsonicAlbum } from '@/lib/api/subsonicTypes'; -import { songToTrack } from '../utils/playback/songToTrack'; +import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; import React, { useEffect, useState, useRef, useCallback, useMemo } from 'react'; import { useNavigateToAlbum } from '@/features/album'; import { Play, ListPlus, ChevronLeft, ChevronRight } from 'lucide-react'; @@ -11,7 +11,7 @@ import { useArtistBanner, useArtistFanart } from '../cover/useArtistFanart'; import { useCoverArt } from '../cover/useCoverArt'; import { useHeroBackdrop } from '../cover/useHeroBackdrop'; import { useCachedUrl } from '@/ui/CachedImage'; -import { usePlayerStore } from '../store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { useTranslation } from 'react-i18next'; import { useIsMobile } from '@/lib/hooks/useIsMobile'; import { useWindowVisibility } from '@/lib/hooks/useWindowVisibility'; @@ -19,7 +19,7 @@ import { useAuthStore } from '../store/authStore'; import { useThemeStore } from '../store/themeStore'; import { filterAlbumsByMixRatings, getMixMinRatingsConfigFromAuth } from '../utils/mix/mixRatingFilter'; import { usePerfProbeFlags } from '../utils/perf/perfFlags'; -import { playAlbum, playAlbumShuffled } from '../utils/playback/playAlbum'; +import { playAlbum, playAlbumShuffled } from '@/features/playback/utils/playback/playAlbum'; import { useLongPressAction } from '@/lib/hooks/useLongPressAction'; import { LongPressWaveOverlay } from './LongPressWaveOverlay'; import { albumArtistDisplayName, deriveAlbumArtistRefs } from '@/features/album'; diff --git a/src/components/LyricsPane.tsx b/src/components/LyricsPane.tsx index db5ceb2f..5b049cef 100644 --- a/src/components/LyricsPane.tsx +++ b/src/components/LyricsPane.tsx @@ -1,12 +1,12 @@ -import { getPlaybackProgressSnapshot, subscribePlaybackProgress } from '../store/playbackProgress'; +import { getPlaybackProgressSnapshot, subscribePlaybackProgress } from '@/features/playback/store/playbackProgress'; import { useEffect, useRef, useCallback } from 'react'; import { useShallow } from 'zustand/react/shallow'; -import { usePlayerStore } from '../store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import type { LrcLine } from '../api/lrclib'; import { useLyrics, type WordLyricsLine } from '../hooks/useLyrics'; import { useAuthStore } from '../store/authStore'; import { useTranslation } from 'react-i18next'; -import type { Track } from '../store/playerStoreTypes'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; import { EaseScroller, targetForFraction } from '../utils/ui/easeScroll'; import OverlayScrollArea from '@/ui/OverlayScrollArea'; diff --git a/src/components/MobilePlayerView.tsx b/src/components/MobilePlayerView.tsx index d6ec4e70..8fe9db7b 100644 --- a/src/components/MobilePlayerView.tsx +++ b/src/components/MobilePlayerView.tsx @@ -1,8 +1,8 @@ -import { queueSongStar } from '../store/pendingStarSync'; +import { queueSongStar } from '@/features/playback/store/pendingStarSync'; import { usePlaybackCoverArt } from '../hooks/usePlaybackCoverArt'; import { usePlaybackTrackCoverRef } from '../cover/useLibraryCoverRef'; -import type { Track } from '../store/playerStoreTypes'; -import { getPlaybackProgressSnapshot, subscribePlaybackProgress } from '../store/playbackProgress'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; +import { getPlaybackProgressSnapshot, subscribePlaybackProgress } from '@/features/playback/store/playbackProgress'; import React, { useState, useCallback, useRef, useEffect, useSyncExternalStore, CSSProperties } from 'react'; import { useNavigate } from 'react-router-dom'; import { usePlaybackLibraryNavigate } from '../hooks/usePlaybackLibraryNavigate'; @@ -13,7 +13,7 @@ import { Shuffle, Repeat, Repeat1, Heart, Music, MicVocal, ListMusic, X, Moon, Sunrise, } from 'lucide-react'; -import { usePlayerStore } from '../store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { useCachedUrl } from '@/ui/CachedImage'; import { OpenArtistRefInline } from '@/features/artist'; import { formatTrackTime } from '@/lib/format/formatDuration'; diff --git a/src/components/PlaybackDelayModal.tsx b/src/components/PlaybackDelayModal.tsx index 57f89524..9fbf7246 100644 --- a/src/components/PlaybackDelayModal.tsx +++ b/src/components/PlaybackDelayModal.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useMemo, useRef, useState } from 'react'; import { createPortal } from 'react-dom'; import { X, Moon, Sunrise } from 'lucide-react'; import { useTranslation } from 'react-i18next'; -import { usePlayerStore } from '../store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { useAuthStore } from '../store/authStore'; import { useShallow } from 'zustand/react/shallow'; @@ -14,7 +14,7 @@ import { parsePlaybackDelayCustomMinutes, scheduleDelayMsFromSeconds, scheduleSecondsFromCustomMinutes, -} from '../utils/playback/playbackScheduleDelay'; +} from '@/features/playback/utils/playback/playbackScheduleDelay'; /** One tap = schedule; custom minutes still covers any duration. */ const PRESET_SECONDS = [30, 60, 120, 300, 600, 900, 1800, 3600] as const; diff --git a/src/components/PlaybackScheduleBadge.tsx b/src/components/PlaybackScheduleBadge.tsx index 7275867d..77c7a4bd 100644 --- a/src/components/PlaybackScheduleBadge.tsx +++ b/src/components/PlaybackScheduleBadge.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useLayoutEffect, useState } from 'react'; import { createPortal } from 'react-dom'; import { useTranslation } from 'react-i18next'; -import { usePlayerStore } from '../store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { useShallow } from 'zustand/react/shallow'; import { formatPlaybackScheduleRemaining } from '../utils/format/playbackScheduleFormat'; import { useWindowVisibility } from '@/lib/hooks/useWindowVisibility'; diff --git a/src/components/PlayerBar.test.tsx b/src/components/PlayerBar.test.tsx index 1d516636..c4e3d097 100644 --- a/src/components/PlayerBar.test.tsx +++ b/src/components/PlayerBar.test.tsx @@ -39,7 +39,7 @@ vi.mock('@/lib/api/subsonic', () => ({ import PlayerBar from './PlayerBar'; import { renderWithProviders } from '@/test/helpers/renderWithProviders'; -import { usePlayerStore } from '@/store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { useAuthStore } from '@/store/authStore'; import { resetAllStores } from '@/test/helpers/storeReset'; import { makeTrack, seedQueue } from '@/test/helpers/factories'; diff --git a/src/components/PlayerBar.tsx b/src/components/PlayerBar.tsx index e289bb4f..344e712c 100644 --- a/src/components/PlayerBar.tsx +++ b/src/components/PlayerBar.tsx @@ -1,4 +1,4 @@ -import { queueSongStar } from '../store/pendingStarSync'; +import { queueSongStar } from '@/features/playback/store/pendingStarSync'; import { coverArtIdFromRadio } from '../cover/ids'; import { resolvePlaybackTrackCoverArtId } from '../cover/resolveCoverArtId'; import React, { useCallback, useEffect, useRef, useState } from 'react'; @@ -8,7 +8,7 @@ import { PictureInPicture2, Ellipsis, } from 'lucide-react'; import { invoke } from '@tauri-apps/api/core'; -import { usePlayerStore } from '../store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { useShallow } from 'zustand/react/shallow'; import { useAuthStore } from '../store/authStore'; import { useThemeStore } from '../store/themeStore'; @@ -20,10 +20,10 @@ import { useRadioMprisSync } from '@/features/radio'; import { usePlaybackDelayPress } from '../hooks/usePlaybackDelayPress'; import PlaybackDelayModal from './PlaybackDelayModal'; import { usePlaybackScheduleRemaining } from '../utils/format/playbackScheduleFormat'; -import { usePreviewStore } from '../store/previewStore'; +import { usePreviewStore } from '@/features/playback/store/previewStore'; import { usePerfProbeFlags } from '../utils/perf/perfFlags'; import { coerceOpenArtistRefs } from '@/lib/api/openArtistRefs'; -import { resolveTrackArtistRefs } from '../utils/playback/trackArtistRefs'; +import { resolveTrackArtistRefs } from '@/features/playback/utils/playback/trackArtistRefs'; import { PlayerTrackInfo } from './playerBar/PlayerTrackInfo'; import { PlayerTransportControls } from './playerBar/PlayerTransportControls'; import { PlayerSeekbarSection } from './playerBar/PlayerSeekbarSection'; diff --git a/src/components/SongCard.tsx b/src/components/SongCard.tsx index 10a5bd19..05fb183d 100644 --- a/src/components/SongCard.tsx +++ b/src/components/SongCard.tsx @@ -1,20 +1,20 @@ import type { SubsonicSong } from '@/lib/api/subsonicTypes'; -import { songToTrack } from '../utils/playback/songToTrack'; +import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; import React, { memo, useMemo } from 'react'; import { Play, ListPlus, Star, Disc3 } from 'lucide-react'; import { useTranslation } from 'react-i18next'; -import { usePlayerStore } from '../store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { CoverArtImage } from '../cover/CoverArtImage'; import { useCoverArt } from '../cover/useCoverArt'; import { useTrackCoverRef } from '../cover/useLibraryCoverRef'; import { COVER_DENSE_RAIL_CELL_CSS_PX } from '../cover/layoutSizes'; -import { enqueueAndPlay } from '../utils/playback/playSong'; +import { enqueueAndPlay } from '@/features/playback/utils/playback/playSong'; import { useDragDrop } from '@/lib/dnd/DragDropContext'; import { useOrbitSongRowBehavior } from '@/features/orbit'; import { useNavigateToAlbum } from '@/features/album'; import { useNavigateToArtist } from '@/features/artist'; import { OpenArtistRefInline } from '@/features/artist'; -import { resolveTrackArtistRefs } from '../utils/playback/trackArtistRefs'; +import { resolveTrackArtistRefs } from '@/features/playback/utils/playback/trackArtistRefs'; interface SongCardProps { song: SubsonicSong; diff --git a/src/components/SongInfoModal.tsx b/src/components/SongInfoModal.tsx index f51dfb23..2ab44a1e 100644 --- a/src/components/SongInfoModal.tsx +++ b/src/components/SongInfoModal.tsx @@ -4,7 +4,7 @@ import type { SubsonicSong } from '@/lib/api/subsonicTypes'; import React, { useEffect, useState } from 'react'; import { createPortal } from 'react-dom'; import { X } from 'lucide-react'; -import { usePlayerStore } from '../store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { useShallow } from 'zustand/react/shallow'; import { ndGetSongPath } from '@/lib/api/navidromeAdmin'; import { useAuthStore } from '../store/authStore'; diff --git a/src/components/SongRow.tsx b/src/components/SongRow.tsx index dc305ef6..c7fe3545 100644 --- a/src/components/SongRow.tsx +++ b/src/components/SongRow.tsx @@ -1,16 +1,16 @@ import type { SubsonicSong } from '@/lib/api/subsonicTypes'; -import { songToTrack } from '../utils/playback/songToTrack'; +import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; import React, { memo } from 'react'; import { useNavigateToAlbum } from '@/features/album'; import { useNavigateToArtist } from '@/features/artist'; import { Play, ListPlus } from 'lucide-react'; import { useTranslation } from 'react-i18next'; -import { usePlayerStore } from '../store/playerStore'; -import { enqueueAndPlay } from '../utils/playback/playSong'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; +import { enqueueAndPlay } from '@/features/playback/utils/playback/playSong'; import { useDragDrop } from '@/lib/dnd/DragDropContext'; import { useOrbitSongRowBehavior } from '@/features/orbit'; import { formatTrackTime } from '@/lib/format/formatDuration'; -import { resolveTrackArtistRefs } from '../utils/playback/trackArtistRefs'; +import { resolveTrackArtistRefs } from '@/features/playback/utils/playback/trackArtistRefs'; import { tooltipAttrs } from '@/ui/tooltipAttrs'; interface Props { diff --git a/src/components/TitleBar.tsx b/src/components/TitleBar.tsx index 1a3d33de..57f244d1 100644 --- a/src/components/TitleBar.tsx +++ b/src/components/TitleBar.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { getCurrentWindow } from '@tauri-apps/api/window'; import { Minus, Square, X } from 'lucide-react'; -import { usePlayerStore } from '../store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { useAuthStore } from '../store/authStore'; import { IS_MACOS } from '@/lib/util/platform'; diff --git a/src/components/contextMenu/AlbumContextItems.tsx b/src/components/contextMenu/AlbumContextItems.tsx index cb6fd32d..a25aa362 100644 --- a/src/components/contextMenu/AlbumContextItems.tsx +++ b/src/components/contextMenu/AlbumContextItems.tsx @@ -4,7 +4,7 @@ import { useNavigate } from 'react-router-dom'; import { resolveAlbum, resolveMediaServerId } from '@/features/offline'; import { star, unstar } from '@/lib/api/subsonicStarRating'; import type { SubsonicAlbum } from '@/lib/api/subsonicTypes'; -import { songToTrack } from '../../utils/playback/songToTrack'; +import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; import StarRating from '../StarRating'; import { AlbumToPlaylistSubmenu } from './AlbumArtistToPlaylistSubmenu'; import { MultiAlbumToPlaylistSubmenu } from './MultiAlbumToPlaylistSubmenu'; diff --git a/src/components/contextMenu/QueueItemContextItems.tsx b/src/components/contextMenu/QueueItemContextItems.tsx index c531eff0..92f50120 100644 --- a/src/components/contextMenu/QueueItemContextItems.tsx +++ b/src/components/contextMenu/QueueItemContextItems.tsx @@ -1,8 +1,8 @@ import { useTranslation } from 'react-i18next'; import { Play, Radio, Heart, ChevronRight, User, Disc3, ListMusic, Info, Sparkles, Star, Trash2, Share2 } from 'lucide-react'; -import { queueSongStar } from '../../store/pendingStarSync'; +import { queueSongStar } from '@/features/playback/store/pendingStarSync'; import { getMusicNetworkRuntime, useEnrichmentPrimary } from '../../music-network'; -import type { Track } from '../../store/playerStoreTypes'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; import { useAuthStore } from '../../store/authStore'; import { renderPresetIcon } from '../settings/musicNetwork/presetIcon'; import StarRating from '../StarRating'; diff --git a/src/components/contextMenu/SongContextItems.tsx b/src/components/contextMenu/SongContextItems.tsx index 9632bb01..471194d8 100644 --- a/src/components/contextMenu/SongContextItems.tsx +++ b/src/components/contextMenu/SongContextItems.tsx @@ -3,12 +3,12 @@ import { Play, ListPlus, Radio, Heart, ChevronRight, ChevronsRight, User, Disc3, import { useNavigateToAlbum } from '@/features/album'; import { useNavigateToArtist } from '@/features/artist'; import { resolveAlbum, resolveMediaServerId, resolvePlaylist } from '@/features/offline'; -import { queueSongStar } from '../../store/pendingStarSync'; +import { queueSongStar } from '@/features/playback/store/pendingStarSync'; import { getMusicNetworkRuntime, useEnrichmentPrimary } from '../../music-network'; -import type { Track } from '../../store/playerStoreTypes'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; import { useAuthStore } from '../../store/authStore'; import { usePlaylistStore } from '@/features/playlist'; -import { songToTrack } from '../../utils/playback/songToTrack'; +import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; import { showToast } from '../../utils/ui/toast'; import { suggestOrbitTrack, hostEnqueueToOrbit, evaluateOrbitSuggestGate, OrbitSuggestBlockedError } from '@/features/orbit'; import { renderPresetIcon } from '../settings/musicNetwork/presetIcon'; diff --git a/src/components/contextMenu/contextMenuItemTypes.ts b/src/components/contextMenu/contextMenuItemTypes.ts index 7ead8a13..8e90e054 100644 --- a/src/components/contextMenu/contextMenuItemTypes.ts +++ b/src/components/contextMenu/contextMenuItemTypes.ts @@ -1,6 +1,6 @@ import type React from 'react'; import type { SubsonicAlbum, SubsonicArtist } from '@/lib/api/subsonicTypes'; -import type { QueueItemRef, Track } from '../../store/playerStoreTypes'; +import type { QueueItemRef, Track } from '@/features/playback/store/playerStoreTypes'; import type { EntityShareKind } from '../../utils/share/shareLink'; import type { OfflineActionPolicy } from '@/features/offline'; diff --git a/src/components/folderBrowser/FolderBrowserColumn.tsx b/src/components/folderBrowser/FolderBrowserColumn.tsx index 9fbc3f8d..2d9d6790 100644 --- a/src/components/folderBrowser/FolderBrowserColumn.tsx +++ b/src/components/folderBrowser/FolderBrowserColumn.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; import { ChevronRight, Folder, FolderOpen, Music } from 'lucide-react'; import type { SubsonicDirectoryEntry } from '@/lib/api/subsonicTypes'; -import type { Track } from '../../store/playerStoreTypes'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; import { folderBrowserHasKeyModifiers, isFolderBrowserArrowKey, type Column, diff --git a/src/components/playerBar/PlaybackClock.tsx b/src/components/playerBar/PlaybackClock.tsx index e3f698d1..2376f963 100644 --- a/src/components/playerBar/PlaybackClock.tsx +++ b/src/components/playerBar/PlaybackClock.tsx @@ -1,5 +1,5 @@ import { memo, useEffect, useRef } from 'react'; -import { getPlaybackProgressSnapshot, subscribePlaybackProgress } from '../../store/playbackProgress'; +import { getPlaybackProgressSnapshot, subscribePlaybackProgress } from '@/features/playback/store/playbackProgress'; import { formatPlaybarClock, formatPlaybarToggleClock, diff --git a/src/components/playerBar/PlayerPlaybackRate.tsx b/src/components/playerBar/PlayerPlaybackRate.tsx index 6a0646e6..b15d75c9 100644 --- a/src/components/playerBar/PlayerPlaybackRate.tsx +++ b/src/components/playerBar/PlayerPlaybackRate.tsx @@ -2,7 +2,7 @@ import React, { useCallback } from 'react'; import { createPortal } from 'react-dom'; import type { TFunction } from 'i18next'; import { PlaybackRateControls } from '@/features/settings'; -import { usePlaybackRateStore } from '../../store/playbackRateStore'; +import { usePlaybackRateStore } from '@/features/playback/store/playbackRateStore'; import { useOrbitStore } from '@/features/orbit'; import { clampPlaybackPitch, @@ -13,7 +13,7 @@ import { playbackPitchStep, playbackSpeedStep, varispeedSpeedFromSemitones, -} from '../../utils/audio/playbackRateHelpers'; +} from '@/features/playback/utils/audio/playbackRateHelpers'; import { isOrbitPlaybackSyncActive } from '@/features/orbit'; import { usePlayerBarAnchoredPopover } from '../../hooks/usePlayerBarAnchoredPopover'; diff --git a/src/components/playerBar/PlayerTrackInfo.tsx b/src/components/playerBar/PlayerTrackInfo.tsx index c1a82047..31650067 100644 --- a/src/components/playerBar/PlayerTrackInfo.tsx +++ b/src/components/playerBar/PlayerTrackInfo.tsx @@ -1,10 +1,10 @@ import { Cast, Heart, Maximize2, Music } from 'lucide-react'; import type { TFunction } from 'i18next'; -import { queueSongRating } from '../../store/pendingStarSync'; +import { queueSongRating } from '@/features/playback/store/pendingStarSync'; import type { InternetRadioStation, SubsonicOpenArtistRef } from '@/lib/api/subsonicTypes'; -import type { PlayerState, Track } from '../../store/playerStoreTypes'; +import type { PlayerState, Track } from '@/features/playback/store/playerStoreTypes'; import type { RadioMetadata } from '@/features/radio'; -import type { PreviewingTrack } from '../../store/previewStore'; +import type { PreviewingTrack } from '@/features/playback/store/previewStore'; import { CoverArtImage } from '../../cover/CoverArtImage'; import { albumCoverRef } from '../../cover/ref'; import { useAlbumCoverRef } from '../../cover/useLibraryCoverRef'; @@ -13,7 +13,7 @@ import MarqueeText from '../MarqueeText'; import { OpenArtistRefInline } from '@/features/artist'; import StarRating from '../StarRating'; import { PlaybackBufferingOverlay } from '../playback/PlaybackBufferingOverlay'; -import { usePlayerStore } from '../../store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { useEnrichmentPrimaryLabel } from '../../hooks/useEnrichmentPrimaryLabel'; import { useEnrichmentPrimaryIcon } from '../../hooks/useEnrichmentPrimaryIcon'; import { renderPresetIcon } from '../settings/musicNetwork/presetIcon'; diff --git a/src/components/playerBar/PlayerTransportControls.tsx b/src/components/playerBar/PlayerTransportControls.tsx index cd158020..524a347c 100644 --- a/src/components/playerBar/PlayerTransportControls.tsx +++ b/src/components/playerBar/PlayerTransportControls.tsx @@ -2,9 +2,9 @@ import React from 'react'; import { Blend, Moon, Pause, Play, Repeat, Repeat1, SkipBack, SkipForward, Square, Sunrise } from 'lucide-react'; import { invoke } from '@tauri-apps/api/core'; import type { TFunction } from 'i18next'; -import type { PlayerState } from '../../store/playerStoreTypes'; -import { useAutodjTransitionUi } from '../../store/autodjTransitionUi'; -import { usePreviewStore } from '../../store/previewStore'; +import type { PlayerState } from '@/features/playback/store/playerStoreTypes'; +import { useAutodjTransitionUi } from '@/features/playback/store/autodjTransitionUi'; +import { usePreviewStore } from '@/features/playback/store/previewStore'; import PlaybackScheduleBadge from '../PlaybackScheduleBadge'; import { usePlaybackDelayPress } from '../../hooks/usePlaybackDelayPress'; import { usePlaybackScheduleRemaining } from '../../utils/format/playbackScheduleFormat'; diff --git a/src/components/randomMix/RandomMixTrackRow.tsx b/src/components/randomMix/RandomMixTrackRow.tsx index 4a9a509e..c88bf99e 100644 --- a/src/components/randomMix/RandomMixTrackRow.tsx +++ b/src/components/randomMix/RandomMixTrackRow.tsx @@ -2,8 +2,8 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; import { AudioLines, ChevronRight, Heart, Play, Square } from 'lucide-react'; import type { SubsonicSong } from '@/lib/api/subsonicTypes'; -import type { Track } from '../../store/playerStoreTypes'; -import { previewInputFromSong, usePreviewStore } from '../../store/previewStore'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; +import { previewInputFromSong, usePreviewStore } from '@/features/playback/store/previewStore'; import { useDragDrop } from '@/lib/dnd/DragDropContext'; import { formatRandomMixDuration } from '../../utils/componentHelpers/randomMixHelpers'; diff --git a/src/components/tracks/TracksPageChrome.tsx b/src/components/tracks/TracksPageChrome.tsx index 95e731d4..56d63c59 100644 --- a/src/components/tracks/TracksPageChrome.tsx +++ b/src/components/tracks/TracksPageChrome.tsx @@ -1,20 +1,20 @@ import { AlbumCoverArtImage } from '../../cover/AlbumCoverArtImage'; import { getRandomSongs } from '@/lib/api/subsonicLibrary'; import type { SubsonicSong } from '@/lib/api/subsonicTypes'; -import { songToTrack } from '../../utils/playback/songToTrack'; +import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { Play, ListPlus, RefreshCw, Sparkles } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { useAuthStore } from '../../store/authStore'; -import { usePlayerStore } from '../../store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import SongRail from '../SongRail'; -import { playSongNow } from '../../utils/playback/playSong'; +import { playSongNow } from '@/features/playback/utils/playback/playSong'; import { ndListSongs, ndInvalidateSongsCache } from '@/lib/api/navidromeBrowse'; import { usePerfProbeFlags } from '../../utils/perf/perfFlags'; import { useNavigateToAlbum } from '@/features/album'; import { useNavigateToArtist } from '@/features/artist'; import { OpenArtistRefInline } from '@/features/artist'; -import { resolveTrackArtistRefs } from '../../utils/playback/trackArtistRefs'; +import { resolveTrackArtistRefs } from '@/features/playback/utils/playback/trackArtistRefs'; const RANDOM_RAIL_SIZE = 18; const RATED_RAIL_FETCH = 60; diff --git a/src/config/shortcutActionRegistry.ts b/src/config/shortcutActionRegistry.ts index 43d010ae..46211e7c 100644 --- a/src/config/shortcutActionRegistry.ts +++ b/src/config/shortcutActionRegistry.ts @@ -1,11 +1,11 @@ -import { queueSongStar } from '../store/pendingStarSync'; +import { queueSongStar } from '@/features/playback/store/pendingStarSync'; import { getSong } from '@/lib/api/subsonicLibrary'; -import { songToTrack } from '../utils/playback/songToTrack'; +import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; import { invoke } from '@tauri-apps/api/core'; import { getCurrentWindow } from '@tauri-apps/api/window'; import i18n from '@/lib/i18n'; -import { usePlayerStore } from '../store/playerStore'; -import { usePreviewStore } from '../store/previewStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; +import { usePreviewStore } from '@/features/playback/store/previewStore'; import { useLyricsStore } from '../store/lyricsStore'; import { showToast } from '../utils/ui/toast'; import type { ActionContext, ShortcutSlot, ShortcutActionMeta } from '@/config/shortcutTypes'; diff --git a/src/config/shortcutDispatch.ts b/src/config/shortcutDispatch.ts index 1dbf9bd3..24471f54 100644 --- a/src/config/shortcutDispatch.ts +++ b/src/config/shortcutDispatch.ts @@ -1,8 +1,8 @@ -import { queueSongRating } from '../store/pendingStarSync'; +import { queueSongRating } from '@/features/playback/store/pendingStarSync'; import i18n from '@/lib/i18n'; -import { usePlayerStore } from '../store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { showToast } from '../utils/ui/toast'; -import { playByOpaqueId } from '../utils/playback/playByOpaqueId'; +import { playByOpaqueId } from '@/features/playback/utils/playback/playByOpaqueId'; import type { ActionContext, CliContext } from '@/config/shortcutTypes'; import { SHORTCUT_ACTION_REGISTRY, diff --git a/src/cover/fetchUrl.ts b/src/cover/fetchUrl.ts index 35c0ed36..0eadbb31 100644 --- a/src/cover/fetchUrl.ts +++ b/src/cover/fetchUrl.ts @@ -2,7 +2,7 @@ import { buildCoverArtUrl, buildCoverArtUrlForServer, } from '@/lib/api/subsonicStreamUrl'; -import { getPlaybackServerId } from '../utils/playback/playbackServer'; +import { getPlaybackServerId } from '@/features/playback/utils/playback/playbackServer'; import { useAuthStore } from '../store/authStore'; import { connectBaseUrlForServer } from '../utils/server/serverEndpoint'; import type { CoverArtRef, CoverArtTier } from './types'; diff --git a/src/cover/ref.ts b/src/cover/ref.ts index d9c6ea1e..60d60afc 100644 --- a/src/cover/ref.ts +++ b/src/cover/ref.ts @@ -1,4 +1,4 @@ -import { getPlaybackServerId } from '../utils/playback/playbackServer'; +import { getPlaybackServerId } from '@/features/playback/utils/playback/playbackServer'; import { useAuthStore } from '../store/authStore'; import { coverServerScopeForServerId } from './serverScope'; import type { SubsonicSong } from '@/lib/api/subsonicTypes'; diff --git a/src/cover/storageKeys.test.ts b/src/cover/storageKeys.test.ts index 5c075818..11418b69 100644 --- a/src/cover/storageKeys.test.ts +++ b/src/cover/storageKeys.test.ts @@ -26,7 +26,7 @@ vi.mock('../store/authStore', () => ({ useAuthStore: { getState: () => mockState }, })); -vi.mock('../utils/playback/playbackServer', () => ({ +vi.mock('@/features/playback/utils/playback/playbackServer', () => ({ getPlaybackServerId: () => null, })); diff --git a/src/cover/storageKeys.ts b/src/cover/storageKeys.ts index b3e1ee3a..d7c12745 100644 --- a/src/cover/storageKeys.ts +++ b/src/cover/storageKeys.ts @@ -1,4 +1,4 @@ -import { getPlaybackServerId } from '../utils/playback/playbackServer'; +import { getPlaybackServerId } from '@/features/playback/utils/playback/playbackServer'; import { useAuthStore } from '../store/authStore'; import { findServerByIdOrIndexKey } from '../utils/server/serverLookup'; import { diff --git a/src/cover/useCoverRevalidateScheduler.ts b/src/cover/useCoverRevalidateScheduler.ts index 9aeb80c9..e7920f3b 100644 --- a/src/cover/useCoverRevalidateScheduler.ts +++ b/src/cover/useCoverRevalidateScheduler.ts @@ -1,7 +1,7 @@ import { useEffect } from 'react'; import { invoke } from '@tauri-apps/api/core'; import { useAuthStore } from '../store/authStore'; -import { usePlayerStore } from '../store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; const TICK_MS = 60_000; diff --git a/src/cover/useLibraryCoverRef.ts b/src/cover/useLibraryCoverRef.ts index a5877dc3..40c8fab4 100644 --- a/src/cover/useLibraryCoverRef.ts +++ b/src/cover/useLibraryCoverRef.ts @@ -1,7 +1,7 @@ import { useEffect, useMemo, useState, type Dispatch, type SetStateAction } from 'react'; import type { SubsonicSong } from '@/lib/api/subsonicTypes'; import { useAuthStore } from '../store/authStore'; -import { usePlayerStore } from '../store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { albumCoverRef, albumCoverRefForPlayback, @@ -12,7 +12,7 @@ import { } from './ref'; import { coverServerScopeForServerId } from './serverScope'; import { resolveServerIdForIndexKey } from '../utils/server/serverLookup'; -import { sameQueueTrackId } from '../utils/playback/queueIdentity'; +import { sameQueueTrackId } from '@/features/playback/utils/playback/queueIdentity'; import { resolveAlbumCoverRefFromLibrary, resolveArtistCoverRefFromLibrary, diff --git a/src/cover/usePlaybackCoverArt.test.ts b/src/cover/usePlaybackCoverArt.test.ts index 6453de53..708eba1b 100644 --- a/src/cover/usePlaybackCoverArt.test.ts +++ b/src/cover/usePlaybackCoverArt.test.ts @@ -4,7 +4,7 @@ import type { CoverArtHandle } from './types'; import { albumCoverRef } from './ref'; import { usePlaybackCoverArt } from './usePlaybackCoverArt'; import { useAuthStore } from '../store/authStore'; -import { usePlayerStore } from '../store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { makeTrack } from '../test/helpers/factories'; import { resetAllStores } from '../test/helpers/storeReset'; import { toQueueItemRefs } from '../utils/library/queueItemRef'; diff --git a/src/cover/usePlaybackCoverArt.ts b/src/cover/usePlaybackCoverArt.ts index 2abf1855..8529f45b 100644 --- a/src/cover/usePlaybackCoverArt.ts +++ b/src/cover/usePlaybackCoverArt.ts @@ -3,7 +3,7 @@ import { resolvePlaybackCoverScope } from './ref'; import type { CoverArtHandle, CoverArtRef } from './types'; import { useCoverArt } from './useCoverArt'; import { useAuthStore } from '../store/authStore'; -import { usePlayerStore } from '../store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; /** Cover art for playback queue — uses queue server when it differs from browsed server. */ export function usePlaybackCoverArt( diff --git a/src/features/album/components/AlbumCard.tsx b/src/features/album/components/AlbumCard.tsx index 6be3c48d..2eb4ab39 100644 --- a/src/features/album/components/AlbumCard.tsx +++ b/src/features/album/components/AlbumCard.tsx @@ -4,7 +4,7 @@ import { useNavigate } from 'react-router-dom'; import { useNavigateToAlbum } from '@/features/album/hooks/useNavigateToAlbum'; import { Play, ListPlus, HardDriveDownload, Check } from 'lucide-react'; import { useTranslation } from 'react-i18next'; -import { usePlayerStore } from '@/store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { useAuthStore } from '@/store/authStore'; import { useLocalPlaybackStore } from '@/store/localPlaybackStore'; import { isOfflinePinComplete } from '@/features/offline'; @@ -16,7 +16,7 @@ import { COVER_DENSE_GRID_MIN_CELL_CSS_PX } from '@/cover/layoutSizes'; import { resolveCoverDisplayTier } from '@/cover/tiers'; import { acquireUrl } from '@/utils/imageCache/urlPool'; import { OpenArtistRefInline } from '@/features/artist'; -import { fetchAlbumTracks, playAlbum, playAlbumShuffled } from '@/utils/playback/playAlbum'; +import { fetchAlbumTracks, playAlbum, playAlbumShuffled } from '@/features/playback/utils/playback/playAlbum'; import { useLongPressAction } from '@/lib/hooks/useLongPressAction'; import { LongPressWaveOverlay } from '@/components/LongPressWaveOverlay'; import { useDragDrop } from '@/lib/dnd/DragDropContext'; diff --git a/src/features/album/components/AlbumTrackList.tsx b/src/features/album/components/AlbumTrackList.tsx index 1e806b26..d388e574 100644 --- a/src/features/album/components/AlbumTrackList.tsx +++ b/src/features/album/components/AlbumTrackList.tsx @@ -1,8 +1,8 @@ import type { SubsonicSong } from '@/lib/api/subsonicTypes'; -import type { Track } from '@/store/playerStoreTypes'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; import React, { useMemo, useState, useEffect } from 'react'; import { useTracklistColumns } from '@/utils/useTracklistColumns'; -import { usePlayerStore } from '@/store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { useTranslation } from 'react-i18next'; import { useIsMobile } from '@/lib/hooks/useIsMobile'; import { useSelectionStore } from '@/store/selectionStore'; diff --git a/src/features/album/components/AlbumTrackListMobile.tsx b/src/features/album/components/AlbumTrackListMobile.tsx index be529dde..1c9dc55c 100644 --- a/src/features/album/components/AlbumTrackListMobile.tsx +++ b/src/features/album/components/AlbumTrackListMobile.tsx @@ -1,8 +1,8 @@ import React from 'react'; import { AudioLines } from 'lucide-react'; import type { SubsonicSong } from '@/lib/api/subsonicTypes'; -import type { Track } from '@/store/playerStoreTypes'; -import { songToTrack } from '@/utils/playback/songToTrack'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; +import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; import { formatLongDuration } from '@/lib/format/formatDuration'; interface Props { diff --git a/src/features/album/components/TrackRow.tsx b/src/features/album/components/TrackRow.tsx index bd953939..fa6c1ea8 100644 --- a/src/features/album/components/TrackRow.tsx +++ b/src/features/album/components/TrackRow.tsx @@ -4,18 +4,18 @@ import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom'; import type { ColDef } from '@/utils/useTracklistColumns'; import type { SubsonicSong } from '@/lib/api/subsonicTypes'; -import type { Track } from '@/store/playerStoreTypes'; -import { songToTrack } from '@/utils/playback/songToTrack'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; +import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; import { useSelectionStore } from '@/store/selectionStore'; import { useThemeStore } from '@/store/themeStore'; -import { previewInputFromSong, usePreviewStore } from '@/store/previewStore'; +import { previewInputFromSong, usePreviewStore } from '@/features/playback/store/previewStore'; import StarRating from '@/components/StarRating'; import { codecLabel, type ColKey } from '@/features/album/utils/albumTrackListHelpers'; import { formatLongDuration } from '@/lib/format/formatDuration'; import { formatLastSeen } from '@/utils/componentHelpers/userMgmtHelpers'; import i18n from '@/lib/i18n'; import { offlineActionPolicy, type OfflineActionPolicy } from '@/features/offline'; -import { resolveTrackArtistRefs } from '@/utils/playback/trackArtistRefs'; +import { resolveTrackArtistRefs } from '@/features/playback/utils/playback/trackArtistRefs'; type ContextMenuFn = ( x: number, diff --git a/src/features/album/hooks/useAlbumTrackListSelection.ts b/src/features/album/hooks/useAlbumTrackListSelection.ts index f51cf785..fbd2fc75 100644 --- a/src/features/album/hooks/useAlbumTrackListSelection.ts +++ b/src/features/album/hooks/useAlbumTrackListSelection.ts @@ -2,7 +2,7 @@ import React, { useCallback, useEffect, useRef } from 'react'; import type { SubsonicSong } from '@/lib/api/subsonicTypes'; import { useSelectionStore } from '@/store/selectionStore'; import { useDragDrop } from '@/lib/dnd/DragDropContext'; -import { songToTrack } from '@/utils/playback/songToTrack'; +import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; interface UseAlbumTrackListSelectionArgs { songs: SubsonicSong[]; diff --git a/src/features/album/pages/AlbumDetail.tsx b/src/features/album/pages/AlbumDetail.tsx index 884f6e1b..f5bffb8e 100644 --- a/src/features/album/pages/AlbumDetail.tsx +++ b/src/features/album/pages/AlbumDetail.tsx @@ -1,15 +1,15 @@ import { buildDownloadUrl } from '@/lib/api/subsonicStreamUrl'; import { setRating, star, unstar } from '@/lib/api/subsonicStarRating'; -import { queueSongStar, queueSongRating } from '@/store/pendingStarSync'; +import { queueSongStar, queueSongRating } from '@/features/playback/store/pendingStarSync'; import { getAlbumForServer } from '@/lib/api/subsonicLibrary'; import { getArtistInfo } from '@/lib/api/subsonicArtists'; import type { SubsonicSong } from '@/lib/api/subsonicTypes'; -import { songToTrack } from '@/utils/playback/songToTrack'; -import { shuffleArray } from '@/utils/playback/shuffleArray'; +import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; +import { shuffleArray } from '@/features/playback/utils/playback/shuffleArray'; import React, { useEffect, useState, useCallback, useMemo } from 'react'; import { useParams, useSearchParams } from 'react-router-dom'; import { invoke } from '@tauri-apps/api/core'; -import { usePlayerStore } from '@/store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { useAuthStore } from '@/store/authStore'; import { useOrbitSongRowBehavior } from '@/features/orbit'; import { useAlbumDetailData } from '@/features/album/hooks/useAlbumDetailData'; diff --git a/src/features/album/pages/Albums.tsx b/src/features/album/pages/Albums.tsx index cfd18b36..eb59a887 100644 --- a/src/features/album/pages/Albums.tsx +++ b/src/features/album/pages/Albums.tsx @@ -1,6 +1,6 @@ import { buildDownloadUrl } from '@/lib/api/subsonicStreamUrl'; import { resolveAlbum } from '@/features/offline'; -import { songToTrack } from '@/utils/playback/songToTrack'; +import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; import { useState, useEffect, useLayoutEffect, useRef, useMemo } from 'react'; import AlbumCard from '@/features/album/components/AlbumCard'; import { albumGridWarmCovers, coverDisplayCssPxForAlbumGrid } from '@/cover/layoutSizes'; @@ -17,7 +17,7 @@ import { useTranslation } from 'react-i18next'; import { useLocation, useNavigate } from 'react-router-dom'; import { useOfflineStore } from '@/features/offline'; import { useDownloadModalStore } from '@/features/offline'; -import { usePlayerStore } from '@/store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { invoke } from '@tauri-apps/api/core'; import { join } from '@tauri-apps/api/path'; import { showToast } from '@/utils/ui/toast'; diff --git a/src/features/album/pages/LosslessAlbums.tsx b/src/features/album/pages/LosslessAlbums.tsx index 2214ae66..2f4d9e63 100644 --- a/src/features/album/pages/LosslessAlbums.tsx +++ b/src/features/album/pages/LosslessAlbums.tsx @@ -1,7 +1,7 @@ import { buildDownloadUrl } from '@/lib/api/subsonicStreamUrl'; import { resolveAlbum } from '@/features/offline'; import type { SubsonicAlbum } from '@/lib/api/subsonicTypes'; -import { songToTrack } from '@/utils/playback/songToTrack'; +import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import AlbumCard from '@/features/album/components/AlbumCard'; import { LOSSLESS_MODE_QUERY } from '@/utils/library/losslessMode'; @@ -10,7 +10,7 @@ import { useTranslation } from 'react-i18next'; import { useAuthStore } from '@/store/authStore'; import { useOfflineStore } from '@/features/offline'; import { useDownloadModalStore } from '@/features/offline'; -import { usePlayerStore } from '@/store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { useZipDownloadStore } from '@/features/offline'; import { useRangeSelection } from '@/lib/hooks/useRangeSelection'; import { useMainstageInpageHeaderTight } from '@/hooks/useMainstageInpageHeaderTight'; diff --git a/src/features/album/pages/RandomAlbums.tsx b/src/features/album/pages/RandomAlbums.tsx index 34870caa..7b14c430 100644 --- a/src/features/album/pages/RandomAlbums.tsx +++ b/src/features/album/pages/RandomAlbums.tsx @@ -4,7 +4,7 @@ import { getAlbumList } from '@/lib/api/subsonicLibrary'; import { resolveAlbum } from '@/features/offline'; import type { SubsonicAlbum } from '@/lib/api/subsonicTypes'; import { dedupeById } from '@/lib/util/dedupeById'; -import { shuffleArray } from '@/utils/playback/shuffleArray'; +import { shuffleArray } from '@/features/playback/utils/playback/shuffleArray'; import React, { useEffect, useLayoutEffect, useState, useCallback, useRef } from 'react'; import { RefreshCw, Download, HardDriveDownload } from 'lucide-react'; import SelectionToggleButton from '@/components/SelectionToggleButton'; diff --git a/src/features/artist/components/ArtistDetailTopTracks.tsx b/src/features/artist/components/ArtistDetailTopTracks.tsx index 1cc4b221..806bcd4d 100644 --- a/src/features/artist/components/ArtistDetailTopTracks.tsx +++ b/src/features/artist/components/ArtistDetailTopTracks.tsx @@ -2,10 +2,10 @@ import React, { Fragment } from 'react'; import { useTranslation } from 'react-i18next'; import { AudioLines, ChevronRight, Play, Square } from 'lucide-react'; import type { SubsonicAlbum, SubsonicSong } from '@/lib/api/subsonicTypes'; -import { usePlayerStore } from '@/store/playerStore'; -import { previewInputFromSong, usePreviewStore } from '@/store/previewStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; +import { previewInputFromSong, usePreviewStore } from '@/features/playback/store/previewStore'; import { useOrbitSongRowBehavior } from '@/features/orbit'; -import { songToTrack } from '@/utils/playback/songToTrack'; +import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; import { formatTrackTime } from '@/lib/format/formatDuration'; import ArtistTopTrackCover from '@/features/artist/components/ArtistTopTrackCover'; import { topSongAlbumForCover } from '@/features/artist/components/topSongAlbumForCover'; diff --git a/src/features/artist/components/ArtistsGridView.tsx b/src/features/artist/components/ArtistsGridView.tsx index 49885d86..21bcd7c9 100644 --- a/src/features/artist/components/ArtistsGridView.tsx +++ b/src/features/artist/components/ArtistsGridView.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { Check } from 'lucide-react'; import type { TFunction } from 'i18next'; import type { SubsonicArtist } from '@/lib/api/subsonicTypes'; -import type { PlayerState } from '@/store/playerStoreTypes'; +import type { PlayerState } from '@/features/playback/store/playerStoreTypes'; import { ARTISTS_INPAGE_SCROLL_VIEWPORT_ID } from '@/constants/appScroll'; import { VirtualCardGrid } from '@/components/VirtualCardGrid'; import { ArtistCardAvatar } from '@/features/artist/components/ArtistAvatars'; diff --git a/src/features/artist/components/ArtistsListView.tsx b/src/features/artist/components/ArtistsListView.tsx index 2f9d80bd..b8c08565 100644 --- a/src/features/artist/components/ArtistsListView.tsx +++ b/src/features/artist/components/ArtistsListView.tsx @@ -2,7 +2,7 @@ import React from 'react'; import type { Virtualizer } from '@tanstack/react-virtual'; import type { TFunction } from 'i18next'; import type { SubsonicArtist } from '@/lib/api/subsonicTypes'; -import type { PlayerState } from '@/store/playerStoreTypes'; +import type { PlayerState } from '@/features/playback/store/playerStoreTypes'; import { OTHER_BUCKET, type ArtistListFlatRow } from '@/features/artist/utils/artistsHelpers'; import { ArtistRowAvatar } from '@/features/artist/components/ArtistAvatars'; diff --git a/src/features/artist/hooks/useArtistsFiltering.ts b/src/features/artist/hooks/useArtistsFiltering.ts index a407a0d2..8036351e 100644 --- a/src/features/artist/hooks/useArtistsFiltering.ts +++ b/src/features/artist/hooks/useArtistsFiltering.ts @@ -1,6 +1,6 @@ import { useMemo } from 'react'; import type { SubsonicArtist } from '@/lib/api/subsonicTypes'; -import { usePlayerStore } from '@/store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { ALL_SENTINEL, artistLetterBucket, compareBuckets, type ArtistListFlatRow } from '@/features/artist/utils/artistsHelpers'; interface UseArtistsFilteringArgs { diff --git a/src/features/artist/pages/ArtistDetail.tsx b/src/features/artist/pages/ArtistDetail.tsx index 95110a92..3baf8a64 100644 --- a/src/features/artist/pages/ArtistDetail.tsx +++ b/src/features/artist/pages/ArtistDetail.tsx @@ -6,7 +6,7 @@ import { useParams, useSearchParams } from 'react-router-dom'; import { AlbumCard } from '@/features/album'; import { ArrowDownUp } from 'lucide-react'; import { open } from '@tauri-apps/plugin-shell'; -import { usePlayerStore } from '@/store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { useAuthStore } from '@/store/authStore'; import { useTranslation } from 'react-i18next'; import { useArtistLayoutStore, type ArtistSectionId } from '@/features/artist/store/artistLayoutStore'; diff --git a/src/features/artist/pages/Artists.tsx b/src/features/artist/pages/Artists.tsx index fa86e2f2..b5a7ac15 100644 --- a/src/features/artist/pages/Artists.tsx +++ b/src/features/artist/pages/Artists.tsx @@ -4,7 +4,7 @@ import { LayoutGrid, List, Images } from 'lucide-react'; import SelectionToggleButton from '@/components/SelectionToggleButton'; import StarFilterButton from '@/components/StarFilterButton'; import OverlayScrollArea from '@/ui/OverlayScrollArea'; -import { usePlayerStore } from '@/store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { useAuthStore } from '@/store/authStore'; import { useTranslation } from 'react-i18next'; import { useVirtualizer } from '@tanstack/react-virtual'; diff --git a/src/features/artist/utils/runArtistDetailPlay.ts b/src/features/artist/utils/runArtistDetailPlay.ts index ed89c629..58086803 100644 --- a/src/features/artist/utils/runArtistDetailPlay.ts +++ b/src/features/artist/utils/runArtistDetailPlay.ts @@ -1,9 +1,9 @@ import type { TFunction } from 'i18next'; import { getSimilarSongs2, getTopSongs } from '@/lib/api/subsonicArtists'; import type { SubsonicAlbum, SubsonicArtist, SubsonicSong } from '@/lib/api/subsonicTypes'; -import type { Track } from '@/store/playerStoreTypes'; -import { songToTrack } from '@/utils/playback/songToTrack'; -import { runBulkPlayAll, runBulkShuffle } from '@/utils/playback/runBulkPlay'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; +import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; +import { runBulkPlayAll, runBulkShuffle } from '@/features/playback/utils/playback/runBulkPlay'; import { resolveAlbum, resolveMediaServerId } from '@/features/offline'; /** Ordered artist discography tracks for play-all / shuffle (network or local bytes). */ diff --git a/src/features/deviceSync/hooks/useDeviceSyncJobEvents.ts b/src/features/deviceSync/hooks/useDeviceSyncJobEvents.ts index a0ce20f4..f5653c94 100644 --- a/src/features/deviceSync/hooks/useDeviceSyncJobEvents.ts +++ b/src/features/deviceSync/hooks/useDeviceSyncJobEvents.ts @@ -6,7 +6,7 @@ import { useDeviceSyncJobStore } from '@/features/deviceSync/store/deviceSyncJob import { useDeviceSyncStore } from '@/features/deviceSync/store/deviceSyncStore'; import { showToast } from '@/utils/ui/toast'; import { trackToSyncInfo } from '@/features/deviceSync/utils/deviceSyncHelpers'; -import { fetchTracksForSource } from '@/utils/playback/fetchTracksForSource'; +import { fetchTracksForSource } from '@/features/playback/utils/playback/fetchTracksForSource'; export function useDeviceSyncJobEvents( t: TFunction, diff --git a/src/features/deviceSync/hooks/useDeviceSyncSourceStatuses.ts b/src/features/deviceSync/hooks/useDeviceSyncSourceStatuses.ts index a27faade..a0b2b847 100644 --- a/src/features/deviceSync/hooks/useDeviceSyncSourceStatuses.ts +++ b/src/features/deviceSync/hooks/useDeviceSyncSourceStatuses.ts @@ -1,6 +1,6 @@ import { useEffect, useMemo, useState } from 'react'; import { invoke } from '@tauri-apps/api/core'; -import { fetchTracksForSource } from '@/utils/playback/fetchTracksForSource'; +import { fetchTracksForSource } from '@/features/playback/utils/playback/fetchTracksForSource'; import { trackToSyncInfo, type SyncStatus } from '@/features/deviceSync/utils/deviceSyncHelpers'; import type { DeviceSyncSource } from '@/features/deviceSync/store/deviceSyncStore'; diff --git a/src/features/deviceSync/utils/runDeviceSyncExecution.ts b/src/features/deviceSync/utils/runDeviceSyncExecution.ts index 1eaa9d69..78bbc3a0 100644 --- a/src/features/deviceSync/utils/runDeviceSyncExecution.ts +++ b/src/features/deviceSync/utils/runDeviceSyncExecution.ts @@ -6,7 +6,7 @@ import { useDeviceSyncStore, type DeviceSyncSource } from '@/features/deviceSync import { useDeviceSyncJobStore } from '@/features/deviceSync/store/deviceSyncJobStore'; import { showToast } from '@/utils/ui/toast'; import { trackToSyncInfo, uuid } from '@/features/deviceSync/utils/deviceSyncHelpers'; -import { fetchTracksForSource } from '@/utils/playback/fetchTracksForSource'; +import { fetchTracksForSource } from '@/features/playback/utils/playback/fetchTracksForSource'; export interface SyncDelta { addBytes: number; diff --git a/src/features/deviceSync/utils/runDeviceSyncMigration.ts b/src/features/deviceSync/utils/runDeviceSyncMigration.ts index 103fec45..2382d3f1 100644 --- a/src/features/deviceSync/utils/runDeviceSyncMigration.ts +++ b/src/features/deviceSync/utils/runDeviceSyncMigration.ts @@ -4,7 +4,7 @@ import type { DeviceSyncSource } from '@/features/deviceSync/store/deviceSyncSto import type { SubsonicSong } from '@/lib/api/subsonicTypes'; import { applyLegacyTemplate } from '@/features/deviceSync/utils/deviceSyncLegacyTemplate'; import { trackToSyncInfo } from '@/features/deviceSync/utils/deviceSyncHelpers'; -import { fetchTracksForSource } from '@/utils/playback/fetchTracksForSource'; +import { fetchTracksForSource } from '@/features/playback/utils/playback/fetchTracksForSource'; import { IS_WINDOWS } from '@/lib/util/platform'; export type MigrationPhase = 'closed' | 'loading' | 'preview' | 'executing' | 'done' | 'nothing'; diff --git a/src/features/favorites/components/FavoriteSongRow.tsx b/src/features/favorites/components/FavoriteSongRow.tsx index 0a534901..87e9c2a7 100644 --- a/src/features/favorites/components/FavoriteSongRow.tsx +++ b/src/features/favorites/components/FavoriteSongRow.tsx @@ -9,7 +9,7 @@ import i18n from '@/lib/i18n'; import { formatTrackTime } from '@/lib/format/formatDuration'; import StarRating from '@/components/StarRating'; import { OpenArtistRefInline } from '@/features/artist'; -import { resolveTrackArtistRefs } from '@/utils/playback/trackArtistRefs'; +import { resolveTrackArtistRefs } from '@/features/playback/utils/playback/trackArtistRefs'; export interface FavoriteSongRowCallbacks { activate: (song: SubsonicSong, index: number, e: React.MouseEvent) => void; diff --git a/src/features/favorites/components/FavoritesSongsSectionHeader.tsx b/src/features/favorites/components/FavoritesSongsSectionHeader.tsx index da7db3b3..f2b5535a 100644 --- a/src/features/favorites/components/FavoritesSongsSectionHeader.tsx +++ b/src/features/favorites/components/FavoritesSongsSectionHeader.tsx @@ -2,9 +2,9 @@ import React, { useMemo, useRef } from 'react'; import { useTranslation } from 'react-i18next'; import { ListPlus, Play, SlidersHorizontal, X } from 'lucide-react'; import type { SubsonicSong } from '@/lib/api/subsonicTypes'; -import { usePlayerStore } from '@/store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { useSelectionStore } from '@/store/selectionStore'; -import { songToTrack } from '@/utils/playback/songToTrack'; +import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; import { AddToPlaylistSubmenu } from '@/components/ContextMenu'; import GenreFilterBar from '@/components/GenreFilterBar'; diff --git a/src/features/favorites/components/FavoritesSongsTracklist.tsx b/src/features/favorites/components/FavoritesSongsTracklist.tsx index 2324bd67..8d889ede 100644 --- a/src/features/favorites/components/FavoritesSongsTracklist.tsx +++ b/src/features/favorites/components/FavoritesSongsTracklist.tsx @@ -6,13 +6,13 @@ import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom'; import type { ColDef } from '@/utils/useTracklistColumns'; import type { SubsonicSong } from '@/lib/api/subsonicTypes'; -import { usePlayerStore } from '@/store/playerStore'; -import { previewInputFromSong, usePreviewStore } from '@/store/previewStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; +import { previewInputFromSong, usePreviewStore } from '@/features/playback/store/previewStore'; import { useSelectionStore } from '@/store/selectionStore'; import { useThemeStore } from '@/store/themeStore'; import { useDragDrop } from '@/lib/dnd/DragDropContext'; import { useOrbitSongRowBehavior } from '@/features/orbit'; -import { songToTrack } from '@/utils/playback/songToTrack'; +import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; import { appendServerQuery } from '@/utils/navigation/detailServerScope'; import { APP_MAIN_SCROLL_VIEWPORT_ID } from '@/constants/appScroll'; import { useElementClientHeightById } from '@/lib/hooks/useResizeClientHeight'; diff --git a/src/features/favorites/hooks/useFavoritesData.ts b/src/features/favorites/hooks/useFavoritesData.ts index ca2c0e5f..d2572937 100644 --- a/src/features/favorites/hooks/useFavoritesData.ts +++ b/src/features/favorites/hooks/useFavoritesData.ts @@ -5,7 +5,7 @@ import type { InternetRadioStation, SubsonicAlbum, SubsonicArtist, SubsonicSong, } from '@/lib/api/subsonicTypes'; import { useAuthStore } from '@/store/authStore'; -import { usePlayerStore } from '@/store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import type { TopFavoriteArtist } from '@/features/favorites/components/TopFavoriteArtists'; import { useConnectionStatus } from '@/hooks/useConnectionStatus'; import { isActiveServerReachable } from '@/utils/network/activeServerReachability'; diff --git a/src/features/favorites/hooks/useFavoritesSongFiltering.tsx b/src/features/favorites/hooks/useFavoritesSongFiltering.tsx index ac19bb0e..039f2f6d 100644 --- a/src/features/favorites/hooks/useFavoritesSongFiltering.tsx +++ b/src/features/favorites/hooks/useFavoritesSongFiltering.tsx @@ -1,7 +1,7 @@ import React, { useMemo } from 'react'; import { ArrowDown, ArrowUp } from 'lucide-react'; import type { SubsonicSong } from '@/lib/api/subsonicTypes'; -import { usePlayerStore } from '@/store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; const CURRENT_YEAR = new Date().getFullYear(); const MIN_YEAR = 1950; diff --git a/src/features/favorites/pages/Favorites.tsx b/src/features/favorites/pages/Favorites.tsx index 8d24df18..39cf36d4 100644 --- a/src/features/favorites/pages/Favorites.tsx +++ b/src/features/favorites/pages/Favorites.tsx @@ -1,4 +1,4 @@ -import { queueSongStar, queueSongRating } from '@/store/pendingStarSync'; +import { queueSongStar, queueSongRating } from '@/features/playback/store/pendingStarSync'; import React, { useEffect, useMemo, useState } from 'react'; import { useTracklistColumns, type ColDef } from '@/utils/useTracklistColumns'; import { TopFavoriteArtistsRow } from '@/features/favorites/components/TopFavoriteArtists'; @@ -11,7 +11,7 @@ import { useFavoritesSelection } from '@/features/favorites/hooks/useFavoritesSe import { useBulkPlPickerOutsideClick } from '@/hooks/useBulkPlPickerOutsideClick'; import { AlbumRow } from '@/features/album'; import { ArtistRow } from '@/features/artist'; -import { usePlayerStore } from '@/store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { useTranslation } from 'react-i18next'; import { useSelectionStore } from '@/store/selectionStore'; import FavoritesOfflineHeader from '@/features/favorites/components/FavoritesOfflineHeader'; diff --git a/src/features/fullscreenPlayer/components/FsLyricsApple.tsx b/src/features/fullscreenPlayer/components/FsLyricsApple.tsx index 2a130347..dea028d5 100644 --- a/src/features/fullscreenPlayer/components/FsLyricsApple.tsx +++ b/src/features/fullscreenPlayer/components/FsLyricsApple.tsx @@ -1,11 +1,11 @@ import React, { memo, useCallback, useEffect, useRef, useState } from 'react'; -import { usePlayerStore } from '@/store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { useAuthStore } from '@/store/authStore'; import { useLyrics, type WordLyricsLine } from '@/hooks/useLyrics'; import { useWordLyricsSync } from '@/hooks/useWordLyricsSync'; -import { getPlaybackProgressSnapshot, subscribePlaybackProgress } from '@/store/playbackProgress'; +import { getPlaybackProgressSnapshot, subscribePlaybackProgress } from '@/features/playback/store/playbackProgress'; import type { LrcLine } from '@/api/lrclib'; -import type { Track } from '@/store/playerStoreTypes'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; import { EaseScroller, targetForFraction } from '@/utils/ui/easeScroll'; // Fullscreen synced lyrics. diff --git a/src/features/fullscreenPlayer/components/FsPlayBtn.tsx b/src/features/fullscreenPlayer/components/FsPlayBtn.tsx index 5b42d084..1adb9bbe 100644 --- a/src/features/fullscreenPlayer/components/FsPlayBtn.tsx +++ b/src/features/fullscreenPlayer/components/FsPlayBtn.tsx @@ -1,7 +1,7 @@ import React, { memo, useRef } from 'react'; import { Moon, Pause, Play, Sunrise } from 'lucide-react'; import { useTranslation } from 'react-i18next'; -import { usePlayerStore } from '@/store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { usePlaybackDelayPress } from '@/hooks/usePlaybackDelayPress'; import { usePlaybackScheduleRemaining } from '@/utils/format/playbackScheduleFormat'; import PlaybackDelayModal from '@/components/PlaybackDelayModal'; diff --git a/src/features/fullscreenPlayer/components/FsQueueModal.tsx b/src/features/fullscreenPlayer/components/FsQueueModal.tsx index 95fcca40..b2446037 100644 --- a/src/features/fullscreenPlayer/components/FsQueueModal.tsx +++ b/src/features/fullscreenPlayer/components/FsQueueModal.tsx @@ -1,8 +1,8 @@ import { memo, useMemo, useSyncExternalStore } from 'react'; import { useTranslation } from 'react-i18next'; import { X } from 'lucide-react'; -import { usePlayerStore } from '@/store/playerStore'; -import type { Track } from '@/store/playerStoreTypes'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; import { resolveQueueTrack } from '@/utils/library/queueTrackView'; import { getQueueResolverVersion, diff --git a/src/features/fullscreenPlayer/components/FsTimeReadout.tsx b/src/features/fullscreenPlayer/components/FsTimeReadout.tsx index 14c39091..9288f1a5 100644 --- a/src/features/fullscreenPlayer/components/FsTimeReadout.tsx +++ b/src/features/fullscreenPlayer/components/FsTimeReadout.tsx @@ -1,5 +1,5 @@ import { memo, useEffect, useRef } from 'react'; -import { getPlaybackProgressSnapshot, subscribePlaybackProgress } from '@/store/playbackProgress'; +import { getPlaybackProgressSnapshot, subscribePlaybackProgress } from '@/features/playback/store/playbackProgress'; import { formatTrackTime } from '@/lib/format/formatDuration'; /** diff --git a/src/features/fullscreenPlayer/components/FullscreenPlayerStatic.tsx b/src/features/fullscreenPlayer/components/FullscreenPlayerStatic.tsx index a283c007..7d33d775 100644 --- a/src/features/fullscreenPlayer/components/FullscreenPlayerStatic.tsx +++ b/src/features/fullscreenPlayer/components/FullscreenPlayerStatic.tsx @@ -4,8 +4,8 @@ import { Shuffle, ListMusic, ChevronDown, Star, MicVocal, } from 'lucide-react'; import { useTranslation } from 'react-i18next'; -import { usePlayerStore } from '@/store/playerStore'; -import { queueSongStar, queueSongRating } from '@/store/pendingStarSync'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; +import { queueSongStar, queueSongRating } from '@/features/playback/store/pendingStarSync'; import { useAlbumCoverRef, useArtistCoverRef } from '@/cover/useLibraryCoverRef'; import { usePlaybackCoverArt } from '@/hooks/usePlaybackCoverArt'; import { useCachedUrl } from '@/ui/CachedImage'; diff --git a/src/features/miniPlayer/components/MiniPlayer.tsx b/src/features/miniPlayer/components/MiniPlayer.tsx index da484424..1dc0b08e 100644 --- a/src/features/miniPlayer/components/MiniPlayer.tsx +++ b/src/features/miniPlayer/components/MiniPlayer.tsx @@ -4,7 +4,7 @@ import { useEffect, useRef, useState } from 'react'; import { emit } from '@tauri-apps/api/event'; import { invoke } from '@tauri-apps/api/core'; import { useTranslation } from 'react-i18next'; -import { usePlayerStore } from '@/store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { registerQueueDragHitTest } from '@/lib/dnd/DragDropContext'; import MiniContextMenu from '@/features/miniPlayer/components/MiniContextMenu'; import type { MiniSyncPayload, MiniControlAction, MiniTrackInfo } from '@/features/miniPlayer/utils/miniPlayerBridge'; diff --git a/src/features/miniPlayer/components/MiniToolbar.tsx b/src/features/miniPlayer/components/MiniToolbar.tsx index 48dde227..5cf45726 100644 --- a/src/features/miniPlayer/components/MiniToolbar.tsx +++ b/src/features/miniPlayer/components/MiniToolbar.tsx @@ -4,7 +4,7 @@ import { emit } from '@tauri-apps/api/event'; import { Blend, Infinity as InfinityIcon, ListMusic, MoveRight, Shuffle, Volume2, VolumeX, Waves } from 'lucide-react'; import type { TFunction } from 'i18next'; import type { MiniSyncPayload } from '@/features/miniPlayer/utils/miniPlayerBridge'; -import { getTransitionMode } from '@/utils/playback/playbackTransition'; +import { getTransitionMode } from '@/features/playback/utils/playback/playbackTransition'; interface Props { state: MiniSyncPayload; diff --git a/src/features/miniPlayer/hooks/useMiniQueueDrag.ts b/src/features/miniPlayer/hooks/useMiniQueueDrag.ts index 4d6d18db..ef4d5f24 100644 --- a/src/features/miniPlayer/hooks/useMiniQueueDrag.ts +++ b/src/features/miniPlayer/hooks/useMiniQueueDrag.ts @@ -1,7 +1,7 @@ import React, { useEffect, useRef, useState } from 'react'; import { emit } from '@tauri-apps/api/event'; import { useDragDrop } from '@/lib/dnd/DragDropContext'; -import { usePlayerStore } from '@/store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; interface Args { queueOpen: boolean; diff --git a/src/features/miniPlayer/utils/miniPlayerBridge.ts b/src/features/miniPlayer/utils/miniPlayerBridge.ts index f3245972..dd916b5f 100644 --- a/src/features/miniPlayer/utils/miniPlayerBridge.ts +++ b/src/features/miniPlayer/utils/miniPlayerBridge.ts @@ -1,11 +1,11 @@ import { getCurrentWindow } from '@tauri-apps/api/window'; import { listen, emitTo } from '@tauri-apps/api/event'; -import { usePlayerStore } from '@/store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { useAuthStore } from '@/store/authStore'; -import { setTransitionMode, type TransitionMode } from '@/utils/playback/playbackTransition'; +import { setTransitionMode, type TransitionMode } from '@/features/playback/utils/playback/playbackTransition'; import { resolveQueueTrack } from '@/utils/library/queueTrackView'; import type { SubsonicOpenArtistRef } from '@/lib/api/subsonicTypes'; -import type { Track } from '@/store/playerStoreTypes'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; export const MINI_WINDOW_LABEL = 'mini'; diff --git a/src/features/miniPlayer/utils/miniPlayerHelpers.ts b/src/features/miniPlayer/utils/miniPlayerHelpers.ts index cc92d349..e05b60d2 100644 --- a/src/features/miniPlayer/utils/miniPlayerHelpers.ts +++ b/src/features/miniPlayer/utils/miniPlayerHelpers.ts @@ -1,7 +1,7 @@ -import { usePlayerStore } from '@/store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { resolveQueueTrack } from '@/utils/library/queueTrackView'; import type { MiniSyncPayload, MiniTrackInfo } from '@/features/miniPlayer/utils/miniPlayerBridge'; -import type { Track } from '@/store/playerStoreTypes'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; /** Half-width of the mini initial-snapshot queue window (matches the bridge). */ const MINI_SNAPSHOT_HALF = 100; diff --git a/src/features/nowPlaying/components/NowPlayingDropdown.tsx b/src/features/nowPlaying/components/NowPlayingDropdown.tsx index 8957fb2f..06c63628 100644 --- a/src/features/nowPlaying/components/NowPlayingDropdown.tsx +++ b/src/features/nowPlaying/components/NowPlayingDropdown.tsx @@ -6,7 +6,7 @@ import { createPortal } from 'react-dom'; import { PlayCircle, Pause, User, Radio, RefreshCw } from 'lucide-react'; import { nowPlayingPresence } from '@/features/nowPlaying/api/nowPlayingPresence'; import { useAuthStore } from '@/store/authStore'; -import { usePlayerStore } from '@/store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom'; diff --git a/src/features/nowPlaying/components/NowPlayingInfo.tsx b/src/features/nowPlaying/components/NowPlayingInfo.tsx index d88061a3..1214e24c 100644 --- a/src/features/nowPlaying/components/NowPlayingInfo.tsx +++ b/src/features/nowPlaying/components/NowPlayingInfo.tsx @@ -5,13 +5,13 @@ import React, { useEffect, useLayoutEffect, useMemo, useRef, useState } from 're import { useTranslation } from 'react-i18next'; import { Info } from 'lucide-react'; import { open as shellOpen } from '@tauri-apps/plugin-shell'; -import { usePlayerStore } from '@/store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { useAuthStore } from '@/store/authStore'; import { usePlaybackServerId } from '@/hooks/usePlaybackServerId'; import { fetchBandsintownEvents, type BandsintownEvent } from '@/api/bandsintown'; import CachedImage from '@/ui/CachedImage'; import OverlayScrollArea from '@/ui/OverlayScrollArea'; -import { primaryTrackArtistRef } from '@/utils/playback/trackArtistRefs'; +import { primaryTrackArtistRef } from '@/features/playback/utils/playback/trackArtistRefs'; const TOUR_LIMIT = 5; const BIO_CLAMP_LINES = 4; diff --git a/src/features/nowPlaying/components/RadioView.tsx b/src/features/nowPlaying/components/RadioView.tsx index ee859f85..bc5daf20 100644 --- a/src/features/nowPlaying/components/RadioView.tsx +++ b/src/features/nowPlaying/components/RadioView.tsx @@ -2,7 +2,7 @@ import React, { memo } from 'react'; import { useTranslation } from 'react-i18next'; import { Cast, Clock, Radio, SkipForward, Users } from 'lucide-react'; import type { useRadioMetadata } from '@/features/radio'; -import { usePlayerStore } from '@/store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { formatTrackTime } from '@/lib/format/formatDuration'; type NonNullStoreField> = diff --git a/src/features/nowPlaying/hooks/useNowPlayingPrewarm.test.ts b/src/features/nowPlaying/hooks/useNowPlayingPrewarm.test.ts index 71a2444e..95b767b9 100644 --- a/src/features/nowPlaying/hooks/useNowPlayingPrewarm.test.ts +++ b/src/features/nowPlaying/hooks/useNowPlayingPrewarm.test.ts @@ -5,7 +5,7 @@ import { coverIndexKeyFromRef } from '@/cover/storageKeys'; import { useNowPlayingPrewarm } from '@/features/nowPlaying/hooks/useNowPlayingPrewarm'; import { prewarmNowPlayingFetchers } from '@/features/nowPlaying/hooks/useNowPlayingFetchers'; import { useAuthStore } from '@/store/authStore'; -import { usePlayerStore } from '@/store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { makeTrack } from '@/test/helpers/factories'; import { resetAllStores } from '@/test/helpers/storeReset'; import { toQueueItemRefs } from '@/utils/library/queueItemRef'; diff --git a/src/features/nowPlaying/hooks/useNowPlayingPrewarm.ts b/src/features/nowPlaying/hooks/useNowPlayingPrewarm.ts index e698b38f..a840b116 100644 --- a/src/features/nowPlaying/hooks/useNowPlayingPrewarm.ts +++ b/src/features/nowPlaying/hooks/useNowPlayingPrewarm.ts @@ -10,9 +10,9 @@ import { coverArtIdFromRadio } from '@/cover/ids'; import type { CoverArtRef } from '@/cover/types'; import { prewarmNowPlayingFetchers } from '@/features/nowPlaying/hooks/useNowPlayingFetchers'; import { useAuthStore } from '@/store/authStore'; -import { usePlayerStore } from '@/store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { usePlaybackServerId } from '@/hooks/usePlaybackServerId'; -import { primaryTrackArtistRef } from '@/utils/playback/trackArtistRefs'; +import { primaryTrackArtistRef } from '@/features/playback/utils/playback/trackArtistRefs'; const NOW_PLAYING_COVER_CSS_PX = 800; diff --git a/src/features/nowPlaying/hooks/useNowPlayingStarLove.ts b/src/features/nowPlaying/hooks/useNowPlayingStarLove.ts index 8fa38d35..f70a4ebc 100644 --- a/src/features/nowPlaying/hooks/useNowPlayingStarLove.ts +++ b/src/features/nowPlaying/hooks/useNowPlayingStarLove.ts @@ -1,7 +1,7 @@ import { useCallback, useEffect, useState } from 'react'; -import { queueSongStar } from '@/store/pendingStarSync'; +import { queueSongStar } from '@/features/playback/store/pendingStarSync'; import type { SubsonicSong } from '@/lib/api/subsonicTypes'; -import type { Track } from '@/store/playerStoreTypes'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; import type { TrackStats } from '@/music-network'; import { getMusicNetworkRuntime } from '@/music-network'; diff --git a/src/features/nowPlaying/pages/NowPlaying.tsx b/src/features/nowPlaying/pages/NowPlaying.tsx index e84b7b2a..ede611b7 100644 --- a/src/features/nowPlaying/pages/NowPlaying.tsx +++ b/src/features/nowPlaying/pages/NowPlaying.tsx @@ -8,10 +8,10 @@ import { usePlaybackLibraryNavigate } from '@/hooks/usePlaybackLibraryNavigate'; import { usePlaybackServerId } from '@/hooks/usePlaybackServerId'; import { useTranslation } from 'react-i18next'; import { Music, EyeOff, LayoutGrid, RotateCcw, Eye } from 'lucide-react'; -import { usePlayerStore } from '@/store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { useAuthStore } from '@/store/authStore'; import { useLyricsStore } from '@/store/lyricsStore'; -import { songToTrack } from '@/utils/playback/songToTrack'; +import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; import { useRadioMetadata } from '@/features/radio'; import { useDragDrop } from '@/lib/dnd/DragDropContext'; import OverlayScrollArea from '@/ui/OverlayScrollArea'; @@ -38,7 +38,7 @@ import DiscographyCard from '@/features/nowPlaying/components/DiscographyCard'; import { useNowPlayingFetchers } from '@/features/nowPlaying/hooks/useNowPlayingFetchers'; import { useNowPlayingStarLove } from '@/features/nowPlaying/hooks/useNowPlayingStarLove'; import { useArtistInfoBatch } from '@/features/artist'; -import { primaryTrackArtistRef, resolveTrackArtistRefs } from '@/utils/playback/trackArtistRefs'; +import { primaryTrackArtistRef, resolveTrackArtistRefs } from '@/features/playback/utils/playback/trackArtistRefs'; import type { ArtistCardTab } from '@/features/nowPlaying/components/ArtistCard'; // ─── Main Page ──────────────────────────────────────────────────────────────── diff --git a/src/features/offline/pages/OfflineLibrary.tsx b/src/features/offline/pages/OfflineLibrary.tsx index db8c8e55..3858c638 100644 --- a/src/features/offline/pages/OfflineLibrary.tsx +++ b/src/features/offline/pages/OfflineLibrary.tsx @@ -6,7 +6,7 @@ import { useTranslation } from 'react-i18next'; import { useOfflineStore } from '@/features/offline/store/offlineStore'; import { useLocalPlaybackStore } from '@/store/localPlaybackStore'; import { useAuthStore } from '@/store/authStore'; -import { usePlayerStore } from '@/store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { AlbumCoverArtImage } from '@/cover/AlbumCoverArtImage'; import { coverServerScopeForServerId } from '@/cover/serverScope'; import { usePerfProbeFlags } from '@/utils/perf/perfFlags'; @@ -33,7 +33,7 @@ import { type OfflineLibraryCard, } from '@/features/offline/utils/offlineLibraryHelpers'; import { showToast } from '@/utils/ui/toast'; -import { shuffleArray } from '@/utils/playback/shuffleArray'; +import { shuffleArray } from '@/features/playback/utils/playback/shuffleArray'; import { getMediaDir } from '@/utils/media/mediaDir'; import { canonicalQueueServerKey, resolveIndexKey } from '@/utils/server/serverIndexKey'; import { reconcileAllLibraryTiersFromDisk } from '@/features/offline/utils/libraryTierReconcile'; diff --git a/src/features/offline/utils/offlineLibraryHelpers.ts b/src/features/offline/utils/offlineLibraryHelpers.ts index 11a5196c..7f0e4aea 100644 --- a/src/features/offline/utils/offlineLibraryHelpers.ts +++ b/src/features/offline/utils/offlineLibraryHelpers.ts @@ -9,7 +9,7 @@ import { findLocalPlaybackEntry, hasLocalLibraryBytes } from '@/store/localPlayb import { useOfflineStore, type OfflineAlbumMeta } from '@/features/offline/store/offlineStore'; import { resolveTrackCoverArtId, trackToSong } from '@/utils/library/advancedSearchLocal'; import { canonicalQueueServerKey, resolveIndexKey } from '@/utils/server/serverIndexKey'; -import type { Track } from '@/store/playerStoreTypes'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; import { findServerByIdOrIndexKey, resolveServerIdForIndexKey } from '@/utils/server/serverLookup'; import { serverIndexKeyForProfile } from '@/utils/server/serverIndexKey'; diff --git a/src/features/orbit/components/OrbitDiagnosticsPopover.tsx b/src/features/orbit/components/OrbitDiagnosticsPopover.tsx index 754546a2..82abfe1c 100644 --- a/src/features/orbit/components/OrbitDiagnosticsPopover.tsx +++ b/src/features/orbit/components/OrbitDiagnosticsPopover.tsx @@ -3,7 +3,7 @@ import { createPortal } from 'react-dom'; import { Copy, Trash2 } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { useOrbitStore } from '@/features/orbit/store/orbitStore'; -import { usePlayerStore } from '@/store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { showToast } from '@/utils/ui/toast'; import { computeOrbitDriftMs } from '@/features/orbit/utils/orbit'; import { diff --git a/src/features/orbit/components/OrbitSessionBar.tsx b/src/features/orbit/components/OrbitSessionBar.tsx index 834a60c8..4cd6d08e 100644 --- a/src/features/orbit/components/OrbitSessionBar.tsx +++ b/src/features/orbit/components/OrbitSessionBar.tsx @@ -1,11 +1,11 @@ import { getSong } from '@/lib/api/subsonicLibrary'; -import { songToTrack } from '@/utils/playback/songToTrack'; +import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; import { useEffect, useRef, useState } from 'react'; import { X, RefreshCw, Shuffle, Settings2, Share2, HelpCircle, Activity } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { useOrbitStore } from '@/features/orbit/store/orbitStore'; import { useHelpModalStore } from '@/store/helpModalStore'; -import { usePlayerStore } from '@/store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { endOrbitSession, leaveOrbitSession, diff --git a/src/features/orbit/components/OrbitStartModal.tsx b/src/features/orbit/components/OrbitStartModal.tsx index 5bcfd902..354f13f2 100644 --- a/src/features/orbit/components/OrbitStartModal.tsx +++ b/src/features/orbit/components/OrbitStartModal.tsx @@ -12,7 +12,7 @@ import { } from '@/features/orbit/utils/orbit'; import { randomOrbitSessionName } from '@/features/orbit/utils/orbitNames'; import { useAuthStore } from '@/store/authStore'; -import { usePlayerStore } from '@/store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { isLanUrl, serverShareBaseUrl } from '@/utils/server/serverEndpoint'; import { ORBIT_DEFAULT_MAX_USERS } from '@/features/orbit/api/orbit'; diff --git a/src/features/orbit/hooks/useOrbitGuest.ts b/src/features/orbit/hooks/useOrbitGuest.ts index 5667d494..1cf40cee 100644 --- a/src/features/orbit/hooks/useOrbitGuest.ts +++ b/src/features/orbit/hooks/useOrbitGuest.ts @@ -1,9 +1,9 @@ import { getSong } from '@/lib/api/subsonicLibrary'; -import { songToTrack } from '@/utils/playback/songToTrack'; +import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; import { useEffect, useRef } from 'react'; import { useOrbitStore } from '@/features/orbit/store/orbitStore'; import { useAuthStore } from '@/store/authStore'; -import { usePlayerStore } from '@/store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { readOrbitState, applyOrbitTransitionSettings, diff --git a/src/features/orbit/hooks/useOrbitHost.ts b/src/features/orbit/hooks/useOrbitHost.ts index d404ae5a..5023e78b 100644 --- a/src/features/orbit/hooks/useOrbitHost.ts +++ b/src/features/orbit/hooks/useOrbitHost.ts @@ -1,8 +1,8 @@ import { getSong } from '@/lib/api/subsonicLibrary'; -import { songToTrack } from '@/utils/playback/songToTrack'; +import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; import { useEffect } from 'react'; import { useOrbitStore } from '@/features/orbit/store/orbitStore'; -import { usePlayerStore } from '@/store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { writeOrbitState, sweepGuestOutboxes, diff --git a/src/features/orbit/hooks/usePlaybackRateOrbitSync.ts b/src/features/orbit/hooks/usePlaybackRateOrbitSync.ts index f1cfd5a6..34855308 100644 --- a/src/features/orbit/hooks/usePlaybackRateOrbitSync.ts +++ b/src/features/orbit/hooks/usePlaybackRateOrbitSync.ts @@ -1,6 +1,6 @@ import { useEffect } from 'react'; import { useOrbitStore } from '@/features/orbit/store/orbitStore'; -import { usePlaybackRateStore } from '@/store/playbackRateStore'; +import { usePlaybackRateStore } from '@/features/playback/store/playbackRateStore'; /** Re-sync playback rate when Orbit enters or leaves shared playback. */ export function usePlaybackRateOrbitSync() { diff --git a/src/features/orbit/utils/guest.ts b/src/features/orbit/utils/guest.ts index d16fa330..ffe85b6d 100644 --- a/src/features/orbit/utils/guest.ts +++ b/src/features/orbit/utils/guest.ts @@ -1,9 +1,9 @@ import { createPlaylist, deletePlaylist, getPlaylist, getPlaylists, updatePlaylist } from '@/lib/api/subsonicPlaylists'; import { getSong } from '@/lib/api/subsonicLibrary'; -import { songToTrack } from '@/utils/playback/songToTrack'; +import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; import { useAuthStore } from '@/store/authStore'; import { useOrbitStore } from '@/features/orbit/store/orbitStore'; -import { usePlayerStore } from '@/store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { orbitOutboxPlaylistName, type OrbitQueueItem, diff --git a/src/features/orbit/utils/host.test.ts b/src/features/orbit/utils/host.test.ts index fe03cbeb..deba5a5f 100644 --- a/src/features/orbit/utils/host.test.ts +++ b/src/features/orbit/utils/host.test.ts @@ -23,10 +23,10 @@ vi.mock('@/features/orbit/utils/remote', () => ({ })); vi.mock('@/features/orbit/store/orbitStore', () => ({ useOrbitStore: { getState: () => orbitStore } })); vi.mock('@/store/authStore', () => ({ useAuthStore: { getState: () => ({}) } })); -vi.mock('@/store/playerStore', () => ({ usePlayerStore: { getState: () => ({ enqueue: vi.fn() }) } })); +vi.mock('@/features/playback/store/playerStore', () => ({ usePlayerStore: { getState: () => ({ enqueue: vi.fn() }) } })); vi.mock('@/lib/api/subsonicPlaylists', () => ({ createPlaylist: vi.fn(), deletePlaylist: vi.fn() })); vi.mock('@/lib/api/subsonicLibrary', () => ({ getSong: vi.fn() })); -vi.mock('@/utils/playback/songToTrack', () => ({ songToTrack: vi.fn() })); +vi.mock('@/features/playback/utils/playback/songToTrack', () => ({ songToTrack: vi.fn() })); import { updateOrbitSettings } from '@/features/orbit/utils/host'; diff --git a/src/features/orbit/utils/host.ts b/src/features/orbit/utils/host.ts index ad6eb799..39985e58 100644 --- a/src/features/orbit/utils/host.ts +++ b/src/features/orbit/utils/host.ts @@ -1,9 +1,9 @@ import { createPlaylist, deletePlaylist } from '@/lib/api/subsonicPlaylists'; import { getSong } from '@/lib/api/subsonicLibrary'; -import { songToTrack } from '@/utils/playback/songToTrack'; +import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; import { useAuthStore } from '@/store/authStore'; import { useOrbitStore } from '@/features/orbit/store/orbitStore'; -import { usePlayerStore } from '@/store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { makeInitialOrbitState, orbitOutboxPlaylistName, diff --git a/src/store/applyQueueHistorySnapshot.ts b/src/features/playback/store/applyQueueHistorySnapshot.ts similarity index 84% rename from src/store/applyQueueHistorySnapshot.ts rename to src/features/playback/store/applyQueueHistorySnapshot.ts index 01967e98..16e4bf19 100644 --- a/src/store/applyQueueHistorySnapshot.ts +++ b/src/features/playback/store/applyQueueHistorySnapshot.ts @@ -1,29 +1,29 @@ -import { playbackReportStart } from './playbackReportSession'; +import { playbackReportStart } from '@/features/playback/store/playbackReportSession'; import { invoke } from '@tauri-apps/api/core'; -import { getPlaybackServerId } from '../utils/playback/playbackServer'; -import { getPlaybackSourceKind } from '../utils/playback/resolvePlaybackUrl'; +import { getPlaybackServerId } from '@/features/playback/utils/playback/playbackServer'; +import { getPlaybackSourceKind } from '@/features/playback/utils/playback/resolvePlaybackUrl'; import { bumpPlayGeneration, getPlayGeneration, setIsAudioPaused, -} from './engineState'; -import { clearPreloadingIds } from './gaplessPreloadState'; -import { deriveNormalizationSnapshot } from './normalizationSnapshot'; -import type { PlayerState, QueueItemRef } from './playerStoreTypes'; -import { resolveQueueTrack } from '../utils/library/queueTrackView'; -import { seedQueueResolver } from '../utils/library/queueTrackResolver'; -import { canonicalQueueServerKey } from '../utils/server/serverIndexKey'; -import { sameQueueTrackId } from '../utils/playback/queueIdentity'; -import { queueUndoRestoreAudioEngine } from './queueUndoAudioRestore'; +} from '@/features/playback/store/engineState'; +import { clearPreloadingIds } from '@/features/playback/store/gaplessPreloadState'; +import { deriveNormalizationSnapshot } from '@/features/playback/store/normalizationSnapshot'; +import type { PlayerState, QueueItemRef } from '@/features/playback/store/playerStoreTypes'; +import { resolveQueueTrack } from '@/utils/library/queueTrackView'; +import { seedQueueResolver } from '@/utils/library/queueTrackResolver'; +import { canonicalQueueServerKey } from '@/utils/server/serverIndexKey'; +import { sameQueueTrackId } from '@/features/playback/utils/playback/queueIdentity'; +import { queueUndoRestoreAudioEngine } from '@/features/playback/store/queueUndoAudioRestore'; import { setPendingQueueListScrollTop, type QueueUndoSnapshot, -} from './queueUndo'; -import { refreshLoudnessForTrack } from './loudnessRefresh'; -import { refreshWaveformForTrack } from '@/store/waveformRefresh'; -import { stopRadio } from './radioPlayer'; -import { clearAllPlaybackScheduleTimers } from './scheduleTimers'; -import { syncUserQueueMutationToServer } from './queueSync'; +} from '@/features/playback/store/queueUndo'; +import { refreshLoudnessForTrack } from '@/features/playback/store/loudnessRefresh'; +import { refreshWaveformForTrack } from '@/features/playback/store/waveformRefresh'; +import { stopRadio } from '@/features/playback/store/radioPlayer'; +import { clearAllPlaybackScheduleTimers } from '@/features/playback/store/scheduleTimers'; +import { syncUserQueueMutationToServer } from '@/features/playback/store/queueSync'; type SetState = ( partial: Partial | ((state: PlayerState) => Partial), diff --git a/src/store/applyServerPlayQueue.idle.test.ts b/src/features/playback/store/applyServerPlayQueue.idle.test.ts similarity index 83% rename from src/store/applyServerPlayQueue.idle.test.ts rename to src/features/playback/store/applyServerPlayQueue.idle.test.ts index ff69f7b2..8cd9fa30 100644 --- a/src/store/applyServerPlayQueue.idle.test.ts +++ b/src/features/playback/store/applyServerPlayQueue.idle.test.ts @@ -1,5 +1,5 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; -import type { QueueItemRef } from './playerStoreTypes'; +import type { QueueItemRef } from '@/features/playback/store/playerStoreTypes'; const getPlayQueueForServerMock = vi.fn(); @@ -7,11 +7,11 @@ vi.mock('@/lib/api/subsonicPlayQueue', () => ({ getPlayQueueForServer: (...args: unknown[]) => getPlayQueueForServerMock(...args), })); -vi.mock('../utils/server/serverLookup', () => ({ +vi.mock('@/utils/server/serverLookup', () => ({ resolveServerIdForIndexKey: (id: string) => id, })); -vi.mock('../utils/playback/songToTrack', () => ({ +vi.mock('@/features/playback/utils/playback/songToTrack', () => ({ songToTrack: (s: { id: string }) => ({ id: s.id, title: s.id, @@ -23,15 +23,15 @@ vi.mock('../utils/playback/songToTrack', () => ({ }), })); -vi.mock('./pausedRestorePrepare', () => ({ +vi.mock('@/features/playback/store/pausedRestorePrepare', () => ({ preparePausedRestoreOnStartup: vi.fn(), })); -vi.mock('@/store/waveformRefresh', () => ({ +vi.mock('@/features/playback/store/waveformRefresh', () => ({ refreshWaveformForTrack: vi.fn(), })); -vi.mock('./queueSyncUiState', () => ({ +vi.mock('@/features/playback/store/queueSyncUiState', () => ({ clearQueueHandoffPending: vi.fn(), })); @@ -43,7 +43,7 @@ const playerState = { isPlaying: false, }; -vi.mock('./playerStore', () => ({ +vi.mock('@/features/playback/store/playerStore', () => ({ usePlayerStore: { getState: () => playerState, setState: (partial: Partial) => { @@ -52,13 +52,13 @@ vi.mock('./playerStore', () => ({ }, })); -import { applyServerPlayQueue } from './applyServerPlayQueue'; +import { applyServerPlayQueue } from '@/features/playback/store/applyServerPlayQueue'; import { _resetQueuePlaybackIdleForTest, getIdlePullGeneration, isIdleQueuePullSuspended, touchQueueMutationClock, -} from './queuePlaybackIdle'; +} from '@/features/playback/store/queuePlaybackIdle'; describe('applyServerPlayQueue idle guards', () => { beforeEach(() => { diff --git a/src/store/applyServerPlayQueue.test.ts b/src/features/playback/store/applyServerPlayQueue.test.ts similarity index 91% rename from src/store/applyServerPlayQueue.test.ts rename to src/features/playback/store/applyServerPlayQueue.test.ts index 76735b36..fa86dcab 100644 --- a/src/store/applyServerPlayQueue.test.ts +++ b/src/features/playback/store/applyServerPlayQueue.test.ts @@ -3,8 +3,8 @@ import { fingerprintFromLocalQueue, fingerprintFromServer, playQueueFingerprintsEqual, -} from './applyServerPlayQueue'; -import { usePlayerStore } from './playerStore'; +} from '@/features/playback/store/applyServerPlayQueue'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { resetPlayerStore } from '@/test/helpers/storeReset'; describe('playQueueFingerprintsEqual', () => { diff --git a/src/store/applyServerPlayQueue.ts b/src/features/playback/store/applyServerPlayQueue.ts similarity index 85% rename from src/store/applyServerPlayQueue.ts rename to src/features/playback/store/applyServerPlayQueue.ts index df329337..ef186500 100644 --- a/src/store/applyServerPlayQueue.ts +++ b/src/features/playback/store/applyServerPlayQueue.ts @@ -1,22 +1,22 @@ import { getPlayQueueForServer, type PlayQueueResult } from '@/lib/api/subsonicPlayQueue'; -import { songToTrack } from '../utils/playback/songToTrack'; -import { bindQueueServerId } from '../utils/playback/playbackServer'; -import { resolveServerIdForIndexKey } from '../utils/server/serverLookup'; -import { toQueueItemRefs } from '../utils/library/queueItemRef'; -import { seedQueueResolver } from '../utils/library/queueTrackResolver'; -import type { Track } from './playerStoreTypes'; -import { useAuthStore } from './authStore'; -import { usePlayerStore } from './playerStore'; -import { preparePausedRestoreOnStartup } from './pausedRestorePrepare'; -import { pushQueueUndoFromGetter } from './queueUndo'; -import { refreshWaveformForTrack } from '@/store/waveformRefresh'; +import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; +import { bindQueueServerId } from '@/features/playback/utils/playback/playbackServer'; +import { resolveServerIdForIndexKey } from '@/utils/server/serverLookup'; +import { toQueueItemRefs } from '@/utils/library/queueItemRef'; +import { seedQueueResolver } from '@/utils/library/queueTrackResolver'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; +import { useAuthStore } from '@/store/authStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; +import { preparePausedRestoreOnStartup } from '@/features/playback/store/pausedRestorePrepare'; +import { pushQueueUndoFromGetter } from '@/features/playback/store/queueUndo'; +import { refreshWaveformForTrack } from '@/features/playback/store/waveformRefresh'; import { getIdlePullGeneration, isIdleQueuePullSuspended, resumeIdleQueuePull, clearQueueNaturallyEnded, -} from './queuePlaybackIdle'; -import { clearQueueHandoffPending } from './queueSyncUiState'; +} from '@/features/playback/store/queuePlaybackIdle'; +import { clearQueueHandoffPending } from '@/features/playback/store/queueSyncUiState'; export type ApplyPlayQueueMode = 'startup' | 'idle' | 'manual'; diff --git a/src/store/audioEventHandlers.ts b/src/features/playback/store/audioEventHandlers.ts similarity index 89% rename from src/store/audioEventHandlers.ts rename to src/features/playback/store/audioEventHandlers.ts index cb94fd6b..57c48952 100644 --- a/src/store/audioEventHandlers.ts +++ b/src/features/playback/store/audioEventHandlers.ts @@ -1,37 +1,37 @@ import { scrobbleSong } from '@/lib/api/subsonicScrobble'; -import type { Track } from './playerStoreTypes'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; import { playbackReportPlaying, playbackReportStart, playbackReportStopped, -} from './playbackReportSession'; -import { resolveQueueTrack } from '../utils/library/queueTrackView'; +} from '@/features/playback/store/playbackReportSession'; +import { resolveQueueTrack } from '@/utils/library/queueTrackView'; import { invoke } from '@tauri-apps/api/core'; -import { getMusicNetworkRuntimeOrNull } from '../music-network'; -import { setDeferHotCachePrefetch } from '../utils/cache/hotCacheGate'; -import { notifyLibraryPlaybackHint } from './libraryPlaybackHint'; +import { getMusicNetworkRuntimeOrNull } from '@/music-network'; +import { setDeferHotCachePrefetch } from '@/utils/cache/hotCacheGate'; +import { notifyLibraryPlaybackHint } from '@/store/libraryPlaybackHint'; import { playListenSessionFinalize, playListenSessionOnProgress, playListenSessionOnTrackSwitched, playListenSessionOpen, -} from './playListenSession'; -import { appendTimelineLeaveTrack } from './timelineSessionHistory'; -import { getPerfProbeFlags } from '../utils/perf/perfFlags'; -import { bumpPerfCounter } from '../utils/perf/perfTelemetry'; +} 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 { getPlaybackCacheServerKey, getPlaybackIndexKey, playbackCacheKeyForRef, playbackProfileIdForRef, playbackProfileIdForTrack, -} from '../utils/playback/playbackServer'; -import { resolvePlaybackUrl } from '../utils/playback/resolvePlaybackUrl'; -import { resolveReplayGainDb } from '../utils/audio/resolveReplayGainDb'; -import { audioPlayHiResBlendArgs } from '../utils/audio/hiResCrossfadeResample'; -import { showToast } from '../utils/ui/toast'; -import { useAuthStore } from './authStore'; -import { getPlayGeneration, setIsAudioPaused } from './engineState'; +} from '@/features/playback/utils/playback/playbackServer'; +import { resolvePlaybackUrl } from '@/features/playback/utils/playback/resolvePlaybackUrl'; +import { resolveReplayGainDb } from '@/features/playback/utils/audio/resolveReplayGainDb'; +import { audioPlayHiResBlendArgs } from '@/utils/audio/hiResCrossfadeResample'; +import { showToast } from '@/utils/ui/toast'; +import { useAuthStore } from '@/store/authStore'; +import { getPlayGeneration, setIsAudioPaused } from '@/features/playback/store/engineState'; import { clearPreloadingIds, getBytePreloadingId, @@ -40,19 +40,19 @@ import { markGaplessSwitch, setBytePreloadingId, setGaplessPreloadingId, -} from './gaplessPreloadState'; -import { touchHotCacheOnPlayback } from './hotCacheTouch'; +} from '@/features/playback/store/gaplessPreloadState'; +import { touchHotCacheOnPlayback } from '@/features/playback/store/hotCacheTouch'; import { isReplayGainActive, loudnessGainDbForEngineBind, -} from './loudnessGainCache'; -import { refreshLoudnessForTrack } from './loudnessRefresh'; -import { deriveNormalizationSnapshot } from './normalizationSnapshot'; -import { emitNormalizationDebug } from './normalizationDebug'; +} from '@/features/playback/store/loudnessGainCache'; +import { refreshLoudnessForTrack } from '@/features/playback/store/loudnessRefresh'; +import { deriveNormalizationSnapshot } from '@/features/playback/store/normalizationSnapshot'; +import { emitNormalizationDebug } from '@/features/playback/store/normalizationDebug'; import { emitPlaybackProgress, getPlaybackProgressSnapshot, -} from './playbackProgress'; +} from '@/features/playback/store/playbackProgress'; import { LIVE_PROGRESS_EMIT_MIN_DELTA_SEC, LIVE_PROGRESS_EMIT_MIN_MS, @@ -63,44 +63,44 @@ import { markLiveProgressEmit, markStoreProgressCommit, resetProgressEmitThrottles, -} from './playbackThrottles'; +} from '@/features/playback/store/playbackThrottles'; import { playbackSourceHintForResolvedUrl, -} from './playbackUrlRouting'; -import { usePlayerStore } from './playerStore'; -import { promoteCompletedStreamToHotCache } from './promoteStreamCache'; +} from '@/features/playback/store/playbackUrlRouting'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; +import { promoteCompletedStreamToHotCache } from '@/features/playback/store/promoteStreamCache'; import { flushQueueSyncToServer, getLastQueueHeartbeatAt, syncQueueToServer, -} from './queueSync'; -import { clearQueueNaturallyEnded } from './queuePlaybackIdle'; -import { isSeekDebouncePending } from './seekDebounce'; +} from '@/features/playback/store/queueSync'; +import { clearQueueNaturallyEnded } from '@/features/playback/store/queuePlaybackIdle'; +import { isSeekDebouncePending } from '@/features/playback/store/seekDebounce'; import { SEEK_FALLBACK_VISUAL_GUARD_MS, getSeekFallbackVisualTarget, setSeekFallbackVisualTarget, -} from './seekFallbackState'; +} from '@/features/playback/store/seekFallbackState'; import { SEEK_TARGET_GUARD_TIMEOUT_MS, clearSeekTarget, getSeekTarget, getSeekTargetSetAt, -} from './seekTargetState'; -import { refreshWaveformForTrack } from '@/store/waveformRefresh'; -import { analyzeBoundary, computeWaveformSilence } from '../utils/waveform/waveformSilence'; -import { autodjMaxOverlapCapSec } from '../utils/playback/autodjOverlapCap'; +} from '@/features/playback/store/seekTargetState'; +import { refreshWaveformForTrack } from '@/features/playback/store/waveformRefresh'; +import { analyzeBoundary, computeWaveformSilence } from '@/utils/waveform/waveformSilence'; +import { autodjMaxOverlapCapSec } from '@/utils/playback/autodjOverlapCap'; import { autodjJsTriggerAtSec, clampCrossfadeSecs, computeAutodjJsOverlap, nextQueueRefForTransition, shouldJsDriveAutodjTransition, -} from '../utils/playback/autodjAutoAdvance'; -import { isInterruptHandoffPending } from '../utils/playback/autodjInterruptPrep'; -import { isCrossfadeNextReady, maybeCrossfadeBytePreload } from './crossfadePreload'; -import { armCrossfadeDynamicOverlap, getCrossfadeTransition } from './crossfadeTrimCache'; -import { armAutodjMixing } from './autodjTransitionUi'; +} from '@/features/playback/utils/playback/autodjAutoAdvance'; +import { isInterruptHandoffPending } from '@/features/playback/utils/playback/autodjInterruptPrep'; +import { isCrossfadeNextReady, maybeCrossfadeBytePreload } from '@/features/playback/store/crossfadePreload'; +import { armCrossfadeDynamicOverlap, getCrossfadeTransition } from '@/features/playback/store/crossfadeTrimCache'; +import { armAutodjMixing } from '@/features/playback/store/autodjTransitionUi'; // Silence-aware crossfade (A-tail): guards the early advance to once per play // generation so a single playback instance triggers at most one trim-advance diff --git a/src/store/audioListenerSetup/audioEngineListeners.ts b/src/features/playback/store/audioListenerSetup/audioEngineListeners.ts similarity index 87% rename from src/store/audioListenerSetup/audioEngineListeners.ts rename to src/features/playback/store/audioListenerSetup/audioEngineListeners.ts index 3e306f66..81dc6834 100644 --- a/src/store/audioListenerSetup/audioEngineListeners.ts +++ b/src/features/playback/store/audioListenerSetup/audioEngineListeners.ts @@ -1,7 +1,7 @@ import { listen } from '@tauri-apps/api/event'; -import { streamUrlTrackId } from '../../utils/playback/resolvePlaybackUrl'; -import { normalizationAlmostEqual } from '../../utils/audio/normalizationCompare'; -import { normalizeAnalysisTrackId } from '../../utils/playback/queueIdentity'; +import { streamUrlTrackId } from '@/features/playback/utils/playback/resolvePlaybackUrl'; +import { normalizationAlmostEqual } from '@/features/playback/utils/audio/normalizationCompare'; +import { normalizeAnalysisTrackId } from '@/features/playback/utils/playback/queueIdentity'; import { handleAudioEnded, handleAudioError, @@ -9,24 +9,24 @@ import { handleAudioProgress, handleAudioTrackSwitched, type NormalizationStatePayload, -} from '../audioEventHandlers'; +} from '@/features/playback/store/audioEventHandlers'; import { getCachedLoudnessGain, hasStableLoudness, setCachedLoudnessGain, -} from '../loudnessGainCache'; -import { isTrackInsideLoudnessBackfillWindow } from '../loudnessBackfillWindow'; -import { refreshLoudnessForTrack } from '../loudnessRefresh'; -import { emitNormalizationDebug } from '../normalizationDebug'; +} from '@/features/playback/store/loudnessGainCache'; +import { isTrackInsideLoudnessBackfillWindow } from '@/features/playback/store/loudnessBackfillWindow'; +import { refreshLoudnessForTrack } from '@/features/playback/store/loudnessRefresh'; +import { emitNormalizationDebug } from '@/features/playback/store/normalizationDebug'; import { NORMALIZATION_UI_THROTTLE_MS, getLastNormalizationUiUpdateAtMs, markNormalizationUiUpdate, -} from '../playbackThrottles'; -import { usePlayerStore } from '../playerStore'; -import { refreshWaveformForTrack } from '@/store/waveformRefresh'; -import { bumpWaveformRefreshGen } from '@/store/waveformRefreshGen'; -import { setBytePreloadingId } from '../gaplessPreloadState'; +} from '@/features/playback/store/playbackThrottles'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; +import { refreshWaveformForTrack } from '@/features/playback/store/waveformRefresh'; +import { bumpWaveformRefreshGen } from '@/features/playback/store/waveformRefreshGen'; +import { setBytePreloadingId } from '@/features/playback/store/gaplessPreloadState'; type PreloadEventPayload = { url: string; diff --git a/src/store/audioListenerSetup/authSyncListener.ts b/src/features/playback/store/audioListenerSetup/authSyncListener.ts similarity index 80% rename from src/store/audioListenerSetup/authSyncListener.ts rename to src/features/playback/store/audioListenerSetup/authSyncListener.ts index 2b247cc9..c443acf4 100644 --- a/src/store/audioListenerSetup/authSyncListener.ts +++ b/src/features/playback/store/audioListenerSetup/authSyncListener.ts @@ -1,14 +1,14 @@ import { invoke } from '@tauri-apps/api/core'; -import { effectiveLoudnessPreAnalysisAttenuationDb } from '../../utils/audio/loudnessPreAnalysisSlider'; -import { useAuthStore } from '../authStore'; -import { onAnalysisStorageChanged } from '../analysisSync'; -import { emitNormalizationDebug } from '../normalizationDebug'; -import { invokeAudioSetNormalizationDeduped } from '../normalizationIpcDedupe'; -import { usePlayerStore } from '../playerStore'; -import { clearLoudnessCacheStateForTrackId } from '../loudnessGainCache'; -import { refreshLoudnessForTrack } from '../loudnessRefresh'; -import { refreshWaveformForTrack } from '@/store/waveformRefresh'; -import { bumpWaveformRefreshGen } from '@/store/waveformRefreshGen'; +import { effectiveLoudnessPreAnalysisAttenuationDb } from '@/utils/audio/loudnessPreAnalysisSlider'; +import { useAuthStore } from '@/store/authStore'; +import { onAnalysisStorageChanged } from '@/store/analysisSync'; +import { emitNormalizationDebug } from '@/features/playback/store/normalizationDebug'; +import { invokeAudioSetNormalizationDeduped } from '@/features/playback/store/normalizationIpcDedupe'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; +import { clearLoudnessCacheStateForTrackId } from '@/features/playback/store/loudnessGainCache'; +import { refreshLoudnessForTrack } from '@/features/playback/store/loudnessRefresh'; +import { refreshWaveformForTrack } from '@/features/playback/store/waveformRefresh'; +import { bumpWaveformRefreshGen } from '@/features/playback/store/waveformRefreshGen'; /** * Keeps the Rust audio engine in sync whenever the auth store changes diff --git a/src/store/audioListenerSetup/discordPresence.ts b/src/features/playback/store/audioListenerSetup/discordPresence.ts similarity index 91% rename from src/store/audioListenerSetup/discordPresence.ts rename to src/features/playback/store/audioListenerSetup/discordPresence.ts index f54bb811..218097d7 100644 --- a/src/store/audioListenerSetup/discordPresence.ts +++ b/src/features/playback/store/audioListenerSetup/discordPresence.ts @@ -1,10 +1,10 @@ import { invoke } from '@tauri-apps/api/core'; -import { resolvePlaybackCoverScope } from '../../cover/ref'; -import { resolveTrackCoverRefFromLibrary } from '../../cover/resolveEntryLibrary'; -import { coverArtUrlForDiscord } from '../../cover/integrations/discord'; -import { useAuthStore } from '../authStore'; -import { usePlayerStore } from '../playerStore'; -import { getPlaybackProgressSnapshot } from '../playbackProgress'; +import { resolvePlaybackCoverScope } from '@/cover/ref'; +import { resolveTrackCoverRefFromLibrary } from '@/cover/resolveEntryLibrary'; +import { coverArtUrlForDiscord } from '@/cover/integrations/discord'; +import { useAuthStore } from '@/store/authStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; +import { getPlaybackProgressSnapshot } from '@/features/playback/store/playbackProgress'; /** * Discord Rich Presence sync. Updates on track change or play/pause toggle — diff --git a/src/store/audioListenerSetup/eqDeviceSync.test.ts b/src/features/playback/store/audioListenerSetup/eqDeviceSync.test.ts similarity index 96% rename from src/store/audioListenerSetup/eqDeviceSync.test.ts rename to src/features/playback/store/audioListenerSetup/eqDeviceSync.test.ts index 394f9685..1306aa39 100644 --- a/src/store/audioListenerSetup/eqDeviceSync.test.ts +++ b/src/features/playback/store/audioListenerSetup/eqDeviceSync.test.ts @@ -3,9 +3,9 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; const { invokeMock } = vi.hoisted(() => ({ invokeMock: vi.fn(async () => null as unknown) })); vi.mock('@tauri-apps/api/core', () => ({ invoke: invokeMock })); -import { useEqStore, type EqSnapshot } from '../eqStore'; -import { useAuthStore } from '../authStore'; -import { setupEqDeviceSync } from './eqDeviceSync'; +import { useEqStore, type EqSnapshot } from '@/store/eqStore'; +import { useAuthStore } from '@/store/authStore'; +import { setupEqDeviceSync } from '@/features/playback/store/audioListenerSetup/eqDeviceSync'; const FLAT = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; diff --git a/src/store/audioListenerSetup/eqDeviceSync.ts b/src/features/playback/store/audioListenerSetup/eqDeviceSync.ts similarity index 96% rename from src/store/audioListenerSetup/eqDeviceSync.ts rename to src/features/playback/store/audioListenerSetup/eqDeviceSync.ts index e870055b..7c1ad3f5 100644 --- a/src/store/audioListenerSetup/eqDeviceSync.ts +++ b/src/features/playback/store/audioListenerSetup/eqDeviceSync.ts @@ -1,5 +1,5 @@ -import { useAuthStore } from '../authStore'; -import { useEqStore, type EqSnapshot } from '../eqStore'; +import { useAuthStore } from '@/store/authStore'; +import { useEqStore, type EqSnapshot } from '@/store/eqStore'; /** Key used when no specific device is selected (system default). */ const DEFAULT_DEVICE_KEY = '__default__'; diff --git a/src/store/audioListenerSetup/initialAudioSync.ts b/src/features/playback/store/audioListenerSetup/initialAudioSync.ts similarity index 78% rename from src/store/audioListenerSetup/initialAudioSync.ts rename to src/features/playback/store/audioListenerSetup/initialAudioSync.ts index a8fc253d..94a89685 100644 --- a/src/store/audioListenerSetup/initialAudioSync.ts +++ b/src/features/playback/store/audioListenerSetup/initialAudioSync.ts @@ -1,11 +1,11 @@ import { invoke } from '@tauri-apps/api/core'; -import { effectiveLoudnessPreAnalysisAttenuationDb } from '../../utils/audio/loudnessPreAnalysisSlider'; -import { useAuthStore } from '../authStore'; -import { emitNormalizationDebug } from '../normalizationDebug'; -import { invokeAudioSetNormalizationDeduped } from '../normalizationIpcDedupe'; -import { usePlayerStore } from '../playerStore'; -import { refreshLoudnessForTrack } from '../loudnessRefresh'; -import { refreshWaveformForTrack } from '@/store/waveformRefresh'; +import { effectiveLoudnessPreAnalysisAttenuationDb } from '@/utils/audio/loudnessPreAnalysisSlider'; +import { useAuthStore } from '@/store/authStore'; +import { emitNormalizationDebug } from '@/features/playback/store/normalizationDebug'; +import { invokeAudioSetNormalizationDeduped } from '@/features/playback/store/normalizationIpcDedupe'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; +import { refreshLoudnessForTrack } from '@/features/playback/store/loudnessRefresh'; +import { refreshWaveformForTrack } from '@/features/playback/store/waveformRefresh'; /** * One-shot startup sync: pushes the persisted audio settings to the Rust engine diff --git a/src/store/audioListenerSetup/mprisSync.ts b/src/features/playback/store/audioListenerSetup/mprisSync.ts similarity index 91% rename from src/store/audioListenerSetup/mprisSync.ts rename to src/features/playback/store/audioListenerSetup/mprisSync.ts index 5c913e61..b0443d96 100644 --- a/src/store/audioListenerSetup/mprisSync.ts +++ b/src/features/playback/store/audioListenerSetup/mprisSync.ts @@ -1,9 +1,9 @@ import { invoke } from '@tauri-apps/api/core'; -import { resolvePlaybackCoverScope } from '../../cover/ref'; -import { resolveTrackCoverRefFromLibrary } from '../../cover/resolveEntryLibrary'; -import { coverArtUrlForMpris } from '../../cover/integrations/mpris'; -import { usePlayerStore } from '../playerStore'; -import { getPlaybackProgressSnapshot, subscribePlaybackProgress } from '../playbackProgress'; +import { resolvePlaybackCoverScope } from '@/cover/ref'; +import { resolveTrackCoverRefFromLibrary } from '@/cover/resolveEntryLibrary'; +import { coverArtUrlForMpris } from '@/cover/integrations/mpris'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; +import { getPlaybackProgressSnapshot, subscribePlaybackProgress } from '@/features/playback/store/playbackProgress'; /** * MPRIS / OS media-controls sync. Whenever the current track or playback state diff --git a/src/store/audioListenerSetup/radioMprisMetadata.ts b/src/features/playback/store/audioListenerSetup/radioMprisMetadata.ts similarity index 95% rename from src/store/audioListenerSetup/radioMprisMetadata.ts rename to src/features/playback/store/audioListenerSetup/radioMprisMetadata.ts index 81b0d9ed..0d5b9f66 100644 --- a/src/store/audioListenerSetup/radioMprisMetadata.ts +++ b/src/features/playback/store/audioListenerSetup/radioMprisMetadata.ts @@ -1,6 +1,6 @@ import { invoke } from '@tauri-apps/api/core'; import { listen } from '@tauri-apps/api/event'; -import { usePlayerStore } from '../playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; /** * Radio ICY StreamTitle → MPRIS. The Rust download task emits "radio:metadata" diff --git a/src/store/autodjTransitionUi.test.ts b/src/features/playback/store/autodjTransitionUi.test.ts similarity index 85% rename from src/store/autodjTransitionUi.test.ts rename to src/features/playback/store/autodjTransitionUi.test.ts index 85cf6776..0b7b635a 100644 --- a/src/store/autodjTransitionUi.test.ts +++ b/src/features/playback/store/autodjTransitionUi.test.ts @@ -1,10 +1,10 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { setTransitionMode } from '../utils/playback/playbackTransition'; +import { setTransitionMode } from '@/features/playback/utils/playback/playbackTransition'; import { armAutodjMixing, clearAutodjTransitionUi, useAutodjTransitionUi, -} from './autodjTransitionUi'; +} from '@/features/playback/store/autodjTransitionUi'; describe('autodjTransitionUi', () => { beforeEach(() => { diff --git a/src/store/autodjTransitionUi.ts b/src/features/playback/store/autodjTransitionUi.ts similarity index 90% rename from src/store/autodjTransitionUi.ts rename to src/features/playback/store/autodjTransitionUi.ts index 16ce108c..6692453b 100644 --- a/src/store/autodjTransitionUi.ts +++ b/src/features/playback/store/autodjTransitionUi.ts @@ -1,6 +1,6 @@ import { create } from 'zustand'; -import { useAuthStore } from './authStore'; -import { getTransitionMode } from '../utils/playback/playbackTransition'; +import { useAuthStore } from '@/store/authStore'; +import { getTransitionMode } from '@/features/playback/utils/playback/playbackTransition'; /** User-visible AutoDJ transition feedback on the player-bar play button. */ export type AutodjTransitionPhase = 'idle' | 'mixing'; diff --git a/src/store/b1QueueServerIdentity.test.ts b/src/features/playback/store/b1QueueServerIdentity.test.ts similarity index 97% rename from src/store/b1QueueServerIdentity.test.ts rename to src/features/playback/store/b1QueueServerIdentity.test.ts index f90b7ef0..93632630 100644 --- a/src/store/b1QueueServerIdentity.test.ts +++ b/src/features/playback/store/b1QueueServerIdentity.test.ts @@ -13,24 +13,24 @@ import { describe, it, expect, beforeEach, vi } from 'vitest'; import { onInvoke } from '@/test/mocks/tauri'; import { useAuthStore } from '@/store/authStore'; -import { usePlayerStore } from '@/store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { useLibraryIndexStore } from '@/store/libraryIndexStore'; import { resetAuthStore, resetPlayerStore } from '@/test/helpers/storeReset'; import { toQueueItemRefs } from '@/utils/library/queueItemRef'; -import { bindQueueServerForPlayback } from '@/utils/playback/playbackServer'; +import { bindQueueServerForPlayback } from '@/features/playback/utils/playback/playbackServer'; import { _resetQueueResolverForTest, getCachedTrack, seedQueueResolver, } from '@/utils/library/queueTrackResolver'; -import { applyQueueHistorySnapshot } from '@/store/applyQueueHistorySnapshot'; +import { applyQueueHistorySnapshot } from '@/features/playback/store/applyQueueHistorySnapshot'; import { pushQueueUndoSnapshot, type QueueUndoSnapshot, -} from '@/store/queueUndo'; -import type { PlayerState, QueueItemRef, Track } from '@/store/playerStoreTypes'; +} from '@/features/playback/store/queueUndo'; +import type { PlayerState, QueueItemRef, Track } from '@/features/playback/store/playerStoreTypes'; import { savePlayQueue } from '@/lib/api/subsonicPlayQueue'; -import { _resetQueueSyncForTest, flushPlayQueuePosition } from '@/store/queueSync'; +import { _resetQueueSyncForTest, flushPlayQueuePosition } from '@/features/playback/store/queueSync'; vi.mock('@/lib/api/subsonicPlayQueue', () => ({ savePlayQueue: vi.fn(async () => undefined), diff --git a/src/store/crossfadePreload.ts b/src/features/playback/store/crossfadePreload.ts similarity index 89% rename from src/store/crossfadePreload.ts rename to src/features/playback/store/crossfadePreload.ts index cb2a9726..19357a44 100644 --- a/src/store/crossfadePreload.ts +++ b/src/features/playback/store/crossfadePreload.ts @@ -1,21 +1,21 @@ import { invoke } from '@tauri-apps/api/core'; -import { useAuthStore } from './authStore'; -import { autodjMaxOverlapCapSec } from '../utils/playback/autodjOverlapCap'; -import { computeWaveformSilence, planCrossfadeTransition } from '../utils/waveform/waveformSilence'; +import { useAuthStore } from '@/store/authStore'; +import { autodjMaxOverlapCapSec } from '@/utils/playback/autodjOverlapCap'; +import { computeWaveformSilence, planCrossfadeTransition } from '@/utils/waveform/waveformSilence'; import { findLocalPlaybackUrl } from '@/store/localPlaybackResolve'; -import { playbackCacheKeyForRef } from '../utils/playback/playbackServer'; -import { resolvePlaybackUrl } from '../utils/playback/resolvePlaybackUrl'; -import { resolveQueueTrack } from '../utils/library/queueTrackView'; -import type { Track } from './playerStoreTypes'; +import { playbackCacheKeyForRef } from '@/features/playback/utils/playback/playbackServer'; +import { resolvePlaybackUrl } from '@/features/playback/utils/playback/resolvePlaybackUrl'; +import { resolveQueueTrack } from '@/utils/library/queueTrackView'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; import { hasPlannedCrossfade, markPlannedCrossfade, setCrossfadeTransition, -} from './crossfadeTrimCache'; -import { getBytePreloadingId, setBytePreloadingId } from './gaplessPreloadState'; -import { refreshLoudnessForTrack } from './loudnessRefresh'; -import { usePlayerStore } from './playerStore'; -import { fetchWaveformBins } from '@/store/waveformRefresh'; +} from '@/features/playback/store/crossfadeTrimCache'; +import { getBytePreloadingId, setBytePreloadingId } from '@/features/playback/store/gaplessPreloadState'; +import { refreshLoudnessForTrack } from '@/features/playback/store/loudnessRefresh'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; +import { fetchWaveformBins } from '@/features/playback/store/waveformRefresh'; // Crossfade pre-buffer budget: begin downloading the next track this many // seconds before it needs to play (the crossfade start), so a large lossless diff --git a/src/store/crossfadeTrimCache.test.ts b/src/features/playback/store/crossfadeTrimCache.test.ts similarity index 98% rename from src/store/crossfadeTrimCache.test.ts rename to src/features/playback/store/crossfadeTrimCache.test.ts index da7bb8ae..4e52c0bb 100644 --- a/src/store/crossfadeTrimCache.test.ts +++ b/src/features/playback/store/crossfadeTrimCache.test.ts @@ -8,7 +8,7 @@ import { hasPlannedCrossfade, markPlannedCrossfade, setCrossfadeTransition, -} from './crossfadeTrimCache'; +} from '@/features/playback/store/crossfadeTrimCache'; describe('crossfadeTrimCache', () => { beforeEach(() => _resetCrossfadeTrimCacheForTest()); diff --git a/src/store/crossfadeTrimCache.ts b/src/features/playback/store/crossfadeTrimCache.ts similarity index 96% rename from src/store/crossfadeTrimCache.ts rename to src/features/playback/store/crossfadeTrimCache.ts index 4930b7be..0815185e 100644 --- a/src/store/crossfadeTrimCache.ts +++ b/src/features/playback/store/crossfadeTrimCache.ts @@ -10,9 +10,9 @@ * Kept out of the persisted Zustand store on purpose: this is ephemeral, * per-transition playback data, not user state. */ -import type { CrossfadeTransitionPlan } from '../utils/waveform/waveformSilence'; +import type { CrossfadeTransitionPlan } from '@/utils/waveform/waveformSilence'; -export type { CrossfadeTransitionPlan } from '../utils/waveform/waveformSilence'; +export type { CrossfadeTransitionPlan } from '@/utils/waveform/waveformSilence'; /** trackId → planned transition for when this track starts under crossfade. */ const planByTrackId = new Map(); diff --git a/src/store/engineLoadTrackAtPosition.ts b/src/features/playback/store/engineLoadTrackAtPosition.ts similarity index 79% rename from src/store/engineLoadTrackAtPosition.ts rename to src/features/playback/store/engineLoadTrackAtPosition.ts index 98ca82f6..a802d4c7 100644 --- a/src/store/engineLoadTrackAtPosition.ts +++ b/src/features/playback/store/engineLoadTrackAtPosition.ts @@ -1,19 +1,19 @@ -import type { Track } from './playerStoreTypes'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; import { invoke } from '@tauri-apps/api/core'; -import { setDeferHotCachePrefetch } from '../utils/cache/hotCacheGate'; +import { setDeferHotCachePrefetch } from '@/utils/cache/hotCacheGate'; import { getPlaybackIndexKey, playbackCacheKeyForTrack, -} from '../utils/playback/playbackServer'; -import { resolvePlaybackUrl } from '../utils/playback/resolvePlaybackUrl'; -import { resolveReplayGainDb } from '../utils/audio/resolveReplayGainDb'; -import { audioPlayHiResBlendArgs } from '../utils/audio/hiResCrossfadeResample'; -import { useAuthStore } from './authStore'; -import { getPlayGeneration, setIsAudioPaused } from './engineState'; -import { touchHotCacheOnPlayback } from './hotCacheTouch'; -import { isReplayGainActive, loudnessGainDbForEngineBind } from './loudnessGainCache'; -import { playbackSourceHintForResolvedUrl, recordEnginePlayUrl } from './playbackUrlRouting'; -import { usePlayerStore } from './playerStore'; +} from '@/features/playback/utils/playback/playbackServer'; +import { resolvePlaybackUrl } from '@/features/playback/utils/playback/resolvePlaybackUrl'; +import { resolveReplayGainDb } from '@/features/playback/utils/audio/resolveReplayGainDb'; +import { audioPlayHiResBlendArgs } from '@/utils/audio/hiResCrossfadeResample'; +import { useAuthStore } from '@/store/authStore'; +import { getPlayGeneration, setIsAudioPaused } from '@/features/playback/store/engineState'; +import { touchHotCacheOnPlayback } from '@/features/playback/store/hotCacheTouch'; +import { isReplayGainActive, loudnessGainDbForEngineBind } from '@/features/playback/store/loudnessGainCache'; +import { playbackSourceHintForResolvedUrl, recordEnginePlayUrl } from '@/features/playback/store/playbackUrlRouting'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; /** * Load a track into the Rust engine at `atSeconds`, optionally leaving transport diff --git a/src/store/engineState.test.ts b/src/features/playback/store/engineState.test.ts similarity index 96% rename from src/store/engineState.test.ts rename to src/features/playback/store/engineState.test.ts index f73f6c82..e7a72fef 100644 --- a/src/store/engineState.test.ts +++ b/src/features/playback/store/engineState.test.ts @@ -5,7 +5,7 @@ import { getIsAudioPaused, getPlayGeneration, setIsAudioPaused, -} from './engineState'; +} from '@/features/playback/store/engineState'; afterEach(() => { _resetEngineStateForTest(); diff --git a/src/store/engineState.ts b/src/features/playback/store/engineState.ts similarity index 100% rename from src/store/engineState.ts rename to src/features/playback/store/engineState.ts diff --git a/src/store/gaplessPreloadState.test.ts b/src/features/playback/store/gaplessPreloadState.test.ts similarity index 97% rename from src/store/gaplessPreloadState.test.ts rename to src/features/playback/store/gaplessPreloadState.test.ts index 63485283..f453c752 100644 --- a/src/store/gaplessPreloadState.test.ts +++ b/src/features/playback/store/gaplessPreloadState.test.ts @@ -13,7 +13,7 @@ import { markGaplessSwitch, setBytePreloadingId, setGaplessPreloadingId, -} from './gaplessPreloadState'; +} from '@/features/playback/store/gaplessPreloadState'; beforeEach(() => { vi.useFakeTimers(); diff --git a/src/store/gaplessPreloadState.ts b/src/features/playback/store/gaplessPreloadState.ts similarity index 100% rename from src/store/gaplessPreloadState.ts rename to src/features/playback/store/gaplessPreloadState.ts diff --git a/src/store/hotCacheStore.count.test.ts b/src/features/playback/store/hotCacheStore.count.test.ts similarity index 87% rename from src/store/hotCacheStore.count.test.ts rename to src/features/playback/store/hotCacheStore.count.test.ts index b06af157..c4a23e70 100644 --- a/src/store/hotCacheStore.count.test.ts +++ b/src/features/playback/store/hotCacheStore.count.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; -import type { LocalPlaybackEntry } from './localPlaybackStore'; -import { countHotCacheTracks } from './hotCacheStore'; +import type { LocalPlaybackEntry } from '@/store/localPlaybackStore'; +import { countHotCacheTracks } from '@/features/playback/store/hotCacheStore'; function ephemeral( serverIndexKey: string, diff --git a/src/store/hotCacheStore.ts b/src/features/playback/store/hotCacheStore.ts similarity index 88% rename from src/store/hotCacheStore.ts rename to src/features/playback/store/hotCacheStore.ts index 8d706d82..d99bebe8 100644 --- a/src/store/hotCacheStore.ts +++ b/src/features/playback/store/hotCacheStore.ts @@ -1,12 +1,12 @@ -import type { QueueItemRef } from './playerStoreTypes'; +import type { QueueItemRef } from '@/features/playback/store/playerStoreTypes'; import { create } from 'zustand'; -import type { HotCacheEntry } from './hotCacheStoreTypes'; -import { useLocalPlaybackStore, type LocalPlaybackEntry } from './localPlaybackStore'; +import type { HotCacheEntry } from '@/features/playback/store/hotCacheStoreTypes'; +import { useLocalPlaybackStore, type LocalPlaybackEntry } from '@/store/localPlaybackStore'; import { entryBelongsToServer } from '@/store/localPlaybackResolve'; import { invoke } from '@tauri-apps/api/core'; -import { getMediaDir } from '../utils/media/mediaDir'; +import { getMediaDir } from '@/utils/media/mediaDir'; -export type { HotCacheEntry } from './hotCacheStoreTypes'; +export type { HotCacheEntry } from '@/features/playback/store/hotCacheStoreTypes'; /** @deprecated Use {@link LOCAL_PLAYBACK_PROTECT_AFTER_CURRENT}. */ export const HOT_CACHE_PROTECT_AFTER_CURRENT = 1; @@ -36,7 +36,7 @@ interface HotCacheState { /** Ephemeral-tier view for UI selectors (Settings track count, prefetch helpers). */ export function selectHotCacheEntries( - entries: Record, + entries: Record, ): Record { const out: Record = {}; for (const [key, e] of Object.entries(entries)) { diff --git a/src/store/hotCacheStoreTypes.ts b/src/features/playback/store/hotCacheStoreTypes.ts similarity index 100% rename from src/store/hotCacheStoreTypes.ts rename to src/features/playback/store/hotCacheStoreTypes.ts diff --git a/src/store/hotCacheTouch.test.ts b/src/features/playback/store/hotCacheTouch.test.ts similarity index 84% rename from src/store/hotCacheTouch.test.ts rename to src/features/playback/store/hotCacheTouch.test.ts index c46f4a01..ae83304c 100644 --- a/src/store/hotCacheTouch.test.ts +++ b/src/features/playback/store/hotCacheTouch.test.ts @@ -4,11 +4,11 @@ const { touchPlayedMock } = vi.hoisted(() => ({ touchPlayedMock: vi.fn(), })); -vi.mock('./hotCacheStore', () => ({ +vi.mock('@/features/playback/store/hotCacheStore', () => ({ useHotCacheStore: { getState: () => ({ touchPlayed: touchPlayedMock }) }, })); -import { touchHotCacheOnPlayback } from './hotCacheTouch'; +import { touchHotCacheOnPlayback } from '@/features/playback/store/hotCacheTouch'; beforeEach(() => { touchPlayedMock.mockClear(); diff --git a/src/store/hotCacheTouch.ts b/src/features/playback/store/hotCacheTouch.ts similarity index 89% rename from src/store/hotCacheTouch.ts rename to src/features/playback/store/hotCacheTouch.ts index 78895891..7f95acc7 100644 --- a/src/store/hotCacheTouch.ts +++ b/src/features/playback/store/hotCacheTouch.ts @@ -1,4 +1,4 @@ -import { useHotCacheStore } from './hotCacheStore'; +import { useHotCacheStore } from '@/features/playback/store/hotCacheStore'; /** * Mark a track as recently played for the hot-cache LRU. Called from every diff --git a/src/store/infiniteQueueState.test.ts b/src/features/playback/store/infiniteQueueState.test.ts similarity index 93% rename from src/store/infiniteQueueState.test.ts rename to src/features/playback/store/infiniteQueueState.test.ts index ad591cdb..b02aa2f5 100644 --- a/src/store/infiniteQueueState.test.ts +++ b/src/features/playback/store/infiniteQueueState.test.ts @@ -3,7 +3,7 @@ import { _resetInfiniteQueueStateForTest, isInfiniteQueueFetching, setInfiniteQueueFetching, -} from './infiniteQueueState'; +} from '@/features/playback/store/infiniteQueueState'; afterEach(() => { _resetInfiniteQueueStateForTest(); diff --git a/src/store/infiniteQueueState.ts b/src/features/playback/store/infiniteQueueState.ts similarity index 100% rename from src/store/infiniteQueueState.ts rename to src/features/playback/store/infiniteQueueState.ts diff --git a/src/store/initAudioListeners.ts b/src/features/playback/store/initAudioListeners.ts similarity index 58% rename from src/store/initAudioListeners.ts rename to src/features/playback/store/initAudioListeners.ts index c1389881..f3d8538c 100644 --- a/src/store/initAudioListeners.ts +++ b/src/features/playback/store/initAudioListeners.ts @@ -1,10 +1,10 @@ -import { setupAudioEngineListeners } from './audioListenerSetup/audioEngineListeners'; -import { runInitialAudioSync } from './audioListenerSetup/initialAudioSync'; -import { setupAuthSync } from './audioListenerSetup/authSyncListener'; -import { setupMprisSync } from './audioListenerSetup/mprisSync'; -import { setupRadioMprisMetadata } from './audioListenerSetup/radioMprisMetadata'; -import { setupDiscordPresence } from './audioListenerSetup/discordPresence'; -import { setupEqDeviceSync } from './audioListenerSetup/eqDeviceSync'; +import { setupAudioEngineListeners } from '@/features/playback/store/audioListenerSetup/audioEngineListeners'; +import { runInitialAudioSync } from '@/features/playback/store/audioListenerSetup/initialAudioSync'; +import { setupAuthSync } from '@/features/playback/store/audioListenerSetup/authSyncListener'; +import { setupMprisSync } from '@/features/playback/store/audioListenerSetup/mprisSync'; +import { setupRadioMprisMetadata } from '@/features/playback/store/audioListenerSetup/radioMprisMetadata'; +import { setupDiscordPresence } from '@/features/playback/store/audioListenerSetup/discordPresence'; +import { setupEqDeviceSync } from '@/features/playback/store/audioListenerSetup/eqDeviceSync'; /** * Set up Tauri event listeners for the Rust audio engine. diff --git a/src/store/loudnessBackfillState.test.ts b/src/features/playback/store/loudnessBackfillState.test.ts similarity index 98% rename from src/store/loudnessBackfillState.test.ts rename to src/features/playback/store/loudnessBackfillState.test.ts index 0e601d36..e15751fa 100644 --- a/src/store/loudnessBackfillState.test.ts +++ b/src/features/playback/store/loudnessBackfillState.test.ts @@ -15,7 +15,7 @@ import { markBackfillInFlight, resetBackfillAttempts, resetLoudnessBackfillStateForTrackId, -} from './loudnessBackfillState'; +} from '@/features/playback/store/loudnessBackfillState'; afterEach(() => { _resetBackfillStateForTest(); diff --git a/src/store/loudnessBackfillState.ts b/src/features/playback/store/loudnessBackfillState.ts similarity index 96% rename from src/store/loudnessBackfillState.ts rename to src/features/playback/store/loudnessBackfillState.ts index 1f147f3e..207e88f5 100644 --- a/src/store/loudnessBackfillState.ts +++ b/src/features/playback/store/loudnessBackfillState.ts @@ -1,4 +1,4 @@ -import { loudnessCacheStateKeysForTrackId } from './loudnessGainCache'; +import { loudnessCacheStateKeysForTrackId } from '@/features/playback/store/loudnessGainCache'; /** * Bounded retry state for the per-track loudness backfill: each `refresh:miss` diff --git a/src/store/loudnessBackfillWindow.test.ts b/src/features/playback/store/loudnessBackfillWindow.test.ts similarity index 96% rename from src/store/loudnessBackfillWindow.test.ts rename to src/features/playback/store/loudnessBackfillWindow.test.ts index 39b7c5fa..138b0372 100644 --- a/src/store/loudnessBackfillWindow.test.ts +++ b/src/features/playback/store/loudnessBackfillWindow.test.ts @@ -4,13 +4,13 @@ * = current track + next `LOUDNESS_BACKFILL_WINDOW_AHEAD` entries, with * duplicates collapsed. */ -import type { QueueItemRef, Track } from './playerStoreTypes'; +import type { QueueItemRef, Track } from '@/features/playback/store/playerStoreTypes'; import { describe, expect, it } from 'vitest'; import { LOUDNESS_BACKFILL_WINDOW_AHEAD, collectLoudnessBackfillWindowTrackIds, isTrackInsideLoudnessBackfillWindow, -} from './loudnessBackfillWindow'; +} from '@/features/playback/store/loudnessBackfillWindow'; function track(id: string): Track { return { id, title: id, artist: 'A', album: 'X', albumId: 'X', duration: 100 }; diff --git a/src/store/loudnessBackfillWindow.ts b/src/features/playback/store/loudnessBackfillWindow.ts similarity index 96% rename from src/store/loudnessBackfillWindow.ts rename to src/features/playback/store/loudnessBackfillWindow.ts index e23a001f..2d488e6f 100644 --- a/src/store/loudnessBackfillWindow.ts +++ b/src/features/playback/store/loudnessBackfillWindow.ts @@ -1,4 +1,4 @@ -import type { QueueItemRef, Track } from './playerStoreTypes'; +import type { QueueItemRef, Track } from '@/features/playback/store/playerStoreTypes'; /** * After a bulk enqueue (queue replace, append-many, lucky-mix) the runtime * warms the loudness cache for the current track + the next N entries so diff --git a/src/store/loudnessGainCache.test.ts b/src/features/playback/store/loudnessGainCache.test.ts similarity index 98% rename from src/store/loudnessGainCache.test.ts rename to src/features/playback/store/loudnessGainCache.test.ts index 4a6365a0..4a7b904d 100644 --- a/src/store/loudnessGainCache.test.ts +++ b/src/features/playback/store/loudnessGainCache.test.ts @@ -16,7 +16,7 @@ const { authState } = vi.hoisted(() => ({ }, })); -vi.mock('./authStore', () => ({ +vi.mock('@/store/authStore', () => ({ useAuthStore: { getState: () => authState }, })); @@ -31,7 +31,7 @@ import { loudnessGainDbForEngineBind, markLoudnessStable, setCachedLoudnessGain, -} from './loudnessGainCache'; +} from '@/features/playback/store/loudnessGainCache'; beforeEach(() => { authState.normalizationEngine = 'off'; diff --git a/src/store/loudnessGainCache.ts b/src/features/playback/store/loudnessGainCache.ts similarity index 98% rename from src/store/loudnessGainCache.ts rename to src/features/playback/store/loudnessGainCache.ts index 5bbe448d..3b1586d0 100644 --- a/src/store/loudnessGainCache.ts +++ b/src/features/playback/store/loudnessGainCache.ts @@ -1,4 +1,4 @@ -import { useAuthStore } from './authStore'; +import { useAuthStore } from '@/store/authStore'; /** * In-memory cache of the per-track loudness normalization gain (dB). Two diff --git a/src/store/loudnessPrefetch.test.ts b/src/features/playback/store/loudnessPrefetch.test.ts similarity index 85% rename from src/store/loudnessPrefetch.test.ts rename to src/features/playback/store/loudnessPrefetch.test.ts index 5a305070..005773e5 100644 --- a/src/store/loudnessPrefetch.test.ts +++ b/src/features/playback/store/loudnessPrefetch.test.ts @@ -4,7 +4,7 @@ * guard, the window collection, and the no-sync-engine flag on each * refresh call. */ -import type { QueueItemRef, Track } from './playerStoreTypes'; +import type { QueueItemRef, Track } from '@/features/playback/store/playerStoreTypes'; import { beforeEach, describe, expect, it, vi } from 'vitest'; const hoisted = vi.hoisted(() => { const auth = { normalizationEngine: 'loudness' as 'off' | 'replaygain' | 'loudness' }; @@ -17,18 +17,18 @@ const hoisted = vi.hoisted(() => { }; }); -vi.mock('./authStore', () => ({ useAuthStore: { getState: () => hoisted.auth } })); -vi.mock('./playerStore', () => ({ +vi.mock('@/store/authStore', () => ({ useAuthStore: { getState: () => hoisted.auth } })); +vi.mock('@/features/playback/store/playerStore', () => ({ usePlayerStore: { getState: () => hoisted.player }, })); -vi.mock('./loudnessRefresh', () => ({ +vi.mock('@/features/playback/store/loudnessRefresh', () => ({ refreshLoudnessForTrack: hoisted.refreshMock, })); -vi.mock('./loudnessBackfillWindow', () => ({ +vi.mock('@/features/playback/store/loudnessBackfillWindow', () => ({ collectLoudnessBackfillWindowTrackIds: hoisted.collectMock, })); -import { prefetchLoudnessForEnqueuedTracks } from './loudnessPrefetch'; +import { prefetchLoudnessForEnqueuedTracks } from '@/features/playback/store/loudnessPrefetch'; function track(id: string): Track { return { id, title: id, artist: 'A', album: 'X', albumId: 'X', duration: 100 }; diff --git a/src/store/loudnessPrefetch.ts b/src/features/playback/store/loudnessPrefetch.ts similarity index 71% rename from src/store/loudnessPrefetch.ts rename to src/features/playback/store/loudnessPrefetch.ts index c0958f6f..2e0a1e82 100644 --- a/src/store/loudnessPrefetch.ts +++ b/src/features/playback/store/loudnessPrefetch.ts @@ -1,8 +1,8 @@ -import type { QueueItemRef } from './playerStoreTypes'; -import { useAuthStore } from './authStore'; -import { collectLoudnessBackfillWindowTrackIds } from './loudnessBackfillWindow'; -import { refreshLoudnessForTrack } from './loudnessRefresh'; -import { usePlayerStore } from './playerStore'; +import type { QueueItemRef } from '@/features/playback/store/playerStoreTypes'; +import { useAuthStore } from '@/store/authStore'; +import { collectLoudnessBackfillWindowTrackIds } from '@/features/playback/store/loudnessBackfillWindow'; +import { refreshLoudnessForTrack } from '@/features/playback/store/loudnessRefresh'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; /** * After a bulk enqueue (queue replace, append-many, lucky-mix) warm the * loudness cache for the current track + the next N entries so the diff --git a/src/store/loudnessRefresh.test.ts b/src/features/playback/store/loudnessRefresh.test.ts similarity index 93% rename from src/store/loudnessRefresh.test.ts rename to src/features/playback/store/loudnessRefresh.test.ts index e3035155..e7a25094 100644 --- a/src/store/loudnessRefresh.test.ts +++ b/src/features/playback/store/loudnessRefresh.test.ts @@ -39,20 +39,20 @@ const hoisted = vi.hoisted(() => { vi.mock('@tauri-apps/api/core', () => ({ invoke: hoisted.invokeMock })); vi.mock('@/lib/api/subsonicStreamUrl', () => ({ buildStreamUrl: hoisted.buildStreamUrlMock })); -vi.mock('../utils/server/redactSubsonicUrl', () => ({ redactSubsonicUrlForLog: hoisted.redactMock })); -vi.mock('./authStore', () => ({ useAuthStore: { getState: () => hoisted.auth } })); -vi.mock('./playerStore', () => ({ +vi.mock('@/utils/server/redactSubsonicUrl', () => ({ redactSubsonicUrlForLog: hoisted.redactMock })); +vi.mock('@/store/authStore', () => ({ useAuthStore: { getState: () => hoisted.auth } })); +vi.mock('@/features/playback/store/playerStore', () => ({ usePlayerStore: { getState: () => hoisted.playerState, setState: hoisted.playerSetStateMock, }, })); -vi.mock('./normalizationDebug', () => ({ emitNormalizationDebug: hoisted.emitDebugMock })); -vi.mock('./loudnessGainCache', () => ({ +vi.mock('@/features/playback/store/normalizationDebug', () => ({ emitNormalizationDebug: hoisted.emitDebugMock })); +vi.mock('@/features/playback/store/loudnessGainCache', () => ({ forgetLoudnessGain: hoisted.forgetLoudnessMock, markLoudnessStable: hoisted.markLoudnessStableMock, })); -vi.mock('./loudnessBackfillState', () => ({ +vi.mock('@/features/playback/store/loudnessBackfillState', () => ({ MAX_BACKFILL_ATTEMPTS_PER_TRACK: 2, clearBackfillInFlight: hoisted.clearBackfillInFlightMock, getBackfillAttempts: hoisted.getBackfillAttemptsMock, @@ -60,7 +60,7 @@ vi.mock('./loudnessBackfillState', () => ({ markBackfillInFlight: hoisted.markBackfillInFlightMock, resetBackfillAttempts: hoisted.resetBackfillAttemptsMock, })); -vi.mock('./loudnessBackfillWindow', () => ({ +vi.mock('@/features/playback/store/loudnessBackfillWindow', () => ({ LOUDNESS_BACKFILL_WINDOW_AHEAD: 5, isTrackInsideLoudnessBackfillWindow: hoisted.isTrackInsideWindowMock, loudnessBackfillPriorityForTrack: vi.fn(() => 'middle'), @@ -69,7 +69,7 @@ vi.mock('./loudnessBackfillWindow', () => ({ import { _resetLoudnessRefreshInflightForTest, refreshLoudnessForTrack, -} from './loudnessRefresh'; +} from '@/features/playback/store/loudnessRefresh'; beforeEach(() => { _resetLoudnessRefreshInflightForTest(); diff --git a/src/store/loudnessRefresh.ts b/src/features/playback/store/loudnessRefresh.ts similarity index 91% rename from src/store/loudnessRefresh.ts rename to src/features/playback/store/loudnessRefresh.ts index 5ed75e2b..575b0768 100644 --- a/src/store/loudnessRefresh.ts +++ b/src/features/playback/store/loudnessRefresh.ts @@ -1,14 +1,14 @@ import { buildStreamUrl } from '@/lib/api/subsonicStreamUrl'; import { invoke } from '@tauri-apps/api/core'; -import { getPlaybackIndexKey } from '../utils/playback/playbackServer'; -import { redactSubsonicUrlForLog } from '../utils/server/redactSubsonicUrl'; -import { useAuthStore } from './authStore'; -import { usePlayerStore } from './playerStore'; -import { emitNormalizationDebug } from './normalizationDebug'; +import { getPlaybackIndexKey } from '@/features/playback/utils/playback/playbackServer'; +import { redactSubsonicUrlForLog } from '@/utils/server/redactSubsonicUrl'; +import { useAuthStore } from '@/store/authStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; +import { emitNormalizationDebug } from '@/features/playback/store/normalizationDebug'; import { forgetLoudnessGain, markLoudnessStable, -} from './loudnessGainCache'; +} from '@/features/playback/store/loudnessGainCache'; import { MAX_BACKFILL_ATTEMPTS_PER_TRACK, clearBackfillInFlight, @@ -16,12 +16,12 @@ import { isBackfillInFlight, markBackfillInFlight, resetBackfillAttempts, -} from './loudnessBackfillState'; +} from '@/features/playback/store/loudnessBackfillState'; import { LOUDNESS_BACKFILL_WINDOW_AHEAD, isTrackInsideLoudnessBackfillWindow, loudnessBackfillPriorityForTrack, -} from './loudnessBackfillWindow'; +} from '@/features/playback/store/loudnessBackfillWindow'; /** Subsonic-server loudness-cache row as Rust hands it back. */ type LoudnessCachePayload = { diff --git a/src/store/loudnessReseed.test.ts b/src/features/playback/store/loudnessReseed.test.ts similarity index 92% rename from src/store/loudnessReseed.test.ts rename to src/features/playback/store/loudnessReseed.test.ts index 83ec07f9..485998fe 100644 --- a/src/store/loudnessReseed.test.ts +++ b/src/features/playback/store/loudnessReseed.test.ts @@ -32,24 +32,24 @@ const hoisted = vi.hoisted(() => { vi.mock('@tauri-apps/api/core', () => ({ invoke: hoisted.invokeMock })); vi.mock('@/lib/api/subsonicStreamUrl', () => ({ buildStreamUrl: hoisted.buildStreamUrlMock })); -vi.mock('./authStore', () => ({ useAuthStore: { getState: () => hoisted.authState } })); -vi.mock('./playerStore', () => ({ +vi.mock('@/store/authStore', () => ({ useAuthStore: { getState: () => hoisted.authState } })); +vi.mock('@/features/playback/store/playerStore', () => ({ usePlayerStore: { getState: () => hoisted.playerSnapshot, setState: hoisted.playerSetStateMock, }, })); -vi.mock('@/store/waveformRefreshGen', () => ({ +vi.mock('@/features/playback/store/waveformRefreshGen', () => ({ bumpWaveformRefreshGen: hoisted.bumpWaveformRefreshGenMock, })); -vi.mock('./loudnessGainCache', () => ({ +vi.mock('@/features/playback/store/loudnessGainCache', () => ({ clearLoudnessCacheStateForTrackId: hoisted.clearLoudnessCacheMock, })); -vi.mock('./loudnessBackfillState', () => ({ +vi.mock('@/features/playback/store/loudnessBackfillState', () => ({ resetLoudnessBackfillStateForTrackId: hoisted.resetBackfillStateMock, })); -import { reseedLoudnessForTrackId } from './loudnessReseed'; +import { reseedLoudnessForTrackId } from '@/features/playback/store/loudnessReseed'; beforeEach(() => { hoisted.authState.normalizationEngine = 'loudness'; diff --git a/src/store/loudnessReseed.ts b/src/features/playback/store/loudnessReseed.ts similarity index 83% rename from src/store/loudnessReseed.ts rename to src/features/playback/store/loudnessReseed.ts index 1a4d86a8..207f47d5 100644 --- a/src/store/loudnessReseed.ts +++ b/src/features/playback/store/loudnessReseed.ts @@ -1,11 +1,11 @@ import { buildStreamUrl } from '@/lib/api/subsonicStreamUrl'; import { invoke } from '@tauri-apps/api/core'; -import { getPlaybackIndexKey } from '../utils/playback/playbackServer'; -import { useAuthStore } from './authStore'; -import { usePlayerStore } from './playerStore'; -import { bumpWaveformRefreshGen } from '@/store/waveformRefreshGen'; -import { clearLoudnessCacheStateForTrackId } from './loudnessGainCache'; -import { resetLoudnessBackfillStateForTrackId } from './loudnessBackfillState'; +import { getPlaybackIndexKey } from '@/features/playback/utils/playback/playbackServer'; +import { useAuthStore } from '@/store/authStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; +import { bumpWaveformRefreshGen } from '@/features/playback/store/waveformRefreshGen'; +import { clearLoudnessCacheStateForTrackId } from '@/features/playback/store/loudnessGainCache'; +import { resetLoudnessBackfillStateForTrackId } from '@/features/playback/store/loudnessBackfillState'; /** * Tear down every cached piece of analysis for a track and re-enqueue a diff --git a/src/store/miscActions.ts b/src/features/playback/store/miscActions.ts similarity index 81% rename from src/store/miscActions.ts rename to src/features/playback/store/miscActions.ts index be4ea353..05dce615 100644 --- a/src/store/miscActions.ts +++ b/src/features/playback/store/miscActions.ts @@ -1,34 +1,34 @@ -import { applyServerPlayQueue } from './applyServerPlayQueue'; +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 { useAuthStore } from './authStore'; +import { showToast } from '@/utils/ui/toast'; +import { useAuthStore } from '@/store/authStore'; import { bumpPlayGeneration, setIsAudioPaused, -} from './engineState'; -import { clearPreloadingIds } from './gaplessPreloadState'; -import { reseedLoudnessForTrackId } from './loudnessReseed'; -import { getPlaybackProgressSnapshot } from './playbackProgress'; -import { shouldRebindPlaybackToHotCache } from './playbackUrlRouting'; -import type { PlayerState } from './playerStoreTypes'; -import { toQueueItemRefs } from '../utils/library/queueItemRef'; -import { resolveQueueTrack } from '../utils/library/queueTrackView'; -import { seedQueueResolver } from '../utils/library/queueTrackResolver'; -import { pushQueueUndoFromGetter } from './queueUndo'; -import { syncUserQueueMutationToServer } from './queueSync'; +} from '@/features/playback/store/engineState'; +import { clearPreloadingIds } from '@/features/playback/store/gaplessPreloadState'; +import { reseedLoudnessForTrackId } from '@/features/playback/store/loudnessReseed'; +import { getPlaybackProgressSnapshot } from '@/features/playback/store/playbackProgress'; +import { shouldRebindPlaybackToHotCache } from '@/features/playback/store/playbackUrlRouting'; +import type { PlayerState } from '@/features/playback/store/playerStoreTypes'; +import { toQueueItemRefs } from '@/utils/library/queueItemRef'; +import { resolveQueueTrack } from '@/utils/library/queueTrackView'; +import { seedQueueResolver } from '@/utils/library/queueTrackResolver'; +import { pushQueueUndoFromGetter } from '@/features/playback/store/queueUndo'; +import { syncUserQueueMutationToServer } from '@/features/playback/store/queueSync'; import { clearRadioReconnectTimer, playRadioStream, setRadioVolume, -} from './radioPlayer'; -import { clearAllPlaybackScheduleTimers } from './scheduleTimers'; -import { clearSeekDebounce } from './seekDebounce'; +} from '@/features/playback/store/radioPlayer'; +import { clearAllPlaybackScheduleTimers } from '@/features/playback/store/scheduleTimers'; +import { clearSeekDebounce } from '@/features/playback/store/seekDebounce'; import { clearSeekFallbackRetry, setSeekFallbackVisualTarget, -} from './seekFallbackState'; -import { clearSeekTarget } from './seekTargetState'; +} from '@/features/playback/store/seekFallbackState'; +import { clearSeekTarget } from '@/features/playback/store/seekTargetState'; type SetState = ( partial: Partial | ((state: PlayerState) => Partial), diff --git a/src/store/networkLoveActions.ts b/src/features/playback/store/networkLoveActions.ts similarity index 94% rename from src/store/networkLoveActions.ts rename to src/features/playback/store/networkLoveActions.ts index 02af7f14..b40d96cb 100644 --- a/src/store/networkLoveActions.ts +++ b/src/features/playback/store/networkLoveActions.ts @@ -1,5 +1,5 @@ -import { getMusicNetworkRuntimeOrNull } from '../music-network'; -import type { PlayerState } from './playerStoreTypes'; +import { getMusicNetworkRuntimeOrNull } from '@/music-network'; +import type { PlayerState } from '@/features/playback/store/playerStoreTypes'; type SetState = ( partial: Partial | ((state: PlayerState) => Partial), diff --git a/src/store/networkLovedCacheStorage.ts b/src/features/playback/store/networkLovedCacheStorage.ts similarity index 100% rename from src/store/networkLovedCacheStorage.ts rename to src/features/playback/store/networkLovedCacheStorage.ts diff --git a/src/store/nextAction.ts b/src/features/playback/store/nextAction.ts similarity index 93% rename from src/store/nextAction.ts rename to src/features/playback/store/nextAction.ts index 226cf560..d0cf4b3f 100644 --- a/src/store/nextAction.ts +++ b/src/features/playback/store/nextAction.ts @@ -1,28 +1,28 @@ import { getSimilarSongs2, getTopSongs } from '@/lib/api/subsonicArtists'; import { invoke } from '@tauri-apps/api/core'; -import { buildInfiniteQueueCandidates } from '../utils/playback/buildInfiniteQueueCandidates'; -import { songToTrack } from '../utils/playback/songToTrack'; -import { ensureQueueServerPinned } from '../utils/playback/playbackServer'; -import { useAuthStore } from './authStore'; -import { setIsAudioPaused } from './engineState'; +import { buildInfiniteQueueCandidates } from '@/features/playback/utils/playback/buildInfiniteQueueCandidates'; +import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; +import { ensureQueueServerPinned } from '@/features/playback/utils/playback/playbackServer'; +import { useAuthStore } from '@/store/authStore'; +import { setIsAudioPaused } from '@/features/playback/store/engineState'; import { isInfiniteQueueFetching, setInfiniteQueueFetching, -} from './infiniteQueueState'; +} from '@/features/playback/store/infiniteQueueState'; import { isInOrbitSession } from '@/store/orbitRuntime'; -import type { PlayerState, QueueItemRef, Track } from './playerStoreTypes'; -import { toQueueItemRefs } from '../utils/library/queueItemRef'; -import { resolveQueueTrack } from '../utils/library/queueTrackView'; -import { seedQueueResolver } from '../utils/library/queueTrackResolver'; +import type { PlayerState, QueueItemRef, Track } from '@/features/playback/store/playerStoreTypes'; +import { toQueueItemRefs } from '@/utils/library/queueItemRef'; +import { resolveQueueTrack } from '@/utils/library/queueTrackView'; +import { seedQueueResolver } from '@/utils/library/queueTrackResolver'; import { addRadioSessionSeen, getCurrentRadioArtistId, hasRadioSessionSeen, isRadioFetching, setRadioFetching, -} from './radioSessionState'; -import { finalizePlayQueueAtTrackEnd } from './queueSync'; -import { applySkipStarOnManualNext } from './skipStarRating'; +} from '@/features/playback/store/radioSessionState'; +import { finalizePlayQueueAtTrackEnd } from '@/features/playback/store/queueSync'; +import { applySkipStarOnManualNext } from '@/features/playback/store/skipStarRating'; type SetState = ( partial: Partial | ((state: PlayerState) => Partial), diff --git a/src/store/nextActionOrbitRadio.test.ts b/src/features/playback/store/nextActionOrbitRadio.test.ts similarity index 71% rename from src/store/nextActionOrbitRadio.test.ts rename to src/features/playback/store/nextActionOrbitRadio.test.ts index 998a85fc..31d92302 100644 --- a/src/store/nextActionOrbitRadio.test.ts +++ b/src/features/playback/store/nextActionOrbitRadio.test.ts @@ -13,38 +13,38 @@ vi.mock('@/store/orbitRuntime', async (importOriginal) => ({ ...(await importOriginal()), isInOrbitSession: () => inOrbit.value, })); -vi.mock('./authStore', () => ({ +vi.mock('@/store/authStore', () => ({ useAuthStore: { getState: () => ({ infiniteQueueEnabled: false }) }, })); -vi.mock('./radioSessionState', () => ({ +vi.mock('@/features/playback/store/radioSessionState', () => ({ addRadioSessionSeen: vi.fn(), getCurrentRadioArtistId: () => null, hasRadioSessionSeen: () => false, isRadioFetching: () => false, setRadioFetching: vi.fn(), })); -vi.mock('./infiniteQueueState', () => ({ +vi.mock('@/features/playback/store/infiniteQueueState', () => ({ isInfiniteQueueFetching: () => false, setInfiniteQueueFetching: vi.fn(), })); -vi.mock('./engineState', () => ({ setIsAudioPaused: vi.fn() })); -vi.mock('./skipStarRating', () => ({ applySkipStarOnManualNext: vi.fn() })); -vi.mock('../utils/library/queueTrackView', () => ({ +vi.mock('@/features/playback/store/engineState', () => ({ setIsAudioPaused: vi.fn() })); +vi.mock('@/features/playback/store/skipStarRating', () => ({ applySkipStarOnManualNext: vi.fn() })); +vi.mock('@/utils/library/queueTrackView', () => ({ resolveQueueTrack: (ref: { trackId: string }) => ({ id: ref.trackId, artistId: 'a1', artist: 'Artist', }), })); -vi.mock('../utils/playback/buildInfiniteQueueCandidates', () => ({ +vi.mock('@/features/playback/utils/playback/buildInfiniteQueueCandidates', () => ({ buildInfiniteQueueCandidates: vi.fn(() => Promise.resolve([])), })); -vi.mock('../utils/playback/songToTrack', () => ({ songToTrack: (s: unknown) => s })); -vi.mock('../utils/playback/playbackServer', () => ({ ensureQueueServerPinned: () => null })); -vi.mock('../utils/library/queueTrackResolver', () => ({ seedQueueResolver: vi.fn() })); -vi.mock('../utils/library/queueItemRef', () => ({ toQueueItemRefs: () => [] })); +vi.mock('@/features/playback/utils/playback/songToTrack', () => ({ songToTrack: (s: unknown) => s })); +vi.mock('@/features/playback/utils/playback/playbackServer', () => ({ ensureQueueServerPinned: () => null })); +vi.mock('@/utils/library/queueTrackResolver', () => ({ seedQueueResolver: vi.fn() })); +vi.mock('@/utils/library/queueItemRef', () => ({ toQueueItemRefs: () => [] })); -import { runNext } from './nextAction'; +import { runNext } from '@/features/playback/store/nextAction'; function fakeGet() { // index 0 → next is the radioAdded ref at index 1; nothing radio ahead of it, diff --git a/src/store/normalizationDebug.test.ts b/src/features/playback/store/normalizationDebug.test.ts similarity index 91% rename from src/store/normalizationDebug.test.ts rename to src/features/playback/store/normalizationDebug.test.ts index 63dbbda3..eca08263 100644 --- a/src/store/normalizationDebug.test.ts +++ b/src/features/playback/store/normalizationDebug.test.ts @@ -6,9 +6,9 @@ const { authState, invokeMock } = vi.hoisted(() => ({ })); vi.mock('@tauri-apps/api/core', () => ({ invoke: invokeMock })); -vi.mock('./authStore', () => ({ useAuthStore: { getState: () => authState } })); +vi.mock('@/store/authStore', () => ({ useAuthStore: { getState: () => authState } })); -import { emitNormalizationDebug } from './normalizationDebug'; +import { emitNormalizationDebug } from '@/features/playback/store/normalizationDebug'; beforeEach(() => { authState.loggingMode = 'off'; diff --git a/src/store/normalizationDebug.ts b/src/features/playback/store/normalizationDebug.ts similarity index 93% rename from src/store/normalizationDebug.ts rename to src/features/playback/store/normalizationDebug.ts index 3e2cab1b..a29b242c 100644 --- a/src/store/normalizationDebug.ts +++ b/src/features/playback/store/normalizationDebug.ts @@ -1,5 +1,5 @@ import { invoke } from '@tauri-apps/api/core'; -import { useAuthStore } from './authStore'; +import { useAuthStore } from '@/store/authStore'; /** * Forward a structured normalization-pipeline trace to the Rust-side debug diff --git a/src/store/normalizationIpcDedupe.test.ts b/src/features/playback/store/normalizationIpcDedupe.test.ts similarity index 95% rename from src/store/normalizationIpcDedupe.test.ts rename to src/features/playback/store/normalizationIpcDedupe.test.ts index bdf608e6..6bbe13fd 100644 --- a/src/store/normalizationIpcDedupe.test.ts +++ b/src/features/playback/store/normalizationIpcDedupe.test.ts @@ -16,8 +16,8 @@ const { authState, invokeMock } = vi.hoisted(() => ({ })); vi.mock('@tauri-apps/api/core', () => ({ invoke: invokeMock })); -vi.mock('./authStore', () => ({ useAuthStore: { getState: () => authState } })); -vi.mock('../utils/audio/loudnessPreAnalysisSlider', () => ({ +vi.mock('@/store/authStore', () => ({ useAuthStore: { getState: () => authState } })); +vi.mock('@/utils/audio/loudnessPreAnalysisSlider', () => ({ effectiveLoudnessPreAnalysisAttenuationDb: (attenuation: number) => attenuation, })); @@ -25,7 +25,7 @@ import { _resetNormalizationIpcDedupeForTest, invokeAudioSetNormalizationDeduped, invokeAudioUpdateReplayGainDeduped, -} from './normalizationIpcDedupe'; +} from '@/features/playback/store/normalizationIpcDedupe'; beforeEach(() => { vi.useFakeTimers(); diff --git a/src/store/normalizationIpcDedupe.ts b/src/features/playback/store/normalizationIpcDedupe.ts similarity index 95% rename from src/store/normalizationIpcDedupe.ts rename to src/features/playback/store/normalizationIpcDedupe.ts index 230d2296..5f07a9ad 100644 --- a/src/store/normalizationIpcDedupe.ts +++ b/src/features/playback/store/normalizationIpcDedupe.ts @@ -1,6 +1,6 @@ import { invoke } from '@tauri-apps/api/core'; -import { useAuthStore } from './authStore'; -import { effectiveLoudnessPreAnalysisAttenuationDb } from '../utils/audio/loudnessPreAnalysisSlider'; +import { useAuthStore } from '@/store/authStore'; +import { effectiveLoudnessPreAnalysisAttenuationDb } from '@/utils/audio/loudnessPreAnalysisSlider'; /** * Two IPC entry points to the Rust normalization pipeline that get hammered diff --git a/src/store/normalizationSnapshot.test.ts b/src/features/playback/store/normalizationSnapshot.test.ts similarity index 88% rename from src/store/normalizationSnapshot.test.ts rename to src/features/playback/store/normalizationSnapshot.test.ts index 214e0d87..8e8bf22a 100644 --- a/src/store/normalizationSnapshot.test.ts +++ b/src/features/playback/store/normalizationSnapshot.test.ts @@ -4,7 +4,7 @@ * album-mode resolution). useAuthStore is mocked through a hoisted state * object so each test can flip flags without rebuilding the store. */ -import type { Track } from './playerStoreTypes'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; import { beforeEach, describe, expect, it, vi } from 'vitest'; const { authState } = vi.hoisted(() => ({ authState: { @@ -17,17 +17,17 @@ const { authState } = vi.hoisted(() => ({ }, })); -vi.mock('./authStore', () => ({ +vi.mock('@/store/authStore', () => ({ useAuthStore: { getState: () => authState }, })); -vi.mock('../utils/audio/resolveReplayGainDb', () => ({ +vi.mock('@/features/playback/utils/audio/resolveReplayGainDb', () => ({ resolveReplayGainDb: vi.fn( (track: Track) => (track as Track & { _testGain?: number | null })._testGain ?? null, ), })); -import { deriveNormalizationSnapshot } from './normalizationSnapshot'; +import { deriveNormalizationSnapshot } from '@/features/playback/store/normalizationSnapshot'; function track(id: string, gain: number | null = null): Track { return { @@ -99,7 +99,7 @@ describe("engine='replaygain'", () => { }); it('passes neighbour-track context for album-mode resolution', async () => { - const { resolveReplayGainDb } = await import('../utils/audio/resolveReplayGainDb'); + const { resolveReplayGainDb } = await import('@/features/playback/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/audio/resolveReplayGainDb'); + const { resolveReplayGainDb } = await import('@/features/playback/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/audio/resolveReplayGainDb'); + const { resolveReplayGainDb } = await import('@/features/playback/utils/audio/resolveReplayGainDb'); const spy = vi.mocked(resolveReplayGainDb); spy.mockClear(); authState.normalizationEngine = 'replaygain'; diff --git a/src/store/normalizationSnapshot.ts b/src/features/playback/store/normalizationSnapshot.ts similarity index 88% rename from src/store/normalizationSnapshot.ts rename to src/features/playback/store/normalizationSnapshot.ts index 4fa07062..76bf80b6 100644 --- a/src/store/normalizationSnapshot.ts +++ b/src/features/playback/store/normalizationSnapshot.ts @@ -1,6 +1,6 @@ -import type { PlayerState, Track } from './playerStoreTypes'; -import { useAuthStore } from './authStore'; -import { resolveReplayGainDb } from '../utils/audio/resolveReplayGainDb'; +import type { PlayerState, Track } from '@/features/playback/store/playerStoreTypes'; +import { useAuthStore } from '@/store/authStore'; +import { resolveReplayGainDb } from '@/features/playback/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: diff --git a/src/store/pausedRestorePrepare.test.ts b/src/features/playback/store/pausedRestorePrepare.test.ts similarity index 75% rename from src/store/pausedRestorePrepare.test.ts rename to src/features/playback/store/pausedRestorePrepare.test.ts index e727f23a..93e38f1d 100644 --- a/src/store/pausedRestorePrepare.test.ts +++ b/src/features/playback/store/pausedRestorePrepare.test.ts @@ -1,4 +1,4 @@ -import type { Track } from './playerStoreTypes'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; import { beforeEach, describe, expect, it, vi } from 'vitest'; const hoisted = vi.hoisted(() => ({ @@ -22,17 +22,17 @@ const hoisted = vi.hoisted(() => ({ })); vi.mock('@tauri-apps/api/core', () => ({ invoke: hoisted.invokeMock })); -vi.mock('./playbackProgress', () => ({ emitPlaybackProgress: hoisted.emitMock })); -vi.mock('./seekFallbackState', () => ({ setSeekFallbackVisualTarget: hoisted.setSeekMock })); -vi.mock('../hotCachePrefetch', () => ({ scheduleHotCachePrefetchForTrack: hoisted.prefetchMock })); -vi.mock('./promoteStreamCache', () => ({ promoteCompletedStreamToHotCache: hoisted.promoteMock })); -vi.mock('./engineLoadTrackAtPosition', () => ({ engineLoadTrackAtPosition: hoisted.engineLoadMock })); -vi.mock('./engineState', () => ({ +vi.mock('@/features/playback/store/playbackProgress', () => ({ emitPlaybackProgress: hoisted.emitMock })); +vi.mock('@/features/playback/store/seekFallbackState', () => ({ setSeekFallbackVisualTarget: hoisted.setSeekMock })); +vi.mock('@/hotCachePrefetch', () => ({ scheduleHotCachePrefetchForTrack: hoisted.prefetchMock })); +vi.mock('@/features/playback/store/promoteStreamCache', () => ({ promoteCompletedStreamToHotCache: hoisted.promoteMock })); +vi.mock('@/features/playback/store/engineLoadTrackAtPosition', () => ({ engineLoadTrackAtPosition: hoisted.engineLoadMock })); +vi.mock('@/features/playback/store/engineState', () => ({ bumpPlayGeneration: hoisted.bumpGenMock, getPlayGeneration: hoisted.getGenMock, })); -vi.mock('./authStore', () => ({ useAuthStore: { getState: () => hoisted.authState } })); -vi.mock('./playerStore', () => ({ +vi.mock('@/store/authStore', () => ({ useAuthStore: { getState: () => hoisted.authState } })); +vi.mock('@/features/playback/store/playerStore', () => ({ usePlayerStore: { getState: () => hoisted.playerState, setState: vi.fn((partial: Record) => { @@ -40,7 +40,7 @@ vi.mock('./playerStore', () => ({ }), }, })); -vi.mock('../utils/library/queueTrackView', () => ({ +vi.mock('@/utils/library/queueTrackView', () => ({ getQueueTracksView: vi.fn((refs: { trackId: string }[]) => refs.map(r => ({ id: r.trackId, @@ -52,14 +52,14 @@ vi.mock('../utils/library/queueTrackView', () => ({ })), ), })); -vi.mock('../utils/playback/playbackServer', () => ({ +vi.mock('@/features/playback/utils/playback/playbackServer', () => ({ getPlaybackCacheServerKey: () => 'srv-key', })); import { applyRestoredPlaybackVisual, preparePausedRestoreOnStartup, -} from './pausedRestorePrepare'; +} from '@/features/playback/store/pausedRestorePrepare'; function track(id: string, duration = 200): Track { return { id, title: id, artist: 'A', album: 'B', albumId: 'B', duration }; diff --git a/src/store/pausedRestorePrepare.ts b/src/features/playback/store/pausedRestorePrepare.ts similarity index 67% rename from src/store/pausedRestorePrepare.ts rename to src/features/playback/store/pausedRestorePrepare.ts index e8bbc1bc..4134e739 100644 --- a/src/store/pausedRestorePrepare.ts +++ b/src/features/playback/store/pausedRestorePrepare.ts @@ -1,14 +1,14 @@ -import type { QueueItemRef, Track } from './playerStoreTypes'; -import { getQueueTracksView } from '../utils/library/queueTrackView'; -import { scheduleHotCachePrefetchForTrack } from '../hotCachePrefetch'; -import { getPlaybackCacheServerKey } from '../utils/playback/playbackServer'; -import { useAuthStore } from './authStore'; -import { bumpPlayGeneration, getPlayGeneration } from './engineState'; -import { engineLoadTrackAtPosition } from './engineLoadTrackAtPosition'; -import { emitPlaybackProgress } from './playbackProgress'; -import { promoteCompletedStreamToHotCache } from './promoteStreamCache'; -import { usePlayerStore } from './playerStore'; -import { setSeekFallbackVisualTarget } from './seekFallbackState'; +import type { QueueItemRef, Track } from '@/features/playback/store/playerStoreTypes'; +import { getQueueTracksView } from '@/utils/library/queueTrackView'; +import { scheduleHotCachePrefetchForTrack } from '@/hotCachePrefetch'; +import { getPlaybackCacheServerKey } from '@/features/playback/utils/playback/playbackServer'; +import { useAuthStore } from '@/store/authStore'; +import { bumpPlayGeneration, getPlayGeneration } from '@/features/playback/store/engineState'; +import { engineLoadTrackAtPosition } from '@/features/playback/store/engineLoadTrackAtPosition'; +import { emitPlaybackProgress } from '@/features/playback/store/playbackProgress'; +import { promoteCompletedStreamToHotCache } from '@/features/playback/store/promoteStreamCache'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; +import { setSeekFallbackVisualTarget } from '@/features/playback/store/seekFallbackState'; /** Push restored position into the store + progress channel so the seekbar paints immediately. */ export function applyRestoredPlaybackVisual(track: Track, atSeconds: number): void { diff --git a/src/store/pendingStarSync.test.ts b/src/features/playback/store/pendingStarSync.test.ts similarity index 92% rename from src/store/pendingStarSync.test.ts rename to src/features/playback/store/pendingStarSync.test.ts index c705cf41..55d9dc14 100644 --- a/src/store/pendingStarSync.test.ts +++ b/src/features/playback/store/pendingStarSync.test.ts @@ -9,15 +9,15 @@ vi.mock('@/lib/api/subsonicStarRating', () => ({ setRating: (...a: unknown[]) => setRatingMock(...a), })); -import { usePlayerStore } from './playerStore'; -import type { Track } from './playerStoreTypes'; -import { queueSongStar, queueSongRating, _resetPendingStarSyncForTest } from './pendingStarSync'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; +import { queueSongStar, queueSongRating, _resetPendingStarSyncForTest } from '@/features/playback/store/pendingStarSync'; import { getCachedTrack, seedQueueResolver, _resetQueueResolverForTest, -} from '../utils/library/queueTrackResolver'; -import { toQueueItemRefs } from '../utils/library/queueItemRef'; +} from '@/utils/library/queueTrackResolver'; +import { toQueueItemRefs } from '@/utils/library/queueItemRef'; const track = (id: string): Track => ({ id, title: id, artist: '', album: 'A', albumId: 'A', duration: 1, diff --git a/src/store/pendingStarSync.ts b/src/features/playback/store/pendingStarSync.ts similarity index 97% rename from src/store/pendingStarSync.ts rename to src/features/playback/store/pendingStarSync.ts index 7b0444be..835f0500 100644 --- a/src/store/pendingStarSync.ts +++ b/src/features/playback/store/pendingStarSync.ts @@ -1,6 +1,6 @@ import { setRating, star, unstar } from '@/lib/api/subsonicStarRating'; -import { usePlayerStore } from './playerStore'; -import { patchCachedTrack } from '../utils/library/queueTrackResolver'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; +import { patchCachedTrack } from '@/utils/library/queueTrackResolver'; /** * F4 — pending-sync for **song** star + rating (spec §6.5 / R7-18). diff --git a/src/store/playListenSession.test.ts b/src/features/playback/store/playListenSession.test.ts similarity index 89% rename from src/store/playListenSession.test.ts rename to src/features/playback/store/playListenSession.test.ts index d6ac479b..6973fd77 100644 --- a/src/store/playListenSession.test.ts +++ b/src/features/playback/store/playListenSession.test.ts @@ -1,9 +1,9 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import { invoke } from '@tauri-apps/api/core'; -import { onInvoke } from '../test/mocks/tauri'; -import { useLibraryIndexStore } from './libraryIndexStore'; -import { usePlayerStore } from './playerStore'; -import { usePreviewStore } from './previewStore'; +import { onInvoke } from '@/test/mocks/tauri'; +import { useLibraryIndexStore } from '@/store/libraryIndexStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; +import { usePreviewStore } from '@/features/playback/store/previewStore'; import { _resetPlayListenSessionForTest, playListenSessionFinalize, @@ -11,18 +11,18 @@ import { playListenSessionOnProgress, playListenSessionOpen, resolveDurationSecHint, -} from './playListenSession'; -import { onPlaySessionRecorded } from './playSessionRecorded'; +} from '@/features/playback/store/playListenSession'; +import { onPlaySessionRecorded } from '@/features/playback/store/playSessionRecorded'; -vi.mock('../utils/library/libraryReady', () => ({ +vi.mock('@/utils/library/libraryReady', () => ({ libraryIsReady: vi.fn(async () => true), })); -vi.mock('../utils/playback/playbackServer', () => ({ +vi.mock('@/features/playback/utils/playback/playbackServer', () => ({ getPlaybackServerId: vi.fn(() => 'server-1'), })); -import type { Track } from './playerStoreTypes'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; const testTrack: Track = { id: 't1', @@ -105,7 +105,7 @@ describe('playListenSession', () => { }); it('skips preview playback', async () => { - const { usePreviewStore } = await import('./previewStore'); + const { usePreviewStore } = await import('@/features/playback/store/previewStore'); usePreviewStore.setState({ previewingId: 'preview-1' }); vi.useFakeTimers(); await playListenSessionOpen(testTrack, 'server-1'); @@ -130,7 +130,7 @@ describe('playListenSession', () => { }); it('does not accumulate listened time while paused or buffering', async () => { - const { usePlayerStore } = await import('./playerStore'); + const { usePlayerStore } = await import('@/features/playback/store/playerStore'); vi.useFakeTimers(); await playListenSessionOpen(testTrack, 'server-1'); vi.setSystemTime(Date.now() + 15_000); @@ -178,7 +178,7 @@ describe('playListenSession', () => { }); it('skips when library is not ready', async () => { - const { libraryIsReady } = await import('../utils/library/libraryReady'); + const { libraryIsReady } = await import('@/utils/library/libraryReady'); vi.mocked(libraryIsReady).mockResolvedValueOnce(false); vi.useFakeTimers(); await playListenSessionOpen(testTrack, 'server-1'); diff --git a/src/store/playListenSession.ts b/src/features/playback/store/playListenSession.ts similarity index 91% rename from src/store/playListenSession.ts rename to src/features/playback/store/playListenSession.ts index 2e802bd1..d775a908 100644 --- a/src/store/playListenSession.ts +++ b/src/features/playback/store/playListenSession.ts @@ -1,14 +1,14 @@ -import type { Track } from './playerStoreTypes'; -import { usePlayerStore } from './playerStore'; -import { usePreviewStore } from './previewStore'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; +import { usePreviewStore } from '@/features/playback/store/previewStore'; import { libraryRecordPlaySession, type PlaySessionEndReason, } from '@/lib/api/library'; -import { libraryIsReady } from '../utils/library/libraryReady'; -import { getPlaybackServerId } from '../utils/playback/playbackServer'; -import { emitPlaySessionRecorded } from './playSessionRecorded'; -import { useLibraryIndexStore } from './libraryIndexStore'; +import { libraryIsReady } from '@/utils/library/libraryReady'; +import { getPlaybackServerId } from '@/features/playback/utils/playback/playbackServer'; +import { emitPlaySessionRecorded } from '@/features/playback/store/playSessionRecorded'; +import { useLibraryIndexStore } from '@/store/libraryIndexStore'; const MIN_LISTENED_SEC = 10; diff --git a/src/store/playSessionRecorded.test.ts b/src/features/playback/store/playSessionRecorded.test.ts similarity index 92% rename from src/store/playSessionRecorded.test.ts rename to src/features/playback/store/playSessionRecorded.test.ts index af234db5..0e59e483 100644 --- a/src/store/playSessionRecorded.test.ts +++ b/src/features/playback/store/playSessionRecorded.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it, vi } from 'vitest'; -import { emitPlaySessionRecorded, onPlaySessionRecorded } from './playSessionRecorded'; +import { emitPlaySessionRecorded, onPlaySessionRecorded } from '@/features/playback/store/playSessionRecorded'; describe('playSessionRecorded', () => { it('notifies subscribers when a listen is persisted', () => { diff --git a/src/store/playSessionRecorded.ts b/src/features/playback/store/playSessionRecorded.ts similarity index 100% rename from src/store/playSessionRecorded.ts rename to src/features/playback/store/playSessionRecorded.ts diff --git a/src/store/playTrackAction.ts b/src/features/playback/store/playTrackAction.ts similarity index 88% rename from src/store/playTrackAction.ts rename to src/features/playback/store/playTrackAction.ts index f6d31c24..3468ba8a 100644 --- a/src/store/playTrackAction.ts +++ b/src/features/playback/store/playTrackAction.ts @@ -1,23 +1,23 @@ -import { playbackReportStart, playbackReportStopped } from './playbackReportSession'; +import { playbackReportStart, playbackReportStopped } from '@/features/playback/store/playbackReportSession'; import { invoke } from '@tauri-apps/api/core'; -import { getMusicNetworkRuntimeOrNull } from '../music-network'; -import { setDeferHotCachePrefetch } from '../utils/cache/hotCacheGate'; +import { getMusicNetworkRuntimeOrNull } from '@/music-network'; +import { setDeferHotCachePrefetch } from '@/utils/cache/hotCacheGate'; import { orbitBulkGuard, orbitSnapshot } from '@/store/orbitRuntime'; -import { sameQueueTrackId } from '../utils/playback/queueIdentity'; +import { sameQueueTrackId } from '@/features/playback/utils/playback/queueIdentity'; import { computeAutodjManualBlendPlan, shouldAutodjInterruptBlend, -} from '../utils/playback/autodjManualBlend'; -import type { CrossfadeTransitionPlan } from '../utils/waveform/waveformSilence'; +} from '@/features/playback/utils/playback/autodjManualBlend'; +import type { CrossfadeTransitionPlan } from '@/utils/waveform/waveformSilence'; import { armInterruptHandoff, clearInterruptHandoff, runInterruptBlendPrep, shouldDeferInterruptHandoffUi, -} from '../utils/playback/autodjInterruptPrep'; -import { isCrossfadeNextReady } from './crossfadePreload'; -import { STANDARD_BLEND_SEC } from '../utils/waveform/waveformSilence'; -import { armAutodjMixing, clearAutodjTransitionUi } from './autodjTransitionUi'; +} from '@/features/playback/utils/playback/autodjInterruptPrep'; +import { isCrossfadeNextReady } from '@/features/playback/store/crossfadePreload'; +import { STANDARD_BLEND_SEC } from '@/utils/waveform/waveformSilence'; +import { armAutodjMixing, clearAutodjTransitionUi } from '@/features/playback/store/autodjTransitionUi'; import { bindQueueServerForTracks, getPlaybackCacheServerKey, @@ -25,61 +25,61 @@ import { playbackCacheKeyForTrack, playbackProfileIdForTrack, shouldBindQueueServerForPlay, -} from '../utils/playback/playbackServer'; -import { stampTrackServerId, stampTrackServerIds } from '../utils/playback/trackServerScope'; +} from '@/features/playback/utils/playback/playbackServer'; +import { stampTrackServerId, stampTrackServerIds } from '@/features/playback/utils/playback/trackServerScope'; import { findLocalPlaybackUrl, hasLocalPersistentPlaybackBytes, } from '@/store/localPlaybackResolve'; -import { resolvePlaybackUrl } from '../utils/playback/resolvePlaybackUrl'; -import { resolveReplayGainDb } from '../utils/audio/resolveReplayGainDb'; -import { audioPlayHiResBlendArgs } from '../utils/audio/hiResCrossfadeResample'; -import { useAuthStore } from './authStore'; -import { consumeCrossfadeDynamicOverlap, getCrossfadeTransition, peekArmedCrossfadeDynamicOverlap } from './crossfadeTrimCache'; +import { resolvePlaybackUrl } from '@/features/playback/utils/playback/resolvePlaybackUrl'; +import { resolveReplayGainDb } from '@/features/playback/utils/audio/resolveReplayGainDb'; +import { audioPlayHiResBlendArgs } from '@/utils/audio/hiResCrossfadeResample'; +import { useAuthStore } from '@/store/authStore'; +import { consumeCrossfadeDynamicOverlap, getCrossfadeTransition, peekArmedCrossfadeDynamicOverlap } from '@/features/playback/store/crossfadeTrimCache'; import { bumpPlayGeneration, getPlayGeneration, setIsAudioPaused, -} from './engineState'; +} from '@/features/playback/store/engineState'; import { clearPreloadingIds, getLastGaplessSwitchTime, -} from './gaplessPreloadState'; -import { touchHotCacheOnPlayback } from './hotCacheTouch'; +} from '@/features/playback/store/gaplessPreloadState'; +import { touchHotCacheOnPlayback } from '@/features/playback/store/hotCacheTouch'; import { isReplayGainActive, loudnessGainDbForEngineBind, -} from './loudnessGainCache'; -import { refreshLoudnessForTrack } from './loudnessRefresh'; -import { fetchWaveformBins, refreshWaveformForTrack } from '@/store/waveformRefresh'; -import { deriveNormalizationSnapshot } from './normalizationSnapshot'; +} from '@/features/playback/store/loudnessGainCache'; +import { refreshLoudnessForTrack } from '@/features/playback/store/loudnessRefresh'; +import { fetchWaveformBins, refreshWaveformForTrack } from '@/features/playback/store/waveformRefresh'; +import { deriveNormalizationSnapshot } from '@/features/playback/store/normalizationSnapshot'; import { playbackSourceHintForResolvedUrl, recordEnginePlayUrl, -} from './playbackUrlRouting'; -import type { PlayerState, Track } from './playerStoreTypes'; -import { toQueueItemRefs } from '../utils/library/queueItemRef'; -import { getQueueTracksView, resolveQueueTrack } from '../utils/library/queueTrackView'; -import { seedQueueResolver } from '../utils/library/queueTrackResolver'; -import { promoteCompletedStreamToHotCache } from './promoteStreamCache'; -import { pushQueueOnPlaybackStart } from './queueSync'; -import { playListenSessionFinalize } from './playListenSession'; -import { pushQueueUndoFromGetter } from './queueUndo'; -import { appendTimelineLeaveTrack } from './timelineSessionHistory'; -import { stopRadio } from './radioPlayer'; -import { clearAllPlaybackScheduleTimers } from './scheduleTimers'; -import { clearSeekDebounce } from './seekDebounce'; +} from '@/features/playback/store/playbackUrlRouting'; +import type { PlayerState, Track } from '@/features/playback/store/playerStoreTypes'; +import { toQueueItemRefs } from '@/utils/library/queueItemRef'; +import { getQueueTracksView, resolveQueueTrack } from '@/utils/library/queueTrackView'; +import { seedQueueResolver } from '@/utils/library/queueTrackResolver'; +import { promoteCompletedStreamToHotCache } from '@/features/playback/store/promoteStreamCache'; +import { pushQueueOnPlaybackStart } from '@/features/playback/store/queueSync'; +import { playListenSessionFinalize } from '@/features/playback/store/playListenSession'; +import { pushQueueUndoFromGetter } from '@/features/playback/store/queueUndo'; +import { appendTimelineLeaveTrack } from '@/features/playback/store/timelineSessionHistory'; +import { stopRadio } from '@/features/playback/store/radioPlayer'; +import { clearAllPlaybackScheduleTimers } from '@/features/playback/store/scheduleTimers'; +import { clearSeekDebounce } from '@/features/playback/store/seekDebounce'; import { clearSeekFallbackRetry, getSeekFallbackVisualTarget, setSeekFallbackRestartAt, setSeekFallbackTrackId, setSeekFallbackVisualTarget, -} from './seekFallbackState'; +} from '@/features/playback/store/seekFallbackState'; import { clearSeekTarget, setSeekTarget, -} from './seekTargetState'; +} from '@/features/playback/store/seekTargetState'; type SetState = ( partial: Partial | ((state: PlayerState) => Partial), ) => void; diff --git a/src/store/playbackEngineBridgeRegister.ts b/src/features/playback/store/playbackEngineBridgeRegister.ts similarity index 69% rename from src/store/playbackEngineBridgeRegister.ts rename to src/features/playback/store/playbackEngineBridgeRegister.ts index 1a50a7f0..eb36f65b 100644 --- a/src/store/playbackEngineBridgeRegister.ts +++ b/src/features/playback/store/playbackEngineBridgeRegister.ts @@ -2,9 +2,9 @@ // importing it installs the engine's operations into @/store/playbackEngineBridge. // MainApp side-effect-imports this at boot. Lives with the engine (moves into // @/features/playback alongside playerStore); the bridge itself stays in core. -import { usePlayerStore } from './playerStore'; -import { clearQueueServerForPlayback } from '../utils/playback/playbackServer'; -import { registerPlaybackEngineBridge } from './playbackEngineBridge'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; +import { clearQueueServerForPlayback } from '@/features/playback/utils/playback/playbackServer'; +import { registerPlaybackEngineBridge } from '@/store/playbackEngineBridge'; registerPlaybackEngineBridge({ getQueueServerId: () => usePlayerStore.getState().queueServerId, diff --git a/src/store/playbackProgress.test.ts b/src/features/playback/store/playbackProgress.test.ts similarity index 98% rename from src/store/playbackProgress.test.ts rename to src/features/playback/store/playbackProgress.test.ts index 6b0bd592..e17ed2f0 100644 --- a/src/store/playbackProgress.test.ts +++ b/src/features/playback/store/playbackProgress.test.ts @@ -10,7 +10,7 @@ import { emitPlaybackProgress, getPlaybackProgressSnapshot, subscribePlaybackProgress, -} from './playbackProgress'; +} from '@/features/playback/store/playbackProgress'; afterEach(() => { _resetPlaybackProgressForTest(); diff --git a/src/store/playbackProgress.ts b/src/features/playback/store/playbackProgress.ts similarity index 100% rename from src/store/playbackProgress.ts rename to src/features/playback/store/playbackProgress.ts diff --git a/src/store/playbackRateStore.ts b/src/features/playback/store/playbackRateStore.ts similarity index 96% rename from src/store/playbackRateStore.ts rename to src/features/playback/store/playbackRateStore.ts index 4af884fc..0777da6f 100644 --- a/src/store/playbackRateStore.ts +++ b/src/features/playback/store/playbackRateStore.ts @@ -8,12 +8,12 @@ import { effectivePlaybackPitch, engineStrategy, type PlaybackStrategy, -} from '../utils/audio/playbackRateHelpers'; +} from '@/features/playback/utils/audio/playbackRateHelpers'; import { restartPlaybackForRateChange, shouldRestartPlaybackForRateChange, type PlaybackRateSnapshot, -} from '../utils/audio/playbackRateRestart'; +} from '@/features/playback/utils/audio/playbackRateRestart'; import { isOrbitPlaybackSyncActive } from '@/store/orbitRuntime'; interface PlaybackRateState extends PlaybackRateSnapshot { diff --git a/src/store/playbackReportSession.test.ts b/src/features/playback/store/playbackReportSession.test.ts similarity index 91% rename from src/store/playbackReportSession.test.ts rename to src/features/playback/store/playbackReportSession.test.ts index 9ff7530a..475b7dbf 100644 --- a/src/store/playbackReportSession.test.ts +++ b/src/features/playback/store/playbackReportSession.test.ts @@ -4,29 +4,29 @@ vi.mock('@/lib/api/subsonicScrobble', () => ({ reportPlayback: vi.fn(() => Promise.resolve()), reportNowPlaying: vi.fn(() => Promise.resolve()), })); -vi.mock('../serverCapabilities/storeView', () => ({ +vi.mock('@/serverCapabilities/storeView', () => ({ isFeatureActiveForServer: vi.fn(), })); -vi.mock('./authStore', () => ({ +vi.mock('@/store/authStore', () => ({ useAuthStore: { getState: vi.fn(() => ({ nowPlayingEnabled: true })) }, })); -vi.mock('./playbackProgress', () => ({ +vi.mock('@/features/playback/store/playbackProgress', () => ({ getPlaybackProgressSnapshot: vi.fn(() => ({ currentTime: 12, progress: 0, buffered: 0, buffering: false })), })); -vi.mock('./playbackRateStore', () => ({ +vi.mock('@/features/playback/store/playbackRateStore', () => ({ usePlaybackRateStore: { getState: () => ({ enabled: false, strategy: 'speed_corrected', speed: 1, pitchSemitones: 0 }), }, })); -vi.mock('../utils/audio/playbackRateHelpers', () => ({ isPlaybackRateApplied: () => false })); +vi.mock('@/features/playback/utils/audio/playbackRateHelpers', () => ({ isPlaybackRateApplied: () => false })); vi.mock('@/store/orbitRuntime', async (importOriginal) => ({ ...(await importOriginal()), isOrbitPlaybackSyncActive: () => false, })); import { reportNowPlaying, reportPlayback } from '@/lib/api/subsonicScrobble'; -import { isFeatureActiveForServer } from '../serverCapabilities/storeView'; -import { useAuthStore } from './authStore'; +import { isFeatureActiveForServer } from '@/serverCapabilities/storeView'; +import { useAuthStore } from '@/store/authStore'; import { _resetPlaybackReportSessionForTest, playbackReportPaused, @@ -34,7 +34,7 @@ import { playbackReportSeek, playbackReportStart, playbackReportStopped, -} from './playbackReportSession'; +} from '@/features/playback/store/playbackReportSession'; const reportPlaybackMock = vi.mocked(reportPlayback); const reportNowPlayingMock = vi.mocked(reportNowPlaying); diff --git a/src/store/playbackReportSession.ts b/src/features/playback/store/playbackReportSession.ts similarity index 91% rename from src/store/playbackReportSession.ts rename to src/features/playback/store/playbackReportSession.ts index b203cfe7..8972ea70 100644 --- a/src/store/playbackReportSession.ts +++ b/src/features/playback/store/playbackReportSession.ts @@ -1,12 +1,12 @@ import { reportNowPlaying, reportPlayback } from '@/lib/api/subsonicScrobble'; import type { PlaybackReportState } from '@/lib/api/subsonicTypes'; -import { FEATURE_PLAYBACK_REPORT } from '../serverCapabilities/catalog'; -import { isFeatureActiveForServer } from '../serverCapabilities/storeView'; -import { isPlaybackRateApplied } from '../utils/audio/playbackRateHelpers'; +import { FEATURE_PLAYBACK_REPORT } from '@/serverCapabilities/catalog'; +import { isFeatureActiveForServer } from '@/serverCapabilities/storeView'; +import { isPlaybackRateApplied } from '@/features/playback/utils/audio/playbackRateHelpers'; import { isOrbitPlaybackSyncActive } from '@/store/orbitRuntime'; -import { useAuthStore } from './authStore'; -import { getPlaybackProgressSnapshot } from './playbackProgress'; -import { usePlaybackRateStore } from './playbackRateStore'; +import { useAuthStore } from '@/store/authStore'; +import { getPlaybackProgressSnapshot } from '@/features/playback/store/playbackProgress'; +import { usePlaybackRateStore } from '@/features/playback/store/playbackRateStore'; /** * Live now-playing presence on the Subsonic server channel. diff --git a/src/store/playbackThrottles.test.ts b/src/features/playback/store/playbackThrottles.test.ts similarity index 97% rename from src/store/playbackThrottles.test.ts rename to src/features/playback/store/playbackThrottles.test.ts index 0c75a875..c9077f03 100644 --- a/src/store/playbackThrottles.test.ts +++ b/src/features/playback/store/playbackThrottles.test.ts @@ -13,7 +13,7 @@ import { markNormalizationUiUpdate, markStoreProgressCommit, resetProgressEmitThrottles, -} from './playbackThrottles'; +} from '@/features/playback/store/playbackThrottles'; afterEach(() => { _resetPlaybackThrottlesForTest(); diff --git a/src/store/playbackThrottles.ts b/src/features/playback/store/playbackThrottles.ts similarity index 100% rename from src/store/playbackThrottles.ts rename to src/features/playback/store/playbackThrottles.ts diff --git a/src/store/playbackUrlRouting.test.ts b/src/features/playback/store/playbackUrlRouting.test.ts similarity index 97% rename from src/store/playbackUrlRouting.test.ts rename to src/features/playback/store/playbackUrlRouting.test.ts index cfc03719..f282cb26 100644 --- a/src/store/playbackUrlRouting.test.ts +++ b/src/features/playback/store/playbackUrlRouting.test.ts @@ -20,7 +20,7 @@ vi.mock('@/store/localPlaybackResolve', () => ({ }, })); -vi.mock('../utils/playback/resolvePlaybackUrl', () => ({ +vi.mock('@/features/playback/utils/playback/resolvePlaybackUrl', () => ({ resolvePlaybackUrl: vi.fn((trackId: string, serverId: string) => { if (offlineStoreState.localUrlByKey.has(`${serverId}:${trackId}`)) { return `psysonic-local://${serverId}/${trackId}`; @@ -34,7 +34,7 @@ import { playbackSourceHintForResolvedUrl, recordEnginePlayUrl, shouldRebindPlaybackToHotCache, -} from './playbackUrlRouting'; +} from '@/features/playback/store/playbackUrlRouting'; beforeEach(() => { offlineStoreState.localUrlByKey.clear(); diff --git a/src/store/playbackUrlRouting.ts b/src/features/playback/store/playbackUrlRouting.ts similarity index 86% rename from src/store/playbackUrlRouting.ts rename to src/features/playback/store/playbackUrlRouting.ts index 58e5b10d..1dba27f1 100644 --- a/src/store/playbackUrlRouting.ts +++ b/src/features/playback/store/playbackUrlRouting.ts @@ -1,7 +1,7 @@ import { findLocalPlaybackUrl } from '@/store/localPlaybackResolve'; -import { resolvePlaybackUrl, type PlaybackSourceKind } from '../utils/playback/resolvePlaybackUrl'; -import { resolveServerIdForIndexKey } from '../utils/server/serverLookup'; -import { sameQueueTrackId } from '../utils/playback/queueIdentity'; +import { resolvePlaybackUrl, type PlaybackSourceKind } from '@/features/playback/utils/playback/resolvePlaybackUrl'; +import { resolveServerIdForIndexKey } from '@/utils/server/serverLookup'; +import { sameQueueTrackId } from '@/features/playback/utils/playback/queueIdentity'; /** * Helpers that classify the URL `audio_play` was last invoked with, so the diff --git a/src/store/playerPrefsStorage.test.ts b/src/features/playback/store/playerPrefsStorage.test.ts similarity index 97% rename from src/store/playerPrefsStorage.test.ts rename to src/features/playback/store/playerPrefsStorage.test.ts index 6da75d9d..9c01481f 100644 --- a/src/store/playerPrefsStorage.test.ts +++ b/src/features/playback/store/playerPrefsStorage.test.ts @@ -2,7 +2,7 @@ import { afterEach, beforeEach, describe, expect, it } from 'vitest'; import { persistPlayerPrefs, readInitialPlayerPrefs, -} from './playerPrefsStorage'; +} from '@/features/playback/store/playerPrefsStorage'; const PREFS_KEY = 'psysonic_player_prefs'; const LEGACY_KEY = 'psysonic-player'; diff --git a/src/store/playerPrefsStorage.ts b/src/features/playback/store/playerPrefsStorage.ts similarity index 100% rename from src/store/playerPrefsStorage.ts rename to src/features/playback/store/playerPrefsStorage.ts diff --git a/src/store/playerStore.events.test.ts b/src/features/playback/store/playerStore.events.test.ts similarity index 97% rename from src/store/playerStore.events.test.ts rename to src/features/playback/store/playerStore.events.test.ts index cbc21fad..6ae26f9c 100644 --- a/src/store/playerStore.events.test.ts +++ b/src/features/playback/store/playerStore.events.test.ts @@ -6,8 +6,8 @@ * regression test from §4.2 of the pre-refactor testing plan v2 — the * cleanup function returned by `initAudioListeners` must actually unsub. */ -import { initAudioListeners } from './initAudioListeners'; -import { armInterruptHandoff, clearInterruptHandoff } from '../utils/playback/autodjInterruptPrep'; +import { initAudioListeners } from '@/features/playback/store/initAudioListeners'; +import { armInterruptHandoff, clearInterruptHandoff } from '@/features/playback/utils/playback/autodjInterruptPrep'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; vi.mock('@/lib/api/subsonic', async () => { @@ -49,7 +49,7 @@ vi.mock('@/store/orbitRuntime', async (importOriginal) => ({ orbitBulkGuard: vi.fn(async () => true), })); -import { usePlayerStore } from './playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { emitTauriEvent, onInvoke, diff --git a/src/store/playerStore.misc.test.ts b/src/features/playback/store/playerStore.misc.test.ts similarity index 99% rename from src/store/playerStore.misc.test.ts rename to src/features/playback/store/playerStore.misc.test.ts index f7b773cf..fbca2500 100644 --- a/src/store/playerStore.misc.test.ts +++ b/src/features/playback/store/playerStore.misc.test.ts @@ -47,7 +47,7 @@ vi.mock('@/store/orbitRuntime', async (importOriginal) => ({ orbitBulkGuard: vi.fn(async () => true), })); -import { usePlayerStore } from './playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { onInvoke, invokeMock } from '@/test/mocks/tauri'; import { resetPlayerStore, resetAuthStore } from '@/test/helpers/storeReset'; import { makeTrack, makeTracks, seedQueue } from '@/test/helpers/factories'; diff --git a/src/store/playerStore.persistence.test.ts b/src/features/playback/store/playerStore.persistence.test.ts similarity index 97% rename from src/store/playerStore.persistence.test.ts rename to src/features/playback/store/playerStore.persistence.test.ts index 5eaaedfe..50980226 100644 --- a/src/store/playerStore.persistence.test.ts +++ b/src/features/playback/store/playerStore.persistence.test.ts @@ -14,8 +14,8 @@ * args passed to the Subsonic API call. */ import { savePlayQueue } from '@/lib/api/subsonicPlayQueue'; -import { initAudioListeners } from './initAudioListeners'; -import { flushPlayQueuePosition } from './queueSync'; +import { initAudioListeners } from '@/features/playback/store/initAudioListeners'; +import { flushPlayQueuePosition } from '@/features/playback/store/queueSync'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; // Explicit (non-spread) mock map — the `...actual` spread pattern lets the @@ -54,7 +54,7 @@ vi.mock('@/lib/api/subsonicScrobble', () => ({ reportNowPlaying: vi.fn(async () => undefined), scrobbleSong: vi.fn(async () => undefined), })); -vi.mock('@/utils/playback/playbackServer', () => ({ +vi.mock('@/features/playback/utils/playback/playbackServer', () => ({ getPlaybackServerId: () => 'srv-test', bindQueueServerForPlayback: vi.fn(), clearQueueServerForPlayback: vi.fn(), @@ -72,7 +72,7 @@ vi.mock('@/lib/api/subsonicStarRating', () => ({ })); -import { usePlayerStore } from './playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { emitTauriEvent, onInvoke } from '@/test/mocks/tauri'; import { resetPlayerStore, resetAuthStore } from '@/test/helpers/storeReset'; import { makeTrack, makeTracks, seedQueue } from '@/test/helpers/factories'; diff --git a/src/store/playerStore.playbackActions.test.ts b/src/features/playback/store/playerStore.playbackActions.test.ts similarity index 99% rename from src/store/playerStore.playbackActions.test.ts rename to src/features/playback/store/playerStore.playbackActions.test.ts index 05f514be..1a6d21d5 100644 --- a/src/store/playerStore.playbackActions.test.ts +++ b/src/features/playback/store/playerStore.playbackActions.test.ts @@ -52,7 +52,7 @@ vi.mock('@/store/orbitRuntime', async (importOriginal) => ({ orbitBulkGuard: vi.fn(async () => true), })); -import { usePlayerStore } from './playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { onInvoke, invokeMock } from '@/test/mocks/tauri'; import { resetPlayerStore, resetAuthStore } from '@/test/helpers/storeReset'; import { makeTrack, makeTracks, seedQueue } from '@/test/helpers/factories'; diff --git a/src/store/playerStore.progress.test.ts b/src/features/playback/store/playerStore.progress.test.ts similarity index 97% rename from src/store/playerStore.progress.test.ts rename to src/features/playback/store/playerStore.progress.test.ts index 628ca498..92ccae54 100644 --- a/src/store/playerStore.progress.test.ts +++ b/src/features/playback/store/playerStore.progress.test.ts @@ -10,8 +10,8 @@ * Drive emits via the `audio:progress` Tauri event (the only public path * to `emitPlaybackProgress`). */ -import { initAudioListeners } from './initAudioListeners'; -import { getPlaybackProgressSnapshot, subscribePlaybackProgress, type PlaybackProgressSnapshot } from './playbackProgress'; +import { initAudioListeners } from '@/features/playback/store/initAudioListeners'; +import { getPlaybackProgressSnapshot, subscribePlaybackProgress, type PlaybackProgressSnapshot } from '@/features/playback/store/playbackProgress'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; vi.mock('@/lib/api/subsonic', async () => { @@ -32,7 +32,7 @@ vi.mock('@/lib/api/subsonic', async () => { }); -import { usePlayerStore } from './playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { emitTauriEvent, onInvoke } from '@/test/mocks/tauri'; import { resetPlayerStore, resetAuthStore } from '@/test/helpers/storeReset'; import { makeTrack } from '@/test/helpers/factories'; diff --git a/src/store/playerStore.queue.test.ts b/src/features/playback/store/playerStore.queue.test.ts similarity index 98% rename from src/store/playerStore.queue.test.ts rename to src/features/playback/store/playerStore.queue.test.ts index a33a27e9..8f898dec 100644 --- a/src/store/playerStore.queue.test.ts +++ b/src/features/playback/store/playerStore.queue.test.ts @@ -31,11 +31,11 @@ vi.mock('@/store/orbitRuntime', async (importOriginal) => ({ orbitBulkGuard: vi.fn(async () => true), })); -import { usePlayerStore } from './playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { appendTimelineSessionPlay, getTimelineSessionHistorySnapshot, -} from './timelineSessionHistory'; +} from '@/features/playback/store/timelineSessionHistory'; import { onInvoke } from '@/test/mocks/tauri'; import { resetPlayerStore } from '@/test/helpers/storeReset'; import { makeTrack, makeTracks, seedQueue } from '@/test/helpers/factories'; diff --git a/src/store/playerStore.ts b/src/features/playback/store/playerStore.ts similarity index 83% rename from src/store/playerStore.ts rename to src/features/playback/store/playerStore.ts index a4700e64..a00fcdd7 100644 --- a/src/store/playerStore.ts +++ b/src/features/playback/store/playerStore.ts @@ -1,25 +1,25 @@ import { create } from 'zustand'; import { persist } from 'zustand/middleware'; -import { readInitialNetworkLovedCache, persistNetworkLovedCache } from './networkLovedCacheStorage'; -import { readInitialPlayerPrefs, persistPlayerPrefs } from './playerPrefsStorage'; +import { readInitialNetworkLovedCache, persistNetworkLovedCache } from '@/features/playback/store/networkLovedCacheStorage'; +import { readInitialPlayerPrefs, persistPlayerPrefs } from '@/features/playback/store/playerPrefsStorage'; import { createHydrationGatedStorage, createSafeJSONStorage } from '@/lib/util/safeStorage'; -import { emitPlaybackProgress } from './playbackProgress'; -import type { PlayerState, QueueItemRef, Track } from './playerStoreTypes'; -import { toQueueItemRefs } from '../utils/library/queueItemRef'; -import { canonicalQueueServerKey } from '../utils/server/serverIndexKey'; -import { readInitialQueueVisibility } from './queueVisibilityStorage'; -import { createNetworkLoveActions } from './networkLoveActions'; -import { createMiscActions } from './miscActions'; -import { runNext } from './nextAction'; -import { runPlayTrack } from './playTrackAction'; -import { runResume } from './resumeAction'; -import { runSeek } from './seekAction'; -import { runUpdateReplayGainForCurrentTrack } from './updateReplayGainAction'; -import { createQueueMutationActions } from './queueMutationActions'; -import { createScheduleActions } from './scheduleActions'; -import { createTransportLightActions } from './transportLightActions'; -import { createUiStateActions } from './uiStateActions'; -import { createUndoRedoActions } from './undoRedoActions'; +import { emitPlaybackProgress } from '@/features/playback/store/playbackProgress'; +import type { PlayerState, QueueItemRef, Track } from '@/features/playback/store/playerStoreTypes'; +import { toQueueItemRefs } from '@/utils/library/queueItemRef'; +import { canonicalQueueServerKey } from '@/utils/server/serverIndexKey'; +import { readInitialQueueVisibility } from '@/features/playback/store/queueVisibilityStorage'; +import { createNetworkLoveActions } from '@/features/playback/store/networkLoveActions'; +import { createMiscActions } from '@/features/playback/store/miscActions'; +import { runNext } from '@/features/playback/store/nextAction'; +import { runPlayTrack } from '@/features/playback/store/playTrackAction'; +import { runResume } from '@/features/playback/store/resumeAction'; +import { runSeek } from '@/features/playback/store/seekAction'; +import { runUpdateReplayGainForCurrentTrack } from '@/features/playback/store/updateReplayGainAction'; +import { createQueueMutationActions } from '@/features/playback/store/queueMutationActions'; +import { createScheduleActions } from '@/features/playback/store/scheduleActions'; +import { createTransportLightActions } from '@/features/playback/store/transportLightActions'; +import { createUiStateActions } from '@/features/playback/store/uiStateActions'; +import { createUndoRedoActions } from '@/features/playback/store/undoRedoActions'; const initialPlayerPrefs = readInitialPlayerPrefs(); const initialNetworkLovedCache = readInitialNetworkLovedCache(); diff --git a/src/store/playerStoreTypes.ts b/src/features/playback/store/playerStoreTypes.ts similarity index 99% rename from src/store/playerStoreTypes.ts rename to src/features/playback/store/playerStoreTypes.ts index 82091837..85b81c10 100644 --- a/src/store/playerStoreTypes.ts +++ b/src/features/playback/store/playerStoreTypes.ts @@ -1,5 +1,5 @@ import type { InternetRadioStation, SubsonicOpenArtistRef } from '@/lib/api/subsonicTypes'; -import type { PlaybackSourceKind } from '../utils/playback/resolvePlaybackUrl'; +import type { PlaybackSourceKind } from '@/features/playback/utils/playback/resolvePlaybackUrl'; export interface Track { id: string; diff --git a/src/store/previewPlayerVolumeSync.ts b/src/features/playback/store/previewPlayerVolumeSync.ts similarity index 72% rename from src/store/previewPlayerVolumeSync.ts rename to src/features/playback/store/previewPlayerVolumeSync.ts index b5383710..a001e444 100644 --- a/src/store/previewPlayerVolumeSync.ts +++ b/src/features/playback/store/previewPlayerVolumeSync.ts @@ -3,8 +3,8 @@ * player slider while a track preview is active. */ import { invoke } from '@tauri-apps/api/core'; -import { computePreviewVolume, usePreviewStore } from './previewStore'; -import { usePlayerStore } from './playerStore'; +import { computePreviewVolume, usePreviewStore } from '@/features/playback/store/previewStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; usePlayerStore.subscribe((state, prev) => { if (state.volume === prev.volume) return; diff --git a/src/store/previewStore.test.ts b/src/features/playback/store/previewStore.test.ts similarity index 98% rename from src/store/previewStore.test.ts rename to src/features/playback/store/previewStore.test.ts index a6f895d5..591bba67 100644 --- a/src/store/previewStore.test.ts +++ b/src/features/playback/store/previewStore.test.ts @@ -26,11 +26,11 @@ vi.mock('@/lib/api/subsonic', () => ({ scrobbleSong: vi.fn(async () => undefined), })); -import { usePreviewStore } from './previewStore'; -import { useAuthStore } from './authStore'; +import { usePreviewStore } from '@/features/playback/store/previewStore'; +import { useAuthStore } from '@/store/authStore'; import { useOrbitStore } from '@/features/orbit'; -import { usePlayerStore } from './playerStore'; -import './previewPlayerVolumeSync'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; +import '@/features/playback/store/previewPlayerVolumeSync'; import { onInvoke, invokeMock } from '@/test/mocks/tauri'; import { resetAuthStore, resetPreviewStore, resetPlayerStore, resetOrbitStore } from '@/test/helpers/storeReset'; diff --git a/src/store/previewStore.ts b/src/features/playback/store/previewStore.ts similarity index 97% rename from src/store/previewStore.ts rename to src/features/playback/store/previewStore.ts index 936e0ae9..0c645880 100644 --- a/src/store/previewStore.ts +++ b/src/features/playback/store/previewStore.ts @@ -1,10 +1,10 @@ import { buildStreamUrl } from '@/lib/api/subsonicStreamUrl'; import type { SubsonicSong } from '@/lib/api/subsonicTypes'; -import type { TrackPreviewLocation } from './authStoreTypes'; +import type { TrackPreviewLocation } from '@/store/authStoreTypes'; import { create } from 'zustand'; import { invoke } from '@tauri-apps/api/core'; -import { usePlayerStore } from './playerStore'; -import { useAuthStore } from './authStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; +import { useAuthStore } from '@/store/authStore'; import { isOrbitPlaybackSyncActive } from '@/store/orbitRuntime'; /** Minimal track info needed to surface the preview in the player bar UI. */ diff --git a/src/store/promoteStreamCache.test.ts b/src/features/playback/store/promoteStreamCache.test.ts similarity index 90% rename from src/store/promoteStreamCache.test.ts rename to src/features/playback/store/promoteStreamCache.test.ts index d5bf8a40..f0cdc578 100644 --- a/src/store/promoteStreamCache.test.ts +++ b/src/features/playback/store/promoteStreamCache.test.ts @@ -1,5 +1,5 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; -import type { Track } from './playerStoreTypes'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; const invokeMock = vi.fn(); const setEntryMock = vi.fn(); @@ -8,13 +8,13 @@ vi.mock('@tauri-apps/api/core', () => ({ invoke: (...args: unknown[]) => invokeMock(...args), })); -vi.mock('./hotCacheStore', () => ({ +vi.mock('@/features/playback/store/hotCacheStore', () => ({ useHotCacheStore: { getState: () => ({ setEntry: setEntryMock }), }, })); -vi.mock('../utils/media/mediaDir', () => ({ +vi.mock('@/utils/media/mediaDir', () => ({ getMediaDir: () => '/media', })); @@ -22,7 +22,7 @@ vi.mock('@/lib/api/subsonicStreamUrl', () => ({ buildStreamUrlForServer: (_sid: string, id: string) => `https://mock/stream/${id}`, })); -vi.mock('../api/coverCache', () => ({ +vi.mock('@/api/coverCache', () => ({ librarySqlServerId: (k: string) => k, })); @@ -33,7 +33,7 @@ vi.mock('@/store/localPlaybackResolve', () => ({ hasLocalPersistentPlaybackBytesMock(trackId, serverId), })); -import { promoteCompletedStreamToHotCache } from './promoteStreamCache'; +import { promoteCompletedStreamToHotCache } from '@/features/playback/store/promoteStreamCache'; function track(id: string, overrides: Partial = {}): Track { return { diff --git a/src/store/promoteStreamCache.ts b/src/features/playback/store/promoteStreamCache.ts similarity index 83% rename from src/store/promoteStreamCache.ts rename to src/features/playback/store/promoteStreamCache.ts index b8327e5a..3459ae22 100644 --- a/src/store/promoteStreamCache.ts +++ b/src/features/playback/store/promoteStreamCache.ts @@ -1,9 +1,9 @@ import { buildStreamUrlForServer } from '@/lib/api/subsonicStreamUrl'; -import type { Track } from './playerStoreTypes'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; import { invoke } from '@tauri-apps/api/core'; -import { useHotCacheStore } from './hotCacheStore'; -import { getMediaDir } from '../utils/media/mediaDir'; -import { librarySqlServerId } from '../api/coverCache'; +import { useHotCacheStore } from '@/features/playback/store/hotCacheStore'; +import { getMediaDir } from '@/utils/media/mediaDir'; +import { librarySqlServerId } from '@/api/coverCache'; import { hasLocalPersistentPlaybackBytes } from '@/store/localPlaybackResolve'; /** diff --git a/src/store/queueMutationActions.ts b/src/features/playback/store/queueMutationActions.ts similarity index 91% rename from src/store/queueMutationActions.ts rename to src/features/playback/store/queueMutationActions.ts index 49474abd..498822ba 100644 --- a/src/store/queueMutationActions.ts +++ b/src/features/playback/store/queueMutationActions.ts @@ -1,13 +1,13 @@ import { invoke } from '@tauri-apps/api/core'; import { orbitBulkGuard } from '@/store/orbitRuntime'; -import { useAuthStore } from './authStore'; -import { setIsAudioPaused } from './engineState'; -import { prefetchLoudnessForEnqueuedTracks } from './loudnessPrefetch'; -import type { PlayerState, QueueItemRef, Track } from './playerStoreTypes'; -import { toQueueItemRefs } from '../utils/library/queueItemRef'; -import { seedQueueResolver } from '../utils/library/queueTrackResolver'; -import { pushQueueUndoFromGetter } from './queueUndo'; -import { syncUserQueueMutationToServer } from './queueSync'; +import { useAuthStore } from '@/store/authStore'; +import { setIsAudioPaused } from '@/features/playback/store/engineState'; +import { prefetchLoudnessForEnqueuedTracks } from '@/features/playback/store/loudnessPrefetch'; +import type { PlayerState, QueueItemRef, Track } from '@/features/playback/store/playerStoreTypes'; +import { toQueueItemRefs } from '@/utils/library/queueItemRef'; +import { seedQueueResolver } from '@/utils/library/queueTrackResolver'; +import { pushQueueUndoFromGetter } from '@/features/playback/store/queueUndo'; +import { syncUserQueueMutationToServer } from '@/features/playback/store/queueSync'; import { addRadioSessionSeen, clearRadioSessionSeenIds, @@ -15,16 +15,16 @@ import { getCurrentRadioArtistId, hasRadioSessionSeen, setCurrentRadioArtistId, -} from './radioSessionState'; -import { clearSeekDebounce } from './seekDebounce'; -import { clearSeekFallbackRetry } from './seekFallbackState'; -import { clearSeekTarget } from './seekTargetState'; -import { playListenSessionFinalize } from './playListenSession'; +} from '@/features/playback/store/radioSessionState'; +import { clearSeekDebounce } from '@/features/playback/store/seekDebounce'; +import { clearSeekFallbackRetry } from '@/features/playback/store/seekFallbackState'; +import { clearSeekTarget } from '@/features/playback/store/seekTargetState'; +import { playListenSessionFinalize } from '@/features/playback/store/playListenSession'; import { clearQueueServerForPlayback, ensureQueueServerPinned, -} from '../utils/playback/playbackServer'; -import { clearTimelineSessionHistory } from './timelineSessionHistory'; +} from '@/features/playback/utils/playback/playbackServer'; +import { clearTimelineSessionHistory } from '@/features/playback/store/timelineSessionHistory'; type SetState = ( partial: Partial | ((state: PlayerState) => Partial), diff --git a/src/store/queuePlaybackIdle.test.ts b/src/features/playback/store/queuePlaybackIdle.test.ts similarity index 97% rename from src/store/queuePlaybackIdle.test.ts rename to src/features/playback/store/queuePlaybackIdle.test.ts index 58f76cdb..ebeecf91 100644 --- a/src/store/queuePlaybackIdle.test.ts +++ b/src/features/playback/store/queuePlaybackIdle.test.ts @@ -9,7 +9,7 @@ import { resumeIdleQueuePull, subscribeIdleQueuePullSuspended, touchQueueMutationClock, -} from './queuePlaybackIdle'; +} from '@/features/playback/store/queuePlaybackIdle'; describe('idle queue pull suspension', () => { beforeEach(() => { diff --git a/src/store/queuePlaybackIdle.ts b/src/features/playback/store/queuePlaybackIdle.ts similarity index 100% rename from src/store/queuePlaybackIdle.ts rename to src/features/playback/store/queuePlaybackIdle.ts diff --git a/src/store/queueResolverBridge.ts b/src/features/playback/store/queueResolverBridge.ts similarity index 86% rename from src/store/queueResolverBridge.ts rename to src/features/playback/store/queueResolverBridge.ts index 3725a0e1..f35abca8 100644 --- a/src/store/queueResolverBridge.ts +++ b/src/features/playback/store/queueResolverBridge.ts @@ -7,8 +7,8 @@ * stay pure (no cache mutation in render); the seed travels with the playing * track here, off the render path. */ -import { usePlayerStore } from './playerStore'; -import { resolveVisibleRange } from '../utils/library/queueTrackResolver'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; +import { resolveVisibleRange } from '@/utils/library/queueTrackResolver'; usePlayerStore.subscribe((state, prev) => { // Re-seed when the queue refs or the current index change — the prefetch diff --git a/src/store/queueSync.test.ts b/src/features/playback/store/queueSync.test.ts similarity index 93% rename from src/store/queueSync.test.ts rename to src/features/playback/store/queueSync.test.ts index ef15976e..cf47ef40 100644 --- a/src/store/queueSync.test.ts +++ b/src/features/playback/store/queueSync.test.ts @@ -1,5 +1,5 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import type { QueueItemRef, Track } from './playerStoreTypes'; +import type { QueueItemRef, Track } from '@/features/playback/store/playerStoreTypes'; const { savePlayQueueMock, playerState, progressSnapshot, isSubsonicServerReachableMock } = vi.hoisted(() => ({ savePlayQueueMock: vi.fn(async () => undefined), @@ -13,23 +13,23 @@ const { savePlayQueueMock, playerState, progressSnapshot, isSubsonicServerReacha })); vi.mock('@/lib/api/subsonicPlayQueue', () => ({ savePlayQueue: savePlayQueueMock })); -vi.mock('../utils/network/subsonicNetworkGuard', () => ({ +vi.mock('@/utils/network/subsonicNetworkGuard', () => ({ isSubsonicServerReachable: (serverId: string) => isSubsonicServerReachableMock(serverId), })); -vi.mock('../utils/playback/playbackServer', () => ({ +vi.mock('@/features/playback/utils/playback/playbackServer', () => ({ getPlaybackServerId: () => 'srv-a', playbackProfileIdForTrack: (track: Track) => track.serverId ?? 'srv-a', filterQueueRefsForPlaybackServer: (refs: QueueItemRef[]) => refs.filter(r => r.serverId === 'a.test' || r.serverId === 'srv-a'), })); -vi.mock('../utils/playback/trackServerScope', () => ({ +vi.mock('@/features/playback/utils/playback/trackServerScope', () => ({ filterQueueRefsForServerProfile: (refs: QueueItemRef[], profileId: string) => refs.filter(r => r.serverId === profileId || (profileId === 'srv-a' && r.serverId === 'srv-a')), })); -vi.mock('./playerStore', () => ({ +vi.mock('@/features/playback/store/playerStore', () => ({ usePlayerStore: { getState: () => playerState }, })); -vi.mock('./playbackProgress', () => ({ +vi.mock('@/features/playback/store/playbackProgress', () => ({ getPlaybackProgressSnapshot: () => progressSnapshot, })); @@ -44,12 +44,12 @@ import { pushQueueOnPlaybackStart, syncQueueToServer, syncUserQueueMutationToServer, -} from './queueSync'; +} from '@/features/playback/store/queueSync'; import { _resetQueuePlaybackIdleForTest, isIdleQueuePullSuspended, isQueueNaturallyEnded, -} from './queuePlaybackIdle'; +} from '@/features/playback/store/queuePlaybackIdle'; function track(id: string, serverId = 'srv-a'): Track { return { id, title: id, artist: 'A', album: 'X', albumId: 'X', duration: 100, serverId }; diff --git a/src/store/queueSync.ts b/src/features/playback/store/queueSync.ts similarity index 92% rename from src/store/queueSync.ts rename to src/features/playback/store/queueSync.ts index 28d238a9..e1c1b1f5 100644 --- a/src/store/queueSync.ts +++ b/src/features/playback/store/queueSync.ts @@ -1,15 +1,15 @@ import { savePlayQueue } from '@/lib/api/subsonicPlayQueue'; -import type { QueueItemRef, Track } from './playerStoreTypes'; -import { isSubsonicServerReachable } from '../utils/network/subsonicNetworkGuard'; +import type { QueueItemRef, Track } from '@/features/playback/store/playerStoreTypes'; +import { isSubsonicServerReachable } from '@/utils/network/subsonicNetworkGuard'; import { filterQueueRefsForPlaybackServer, getPlaybackServerId, playbackProfileIdForTrack, -} from '../utils/playback/playbackServer'; -import { filterQueueRefsForServerProfile } from '../utils/playback/trackServerScope'; -import { getPlaybackProgressSnapshot } from './playbackProgress'; -import { touchQueueMutationClock, isIdleQueuePullSuspended, resumeIdleQueuePull, markQueueNaturallyEnded } from './queuePlaybackIdle'; -import { usePlayerStore } from './playerStore'; +} from '@/features/playback/utils/playback/playbackServer'; +import { filterQueueRefsForServerProfile } from '@/features/playback/utils/playback/trackServerScope'; +import { getPlaybackProgressSnapshot } from '@/features/playback/store/playbackProgress'; +import { touchQueueMutationClock, isIdleQueuePullSuspended, resumeIdleQueuePull, markQueueNaturallyEnded } from '@/features/playback/store/queuePlaybackIdle'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; /** * Server-side play-queue persistence. Subsonic's `savePlayQueue` accepts diff --git a/src/store/queueSyncUiState.ts b/src/features/playback/store/queueSyncUiState.ts similarity index 100% rename from src/store/queueSyncUiState.ts rename to src/features/playback/store/queueSyncUiState.ts diff --git a/src/store/queueUndo.test.ts b/src/features/playback/store/queueUndo.test.ts similarity index 95% rename from src/store/queueUndo.test.ts rename to src/features/playback/store/queueUndo.test.ts index 4d58b9be..ebd3f807 100644 --- a/src/store/queueUndo.test.ts +++ b/src/features/playback/store/queueUndo.test.ts @@ -4,8 +4,8 @@ * push, and (c) the scroll-top reader / consumer pair that QueuePanel uses * to restore list scroll position after an undo/redo commit. */ -import type { PlayerState, Track } from './playerStoreTypes'; -import { toQueueItemRefs } from '../utils/library/queueItemRef'; +import type { PlayerState, Track } from '@/features/playback/store/playerStoreTypes'; +import { toQueueItemRefs } from '@/utils/library/queueItemRef'; import { beforeEach, describe, expect, it } from 'vitest'; import { QUEUE_UNDO_MAX, @@ -19,7 +19,7 @@ import { queueUndoSnapshotFromState, registerQueueListScrollTopReader, setPendingQueueListScrollTop, -} from './queueUndo'; +} from '@/features/playback/store/queueUndo'; function track(id: string): Track { return { id, title: id, artist: 'A', album: 'X', albumId: 'X', duration: 100 }; diff --git a/src/store/queueUndo.ts b/src/features/playback/store/queueUndo.ts similarity index 97% rename from src/store/queueUndo.ts rename to src/features/playback/store/queueUndo.ts index ee43b52c..dba0b6b3 100644 --- a/src/store/queueUndo.ts +++ b/src/features/playback/store/queueUndo.ts @@ -1,4 +1,4 @@ -import type { PlayerState, QueueItemRef, Track } from './playerStoreTypes'; +import type { PlayerState, QueueItemRef, Track } from '@/features/playback/store/playerStoreTypes'; /** Hard cap on undo/redo depth — keeps memory bounded for very long sessions. */ export const QUEUE_UNDO_MAX = 32; diff --git a/src/store/queueUndoAudioRestore.test.ts b/src/features/playback/store/queueUndoAudioRestore.test.ts similarity index 85% rename from src/store/queueUndoAudioRestore.test.ts rename to src/features/playback/store/queueUndoAudioRestore.test.ts index 628493b1..f4021fc2 100644 --- a/src/store/queueUndoAudioRestore.test.ts +++ b/src/features/playback/store/queueUndoAudioRestore.test.ts @@ -4,7 +4,7 @@ * atSeconds > 0.05, the wantPlaying=false branch that issues audio_pause, * and the generation-mismatch bail-out. */ -import type { Track } from './playerStoreTypes'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; import { beforeEach, describe, expect, it, vi } from 'vitest'; const hoisted = vi.hoisted(() => { const auth = { @@ -38,31 +38,31 @@ const hoisted = vi.hoisted(() => { }); vi.mock('@tauri-apps/api/core', () => ({ invoke: hoisted.invokeMock })); -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', () => ({ +vi.mock('@/utils/cache/hotCacheGate', () => ({ setDeferHotCachePrefetch: hoisted.setDeferHotCachePrefetchMock })); +vi.mock('@/features/playback/utils/playback/resolvePlaybackUrl', () => ({ resolvePlaybackUrl: hoisted.resolvePlaybackUrlMock })); +vi.mock('@/features/playback/utils/audio/resolveReplayGainDb', () => ({ resolveReplayGainDb: hoisted.resolveReplayGainDbMock })); +vi.mock('@/store/authStore', () => ({ useAuthStore: { getState: () => hoisted.auth } })); +vi.mock('@/features/playback/store/engineState', () => ({ getPlayGeneration: hoisted.getPlayGeneration, setIsAudioPaused: hoisted.setIsAudioPausedMock, })); -vi.mock('./hotCacheTouch', () => ({ touchHotCacheOnPlayback: hoisted.touchHotCacheOnPlaybackMock })); -vi.mock('./loudnessGainCache', () => ({ +vi.mock('@/features/playback/store/hotCacheTouch', () => ({ touchHotCacheOnPlayback: hoisted.touchHotCacheOnPlaybackMock })); +vi.mock('@/features/playback/store/loudnessGainCache', () => ({ isReplayGainActive: hoisted.isReplayGainActiveMock, loudnessGainDbForEngineBind: hoisted.loudnessGainDbForEngineBindMock, })); -vi.mock('./playbackUrlRouting', () => ({ +vi.mock('@/features/playback/store/playbackUrlRouting', () => ({ playbackSourceHintForResolvedUrl: hoisted.playbackSourceHintMock, recordEnginePlayUrl: hoisted.recordEnginePlayUrlMock, })); -vi.mock('./playerStore', () => ({ +vi.mock('@/features/playback/store/playerStore', () => ({ usePlayerStore: { getState: () => hoisted.player, setState: hoisted.playerSetStateMock, }, })); -import { queueUndoRestoreAudioEngine } from './queueUndoAudioRestore'; +import { queueUndoRestoreAudioEngine } from '@/features/playback/store/queueUndoAudioRestore'; function track(id: string, duration = 100): Track { return { id, title: id, artist: 'A', album: 'X', albumId: 'X', duration }; diff --git a/src/store/queueUndoAudioRestore.ts b/src/features/playback/store/queueUndoAudioRestore.ts similarity index 82% rename from src/store/queueUndoAudioRestore.ts rename to src/features/playback/store/queueUndoAudioRestore.ts index 1ec67839..4a1900bb 100644 --- a/src/store/queueUndoAudioRestore.ts +++ b/src/features/playback/store/queueUndoAudioRestore.ts @@ -1,5 +1,5 @@ -import type { Track } from './playerStoreTypes'; -import { engineLoadTrackAtPosition } from './engineLoadTrackAtPosition'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; +import { engineLoadTrackAtPosition } from '@/features/playback/store/engineLoadTrackAtPosition'; /** * Reload the Rust audio engine to match a queue-undo snapshot. Zustand diff --git a/src/store/queueUndoHotkey.test.ts b/src/features/playback/store/queueUndoHotkey.test.ts similarity index 95% rename from src/store/queueUndoHotkey.test.ts rename to src/features/playback/store/queueUndoHotkey.test.ts index c3c5d2f3..cf181d3c 100644 --- a/src/store/queueUndoHotkey.test.ts +++ b/src/features/playback/store/queueUndoHotkey.test.ts @@ -12,8 +12,8 @@ const hoisted = vi.hoisted(() => ({ redoMock: vi.fn(() => true), })); -vi.mock('../app/windowKind', () => ({ getWindowKind: hoisted.windowKindMock })); -vi.mock('./playerStore', () => ({ +vi.mock('@/app/windowKind', () => ({ getWindowKind: hoisted.windowKindMock })); +vi.mock('@/features/playback/store/playerStore', () => ({ usePlayerStore: { getState: () => ({ undoLastQueueEdit: hoisted.undoMock, @@ -22,7 +22,7 @@ vi.mock('./playerStore', () => ({ }, })); -import { _resetQueueUndoHotkeyForTest, installQueueUndoHotkey } from './queueUndoHotkey'; +import { _resetQueueUndoHotkeyForTest, installQueueUndoHotkey } from '@/features/playback/store/queueUndoHotkey'; beforeEach(() => { hoisted.windowKindMock.mockReturnValue('main'); diff --git a/src/store/queueUndoHotkey.ts b/src/features/playback/store/queueUndoHotkey.ts similarity index 94% rename from src/store/queueUndoHotkey.ts rename to src/features/playback/store/queueUndoHotkey.ts index cf3cdc6f..15b1a363 100644 --- a/src/store/queueUndoHotkey.ts +++ b/src/features/playback/store/queueUndoHotkey.ts @@ -1,5 +1,5 @@ -import { getWindowKind } from '../app/windowKind'; -import { usePlayerStore } from './playerStore'; +import { getWindowKind } from '@/app/windowKind'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; const QUEUE_UNDO_HOTKEY_FLAG = '__psyQueueUndoListenerInstalled'; diff --git a/src/store/queueVisibilityStorage.test.ts b/src/features/playback/store/queueVisibilityStorage.test.ts similarity index 96% rename from src/store/queueVisibilityStorage.test.ts rename to src/features/playback/store/queueVisibilityStorage.test.ts index bb70ba2d..f0dae871 100644 --- a/src/store/queueVisibilityStorage.test.ts +++ b/src/features/playback/store/queueVisibilityStorage.test.ts @@ -2,7 +2,7 @@ import { afterEach, beforeEach, describe, expect, it } from 'vitest'; import { persistQueueVisibility, readInitialQueueVisibility, -} from './queueVisibilityStorage'; +} from '@/features/playback/store/queueVisibilityStorage'; const KEY = 'psysonic_queue_visible'; diff --git a/src/store/queueVisibilityStorage.ts b/src/features/playback/store/queueVisibilityStorage.ts similarity index 100% rename from src/store/queueVisibilityStorage.ts rename to src/features/playback/store/queueVisibilityStorage.ts diff --git a/src/store/radioPlayer.test.ts b/src/features/playback/store/radioPlayer.test.ts similarity index 97% rename from src/store/radioPlayer.test.ts rename to src/features/playback/store/radioPlayer.test.ts index d48b73cb..13b47005 100644 --- a/src/store/radioPlayer.test.ts +++ b/src/features/playback/store/radioPlayer.test.ts @@ -15,8 +15,8 @@ const hoisted = vi.hoisted(() => { }; }); -vi.mock('../utils/ui/toast', () => ({ showToast: hoisted.showToastMock })); -vi.mock('./playerStore', () => ({ +vi.mock('@/utils/ui/toast', () => ({ showToast: hoisted.showToastMock })); +vi.mock('@/features/playback/store/playerStore', () => ({ usePlayerStore: { getState: hoisted.playerStateGet, setState: hoisted.playerSetStateMock, @@ -32,7 +32,7 @@ import { resumeRadio, setRadioVolume, stopRadio, -} from './radioPlayer'; +} from '@/features/playback/store/radioPlayer'; const audio = _radioAudioForTest(); let pauseSpy: ReturnType; diff --git a/src/store/radioPlayer.ts b/src/features/playback/store/radioPlayer.ts similarity index 97% rename from src/store/radioPlayer.ts rename to src/features/playback/store/radioPlayer.ts index dfb8e75c..8314cde7 100644 --- a/src/store/radioPlayer.ts +++ b/src/features/playback/store/radioPlayer.ts @@ -1,5 +1,5 @@ -import { showToast } from '../utils/ui/toast'; -import { usePlayerStore } from './playerStore'; +import { showToast } from '@/utils/ui/toast'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; /** * Internet radio streams play through a native HTMLAudioElement rather diff --git a/src/store/radioSessionState.test.ts b/src/features/playback/store/radioSessionState.test.ts similarity index 97% rename from src/store/radioSessionState.test.ts rename to src/features/playback/store/radioSessionState.test.ts index a4313ffa..85ffdcbf 100644 --- a/src/store/radioSessionState.test.ts +++ b/src/features/playback/store/radioSessionState.test.ts @@ -9,7 +9,7 @@ import { isRadioFetching, setCurrentRadioArtistId, setRadioFetching, -} from './radioSessionState'; +} from '@/features/playback/store/radioSessionState'; afterEach(() => { _resetRadioSessionStateForTest(); diff --git a/src/store/radioSessionState.ts b/src/features/playback/store/radioSessionState.ts similarity index 100% rename from src/store/radioSessionState.ts rename to src/features/playback/store/radioSessionState.ts diff --git a/src/store/resumeAction.ts b/src/features/playback/store/resumeAction.ts similarity index 87% rename from src/store/resumeAction.ts rename to src/features/playback/store/resumeAction.ts index b151e4b0..d7457125 100644 --- a/src/store/resumeAction.ts +++ b/src/features/playback/store/resumeAction.ts @@ -1,39 +1,39 @@ import { getSong } from '@/lib/api/subsonicLibrary'; import { invoke } from '@tauri-apps/api/core'; import { estimateLivePosition, orbitSnapshot } from '@/store/orbitRuntime'; -import { setDeferHotCachePrefetch } from '../utils/cache/hotCacheGate'; +import { setDeferHotCachePrefetch } from '@/utils/cache/hotCacheGate'; import { getPlaybackCacheServerKey, getPlaybackIndexKey, -} from '../utils/playback/playbackServer'; -import { resolvePlaybackUrl } from '../utils/playback/resolvePlaybackUrl'; -import { resolveReplayGainDb } from '../utils/audio/resolveReplayGainDb'; -import { audioPlayHiResBlendArgs } from '../utils/audio/hiResCrossfadeResample'; -import { songToTrack } from '../utils/playback/songToTrack'; -import { useAuthStore } from './authStore'; +} from '@/features/playback/utils/playback/playbackServer'; +import { resolvePlaybackUrl } from '@/features/playback/utils/playback/resolvePlaybackUrl'; +import { resolveReplayGainDb } from '@/features/playback/utils/audio/resolveReplayGainDb'; +import { audioPlayHiResBlendArgs } from '@/utils/audio/hiResCrossfadeResample'; +import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; +import { useAuthStore } from '@/store/authStore'; import { bumpPlayGeneration, getIsAudioPaused, getPlayGeneration, setIsAudioPaused, -} from './engineState'; -import { touchHotCacheOnPlayback } from './hotCacheTouch'; +} from '@/features/playback/store/engineState'; +import { touchHotCacheOnPlayback } from '@/features/playback/store/hotCacheTouch'; import { isReplayGainActive, loudnessGainDbForEngineBind, -} from './loudnessGainCache'; +} from '@/features/playback/store/loudnessGainCache'; import { playbackSourceHintForResolvedUrl, recordEnginePlayUrl, -} from './playbackUrlRouting'; -import type { PlayerState } from './playerStoreTypes'; -import { resolveQueueTrack } from '../utils/library/queueTrackView'; -import { promoteCompletedStreamToHotCache } from './promoteStreamCache'; -import { pushQueueOnPlaybackStart, flushLocalQueueWhenTakingPlayback } from './queueSync'; -import { markPlaybackActive } from './queuePlaybackIdle'; -import { playbackReportPlaying } from './playbackReportSession'; -import { resumeRadio } from './radioPlayer'; -import { clearAllPlaybackScheduleTimers } from './scheduleTimers'; +} from '@/features/playback/store/playbackUrlRouting'; +import type { PlayerState } from '@/features/playback/store/playerStoreTypes'; +import { resolveQueueTrack } from '@/utils/library/queueTrackView'; +import { promoteCompletedStreamToHotCache } from '@/features/playback/store/promoteStreamCache'; +import { pushQueueOnPlaybackStart, flushLocalQueueWhenTakingPlayback } from '@/features/playback/store/queueSync'; +import { markPlaybackActive } from '@/features/playback/store/queuePlaybackIdle'; +import { playbackReportPlaying } from '@/features/playback/store/playbackReportSession'; +import { resumeRadio } from '@/features/playback/store/radioPlayer'; +import { clearAllPlaybackScheduleTimers } from '@/features/playback/store/scheduleTimers'; type SetState = ( partial: Partial | ((state: PlayerState) => Partial), diff --git a/src/store/scheduleActions.ts b/src/features/playback/store/scheduleActions.ts similarity index 92% rename from src/store/scheduleActions.ts rename to src/features/playback/store/scheduleActions.ts index bf5a1a8e..693f7d9a 100644 --- a/src/store/scheduleActions.ts +++ b/src/features/playback/store/scheduleActions.ts @@ -1,11 +1,11 @@ -import { scheduleDeadlineMs, scheduleDelayMsFromSeconds } from '../utils/playback/playbackScheduleDelay'; -import type { PlayerState } from './playerStoreTypes'; +import { scheduleDeadlineMs, scheduleDelayMsFromSeconds } from '@/features/playback/utils/playback/playbackScheduleDelay'; +import type { PlayerState } from '@/features/playback/store/playerStoreTypes'; import { clearScheduledPauseTimers, clearScheduledResumeTimers, schedulePauseTimer, scheduleResumeTimer, -} from './scheduleTimers'; +} from '@/features/playback/store/scheduleTimers'; type SetState = ( partial: Partial | ((state: PlayerState) => Partial), diff --git a/src/store/scheduleTimers.test.ts b/src/features/playback/store/scheduleTimers.test.ts similarity index 98% rename from src/store/scheduleTimers.test.ts rename to src/features/playback/store/scheduleTimers.test.ts index e3062c17..0cb58cac 100644 --- a/src/store/scheduleTimers.test.ts +++ b/src/features/playback/store/scheduleTimers.test.ts @@ -12,7 +12,7 @@ import { clearScheduledResumeTimers, schedulePauseTimer, scheduleResumeTimer, -} from './scheduleTimers'; +} from '@/features/playback/store/scheduleTimers'; beforeEach(() => { vi.useFakeTimers(); diff --git a/src/store/scheduleTimers.ts b/src/features/playback/store/scheduleTimers.ts similarity index 100% rename from src/store/scheduleTimers.ts rename to src/features/playback/store/scheduleTimers.ts diff --git a/src/store/seekAction.ts b/src/features/playback/store/seekAction.ts similarity index 85% rename from src/store/seekAction.ts rename to src/features/playback/store/seekAction.ts index 8bc4eb2b..6b64e0fd 100644 --- a/src/store/seekAction.ts +++ b/src/features/playback/store/seekAction.ts @@ -1,11 +1,11 @@ import { invoke } from '@tauri-apps/api/core'; -import { playbackReportSeek } from './playbackReportSession'; -import { isRecoverableSeekError } from '../utils/audio/seekErrors'; -import { getPlaybackServerId } from '../utils/playback/playbackServer'; -import { maybeCrossfadeBytePreload } from './crossfadePreload'; -import { shouldRebindPlaybackToHotCache } from './playbackUrlRouting'; -import type { PlayerState } from './playerStoreTypes'; -import { armSeekDebounce } from './seekDebounce'; +import { playbackReportSeek } from '@/features/playback/store/playbackReportSession'; +import { isRecoverableSeekError } from '@/features/playback/utils/audio/seekErrors'; +import { getPlaybackServerId } from '@/features/playback/utils/playback/playbackServer'; +import { maybeCrossfadeBytePreload } from '@/features/playback/store/crossfadePreload'; +import { shouldRebindPlaybackToHotCache } from '@/features/playback/store/playbackUrlRouting'; +import type { PlayerState } from '@/features/playback/store/playerStoreTypes'; +import { armSeekDebounce } from '@/features/playback/store/seekDebounce'; import { clearSeekFallbackRetry, getSeekFallbackRestartAt, @@ -14,11 +14,11 @@ import { setSeekFallbackRestartAt, setSeekFallbackTrackId, setSeekFallbackVisualTarget, -} from './seekFallbackState'; +} from '@/features/playback/store/seekFallbackState'; import { clearSeekTarget, setSeekTarget, -} from './seekTargetState'; +} from '@/features/playback/store/seekTargetState'; type SetState = ( partial: Partial | ((state: PlayerState) => Partial), diff --git a/src/store/seekDebounce.test.ts b/src/features/playback/store/seekDebounce.test.ts similarity index 96% rename from src/store/seekDebounce.test.ts rename to src/features/playback/store/seekDebounce.test.ts index 718ca542..8b94e927 100644 --- a/src/store/seekDebounce.test.ts +++ b/src/features/playback/store/seekDebounce.test.ts @@ -4,7 +4,7 @@ import { armSeekDebounce, clearSeekDebounce, isSeekDebouncePending, -} from './seekDebounce'; +} from '@/features/playback/store/seekDebounce'; beforeEach(() => { vi.useFakeTimers(); diff --git a/src/store/seekDebounce.ts b/src/features/playback/store/seekDebounce.ts similarity index 100% rename from src/store/seekDebounce.ts rename to src/features/playback/store/seekDebounce.ts diff --git a/src/store/seekFallbackState.test.ts b/src/features/playback/store/seekFallbackState.test.ts similarity index 95% rename from src/store/seekFallbackState.test.ts rename to src/features/playback/store/seekFallbackState.test.ts index e5216525..304f7f94 100644 --- a/src/store/seekFallbackState.test.ts +++ b/src/features/playback/store/seekFallbackState.test.ts @@ -19,9 +19,9 @@ const hoisted = vi.hoisted(() => { }); vi.mock('@tauri-apps/api/core', () => ({ invoke: hoisted.invokeMock })); -vi.mock('../utils/audio/seekErrors', () => ({ isRecoverableSeekError: hoisted.isRecoverableSeekErrorMock })); -vi.mock('./seekTargetState', () => ({ setSeekTarget: hoisted.setSeekTargetMock })); -vi.mock('./playerStore', () => ({ +vi.mock('@/features/playback/utils/audio/seekErrors', () => ({ isRecoverableSeekError: hoisted.isRecoverableSeekErrorMock })); +vi.mock('@/features/playback/store/seekTargetState', () => ({ setSeekTarget: hoisted.setSeekTargetMock })); +vi.mock('@/features/playback/store/playerStore', () => ({ usePlayerStore: { getState: hoisted.playerStateGet }, })); @@ -38,7 +38,7 @@ import { setSeekFallbackRestartAt, setSeekFallbackTrackId, setSeekFallbackVisualTarget, -} from './seekFallbackState'; +} from '@/features/playback/store/seekFallbackState'; beforeEach(() => { vi.useFakeTimers(); diff --git a/src/store/seekFallbackState.ts b/src/features/playback/store/seekFallbackState.ts similarity index 94% rename from src/store/seekFallbackState.ts rename to src/features/playback/store/seekFallbackState.ts index 7014c150..3443b36a 100644 --- a/src/store/seekFallbackState.ts +++ b/src/features/playback/store/seekFallbackState.ts @@ -1,7 +1,7 @@ import { invoke } from '@tauri-apps/api/core'; -import { isRecoverableSeekError } from '../utils/audio/seekErrors'; -import { usePlayerStore } from './playerStore'; -import { setSeekTarget } from './seekTargetState'; +import { isRecoverableSeekError } from '@/features/playback/utils/audio/seekErrors'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; +import { setSeekTarget } from '@/features/playback/store/seekTargetState'; /** * Streaming-fallback seek recovery + visual coverup. diff --git a/src/store/seekTargetState.test.ts b/src/features/playback/store/seekTargetState.test.ts similarity index 96% rename from src/store/seekTargetState.test.ts rename to src/features/playback/store/seekTargetState.test.ts index c0e9f30f..31a278e8 100644 --- a/src/store/seekTargetState.test.ts +++ b/src/features/playback/store/seekTargetState.test.ts @@ -6,7 +6,7 @@ import { getSeekTarget, getSeekTargetSetAt, setSeekTarget, -} from './seekTargetState'; +} from '@/features/playback/store/seekTargetState'; beforeEach(() => { vi.useFakeTimers(); diff --git a/src/store/seekTargetState.ts b/src/features/playback/store/seekTargetState.ts similarity index 100% rename from src/store/seekTargetState.ts rename to src/features/playback/store/seekTargetState.ts diff --git a/src/store/skipStarRating.test.ts b/src/features/playback/store/skipStarRating.test.ts similarity index 88% rename from src/store/skipStarRating.test.ts rename to src/features/playback/store/skipStarRating.test.ts index d4866e65..74f8f8eb 100644 --- a/src/store/skipStarRating.test.ts +++ b/src/features/playback/store/skipStarRating.test.ts @@ -4,7 +4,7 @@ * (pending-sync, F4) — its optimistic patch + retry behaviour is covered in * `pendingStarSync.test.ts`, so here we only assert the delegation + guards. */ -import type { Track } from './playerStoreTypes'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; import { beforeEach, describe, expect, it, vi } from 'vitest'; const { queueSongRatingMock, recordSkipStarMock, playerStateGet } = vi.hoisted(() => { const playerState = { @@ -19,16 +19,16 @@ const { queueSongRatingMock, recordSkipStarMock, playerStateGet } = vi.hoisted(( }; }); -vi.mock('./pendingStarSync', () => ({ queueSongRating: queueSongRatingMock })); -vi.mock('./authStore', () => ({ +vi.mock('@/features/playback/store/pendingStarSync', () => ({ queueSongRating: queueSongRatingMock })); +vi.mock('@/store/authStore', () => ({ useAuthStore: { getState: () => ({ recordSkipStarManualAdvance: recordSkipStarMock }) }, })); -vi.mock('./playerStore', () => ({ +vi.mock('@/features/playback/store/playerStore', () => ({ usePlayerStore: { getState: playerStateGet }, })); -import { applySkipStarOnManualNext } from './skipStarRating'; -import { seedQueueResolver, _resetQueueResolverForTest } from '../utils/library/queueTrackResolver'; +import { applySkipStarOnManualNext } from '@/features/playback/store/skipStarRating'; +import { seedQueueResolver, _resetQueueResolverForTest } from '@/utils/library/queueTrackResolver'; function track(id: string, overrides: Partial = {}): Track { return { diff --git a/src/store/skipStarRating.ts b/src/features/playback/store/skipStarRating.ts similarity index 79% rename from src/store/skipStarRating.ts rename to src/features/playback/store/skipStarRating.ts index 04aa7c7b..1bf0237a 100644 --- a/src/store/skipStarRating.ts +++ b/src/features/playback/store/skipStarRating.ts @@ -1,8 +1,8 @@ -import type { Track } from './playerStoreTypes'; -import { useAuthStore } from './authStore'; -import { usePlayerStore } from './playerStore'; -import { getCachedTrack } from '../utils/library/queueTrackResolver'; -import { queueSongRating } from './pendingStarSync'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; +import { useAuthStore } from '@/store/authStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; +import { getCachedTrack } from '@/utils/library/queueTrackResolver'; +import { queueSongRating } from '@/features/playback/store/pendingStarSync'; /** * Skip → 1★ behaviour: every user-initiated `next()` on an unrated track * counts in `authStore.skipStarManualSkipCountsByKey` (persisted). Once the diff --git a/src/store/timelinePlayHistory.integration.test.ts b/src/features/playback/store/timelinePlayHistory.integration.test.ts similarity index 86% rename from src/store/timelinePlayHistory.integration.test.ts rename to src/features/playback/store/timelinePlayHistory.integration.test.ts index cc192c8f..c4e3bac6 100644 --- a/src/store/timelinePlayHistory.integration.test.ts +++ b/src/features/playback/store/timelinePlayHistory.integration.test.ts @@ -1,10 +1,10 @@ import { describe, it, expect, beforeEach } from 'vitest'; -import { usePlayerStore } from './playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { resetAllStores } from '@/test/helpers/storeReset'; import { makeTracks, seedQueue } from '@/test/helpers/factories'; -import { getTimelineSessionHistorySnapshot } from './timelineSessionHistory'; +import { getTimelineSessionHistorySnapshot } from '@/features/playback/store/timelineSessionHistory'; import { onInvoke, registerDefaultCoverInvokeHandlers } from '@/test/mocks/tauri'; -import { useAuthStore } from './authStore'; +import { useAuthStore } from '@/store/authStore'; describe('timeline history on queue replace', () => { beforeEach(() => { diff --git a/src/store/timelineSessionHistory.test.ts b/src/features/playback/store/timelineSessionHistory.test.ts similarity index 97% rename from src/store/timelineSessionHistory.test.ts rename to src/features/playback/store/timelineSessionHistory.test.ts index ad31c918..52f4546e 100644 --- a/src/store/timelineSessionHistory.test.ts +++ b/src/features/playback/store/timelineSessionHistory.test.ts @@ -10,7 +10,7 @@ import { markTimelineBootstrapAttempted, TIMELINE_APPEND_DEDUPE_MS, TIMELINE_MERGE_DEDUPE_MS, -} from './timelineSessionHistory'; +} from '@/features/playback/store/timelineSessionHistory'; describe('timelineSessionHistory', () => { beforeEach(() => { diff --git a/src/store/timelineSessionHistory.ts b/src/features/playback/store/timelineSessionHistory.ts similarity index 92% rename from src/store/timelineSessionHistory.ts rename to src/features/playback/store/timelineSessionHistory.ts index 4bd1e1f1..21867f31 100644 --- a/src/store/timelineSessionHistory.ts +++ b/src/features/playback/store/timelineSessionHistory.ts @@ -1,10 +1,10 @@ -import type { QueueItemRef, Track } from './playerStoreTypes'; +import type { QueueItemRef, Track } from '@/features/playback/store/playerStoreTypes'; import { getPlaybackServerId, playbackProfileIdForTrack, -} from '../utils/playback/playbackServer'; -import { usePreviewStore } from './previewStore'; -import { usePlayerStore } from './playerStore'; +} from '@/features/playback/utils/playback/playbackServer'; +import { usePreviewStore } from '@/features/playback/store/previewStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; export const TIMELINE_HISTORY_BOOTSTRAP_LIMIT = 50; export const TIMELINE_APPEND_DEDUPE_MS = 2_000; diff --git a/src/store/togglePlayLock.test.ts b/src/features/playback/store/togglePlayLock.test.ts similarity index 96% rename from src/store/togglePlayLock.test.ts rename to src/features/playback/store/togglePlayLock.test.ts index 891099d8..23a04791 100644 --- a/src/store/togglePlayLock.test.ts +++ b/src/features/playback/store/togglePlayLock.test.ts @@ -2,7 +2,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { _resetTogglePlayLockForTest, tryAcquireTogglePlayLock, -} from './togglePlayLock'; +} from '@/features/playback/store/togglePlayLock'; beforeEach(() => { vi.useFakeTimers(); diff --git a/src/store/togglePlayLock.ts b/src/features/playback/store/togglePlayLock.ts similarity index 100% rename from src/store/togglePlayLock.ts rename to src/features/playback/store/togglePlayLock.ts diff --git a/src/store/transportLightActions.ts b/src/features/playback/store/transportLightActions.ts similarity index 75% rename from src/store/transportLightActions.ts rename to src/features/playback/store/transportLightActions.ts index 5dfab022..fd768171 100644 --- a/src/store/transportLightActions.ts +++ b/src/features/playback/store/transportLightActions.ts @@ -1,18 +1,18 @@ import { invoke } from '@tauri-apps/api/core'; -import { setIsAudioPaused } from './engineState'; -import type { PlayerState } from './playerStoreTypes'; -import { flushQueueSyncToServer } from './queueSync'; -import { markPlaybackIdle } from './queuePlaybackIdle'; -import { playListenSessionFinalize, playListenSessionOnPause } from './playListenSession'; -import { playbackReportPaused, playbackReportStopped } from './playbackReportSession'; -import { pauseRadio, stopRadio } from './radioPlayer'; -import { clearAllPlaybackScheduleTimers } from './scheduleTimers'; -import { clearSeekDebounce } from './seekDebounce'; -import { clearSeekFallbackRetry } from './seekFallbackState'; -import { clearSeekTarget } from './seekTargetState'; -import { tryAcquireTogglePlayLock } from './togglePlayLock'; -import { refreshWaveformForTrack } from '@/store/waveformRefresh'; -import { clearAutodjTransitionUi } from './autodjTransitionUi'; +import { setIsAudioPaused } from '@/features/playback/store/engineState'; +import type { PlayerState } from '@/features/playback/store/playerStoreTypes'; +import { flushQueueSyncToServer } from '@/features/playback/store/queueSync'; +import { markPlaybackIdle } from '@/features/playback/store/queuePlaybackIdle'; +import { playListenSessionFinalize, playListenSessionOnPause } from '@/features/playback/store/playListenSession'; +import { playbackReportPaused, playbackReportStopped } from '@/features/playback/store/playbackReportSession'; +import { pauseRadio, stopRadio } from '@/features/playback/store/radioPlayer'; +import { clearAllPlaybackScheduleTimers } from '@/features/playback/store/scheduleTimers'; +import { clearSeekDebounce } from '@/features/playback/store/seekDebounce'; +import { clearSeekFallbackRetry } from '@/features/playback/store/seekFallbackState'; +import { clearSeekTarget } from '@/features/playback/store/seekTargetState'; +import { tryAcquireTogglePlayLock } from '@/features/playback/store/togglePlayLock'; +import { refreshWaveformForTrack } from '@/features/playback/store/waveformRefresh'; +import { clearAutodjTransitionUi } from '@/features/playback/store/autodjTransitionUi'; type SetState = ( partial: Partial | ((state: PlayerState) => Partial), diff --git a/src/store/uiStateActions.ts b/src/features/playback/store/uiStateActions.ts similarity index 93% rename from src/store/uiStateActions.ts rename to src/features/playback/store/uiStateActions.ts index f743a573..260632f5 100644 --- a/src/store/uiStateActions.ts +++ b/src/features/playback/store/uiStateActions.ts @@ -1,11 +1,11 @@ import { persistQueueVisibility, -} from './queueVisibilityStorage'; -import type { PlayerState } from './playerStoreTypes'; +} from '@/features/playback/store/queueVisibilityStorage'; +import type { PlayerState } from '@/features/playback/store/playerStoreTypes'; import { ensurePlaybackServerActive, playbackServerDiffersFromActive, -} from '../utils/playback/playbackServer'; +} from '@/features/playback/utils/playback/playbackServer'; type SetState = ( partial: Partial | ((state: PlayerState) => Partial), diff --git a/src/store/undoRedoActions.ts b/src/features/playback/store/undoRedoActions.ts similarity index 84% rename from src/store/undoRedoActions.ts rename to src/features/playback/store/undoRedoActions.ts index 881a81e1..b76ac694 100644 --- a/src/store/undoRedoActions.ts +++ b/src/features/playback/store/undoRedoActions.ts @@ -1,12 +1,12 @@ -import { applyQueueHistorySnapshot } from './applyQueueHistorySnapshot'; -import type { PlayerState } from './playerStoreTypes'; +import { applyQueueHistorySnapshot } from '@/features/playback/store/applyQueueHistorySnapshot'; +import type { PlayerState } from '@/features/playback/store/playerStoreTypes'; import { popQueueRedoSnapshot, popQueueUndoSnapshot, pushQueueRedoSnapshot, pushQueueUndoSnapshot, queueUndoSnapshotFromState, -} from './queueUndo'; +} from '@/features/playback/store/queueUndo'; type SetState = ( partial: Partial | ((state: PlayerState) => Partial), diff --git a/src/store/updateReplayGainAction.ts b/src/features/playback/store/updateReplayGainAction.ts similarity index 81% rename from src/store/updateReplayGainAction.ts rename to src/features/playback/store/updateReplayGainAction.ts index ecc07c6a..79c4b350 100644 --- a/src/store/updateReplayGainAction.ts +++ b/src/features/playback/store/updateReplayGainAction.ts @@ -1,16 +1,16 @@ -import { effectiveLoudnessPreAnalysisAttenuationDb } from '../utils/audio/loudnessPreAnalysisSlider'; -import { loudnessGainPlaceholderUntilCacheDb } from '../utils/audio/loudnessPlaceholder'; -import { resolveReplayGainDb } from '../utils/audio/resolveReplayGainDb'; -import { useAuthStore } from './authStore'; +import { effectiveLoudnessPreAnalysisAttenuationDb } from '@/utils/audio/loudnessPreAnalysisSlider'; +import { loudnessGainPlaceholderUntilCacheDb } from '@/features/playback/utils/audio/loudnessPlaceholder'; +import { resolveReplayGainDb } from '@/features/playback/utils/audio/resolveReplayGainDb'; +import { useAuthStore } from '@/store/authStore'; import { getCachedLoudnessGain, hasStableLoudness, isReplayGainActive, -} from './loudnessGainCache'; -import { deriveNormalizationSnapshot } from './normalizationSnapshot'; -import { invokeAudioUpdateReplayGainDeduped } from './normalizationIpcDedupe'; -import type { PlayerState } from './playerStoreTypes'; -import { resolveQueueTrack } from '../utils/library/queueTrackView'; +} from '@/features/playback/store/loudnessGainCache'; +import { deriveNormalizationSnapshot } from '@/features/playback/store/normalizationSnapshot'; +import { invokeAudioUpdateReplayGainDeduped } from '@/features/playback/store/normalizationIpcDedupe'; +import type { PlayerState } from '@/features/playback/store/playerStoreTypes'; +import { resolveQueueTrack } from '@/utils/library/queueTrackView'; type SetState = ( partial: Partial | ((state: PlayerState) => Partial), diff --git a/src/store/waveformRefresh.test.ts b/src/features/playback/store/waveformRefresh.test.ts similarity index 94% rename from src/store/waveformRefresh.test.ts rename to src/features/playback/store/waveformRefresh.test.ts index 0fb24f32..ac8b9056 100644 --- a/src/store/waveformRefresh.test.ts +++ b/src/features/playback/store/waveformRefresh.test.ts @@ -23,17 +23,17 @@ const hoisted = vi.hoisted(() => ({ vi.mock('@tauri-apps/api/core', () => ({ invoke: hoisted.invokeMock })); vi.mock('@/utils/waveform/waveformParse', () => ({ coerceWaveformBins: hoisted.coerceWaveformBinsMock })); -vi.mock('@/store/playerStore', () => ({ +vi.mock('@/features/playback/store/playerStore', () => ({ usePlayerStore: { getState: () => hoisted.playerSnapshot, setState: hoisted.playerSetStateMock, }, })); -vi.mock('@/store/waveformRefreshGen', () => ({ +vi.mock('@/features/playback/store/waveformRefreshGen', () => ({ getWaveformRefreshGen: hoisted.getGenMock, })); -import { refreshWaveformForTrack } from '@/store/waveformRefresh'; +import { refreshWaveformForTrack } from '@/features/playback/store/waveformRefresh'; beforeEach(() => { hoisted.invokeMock.mockReset(); diff --git a/src/store/waveformRefresh.ts b/src/features/playback/store/waveformRefresh.ts similarity index 91% rename from src/store/waveformRefresh.ts rename to src/features/playback/store/waveformRefresh.ts index 6ebebaa6..a6adb9ed 100644 --- a/src/store/waveformRefresh.ts +++ b/src/features/playback/store/waveformRefresh.ts @@ -1,8 +1,8 @@ import { invoke } from '@tauri-apps/api/core'; import { coerceWaveformBins } from '@/utils/waveform/waveformParse'; -import { getPlaybackIndexKey } from '@/utils/playback/playbackServer'; -import { usePlayerStore } from '@/store/playerStore'; -import { getWaveformRefreshGen } from '@/store/waveformRefreshGen'; +import { getPlaybackIndexKey } from '@/features/playback/utils/playback/playbackServer'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; +import { getWaveformRefreshGen } from '@/features/playback/store/waveformRefreshGen'; /** Subsonic-server waveform-cache row as Rust hands it back. */ export type WaveformCachePayload = { diff --git a/src/store/waveformRefreshGen.test.ts b/src/features/playback/store/waveformRefreshGen.test.ts similarity index 95% rename from src/store/waveformRefreshGen.test.ts rename to src/features/playback/store/waveformRefreshGen.test.ts index d0fed0a7..be8e9246 100644 --- a/src/store/waveformRefreshGen.test.ts +++ b/src/features/playback/store/waveformRefreshGen.test.ts @@ -3,7 +3,7 @@ import { _resetWaveformRefreshGenForTest, bumpWaveformRefreshGen, getWaveformRefreshGen, -} from '@/store/waveformRefreshGen'; +} from '@/features/playback/store/waveformRefreshGen'; afterEach(() => { _resetWaveformRefreshGenForTest(); diff --git a/src/store/waveformRefreshGen.ts b/src/features/playback/store/waveformRefreshGen.ts similarity index 100% rename from src/store/waveformRefreshGen.ts rename to src/features/playback/store/waveformRefreshGen.ts diff --git a/src/utils/audio/audioDeviceLabels.ts b/src/features/playback/utils/audio/audioDeviceLabels.ts similarity index 100% rename from src/utils/audio/audioDeviceLabels.ts rename to src/features/playback/utils/audio/audioDeviceLabels.ts diff --git a/src/utils/audio/autoEqParse.ts b/src/features/playback/utils/audio/autoEqParse.ts similarity index 100% rename from src/utils/audio/autoEqParse.ts rename to src/features/playback/utils/audio/autoEqParse.ts diff --git a/src/utils/audio/eqCurve.ts b/src/features/playback/utils/audio/eqCurve.ts similarity index 98% rename from src/utils/audio/eqCurve.ts rename to src/features/playback/utils/audio/eqCurve.ts index c3a72929..c6c9eb03 100644 --- a/src/utils/audio/eqCurve.ts +++ b/src/features/playback/utils/audio/eqCurve.ts @@ -1,4 +1,4 @@ -import { EQ_BANDS } from '../../store/eqStore'; +import { EQ_BANDS } from '@/store/eqStore'; // ─── Frequency response canvas ──────────────────────────────────────────────── diff --git a/src/utils/audio/loudnessPlaceholder.ts b/src/features/playback/utils/audio/loudnessPlaceholder.ts similarity index 100% rename from src/utils/audio/loudnessPlaceholder.ts rename to src/features/playback/utils/audio/loudnessPlaceholder.ts diff --git a/src/utils/audio/normalizationCompare.test.ts b/src/features/playback/utils/audio/normalizationCompare.test.ts similarity index 91% rename from src/utils/audio/normalizationCompare.test.ts rename to src/features/playback/utils/audio/normalizationCompare.test.ts index 05538cfd..1f9d6963 100644 --- a/src/utils/audio/normalizationCompare.test.ts +++ b/src/features/playback/utils/audio/normalizationCompare.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import { normalizationAlmostEqual } from './normalizationCompare'; +import { normalizationAlmostEqual } from '@/features/playback/utils/audio/normalizationCompare'; describe('normalizationAlmostEqual', () => { it('returns true for two nulls', () => { diff --git a/src/utils/audio/normalizationCompare.ts b/src/features/playback/utils/audio/normalizationCompare.ts similarity index 100% rename from src/utils/audio/normalizationCompare.ts rename to src/features/playback/utils/audio/normalizationCompare.ts diff --git a/src/utils/audio/playbackRateHelpers.test.ts b/src/features/playback/utils/audio/playbackRateHelpers.test.ts similarity index 98% rename from src/utils/audio/playbackRateHelpers.test.ts rename to src/features/playback/utils/audio/playbackRateHelpers.test.ts index e86f66a1..72e9b7a0 100644 --- a/src/utils/audio/playbackRateHelpers.test.ts +++ b/src/features/playback/utils/audio/playbackRateHelpers.test.ts @@ -10,7 +10,7 @@ import { playbackPitchStep, playbackSpeedStep, varispeedSpeedFromSemitones, -} from './playbackRateHelpers'; +} from '@/features/playback/utils/audio/playbackRateHelpers'; describe('playbackRateHelpers', () => { it('is inactive when disabled', () => { diff --git a/src/utils/audio/playbackRateHelpers.ts b/src/features/playback/utils/audio/playbackRateHelpers.ts similarity index 100% rename from src/utils/audio/playbackRateHelpers.ts rename to src/features/playback/utils/audio/playbackRateHelpers.ts diff --git a/src/utils/audio/playbackRateRestart.test.ts b/src/features/playback/utils/audio/playbackRateRestart.test.ts similarity index 93% rename from src/utils/audio/playbackRateRestart.test.ts rename to src/features/playback/utils/audio/playbackRateRestart.test.ts index a00cc2af..bbcbd08d 100644 --- a/src/utils/audio/playbackRateRestart.test.ts +++ b/src/features/playback/utils/audio/playbackRateRestart.test.ts @@ -1,10 +1,10 @@ import { describe, expect, it } from 'vitest'; -import { isPlaybackEffectActive } from './playbackRateHelpers'; +import { isPlaybackEffectActive } from '@/features/playback/utils/audio/playbackRateHelpers'; import { playbackPathChanged, shouldRestartPlaybackForRateChange, usesPreservePlaybackPath, -} from './playbackRateRestart'; +} from '@/features/playback/utils/audio/playbackRateRestart'; describe('playbackRateRestart', () => { const base = { diff --git a/src/utils/audio/playbackRateRestart.ts b/src/features/playback/utils/audio/playbackRateRestart.ts similarity index 90% rename from src/utils/audio/playbackRateRestart.ts rename to src/features/playback/utils/audio/playbackRateRestart.ts index 02a9a38b..a3b63d63 100644 --- a/src/utils/audio/playbackRateRestart.ts +++ b/src/features/playback/utils/audio/playbackRateRestart.ts @@ -1,10 +1,10 @@ -import { usePlayerStore } from '../../store/playerStore'; -import { setSeekFallbackVisualTarget } from '../../store/seekFallbackState'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; +import { setSeekFallbackVisualTarget } from '@/features/playback/store/seekFallbackState'; import { engineStrategy, isPlaybackEffectActive, type PlaybackStrategy, -} from './playbackRateHelpers'; +} from '@/features/playback/utils/audio/playbackRateHelpers'; /** Preserve-pitch DSP (worker) vs direct varispeed sample-rate scaling. */ export function usesPreservePlaybackPath(strategy: PlaybackStrategy): boolean { diff --git a/src/utils/audio/resolveReplayGainDb.test.ts b/src/features/playback/utils/audio/resolveReplayGainDb.test.ts similarity index 94% rename from src/utils/audio/resolveReplayGainDb.test.ts rename to src/features/playback/utils/audio/resolveReplayGainDb.test.ts index 5e5bf48a..a852335e 100644 --- a/src/utils/audio/resolveReplayGainDb.test.ts +++ b/src/features/playback/utils/audio/resolveReplayGainDb.test.ts @@ -5,9 +5,9 @@ * Originally lived in `playerStore.ts`; extracted in M0 of the frontend * refactor (2026-05-12). */ -import type { Track } from '../../store/playerStoreTypes'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; import { describe, expect, it } from 'vitest'; -import { resolveReplayGainDb } from './resolveReplayGainDb'; +import { resolveReplayGainDb } from '@/features/playback/utils/audio/resolveReplayGainDb'; describe('resolveReplayGainDb', () => { const t = (overrides: Partial = {}): Track => ({ id: 'x', diff --git a/src/utils/audio/resolveReplayGainDb.ts b/src/features/playback/utils/audio/resolveReplayGainDb.ts similarity index 93% rename from src/utils/audio/resolveReplayGainDb.ts rename to src/features/playback/utils/audio/resolveReplayGainDb.ts index a743cbb7..980ee990 100644 --- a/src/utils/audio/resolveReplayGainDb.ts +++ b/src/features/playback/utils/audio/resolveReplayGainDb.ts @@ -1,4 +1,4 @@ -import type { Track } from '../../store/playerStoreTypes'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; /** * Resolve the ReplayGain dB value for a track based on the configured mode. * In 'auto' mode, picks album-gain when an adjacent queue neighbour shares the diff --git a/src/utils/audio/seekErrors.test.ts b/src/features/playback/utils/audio/seekErrors.test.ts similarity index 91% rename from src/utils/audio/seekErrors.test.ts rename to src/features/playback/utils/audio/seekErrors.test.ts index f48a866e..bf4f672a 100644 --- a/src/utils/audio/seekErrors.test.ts +++ b/src/features/playback/utils/audio/seekErrors.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import { isRecoverableSeekError } from './seekErrors'; +import { isRecoverableSeekError } from '@/features/playback/utils/audio/seekErrors'; describe('isRecoverableSeekError', () => { it.each([ diff --git a/src/utils/audio/seekErrors.ts b/src/features/playback/utils/audio/seekErrors.ts similarity index 100% rename from src/utils/audio/seekErrors.ts rename to src/features/playback/utils/audio/seekErrors.ts diff --git a/src/utils/playback/autodjAutoAdvance.test.ts b/src/features/playback/utils/playback/autodjAutoAdvance.test.ts similarity index 93% rename from src/utils/playback/autodjAutoAdvance.test.ts rename to src/features/playback/utils/playback/autodjAutoAdvance.test.ts index 749934d8..06c5a13d 100644 --- a/src/utils/playback/autodjAutoAdvance.test.ts +++ b/src/features/playback/utils/playback/autodjAutoAdvance.test.ts @@ -1,11 +1,11 @@ import { describe, expect, it } from 'vitest'; -import type { QueueItemRef } from '../../store/playerStoreTypes'; +import type { QueueItemRef } from '@/features/playback/store/playerStoreTypes'; import { autodjJsTriggerAtSec, computeAutodjJsOverlap, nextQueueRefForTransition, shouldJsDriveAutodjTransition, -} from './autodjAutoAdvance'; +} from '@/features/playback/utils/playback/autodjAutoAdvance'; describe('shouldJsDriveAutodjTransition', () => { it('drives loud→loud even when overlap is shorter than crossfadeSecs', () => { diff --git a/src/utils/playback/autodjAutoAdvance.ts b/src/features/playback/utils/playback/autodjAutoAdvance.ts similarity index 95% rename from src/utils/playback/autodjAutoAdvance.ts rename to src/features/playback/utils/playback/autodjAutoAdvance.ts index 9c4a8236..d18a2928 100644 --- a/src/utils/playback/autodjAutoAdvance.ts +++ b/src/features/playback/utils/playback/autodjAutoAdvance.ts @@ -1,5 +1,5 @@ -import { DYNAMIC_OVERLAP_HARD_CAP_SEC, STANDARD_BLEND_SEC } from '../waveform/waveformSilence'; -import type { QueueItemRef } from '../../store/playerStoreTypes'; +import { DYNAMIC_OVERLAP_HARD_CAP_SEC, STANDARD_BLEND_SEC } from '@/utils/waveform/waveformSilence'; +import type { QueueItemRef } from '@/features/playback/store/playerStoreTypes'; export type QueueRepeatMode = 'off' | 'all' | 'one'; diff --git a/src/utils/playback/autodjInterruptPrep.test.ts b/src/features/playback/utils/playback/autodjInterruptPrep.test.ts similarity index 90% rename from src/utils/playback/autodjInterruptPrep.test.ts rename to src/features/playback/utils/playback/autodjInterruptPrep.test.ts index 4e879b82..a19a7b57 100644 --- a/src/utils/playback/autodjInterruptPrep.test.ts +++ b/src/features/playback/utils/playback/autodjInterruptPrep.test.ts @@ -1,7 +1,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { invoke } from '@tauri-apps/api/core'; -import type { Track } from '../../store/playerStoreTypes'; -import * as crossfadePreload from '../../store/crossfadePreload'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; +import * as crossfadePreload from '@/features/playback/store/crossfadePreload'; import { armInterruptHandoff, clearInterruptHandoff, @@ -9,7 +9,7 @@ import { isInterruptHandoffPending, runInterruptBlendPrep, shouldDeferInterruptHandoffUi, -} from './autodjInterruptPrep'; +} from '@/features/playback/utils/playback/autodjInterruptPrep'; vi.mock('@tauri-apps/api/core', () => ({ invoke: vi.fn(() => Promise.resolve()), diff --git a/src/utils/playback/autodjInterruptPrep.ts b/src/features/playback/utils/playback/autodjInterruptPrep.ts similarity index 94% rename from src/utils/playback/autodjInterruptPrep.ts rename to src/features/playback/utils/playback/autodjInterruptPrep.ts index a48bdd56..a4cb1a1c 100644 --- a/src/utils/playback/autodjInterruptPrep.ts +++ b/src/features/playback/utils/playback/autodjInterruptPrep.ts @@ -1,11 +1,11 @@ import { invoke } from '@tauri-apps/api/core'; -import type { Track } from '../../store/playerStoreTypes'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; import { INTERRUPT_BLEND_PREP_FADE_SEC, isCrossfadeNextReady, kickEagerCrossfadePreload, waitForCrossfadeNextReady, -} from '../../store/crossfadePreload'; +} from '@/features/playback/store/crossfadePreload'; export { INTERRUPT_BLEND_PREP_FADE_SEC }; diff --git a/src/utils/playback/autodjManualBlend.test.ts b/src/features/playback/utils/playback/autodjManualBlend.test.ts similarity index 92% rename from src/utils/playback/autodjManualBlend.test.ts rename to src/features/playback/utils/playback/autodjManualBlend.test.ts index 7f07818a..7c5e48c5 100644 --- a/src/utils/playback/autodjManualBlend.test.ts +++ b/src/features/playback/utils/playback/autodjManualBlend.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from 'vitest'; -import { useAuthStore } from '../../store/authStore'; -import { setTransitionMode } from './playbackTransition'; -import { computeAutodjManualBlendPlan, shouldAutodjInterruptBlend } from './autodjManualBlend'; +import { useAuthStore } from '@/store/authStore'; +import { setTransitionMode } from '@/features/playback/utils/playback/playbackTransition'; +import { computeAutodjManualBlendPlan, shouldAutodjInterruptBlend } from '@/features/playback/utils/playback/autodjManualBlend'; /** Loud plateau with a short trailing silence (500 bins, 100 s). */ function loudTrackBins(trailQuietBins = 8): number[] { diff --git a/src/utils/playback/autodjManualBlend.ts b/src/features/playback/utils/playback/autodjManualBlend.ts similarity index 93% rename from src/utils/playback/autodjManualBlend.ts rename to src/features/playback/utils/playback/autodjManualBlend.ts index e8f3df65..cc945d3e 100644 --- a/src/utils/playback/autodjManualBlend.ts +++ b/src/features/playback/utils/playback/autodjManualBlend.ts @@ -1,12 +1,12 @@ -import { useAuthStore } from '../../store/authStore'; -import { autodjMaxOverlapCapSec } from './autodjOverlapCap'; +import { useAuthStore } from '@/store/authStore'; +import { autodjMaxOverlapCapSec } from '@/utils/playback/autodjOverlapCap'; import { analyzeBoundary, planCrossfadeTransition, STANDARD_BLEND_SEC, type CrossfadeTransitionPlan, -} from '../waveform/waveformSilence'; -import { getTransitionMode } from './playbackTransition'; +} from '@/utils/waveform/waveformSilence'; +import { getTransitionMode } from '@/features/playback/utils/playback/playbackTransition'; /** Same trust threshold as end-of-track scenario A in `waveformSilence.ts`. */ const OWN_FADE_TRUST_SEC = 1.0; diff --git a/src/utils/playback/buildInfiniteQueueCandidates.test.ts b/src/features/playback/utils/playback/buildInfiniteQueueCandidates.test.ts similarity index 97% rename from src/utils/playback/buildInfiniteQueueCandidates.test.ts rename to src/features/playback/utils/playback/buildInfiniteQueueCandidates.test.ts index d450b87e..ef779e44 100644 --- a/src/utils/playback/buildInfiniteQueueCandidates.test.ts +++ b/src/features/playback/utils/playback/buildInfiniteQueueCandidates.test.ts @@ -8,7 +8,7 @@ */ import { getSimilarSongs2, getTopSongs } from '@/lib/api/subsonicArtists'; import { getRandomSongs } from '@/lib/api/subsonicLibrary'; -import type { Track } from '../../store/playerStoreTypes'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; // Mock only the artist Subsonic API submodule (the pre-move target was @@ -23,17 +23,17 @@ vi.mock('@/lib/api/subsonicLibrary', () => ({ getRandomSongs: vi.fn(), })); -vi.mock('../mix/mixRatingFilter', () => ({ +vi.mock('@/utils/mix/mixRatingFilter', () => ({ getMixMinRatingsConfigFromAuth: vi.fn(), enrichSongsForMixRatingFilter: vi.fn(), passesMixMinRatings: vi.fn(), })); -import { buildInfiniteQueueCandidates } from './buildInfiniteQueueCandidates'; +import { buildInfiniteQueueCandidates } from '@/features/playback/utils/playback/buildInfiniteQueueCandidates'; import { enrichSongsForMixRatingFilter, getMixMinRatingsConfigFromAuth, -} from '../mix/mixRatingFilter'; +} from '@/utils/mix/mixRatingFilter'; import { makeSubsonicSong } from '@/test/helpers/factories'; const seed = (overrides: Partial = {}): Track => ({ diff --git a/src/utils/playback/buildInfiniteQueueCandidates.ts b/src/features/playback/utils/playback/buildInfiniteQueueCandidates.ts similarity index 89% rename from src/utils/playback/buildInfiniteQueueCandidates.ts rename to src/features/playback/utils/playback/buildInfiniteQueueCandidates.ts index 99aabb19..632f4442 100644 --- a/src/utils/playback/buildInfiniteQueueCandidates.ts +++ b/src/features/playback/utils/playback/buildInfiniteQueueCandidates.ts @@ -1,13 +1,13 @@ import { getSimilarSongs2, getTopSongs } from '@/lib/api/subsonicArtists'; import { getRandomSongs } from '@/lib/api/subsonicLibrary'; -import type { Track } from '../../store/playerStoreTypes'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; import { enrichSongsForMixRatingFilter, getMixMinRatingsConfigFromAuth, passesMixMinRatings, -} from '../mix/mixRatingFilter'; -import { shuffleArray } from './shuffleArray'; -import { songToTrack } from './songToTrack'; +} from '@/utils/mix/mixRatingFilter'; +import { shuffleArray } from '@/features/playback/utils/playback/shuffleArray'; +import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; /** * Infinite queue source strategy (Instant Mix-like): * 1) Prefer artist-driven candidates (Top + Similar) around the current track. diff --git a/src/utils/playback/ensureQueueServerPinned.test.ts b/src/features/playback/utils/playback/ensureQueueServerPinned.test.ts similarity index 95% rename from src/utils/playback/ensureQueueServerPinned.test.ts rename to src/features/playback/utils/playback/ensureQueueServerPinned.test.ts index 9f285544..7e25d95b 100644 --- a/src/utils/playback/ensureQueueServerPinned.test.ts +++ b/src/features/playback/utils/playback/ensureQueueServerPinned.test.ts @@ -15,9 +15,9 @@ */ import { describe, it, expect, beforeEach } from 'vitest'; import { useAuthStore } from '@/store/authStore'; -import { usePlayerStore } from '@/store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { resetAuthStore, resetPlayerStore } from '@/test/helpers/storeReset'; -import { ensureQueueServerPinned } from '@/utils/playback/playbackServer'; +import { ensureQueueServerPinned } from '@/features/playback/utils/playback/playbackServer'; const SERVER_A = { id: 'uuid-a', diff --git a/src/utils/playback/fadeOut.ts b/src/features/playback/utils/playback/fadeOut.ts similarity index 100% rename from src/utils/playback/fadeOut.ts rename to src/features/playback/utils/playback/fadeOut.ts diff --git a/src/utils/playback/fetchTracksForSource.ts b/src/features/playback/utils/playback/fetchTracksForSource.ts similarity index 100% rename from src/utils/playback/fetchTracksForSource.ts rename to src/features/playback/utils/playback/fetchTracksForSource.ts diff --git a/src/utils/playback/playAlbum.test.ts b/src/features/playback/utils/playback/playAlbum.test.ts similarity index 92% rename from src/utils/playback/playAlbum.test.ts rename to src/features/playback/utils/playback/playAlbum.test.ts index 2e8521d9..49121cfe 100644 --- a/src/utils/playback/playAlbum.test.ts +++ b/src/features/playback/utils/playback/playAlbum.test.ts @@ -2,7 +2,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { makeSubsonicSong } from '@/test/helpers/factories'; import { onInvoke } from '@/test/mocks/tauri'; import { resetOrbitStore, resetPlayerStore } from '@/test/helpers/storeReset'; -import type { Track } from '../../store/playerStoreTypes'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; // Spread the real module so registerMediaResolver stays callable — the offline // barrel loads offlineMediaResolve transitively, which registers at module init. @@ -11,15 +11,15 @@ vi.mock('@/store/mediaResolver', async (importOriginal) => ({ resolveAlbumForActiveServer: vi.fn(), })); -vi.mock('./fadeOut', () => ({ +vi.mock('@/features/playback/utils/playback/fadeOut', () => ({ fadeOut: vi.fn(async () => undefined), })); import { resolveAlbumForActiveServer } from '@/store/mediaResolver'; import { useOrbitStore } from '@/features/orbit'; -import { usePlayerStore } from '../../store/playerStore'; -import { playAlbum, playAlbumShuffled } from './playAlbum'; -import * as shuffleModule from './shuffleArray'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; +import { playAlbum, playAlbumShuffled } from '@/features/playback/utils/playback/playAlbum'; +import * as shuffleModule from '@/features/playback/utils/playback/shuffleArray'; const albumPayload = { album: { diff --git a/src/utils/playback/playAlbum.ts b/src/features/playback/utils/playback/playAlbum.ts similarity index 82% rename from src/utils/playback/playAlbum.ts rename to src/features/playback/utils/playback/playAlbum.ts index b74911f0..e91c03c0 100644 --- a/src/utils/playback/playAlbum.ts +++ b/src/features/playback/utils/playback/playAlbum.ts @@ -1,11 +1,11 @@ -import { usePlayerStore } from '../../store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { resolveAlbumForActiveServer } from '@/store/mediaResolver'; -import { songToTrack } from './songToTrack'; +import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; import { orbitSnapshot } from '@/store/orbitRuntime'; -import { fadeOut } from './fadeOut'; -import { shouldAutodjInterruptBlend } from './autodjManualBlend'; -import type { Track } from '../../store/playerStoreTypes'; -import { shuffleArray } from './shuffleArray'; +import { fadeOut } from '@/features/playback/utils/playback/fadeOut'; +import { shouldAutodjInterruptBlend } from '@/features/playback/utils/playback/autodjManualBlend'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; +import { shuffleArray } from '@/features/playback/utils/playback/shuffleArray'; export async function fetchAlbumTracks(albumId: string, serverId?: string): Promise { const albumData = await resolveAlbumForActiveServer(albumId, serverId); diff --git a/src/utils/playback/playArtistShuffled.ts b/src/features/playback/utils/playback/playArtistShuffled.ts similarity index 82% rename from src/utils/playback/playArtistShuffled.ts rename to src/features/playback/utils/playback/playArtistShuffled.ts index 6c43f8c3..e3e24246 100644 --- a/src/utils/playback/playArtistShuffled.ts +++ b/src/features/playback/utils/playback/playArtistShuffled.ts @@ -1,7 +1,7 @@ import { resolveAlbum, resolveArtist, resolveMediaServerId } from '@/store/mediaResolver'; -import { songToTrack } from './songToTrack'; -import { shuffleArray } from './shuffleArray'; -import { usePlayerStore } from '../../store/playerStore'; +import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; +import { shuffleArray } from '@/features/playback/utils/playback/shuffleArray'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; /** * All tracks from the artist’s albums, shuffled — same idea as Artist page “shuffle play”. */ diff --git a/src/utils/playback/playByOpaqueId.ts b/src/features/playback/utils/playback/playByOpaqueId.ts similarity index 71% rename from src/utils/playback/playByOpaqueId.ts rename to src/features/playback/utils/playback/playByOpaqueId.ts index 1f2c8bf0..578bbf3b 100644 --- a/src/utils/playback/playByOpaqueId.ts +++ b/src/features/playback/utils/playback/playByOpaqueId.ts @@ -1,9 +1,9 @@ import { getSong } from '@/lib/api/subsonicLibrary'; import { resolveAlbumForActiveServer } from '@/store/mediaResolver'; -import { songToTrack } from './songToTrack'; -import { playAlbum } from './playAlbum'; -import { playArtistShuffled } from './playArtistShuffled'; -import { usePlayerStore } from '../../store/playerStore'; +import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; +import { playAlbum } from '@/features/playback/utils/playback/playAlbum'; +import { playArtistShuffled } from '@/features/playback/utils/playback/playArtistShuffled'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; /** * `getSong` → `getAlbum` → `getArtist`: one opaque Subsonic id may refer to a track, * album, or artist depending on the server. diff --git a/src/utils/playback/playSong.ts b/src/features/playback/utils/playback/playSong.ts similarity index 83% rename from src/utils/playback/playSong.ts rename to src/features/playback/utils/playback/playSong.ts index b276a143..3dbed440 100644 --- a/src/utils/playback/playSong.ts +++ b/src/features/playback/utils/playback/playSong.ts @@ -1,8 +1,8 @@ import type { SubsonicSong } from '@/lib/api/subsonicTypes'; -import { songToTrack } from './songToTrack'; -import { usePlayerStore } from '../../store/playerStore'; -import { fadeOut } from './fadeOut'; -import { shouldAutodjInterruptBlend } from './autodjManualBlend'; +import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; +import { fadeOut } from '@/features/playback/utils/playback/fadeOut'; +import { shouldAutodjInterruptBlend } from '@/features/playback/utils/playback/autodjManualBlend'; /** * Play a single song. When `queue` is provided, surrounds the chosen song with that queue diff --git a/src/utils/playback/playbackScheduleDelay.test.ts b/src/features/playback/utils/playback/playbackScheduleDelay.test.ts similarity index 97% rename from src/utils/playback/playbackScheduleDelay.test.ts rename to src/features/playback/utils/playback/playbackScheduleDelay.test.ts index 812a2a3c..7d31a7b8 100644 --- a/src/utils/playback/playbackScheduleDelay.test.ts +++ b/src/features/playback/utils/playback/playbackScheduleDelay.test.ts @@ -7,7 +7,7 @@ import { scheduleDeadlineMs, scheduleDelayMsFromSeconds, scheduleSecondsFromCustomMinutes, -} from './playbackScheduleDelay'; +} from '@/features/playback/utils/playback/playbackScheduleDelay'; describe('parsePlaybackDelayCustomMinutes', () => { it('accepts fractional minutes', () => { diff --git a/src/utils/playback/playbackScheduleDelay.ts b/src/features/playback/utils/playback/playbackScheduleDelay.ts similarity index 100% rename from src/utils/playback/playbackScheduleDelay.ts rename to src/features/playback/utils/playback/playbackScheduleDelay.ts diff --git a/src/utils/playback/playbackServer.test.ts b/src/features/playback/utils/playback/playbackServer.test.ts similarity index 94% rename from src/utils/playback/playbackServer.test.ts rename to src/features/playback/utils/playback/playbackServer.test.ts index 5c4e32f7..a52fd874 100644 --- a/src/utils/playback/playbackServer.test.ts +++ b/src/features/playback/utils/playback/playbackServer.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it, beforeEach } from 'vitest'; -import { useAuthStore } from '../../store/authStore'; -import { usePlayerStore } from '../../store/playerStore'; +import { useAuthStore } from '@/store/authStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { bindQueueServerForPlayback, bindQueueServerForTracks, @@ -13,11 +13,11 @@ import { prepareActiveServerForNewMix, shouldBindQueueServerForPlay, shouldHandoffQueueToActiveServer, -} from './playbackServer'; +} from '@/features/playback/utils/playback/playbackServer'; import { invoke } from '@tauri-apps/api/core'; import { vi } from 'vitest'; -vi.mock('../server/switchActiveServer', () => ({ +vi.mock('@/utils/server/switchActiveServer', () => ({ switchActiveServer: vi.fn(async () => true), })); @@ -113,7 +113,7 @@ describe('playbackServer', () => { }); it('ensurePlaybackServerActive calls switch when servers differ', async () => { - const { switchActiveServer } = await import('../server/switchActiveServer'); + const { switchActiveServer } = await import('@/utils/server/switchActiveServer'); useAuthStore.setState({ activeServerId: 'b' }); await ensurePlaybackServerActive(); expect(switchActiveServer).toHaveBeenCalledWith( diff --git a/src/utils/playback/playbackServer.ts b/src/features/playback/utils/playback/playbackServer.ts similarity index 90% rename from src/utils/playback/playbackServer.ts rename to src/features/playback/utils/playback/playbackServer.ts index f4011637..5cda37c1 100644 --- a/src/utils/playback/playbackServer.ts +++ b/src/features/playback/utils/playback/playbackServer.ts @@ -1,21 +1,21 @@ -import { buildCoverArtFetchUrl } from '../../cover/fetchUrl'; -import { resolvePlaybackCoverScope } from '../../cover/ref'; -import { coverEntryToRef, resolveAlbumCoverEntry } from '../../cover/resolveEntry'; -import { coverStorageKeyFromRef } from '../../cover/storageKeys'; -import { resolveCoverDisplayTier } from '../../cover/tiers'; -import { useAuthStore } from '../../store/authStore'; -import type { ServerProfile } from '../../store/authStoreTypes'; -import { usePlayerStore } from '../../store/playerStore'; -import { switchActiveServer } from '../server/switchActiveServer'; -import { sameQueueTrackId } from './queueIdentity'; -import type { QueueItemRef, Track } from '../../store/playerStoreTypes'; -import { findServerByIdOrIndexKey, resolveServerIdForIndexKey } from '../server/serverLookup'; +import { buildCoverArtFetchUrl } from '@/cover/fetchUrl'; +import { resolvePlaybackCoverScope } from '@/cover/ref'; +import { coverEntryToRef, resolveAlbumCoverEntry } from '@/cover/resolveEntry'; +import { coverStorageKeyFromRef } from '@/cover/storageKeys'; +import { resolveCoverDisplayTier } from '@/cover/tiers'; +import { useAuthStore } from '@/store/authStore'; +import type { ServerProfile } from '@/store/authStoreTypes'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; +import { switchActiveServer } from '@/utils/server/switchActiveServer'; +import { sameQueueTrackId } from '@/features/playback/utils/playback/queueIdentity'; +import type { QueueItemRef, Track } from '@/features/playback/store/playerStoreTypes'; +import { findServerByIdOrIndexKey, resolveServerIdForIndexKey } from '@/utils/server/serverLookup'; import { canonicalQueueServerKey, resolveIndexKey, serverIndexKeyForProfile, serverIndexKeyFromUrl, -} from '../server/serverIndexKey'; +} from '@/utils/server/serverIndexKey'; import { activeServerProfileId, filterQueueRefsForServerProfile, @@ -23,7 +23,7 @@ import { profileIdFromQueueRef, queueItemRefAt, stampTrackServerIds, -} from './trackServerScope'; +} from '@/features/playback/utils/playback/trackServerScope'; function playbackServerFromRef(ref: QueueItemRef): ServerProfile | undefined { const profileId = profileIdFromQueueRef(ref); diff --git a/src/utils/playback/playbackTransition.test.ts b/src/features/playback/utils/playback/playbackTransition.test.ts similarity index 96% rename from src/utils/playback/playbackTransition.test.ts rename to src/features/playback/utils/playback/playbackTransition.test.ts index b9a7a7a9..b65f0d1e 100644 --- a/src/utils/playback/playbackTransition.test.ts +++ b/src/features/playback/utils/playback/playbackTransition.test.ts @@ -1,11 +1,11 @@ import { describe, expect, it, beforeEach } from 'vitest'; -import { useAuthStore } from '../../store/authStore'; +import { useAuthStore } from '@/store/authStore'; import { getTransitionMode, transitionFlagsFor, setTransitionMode, type TransitionMode, -} from './playbackTransition'; +} from '@/features/playback/utils/playback/playbackTransition'; const MODES: TransitionMode[] = ['none', 'gapless', 'crossfade', 'autodj']; diff --git a/src/utils/playback/playbackTransition.ts b/src/features/playback/utils/playback/playbackTransition.ts similarity index 97% rename from src/utils/playback/playbackTransition.ts rename to src/features/playback/utils/playback/playbackTransition.ts index 62b09d51..aeda16d3 100644 --- a/src/utils/playback/playbackTransition.ts +++ b/src/features/playback/utils/playback/playbackTransition.ts @@ -1,4 +1,4 @@ -import { useAuthStore } from '../../store/authStore'; +import { useAuthStore } from '@/store/authStore'; /** * The one active track-transition behaviour. These are mutually exclusive — diff --git a/src/utils/playback/queueIdentity.test.ts b/src/features/playback/utils/playback/queueIdentity.test.ts similarity index 96% rename from src/utils/playback/queueIdentity.test.ts rename to src/features/playback/utils/playback/queueIdentity.test.ts index 156c6dc8..65070332 100644 --- a/src/utils/playback/queueIdentity.test.ts +++ b/src/features/playback/utils/playback/queueIdentity.test.ts @@ -4,14 +4,14 @@ * routing through the HTTP source) and the no-op detection in * `queuesStructuralEqual` that prevents unnecessary store rewrites. */ -import type { Track } from '../../store/playerStoreTypes'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; import { describe, expect, it } from 'vitest'; import { normalizeAnalysisTrackId, queuesStructuralEqual, sameQueueTrackId, shallowCloneQueueTracks, -} from './queueIdentity'; +} from '@/features/playback/utils/playback/queueIdentity'; function track(id: string, overrides: Partial = {}): Track { return { diff --git a/src/utils/playback/queueIdentity.ts b/src/features/playback/utils/playback/queueIdentity.ts similarity index 95% rename from src/utils/playback/queueIdentity.ts rename to src/features/playback/utils/playback/queueIdentity.ts index 975d09a8..b5b676e8 100644 --- a/src/utils/playback/queueIdentity.ts +++ b/src/features/playback/utils/playback/queueIdentity.ts @@ -1,4 +1,4 @@ -import type { QueueItemRef, Track } from '../../store/playerStoreTypes'; +import type { QueueItemRef, Track } from '@/features/playback/store/playerStoreTypes'; /** * Strip the `stream:` prefix that some Rust events attach to track ids when diff --git a/src/utils/playback/resolvePlaybackUrl.test.ts b/src/features/playback/utils/playback/resolvePlaybackUrl.test.ts similarity index 98% rename from src/utils/playback/resolvePlaybackUrl.test.ts rename to src/features/playback/utils/playback/resolvePlaybackUrl.test.ts index c5b03878..6215fb31 100644 --- a/src/utils/playback/resolvePlaybackUrl.test.ts +++ b/src/features/playback/utils/playback/resolvePlaybackUrl.test.ts @@ -25,7 +25,7 @@ import { getPlaybackSourceKind, resolvePlaybackUrl, streamUrlTrackId, -} from './resolvePlaybackUrl'; +} from '@/features/playback/utils/playback/resolvePlaybackUrl'; import { useAuthStore } from '@/store/authStore'; import { resetAuthStore } from '@/test/helpers/storeReset'; diff --git a/src/utils/playback/resolvePlaybackUrl.ts b/src/features/playback/utils/playback/resolvePlaybackUrl.ts similarity index 95% rename from src/utils/playback/resolvePlaybackUrl.ts rename to src/features/playback/utils/playback/resolvePlaybackUrl.ts index b6d4d7ec..e8eeff8c 100644 --- a/src/utils/playback/resolvePlaybackUrl.ts +++ b/src/features/playback/utils/playback/resolvePlaybackUrl.ts @@ -1,7 +1,7 @@ import { buildStreamUrlForServer } from '@/lib/api/subsonicStreamUrl'; import { findLocalPlaybackUrl } from '@/store/localPlaybackResolve'; -import { resolveServerIdForIndexKey } from '../server/serverLookup'; -import { getPlaybackCacheServerKey, getPlaybackServerId } from './playbackServer'; +import { resolveServerIdForIndexKey } from '@/utils/server/serverLookup'; +import { getPlaybackCacheServerKey, getPlaybackServerId } from '@/features/playback/utils/playback/playbackServer'; /** Same resolution order as {@link resolvePlaybackUrl} — for UI hints only. */ export type PlaybackSourceKind = 'offline' | 'hot' | 'stream'; diff --git a/src/utils/playback/runBulkPlay.test.ts b/src/features/playback/utils/playback/runBulkPlay.test.ts similarity index 95% rename from src/utils/playback/runBulkPlay.test.ts rename to src/features/playback/utils/playback/runBulkPlay.test.ts index 0d68e0fc..bcc4d58f 100644 --- a/src/utils/playback/runBulkPlay.test.ts +++ b/src/features/playback/utils/playback/runBulkPlay.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it, vi } from 'vitest'; -import type { Track } from '../../store/playerStoreTypes'; -import { runBulkEnqueue, runBulkPlayAll, runBulkShuffle } from './runBulkPlay'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; +import { runBulkEnqueue, runBulkPlayAll, runBulkShuffle } from '@/features/playback/utils/playback/runBulkPlay'; function track(id: string): Track { return { id, title: id } as Track; diff --git a/src/utils/playback/runBulkPlay.ts b/src/features/playback/utils/playback/runBulkPlay.ts similarity index 91% rename from src/utils/playback/runBulkPlay.ts rename to src/features/playback/utils/playback/runBulkPlay.ts index c16cd77b..c1130ffc 100644 --- a/src/utils/playback/runBulkPlay.ts +++ b/src/features/playback/utils/playback/runBulkPlay.ts @@ -1,5 +1,5 @@ -import type { Track } from '../../store/playerStoreTypes'; -import { shuffleArray } from './shuffleArray'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; +import { shuffleArray } from '@/features/playback/utils/playback/shuffleArray'; /** * Shared "play / shuffle / enqueue a fetched track list" core for detail pages whose diff --git a/src/utils/playback/shuffleArray.test.ts b/src/features/playback/utils/playback/shuffleArray.test.ts similarity index 95% rename from src/utils/playback/shuffleArray.test.ts rename to src/features/playback/utils/playback/shuffleArray.test.ts index a189e9bb..a064637c 100644 --- a/src/utils/playback/shuffleArray.test.ts +++ b/src/features/playback/utils/playback/shuffleArray.test.ts @@ -5,7 +5,7 @@ * refactor (2026-05-12). */ import { afterEach, describe, expect, it, vi } from 'vitest'; -import { shuffleArray } from './shuffleArray'; +import { shuffleArray } from '@/features/playback/utils/playback/shuffleArray'; describe('shuffleArray', () => { afterEach(() => { diff --git a/src/utils/playback/shuffleArray.ts b/src/features/playback/utils/playback/shuffleArray.ts similarity index 100% rename from src/utils/playback/shuffleArray.ts rename to src/features/playback/utils/playback/shuffleArray.ts diff --git a/src/utils/playback/songToTrack.test.ts b/src/features/playback/utils/playback/songToTrack.test.ts similarity index 95% rename from src/utils/playback/songToTrack.test.ts rename to src/features/playback/utils/playback/songToTrack.test.ts index 705df1dc..8ce2b93a 100644 --- a/src/utils/playback/songToTrack.test.ts +++ b/src/features/playback/utils/playback/songToTrack.test.ts @@ -5,9 +5,9 @@ * playback queue. Originally lived in `playerStore.ts`; extracted in M0 of * the frontend refactor (2026-05-12). */ -import type { Track } from '../../store/playerStoreTypes'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; import { describe, expect, it } from 'vitest'; -import { songToTrack } from './songToTrack'; +import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; import { makeSubsonicSong } from '@/test/helpers/factories'; describe('songToTrack', () => { diff --git a/src/utils/playback/songToTrack.ts b/src/features/playback/utils/playback/songToTrack.ts similarity index 86% rename from src/utils/playback/songToTrack.ts rename to src/features/playback/utils/playback/songToTrack.ts index 70bc21f1..08cde482 100644 --- a/src/utils/playback/songToTrack.ts +++ b/src/features/playback/utils/playback/songToTrack.ts @@ -1,7 +1,7 @@ import type { SubsonicSong } from '@/lib/api/subsonicTypes'; -import type { Track } from '../../store/playerStoreTypes'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; import { coerceOpenArtistRefs } from '@/lib/api/openArtistRefs'; -import { activeServerProfileId } from './trackServerScope'; +import { activeServerProfileId } from '@/features/playback/utils/playback/trackServerScope'; export function songToTrack(song: SubsonicSong): Track { return { diff --git a/src/utils/playback/trackArtistRefs.test.ts b/src/features/playback/utils/playback/trackArtistRefs.test.ts similarity index 87% rename from src/utils/playback/trackArtistRefs.test.ts rename to src/features/playback/utils/playback/trackArtistRefs.test.ts index 35da2767..75eb0aa1 100644 --- a/src/utils/playback/trackArtistRefs.test.ts +++ b/src/features/playback/utils/playback/trackArtistRefs.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; -import type { Track } from '../../store/playerStoreTypes'; -import { primaryTrackArtistRef, resolveTrackArtistRefs } from './trackArtistRefs'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; +import { primaryTrackArtistRef, resolveTrackArtistRefs } from '@/features/playback/utils/playback/trackArtistRefs'; describe('resolveTrackArtistRefs', () => { it('prefers OpenSubsonic artists[] when present', () => { diff --git a/src/utils/playback/trackArtistRefs.ts b/src/features/playback/utils/playback/trackArtistRefs.ts similarity index 92% rename from src/utils/playback/trackArtistRefs.ts rename to src/features/playback/utils/playback/trackArtistRefs.ts index 3eb39c0e..e7a1391b 100644 --- a/src/utils/playback/trackArtistRefs.ts +++ b/src/features/playback/utils/playback/trackArtistRefs.ts @@ -1,5 +1,5 @@ import type { SubsonicOpenArtistRef } from '@/lib/api/subsonicTypes'; -import type { Track } from '../../store/playerStoreTypes'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; import { coerceOpenArtistRefs } from '@/lib/api/openArtistRefs'; type TrackArtistFields = Pick; diff --git a/src/utils/playback/trackServerScope.test.ts b/src/features/playback/utils/playback/trackServerScope.test.ts similarity index 90% rename from src/utils/playback/trackServerScope.test.ts rename to src/features/playback/utils/playback/trackServerScope.test.ts index 1bb65e86..6bae3c2d 100644 --- a/src/utils/playback/trackServerScope.test.ts +++ b/src/features/playback/utils/playback/trackServerScope.test.ts @@ -1,14 +1,14 @@ import { beforeEach, describe, expect, it } from 'vitest'; -import { useAuthStore } from '../../store/authStore'; -import { usePlayerStore } from '../../store/playerStore'; -import type { Track } from '../../store/playerStoreTypes'; +import { useAuthStore } from '@/store/authStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; import { activeServerQueueTrackIds, filterQueueRefsForActiveServer, isMultiServerQueue, queueItemRefAt, stampTrackServerId, -} from './trackServerScope'; +} from '@/features/playback/utils/playback/trackServerScope'; const baseTrack = (): Track => ({ id: 't1', diff --git a/src/utils/playback/trackServerScope.ts b/src/features/playback/utils/playback/trackServerScope.ts similarity index 89% rename from src/utils/playback/trackServerScope.ts rename to src/features/playback/utils/playback/trackServerScope.ts index 126f0f0c..6355ec16 100644 --- a/src/utils/playback/trackServerScope.ts +++ b/src/features/playback/utils/playback/trackServerScope.ts @@ -1,8 +1,8 @@ -import type { QueueItemRef, Track } from '../../store/playerStoreTypes'; -import { useAuthStore } from '../../store/authStore'; -import { usePlayerStore } from '../../store/playerStore'; -import { canonicalQueueServerKey } from '../server/serverIndexKey'; -import { resolveServerIdForIndexKey } from '../server/serverLookup'; +import type { QueueItemRef, Track } from '@/features/playback/store/playerStoreTypes'; +import { useAuthStore } from '@/store/authStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; +import { canonicalQueueServerKey } from '@/utils/server/serverIndexKey'; +import { resolveServerIdForIndexKey } from '@/utils/server/serverLookup'; /** Active saved-server profile id (auth UUID), when logged in. */ export function activeServerProfileId(): string | undefined { diff --git a/src/features/playlist/components/PlaylistArtistCell.tsx b/src/features/playlist/components/PlaylistArtistCell.tsx index 50ecbe56..76670042 100644 --- a/src/features/playlist/components/PlaylistArtistCell.tsx +++ b/src/features/playlist/components/PlaylistArtistCell.tsx @@ -1,7 +1,7 @@ import React, { useMemo } from 'react'; import { useNavigate } from 'react-router-dom'; import type { SubsonicSong } from '@/lib/api/subsonicTypes'; -import { resolveTrackArtistRefs } from '@/utils/playback/trackArtistRefs'; +import { resolveTrackArtistRefs } from '@/features/playback/utils/playback/trackArtistRefs'; /** * Multi-artist credit for playlist track rows (main list + suggestions). diff --git a/src/features/playlist/components/PlaylistCard.tsx b/src/features/playlist/components/PlaylistCard.tsx index 81395dab..6b74c389 100644 --- a/src/features/playlist/components/PlaylistCard.tsx +++ b/src/features/playlist/components/PlaylistCard.tsx @@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom'; import { Check, Clock3, ListMusic, Pencil, Play, Sparkles, Trash2 } from 'lucide-react'; import type { SubsonicPlaylist } from '@/lib/api/subsonicTypes'; -import { usePlayerStore } from '@/store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { displayPlaylistName, isSmartPlaylistName, type PendingSmartPlaylist, } from '@/features/playlist/utils/playlistsSmart'; diff --git a/src/features/playlist/components/PlaylistSongSearchPanel.tsx b/src/features/playlist/components/PlaylistSongSearchPanel.tsx index 35295f8a..ee6a851b 100644 --- a/src/features/playlist/components/PlaylistSongSearchPanel.tsx +++ b/src/features/playlist/components/PlaylistSongSearchPanel.tsx @@ -2,8 +2,8 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; import { Check, ListPlus, X } from 'lucide-react'; import type { SubsonicSong } from '@/lib/api/subsonicTypes'; -import { usePlayerStore } from '@/store/playerStore'; -import { songToTrack } from '@/utils/playback/songToTrack'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; +import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; import { formatTrackTime } from '@/lib/format/formatDuration'; import { AlbumCoverArtImage } from '@/cover/AlbumCoverArtImage'; import { COVER_DENSE_SEARCH_CSS_PX } from '@/cover/layoutSizes'; diff --git a/src/features/playlist/components/PlaylistSuggestions.tsx b/src/features/playlist/components/PlaylistSuggestions.tsx index 3e473842..4d8caace 100644 --- a/src/features/playlist/components/PlaylistSuggestions.tsx +++ b/src/features/playlist/components/PlaylistSuggestions.tsx @@ -4,13 +4,13 @@ import { useNavigate } from 'react-router-dom'; import { ChevronRight, Heart, Play, Plus, RefreshCw, Square } from 'lucide-react'; import type { ColDef } from '@/utils/useTracklistColumns'; import type { SubsonicSong } from '@/lib/api/subsonicTypes'; -import { usePlayerStore } from '@/store/playerStore'; -import { usePreviewStore } from '@/store/previewStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; +import { usePreviewStore } from '@/features/playback/store/previewStore'; import StarRating from '@/components/StarRating'; import { PlaylistArtistCell } from '@/features/playlist/components/PlaylistArtistCell'; import { useThemeStore } from '@/store/themeStore'; import { usePlaylistLayoutStore } from '@/features/playlist/store/playlistLayoutStore'; -import { songToTrack } from '@/utils/playback/songToTrack'; +import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; import { getQueueTracksView } from '@/utils/library/queueTrackView'; import { codecLabel } from '@/utils/componentHelpers/playlistDetailHelpers'; import { formatLastSeen } from '@/utils/componentHelpers/userMgmtHelpers'; diff --git a/src/features/playlist/components/PlaylistTracklist.tsx b/src/features/playlist/components/PlaylistTracklist.tsx index 16aa5575..09004f77 100644 --- a/src/features/playlist/components/PlaylistTracklist.tsx +++ b/src/features/playlist/components/PlaylistTracklist.tsx @@ -11,13 +11,13 @@ import { } from 'lucide-react'; import type { ColDef } from '@/utils/useTracklistColumns'; import type { SubsonicSong } from '@/lib/api/subsonicTypes'; -import type { Track } from '@/store/playerStoreTypes'; -import { usePlayerStore } from '@/store/playerStore'; -import { previewInputFromSong, usePreviewStore } from '@/store/previewStore'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; +import { previewInputFromSong, usePreviewStore } from '@/features/playback/store/previewStore'; import { useThemeStore } from '@/store/themeStore'; import { useDragDrop } from '@/lib/dnd/DragDropContext'; import { useOrbitSongRowBehavior } from '@/features/orbit'; -import { songToTrack } from '@/utils/playback/songToTrack'; +import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; import type { PlaylistSortKey, PlaylistSortDir } from '@/features/playlist/utils/playlistDisplayedSongs'; import { AddToPlaylistSubmenu } from '@/components/ContextMenu'; diff --git a/src/features/playlist/hooks/usePlaylistBulkPlayCallbacks.ts b/src/features/playlist/hooks/usePlaylistBulkPlayCallbacks.ts index 5a206d33..d8c12127 100644 --- a/src/features/playlist/hooks/usePlaylistBulkPlayCallbacks.ts +++ b/src/features/playlist/hooks/usePlaylistBulkPlayCallbacks.ts @@ -1,5 +1,5 @@ import { useCallback } from 'react'; -import type { Track } from '@/store/playerStoreTypes'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; import { enqueuePlaylistAll, playPlaylistAll, shufflePlaylistAll } from '@/features/playlist/utils/playlistBulkPlayActions'; export interface PlaylistBulkPlayCallbacksDeps { diff --git a/src/features/playlist/hooks/usePlaylistDerived.ts b/src/features/playlist/hooks/usePlaylistDerived.ts index 989457e3..0c60685c 100644 --- a/src/features/playlist/hooks/usePlaylistDerived.ts +++ b/src/features/playlist/hooks/usePlaylistDerived.ts @@ -1,8 +1,8 @@ import { useMemo } from 'react'; import type { SubsonicSong } from '@/lib/api/subsonicTypes'; -import type { Track } from '@/store/playerStoreTypes'; -import { usePlayerStore } from '@/store/playerStore'; -import { songToTrack } from '@/utils/playback/songToTrack'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; +import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; import { getDisplayedSongs, type PlaylistSortDir, type PlaylistSortKey } from '@/features/playlist/utils/playlistDisplayedSongs'; export interface PlaylistDerivedOptions { diff --git a/src/features/playlist/hooks/usePlaylistPreview.ts b/src/features/playlist/hooks/usePlaylistPreview.ts index 17f3e67a..0c9b7a89 100644 --- a/src/features/playlist/hooks/usePlaylistPreview.ts +++ b/src/features/playlist/hooks/usePlaylistPreview.ts @@ -1,5 +1,5 @@ import { useCallback, useEffect } from 'react'; -import { previewInputFromSong, usePreviewStore } from '@/store/previewStore'; +import { previewInputFromSong, usePreviewStore } from '@/features/playback/store/previewStore'; import type { SubsonicSong } from '@/lib/api/subsonicTypes'; export function usePlaylistPreview(): { diff --git a/src/features/playlist/hooks/usePlaylistRouteEffects.ts b/src/features/playlist/hooks/usePlaylistRouteEffects.ts index 2cf090da..65171882 100644 --- a/src/features/playlist/hooks/usePlaylistRouteEffects.ts +++ b/src/features/playlist/hooks/usePlaylistRouteEffects.ts @@ -1,6 +1,6 @@ import React, { useEffect } from 'react'; import type { Location, NavigateFunction } from 'react-router-dom'; -import { usePlayerStore } from '@/store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; export interface PlaylistRouteEffectsDeps { setContextMenuSongId: React.Dispatch>; diff --git a/src/features/playlist/hooks/usePlaylistStarRating.ts b/src/features/playlist/hooks/usePlaylistStarRating.ts index f945ac78..3eb93cf8 100644 --- a/src/features/playlist/hooks/usePlaylistStarRating.ts +++ b/src/features/playlist/hooks/usePlaylistStarRating.ts @@ -1,7 +1,7 @@ import React from 'react'; -import { queueSongStar, queueSongRating } from '@/store/pendingStarSync'; +import { queueSongStar, queueSongRating } from '@/features/playback/store/pendingStarSync'; import type { SubsonicSong } from '@/lib/api/subsonicTypes'; -import { usePlayerStore } from '@/store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; export interface PlaylistStarRatingDeps { ratings: Record; diff --git a/src/features/playlist/pages/PlaylistDetail.tsx b/src/features/playlist/pages/PlaylistDetail.tsx index e9eb2f48..985c7f1c 100644 --- a/src/features/playlist/pages/PlaylistDetail.tsx +++ b/src/features/playlist/pages/PlaylistDetail.tsx @@ -3,7 +3,7 @@ import type { SubsonicPlaylist, SubsonicSong } from '@/lib/api/subsonicTypes'; import React, { useEffect, useState, useCallback, useMemo } from 'react'; import { useParams, useNavigate, useLocation } from 'react-router-dom'; import { useTracklistColumns, type ColDef } from '@/utils/useTracklistColumns'; -import { usePlayerStore } from '@/store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { useShallow } from 'zustand/react/shallow'; import { usePlaylistStore } from '@/features/playlist/store/playlistStore'; import { useOfflineStore } from '@/features/offline'; diff --git a/src/features/playlist/pages/Playlists.tsx b/src/features/playlist/pages/Playlists.tsx index b93b5de4..0df2d9f3 100644 --- a/src/features/playlist/pages/Playlists.tsx +++ b/src/features/playlist/pages/Playlists.tsx @@ -2,9 +2,9 @@ import { resolveMediaServerId, resolvePlaylist } from '@/features/offline'; import { getGenres } from '@/lib/api/subsonicGenres'; import { filterSongsToActiveLibrary } from '@/lib/api/subsonicLibrary'; import type { SubsonicPlaylist, SubsonicGenre } from '@/lib/api/subsonicTypes'; -import { songToTrack } from '@/utils/playback/songToTrack'; +import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; import React, { useEffect, useState, useRef, useCallback } from 'react'; -import { usePlayerStore } from '@/store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { usePlaylistStore } from '@/features/playlist/store/playlistStore'; import { useAuthStore } from '@/store/authStore'; import { useTranslation } from 'react-i18next'; diff --git a/src/features/playlist/utils/playPlaylistById.ts b/src/features/playlist/utils/playPlaylistById.ts index fe136adb..7d7a146a 100644 --- a/src/features/playlist/utils/playPlaylistById.ts +++ b/src/features/playlist/utils/playPlaylistById.ts @@ -1,6 +1,6 @@ import { getPlaylist } from '@/lib/api/subsonicPlaylists'; -import { songToTrack } from '@/utils/playback/songToTrack'; -import { usePlayerStore } from '@/store/playerStore'; +import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { playPlaylistAll } from '@/features/playlist/utils/playlistBulkPlayActions'; /** diff --git a/src/features/playlist/utils/playlistBulkPlayActions.test.ts b/src/features/playlist/utils/playlistBulkPlayActions.test.ts index 46ffc6a8..39dde17d 100644 --- a/src/features/playlist/utils/playlistBulkPlayActions.test.ts +++ b/src/features/playlist/utils/playlistBulkPlayActions.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it, vi } from 'vitest'; import { enqueuePlaylistAll, playPlaylistAll, shufflePlaylistAll } from '@/features/playlist/utils/playlistBulkPlayActions'; -import type { Track } from '@/store/playerStoreTypes'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; // Only id/queue identity matters for these actions. const tracks = [{ id: 'a' }, { id: 'b' }, { id: 'c' }] as unknown as Track[]; diff --git a/src/features/playlist/utils/playlistBulkPlayActions.ts b/src/features/playlist/utils/playlistBulkPlayActions.ts index 1b4d3117..edea3882 100644 --- a/src/features/playlist/utils/playlistBulkPlayActions.ts +++ b/src/features/playlist/utils/playlistBulkPlayActions.ts @@ -1,4 +1,4 @@ -import type { Track } from '@/store/playerStoreTypes'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; // No `touchPlaylist` here: playing/shuffling/enqueuing does not modify the // playlist. Touching it bumps `lastModified`, which is the playlist detail diff --git a/src/features/playlist/utils/startPlaylistRowDrag.ts b/src/features/playlist/utils/startPlaylistRowDrag.ts index ed8e9fec..58bc546a 100644 --- a/src/features/playlist/utils/startPlaylistRowDrag.ts +++ b/src/features/playlist/utils/startPlaylistRowDrag.ts @@ -1,6 +1,6 @@ import type React from 'react'; import type { SubsonicSong } from '@/lib/api/subsonicTypes'; -import { songToTrack } from '@/utils/playback/songToTrack'; +import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; export interface StartPlaylistRowDragDeps { e: React.MouseEvent; diff --git a/src/features/queue/components/QueueCurrentTrack.tsx b/src/features/queue/components/QueueCurrentTrack.tsx index 8ad073cd..f5656b47 100644 --- a/src/features/queue/components/QueueCurrentTrack.tsx +++ b/src/features/queue/components/QueueCurrentTrack.tsx @@ -1,14 +1,14 @@ import React from 'react'; import { ChevronDown, FolderOpen, HardDrive, Music, Waves } from 'lucide-react'; import type { TFunction } from 'i18next'; -import type { Track } from '@/store/playerStoreTypes'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; import type { LoudnessLufsPreset, NormalizationEngine } from '@/store/authStoreTypes'; -import type { PlaybackSourceKind } from '@/utils/playback/resolvePlaybackUrl'; +import type { PlaybackSourceKind } from '@/features/playback/utils/playback/resolvePlaybackUrl'; import { formatQueueReplayGainParts, renderStars, } from '@/utils/componentHelpers/queuePanelHelpers'; -import { loudnessGainPlaceholderUntilCacheDb } from '@/utils/audio/loudnessPlaceholder'; +import { loudnessGainPlaceholderUntilCacheDb } from '@/features/playback/utils/audio/loudnessPlaceholder'; import { effectiveLoudnessPreAnalysisAttenuationDb } from '@/utils/audio/loudnessPreAnalysisSlider'; import { formatQueueBpmTech, formatQueueMoodLabels } from '@/utils/library/trackEnrichment'; import { useQueueTrackEnrichment } from '@/features/queue/hooks/useQueueTrackEnrichment'; @@ -17,8 +17,8 @@ import { PlaybackBufferingOverlay } from '@/components/playback/PlaybackBufferin import { CoverArtImage } from '@/cover/CoverArtImage'; import { OpenArtistRefInline } from '@/features/artist'; import { usePlaybackTrackCoverRef } from '@/cover/useLibraryCoverRef'; -import { usePlayerStore } from '@/store/playerStore'; -import { resolveTrackArtistRefs } from '@/utils/playback/trackArtistRefs'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; +import { resolveTrackArtistRefs } from '@/features/playback/utils/playback/trackArtistRefs'; interface Props { currentTrack: Track; diff --git a/src/features/queue/components/QueueHeader.tsx b/src/features/queue/components/QueueHeader.tsx index 3033a1e3..26f99807 100644 --- a/src/features/queue/components/QueueHeader.tsx +++ b/src/features/queue/components/QueueHeader.tsx @@ -2,9 +2,9 @@ import { useDeferredValue, useMemo, useSyncExternalStore } from 'react'; import { AlignCenterVertical, ChevronDown, ListMusic, ListOrdered } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import type { TFunction } from 'i18next'; -import { usePlayerStore } from '@/store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { useAuthStore } from '@/store/authStore'; -import type { QueueItemRef } from '@/store/playerStoreTypes'; +import type { QueueItemRef } from '@/features/playback/store/playerStoreTypes'; import type { QueueDisplayMode } from '@/store/authStoreTypes'; import type { DurationMode } from '@/utils/componentHelpers/queuePanelHelpers'; import { formatLongDuration } from '@/lib/format/formatDuration'; diff --git a/src/features/queue/components/QueueList.tsx b/src/features/queue/components/QueueList.tsx index 7ae5dcc6..abea7feb 100644 --- a/src/features/queue/components/QueueList.tsx +++ b/src/features/queue/components/QueueList.tsx @@ -3,9 +3,9 @@ import { useVirtualizer } from '@tanstack/react-virtual'; import { Play } from 'lucide-react'; import type { TFunction } from 'i18next'; import OverlayScrollArea from '@/ui/OverlayScrollArea'; -import { usePlayerStore } from '@/store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { useLuckyMixStore } from '@/store/luckyMixStore'; -import type { QueueItemRef, PlayerState } from '@/store/playerStoreTypes'; +import type { QueueItemRef, PlayerState } from '@/features/playback/store/playerStoreTypes'; import type { QueueDisplayMode } from '@/store/authStoreTypes'; import { formatTrackTime } from '@/lib/format/formatDuration'; import { resolveQueueTrack } from '@/utils/library/queueTrackView'; @@ -13,7 +13,7 @@ import { getQueueResolverVersion, subscribeQueueResolver, } from '@/utils/library/queueTrackResolver'; -import { findQueueItemRefIndex } from '@/utils/playback/queueIdentity'; +import { findQueueItemRefIndex } from '@/features/playback/utils/playback/queueIdentity'; import type { TimelineDisplayRow } from '@/utils/queue/buildTimelineDisplayRows'; import { findTimelineScrollLocalIndex } from '@/utils/queue/buildTimelineDisplayRows'; import { playTimelineHistoryTrack } from '@/utils/queue/playTimelineHistoryTrack'; diff --git a/src/features/queue/components/QueuePanel.test.tsx b/src/features/queue/components/QueuePanel.test.tsx index 2c0595ac..391ef999 100644 --- a/src/features/queue/components/QueuePanel.test.tsx +++ b/src/features/queue/components/QueuePanel.test.tsx @@ -33,7 +33,7 @@ vi.mock('@/features/orbit/utils/orbitBulkGuard', () => ({ import QueuePanel from '@/features/queue/components/QueuePanel'; import { fireEvent } from '@testing-library/react'; import { renderWithProviders } from '@/test/helpers/renderWithProviders'; -import { usePlayerStore } from '@/store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { useAuthStore } from '@/store/authStore'; import { resetAllStores } from '@/test/helpers/storeReset'; import { makeTrack, makeTracks, seedQueue } from '@/test/helpers/factories'; diff --git a/src/features/queue/components/QueuePanel.tsx b/src/features/queue/components/QueuePanel.tsx index 931daf57..9724eea8 100644 --- a/src/features/queue/components/QueuePanel.tsx +++ b/src/features/queue/components/QueuePanel.tsx @@ -1,10 +1,10 @@ import { Play } from 'lucide-react'; import { updatePlaylist } from '@/lib/api/subsonicPlaylists'; import { resolvePlaylist, resolveMediaServerId } from '@/features/offline'; -import { songToTrack } from '@/utils/playback/songToTrack'; -import type { Track } from '@/store/playerStoreTypes'; +import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; import { useState, useRef, useMemo } from 'react'; -import { usePlayerStore } from '@/store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { useOrbitStore } from '@/features/orbit'; import { OrbitGuestQueue, OrbitQueueHead } from '@/features/orbit'; import HostApprovalQueue from '@/components/HostApprovalQueue'; @@ -34,7 +34,7 @@ import { QueueTabBar } from '@/features/queue/components/QueueTabBar'; import { useQueueAutoScroll } from '@/features/queue/hooks/useQueueAutoScroll'; import { useTimelineBootstrapOnMode, useTimelineHistoryResolver, useTimelinePlayHistory } from '@/hooks/useTimelinePlayHistory'; import { buildTimelineDisplayRows } from '@/utils/queue/buildTimelineDisplayRows'; -import { activeServerQueueTrackIds } from '@/utils/playback/trackServerScope'; +import { activeServerQueueTrackIds } from '@/features/playback/utils/playback/trackServerScope'; export default function QueuePanel() { const orbitRole = useOrbitStore(s => s.role); diff --git a/src/features/queue/components/QueueToolbar.tsx b/src/features/queue/components/QueueToolbar.tsx index 477a772a..be58bfe3 100644 --- a/src/features/queue/components/QueueToolbar.tsx +++ b/src/features/queue/components/QueueToolbar.tsx @@ -3,12 +3,12 @@ import { Blend, Check, FolderOpen, Infinity as InfinityIcon, ListMusic, MoveRight, Save, Share2, Shuffle, Trash2, Waves, } from 'lucide-react'; import type { TFunction } from 'i18next'; -import type { QueueItemRef } from '@/store/playerStoreTypes'; +import type { QueueItemRef } from '@/features/playback/store/playerStoreTypes'; import type { QueueToolbarButtonConfig, QueueToolbarButtonId, } from '@/store/queueToolbarStore'; -import { getTransitionMode, setTransitionMode } from '@/utils/playback/playbackTransition'; +import { getTransitionMode, setTransitionMode } from '@/features/playback/utils/playback/playbackTransition'; import { useOrbitStore } from '@/features/orbit'; interface Props { diff --git a/src/features/queue/hooks/useQueueAutoScroll.ts b/src/features/queue/hooks/useQueueAutoScroll.ts index dbb25615..99d08008 100644 --- a/src/features/queue/hooks/useQueueAutoScroll.ts +++ b/src/features/queue/hooks/useQueueAutoScroll.ts @@ -1,6 +1,6 @@ import React, { useLayoutEffect } from 'react'; -import { registerQueueListScrollTopReader, consumePendingQueueListScrollTop } from '@/store/queueUndo'; -import type { QueueItemRef, Track } from '@/store/playerStoreTypes'; +import { registerQueueListScrollTopReader, consumePendingQueueListScrollTop } from '@/features/playback/store/queueUndo'; +import type { QueueItemRef, Track } from '@/features/playback/store/playerStoreTypes'; interface Args { queue: QueueItemRef[]; diff --git a/src/features/queue/hooks/useQueuePanelDrag.ts b/src/features/queue/hooks/useQueuePanelDrag.ts index d35931c2..fc52f75b 100644 --- a/src/features/queue/hooks/useQueuePanelDrag.ts +++ b/src/features/queue/hooks/useQueuePanelDrag.ts @@ -1,9 +1,9 @@ import React, { useEffect, useRef, useState } from 'react'; import { resolveAlbum, resolveMediaServerId } from '@/features/offline'; -import { songToTrack } from '@/utils/playback/songToTrack'; +import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; import { useDragDrop, registerQueueDragHitTest } from '@/lib/dnd/DragDropContext'; -import { usePlayerStore } from '@/store/playerStore'; -import type { Track } from '@/store/playerStoreTypes'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; /** Drag types that may be dropped into the queue panel. */ const QUEUE_DROP_TYPES = new Set(['song', 'album', 'queue_reorder']); diff --git a/src/features/queue/hooks/useQueueTrackEnrichment.ts b/src/features/queue/hooks/useQueueTrackEnrichment.ts index a08fb768..6e3b25e2 100644 --- a/src/features/queue/hooks/useQueueTrackEnrichment.ts +++ b/src/features/queue/hooks/useQueueTrackEnrichment.ts @@ -10,7 +10,7 @@ import { type ParsedTrackEnrichment, } from '@/utils/library/trackEnrichment'; import { libraryIsReady } from '@/utils/library/libraryReady'; -import { normalizeAnalysisTrackId } from '@/utils/playback/queueIdentity'; +import { normalizeAnalysisTrackId } from '@/features/playback/utils/playback/queueIdentity'; const EMPTY: ParsedTrackEnrichment = { serverBpm: null, diff --git a/src/features/queue/hooks/useQueueTracks.test.ts b/src/features/queue/hooks/useQueueTracks.test.ts index 2d85b9b9..3cb098e7 100644 --- a/src/features/queue/hooks/useQueueTracks.test.ts +++ b/src/features/queue/hooks/useQueueTracks.test.ts @@ -1,7 +1,7 @@ import { describe, it, expect, beforeEach } from 'vitest'; import { renderHook } from '@testing-library/react'; -import { usePlayerStore } from '@/store/playerStore'; -import type { Track } from '@/store/playerStoreTypes'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; import { getCachedTrack, _resetQueueResolverForTest } from '@/utils/library/queueTrackResolver'; import { seedQueue } from '@/test/helpers/factories'; import { useQueueTrackAt, useCurrentTrack, useQueueItems } from '@/features/queue/hooks/useQueueTracks'; diff --git a/src/features/queue/hooks/useQueueTracks.ts b/src/features/queue/hooks/useQueueTracks.ts index d842c098..ddccd37a 100644 --- a/src/features/queue/hooks/useQueueTracks.ts +++ b/src/features/queue/hooks/useQueueTracks.ts @@ -1,6 +1,6 @@ import { useMemo, useSyncExternalStore } from 'react'; -import { usePlayerStore } from '@/store/playerStore'; -import type { QueueItemRef, Track } from '@/store/playerStoreTypes'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; +import type { QueueItemRef, Track } from '@/features/playback/store/playerStoreTypes'; import { resolveQueueTrack } from '@/utils/library/queueTrackView'; import { getQueueResolverVersion, diff --git a/src/features/radio/pages/InternetRadio.tsx b/src/features/radio/pages/InternetRadio.tsx index 38debe11..49947c51 100644 --- a/src/features/radio/pages/InternetRadio.tsx +++ b/src/features/radio/pages/InternetRadio.tsx @@ -2,9 +2,9 @@ import { getInternetRadioStations, createInternetRadioStation, updateInternetRad import { type InternetRadioStation } from '@/lib/api/subsonicTypes'; import React, { useEffect, useState, useMemo, useCallback } from 'react'; import { Plus, Search } from 'lucide-react'; -import { usePlayerStore } from '@/store/playerStore'; -import { setRadioVolume } from '@/store/radioPlayer'; -import { fadeOut } from '@/utils/playback/fadeOut'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; +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'; diff --git a/src/features/search/components/LiveSearch.tsx b/src/features/search/components/LiveSearch.tsx index 72612082..626d0976 100644 --- a/src/features/search/components/LiveSearch.tsx +++ b/src/features/search/components/LiveSearch.tsx @@ -1,6 +1,6 @@ import { subscribeLibrarySyncIdle, subscribeLibrarySyncProgress } from '@/lib/api/library'; import type { SearchResults, SubsonicArtist } from '@/lib/api/subsonicTypes'; -import { songToTrack } from '@/utils/playback/songToTrack'; +import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; import { LIVE_SEARCH_DEBOUNCE_NETWORK_MS, LIVE_SEARCH_DEBOUNCE_RACE_MS, @@ -24,7 +24,7 @@ import React, { useState, useEffect, useRef, useCallback } from 'react'; import { useNavigate, useLocation } from 'react-router-dom'; import { useNavigateToAlbum } from '@/features/album'; import { Search, Disc3, Users, Music, TextSearch, Database, Globe } from 'lucide-react'; -import { usePlayerStore } from '@/store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { useAuthStore } from '@/store/authStore'; import { useLibraryIndexStore } from '@/store/libraryIndexStore'; import { useTranslation } from 'react-i18next'; diff --git a/src/features/search/components/MobileSearchOverlay.tsx b/src/features/search/components/MobileSearchOverlay.tsx index 22f4f2be..0b76ba03 100644 --- a/src/features/search/components/MobileSearchOverlay.tsx +++ b/src/features/search/components/MobileSearchOverlay.tsx @@ -1,13 +1,13 @@ import { search } from '@/lib/api/subsonicSearch'; import type { SearchResults, SubsonicArtist } from '@/lib/api/subsonicTypes'; -import { songToTrack } from '@/utils/playback/songToTrack'; +import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; import { useLiveSearchScopeStore } from '@/store/liveSearchScopeStore'; import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react'; import { createPortal } from 'react-dom'; import { useLocation, useNavigate } from 'react-router-dom'; import { navigatePathWithAlbumReturnTo } from '@/utils/navigation/albumDetailNavigation'; import { X, Search, Disc3, Users, Music, Music2, Clock, ChevronRight } from 'lucide-react'; -import { usePlayerStore } from '@/store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { useAuthStore } from '@/store/authStore'; import { useTranslation } from 'react-i18next'; import { FETCH_QUEUE_BIAS_SEARCH_ARTIST_OVER_ALBUM } from '@/ui/CachedImage'; diff --git a/src/features/search/components/ShareQueuePreviewModal.tsx b/src/features/search/components/ShareQueuePreviewModal.tsx index 5c5ccccf..605de872 100644 --- a/src/features/search/components/ShareQueuePreviewModal.tsx +++ b/src/features/search/components/ShareQueuePreviewModal.tsx @@ -8,7 +8,7 @@ import { formatTrackTime } from '@/lib/format/formatDuration'; import type { ShareQueuePreviewState } from '@/features/search/hooks/useShareQueuePreview'; import { sharePayloadTotal, type QueueableShareSearchPayload } from '@/utils/share/shareSearch'; import OverlayScrollArea from '@/ui/OverlayScrollArea'; -import { usePlayerStore } from '@/store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { COVER_DENSE_SEARCH_CSS_PX } from '@/cover/layoutSizes'; import { COVER_SCOPE_ACTIVE, type CoverServerScope } from '@/cover/types'; import { AlbumCoverArtImage } from '@/cover/AlbumCoverArtImage'; diff --git a/src/features/search/components/ShareSearchResults.tsx b/src/features/search/components/ShareSearchResults.tsx index 58b91074..191859d3 100644 --- a/src/features/search/components/ShareSearchResults.tsx +++ b/src/features/search/components/ShareSearchResults.tsx @@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next'; import type { TFunction } from 'i18next'; import type { SubsonicArtist } from '@/lib/api/subsonicTypes'; import type { ServerProfile } from '@/store/authStoreTypes'; -import { songToTrack } from '@/utils/playback/songToTrack'; +import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; import { activateShareSearchServer } from '@/utils/share/enqueueShareSearchPayload'; import { sharePayloadTotal, type ShareSearchMatch } from '@/utils/share/shareSearch'; import type { ShareSearchPreviewState } from '@/features/search/hooks/useShareSearchPreview'; diff --git a/src/features/search/pages/SearchBrowsePage.tsx b/src/features/search/pages/SearchBrowsePage.tsx index 112a0f68..b1f84d7a 100644 --- a/src/features/search/pages/SearchBrowsePage.tsx +++ b/src/features/search/pages/SearchBrowsePage.tsx @@ -14,7 +14,7 @@ import StarFilterButton from '@/components/StarFilterButton'; import { tooltipAttrs } from '@/ui/tooltipAttrs'; import { APP_MAIN_SCROLL_VIEWPORT_ID } from '@/constants/appScroll'; import { useAuthStore } from '@/store/authStore'; -import { usePlayerStore } from '@/store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { isAdvancedSearchLeaveTargetPath } from '@/features/album'; import { isAdvancedSearchPath, diff --git a/src/features/settings/components/StorageTab.tsx b/src/features/settings/components/StorageTab.tsx index 5d8bef34..adf3a192 100644 --- a/src/features/settings/components/StorageTab.tsx +++ b/src/features/settings/components/StorageTab.tsx @@ -4,7 +4,7 @@ import { invoke } from '@tauri-apps/api/core'; import { open as openDialog } from '@tauri-apps/plugin-dialog'; import { Download, FolderOpen, Trash2, X } from 'lucide-react'; import { useAuthStore } from '@/store/authStore'; -import { countHotCacheTracks } from '@/store/hotCacheStore'; +import { countHotCacheTracks } from '@/features/playback/store/hotCacheStore'; import { useLocalPlaybackStore } from '@/store/localPlaybackStore'; import { formatBytes, snapHotCacheMb } from '@/lib/format/formatBytes'; import SettingsSubSection from '@/features/settings/components/SettingsSubSection'; diff --git a/src/features/settings/components/audio/AudioOutputDeviceSection.tsx b/src/features/settings/components/audio/AudioOutputDeviceSection.tsx index e5024227..e88cbd39 100644 --- a/src/features/settings/components/audio/AudioOutputDeviceSection.tsx +++ b/src/features/settings/components/audio/AudioOutputDeviceSection.tsx @@ -8,7 +8,7 @@ import { SettingsGroup } from '@/features/settings/components/SettingsGroup'; import { SettingsToggle } from '@/features/settings/components/SettingsToggle'; import { useAuthStore } from '@/store/authStore'; import { useEqStore } from '@/store/eqStore'; -import { buildAudioDeviceSelectOptions } from '@/utils/audio/audioDeviceLabels'; +import { buildAudioDeviceSelectOptions } from '@/features/playback/utils/audio/audioDeviceLabels'; interface Props { audioDevices: string[]; diff --git a/src/features/settings/components/audio/PlaybackRateBlock.tsx b/src/features/settings/components/audio/PlaybackRateBlock.tsx index 806b71ee..b437597a 100644 --- a/src/features/settings/components/audio/PlaybackRateBlock.tsx +++ b/src/features/settings/components/audio/PlaybackRateBlock.tsx @@ -17,8 +17,8 @@ import { playbackSpeedStep, varispeedSpeedFromSemitones, type PlaybackStrategy, -} from '@/utils/audio/playbackRateHelpers'; -import { usePlaybackRateStore } from '@/store/playbackRateStore'; +} from '@/features/playback/utils/audio/playbackRateHelpers'; +import { usePlaybackRateStore } from '@/features/playback/store/playbackRateStore'; import { useOrbitStore } from '@/features/orbit'; import { useAuthStore } from '@/store/authStore'; import { isOrbitPlaybackSyncActive } from '@/features/orbit'; diff --git a/src/features/settings/components/audio/TrackTransitionsBlock.tsx b/src/features/settings/components/audio/TrackTransitionsBlock.tsx index 6387adba..15dff162 100644 --- a/src/features/settings/components/audio/TrackTransitionsBlock.tsx +++ b/src/features/settings/components/audio/TrackTransitionsBlock.tsx @@ -10,7 +10,7 @@ import { getTransitionMode, setTransitionMode, type TransitionMode, -} from '@/utils/playback/playbackTransition'; +} from '@/features/playback/utils/playback/playbackTransition'; import { SettingsGroup } from '@/features/settings/components/SettingsGroup'; import { SettingsToggle } from '@/features/settings/components/SettingsToggle'; import { SettingsSubCard, SettingsField, SettingsRow, SettingsValue } from '@/features/settings/components/SettingsSubCard'; diff --git a/src/features/sidebar/components/Sidebar.tsx b/src/features/sidebar/components/Sidebar.tsx index 9557150d..abd6774a 100644 --- a/src/features/sidebar/components/Sidebar.tsx +++ b/src/features/sidebar/components/Sidebar.tsx @@ -1,6 +1,6 @@ import { useState, useRef, useEffect, useMemo } from 'react'; import { createPortal } from 'react-dom'; -import { usePlayerStore } from '@/store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { useOfflineJobStore } from '@/features/offline'; import { clearOfflinePinTasks } from '@/features/offline'; import { useDeviceSyncJobStore } from '@/features/deviceSync'; diff --git a/src/features/sidebar/components/SidebarPlaylistsSection.tsx b/src/features/sidebar/components/SidebarPlaylistsSection.tsx index 13b65659..fb570f41 100644 --- a/src/features/sidebar/components/SidebarPlaylistsSection.tsx +++ b/src/features/sidebar/components/SidebarPlaylistsSection.tsx @@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next'; import { ChevronRight, Folder, PlayCircle, Sparkles } from 'lucide-react'; import { displayPlaylistName, isSmartPlaylistName } from '@/features/sidebar/utils/sidebarHelpers'; import { useAuthStore } from '@/store/authStore'; -import { usePlayerStore } from '@/store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { usePlaylistStore } from '@/features/playlist'; import { EMPTY_SERVER_FOLDERS, usePlaylistFolderStore } from '@/features/playlist'; import { groupPlaylistsByFolder } from '@/features/playlist'; diff --git a/src/features/sidebar/components/perfProbe/SidebarPerfProbeConnectionsTab.tsx b/src/features/sidebar/components/perfProbe/SidebarPerfProbeConnectionsTab.tsx index c954af09..b687a643 100644 --- a/src/features/sidebar/components/perfProbe/SidebarPerfProbeConnectionsTab.tsx +++ b/src/features/sidebar/components/perfProbe/SidebarPerfProbeConnectionsTab.tsx @@ -1,5 +1,5 @@ import { useAuthStore } from '@/store/authStore'; -import { usePlayerStore } from '@/store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { useConnectionStatus } from '@/hooks/useConnectionStatus'; import { useNavidromeAdminRole } from '@/hooks/useNavidromeAdminRole'; import { serverListDisplayLabel } from '@/utils/server/serverDisplayName'; diff --git a/src/features/stats/hooks/usePlayerStatsLiveRefresh.test.ts b/src/features/stats/hooks/usePlayerStatsLiveRefresh.test.ts index 7ef0d02d..09859550 100644 --- a/src/features/stats/hooks/usePlayerStatsLiveRefresh.test.ts +++ b/src/features/stats/hooks/usePlayerStatsLiveRefresh.test.ts @@ -1,7 +1,7 @@ import { renderHook } from '@testing-library/react'; import { describe, expect, it, vi } from 'vitest'; import { usePlayerStatsLiveRefresh } from '@/features/stats/hooks/usePlayerStatsLiveRefresh'; -import { emitPlaySessionRecorded } from '@/store/playSessionRecorded'; +import { emitPlaySessionRecorded } from '@/features/playback/store/playSessionRecorded'; describe('usePlayerStatsLiveRefresh', () => { it('refreshes when a play session is recorded and the tab is visible', () => { diff --git a/src/features/stats/hooks/usePlayerStatsLiveRefresh.ts b/src/features/stats/hooks/usePlayerStatsLiveRefresh.ts index 32004b92..5f3873d8 100644 --- a/src/features/stats/hooks/usePlayerStatsLiveRefresh.ts +++ b/src/features/stats/hooks/usePlayerStatsLiveRefresh.ts @@ -1,5 +1,5 @@ import { useEffect, useRef } from 'react'; -import { onPlaySessionRecorded } from '@/store/playSessionRecorded'; +import { onPlaySessionRecorded } from '@/features/playback/store/playSessionRecorded'; /** Refresh player stats when a listen is persisted or the tab becomes visible again. */ export function usePlayerStatsLiveRefresh(onRefresh: () => void) { diff --git a/src/features/waveform/components/WaveformSeek.test.tsx b/src/features/waveform/components/WaveformSeek.test.tsx index ca7ebb7d..575b98c6 100644 --- a/src/features/waveform/components/WaveformSeek.test.tsx +++ b/src/features/waveform/components/WaveformSeek.test.tsx @@ -27,7 +27,7 @@ vi.mock('@/lib/api/subsonic', () => ({ import WaveformSeek from '@/features/waveform/components/WaveformSeek'; import { renderWithProviders } from '@/test/helpers/renderWithProviders'; -import { usePlayerStore } from '@/store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { useAuthStore } from '@/store/authStore'; import { resetAllStores } from '@/test/helpers/storeReset'; import { makeTrack } from '@/test/helpers/factories'; diff --git a/src/features/waveform/components/WaveformSeek.tsx b/src/features/waveform/components/WaveformSeek.tsx index c62b16cf..c1a8f863 100644 --- a/src/features/waveform/components/WaveformSeek.tsx +++ b/src/features/waveform/components/WaveformSeek.tsx @@ -1,7 +1,7 @@ -import { getPlaybackProgressSnapshot, subscribePlaybackProgress } from '@/store/playbackProgress'; +import { getPlaybackProgressSnapshot, subscribePlaybackProgress } from '@/features/playback/store/playbackProgress'; import { useEffect, useRef, useState } from 'react'; -import { usePlayerStore } from '@/store/playerStore'; -import { usePreviewStore } from '@/store/previewStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; +import { usePreviewStore } from '@/features/playback/store/previewStore'; import { useAuthStore } from '@/store/authStore'; import { ANIMATED_STYLES, AnimState, diff --git a/src/features/waveform/hooks/useWaveformInterpolation.ts b/src/features/waveform/hooks/useWaveformInterpolation.ts index d360eb09..ab25ce2c 100644 --- a/src/features/waveform/hooks/useWaveformInterpolation.ts +++ b/src/features/waveform/hooks/useWaveformInterpolation.ts @@ -1,6 +1,6 @@ import React, { useEffect } from 'react'; import type { SeekbarStyle } from '@/store/authStoreTypes'; -import { getPlaybackProgressSnapshot } from '@/store/playbackProgress'; +import { getPlaybackProgressSnapshot } from '@/features/playback/store/playbackProgress'; import { ANIMATED_STYLES, AnimState, INTERPOLATION_PAINT_MIN_MS, isBarQuantizedSeekStyle, quantizeProgressByBars, diff --git a/src/hooks/tauriBridge/useAudioDeviceBridge.test.ts b/src/hooks/tauriBridge/useAudioDeviceBridge.test.ts index 9ebd1a75..2e58814e 100644 --- a/src/hooks/tauriBridge/useAudioDeviceBridge.test.ts +++ b/src/hooks/tauriBridge/useAudioDeviceBridge.test.ts @@ -15,10 +15,10 @@ import { renderHook } from '@testing-library/react'; import { emitTauriEvent } from '@/test/mocks/tauri'; import { resetAllStores } from '@/test/helpers/storeReset'; import { makeTrack } from '@/test/helpers/factories'; -import { usePlayerStore } from '@/store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { useAuthStore } from '@/store/authStore'; -import { getSeekFallbackVisualTarget, setSeekFallbackVisualTarget } from '@/store/seekFallbackState'; -import { setIsAudioPaused } from '@/store/engineState'; +import { getSeekFallbackVisualTarget, setSeekFallbackVisualTarget } from '@/features/playback/store/seekFallbackState'; +import { setIsAudioPaused } from '@/features/playback/store/engineState'; import { useAudioDeviceBridge } from './useAudioDeviceBridge'; const track = makeTrack({ id: 't1', duration: 300 }); diff --git a/src/hooks/tauriBridge/useAudioDeviceBridge.ts b/src/hooks/tauriBridge/useAudioDeviceBridge.ts index ced55f25..7f679513 100644 --- a/src/hooks/tauriBridge/useAudioDeviceBridge.ts +++ b/src/hooks/tauriBridge/useAudioDeviceBridge.ts @@ -1,9 +1,9 @@ import { useEffect } from 'react'; import { listen } from '@tauri-apps/api/event'; -import { usePlayerStore } from '../../store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { useAuthStore } from '../../store/authStore'; -import { setSeekFallbackVisualTarget } from '../../store/seekFallbackState'; -import { getIsAudioPaused } from '../../store/engineState'; +import { setSeekFallbackVisualTarget } from '@/features/playback/store/seekFallbackState'; +import { getIsAudioPaused } from '@/features/playback/store/engineState'; /** Audio output device lifecycle: device switches (Bluetooth headphones, USB * DAC, …) and pinned-device-unplugged fallbacks emitted by the Rust diff --git a/src/hooks/tauriBridge/useCliBridge.ts b/src/hooks/tauriBridge/useCliBridge.ts index a2ceac8d..4e8b8043 100644 --- a/src/hooks/tauriBridge/useCliBridge.ts +++ b/src/hooks/tauriBridge/useCliBridge.ts @@ -6,12 +6,12 @@ import { getSimilarSongs } from '@/lib/api/subsonicArtists'; import { getMusicFolders } from '@/lib/api/subsonicLibrary'; import { search as subsonicSearch } from '@/lib/api/subsonicSearch'; import { filterSongsForLuckyMixRatings, getMixMinRatingsConfigFromAuth } from '../../utils/mix/mixRatingFilter'; -import { shuffleArray } from '../../utils/playback/shuffleArray'; -import { songToTrack } from '../../utils/playback/songToTrack'; +import { shuffleArray } from '@/features/playback/utils/playback/shuffleArray'; +import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; import { showToast } from '../../utils/ui/toast'; import { switchActiveServer } from '../../utils/server/switchActiveServer'; import i18n from '@/lib/i18n'; -import { usePlayerStore } from '../../store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { useAuthStore } from '../../store/authStore'; import { executeCliPlayerCommand } from '@/config/shortcutActions'; diff --git a/src/hooks/tauriBridge/useMediaAndWindowBridge.ts b/src/hooks/tauriBridge/useMediaAndWindowBridge.ts index a5a2bd51..f79c5817 100644 --- a/src/hooks/tauriBridge/useMediaAndWindowBridge.ts +++ b/src/hooks/tauriBridge/useMediaAndWindowBridge.ts @@ -3,11 +3,11 @@ import { listen } from '@tauri-apps/api/event'; import { invoke } from '@tauri-apps/api/core'; import { getCurrentWindow } from '@tauri-apps/api/window'; import type { NavigateFunction } from 'react-router-dom'; -import { flushPlayQueuePosition } from '../../store/queueSync'; -import { playListenSessionFinalize } from '../../store/playListenSession'; -import { playbackReportStopped } from '../../store/playbackReportSession'; -import { getPlaybackProgressSnapshot } from '../../store/playbackProgress'; -import { usePlayerStore } from '../../store/playerStore'; +import { flushPlayQueuePosition } from '@/features/playback/store/queueSync'; +import { playListenSessionFinalize } from '@/features/playback/store/playListenSession'; +import { playbackReportStopped } from '@/features/playback/store/playbackReportSession'; +import { getPlaybackProgressSnapshot } from '@/features/playback/store/playbackProgress'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { useAuthStore } from '../../store/authStore'; import { useOrbitStore } from '@/features/orbit'; import { endOrbitSession, leaveOrbitSession } from '@/features/orbit'; diff --git a/src/hooks/tauriBridge/usePlayerSnapshotPublisher.ts b/src/hooks/tauriBridge/usePlayerSnapshotPublisher.ts index 5505a69c..8d60dc9e 100644 --- a/src/hooks/tauriBridge/usePlayerSnapshotPublisher.ts +++ b/src/hooks/tauriBridge/usePlayerSnapshotPublisher.ts @@ -1,7 +1,7 @@ import { useEffect } from 'react'; import { invoke } from '@tauri-apps/api/core'; -import { getPlaybackProgressSnapshot } from '../../store/playbackProgress'; -import { usePlayerStore } from '../../store/playerStore'; +import { getPlaybackProgressSnapshot } from '@/features/playback/store/playbackProgress'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { useAuthStore } from '../../store/authStore'; import { resolveQueueTrack } from '../../utils/library/queueTrackView'; diff --git a/src/hooks/tauriBridge/usePreviewBridge.ts b/src/hooks/tauriBridge/usePreviewBridge.ts index c9ff91d8..6bbbc4b5 100644 --- a/src/hooks/tauriBridge/usePreviewBridge.ts +++ b/src/hooks/tauriBridge/usePreviewBridge.ts @@ -1,6 +1,6 @@ import { useEffect } from 'react'; import { listen } from '@tauri-apps/api/event'; -import { usePreviewStore } from '../../store/previewStore'; +import { usePreviewStore } from '@/features/playback/store/previewStore'; /** Track-preview lifecycle: Rust audio engine emits start/progress/end. The * store mirrors them so any tracklist row can render its preview UI. */ diff --git a/src/hooks/useAudioDevicesProbe.ts b/src/hooks/useAudioDevicesProbe.ts index 8f6c2bf1..b22fecfa 100644 --- a/src/hooks/useAudioDevicesProbe.ts +++ b/src/hooks/useAudioDevicesProbe.ts @@ -4,7 +4,7 @@ import { listen } from '@tauri-apps/api/event'; import type { TFunction } from 'i18next'; import { useAuthStore } from '../store/authStore'; import { IS_MACOS } from '@/lib/util/platform'; -import { sortAudioDeviceIds } from '../utils/audio/audioDeviceLabels'; +import { sortAudioDeviceIds } from '@/features/playback/utils/audio/audioDeviceLabels'; import { showToast } from '../utils/ui/toast'; interface UseAudioDevicesProbeResult { diff --git a/src/hooks/useAutoEq.ts b/src/hooks/useAutoEq.ts index 420340cb..97ab2ccc 100644 --- a/src/hooks/useAutoEq.ts +++ b/src/hooks/useAutoEq.ts @@ -2,7 +2,7 @@ import { useState, useRef, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { invoke } from '@tauri-apps/api/core'; import { useEqStore } from '../store/eqStore'; -import { parseFixedBandEqString, type AutoEqVariant, type AutoEqResult } from '../utils/audio/autoEqParse'; +import { parseFixedBandEqString, type AutoEqVariant, type AutoEqResult } from '@/features/playback/utils/audio/autoEqParse'; /** AutoEQ search/apply state for the Equalizer. Loads the entries index lazily * when the section opens, filters client-side, and applies fetched profiles via diff --git a/src/hooks/useContextMenuRating.ts b/src/hooks/useContextMenuRating.ts index eb5d15bc..28697bbd 100644 --- a/src/hooks/useContextMenuRating.ts +++ b/src/hooks/useContextMenuRating.ts @@ -1,8 +1,8 @@ import { useCallback } from 'react'; import { setRating } from '@/lib/api/subsonicStarRating'; -import { queueSongRating } from '../store/pendingStarSync'; +import { queueSongRating } from '@/features/playback/store/pendingStarSync'; import type { SubsonicAlbum, SubsonicArtist } from '@/lib/api/subsonicTypes'; -import type { Track } from '../store/playerStoreTypes'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; import { useAuthStore } from '../store/authStore'; import { showToast } from '../utils/ui/toast'; diff --git a/src/hooks/useFolderBrowserKeyboardNav.ts b/src/hooks/useFolderBrowserKeyboardNav.ts index b0d12351..ca2f8d73 100644 --- a/src/hooks/useFolderBrowserKeyboardNav.ts +++ b/src/hooks/useFolderBrowserKeyboardNav.ts @@ -1,6 +1,6 @@ import { useCallback } from 'react'; import type { SubsonicDirectoryEntry } from '@/lib/api/subsonicTypes'; -import { usePlayerStore } from '../store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { entryToTrack, folderBrowserHasKeyModifiers, isFolderBrowserArrowKey, type Column, type NavPos, diff --git a/src/hooks/useFolderBrowserNowPlayingPath.ts b/src/hooks/useFolderBrowserNowPlayingPath.ts index e9708e77..a29ef056 100644 --- a/src/hooks/useFolderBrowserNowPlayingPath.ts +++ b/src/hooks/useFolderBrowserNowPlayingPath.ts @@ -2,7 +2,7 @@ import { useCallback, useEffect, useRef, useState } from 'react'; import { useLocation } from 'react-router-dom'; import { getMusicDirectory, getMusicIndexes } from '@/lib/api/subsonicLibrary'; import type { SubsonicDirectoryEntry } from '@/lib/api/subsonicTypes'; -import type { Track } from '../store/playerStoreTypes'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; import type { Column, NavPos } from '../utils/componentHelpers/folderBrowserHelpers'; let persistedPlayingPathIds: string[] = []; diff --git a/src/hooks/useIdlePlayQueuePull.ts b/src/hooks/useIdlePlayQueuePull.ts index 7add1499..4eb2313c 100644 --- a/src/hooks/useIdlePlayQueuePull.ts +++ b/src/hooks/useIdlePlayQueuePull.ts @@ -1,16 +1,16 @@ import { useEffect, useRef } from 'react'; -import { applyServerPlayQueue } from '../store/applyServerPlayQueue'; +import { applyServerPlayQueue } from '@/features/playback/store/applyServerPlayQueue'; import { useAuthStore } from '../store/authStore'; import { useOrbitStore } from '@/features/orbit'; -import { usePlayerStore } from '../store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { getPlaybackIdleSinceMs, isIdleQueuePullSuspended, isQueueNaturallyEnded, isPlaybackIdleLongEnough, markPlaybackIdle, -} from '../store/queuePlaybackIdle'; -import { hasPendingQueueSync } from '../store/queueSync'; +} from '@/features/playback/store/queuePlaybackIdle'; +import { hasPendingQueueSync } from '@/features/playback/store/queueSync'; import type { ConnectionStatus } from './useConnectionStatus'; import { canAutoIdlePlayQueuePull } from './usePlayQueueSyncLedState'; diff --git a/src/hooks/useLyrics.ts b/src/hooks/useLyrics.ts index af76bbe0..9c49b950 100644 --- a/src/hooks/useLyrics.ts +++ b/src/hooks/useLyrics.ts @@ -1,6 +1,6 @@ import { getLyricsBySongId } from '@/lib/api/subsonicLyrics'; import type { SubsonicStructuredLyrics } from '@/lib/api/subsonicTypes'; -import type { Track } from '../store/playerStoreTypes'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; import { useEffect, useState } from 'react'; import { useShallow } from 'zustand/react/shallow'; import { invoke } from '@tauri-apps/api/core'; @@ -9,7 +9,7 @@ import { fetchNeteaselyrics } from '../api/netease'; import { fetchLyricsPlus, hasWordSync } from '../api/lyricsplus'; import { useAuthStore } from '../store/authStore'; import { useOfflineStore } from '@/features/offline'; -import { useHotCacheStore } from '../store/hotCacheStore'; +import { useHotCacheStore } from '@/features/playback/store/hotCacheStore'; import { getCachedLyrics, putCachedLyrics, lyricsCacheKey } from '../utils/cache/lyricsPersistentCache'; export type LyricsSource = 'server' | 'lrclib' | 'netease' | 'embedded' | 'lyricsplus'; diff --git a/src/hooks/useNowPlayingTrayTitle.ts b/src/hooks/useNowPlayingTrayTitle.ts index 7ae9bc35..d95f61cf 100644 --- a/src/hooks/useNowPlayingTrayTitle.ts +++ b/src/hooks/useNowPlayingTrayTitle.ts @@ -1,7 +1,7 @@ import { useEffect } from 'react'; import { invoke } from '@tauri-apps/api/core'; import { getCurrentWindow } from '@tauri-apps/api/window'; -import type { Track } from '../store/playerStoreTypes'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; /** * Keep `document.title`, the OS window title, and the tray tooltip in sync diff --git a/src/hooks/usePlayQueueSyncLedState.ts b/src/hooks/usePlayQueueSyncLedState.ts index b58a39d2..a273ac74 100644 --- a/src/hooks/usePlayQueueSyncLedState.ts +++ b/src/hooks/usePlayQueueSyncLedState.ts @@ -1,16 +1,16 @@ import { useCallback, useEffect, useMemo, useState, useSyncExternalStore } from 'react'; import { useTranslation } from 'react-i18next'; import type { ConnectionStatus } from '../hooks/useConnectionStatus'; -import { pullPlayQueueFromActiveServer } from '../store/applyServerPlayQueue'; +import { pullPlayQueueFromActiveServer } from '@/features/playback/store/applyServerPlayQueue'; import { useAuthStore } from '../store/authStore'; import { useOrbitStore } from '@/features/orbit'; -import { usePlayerStore } from '../store/playerStore'; -import { getPlaybackServerId, queueIsMultiServer } from '../utils/playback/playbackServer'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; +import { getPlaybackServerId, queueIsMultiServer } from '@/features/playback/utils/playback/playbackServer'; import { getIdleQueuePullSuspendedSnapshot, subscribeIdleQueuePullSuspended, -} from '../store/queuePlaybackIdle'; -import { clearQueueHandoffPending, isQueueHandoffPending } from '../store/queueSyncUiState'; +} from '@/features/playback/store/queuePlaybackIdle'; +import { clearQueueHandoffPending, isQueueHandoffPending } from '@/features/playback/store/queueSyncUiState'; import { showToast } from '../utils/ui/toast'; export function usePlayQueueSyncLedState(status: ConnectionStatus) { diff --git a/src/hooks/usePlaybackLibraryNavigate.ts b/src/hooks/usePlaybackLibraryNavigate.ts index 144fef6d..0d7e9bca 100644 --- a/src/hooks/usePlaybackLibraryNavigate.ts +++ b/src/hooks/usePlaybackLibraryNavigate.ts @@ -1,6 +1,6 @@ import { useCallback } from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; -import { ensurePlaybackServerActive } from '../utils/playback/playbackServer'; +import { ensurePlaybackServerActive } from '@/features/playback/utils/playback/playbackServer'; import { navigatePathWithAlbumReturnTo } from '../utils/navigation/albumDetailNavigation'; /** Navigate to library routes for the playing queue — switches to {@link queueServerId} when needed. */ diff --git a/src/hooks/usePlaybackServerId.test.ts b/src/hooks/usePlaybackServerId.test.ts index 2dbec337..6c0d6830 100644 --- a/src/hooks/usePlaybackServerId.test.ts +++ b/src/hooks/usePlaybackServerId.test.ts @@ -1,7 +1,7 @@ import { renderHook } from '@testing-library/react'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { useAuthStore } from '../store/authStore'; -import { usePlayerStore } from '../store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { usePlaybackServerId } from './usePlaybackServerId'; vi.mock('../utils/server/switchActiveServer', () => ({ diff --git a/src/hooks/usePlaybackServerId.ts b/src/hooks/usePlaybackServerId.ts index ea68b490..116109ef 100644 --- a/src/hooks/usePlaybackServerId.ts +++ b/src/hooks/usePlaybackServerId.ts @@ -1,7 +1,7 @@ import { useMemo } from 'react'; import { useAuthStore } from '../store/authStore'; -import { usePlayerStore } from '../store/playerStore'; -import { getPlaybackServerId } from '../utils/playback/playbackServer'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; +import { getPlaybackServerId } from '@/features/playback/utils/playback/playbackServer'; /** * Subsonic server that owns the current queue / stream (may differ from the browsed diff --git a/src/hooks/useTimelinePlayHistory.test.ts b/src/hooks/useTimelinePlayHistory.test.ts index f66c3b86..b47043c7 100644 --- a/src/hooks/useTimelinePlayHistory.test.ts +++ b/src/hooks/useTimelinePlayHistory.test.ts @@ -2,7 +2,7 @@ import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest'; import { _resetTimelineSessionHistoryForTest, isTimelineBootstrapAttempted, -} from '../store/timelineSessionHistory'; +} from '@/features/playback/store/timelineSessionHistory'; import { _resetTimelineBootstrapInFlightForTest, ensureTimelineBootstrap, diff --git a/src/hooks/useTimelinePlayHistory.ts b/src/hooks/useTimelinePlayHistory.ts index da4a5560..a9e3ee2b 100644 --- a/src/hooks/useTimelinePlayHistory.ts +++ b/src/hooks/useTimelinePlayHistory.ts @@ -9,7 +9,7 @@ import { subscribeTimelineSessionHistory, TIMELINE_HISTORY_BOOTSTRAP_LIMIT, type TimelinePlayedRef, -} from '../store/timelineSessionHistory'; +} from '@/features/playback/store/timelineSessionHistory'; import { bootstrapTrackFromPlaySession, timelineHistoryToQueueRefs, diff --git a/src/hooks/useWordLyricsSync.ts b/src/hooks/useWordLyricsSync.ts index 55b90dbb..c1887a42 100644 --- a/src/hooks/useWordLyricsSync.ts +++ b/src/hooks/useWordLyricsSync.ts @@ -1,6 +1,6 @@ import { useEffect, useRef } from 'react'; -import { getPlaybackProgressSnapshot, subscribePlaybackProgress } from '../store/playbackProgress'; -import type { Track } from '../store/playerStoreTypes'; +import { getPlaybackProgressSnapshot, subscribePlaybackProgress } from '@/features/playback/store/playbackProgress'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; import type { WordLyricsLine } from './useLyrics'; interface Args { diff --git a/src/hotCachePrefetch.ts b/src/hotCachePrefetch.ts index a82827d1..18c513fa 100644 --- a/src/hotCachePrefetch.ts +++ b/src/hotCachePrefetch.ts @@ -1,14 +1,14 @@ import { buildStreamUrlForServer } from '@/lib/api/subsonicStreamUrl'; -import { getPlaybackCacheServerKey } from './utils/playback/playbackServer'; -import type { QueueItemRef } from './store/playerStoreTypes'; +import { getPlaybackCacheServerKey } from '@/features/playback/utils/playback/playbackServer'; +import type { QueueItemRef } from '@/features/playback/store/playerStoreTypes'; import { resolveQueueTrack } from './utils/library/queueTrackView'; import { invoke } from '@tauri-apps/api/core'; import { useAuthStore } from './store/authStore'; -import { selectHotCacheEntries, useHotCacheStore } from './store/hotCacheStore'; +import { selectHotCacheEntries, useHotCacheStore } from '@/features/playback/store/hotCacheStore'; import { useLocalPlaybackStore } from './store/localPlaybackStore'; import { getMediaDir } from './utils/media/mediaDir'; import { librarySqlServerId } from './api/coverCache'; -import { usePlayerStore } from './store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { bumpHotCachePreviousTrackGrace, clearHotCachePreviousGrace, diff --git a/src/hotCachePrefetch/analysisPrune.ts b/src/hotCachePrefetch/analysisPrune.ts index 0ebc9168..c0f31992 100644 --- a/src/hotCachePrefetch/analysisPrune.ts +++ b/src/hotCachePrefetch/analysisPrune.ts @@ -1,8 +1,8 @@ import { invoke } from '@tauri-apps/api/core'; import { useAuthStore } from '../store/authStore'; -import { usePlayerStore } from '../store/playerStore'; -import { collectPlaybackMiddlePriorityTrackIds } from '../store/loudnessBackfillWindow'; -import { getPlaybackServerId } from '../utils/playback/playbackServer'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; +import { collectPlaybackMiddlePriorityTrackIds } from '@/features/playback/store/loudnessBackfillWindow'; +import { getPlaybackServerId } from '@/features/playback/utils/playback/playbackServer'; import { analysisSetPlaybackPriorityHints } from '../api/analysis'; import { serverIndexKeyFromUrl } from '../utils/server/serverIndexKey'; import { hotCacheFrontendDebug } from './helpers'; diff --git a/src/hotCachePrefetch/helpers.ts b/src/hotCachePrefetch/helpers.ts index c2512847..14ff1afd 100644 --- a/src/hotCachePrefetch/helpers.ts +++ b/src/hotCachePrefetch/helpers.ts @@ -1,7 +1,7 @@ import { invoke } from '@tauri-apps/api/core'; -import type { QueueItemRef, Track } from '../store/playerStoreTypes'; +import type { QueueItemRef, Track } from '@/features/playback/store/playerStoreTypes'; import { useAuthStore } from '../store/authStore'; -import { HOT_CACHE_PROTECT_AFTER_CURRENT, type HotCacheEntry } from '../store/hotCacheStore'; +import { HOT_CACHE_PROTECT_AFTER_CURRENT, type HotCacheEntry } from '@/features/playback/store/hotCacheStore'; /** Settings → Logging → Debug (`frontend_debug_log` → Rust stderr), same as normalization / lucky-mix. */ export function hotCacheFrontendDebug(payload: Record): void { diff --git a/src/lib/api/subsonicScrobble.test.ts b/src/lib/api/subsonicScrobble.test.ts index b4989297..e7e0f4ca 100644 --- a/src/lib/api/subsonicScrobble.test.ts +++ b/src/lib/api/subsonicScrobble.test.ts @@ -1,6 +1,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import { useAuthStore } from '@/store/authStore'; -import { usePlayerStore } from '@/store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { reportNowPlaying, scrobbleSong } from '@/lib/api/subsonicScrobble'; import { shouldAttemptSubsonicForServer } from '@/utils/network/subsonicNetworkGuard'; diff --git a/src/pages/ComposerDetail.tsx b/src/pages/ComposerDetail.tsx index 65ca2c3f..78ea18b1 100644 --- a/src/pages/ComposerDetail.tsx +++ b/src/pages/ComposerDetail.tsx @@ -11,7 +11,7 @@ import { useCoverLightboxSrc } from '../cover/lightbox'; import { ArrowLeft, Users, Heart, Feather, Share2 } from 'lucide-react'; import WikipediaIcon from '../components/WikipediaIcon'; import { open } from '@tauri-apps/plugin-shell'; -import { usePlayerStore } from '../store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { useAuthStore } from '../store/authStore'; import { useTranslation } from 'react-i18next'; import { copyEntityShareLink } from '../utils/share/copyEntityShareLink'; diff --git a/src/pages/Composers.tsx b/src/pages/Composers.tsx index 668aec78..ea0f0006 100644 --- a/src/pages/Composers.tsx +++ b/src/pages/Composers.tsx @@ -4,7 +4,7 @@ import { useLocation, useNavigate } from 'react-router-dom'; import { ndListArtistsByRole } from '@/lib/api/navidromeBrowse'; import { LayoutGrid, List } from 'lucide-react'; import StarFilterButton from '../components/StarFilterButton'; -import { usePlayerStore } from '../store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { useAuthStore } from '../store/authStore'; import { useTranslation } from 'react-i18next'; import { useVirtualizer } from '@tanstack/react-virtual'; diff --git a/src/pages/FolderBrowser.tsx b/src/pages/FolderBrowser.tsx index b912c365..6398cc9d 100644 --- a/src/pages/FolderBrowser.tsx +++ b/src/pages/FolderBrowser.tsx @@ -1,7 +1,7 @@ import { getMusicFolders, getMusicDirectory, getMusicIndexes } from '@/lib/api/subsonicLibrary'; import type { SubsonicDirectoryEntry, SubsonicArtist } from '@/lib/api/subsonicTypes'; import React, { useEffect, useRef, useState, useCallback, useMemo } from 'react'; -import { usePlayerStore } from '../store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { useTranslation } from 'react-i18next'; import { entryToAlbumIfPresent, entryToTrack, diff --git a/src/pages/GenreDetail.tsx b/src/pages/GenreDetail.tsx index 1e10db0f..708d93a1 100644 --- a/src/pages/GenreDetail.tsx +++ b/src/pages/GenreDetail.tsx @@ -18,7 +18,7 @@ import { useLongPressAction } from '@/lib/hooks/useLongPressAction'; import { useMainstageInpageHeaderTight } from '../hooks/useMainstageInpageHeaderTight'; import { useAuthStore } from '../store/authStore'; import { useLibraryIndexStore } from '../store/libraryIndexStore'; -import { usePlayerStore } from '../store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { fetchGenreAlbumCount, fetchGenreTracksForPlayback, @@ -30,7 +30,7 @@ import { readAlbumDetailReturnTo, } from '../utils/navigation/albumDetailNavigation'; import { usePerfProbeFlags } from '../utils/perf/perfFlags'; -import { runBulkEnqueue, runBulkPlayAll, runBulkShuffle } from '../utils/playback/runBulkPlay'; +import { runBulkEnqueue, runBulkPlayAll, runBulkShuffle } from '@/features/playback/utils/playback/runBulkPlay'; export default function GenreDetail() { const { name } = useParams<{ name: string }>(); diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index 3c97deb0..038cc8eb 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -17,7 +17,7 @@ import { filterAlbumsByMixRatings, getMixMinRatingsConfigFromAuth } from '../uti import { usePerfProbeFlags } from '../utils/perf/perfFlags'; import { bumpPerfCounter } from '../utils/perf/perfTelemetry'; import { dedupeById } from '@/lib/util/dedupeById'; -import { shuffleArray } from '../utils/playback/shuffleArray'; +import { shuffleArray } from '@/features/playback/utils/playback/shuffleArray'; import { useLibraryCoverPrefetch } from '../cover/useLibraryCoverPrefetch'; import { primeAlbumCoversForDisplay, warmHomeMainstageCovers } from '../cover/warmDiskPeek'; import { readBecauseYouLikeCache } from '../store/becauseYouLikeCache'; diff --git a/src/pages/MostPlayed.tsx b/src/pages/MostPlayed.tsx index a5ef2d36..2981d911 100644 --- a/src/pages/MostPlayed.tsx +++ b/src/pages/MostPlayed.tsx @@ -1,15 +1,15 @@ import { getAlbumList } from '@/lib/api/subsonicLibrary'; import { resolveAlbum } from '@/features/offline'; import type { SubsonicAlbum } from '@/lib/api/subsonicTypes'; -import { songToTrack } from '../utils/playback/songToTrack'; +import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; import React, { useEffect, useState, useCallback } from 'react'; import { useNavigate } from 'react-router-dom'; import { ArrowUpDown, ArrowDown, ArrowUp, TrendingUp, UsersRound, Play, ListPlus } from 'lucide-react'; import { useAuthStore } from '../store/authStore'; -import { usePlayerStore } from '../store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { AlbumCoverArtImage } from '../cover/AlbumCoverArtImage'; import { ArtistCoverArtImage } from '../cover/ArtistCoverArtImage'; -import { playAlbum, playAlbumShuffled } from '../utils/playback/playAlbum'; +import { playAlbum, playAlbumShuffled } from '@/features/playback/utils/playback/playAlbum'; import { useLongPressAction } from '@/lib/hooks/useLongPressAction'; import { LongPressWaveOverlay } from '../components/LongPressWaveOverlay'; import { useTranslation } from 'react-i18next'; diff --git a/src/pages/RandomMix.tsx b/src/pages/RandomMix.tsx index 76b5fbd0..5d4bc847 100644 --- a/src/pages/RandomMix.tsx +++ b/src/pages/RandomMix.tsx @@ -1,9 +1,9 @@ -import { queueSongStar } from '../store/pendingStarSync'; +import { queueSongStar } from '@/features/playback/store/pendingStarSync'; import type { SubsonicSong, SubsonicGenre } from '@/lib/api/subsonicTypes'; -import { songToTrack } from '../utils/playback/songToTrack'; +import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; import React, { useEffect, useMemo, useState } from 'react'; -import { usePlayerStore } from '../store/playerStore'; -import { usePreviewStore } from '../store/previewStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; +import { usePreviewStore } from '@/features/playback/store/previewStore'; import { useAuthStore } from '../store/authStore'; import { useLibraryIndexStore } from '../store/libraryIndexStore'; import { useTranslation } from 'react-i18next'; diff --git a/src/store/authStore.servers.test.ts b/src/store/authStore.servers.test.ts index 235bee49..f184d852 100644 --- a/src/store/authStore.servers.test.ts +++ b/src/store/authStore.servers.test.ts @@ -12,8 +12,8 @@ */ import { beforeEach, describe, expect, it } from 'vitest'; import { useAuthStore } from './authStore'; -import { usePlayerStore } from './playerStore'; -import './playbackEngineBridgeRegister'; // wire removeServer's queue-clear through the real engine bridge +import { usePlayerStore } from '@/features/playback/store/playerStore'; +import '@/features/playback/store/playbackEngineBridgeRegister'; // wire removeServer's queue-clear through the real engine bridge import { resetAuthStore } from '@/test/helpers/storeReset'; import { resetPlayerStore } from '@/test/helpers/storeReset'; diff --git a/src/store/localPlaybackMigration.ts b/src/store/localPlaybackMigration.ts index 702ef669..b28d8159 100644 --- a/src/store/localPlaybackMigration.ts +++ b/src/store/localPlaybackMigration.ts @@ -1,5 +1,5 @@ import type { ServerProfile } from './authStoreTypes'; -import type { HotCacheEntry } from './hotCacheStoreTypes'; +import type { HotCacheEntry } from '@/features/playback/store/hotCacheStoreTypes'; import { localPlaybackEntryKey } from './localPlaybackKeys'; import type { LocalPlaybackEntry, PinSource } from './localPlaybackStore'; import type { OfflineAlbumMeta, OfflineTrackMeta } from '@/features/offline'; diff --git a/src/store/localPlaybackStore.ts b/src/store/localPlaybackStore.ts index 8a0fcca4..fa56251f 100644 --- a/src/store/localPlaybackStore.ts +++ b/src/store/localPlaybackStore.ts @@ -1,4 +1,4 @@ -import type { QueueItemRef } from './playerStoreTypes'; +import type { QueueItemRef } from '@/features/playback/store/playerStoreTypes'; import { create } from 'zustand'; import { persist, createJSONStorage } from 'zustand/middleware'; import { invoke } from '@tauri-apps/api/core'; diff --git a/src/test/helpers/factories.ts b/src/test/helpers/factories.ts index 05f04975..db25610a 100644 --- a/src/test/helpers/factories.ts +++ b/src/test/helpers/factories.ts @@ -7,8 +7,8 @@ */ import type { SubsonicSong } from '@/lib/api/subsonicTypes'; import type { ServerProfile } from '@/store/authStoreTypes'; -import type { Track } from '@/store/playerStoreTypes'; -import { usePlayerStore } from '@/store/playerStore'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { toQueueItemRefs } from '@/utils/library/queueItemRef'; import { seedQueueResolver } from '@/utils/library/queueTrackResolver'; let trackCounter = 0; diff --git a/src/test/helpers/storeReset.ts b/src/test/helpers/storeReset.ts index c3dfc408..d77123f5 100644 --- a/src/test/helpers/storeReset.ts +++ b/src/test/helpers/storeReset.ts @@ -20,11 +20,11 @@ * // or for cross-store tests: * beforeEach(resetAllStores); */ -import { _resetQueueUndoStacksForTest } from '@/store/queueUndo'; -import { _resetTimelineSessionHistoryForTest } from '@/store/timelineSessionHistory'; -import { usePlayerStore } from '@/store/playerStore'; +import { _resetQueueUndoStacksForTest } from '@/features/playback/store/queueUndo'; +import { _resetTimelineSessionHistoryForTest } from '@/features/playback/store/timelineSessionHistory'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { useAuthStore } from '@/store/authStore'; -import { usePreviewStore } from '@/store/previewStore'; +import { usePreviewStore } from '@/features/playback/store/previewStore'; import { useOrbitStore } from '@/features/orbit'; const INITIAL_PLAYER_STATE = usePlayerStore.getState(); diff --git a/src/utils/componentHelpers/contextMenuActions.ts b/src/utils/componentHelpers/contextMenuActions.ts index 2e23b132..9d6b8e5a 100644 --- a/src/utils/componentHelpers/contextMenuActions.ts +++ b/src/utils/componentHelpers/contextMenuActions.ts @@ -4,15 +4,15 @@ import { getSimilarSongs2, fetchSimilarTracksRouted, getTopSongs } from '@/lib/a import { filterSongsForLuckyMixRatings, getMixMinRatingsConfigFromAuth } from '../mix/mixRatingFilter'; import { buildDownloadUrl } from '@/lib/api/subsonicStreamUrl'; import { useAuthStore } from '../../store/authStore'; -import { usePlayerStore } from '../../store/playerStore'; -import type { Track } from '../../store/playerStoreTypes'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; import { resolveQueueTrack } from '../library/queueTrackView'; import { useZipDownloadStore } from '@/features/offline'; import { useDownloadModalStore } from '@/features/offline'; import type { EntityShareKind } from '../share/shareLink'; import { copyEntityShareLink } from '../share/copyEntityShareLink'; import { sanitizeFilename, shuffleArray } from './contextMenuHelpers'; -import { songToTrack } from '../playback/songToTrack'; +import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; import { showToast } from '../ui/toast'; export async function copyShareLink( diff --git a/src/utils/componentHelpers/folderBrowserHelpers.ts b/src/utils/componentHelpers/folderBrowserHelpers.ts index b6bb5d9e..fd173e66 100644 --- a/src/utils/componentHelpers/folderBrowserHelpers.ts +++ b/src/utils/componentHelpers/folderBrowserHelpers.ts @@ -1,6 +1,6 @@ import type React from 'react'; import type { SubsonicAlbum, SubsonicDirectoryEntry } from '@/lib/api/subsonicTypes'; -import type { Track } from '../../store/playerStoreTypes'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; export type ColumnKind = 'roots' | 'indexes' | 'directory'; export type NavPos = { colIndex: number; rowIndex: number }; diff --git a/src/utils/componentHelpers/queuePanelHelpers.tsx b/src/utils/componentHelpers/queuePanelHelpers.tsx index f2d8f4e6..423e533f 100644 --- a/src/utils/componentHelpers/queuePanelHelpers.tsx +++ b/src/utils/componentHelpers/queuePanelHelpers.tsx @@ -1,6 +1,6 @@ import { Star } from 'lucide-react'; import type { TFunction } from 'i18next'; -import type { Track } from '../../store/playerStoreTypes'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; export type { DurationMode } from '../../store/authStoreTypes'; diff --git a/src/utils/format/playbackScheduleFormat.ts b/src/utils/format/playbackScheduleFormat.ts index dc850a8d..0b0d64b5 100644 --- a/src/utils/format/playbackScheduleFormat.ts +++ b/src/utils/format/playbackScheduleFormat.ts @@ -1,6 +1,6 @@ import { useEffect, useState } from 'react'; import { useShallow } from 'zustand/react/shallow'; -import { usePlayerStore } from '../../store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { useWindowVisibility } from '@/lib/hooks/useWindowVisibility'; /** Remaining time until wall-clock `deadlineMs` (m:ss or h:mm:ss). */ diff --git a/src/utils/library/genreBrowsePlayback.ts b/src/utils/library/genreBrowsePlayback.ts index 6d006965..c41e14e1 100644 --- a/src/utils/library/genreBrowsePlayback.ts +++ b/src/utils/library/genreBrowsePlayback.ts @@ -5,9 +5,9 @@ import { libraryAdvancedSearch, libraryGetGenreAlbumCounts, type LibrarySortClau import { fetchAllSongsByGenre, getGenres } from '@/lib/api/subsonicGenres'; import type { SubsonicGenre } from '@/lib/api/subsonicTypes'; import { libraryScopeForServer } from '@/lib/api/subsonicClient'; -import type { Track } from '../../store/playerStoreTypes'; -import { songToTrack } from '../playback/songToTrack'; -import { shuffleArray } from '../playback/shuffleArray'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; +import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; +import { shuffleArray } from '@/features/playback/utils/playback/shuffleArray'; import { trackToSong } from './advancedSearchLocal'; import { type AlbumBrowseSort } from './albumBrowseSort'; import { diff --git a/src/utils/library/queueItemRef.test.ts b/src/utils/library/queueItemRef.test.ts index b8c7542d..09d14fe8 100644 --- a/src/utils/library/queueItemRef.test.ts +++ b/src/utils/library/queueItemRef.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; import { toQueueItemRefs } from './queueItemRef'; -import type { Track } from '../../store/playerStoreTypes'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; describe('toQueueItemRefs', () => { it('uses per-track serverId when present', () => { diff --git a/src/utils/library/queueItemRef.ts b/src/utils/library/queueItemRef.ts index aeea6163..99fb5e83 100644 --- a/src/utils/library/queueItemRef.ts +++ b/src/utils/library/queueItemRef.ts @@ -1,5 +1,5 @@ -import type { QueueItemRef, Track } from '../../store/playerStoreTypes'; -import { stampTrackServerId } from '../playback/trackServerScope'; +import type { QueueItemRef, Track } from '@/features/playback/store/playerStoreTypes'; +import { stampTrackServerId } from '@/features/playback/utils/playback/trackServerScope'; import { canonicalQueueServerKey } from '../server/serverIndexKey'; /** diff --git a/src/utils/library/queueRestore.test.ts b/src/utils/library/queueRestore.test.ts index 4e0a90c3..9e37a24c 100644 --- a/src/utils/library/queueRestore.test.ts +++ b/src/utils/library/queueRestore.test.ts @@ -1,9 +1,9 @@ import { describe, it, expect, beforeEach } from 'vitest'; import { onInvoke } from '@/test/mocks/tauri'; import { useLibraryIndexStore } from '@/store/libraryIndexStore'; -import { usePlayerStore } from '@/store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import type { TrackRefDto } from '@/lib/api/library'; -import type { Track } from '@/store/playerStoreTypes'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; import { getCachedTrack, _resetQueueResolverForTest, diff --git a/src/utils/library/queueRestore.ts b/src/utils/library/queueRestore.ts index 162b38a8..1086e834 100644 --- a/src/utils/library/queueRestore.ts +++ b/src/utils/library/queueRestore.ts @@ -1,6 +1,6 @@ import { useAuthStore } from '../../store/authStore'; -import { usePlayerStore } from '../../store/playerStore'; -import type { QueueItemRef } from '../../store/playerStoreTypes'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; +import type { QueueItemRef } from '@/features/playback/store/playerStoreTypes'; import { canonicalQueueServerKey } from '../server/serverIndexKey'; import { resolveBatch } from './queueTrackResolver'; diff --git a/src/utils/library/queueTrackResolver.test.ts b/src/utils/library/queueTrackResolver.test.ts index 216fad33..3695add1 100644 --- a/src/utils/library/queueTrackResolver.test.ts +++ b/src/utils/library/queueTrackResolver.test.ts @@ -1,9 +1,9 @@ import { describe, it, expect, beforeEach, vi } from 'vitest'; import { onInvoke } from '@/test/mocks/tauri'; import { useLibraryIndexStore } from '@/store/libraryIndexStore'; -import { usePlayerStore } from '@/store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import type { TrackRefDto } from '@/lib/api/library'; -import type { QueueItemRef } from '@/store/playerStoreTypes'; +import type { QueueItemRef } from '@/features/playback/store/playerStoreTypes'; import * as subsonic from '@/lib/api/subsonicLibrary'; import { resolveBatch, diff --git a/src/utils/library/queueTrackResolver.ts b/src/utils/library/queueTrackResolver.ts index 1c0e8a3c..60024f36 100644 --- a/src/utils/library/queueTrackResolver.ts +++ b/src/utils/library/queueTrackResolver.ts @@ -1,8 +1,8 @@ import { libraryGetTracksBatch, type TrackRefDto } from '@/lib/api/library'; import { getSongForServer } from '@/lib/api/subsonicLibrary'; -import { usePlayerStore } from '../../store/playerStore'; -import type { QueueItemRef, Track } from '../../store/playerStoreTypes'; -import { songToTrack } from '../playback/songToTrack'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; +import type { QueueItemRef, Track } from '@/features/playback/store/playerStoreTypes'; +import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; import { resolveServerIdForIndexKey } from '../server/serverLookup'; import { canonicalQueueServerKey } from '../server/serverIndexKey'; import { trackToSong } from './advancedSearchLocal'; diff --git a/src/utils/library/queueTrackView.test.ts b/src/utils/library/queueTrackView.test.ts index 45f39611..f884e897 100644 --- a/src/utils/library/queueTrackView.test.ts +++ b/src/utils/library/queueTrackView.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect, beforeEach } from 'vitest'; -import { usePlayerStore } from '@/store/playerStore'; -import type { QueueItemRef, Track } from '@/store/playerStoreTypes'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; +import type { QueueItemRef, Track } from '@/features/playback/store/playerStoreTypes'; import { seedQueueResolver, _resetQueueResolverForTest } from '@/utils/library/queueTrackResolver'; import { resolveQueueTrack, getQueueTracksView } from './queueTrackView'; diff --git a/src/utils/library/queueTrackView.ts b/src/utils/library/queueTrackView.ts index 4f11900a..edb17746 100644 --- a/src/utils/library/queueTrackView.ts +++ b/src/utils/library/queueTrackView.ts @@ -1,4 +1,4 @@ -import type { QueueItemRef, Track } from '../../store/playerStoreTypes'; +import type { QueueItemRef, Track } from '@/features/playback/store/playerStoreTypes'; import { getCachedTrack, placeholderTrack, applyQueueOverrides } from './queueTrackResolver'; /** diff --git a/src/utils/mix/luckyMix.ts b/src/utils/mix/luckyMix.ts index 49341690..b1b984df 100644 --- a/src/utils/mix/luckyMix.ts +++ b/src/utils/mix/luckyMix.ts @@ -1,13 +1,13 @@ import { fetchSimilarTracksRouted } from '@/lib/api/subsonicArtists'; import { filterSongsToActiveLibrary, getRandomSongs } from '@/lib/api/subsonicLibrary'; import type { SubsonicAlbum, SubsonicSong } from '@/lib/api/subsonicTypes'; -import type { QueueItemRef } from '../../store/playerStoreTypes'; -import { songToTrack } from '../playback/songToTrack'; +import type { QueueItemRef } from '@/features/playback/store/playerStoreTypes'; +import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; import { invoke } from '@tauri-apps/api/core'; import i18n from '@/lib/i18n'; import { useAuthStore } from '../../store/authStore'; -import { pushQueueUndoFromGetter } from '../../store/queueUndo'; -import { usePlayerStore } from '../../store/playerStore'; +import { pushQueueUndoFromGetter } from '@/features/playback/store/queueUndo'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { useLuckyMixStore } from '../../store/luckyMixStore'; import { isLuckyMixAvailable } from '../../hooks/useLuckyMixAvailable'; import { showToast } from '../ui/toast'; @@ -16,7 +16,7 @@ import { playbackServerDiffersFromActive, prepareActiveServerForNewMix, shouldHandoffQueueToActiveServer, -} from '../playback/playbackServer'; +} from '@/features/playback/utils/playback/playbackServer'; import { filterSongsForLuckyMixRatings, filterTopArtistsForMixRatings, diff --git a/src/utils/mix/mixRatingFilter.test.ts b/src/utils/mix/mixRatingFilter.test.ts index ce0276f6..2bbc7eaf 100644 --- a/src/utils/mix/mixRatingFilter.test.ts +++ b/src/utils/mix/mixRatingFilter.test.ts @@ -1,6 +1,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import type { SubsonicSong } from '@/lib/api/subsonicTypes'; -import { usePlayerStore } from '../../store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { resetPlayerStore } from '../../test/helpers/storeReset'; vi.mock('@/lib/api/subsonicRatings', () => ({ diff --git a/src/utils/mix/mixRatingFilter.ts b/src/utils/mix/mixRatingFilter.ts index 822faf90..0b320ba1 100644 --- a/src/utils/mix/mixRatingFilter.ts +++ b/src/utils/mix/mixRatingFilter.ts @@ -2,7 +2,7 @@ import { parseSubsonicEntityStarRating, prefetchAlbumUserRatings, prefetchArtist import { getRandomSongs } from '@/lib/api/subsonicLibrary'; import type { SubsonicAlbum, SubsonicSong } from '@/lib/api/subsonicTypes'; import { useAuthStore } from '../../store/authStore'; -import { usePlayerStore } from '../../store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; /** Default target list size for Random Mix; per-call override via `fetchRandomMixSongsUntilFull(c, { targetSize })`. */ export const RANDOM_MIX_TARGET_SIZE = 50; diff --git a/src/utils/network/subsonicNetworkGuard.test.ts b/src/utils/network/subsonicNetworkGuard.test.ts index c759c671..d07b15ec 100644 --- a/src/utils/network/subsonicNetworkGuard.test.ts +++ b/src/utils/network/subsonicNetworkGuard.test.ts @@ -8,7 +8,7 @@ const resolvePlaybackUrlMock = vi.fn((_trackId: string, _serverId?: string) => 'https://music.test/stream', ); -vi.mock('../playback/resolvePlaybackUrl', () => ({ +vi.mock('@/features/playback/utils/playback/resolvePlaybackUrl', () => ({ resolvePlaybackUrl: (trackId: string, serverId?: string) => resolvePlaybackUrlMock(trackId, serverId), })); diff --git a/src/utils/network/subsonicNetworkGuard.ts b/src/utils/network/subsonicNetworkGuard.ts index 6f51b900..5309178f 100644 --- a/src/utils/network/subsonicNetworkGuard.ts +++ b/src/utils/network/subsonicNetworkGuard.ts @@ -1,5 +1,5 @@ import { useAuthStore } from '../../store/authStore'; -import { resolvePlaybackUrl } from '../playback/resolvePlaybackUrl'; +import { resolvePlaybackUrl } from '@/features/playback/utils/playback/resolvePlaybackUrl'; import { isDevOfflineBrowseForced } from '@/features/offline'; import { resolveServerIdForIndexKey } from '../server/serverLookup'; import { isActiveServerReachable } from './activeServerReachability'; diff --git a/src/utils/queue/buildTimelineDisplayRows.test.ts b/src/utils/queue/buildTimelineDisplayRows.test.ts index 9066812c..d641fb32 100644 --- a/src/utils/queue/buildTimelineDisplayRows.test.ts +++ b/src/utils/queue/buildTimelineDisplayRows.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect } from 'vitest'; import { buildTimelineDisplayRows, findTimelineScrollLocalIndex } from './buildTimelineDisplayRows'; -import type { QueueItemRef } from '../../store/playerStoreTypes'; +import type { QueueItemRef } from '@/features/playback/store/playerStoreTypes'; const ref = (trackId: string, extra?: Partial): QueueItemRef => ({ serverId: 's1', diff --git a/src/utils/queue/buildTimelineDisplayRows.ts b/src/utils/queue/buildTimelineDisplayRows.ts index e7a97c65..a4f436c5 100644 --- a/src/utils/queue/buildTimelineDisplayRows.ts +++ b/src/utils/queue/buildTimelineDisplayRows.ts @@ -1,5 +1,5 @@ -import type { QueueItemRef } from '../../store/playerStoreTypes'; -import type { TimelinePlayedRef } from '../../store/timelineSessionHistory'; +import type { QueueItemRef } from '@/features/playback/store/playerStoreTypes'; +import type { TimelinePlayedRef } from '@/features/playback/store/timelineSessionHistory'; export type TimelineDisplayRow = | { kind: 'history'; ref: TimelinePlayedRef; localIndex: number; key: string } diff --git a/src/utils/queue/playTimelineHistoryTrack.test.ts b/src/utils/queue/playTimelineHistoryTrack.test.ts index 740e0457..687ff7fd 100644 --- a/src/utils/queue/playTimelineHistoryTrack.test.ts +++ b/src/utils/queue/playTimelineHistoryTrack.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeEach, vi } from 'vitest'; -import { usePlayerStore } from '../../store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { resetPlayerStore } from '@/test/helpers/storeReset'; import { makeTrack, seedQueue } from '@/test/helpers/factories'; import { seedQueueResolver } from '@/utils/library/queueTrackResolver'; diff --git a/src/utils/queue/playTimelineHistoryTrack.ts b/src/utils/queue/playTimelineHistoryTrack.ts index 409b9eb0..20ba60bd 100644 --- a/src/utils/queue/playTimelineHistoryTrack.ts +++ b/src/utils/queue/playTimelineHistoryTrack.ts @@ -1,8 +1,8 @@ -import { usePlayerStore } from '../../store/playerStore'; -import type { QueueItemRef } from '../../store/playerStoreTypes'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; +import type { QueueItemRef } from '@/features/playback/store/playerStoreTypes'; import { getQueueTracksView, resolveQueueTrack } from '../library/queueTrackView'; import { resolveBatch } from '../library/queueTrackResolver'; -import { findQueueItemRefIndex, sameQueueItemRef } from '../playback/queueIdentity'; +import { findQueueItemRefIndex, sameQueueItemRef } from '@/features/playback/utils/playback/queueIdentity'; /** * Play a timeline history row without replacing the queue. Upcoming slots jump diff --git a/src/utils/queue/timelineHistoryRefs.ts b/src/utils/queue/timelineHistoryRefs.ts index 27922a36..3a7e6c99 100644 --- a/src/utils/queue/timelineHistoryRefs.ts +++ b/src/utils/queue/timelineHistoryRefs.ts @@ -1,7 +1,7 @@ -import type { QueueItemRef } from '../../store/playerStoreTypes'; +import type { QueueItemRef } from '@/features/playback/store/playerStoreTypes'; import type { PlaySessionRecentTrack } from '@/lib/api/library'; -import type { TimelinePlayedRef } from '../../store/timelineSessionHistory'; -import type { Track } from '../../store/playerStoreTypes'; +import type { TimelinePlayedRef } from '@/features/playback/store/timelineSessionHistory'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; export function timelineHistoryToQueueRefs( history: TimelinePlayedRef[], diff --git a/src/utils/server/rewriteFrontendStoreKeys.test.ts b/src/utils/server/rewriteFrontendStoreKeys.test.ts index 1c6fde1a..a6ec9c4d 100644 --- a/src/utils/server/rewriteFrontendStoreKeys.test.ts +++ b/src/utils/server/rewriteFrontendStoreKeys.test.ts @@ -3,7 +3,7 @@ import { useAnalysisStrategyStore } from '../../store/analysisStrategyStore'; import { useCoverStrategyStore } from '../../store/coverStrategyStore'; import { useLocalPlaybackStore } from '../../store/localPlaybackStore'; import { useOfflineStore } from '@/features/offline'; -import { usePlayerStore } from '../../store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { rewriteFrontendStoreKeysForRemap } from './rewriteFrontendStoreKeys'; describe('rewriteFrontendStoreKeysForRemap', () => { diff --git a/src/utils/server/rewriteFrontendStoreKeys.ts b/src/utils/server/rewriteFrontendStoreKeys.ts index 0b289e56..921ae26e 100644 --- a/src/utils/server/rewriteFrontendStoreKeys.ts +++ b/src/utils/server/rewriteFrontendStoreKeys.ts @@ -4,7 +4,7 @@ import { useCoverStrategyStore } from '../../store/coverStrategyStore'; import { useLocalPlaybackStore } from '../../store/localPlaybackStore'; import { useLibraryIndexStore } from '../../store/libraryIndexStore'; import { useOfflineStore } from '@/features/offline'; -import { usePlayerStore } from '../../store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { serverIndexKeyFromUrl } from './serverIndexKey'; /** diff --git a/src/utils/server/switchActiveServer.ts b/src/utils/server/switchActiveServer.ts index 530bd47c..d6dd3bee 100644 --- a/src/utils/server/switchActiveServer.ts +++ b/src/utils/server/switchActiveServer.ts @@ -6,8 +6,8 @@ import { } from '../../cover/coverTraffic'; import { useAuthStore } from '../../store/authStore'; import { useOrbitStore } from '@/features/orbit'; -import { flushPlayQueueForServer } from '../../store/queueSync'; -import { markQueueHandoffPending } from '../../store/queueSyncUiState'; +import { flushPlayQueueForServer } from '@/features/playback/store/queueSync'; +import { markQueueHandoffPending } from '@/features/playback/store/queueSyncUiState'; import { endOrbitSession, leaveOrbitSession } from '@/features/orbit'; import { ensureConnectUrlResolved } from './serverEndpoint'; import { syncServerHttpContextForProfile } from './syncServerHttpContext'; diff --git a/src/utils/share/applySharePaste.ts b/src/utils/share/applySharePaste.ts index 59516c48..379de34b 100644 --- a/src/utils/share/applySharePaste.ts +++ b/src/utils/share/applySharePaste.ts @@ -1,11 +1,11 @@ import { getSong } from '@/lib/api/subsonicLibrary'; import { resolveAlbum, resolveArtist } from '@/features/offline'; import type { SubsonicSong } from '@/lib/api/subsonicTypes'; -import { songToTrack } from '../playback/songToTrack'; +import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; import type { Location, NavigateFunction } from 'react-router-dom'; import type { TFunction } from 'i18next'; import { useAuthStore } from '../../store/authStore'; -import { usePlayerStore } from '../../store/playerStore'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; import { navigateToAlbumDetail } from '../navigation/albumDetailNavigation'; import { findServerIdForShareUrl, type EntitySharePayloadV1 } from './shareLink'; import { showToast } from '../ui/toast'; diff --git a/src/utils/share/enqueueShareSearchPayload.test.ts b/src/utils/share/enqueueShareSearchPayload.test.ts index dee9be2c..2fb73670 100644 --- a/src/utils/share/enqueueShareSearchPayload.test.ts +++ b/src/utils/share/enqueueShareSearchPayload.test.ts @@ -43,13 +43,13 @@ vi.mock('../../store/authStore', () => ({ }, })); -vi.mock('../../store/playerStore', () => ({ +vi.mock('@/features/playback/store/playerStore', () => ({ usePlayerStore: { getState: () => ({ enqueue: mocks.enqueue }), }, })); -vi.mock('../playback/songToTrack', () => ({ +vi.mock('@/features/playback/utils/playback/songToTrack', () => ({ songToTrack: mocks.songToTrack, })); diff --git a/src/utils/share/enqueueShareSearchPayload.ts b/src/utils/share/enqueueShareSearchPayload.ts index 34384f66..2bb5d7f9 100644 --- a/src/utils/share/enqueueShareSearchPayload.ts +++ b/src/utils/share/enqueueShareSearchPayload.ts @@ -9,9 +9,9 @@ import { resolveAlbum, resolveArtist } from '@/features/offline'; import type { SubsonicAlbum, SubsonicArtist, SubsonicSong } from '@/lib/api/subsonicTypes'; import { useAuthStore } from '../../store/authStore'; import type { ServerProfile } from '../../store/authStoreTypes'; -import { usePlayerStore } from '../../store/playerStore'; -import { songToTrack } from '../playback/songToTrack'; -import type { Track } from '../../store/playerStoreTypes'; +import { usePlayerStore } from '@/features/playback/store/playerStore'; +import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; +import type { Track } from '@/features/playback/store/playerStoreTypes'; import { orbitBulkGuard } from '@/features/orbit'; import { findServerIdForShareUrl } from './shareLink'; import { connectBaseUrlForServer } from '../server/serverEndpoint';