From 03ce57feff42d8aba116c2c36e89a619d2f00a21 Mon Sep 17 00:00:00 2001 From: Psychotoxical <171614930+Psychotoxical@users.noreply.github.com> Date: Tue, 30 Jun 2026 20:09:22 +0200 Subject: [PATCH] refactor(app): co-locate shell-only lifecycle hooks in app/hooks; hooks/ now holds only cross-cutting hooks --- src/App.tsx | 2 +- src/app/AppShell.tsx | 18 +++++++++--------- src/app/BlockingMigrationGate.tsx | 2 +- src/app/ConnectionIndicator.tsx | 2 +- src/app/MainApp.tsx | 2 +- src/{ => app}/hooks/useAppActivityTracking.ts | 0 src/{ => app}/hooks/useIdlePlayQueuePull.ts | 6 +++--- .../hooks/useMainScrollingIndicator.ts | 2 +- .../hooks/useMigrationOrchestrator.test.ts | 10 +++++----- .../hooks/useMigrationOrchestrator.ts | 10 +++++----- src/{ => app}/hooks/useNowPlayingTrayTitle.ts | 0 src/{ => app}/hooks/usePlatformShellSetup.ts | 4 ++-- .../hooks/usePlayQueueSyncLedState.ts | 6 +++--- src/{ => app}/hooks/usePrefetchReleaseNotes.ts | 6 +++--- .../hooks/useServerCapabilitiesProbe.ts | 2 +- src/{ => app}/hooks/useThemeScheduler.ts | 2 +- src/{ => app}/hooks/useTrayMenuI18n.ts | 0 .../hooks/useWindowFullscreenState.ts | 0 18 files changed, 37 insertions(+), 37 deletions(-) rename src/{ => app}/hooks/useAppActivityTracking.ts (100%) rename src/{ => app}/hooks/useIdlePlayQueuePull.ts (90%) rename src/{ => app}/hooks/useMainScrollingIndicator.ts (98%) rename src/{ => app}/hooks/useMigrationOrchestrator.test.ts (96%) rename src/{ => app}/hooks/useMigrationOrchestrator.ts (95%) rename src/{ => app}/hooks/useNowPlayingTrayTitle.ts (100%) rename src/{ => app}/hooks/usePlatformShellSetup.ts (97%) rename src/{ => app}/hooks/usePlayQueueSyncLedState.ts (96%) rename src/{ => app}/hooks/usePrefetchReleaseNotes.ts (68%) rename src/{ => app}/hooks/useServerCapabilitiesProbe.ts (97%) rename src/{ => app}/hooks/useThemeScheduler.ts (95%) rename src/{ => app}/hooks/useTrayMenuI18n.ts (100%) rename src/{ => app}/hooks/useWindowFullscreenState.ts (100%) diff --git a/src/App.tsx b/src/App.tsx index bff35da0..cfd09135 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -5,7 +5,7 @@ import { useLyricsStore } from './store/lyricsStore'; import { useThemeStore } from './store/themeStore'; import { useInstalledThemesStore } from './store/installedThemesStore'; import { syncInjectedThemes } from './utils/themes/themeInjection'; -import { useThemeScheduler } from './hooks/useThemeScheduler'; +import { useThemeScheduler } from '@/app/hooks/useThemeScheduler'; import { useFontStore } from './store/fontStore'; import { getWindowKind } from './app/windowKind'; import MiniPlayerApp from './app/MiniPlayerApp'; diff --git a/src/app/AppShell.tsx b/src/app/AppShell.tsx index e71265d7..7eaf5064 100644 --- a/src/app/AppShell.tsx +++ b/src/app/AppShell.tsx @@ -36,18 +36,18 @@ import { OrbitSessionBar, OrbitStartTrigger } from '@/features/orbit'; import { useOrbitHost } from '@/features/orbit'; import { useOrbitGuest } from '@/features/orbit'; import { useOrbitBodyAttrs } from '@/features/orbit'; -import { usePlatformShellSetup } from '../hooks/usePlatformShellSetup'; +import { usePlatformShellSetup } from '@/app/hooks/usePlatformShellSetup'; import { useOfflineBrowseContext } from '@/features/offline'; import { offlineBrowseNavFlags } from '@/features/offline'; -import { useWindowFullscreenState } from '../hooks/useWindowFullscreenState'; -import { useNowPlayingTrayTitle } from '../hooks/useNowPlayingTrayTitle'; -import { usePrefetchReleaseNotes } from '../hooks/usePrefetchReleaseNotes'; -import { useTrayMenuI18n } from '../hooks/useTrayMenuI18n'; -import { useServerCapabilitiesProbe } from '../hooks/useServerCapabilitiesProbe'; +import { useWindowFullscreenState } from '@/app/hooks/useWindowFullscreenState'; +import { useNowPlayingTrayTitle } from '@/app/hooks/useNowPlayingTrayTitle'; +import { usePrefetchReleaseNotes } from '@/app/hooks/usePrefetchReleaseNotes'; +import { useTrayMenuI18n } from '@/app/hooks/useTrayMenuI18n'; +import { useServerCapabilitiesProbe } from '@/app/hooks/useServerCapabilitiesProbe'; import { useQueueResizer } from '@/features/queue'; import { useGlobalDndAndSelectionBlockers } from '@/lib/hooks/useGlobalDndAndSelectionBlockers'; -import { useAppActivityTracking } from '../hooks/useAppActivityTracking'; -import { useMainScrollingIndicator } from '../hooks/useMainScrollingIndicator'; +import { useAppActivityTracking } from '@/app/hooks/useAppActivityTracking'; +import { useMainScrollingIndicator } from '@/app/hooks/useMainScrollingIndicator'; import { useCoverNavigationPriority } from '@/cover/useCoverNavigationPriority'; import { useLiveSearchRouteScope } from '@/features/search'; import { useNowPlayingPrewarm } from '@/features/nowPlaying'; @@ -56,7 +56,7 @@ import { useOfflineLibraryFilterSuspend } from '@/features/offline'; import { AppShellQueueResizerSeam } from '@/app/AppShellQueueResizerSeam'; import { IS_LINUX, IS_MACOS } from '@/lib/util/platform'; import { useConnectionStatus } from '../hooks/useConnectionStatus'; -import { useIdlePlayQueuePull } from '../hooks/useIdlePlayQueuePull'; +import { useIdlePlayQueuePull } from '@/app/hooks/useIdlePlayQueuePull'; import { useAuthStore } from '../store/authStore'; import { usePlayerStore } from '@/features/playback/store/playerStore'; import '@/features/playback/store/previewPlayerVolumeSync'; diff --git a/src/app/BlockingMigrationGate.tsx b/src/app/BlockingMigrationGate.tsx index 6f65a93e..2038ca67 100644 --- a/src/app/BlockingMigrationGate.tsx +++ b/src/app/BlockingMigrationGate.tsx @@ -1,6 +1,6 @@ import type { ReactNode } from 'react'; import { useTranslation } from 'react-i18next'; -import { retryBlockingMigration } from '../hooks/useMigrationOrchestrator'; +import { retryBlockingMigration } from '@/app/hooks/useMigrationOrchestrator'; import { useMigrationStore } from '../store/migrationStore'; function MigrationModal() { diff --git a/src/app/ConnectionIndicator.tsx b/src/app/ConnectionIndicator.tsx index 809b5cd9..63b5756d 100644 --- a/src/app/ConnectionIndicator.tsx +++ b/src/app/ConnectionIndicator.tsx @@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom'; import { Check, ChevronDown, RefreshCw } from 'lucide-react'; import type { ConnectionStatus } from '@/hooks/useConnectionStatus'; -import { usePlayQueueSyncLedState } from '@/hooks/usePlayQueueSyncLedState'; +import { usePlayQueueSyncLedState } from '@/app/hooks/usePlayQueueSyncLedState'; import type { ServerProfile } from '@/store/authStoreTypes'; import { useAuthStore } from '@/store/authStore'; import { switchActiveServer } from '@/utils/server/switchActiveServer'; diff --git a/src/app/MainApp.tsx b/src/app/MainApp.tsx index e430631b..9caf9fe9 100644 --- a/src/app/MainApp.tsx +++ b/src/app/MainApp.tsx @@ -29,7 +29,7 @@ import { useCoverArtPrefetch } from '../cover/useCoverArtPrefetch'; import { useLibraryCoverBackfill } from '@/cover/useLibraryCoverBackfill'; import { useCoverRevalidateScheduler } from '../cover/useCoverRevalidateScheduler'; import { runCoverIdbUpgradeMigration } from '../utils/migrations/coverIdbUpgradeMigration'; -import { useMigrationOrchestrator } from '../hooks/useMigrationOrchestrator'; +import { useMigrationOrchestrator } from '@/app/hooks/useMigrationOrchestrator'; import { IS_WINDOWS } from '@/lib/util/platform'; import TauriEventBridge from './TauriEventBridge'; import AppShell from './AppShell'; diff --git a/src/hooks/useAppActivityTracking.ts b/src/app/hooks/useAppActivityTracking.ts similarity index 100% rename from src/hooks/useAppActivityTracking.ts rename to src/app/hooks/useAppActivityTracking.ts diff --git a/src/hooks/useIdlePlayQueuePull.ts b/src/app/hooks/useIdlePlayQueuePull.ts similarity index 90% rename from src/hooks/useIdlePlayQueuePull.ts rename to src/app/hooks/useIdlePlayQueuePull.ts index 4eb2313c..229a247b 100644 --- a/src/hooks/useIdlePlayQueuePull.ts +++ b/src/app/hooks/useIdlePlayQueuePull.ts @@ -1,6 +1,6 @@ import { useEffect, useRef } from 'react'; import { applyServerPlayQueue } from '@/features/playback/store/applyServerPlayQueue'; -import { useAuthStore } from '../store/authStore'; +import { useAuthStore } from '@/store/authStore'; import { useOrbitStore } from '@/features/orbit'; import { usePlayerStore } from '@/features/playback/store/playerStore'; import { @@ -11,8 +11,8 @@ import { markPlaybackIdle, } from '@/features/playback/store/queuePlaybackIdle'; import { hasPendingQueueSync } from '@/features/playback/store/queueSync'; -import type { ConnectionStatus } from './useConnectionStatus'; -import { canAutoIdlePlayQueuePull } from './usePlayQueueSyncLedState'; +import type { ConnectionStatus } from '@/hooks/useConnectionStatus'; +import { canAutoIdlePlayQueuePull } from '@/app/hooks/usePlayQueueSyncLedState'; const IDLE_THRESHOLD_MS = 30_000; const POLL_INTERVAL_MS = 10_000; diff --git a/src/hooks/useMainScrollingIndicator.ts b/src/app/hooks/useMainScrollingIndicator.ts similarity index 98% rename from src/hooks/useMainScrollingIndicator.ts rename to src/app/hooks/useMainScrollingIndicator.ts index ad434768..00d027c4 100644 --- a/src/hooks/useMainScrollingIndicator.ts +++ b/src/app/hooks/useMainScrollingIndicator.ts @@ -2,7 +2,7 @@ import { useEffect, useState } from 'react'; import { APP_MAIN_SCROLL_VIEWPORT_ID, mainRouteInpageScrollViewportId, -} from '../constants/appScroll'; +} from '@/constants/appScroll'; const SCROLL_IDLE_MS = 180; diff --git a/src/hooks/useMigrationOrchestrator.test.ts b/src/app/hooks/useMigrationOrchestrator.test.ts similarity index 96% rename from src/hooks/useMigrationOrchestrator.test.ts rename to src/app/hooks/useMigrationOrchestrator.test.ts index 53dc49c8..5b939adc 100644 --- a/src/hooks/useMigrationOrchestrator.test.ts +++ b/src/app/hooks/useMigrationOrchestrator.test.ts @@ -1,7 +1,7 @@ import { renderHook, waitFor } from '@testing-library/react'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { useAuthStore } from '../store/authStore'; -import { useMigrationStore } from '../store/migrationStore'; +import { useAuthStore } from '@/store/authStore'; +import { useMigrationStore } from '@/store/migrationStore'; const migrationInspectMock = vi.fn(); const migrationRunMock = vi.fn(); @@ -13,7 +13,7 @@ vi.mock('@tauri-apps/api/event', () => ({ listen: vi.fn(async () => () => {}), })); -vi.mock('../api/migration', () => ({ +vi.mock('@/api/migration', () => ({ migrationInspect: (mappings: unknown) => migrationInspectMock(mappings), migrationRun: (mappings: unknown) => migrationRunMock(mappings), })); @@ -23,11 +23,11 @@ vi.mock('@/lib/api/library', () => ({ libraryGenreTagsRun: () => libraryGenreTagsRunMock(), })); -vi.mock('../utils/server/rewriteFrontendStoreKeys', () => ({ +vi.mock('@/utils/server/rewriteFrontendStoreKeys', () => ({ rewriteFrontendStoreKeys: (servers: unknown) => rewriteFrontendStoreKeysMock(servers), })); -import { useMigrationOrchestrator } from './useMigrationOrchestrator'; +import { useMigrationOrchestrator } from '@/app/hooks/useMigrationOrchestrator'; const DONE_FLAG = 'psysonic-server-key-migration-v1'; const REAL_MIGRATION_TEST_OVERRIDE = '__PSYSONIC_REAL_MIGRATION_TEST__'; diff --git a/src/hooks/useMigrationOrchestrator.ts b/src/app/hooks/useMigrationOrchestrator.ts similarity index 95% rename from src/hooks/useMigrationOrchestrator.ts rename to src/app/hooks/useMigrationOrchestrator.ts index 7898d5c7..5841cddd 100644 --- a/src/hooks/useMigrationOrchestrator.ts +++ b/src/app/hooks/useMigrationOrchestrator.ts @@ -1,11 +1,11 @@ import { useEffect } from 'react'; import { listen } from '@tauri-apps/api/event'; import { libraryGenreTagsInspect, libraryGenreTagsRun } from '@/lib/api/library'; -import { migrationInspect, migrationRun, type ServerIndexMapping } from '../api/migration'; -import { useAuthStore } from '../store/authStore'; -import { useMigrationStore } from '../store/migrationStore'; -import { serverIndexKeyFromUrl } from '../utils/server/serverIndexKey'; -import { rewriteFrontendStoreKeys } from '../utils/server/rewriteFrontendStoreKeys'; +import { migrationInspect, migrationRun, type ServerIndexMapping } from '@/api/migration'; +import { useAuthStore } from '@/store/authStore'; +import { useMigrationStore } from '@/store/migrationStore'; +import { serverIndexKeyFromUrl } from '@/utils/server/serverIndexKey'; +import { rewriteFrontendStoreKeys } from '@/utils/server/rewriteFrontendStoreKeys'; const MIGRATION_DONE_FLAG = 'psysonic-server-key-migration-v1'; let migrationInFlight: Promise | null = null; diff --git a/src/hooks/useNowPlayingTrayTitle.ts b/src/app/hooks/useNowPlayingTrayTitle.ts similarity index 100% rename from src/hooks/useNowPlayingTrayTitle.ts rename to src/app/hooks/useNowPlayingTrayTitle.ts diff --git a/src/hooks/usePlatformShellSetup.ts b/src/app/hooks/usePlatformShellSetup.ts similarity index 97% rename from src/hooks/usePlatformShellSetup.ts rename to src/app/hooks/usePlatformShellSetup.ts index e70bed91..ccf36b65 100644 --- a/src/hooks/usePlatformShellSetup.ts +++ b/src/app/hooks/usePlatformShellSetup.ts @@ -1,7 +1,7 @@ import { useEffect, useState } from 'react'; import { invoke } from '@tauri-apps/api/core'; -import { useAuthStore } from '../store/authStore'; -import type { LinuxWaylandTextRenderProfile } from '../store/authStoreTypes'; +import { useAuthStore } from '@/store/authStore'; +import type { LinuxWaylandTextRenderProfile } from '@/store/authStoreTypes'; import { IS_LINUX, IS_MACOS, IS_WINDOWS } from '@/lib/util/platform'; /** diff --git a/src/hooks/usePlayQueueSyncLedState.ts b/src/app/hooks/usePlayQueueSyncLedState.ts similarity index 96% rename from src/hooks/usePlayQueueSyncLedState.ts rename to src/app/hooks/usePlayQueueSyncLedState.ts index a273ac74..5457f403 100644 --- a/src/hooks/usePlayQueueSyncLedState.ts +++ b/src/app/hooks/usePlayQueueSyncLedState.ts @@ -1,8 +1,8 @@ import { useCallback, useEffect, useMemo, useState, useSyncExternalStore } from 'react'; import { useTranslation } from 'react-i18next'; -import type { ConnectionStatus } from '../hooks/useConnectionStatus'; +import type { ConnectionStatus } from '@/hooks/useConnectionStatus'; import { pullPlayQueueFromActiveServer } from '@/features/playback/store/applyServerPlayQueue'; -import { useAuthStore } from '../store/authStore'; +import { useAuthStore } from '@/store/authStore'; import { useOrbitStore } from '@/features/orbit'; import { usePlayerStore } from '@/features/playback/store/playerStore'; import { getPlaybackServerId, queueIsMultiServer } from '@/features/playback/utils/playback/playbackServer'; @@ -11,7 +11,7 @@ import { subscribeIdleQueuePullSuspended, } from '@/features/playback/store/queuePlaybackIdle'; import { clearQueueHandoffPending, isQueueHandoffPending } from '@/features/playback/store/queueSyncUiState'; -import { showToast } from '../utils/ui/toast'; +import { showToast } from '@/utils/ui/toast'; export function usePlayQueueSyncLedState(status: ConnectionStatus) { const { t } = useTranslation(); diff --git a/src/hooks/usePrefetchReleaseNotes.ts b/src/app/hooks/usePrefetchReleaseNotes.ts similarity index 68% rename from src/hooks/usePrefetchReleaseNotes.ts rename to src/app/hooks/usePrefetchReleaseNotes.ts index fbd1e56a..b8911caa 100644 --- a/src/hooks/usePrefetchReleaseNotes.ts +++ b/src/app/hooks/usePrefetchReleaseNotes.ts @@ -1,7 +1,7 @@ import { useEffect } from 'react'; -import { version as appVersion } from '../../package.json'; -import { isWorkspaceReleaseNotesMode } from '../utils/releaseNotes/releaseNotesChannel'; -import { resolveReleaseNotes } from '../utils/releaseNotes/releaseNotesResolve'; +import { version as appVersion } from '@/../package.json'; +import { isWorkspaceReleaseNotesMode } from '@/utils/releaseNotes/releaseNotesChannel'; +import { resolveReleaseNotes } from '@/utils/releaseNotes/releaseNotesResolve'; /** * Warm the release-notes cache after an update (RC/stable only). diff --git a/src/hooks/useServerCapabilitiesProbe.ts b/src/app/hooks/useServerCapabilitiesProbe.ts similarity index 97% rename from src/hooks/useServerCapabilitiesProbe.ts rename to src/app/hooks/useServerCapabilitiesProbe.ts index 986b5795..35eafef5 100644 --- a/src/hooks/useServerCapabilitiesProbe.ts +++ b/src/app/hooks/useServerCapabilitiesProbe.ts @@ -1,7 +1,7 @@ import { useEffect } from 'react'; import { getMusicFolders } from '@/lib/api/subsonicLibrary'; import { probeEntityRatingSupport } from '@/lib/api/subsonicStarRating'; -import { useAuthStore } from '../store/authStore'; +import { useAuthStore } from '@/store/authStore'; import { cleanupOrphanedOrbitPlaylists } from '@/features/orbit'; /** diff --git a/src/hooks/useThemeScheduler.ts b/src/app/hooks/useThemeScheduler.ts similarity index 95% rename from src/hooks/useThemeScheduler.ts rename to src/app/hooks/useThemeScheduler.ts index 8e40a0b1..ed72e581 100644 --- a/src/hooks/useThemeScheduler.ts +++ b/src/app/hooks/useThemeScheduler.ts @@ -1,5 +1,5 @@ import { useEffect, useState } from 'react'; -import { useThemeStore, getScheduledTheme } from '../store/themeStore'; +import { useThemeStore, getScheduledTheme } from '@/store/themeStore'; import { useSystemPrefersDark } from '@/lib/hooks/useSystemPrefersDark'; /** diff --git a/src/hooks/useTrayMenuI18n.ts b/src/app/hooks/useTrayMenuI18n.ts similarity index 100% rename from src/hooks/useTrayMenuI18n.ts rename to src/app/hooks/useTrayMenuI18n.ts diff --git a/src/hooks/useWindowFullscreenState.ts b/src/app/hooks/useWindowFullscreenState.ts similarity index 100% rename from src/hooks/useWindowFullscreenState.ts rename to src/app/hooks/useWindowFullscreenState.ts