diff --git a/src/components/BecauseYouLikeRail.tsx b/src/components/BecauseYouLikeRail.tsx
index 59dc5a1c..4813dcf9 100644
--- a/src/components/BecauseYouLikeRail.tsx
+++ b/src/components/BecauseYouLikeRail.tsx
@@ -1,4 +1,4 @@
-import { getArtist, getArtistInfo } from '@/features/artist';
+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';
diff --git a/src/components/QueuePanel.tsx b/src/components/QueuePanel.tsx
index f9b833af..fa71130b 100644
--- a/src/components/QueuePanel.tsx
+++ b/src/components/QueuePanel.tsx
@@ -1,5 +1,5 @@
import { Play } from 'lucide-react';
-import { updatePlaylist } from '@/features/playlist';
+import { updatePlaylist } from '@/lib/api/subsonicPlaylists';
import { resolvePlaylist, resolveMediaServerId } from '@/features/offline';
import { songToTrack } from '../utils/playback/songToTrack';
import type { Track } from '../store/playerStoreTypes';
diff --git a/src/components/contextMenu/AddToPlaylistSubmenu.tsx b/src/components/contextMenu/AddToPlaylistSubmenu.tsx
index b8a2b7a6..98e0d216 100644
--- a/src/components/contextMenu/AddToPlaylistSubmenu.tsx
+++ b/src/components/contextMenu/AddToPlaylistSubmenu.tsx
@@ -1,7 +1,7 @@
import React, { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { ListMusic, Plus } from 'lucide-react';
-import { getPlaylist, updatePlaylist } from '@/features/playlist';
+import { getPlaylist, updatePlaylist } from '@/lib/api/subsonicPlaylists';
import type { SubsonicPlaylist } from '@/lib/api/subsonicTypes';
import { usePlaylistStore } from '@/features/playlist';
import { showToast } from '../../utils/ui/toast';
diff --git a/src/components/contextMenu/MultiAlbumToPlaylistSubmenu.tsx b/src/components/contextMenu/MultiAlbumToPlaylistSubmenu.tsx
index a1acb3ec..9c2272f0 100644
--- a/src/components/contextMenu/MultiAlbumToPlaylistSubmenu.tsx
+++ b/src/components/contextMenu/MultiAlbumToPlaylistSubmenu.tsx
@@ -39,7 +39,7 @@ export function MultiAlbumToPlaylistSubmenu({ albumIds, onDone, triggerId: _trig
}, [albumIds]);
const handleAddWithToast = async (pl: SubsonicPlaylist, songIds: string[]) => {
- const { updatePlaylist } = await import('@/features/playlist');
+ const { updatePlaylist } = await import('@/lib/api/subsonicPlaylists');
const touchPlaylist = usePlaylistStore.getState().touchPlaylist;
try {
@@ -125,7 +125,7 @@ export function MultiAlbumToPlaylistSubmenu({ albumIds, onDone, triggerId: _trig
const handleCreate = async () => {
const name = newName.trim() || t('playlists.unnamed');
try {
- const { createPlaylist } = await import('@/features/playlist');
+ const { createPlaylist } = await import('@/lib/api/subsonicPlaylists');
const pl = await createPlaylist(name, songIds);
if (pl?.id) {
usePlaylistStore.getState().touchPlaylist(pl.id);
diff --git a/src/components/contextMenu/MultiArtistToPlaylistSubmenu.tsx b/src/components/contextMenu/MultiArtistToPlaylistSubmenu.tsx
index 90d1820e..096c8f32 100644
--- a/src/components/contextMenu/MultiArtistToPlaylistSubmenu.tsx
+++ b/src/components/contextMenu/MultiArtistToPlaylistSubmenu.tsx
@@ -2,7 +2,7 @@ import React, { useEffect, useLayoutEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { ListMusic, Plus } from 'lucide-react';
import { resolveAlbum, resolveArtist, resolveMediaServerId, resolvePlaylist } from '@/features/offline';
-import { getPlaylists } from '@/features/playlist';
+import { getPlaylists } from '@/lib/api/subsonicPlaylists';
import type { SubsonicPlaylist } from '@/lib/api/subsonicTypes';
import { usePlaylistStore } from '@/features/playlist';
import { showToast } from '../../utils/ui/toast';
@@ -53,7 +53,7 @@ export function MultiArtistToPlaylistSubmenu({ artistIds, onDone, triggerId: _tr
}, [artistIds]);
const handleAddWithToast = async (pl: SubsonicPlaylist, songIds: string[]) => {
- const { updatePlaylist } = await import('@/features/playlist');
+ const { updatePlaylist } = await import('@/lib/api/subsonicPlaylists');
const touchPlaylist = usePlaylistStore.getState().touchPlaylist;
try {
@@ -139,7 +139,7 @@ export function MultiArtistToPlaylistSubmenu({ artistIds, onDone, triggerId: _tr
const handleCreate = async () => {
const name = newName.trim() || t('playlists.unnamed');
try {
- const { createPlaylist } = await import('@/features/playlist');
+ const { createPlaylist } = await import('@/lib/api/subsonicPlaylists');
const pl = await createPlaylist(name, songIds);
if (pl?.id) {
usePlaylistStore.getState().touchPlaylist(pl.id);
diff --git a/src/components/contextMenu/PlaylistContextItems.tsx b/src/components/contextMenu/PlaylistContextItems.tsx
index aa259015..efb345f0 100644
--- a/src/components/contextMenu/PlaylistContextItems.tsx
+++ b/src/components/contextMenu/PlaylistContextItems.tsx
@@ -65,7 +65,7 @@ export default function PlaylistContextItems(props: ContextMenuItemsProps) {
handleAction(async () => {
const { showToast } = await import('../../utils/ui/toast');
- const { deletePlaylist } = await import('@/features/playlist');
+ const { deletePlaylist } = await import('@/lib/api/subsonicPlaylists');
const { removeId } = usePlaylistStore.getState();
try {
await deletePlaylist(playlist.id);
@@ -113,7 +113,7 @@ export default function PlaylistContextItems(props: ContextMenuItemsProps) {
{offlinePolicy.canEditPlaylist && (
handleAction(async () => {
const { showToast } = await import('../../utils/ui/toast');
- const { deletePlaylist } = await import('@/features/playlist');
+ const { deletePlaylist } = await import('@/lib/api/subsonicPlaylists');
const { removeId } = usePlaylistStore.getState();
const deletedIds: string[] = [];
for (const pl of selectedPlaylists) {
diff --git a/src/components/contextMenu/PlaylistToPlaylistSubmenus.tsx b/src/components/contextMenu/PlaylistToPlaylistSubmenus.tsx
index 4dadfb3a..f090aa3e 100644
--- a/src/components/contextMenu/PlaylistToPlaylistSubmenus.tsx
+++ b/src/components/contextMenu/PlaylistToPlaylistSubmenus.tsx
@@ -41,7 +41,7 @@ export function SinglePlaylistToPlaylistSubmenu({ playlist, onDone, triggerId }:
const handleCreate = async () => {
if (!newName.trim()) return;
- const { createPlaylist } = await import('@/features/playlist');
+ const { createPlaylist } = await import('@/lib/api/subsonicPlaylists');
try {
const newPl = await createPlaylist(newName.trim(), []);
if (newPl?.id) {
@@ -55,7 +55,7 @@ export function SinglePlaylistToPlaylistSubmenu({ playlist, onDone, triggerId }:
};
const handleAddToNewPlaylist = async (targetId: string, targetName: string) => {
- const { getPlaylist, updatePlaylist } = await import('@/features/playlist');
+ const { getPlaylist, updatePlaylist } = await import('@/lib/api/subsonicPlaylists');
const touchPlaylist = usePlaylistStore.getState().touchPlaylist;
try {
@@ -73,7 +73,7 @@ export function SinglePlaylistToPlaylistSubmenu({ playlist, onDone, triggerId }:
};
const handleAdd = async (targetId: string, targetName: string) => {
- const { getPlaylist, updatePlaylist } = await import('@/features/playlist');
+ const { getPlaylist, updatePlaylist } = await import('@/lib/api/subsonicPlaylists');
const touchPlaylist = usePlaylistStore.getState().touchPlaylist;
try {
@@ -180,7 +180,7 @@ export function MultiPlaylistToPlaylistSubmenu({ playlists, onDone, triggerId }:
const handleCreate = async () => {
if (!newName.trim()) return;
- const { createPlaylist } = await import('@/features/playlist');
+ const { createPlaylist } = await import('@/lib/api/subsonicPlaylists');
try {
const newPl = await createPlaylist(newName.trim(), []);
if (newPl?.id) {
@@ -194,7 +194,7 @@ export function MultiPlaylistToPlaylistSubmenu({ playlists, onDone, triggerId }:
};
const handleMergeToNewPlaylist = async (targetId: string, targetName: string) => {
- const { getPlaylist, updatePlaylist } = await import('@/features/playlist');
+ const { getPlaylist, updatePlaylist } = await import('@/lib/api/subsonicPlaylists');
const touchPlaylist = usePlaylistStore.getState().touchPlaylist;
try {
@@ -223,7 +223,7 @@ export function MultiPlaylistToPlaylistSubmenu({ playlists, onDone, triggerId }:
};
const handleMerge = async (targetId: string, targetName: string) => {
- const { getPlaylist, updatePlaylist } = await import('@/features/playlist');
+ const { getPlaylist, updatePlaylist } = await import('@/lib/api/subsonicPlaylists');
const touchPlaylist = usePlaylistStore.getState().touchPlaylist;
try {
diff --git a/src/components/contextMenu/SongContextItems.tsx b/src/components/contextMenu/SongContextItems.tsx
index c6f40c58..9632bb01 100644
--- a/src/components/contextMenu/SongContextItems.tsx
+++ b/src/components/contextMenu/SongContextItems.tsx
@@ -178,7 +178,7 @@ export default function SongContextItems(props: ContextMenuItemsProps) {
{offlinePolicy.canEditPlaylist && playlistId && playlistSongIndex !== undefined && (
handleAction(async () => {
- const { updatePlaylist } = await import('@/features/playlist');
+ const { updatePlaylist } = await import('@/lib/api/subsonicPlaylists');
const { showToast } = await import('../../utils/ui/toast');
const touchPlaylist = usePlaylistStore.getState().touchPlaylist;
try {
diff --git a/src/components/queuePanel/LoadPlaylistModal.tsx b/src/components/queuePanel/LoadPlaylistModal.tsx
index 9929ffb1..0c7e94c5 100644
--- a/src/components/queuePanel/LoadPlaylistModal.tsx
+++ b/src/components/queuePanel/LoadPlaylistModal.tsx
@@ -1,7 +1,7 @@
import { useEffect, useState } from 'react';
import { Play, X, Trash2, ListPlus } from 'lucide-react';
import { useTranslation } from 'react-i18next';
-import { getPlaylists, deletePlaylist } from '@/features/playlist';
+import { getPlaylists, deletePlaylist } from '@/lib/api/subsonicPlaylists';
import type { SubsonicPlaylist } from '@/lib/api/subsonicTypes';
interface Props {
diff --git a/src/features/album/index.ts b/src/features/album/index.ts
index c063e08e..86f6153c 100644
--- a/src/features/album/index.ts
+++ b/src/features/album/index.ts
@@ -11,7 +11,6 @@
* sync, core), the album context-menu items (context-menu subsystem), the
* shared `TracklistColumnPicker` (also used by favorites), and `cover/*`.
*/
-export * from './api/subsonicAlbumInfo';
export * from './hooks/useAlbumBrowseData';
export * from './hooks/useAlbumBrowseFilters';
export * from './hooks/useAlbumBrowseScrollReset';
diff --git a/src/features/album/pages/AlbumDetail.tsx b/src/features/album/pages/AlbumDetail.tsx
index efed065f..884f6e1b 100644
--- a/src/features/album/pages/AlbumDetail.tsx
+++ b/src/features/album/pages/AlbumDetail.tsx
@@ -2,7 +2,7 @@ import { buildDownloadUrl } from '@/lib/api/subsonicStreamUrl';
import { setRating, star, unstar } from '@/lib/api/subsonicStarRating';
import { queueSongStar, queueSongRating } from '@/store/pendingStarSync';
import { getAlbumForServer } from '@/lib/api/subsonicLibrary';
-import { getArtistInfo } from '@/features/artist';
+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';
diff --git a/src/features/artist/hooks/useArtistDetailData.test.ts b/src/features/artist/hooks/useArtistDetailData.test.ts
index 7b1179db..26df1ce4 100644
--- a/src/features/artist/hooks/useArtistDetailData.test.ts
+++ b/src/features/artist/hooks/useArtistDetailData.test.ts
@@ -15,10 +15,10 @@ import { MemoryRouter } from 'react-router-dom';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import type { SubsonicArtistInfo } from '@/lib/api/subsonicTypes';
-vi.mock('@/features/artist/api/subsonicArtists');
+vi.mock('@/lib/api/subsonicArtists');
vi.mock('@/lib/api/subsonicSearch');
-import { getArtist, getArtistInfo, getTopSongs } from '@/features/artist/api/subsonicArtists';
+import { getArtist, getArtistInfo, getTopSongs } from '@/lib/api/subsonicArtists';
import { search } from '@/lib/api/subsonicSearch';
import { useArtistDetailData } from '@/features/artist/hooks/useArtistDetailData';
diff --git a/src/features/artist/hooks/useArtistDetailData.ts b/src/features/artist/hooks/useArtistDetailData.ts
index dc0f054c..91b2ceb5 100644
--- a/src/features/artist/hooks/useArtistDetailData.ts
+++ b/src/features/artist/hooks/useArtistDetailData.ts
@@ -1,7 +1,7 @@
import { useEffect, useState } from 'react';
import { useSearchParams } from 'react-router-dom';
import { search } from '@/lib/api/subsonicSearch';
-import { getArtist, getArtistForServer, getArtistInfo, getTopSongs } from '@/features/artist/api/subsonicArtists';
+import { getArtist, getArtistForServer, getArtistInfo, getTopSongs } from '@/lib/api/subsonicArtists';
import type {
SubsonicAlbum, SubsonicArtist, SubsonicArtistInfo, SubsonicSong,
} from '@/lib/api/subsonicTypes';
diff --git a/src/features/artist/hooks/useArtistInfoBatch.ts b/src/features/artist/hooks/useArtistInfoBatch.ts
index 33d1cc16..0385626e 100644
--- a/src/features/artist/hooks/useArtistInfoBatch.ts
+++ b/src/features/artist/hooks/useArtistInfoBatch.ts
@@ -1,5 +1,5 @@
import { useEffect, useMemo, useState } from 'react';
-import { getArtistInfoForServer } from '@/features/artist/api/subsonicArtists';
+import { getArtistInfoForServer } from '@/lib/api/subsonicArtists';
import type { SubsonicArtistInfo, SubsonicOpenArtistRef } from '@/lib/api/subsonicTypes';
import { makeCache } from '@/utils/cache/nowPlayingCache';
diff --git a/src/features/artist/hooks/useArtistsBrowseCatalog.ts b/src/features/artist/hooks/useArtistsBrowseCatalog.ts
index 3276b66a..267174e8 100644
--- a/src/features/artist/hooks/useArtistsBrowseCatalog.ts
+++ b/src/features/artist/hooks/useArtistsBrowseCatalog.ts
@@ -1,4 +1,4 @@
-import { getArtists } from '@/features/artist/api/subsonicArtists';
+import { getArtists } from '@/lib/api/subsonicArtists';
import type { SubsonicArtist } from '@/lib/api/subsonicTypes';
import { useCallback, useEffect, useRef, useState } from 'react';
import { dedupeById } from '@/utils/dedupeById';
diff --git a/src/features/artist/index.ts b/src/features/artist/index.ts
index 7cca687a..ec53dacf 100644
--- a/src/features/artist/index.ts
+++ b/src/features/artist/index.ts
@@ -11,7 +11,6 @@
* `*ContextItems`/`*ToPlaylistSubmenu` items (the cross-cutting context-menu
* subsystem, shared with album), and `PlaylistArtistCell` (playlist).
*/
-export * from './api/subsonicArtists';
export * from './hooks/useArtistDetailData';
export * from './hooks/useArtistInfoBatch';
export * from './hooks/useArtistOfflineState';
diff --git a/src/features/artist/utils/runArtistDetailActions.ts b/src/features/artist/utils/runArtistDetailActions.ts
index 2fba18d4..9fef7871 100644
--- a/src/features/artist/utils/runArtistDetailActions.ts
+++ b/src/features/artist/utils/runArtistDetailActions.ts
@@ -1,6 +1,6 @@
import type React from 'react';
import type { TFunction } from 'i18next';
-import { uploadArtistImage } from '@/features/playlist';
+import { uploadArtistImage } from '@/lib/api/subsonicPlaylists';
import { setRating, star, unstar } from '@/lib/api/subsonicStarRating';
import type { SubsonicArtist } from '@/lib/api/subsonicTypes';
import { useAuthStore } from '@/store/authStore';
diff --git a/src/features/artist/utils/runArtistDetailPlay.ts b/src/features/artist/utils/runArtistDetailPlay.ts
index 3f79f1d5..ed89c629 100644
--- a/src/features/artist/utils/runArtistDetailPlay.ts
+++ b/src/features/artist/utils/runArtistDetailPlay.ts
@@ -1,5 +1,5 @@
import type { TFunction } from 'i18next';
-import { getSimilarSongs2, getTopSongs } from '@/features/artist/api/subsonicArtists';
+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';
diff --git a/src/features/deviceSync/hooks/useDeviceSyncBrowser.ts b/src/features/deviceSync/hooks/useDeviceSyncBrowser.ts
index ead13a41..c407aa75 100644
--- a/src/features/deviceSync/hooks/useDeviceSyncBrowser.ts
+++ b/src/features/deviceSync/hooks/useDeviceSyncBrowser.ts
@@ -1,6 +1,6 @@
import { useCallback, useEffect, useState } from 'react';
-import { getPlaylists } from '@/features/playlist';
-import { getArtists, getArtist } from '@/features/artist';
+import { getPlaylists } from '@/lib/api/subsonicPlaylists';
+import { getArtists, getArtist } from '@/lib/api/subsonicArtists';
import { getAlbumList } from '@/lib/api/subsonicLibrary';
import { search as searchSubsonic } from '@/lib/api/subsonicSearch';
import type {
diff --git a/src/features/favorites/hooks/useFavoritesData.ts b/src/features/favorites/hooks/useFavoritesData.ts
index d04ca2bc..ca2c0e5f 100644
--- a/src/features/favorites/hooks/useFavoritesData.ts
+++ b/src/features/favorites/hooks/useFavoritesData.ts
@@ -1,5 +1,5 @@
import { useEffect, useMemo, useState } from 'react';
-import { getInternetRadioStations } from '@/features/radio';
+import { getInternetRadioStations } from '@/lib/api/subsonicRadio';
import { getStarred } from '@/lib/api/subsonicStarRating';
import type {
InternetRadioStation, SubsonicAlbum, SubsonicArtist, SubsonicSong,
diff --git a/src/features/nowPlaying/components/NowPlayingInfo.tsx b/src/features/nowPlaying/components/NowPlayingInfo.tsx
index e2458e3a..d88061a3 100644
--- a/src/features/nowPlaying/components/NowPlayingInfo.tsx
+++ b/src/features/nowPlaying/components/NowPlayingInfo.tsx
@@ -1,5 +1,5 @@
import { getSongForServer } from '@/lib/api/subsonicLibrary';
-import { getArtistInfoForServer } from '@/features/artist';
+import { getArtistInfoForServer } from '@/lib/api/subsonicArtists';
import type { SubsonicArtistInfo, SubsonicSong } from '@/lib/api/subsonicTypes';
import React, { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
diff --git a/src/features/nowPlaying/hooks/useNowPlayingFetchers.test.ts b/src/features/nowPlaying/hooks/useNowPlayingFetchers.test.ts
index 78ba3959..a37471f9 100644
--- a/src/features/nowPlaying/hooks/useNowPlayingFetchers.test.ts
+++ b/src/features/nowPlaying/hooks/useNowPlayingFetchers.test.ts
@@ -13,7 +13,7 @@ import { renderHook, act, waitFor } from '@testing-library/react';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import type { SubsonicArtistInfo, SubsonicSong, SubsonicAlbum, SubsonicArtist } from '@/lib/api/subsonicTypes';
-vi.mock('@/features/artist');
+vi.mock('@/lib/api/subsonicArtists');
vi.mock('@/lib/api/subsonicLibrary');
vi.mock('@/api/bandsintown');
vi.mock('@/utils/network/subsonicNetworkGuard', () => ({
@@ -21,7 +21,7 @@ vi.mock('@/utils/network/subsonicNetworkGuard', () => ({
}));
import { shouldAttemptSubsonicForServer } from '@/utils/network/subsonicNetworkGuard';
-import { getArtistForServer, getArtistInfoForServer, getTopSongsForServer } from '@/features/artist';
+import { getArtistForServer, getArtistInfoForServer, getTopSongsForServer } from '@/lib/api/subsonicArtists';
import { getAlbumForServer, getSongForServer } from '@/lib/api/subsonicLibrary';
import { fetchBandsintownEvents } from '@/api/bandsintown';
import { useNowPlayingFetchers, type NowPlayingFetchersDeps } from '@/features/nowPlaying/hooks/useNowPlayingFetchers';
diff --git a/src/features/nowPlaying/hooks/useNowPlayingFetchers.ts b/src/features/nowPlaying/hooks/useNowPlayingFetchers.ts
index 6e392c08..f7cd1c2d 100644
--- a/src/features/nowPlaying/hooks/useNowPlayingFetchers.ts
+++ b/src/features/nowPlaying/hooks/useNowPlayingFetchers.ts
@@ -1,5 +1,5 @@
import { useEffect, useState } from 'react';
-import { getArtistInfoForServer } from '@/features/artist';
+import { getArtistInfoForServer } from '@/lib/api/subsonicArtists';
import type { SubsonicAlbum, SubsonicArtistInfo, SubsonicSong } from '@/lib/api/subsonicTypes';
import { resolveNpAlbum, resolveNpDiscography, resolveNpSongMeta, resolveNpTopSongs } from '@/features/nowPlaying/utils/nowPlayingMetadataResolve';
import { fetchBandsintownEvents, type BandsintownEvent } from '@/api/bandsintown';
diff --git a/src/features/nowPlaying/utils/nowPlayingMetadataResolve.test.ts b/src/features/nowPlaying/utils/nowPlayingMetadataResolve.test.ts
index 7e435d73..39f3031e 100644
--- a/src/features/nowPlaying/utils/nowPlayingMetadataResolve.test.ts
+++ b/src/features/nowPlaying/utils/nowPlayingMetadataResolve.test.ts
@@ -9,7 +9,7 @@ import { describe, it, expect, beforeEach, vi } from 'vitest';
import { onInvoke } from '@/test/mocks/tauri';
import { useLibraryIndexStore } from '@/store/libraryIndexStore';
import type { LibraryAdvancedSearchResponse } from '@/lib/api/library';
-import * as subsonicArtists from '@/features/artist';
+import * as subsonicArtists from '@/lib/api/subsonicArtists';
import * as subsonicLibrary from '@/lib/api/subsonicLibrary';
// Network reachability is decided by the guard; mock it so we can test both arms.
diff --git a/src/features/nowPlaying/utils/nowPlayingMetadataResolve.ts b/src/features/nowPlaying/utils/nowPlayingMetadataResolve.ts
index 48fa3d1c..69c076a4 100644
--- a/src/features/nowPlaying/utils/nowPlayingMetadataResolve.ts
+++ b/src/features/nowPlaying/utils/nowPlayingMetadataResolve.ts
@@ -17,7 +17,7 @@
* is intentionally absent here.
*/
import { libraryGetTrack, libraryGetTracksByAlbum } from '@/lib/api/library';
-import { getArtistForServer, getTopSongsForServer } from '@/features/artist';
+import { getArtistForServer, getTopSongsForServer } from '@/lib/api/subsonicArtists';
import { getAlbumForServer, getSongForServer } from '@/lib/api/subsonicLibrary';
import type { SubsonicAlbum, SubsonicSong } from '@/lib/api/subsonicTypes';
import { shouldAttemptSubsonicForServer } from '@/utils/network/subsonicNetworkGuard';
diff --git a/src/features/offline/store/offlineStore.ts b/src/features/offline/store/offlineStore.ts
index eda3a61c..67b7d7c8 100644
--- a/src/features/offline/store/offlineStore.ts
+++ b/src/features/offline/store/offlineStore.ts
@@ -1,7 +1,7 @@
import { libraryUpsertSongsFromApi } from '@/lib/api/library';
import { buildStreamUrl } from '@/lib/api/subsonicStreamUrl';
import { getAlbum } from '@/lib/api/subsonicLibrary';
-import { getArtist } from '@/features/artist';
+import { getArtist } from '@/lib/api/subsonicArtists';
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
diff --git a/src/features/offline/utils/favoritesOfflineSync.test.ts b/src/features/offline/utils/favoritesOfflineSync.test.ts
index 55003e88..298f5dbf 100644
--- a/src/features/offline/utils/favoritesOfflineSync.test.ts
+++ b/src/features/offline/utils/favoritesOfflineSync.test.ts
@@ -28,7 +28,7 @@ vi.mock('@/lib/api/subsonicLibrary', () => ({
getAlbumForServer: vi.fn(async () => ({ songs: [] })),
}));
-vi.mock('@/features/artist', () => ({
+vi.mock('@/lib/api/subsonicArtists', () => ({
getArtistForServer: vi.fn(async () => ({ albums: [] })),
}));
diff --git a/src/features/offline/utils/favoritesOfflineSync.ts b/src/features/offline/utils/favoritesOfflineSync.ts
index 7f42c95c..ca494d91 100644
--- a/src/features/offline/utils/favoritesOfflineSync.ts
+++ b/src/features/offline/utils/favoritesOfflineSync.ts
@@ -1,7 +1,7 @@
import { libraryUpsertSongsFromApi } from '@/lib/api/library';
import { librarySqlServerId } from '@/api/coverCache';
import { getAlbumForServer } from '@/lib/api/subsonicLibrary';
-import { getArtistForServer } from '@/features/artist';
+import { getArtistForServer } from '@/lib/api/subsonicArtists';
import { getStarredForServer } from '@/lib/api/subsonicStarRating';
import { buildStreamUrlForServer } from '@/lib/api/subsonicStreamUrl';
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
diff --git a/src/features/offline/utils/offlineMediaResolve.test.ts b/src/features/offline/utils/offlineMediaResolve.test.ts
index c619257b..fa3e8487 100644
--- a/src/features/offline/utils/offlineMediaResolve.test.ts
+++ b/src/features/offline/utils/offlineMediaResolve.test.ts
@@ -53,11 +53,11 @@ vi.mock('@/lib/api/subsonicLibrary', () => ({
getAlbumForServer: (serverId: string, albumId: string) => getAlbumForServerMock(serverId, albumId),
}));
-vi.mock('@/features/artist', () => ({
+vi.mock('@/lib/api/subsonicArtists', () => ({
getArtistForServer: (serverId: string, artistId: string) => getArtistForServerMock(serverId, artistId),
}));
-vi.mock('@/features/playlist', () => ({
+vi.mock('@/lib/api/subsonicPlaylists', () => ({
getPlaylistForServer: (serverId: string, playlistId: string) =>
getPlaylistForServerMock(serverId, playlistId),
}));
diff --git a/src/features/offline/utils/offlineMediaResolve.ts b/src/features/offline/utils/offlineMediaResolve.ts
index 0e05172c..0cb6940c 100644
--- a/src/features/offline/utils/offlineMediaResolve.ts
+++ b/src/features/offline/utils/offlineMediaResolve.ts
@@ -1,6 +1,6 @@
import { getAlbumForServer } from '@/lib/api/subsonicLibrary';
-import { getArtistForServer } from '@/features/artist';
-import { getPlaylistForServer } from '@/features/playlist';
+import { getArtistForServer } from '@/lib/api/subsonicArtists';
+import { getPlaylistForServer } from '@/lib/api/subsonicPlaylists';
import type {
SubsonicAlbum,
SubsonicArtist,
diff --git a/src/features/offline/utils/pinnedOfflineSync.test.ts b/src/features/offline/utils/pinnedOfflineSync.test.ts
index db6c1303..3db73221 100644
--- a/src/features/offline/utils/pinnedOfflineSync.test.ts
+++ b/src/features/offline/utils/pinnedOfflineSync.test.ts
@@ -29,7 +29,7 @@ vi.mock('@/utils/network/activeServerReachability', () => ({
onActiveServerBecameReachable: () => () => {},
}));
-vi.mock('@/features/playlist/api/subsonicPlaylists', () => ({
+vi.mock('@/lib/api/subsonicPlaylists', () => ({
getPlaylistForServer: (serverId: string, id: string) => getPlaylistMock(serverId, id),
}));
@@ -38,7 +38,7 @@ vi.mock('@/lib/api/subsonicLibrary', () => ({
filterSongsToServerLibrary: (songs: SubsonicSong[]) => filterSongsMock(songs),
}));
-vi.mock('@/features/artist', () => ({
+vi.mock('@/lib/api/subsonicArtists', () => ({
getArtistForServer: (serverId: string, artistId: string) => getArtistForServerMock(serverId, artistId),
}));
diff --git a/src/features/offline/utils/pinnedOfflineSync.ts b/src/features/offline/utils/pinnedOfflineSync.ts
index 8cc93573..dc128f4f 100644
--- a/src/features/offline/utils/pinnedOfflineSync.ts
+++ b/src/features/offline/utils/pinnedOfflineSync.ts
@@ -1,7 +1,7 @@
import { libraryGetTracksByAlbum, subscribeLibrarySyncIdle } from '@/lib/api/library';
import { getAlbumForServer, filterSongsToServerLibrary } from '@/lib/api/subsonicLibrary';
-import { getPlaylistForServer } from '@/features/playlist';
-import { getArtistForServer } from '@/features/artist';
+import { getPlaylistForServer } from '@/lib/api/subsonicPlaylists';
+import { getArtistForServer } from '@/lib/api/subsonicArtists';
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
import { invoke } from '@tauri-apps/api/core';
import { useAuthStore } from '@/store/authStore';
diff --git a/src/features/offline/utils/resumeIncompleteOfflinePins.ts b/src/features/offline/utils/resumeIncompleteOfflinePins.ts
index c2ace389..62d1d4ad 100644
--- a/src/features/offline/utils/resumeIncompleteOfflinePins.ts
+++ b/src/features/offline/utils/resumeIncompleteOfflinePins.ts
@@ -1,5 +1,5 @@
import { libraryGetTracksBatchChunked } from '@/lib/api/library';
-import { getPlaylist } from '@/features/playlist';
+import { getPlaylist } from '@/lib/api/subsonicPlaylists';
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
import { useAuthStore } from '@/store/authStore';
import type { OfflineAlbumMeta } from '@/features/offline/store/offlineStore';
diff --git a/src/features/orbit/utils/cleanup.test.ts b/src/features/orbit/utils/cleanup.test.ts
index 328d6d6d..2e08a4a7 100644
--- a/src/features/orbit/utils/cleanup.test.ts
+++ b/src/features/orbit/utils/cleanup.test.ts
@@ -19,7 +19,7 @@ const { authState, orbitState } = vi.hoisted(() => ({
orbitState: { sessionId: null as string | null },
}));
-vi.mock('@/features/playlist', () => ({ getPlaylists, deletePlaylist }));
+vi.mock('@/lib/api/subsonicPlaylists', () => ({ getPlaylists, deletePlaylist }));
vi.mock('@/store/authStore', () => ({
useAuthStore: {
getState: () => ({
diff --git a/src/features/orbit/utils/cleanup.ts b/src/features/orbit/utils/cleanup.ts
index 12a3ef19..4add5342 100644
--- a/src/features/orbit/utils/cleanup.ts
+++ b/src/features/orbit/utils/cleanup.ts
@@ -1,4 +1,4 @@
-import { deletePlaylist, getPlaylists } from '@/features/playlist';
+import { deletePlaylist, getPlaylists } from '@/lib/api/subsonicPlaylists';
import { useAuthStore } from '@/store/authStore';
import { useOrbitStore } from '@/features/orbit/store/orbitStore';
import { ORBIT_PLAYLIST_PREFIX, parseOrbitState } from '@/features/orbit/api/orbit';
diff --git a/src/features/orbit/utils/guest.ts b/src/features/orbit/utils/guest.ts
index 8b1a4e7a..d16fa330 100644
--- a/src/features/orbit/utils/guest.ts
+++ b/src/features/orbit/utils/guest.ts
@@ -1,4 +1,4 @@
-import { createPlaylist, deletePlaylist, getPlaylist, getPlaylists, updatePlaylist } from '@/features/playlist';
+import { createPlaylist, deletePlaylist, getPlaylist, getPlaylists, updatePlaylist } from '@/lib/api/subsonicPlaylists';
import { getSong } from '@/lib/api/subsonicLibrary';
import { songToTrack } from '@/utils/playback/songToTrack';
import { useAuthStore } from '@/store/authStore';
diff --git a/src/features/orbit/utils/host.test.ts b/src/features/orbit/utils/host.test.ts
index 291048e8..fe03cbeb 100644
--- a/src/features/orbit/utils/host.test.ts
+++ b/src/features/orbit/utils/host.test.ts
@@ -24,7 +24,7 @@ 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/playlist', () => ({ createPlaylist: vi.fn(), deletePlaylist: 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() }));
diff --git a/src/features/orbit/utils/host.ts b/src/features/orbit/utils/host.ts
index d987b7b6..ad6eb799 100644
--- a/src/features/orbit/utils/host.ts
+++ b/src/features/orbit/utils/host.ts
@@ -1,4 +1,4 @@
-import { createPlaylist, deletePlaylist } from '@/features/playlist';
+import { createPlaylist, deletePlaylist } from '@/lib/api/subsonicPlaylists';
import { getSong } from '@/lib/api/subsonicLibrary';
import { songToTrack } from '@/utils/playback/songToTrack';
import { useAuthStore } from '@/store/authStore';
diff --git a/src/features/orbit/utils/moderation.ts b/src/features/orbit/utils/moderation.ts
index 36f58707..a37c588b 100644
--- a/src/features/orbit/utils/moderation.ts
+++ b/src/features/orbit/utils/moderation.ts
@@ -1,4 +1,4 @@
-import { deletePlaylist, getPlaylists } from '@/features/playlist';
+import { deletePlaylist, getPlaylists } from '@/lib/api/subsonicPlaylists';
import { useOrbitStore } from '@/features/orbit/store/orbitStore';
import { orbitOutboxPlaylistName, type OrbitState } from '@/features/orbit/api/orbit';
import { writeOrbitState } from '@/features/orbit/utils/remote';
diff --git a/src/features/orbit/utils/remote.ts b/src/features/orbit/utils/remote.ts
index 448af079..2ba6520b 100644
--- a/src/features/orbit/utils/remote.ts
+++ b/src/features/orbit/utils/remote.ts
@@ -1,4 +1,4 @@
-import { getPlaylist, getPlaylists, updatePlaylistMeta } from '@/features/playlist';
+import { getPlaylist, getPlaylists, updatePlaylistMeta } from '@/lib/api/subsonicPlaylists';
import {
orbitSessionPlaylistName,
parseOrbitState,
diff --git a/src/features/orbit/utils/sweep.ts b/src/features/orbit/utils/sweep.ts
index 369fb1ab..2921e692 100644
--- a/src/features/orbit/utils/sweep.ts
+++ b/src/features/orbit/utils/sweep.ts
@@ -1,4 +1,4 @@
-import { getPlaylist, getPlaylists, updatePlaylist } from '@/features/playlist';
+import { getPlaylist, getPlaylists, updatePlaylist } from '@/lib/api/subsonicPlaylists';
import { type OrbitOutboxMeta } from '@/features/orbit/api/orbit';
import { parseOutboxPlaylistName } from '@/features/orbit/utils/helpers';
import { type OutboxSnapshot } from '@/features/orbit/utils/stateMath';
diff --git a/src/features/playlist/hooks/usePlaylistsLibraryScopeCounts.ts b/src/features/playlist/hooks/usePlaylistsLibraryScopeCounts.ts
index 3fe51bb9..9efb20bd 100644
--- a/src/features/playlist/hooks/usePlaylistsLibraryScopeCounts.ts
+++ b/src/features/playlist/hooks/usePlaylistsLibraryScopeCounts.ts
@@ -1,6 +1,6 @@
import { useEffect, useState } from 'react';
import { filterSongsToActiveLibrary } from '@/lib/api/subsonicLibrary';
-import { getPlaylist } from '@/features/playlist/api/subsonicPlaylists';
+import { getPlaylist } from '@/lib/api/subsonicPlaylists';
import type { SubsonicPlaylist } from '@/lib/api/subsonicTypes';
import { useOfflineBrowseContext } from '@/features/offline';
diff --git a/src/features/playlist/index.ts b/src/features/playlist/index.ts
index 7bec89ea..e4924ba6 100644
--- a/src/features/playlist/index.ts
+++ b/src/features/playlist/index.ts
@@ -10,7 +10,6 @@
* `playlistDetailHelpers` (shared with offline + favorites; keeping it here
* would create a playlist⟷offline barrel cycle → lib/shared in M4).
*/
-export * from './api/subsonicPlaylists';
export * from './hooks/usePlaylistBulkPlayCallbacks';
export * from './hooks/usePlaylistCovers';
export * from './hooks/usePlaylistDerived';
diff --git a/src/features/playlist/pages/PlaylistDetail.tsx b/src/features/playlist/pages/PlaylistDetail.tsx
index 1cb840fb..f371d1f2 100644
--- a/src/features/playlist/pages/PlaylistDetail.tsx
+++ b/src/features/playlist/pages/PlaylistDetail.tsx
@@ -1,4 +1,4 @@
-import { updatePlaylist } from '@/features/playlist/api/subsonicPlaylists';
+import { updatePlaylist } from '@/lib/api/subsonicPlaylists';
import type { SubsonicPlaylist, SubsonicSong } from '@/lib/api/subsonicTypes';
import React, { useEffect, useState, useCallback, useMemo } from 'react';
import { useParams, useNavigate, useLocation } from 'react-router-dom';
diff --git a/src/features/playlist/store/playlistStore.ts b/src/features/playlist/store/playlistStore.ts
index 78f2e618..4bbc8dd0 100644
--- a/src/features/playlist/store/playlistStore.ts
+++ b/src/features/playlist/store/playlistStore.ts
@@ -1,8 +1,8 @@
-import { getPlaylists } from '@/features/playlist/api/subsonicPlaylists';
+import { getPlaylists } from '@/lib/api/subsonicPlaylists';
import type { SubsonicPlaylist } from '@/lib/api/subsonicTypes';
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
-import { createPlaylist as apiCreatePlaylist } from '@/features/playlist/api/subsonicPlaylists';
+import { createPlaylist as apiCreatePlaylist } from '@/lib/api/subsonicPlaylists';
import { useAuthStore } from '@/store/authStore';
import { isOfflineBrowseActive } from '@/features/offline';
import { fetchOfflineBrowsablePlaylists } from '@/features/offline';
diff --git a/src/features/playlist/utils/playPlaylistById.ts b/src/features/playlist/utils/playPlaylistById.ts
index 362965e6..fe136adb 100644
--- a/src/features/playlist/utils/playPlaylistById.ts
+++ b/src/features/playlist/utils/playPlaylistById.ts
@@ -1,4 +1,4 @@
-import { getPlaylist } from '@/features/playlist/api/subsonicPlaylists';
+import { getPlaylist } from '@/lib/api/subsonicPlaylists';
import { songToTrack } from '@/utils/playback/songToTrack';
import { usePlayerStore } from '@/store/playerStore';
import { playPlaylistAll } from '@/features/playlist/utils/playlistBulkPlayActions';
diff --git a/src/features/playlist/utils/runPlaylistLoad.ts b/src/features/playlist/utils/runPlaylistLoad.ts
index 37927548..abf73749 100644
--- a/src/features/playlist/utils/runPlaylistLoad.ts
+++ b/src/features/playlist/utils/runPlaylistLoad.ts
@@ -1,5 +1,5 @@
import type React from 'react';
-import { getPlaylist } from '@/features/playlist/api/subsonicPlaylists';
+import { getPlaylist } from '@/lib/api/subsonicPlaylists';
import { filterSongsToActiveLibrary } from '@/lib/api/subsonicLibrary';
import type { SubsonicPlaylist, SubsonicSong } from '@/lib/api/subsonicTypes';
import { useAuthStore } from '@/store/authStore';
diff --git a/src/features/playlist/utils/runPlaylistSaveMeta.ts b/src/features/playlist/utils/runPlaylistSaveMeta.ts
index e846a375..c1f24244 100644
--- a/src/features/playlist/utils/runPlaylistSaveMeta.ts
+++ b/src/features/playlist/utils/runPlaylistSaveMeta.ts
@@ -1,5 +1,5 @@
import type { TFunction } from 'i18next';
-import { getPlaylist, updatePlaylistMeta, uploadPlaylistCoverArt } from '@/features/playlist/api/subsonicPlaylists';
+import { getPlaylist, updatePlaylistMeta, uploadPlaylistCoverArt } from '@/lib/api/subsonicPlaylists';
import type { SubsonicPlaylist } from '@/lib/api/subsonicTypes';
import { showToast } from '@/utils/ui/toast';
diff --git a/src/features/playlist/utils/runPlaylistsActions.ts b/src/features/playlist/utils/runPlaylistsActions.ts
index af453611..e2b352f8 100644
--- a/src/features/playlist/utils/runPlaylistsActions.ts
+++ b/src/features/playlist/utils/runPlaylistsActions.ts
@@ -1,6 +1,6 @@
import type React from 'react';
import type { TFunction } from 'i18next';
-import { deletePlaylist, getPlaylist, updatePlaylist } from '@/features/playlist/api/subsonicPlaylists';
+import { deletePlaylist, getPlaylist, updatePlaylist } from '@/lib/api/subsonicPlaylists';
import type { SubsonicPlaylist } from '@/lib/api/subsonicTypes';
import { usePlaylistStore } from '@/features/playlist/store/playlistStore';
import { showToast } from '@/utils/ui/toast';
diff --git a/src/features/radio/components/RadioDirectoryModal.tsx b/src/features/radio/components/RadioDirectoryModal.tsx
index da925f0b..de37adcc 100644
--- a/src/features/radio/components/RadioDirectoryModal.tsx
+++ b/src/features/radio/components/RadioDirectoryModal.tsx
@@ -5,7 +5,7 @@ import { Cast, Check, Loader2, Plus, X } from 'lucide-react';
import {
createInternetRadioStation, fetchUrlBytes, getInternetRadioStations,
getTopRadioStations, searchRadioBrowser, uploadRadioCoverArtBytes,
-} from '@/features/radio/api/subsonicRadio';
+} from '@/lib/api/subsonicRadio';
import {
type InternetRadioStation, type RadioBrowserStation, RADIO_PAGE_SIZE,
} from '@/lib/api/subsonicTypes';
diff --git a/src/features/radio/index.ts b/src/features/radio/index.ts
index 6102acbf..7438203b 100644
--- a/src/features/radio/index.ts
+++ b/src/features/radio/index.ts
@@ -8,7 +8,6 @@
* playback/audio core — the player core drives them, so they are not part of
* this UI feature.
*/
-export { getInternetRadioStations } from './api/subsonicRadio';
export { useRadioMetadata } from './hooks/useRadioMetadata';
export type { RadioMetadata } from './hooks/useRadioMetadata';
export { useRadioMprisSync } from './hooks/useRadioMprisSync';
diff --git a/src/features/radio/pages/InternetRadio.tsx b/src/features/radio/pages/InternetRadio.tsx
index 828c7644..38debe11 100644
--- a/src/features/radio/pages/InternetRadio.tsx
+++ b/src/features/radio/pages/InternetRadio.tsx
@@ -1,4 +1,4 @@
-import { getInternetRadioStations, createInternetRadioStation, updateInternetRadioStation, deleteInternetRadioStation, uploadRadioCoverArt, deleteRadioCoverArt } from '@/features/radio/api/subsonicRadio';
+import { getInternetRadioStations, createInternetRadioStation, updateInternetRadioStation, deleteInternetRadioStation, uploadRadioCoverArt, deleteRadioCoverArt } from '@/lib/api/subsonicRadio';
import { type InternetRadioStation } from '@/lib/api/subsonicTypes';
import React, { useEffect, useState, useMemo, useCallback } from 'react';
import { Plus, Search } from 'lucide-react';
diff --git a/src/features/stats/pages/Statistics.tsx b/src/features/stats/pages/Statistics.tsx
index d65ce88d..f5931bd8 100644
--- a/src/features/stats/pages/Statistics.tsx
+++ b/src/features/stats/pages/Statistics.tsx
@@ -1,4 +1,4 @@
-import { fetchStatisticsFormatSample, fetchStatisticsLibraryAggregates, fetchStatisticsOverview } from '@/features/stats/api/subsonicStatistics';
+import { fetchStatisticsFormatSample, fetchStatisticsLibraryAggregates, fetchStatisticsOverview } from '@/lib/api/subsonicStatistics';
import { getAlbumList } from '@/lib/api/subsonicLibrary';
import type { SubsonicAlbum, SubsonicGenre } from '@/lib/api/subsonicTypes';
import React, { useEffect, useState } from 'react';
diff --git a/src/hooks/tauriBridge/useCliBridge.ts b/src/hooks/tauriBridge/useCliBridge.ts
index 32ff20e5..e26411b4 100644
--- a/src/hooks/tauriBridge/useCliBridge.ts
+++ b/src/hooks/tauriBridge/useCliBridge.ts
@@ -2,7 +2,7 @@ import { useEffect } from 'react';
import { listen } from '@tauri-apps/api/event';
import { invoke } from '@tauri-apps/api/core';
import type { NavigateFunction } from 'react-router-dom';
-import { getSimilarSongs } from '@/features/artist';
+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';
diff --git a/src/hooks/usePendingSmartPolling.ts b/src/hooks/usePendingSmartPolling.ts
index 6421310d..04bfd152 100644
--- a/src/hooks/usePendingSmartPolling.ts
+++ b/src/hooks/usePendingSmartPolling.ts
@@ -1,6 +1,6 @@
import { useEffect } from 'react';
import type React from 'react';
-import { getPlaylist } from '@/features/playlist';
+import { getPlaylist } from '@/lib/api/subsonicPlaylists';
import type { SubsonicPlaylist } from '@/lib/api/subsonicTypes';
import { usePlaylistStore } from '@/features/playlist';
import type { PendingSmartPlaylist } from '@/features/playlist';
diff --git a/src/hooks/useSmartCoverCollage.ts b/src/hooks/useSmartCoverCollage.ts
index 978d3155..4ea0c4fd 100644
--- a/src/hooks/useSmartCoverCollage.ts
+++ b/src/hooks/useSmartCoverCollage.ts
@@ -1,6 +1,6 @@
import { useEffect, useState } from 'react';
import { filterSongsToActiveLibrary } from '@/lib/api/subsonicLibrary';
-import { getPlaylist } from '@/features/playlist';
+import { getPlaylist } from '@/lib/api/subsonicPlaylists';
import type { SubsonicPlaylist } from '@/lib/api/subsonicTypes';
import { isSmartPlaylistName } from '@/features/playlist';
diff --git a/src/lib/api/subsonic.async.test.ts b/src/lib/api/subsonic.async.test.ts
index 6c168c41..a804b417 100644
--- a/src/lib/api/subsonic.async.test.ts
+++ b/src/lib/api/subsonic.async.test.ts
@@ -27,11 +27,11 @@ vi.mock('@/utils/network/subsonicNetworkGuard', () => ({
import axios from 'axios';
import { pingWithCredentials, pingWithCredentialsForProfile, ping } from '@/lib/api/subsonic';
-import { getAlbumInfo2 } from '@/features/album';
+import { getAlbumInfo2 } from '@/lib/api/subsonicAlbumInfo';
import { getStarred } from '@/lib/api/subsonicStarRating';
import { search } from '@/lib/api/subsonicSearch';
import { getAlbum, getMusicDirectory, getMusicFolders, getMusicIndexes, getRandomSongs, getSong } from '@/lib/api/subsonicLibrary';
-import { getArtists, getTopSongs } from '@/features/artist';
+import { getArtists, getTopSongs } from '@/lib/api/subsonicArtists';
import { useAuthStore } from '@/store/authStore';
import { resetAuthStore } from '@/test/helpers/storeReset';
diff --git a/src/features/album/api/subsonicAlbumInfo.ts b/src/lib/api/subsonicAlbumInfo.ts
similarity index 100%
rename from src/features/album/api/subsonicAlbumInfo.ts
rename to src/lib/api/subsonicAlbumInfo.ts
diff --git a/src/features/artist/api/subsonicArtists.router.test.ts b/src/lib/api/subsonicArtists.router.test.ts
similarity index 97%
rename from src/features/artist/api/subsonicArtists.router.test.ts
rename to src/lib/api/subsonicArtists.router.test.ts
index 76b53c51..51b757b4 100644
--- a/src/features/artist/api/subsonicArtists.router.test.ts
+++ b/src/lib/api/subsonicArtists.router.test.ts
@@ -14,7 +14,7 @@ vi.mock('@/lib/api/subsonicLibrary', () => ({
}));
import { api } from '@/lib/api/subsonicClient';
-import { fetchSimilarTracksRouted } from '@/features/artist/api/subsonicArtists';
+import { fetchSimilarTracksRouted } from '@/lib/api/subsonicArtists';
import { useAuthStore } from '@/store/authStore';
const SID = 'srv-router';
diff --git a/src/features/artist/api/subsonicArtists.ts b/src/lib/api/subsonicArtists.ts
similarity index 100%
rename from src/features/artist/api/subsonicArtists.ts
rename to src/lib/api/subsonicArtists.ts
diff --git a/src/features/playlist/api/subsonicPlaylists.ts b/src/lib/api/subsonicPlaylists.ts
similarity index 100%
rename from src/features/playlist/api/subsonicPlaylists.ts
rename to src/lib/api/subsonicPlaylists.ts
diff --git a/src/features/radio/api/subsonicRadio.ts b/src/lib/api/subsonicRadio.ts
similarity index 100%
rename from src/features/radio/api/subsonicRadio.ts
rename to src/lib/api/subsonicRadio.ts
diff --git a/src/lib/api/subsonicRatings.test.ts b/src/lib/api/subsonicRatings.test.ts
index f47074c2..e3c29c81 100644
--- a/src/lib/api/subsonicRatings.test.ts
+++ b/src/lib/api/subsonicRatings.test.ts
@@ -1,12 +1,12 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
-vi.mock('@/features/artist', () => ({ getArtist: vi.fn() }));
+vi.mock('@/lib/api/subsonicArtists', () => ({ getArtist: vi.fn() }));
vi.mock('@/lib/api/subsonicLibrary', () => ({ getAlbum: vi.fn() }));
vi.mock('@/utils/network/subsonicNetworkGuard', () => ({
shouldAttemptSubsonicForActiveServer: vi.fn(() => true),
}));
-import { getArtist } from '@/features/artist';
+import { getArtist } from '@/lib/api/subsonicArtists';
import { invalidateEntityUserRatingCaches, prefetchArtistUserRatings } from '@/lib/api/subsonicRatings';
beforeEach(() => {
diff --git a/src/lib/api/subsonicRatings.ts b/src/lib/api/subsonicRatings.ts
index 885cac56..943a4e6e 100644
--- a/src/lib/api/subsonicRatings.ts
+++ b/src/lib/api/subsonicRatings.ts
@@ -1,4 +1,4 @@
-import { getArtist } from '@/features/artist';
+import { getArtist } from '@/lib/api/subsonicArtists';
import { getAlbum } from '@/lib/api/subsonicLibrary';
import { shouldAttemptSubsonicForActiveServer } from '@/utils/network/subsonicNetworkGuard';
diff --git a/src/features/stats/api/subsonicStatistics.ts b/src/lib/api/subsonicStatistics.ts
similarity index 98%
rename from src/features/stats/api/subsonicStatistics.ts
rename to src/lib/api/subsonicStatistics.ts
index 1b0b351a..47911926 100644
--- a/src/features/stats/api/subsonicStatistics.ts
+++ b/src/lib/api/subsonicStatistics.ts
@@ -1,6 +1,6 @@
import { useAuthStore } from '@/store/authStore';
import { genreTagsFor } from '@/utils/library/genreTags';
-import { getArtists } from '@/features/artist';
+import { getArtists } from '@/lib/api/subsonicArtists';
import { getAlbumList, getRandomSongs } from '@/lib/api/subsonicLibrary';
import type {
StatisticsFormatSample,
diff --git a/src/pages/ComposerDetail.tsx b/src/pages/ComposerDetail.tsx
index 16234bb4..71a2f7d7 100644
--- a/src/pages/ComposerDetail.tsx
+++ b/src/pages/ComposerDetail.tsx
@@ -1,5 +1,5 @@
import { star, unstar } from '@/lib/api/subsonicStarRating';
-import { getArtist, getArtistInfo } from '@/features/artist';
+import { getArtist, getArtistInfo } from '@/lib/api/subsonicArtists';
import type { SubsonicArtist, SubsonicAlbum, SubsonicArtistInfo } from '@/lib/api/subsonicTypes';
import { useEffect, useState, useMemo } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx
index 15acd962..7354923e 100644
--- a/src/pages/Home.tsx
+++ b/src/pages/Home.tsx
@@ -1,4 +1,4 @@
-import { getArtists } from '@/features/artist';
+import { getArtists } from '@/lib/api/subsonicArtists';
import { getAlbumList, getRandomSongs } from '@/lib/api/subsonicLibrary';
import type { SubsonicAlbum, SubsonicArtist, SubsonicSong } from '@/lib/api/subsonicTypes';
import { runLocalRandomSongs } from '../utils/library/browseTextSearch';
diff --git a/src/store/nextAction.ts b/src/store/nextAction.ts
index a9699ef3..6438f4b6 100644
--- a/src/store/nextAction.ts
+++ b/src/store/nextAction.ts
@@ -1,4 +1,4 @@
-import { getSimilarSongs2, getTopSongs } from '@/features/artist';
+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';
diff --git a/src/store/nextActionOrbitRadio.test.ts b/src/store/nextActionOrbitRadio.test.ts
index 101342d1..6ddcebff 100644
--- a/src/store/nextActionOrbitRadio.test.ts
+++ b/src/store/nextActionOrbitRadio.test.ts
@@ -7,7 +7,7 @@ const { inOrbit, getSimilarSongs2, getTopSongs } = vi.hoisted(() => ({
getTopSongs: vi.fn(() => Promise.resolve([])),
}));
-vi.mock('@/features/artist', () => ({ getSimilarSongs2, getTopSongs }));
+vi.mock('@/lib/api/subsonicArtists', () => ({ getSimilarSongs2, getTopSongs }));
vi.mock('@tauri-apps/api/core', () => ({ invoke: vi.fn(() => Promise.resolve()) }));
vi.mock('@/features/orbit', () => ({ isInOrbitSession: () => inOrbit.value }));
vi.mock('./authStore', () => ({
diff --git a/src/store/playerStore.persistence.test.ts b/src/store/playerStore.persistence.test.ts
index bb44f8e9..5eaaedfe 100644
--- a/src/store/playerStore.persistence.test.ts
+++ b/src/store/playerStore.persistence.test.ts
@@ -43,11 +43,11 @@ vi.mock('@/lib/api/subsonicLibrary', () => ({
getSong: vi.fn(async () => null),
getRandomSongs: vi.fn(async () => []),
}));
-vi.mock('@/features/artist', () => ({
+vi.mock('@/lib/api/subsonicArtists', () => ({
getSimilarSongs2: vi.fn(async () => []),
getTopSongs: vi.fn(async () => []),
}));
-vi.mock('@/features/album', () => ({
+vi.mock('@/lib/api/subsonicAlbumInfo', () => ({
getAlbumInfo2: vi.fn(async () => null),
}));
vi.mock('@/lib/api/subsonicScrobble', () => ({
diff --git a/src/utils/componentHelpers/contextMenuActions.ts b/src/utils/componentHelpers/contextMenuActions.ts
index 6271036b..2e23b132 100644
--- a/src/utils/componentHelpers/contextMenuActions.ts
+++ b/src/utils/componentHelpers/contextMenuActions.ts
@@ -1,6 +1,6 @@
import { join } from '@tauri-apps/api/path';
import { invoke } from '@tauri-apps/api/core';
-import { getSimilarSongs2, fetchSimilarTracksRouted, getTopSongs } from '@/features/artist';
+import { getSimilarSongs2, fetchSimilarTracksRouted, getTopSongs } from '@/lib/api/subsonicArtists';
import { filterSongsForLuckyMixRatings, getMixMinRatingsConfigFromAuth } from '../mix/mixRatingFilter';
import { buildDownloadUrl } from '@/lib/api/subsonicStreamUrl';
import { useAuthStore } from '../../store/authStore';
diff --git a/src/utils/mix/luckyMix.ts b/src/utils/mix/luckyMix.ts
index adcb18fa..7c7e801e 100644
--- a/src/utils/mix/luckyMix.ts
+++ b/src/utils/mix/luckyMix.ts
@@ -1,4 +1,4 @@
-import { fetchSimilarTracksRouted } from '@/features/artist';
+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';
diff --git a/src/utils/mix/luckyMixHelpers.ts b/src/utils/mix/luckyMixHelpers.ts
index 5a41d136..47af8da1 100644
--- a/src/utils/mix/luckyMixHelpers.ts
+++ b/src/utils/mix/luckyMixHelpers.ts
@@ -1,4 +1,4 @@
-import { getTopSongs } from '@/features/artist';
+import { getTopSongs } from '@/lib/api/subsonicArtists';
import { filterSongsToActiveLibrary, getAlbumList, getRandomSongs } from '@/lib/api/subsonicLibrary';
import { resolveAlbumForActiveServer } from '@/features/offline';
import type { SubsonicAlbum, SubsonicSong } from '@/lib/api/subsonicTypes';
diff --git a/src/utils/playback/buildInfiniteQueueCandidates.test.ts b/src/utils/playback/buildInfiniteQueueCandidates.test.ts
index bed28bb9..d450b87e 100644
--- a/src/utils/playback/buildInfiniteQueueCandidates.test.ts
+++ b/src/utils/playback/buildInfiniteQueueCandidates.test.ts
@@ -6,7 +6,7 @@
* refactor (2026-05-12). This test pins the artist-first / random-fallback
* order, the dedup contract against existingIds, and the autoAdded flag.
*/
-import { getSimilarSongs2, getTopSongs } from '@/features/artist';
+import { getSimilarSongs2, getTopSongs } from '@/lib/api/subsonicArtists';
import { getRandomSongs } from '@/lib/api/subsonicLibrary';
import type { Track } from '../../store/playerStoreTypes';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
@@ -14,7 +14,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
// Mock only the artist Subsonic API submodule (the pre-move target was
// `api/subsonicArtists`); the barrel re-exports it, so consumers still get the
// stubs while `coerceOpenArtistRefs` (used by songToTrack) stays real.
-vi.mock('@/features/artist/api/subsonicArtists', () => ({
+vi.mock('@/lib/api/subsonicArtists', () => ({
getSimilarSongs2: vi.fn(),
getTopSongs: vi.fn(),
}));
diff --git a/src/utils/playback/buildInfiniteQueueCandidates.ts b/src/utils/playback/buildInfiniteQueueCandidates.ts
index cce683f0..99aabb19 100644
--- a/src/utils/playback/buildInfiniteQueueCandidates.ts
+++ b/src/utils/playback/buildInfiniteQueueCandidates.ts
@@ -1,4 +1,4 @@
-import { getSimilarSongs2, getTopSongs } from '@/features/artist';
+import { getSimilarSongs2, getTopSongs } from '@/lib/api/subsonicArtists';
import { getRandomSongs } from '@/lib/api/subsonicLibrary';
import type { Track } from '../../store/playerStoreTypes';
import {
diff --git a/src/utils/share/enqueueShareSearchPayload.test.ts b/src/utils/share/enqueueShareSearchPayload.test.ts
index 8300308c..dee9be2c 100644
--- a/src/utils/share/enqueueShareSearchPayload.test.ts
+++ b/src/utils/share/enqueueShareSearchPayload.test.ts
@@ -27,7 +27,7 @@ vi.mock('@/lib/api/subsonicLibrary', () => ({
getSong: mocks.getSong,
}));
-vi.mock('@/features/artist', () => ({
+vi.mock('@/lib/api/subsonicArtists', () => ({
getArtist: mocks.getArtist,
}));