refactor(network): decouple network-reachability layer from features → lib/network

The connection-reachability + Subsonic network-guard helpers were pinned low by
four lib/api consumers (subsonicLibrary/Playlists/Ratings/Scrobble) yet ran two
lower-layer→feature inversions. Both removed without a registry:

- devOfflineBrowseStore is a self-contained DEV-only toggle with zero offline-
  feature coupling — it was only colocated there. Relocated features/offline/store
  → store/ (global). The @/features/offline barrel re-exports it so feature/UI
  consumers are unchanged; the three lower-layer readers (subsonicNetworkGuard,
  activeServerReachability, useConnectionStatus) now import it from @/store directly.

- subsonicNetworkGuard's only other feature dep was playback's resolvePlaybackUrl,
  used solely for the psysonic-local:// skip check. Added hasLocalPlaybackUrl to the
  existing M4 substrate store/localPlaybackResolve — it mirrors resolvePlaybackUrl's
  local-source branch exactly (same profile resolution; the empty-serverId playback
  fallback never applies in the guard), so the skip stays bit-identical.

network/ (subsonicNetworkGuard + activeServerReachability + tests) → lib/network,
now @/features-free. useConnectionStatus is now iron-rule-clean and stays in hooks/
(cross-cutting). Test mocks retargeted to the new seam modules.

