refactor(utils): split componentHelpers grab-bag to owning layers

Per-file routing of the 9-file utils/componentHelpers grab-bag, each driven by
its verified consumer set:
- contextMenu{Actions,Helpers} → features/contextMenu/utils (sole consumer)
- queuePanelHelpers → features/queue/utils (sole consumer)
- nowPlayingHelpers → features/nowPlaying/utils; isRealArtistImage split out to
  cover/ (consumed by cover/artistHero, a lower layer)
- appUpdaterHelpers(+test) + listReorder(+test) → lib/util (config/ + lib/hooks
  consume them; pure)
- playlistDetailHelpers → lib/format (consumed by 3 features; pure)
- appShellHelpers → app/ (app-shell consumers only)
- userMgmtHelpers left in place: consumed by the a11y-HELD SongInfoModal, so a
  move would rewrite a do-not-touch file (same block as licensesData).

Pure moves; tests pass unmodified.
This commit is contained in:
Psychotoxical
2026-06-30 21:05:16 +02:00
parent f43dcc0e76
commit fbca1831a1
46 changed files with 56 additions and 56 deletions
@@ -7,7 +7,7 @@ import type { PlaybackSourceKind } from '@/features/playback/utils/playback/reso
import {
formatQueueReplayGainParts,
renderStars,
} from '@/utils/componentHelpers/queuePanelHelpers';
} from '@/features/queue/utils/queuePanelHelpers';
import { loudnessGainPlaceholderUntilCacheDb } from '@/features/playback/utils/audio/loudnessPlaceholder';
import { effectiveLoudnessPreAnalysisAttenuationDb } from '@/lib/audio/loudnessPreAnalysisSlider';
import { formatQueueBpmTech, formatQueueMoodLabels } from '@/lib/library/trackEnrichment';
@@ -6,7 +6,7 @@ import { usePlayerStore } from '@/features/playback/store/playerStore';
import { useAuthStore } from '@/store/authStore';
import type { QueueItemRef } from '@/lib/media/trackTypes';
import type { QueueDisplayMode } from '@/store/authStoreTypes';
import type { DurationMode } from '@/utils/componentHelpers/queuePanelHelpers';
import type { DurationMode } from '@/features/queue/utils/queuePanelHelpers';
import { formatLongDuration } from '@/lib/format/formatDuration';
import { formatClockTime } from '@/lib/format/formatClockTime';
import { resolveQueueTrack } from '@/features/playback/store/queueTrackView';
@@ -0,0 +1,36 @@
import { Star } from 'lucide-react';
import type { TFunction } from 'i18next';
import type { Track } from '@/lib/media/trackTypes';
export type { DurationMode } from '@/store/authStoreTypes';
export function formatQueueReplayGainParts(track: Track, t: TFunction): string[] {
const parts: string[] = [];
const fmtDb = (db: number) => `${db >= 0 ? '+' : ''}${db.toFixed(1)}`;
if (track.replayGainTrackDb != null) {
parts.push(t('queue.rgTrack', { db: fmtDb(track.replayGainTrackDb) }));
}
if (track.replayGainAlbumDb != null) {
parts.push(t('queue.rgAlbum', { db: fmtDb(track.replayGainAlbumDb) }));
}
if (track.replayGainPeak != null) {
parts.push(t('queue.rgPeak', { pk: track.replayGainPeak.toFixed(3) }));
}
return parts;
}
export function renderStars(rating?: number) {
if (!rating) return null;
const stars = [];
for (let i = 1; i <= 5; i++) {
stars.push(
<Star
key={i}
size={12}
fill={i <= rating ? 'var(--highlight)' : 'none'}
color={i <= rating ? 'var(--highlight)' : 'var(--text-muted)'}
/>
);
}
return <div style={{ display: 'flex', gap: '2px', alignItems: 'center' }}>{stars}</div>;
}