Behavior-adjacent (covered by suite; default paths identical): the local-bytes skip
helper — flag for offline-playback runtime QA alongside the M4 media-resolver seam.
This commit is contained in:
Psychotoxical
2026-06-30 21:23:44 +02:00
parent d4ab56f5a6
commit c37d5f7389
40 changed files with 81 additions and 68 deletions
@@ -19,10 +19,10 @@ import { resolveAlbumForServer } from '@/features/offline/utils/offlineMediaReso
const isActiveServerReachableMock = vi.fn(() => true);
const shouldAttemptSubsonicForServerMock = vi.fn((_serverId: string, _trackId?: string) => true);
vi.mock('@/utils/network/activeServerReachability', () => ({
vi.mock('@/lib/network/activeServerReachability', () => ({
isActiveServerReachable: () => isActiveServerReachableMock(),
}));
vi.mock('@/utils/network/subsonicNetworkGuard', () => ({
vi.mock('@/lib/network/subsonicNetworkGuard', () => ({
shouldAttemptSubsonicForServer: (serverId: string, trackId?: string) =>
shouldAttemptSubsonicForServerMock(serverId, trackId),
}));
@@ -16,7 +16,7 @@ const getStarredForServerMock = vi.fn(async (_serverId: string) => ({
const isActiveServerReachableMock = vi.fn(() => true);
vi.mock('@/utils/network/activeServerReachability', () => ({
vi.mock('@/lib/network/activeServerReachability', () => ({
isActiveServerReachable: () => isActiveServerReachableMock(),
}));
@@ -14,7 +14,7 @@ import { useLocalPlaybackStore } from '@/store/localPlaybackStore';
import { getMediaDir } from '@/lib/media/mediaDir';
import { resolveIndexKey, serverIndexKeyForProfile } from '@/lib/server/serverIndexKey';
import { FAVORITES_OFFLINE_JOB_ID } from '@/features/offline/utils/favoritesOfflineConstants';
import { isActiveServerReachable } from '@/utils/network/activeServerReachability';
import { isActiveServerReachable } from '@/lib/network/activeServerReachability';
import { favoritesServerIds } from '@/features/offline/utils/favoritesOfflineBrowse';
import { loadAlbumFromLibraryIndex } from '@/features/offline/utils/offlineLibraryIndexLoad';
import {
@@ -1,7 +1,7 @@
import { renderHook, act } from '@testing-library/react';
import { describe, expect, it, beforeEach } from 'vitest';
import { useDevOfflineBrowseStore } from '@/features/offline/store/devOfflineBrowseStore';
import { resetActiveServerConnectionSnapshot } from '@/utils/network/activeServerReachability';
import { useDevOfflineBrowseStore } from '@/store/devOfflineBrowseStore';
import { resetActiveServerConnectionSnapshot } from '@/lib/network/activeServerReachability';
import { useOfflineBrowseActive } from '@/features/offline/utils/offlineBrowseMode';
describe('useOfflineBrowseActive', () => {
@@ -1,9 +1,9 @@
import {
isDevOfflineBrowseForced,
useDevOfflineBrowseStore,
} from '@/features/offline/store/devOfflineBrowseStore';
} from '@/store/devOfflineBrowseStore';
import { useConnectionStatus } from '@/hooks/useConnectionStatus';
import { isActiveServerReachable } from '@/utils/network/activeServerReachability';
import { isActiveServerReachable } from '@/lib/network/activeServerReachability';
/** True when browse/detail pages should use local-bytes-only data sources. */
export function isOfflineBrowseActive(): boolean {
@@ -44,7 +44,7 @@ vi.mock('@/features/offline/utils/offlinePlaylistBrowse', () => ({
loadOfflineBrowsablePlaylistMock(playlistId, serverId),
}));
vi.mock('@/utils/network/subsonicNetworkGuard', () => ({
vi.mock('@/lib/network/subsonicNetworkGuard', () => ({
shouldAttemptSubsonicForServer: (serverId: string, trackId?: string) =>
shouldAttemptSubsonicForServerMock(serverId, trackId),
}));
@@ -8,7 +8,7 @@ import type {
SubsonicSong,
} from '@/lib/api/subsonicTypes';
import { useAuthStore } from '@/store/authStore';
import { shouldAttemptSubsonicForServer } from '@/utils/network/subsonicNetworkGuard';
import { shouldAttemptSubsonicForServer } from '@/lib/network/subsonicNetworkGuard';
import { isOfflineBrowseActive } from '@/features/offline/utils/offlineBrowseMode';
import { libraryIsReady } from '@/lib/library/libraryReady';
import { registerMediaResolver } from '@/store/mediaResolver';
@@ -6,7 +6,7 @@ import type {
SubsonicArtist,
SubsonicSong,
} from '@/lib/api/subsonicTypes';
import { isActiveServerReachable } from '@/utils/network/activeServerReachability';
import { isActiveServerReachable } from '@/lib/network/activeServerReachability';
import {
albumToAlbum,
trackToSong,
@@ -24,7 +24,7 @@ const isReachableMock = vi.fn(() => true);
const enqueueMock = vi.fn((_task: unknown) => true);
const invokeMock = vi.fn(async (_cmd: string, _args?: unknown) => ({}));
vi.mock('@/utils/network/activeServerReachability', () => ({
vi.mock('@/lib/network/activeServerReachability', () => ({
isActiveServerReachable: () => isReachableMock(),
onActiveServerBecameReachable: () => () => {},
}));
@@ -14,7 +14,7 @@ import { getMediaDir } from '@/lib/media/mediaDir';
import {
isActiveServerReachable,
onActiveServerBecameReachable,
} from '@/utils/network/activeServerReachability';
} from '@/lib/network/activeServerReachability';
import { resolveIndexKey, serverIndexKeyForProfile } from '@/lib/server/serverIndexKey';
import { resolveServerIdForIndexKey } from '@/lib/server/serverLookup';
import { findLocalPlaybackEntry } from '@/store/localPlaybackResolve';
@@ -8,7 +8,7 @@ const isOfflinePinCompleteMock = vi.fn((_albumId: string, _serverId: string) =>
const resolveAlbumForServerMock = vi.fn();
const downloadAlbumMock = vi.fn();
vi.mock('@/utils/network/activeServerReachability', () => ({
vi.mock('@/lib/network/activeServerReachability', () => ({
isActiveServerReachable: () => isActiveServerReachableMock(),
onActiveServerBecameReachable: () => () => {},
}));
@@ -5,8 +5,8 @@ import { useAuthStore } from '@/store/authStore';
import type { OfflineAlbumMeta } from '@/features/offline/store/offlineStore';
import { useOfflineStore } from '@/features/offline/store/offlineStore';
import { trackToSong } from '@/lib/library/advancedSearchLocal';
import { isActiveServerReachable, onActiveServerBecameReachable } from '@/utils/network/activeServerReachability';
import { shouldAttemptSubsonicForServer } from '@/utils/network/subsonicNetworkGuard';
import { isActiveServerReachable, onActiveServerBecameReachable } from '@/lib/network/activeServerReachability';
import { shouldAttemptSubsonicForServer } from '@/lib/network/subsonicNetworkGuard';
import { resolveServerIdForIndexKey } from '@/lib/server/serverLookup';
import { isOfflinePinComplete } from '@/features/offline/utils/offlineLibraryHelpers';
import { resolveAlbumForServer } from '@/features/offline/utils/offlineMediaResolve';