refactor(sidebar): H2 — extract 5 hooks + 5 sub-components (#664)

* refactor(sidebar): H2.1 — extract helpers + constants

* refactor(sidebar): H2.2 — extract useSidebarNewReleasesUnread hook

* refactor(sidebar): H2.3 — extract useSidebarNavDnd hook

* refactor(sidebar): H2.4 — extract 3 hooks (LibraryDropdown + ScrollVisible + PerfProbe)

* refactor(sidebar): H2.5 — extract SidebarPerfProbeModal component

* refactor(sidebar): H2.5–H2.8 — split JSX into per-block components

Sidebar.tsx 938 → 271 LOC. All resulting files under the 400-LOC guideline:
  components/sidebar/SidebarLibraryPicker.tsx       96
  components/sidebar/SidebarActiveJobs.tsx          58
  components/sidebar/SidebarNavBody.tsx            293
  components/sidebar/SidebarPerfProbeModal.tsx     259
  components/sidebar/SidebarPerfProbePhase2.tsx    176
This commit is contained in:
Frank Stellmacher
2026-05-13 21:42:18 +02:00
committed by GitHub
parent ef5eda263d
commit b8a9fe860e
12 changed files with 1673 additions and 1298 deletions
@@ -0,0 +1,58 @@
import { useTranslation } from 'react-i18next';
import { HardDriveDownload, HardDriveUpload, X } from 'lucide-react';
interface Props {
isCollapsed: boolean;
activeJobsCount: number;
cancelAllDownloads: () => void;
isSyncing: boolean;
syncJobDone: number;
syncJobSkip: number;
syncJobFail: number;
syncJobTotal: number;
}
export default function SidebarActiveJobs({
isCollapsed, activeJobsCount, cancelAllDownloads,
isSyncing, syncJobDone, syncJobSkip, syncJobFail, syncJobTotal,
}: Props) {
const { t } = useTranslation();
return (
<>
{activeJobsCount > 0 && (
<div
className={`sidebar-offline-queue ${isCollapsed ? 'sidebar-offline-queue--collapsed' : ''}`}
data-tooltip={isCollapsed ? t('sidebar.downloadingTracks', { n: activeJobsCount }) : undefined}
data-tooltip-pos="right"
>
<HardDriveDownload size={isCollapsed ? 18 : 14} className="spin-slow" />
{!isCollapsed && (
<span>{t('sidebar.downloadingTracks', { n: activeJobsCount })}</span>
)}
<button
className="sidebar-offline-cancel"
onClick={cancelAllDownloads}
data-tooltip={t('sidebar.cancelDownload')}
data-tooltip-pos="right"
aria-label={t('sidebar.cancelDownload')}
>
<X size={12} />
</button>
</div>
)}
{isSyncing && (
<div
className={`sidebar-offline-queue sidebar-sync-queue ${isCollapsed ? 'sidebar-offline-queue--collapsed' : ''}`}
data-tooltip={isCollapsed ? t('sidebar.syncingTracks', { done: syncJobDone + syncJobSkip + syncJobFail, total: syncJobTotal }) : undefined}
data-tooltip-pos="right"
>
<HardDriveUpload size={isCollapsed ? 18 : 14} className="spin-slow" />
{!isCollapsed && (
<span>{t('sidebar.syncingTracks', { done: syncJobDone + syncJobSkip + syncJobFail, total: syncJobTotal })}</span>
)}
</div>
)}
</>
);
}
@@ -0,0 +1,96 @@
import React from 'react';
import { createPortal } from 'react-dom';
import { useTranslation } from 'react-i18next';
import { Check, ChevronDown, Music2 } from 'lucide-react';
interface MusicFolder { id: string; name: string }
interface Props {
filterId: string;
selectedFolderName: string | null;
libraryDropdownOpen: boolean;
setLibraryDropdownOpen: (open: boolean) => void;
dropdownRect: { top: number; left: number; width: number };
libraryTriggerRef: React.RefObject<HTMLButtonElement | null>;
musicFolders: MusicFolder[];
pickLibrary: (id: 'all' | string) => void;
}
export default function SidebarLibraryPicker({
filterId, selectedFolderName, libraryDropdownOpen, setLibraryDropdownOpen,
dropdownRect, libraryTriggerRef, musicFolders, pickLibrary,
}: Props) {
const { t } = useTranslation();
const libraryTriggerPlain = filterId === 'all';
return (
<>
<button
ref={libraryTriggerRef}
type="button"
className={`nav-library-scope-trigger ${libraryTriggerPlain ? 'nav-library-scope-trigger--plain' : ''} ${libraryDropdownOpen ? 'nav-library-scope-trigger--open' : ''}`}
onClick={() => setLibraryDropdownOpen(!libraryDropdownOpen)}
aria-label={t('sidebar.libraryScope')}
aria-expanded={libraryDropdownOpen}
aria-haspopup="listbox"
data-tooltip={libraryDropdownOpen ? undefined : t('sidebar.libraryScope')}
data-tooltip-pos="bottom"
>
{!libraryTriggerPlain ? (
<Music2 size={16} className="nav-library-scope-icon" strokeWidth={2} aria-hidden />
) : null}
<div className="nav-library-scope-text">
<span className="nav-library-scope-title">{t('sidebar.library')}</span>
{selectedFolderName ? (
<span className="nav-library-scope-subtitle" data-tooltip={selectedFolderName} data-tooltip-pos="right">
{selectedFolderName}
</span>
) : null}
</div>
<ChevronDown size={16} strokeWidth={2.25} className="nav-library-scope-chevron" aria-hidden />
</button>
{libraryDropdownOpen &&
createPortal(
<div
className={`nav-library-dropdown-panel${musicFolders.length > 10 ? ' nav-library-dropdown-panel--many-libraries' : ''}`}
role="listbox"
aria-label={t('sidebar.libraryScope')}
style={{
position: 'fixed',
top: dropdownRect.top,
left: dropdownRect.left,
width: dropdownRect.width,
minWidth: dropdownRect.width,
maxWidth: dropdownRect.width,
boxSizing: 'border-box',
}}
>
<button
type="button"
role="option"
aria-selected={filterId === 'all'}
className={`nav-library-dropdown-item ${filterId === 'all' ? 'nav-library-dropdown-item--selected' : ''}`}
onClick={() => pickLibrary('all')}
>
<span className="nav-library-dropdown-item-label">{t('sidebar.allLibraries')}</span>
{filterId === 'all' ? <Check size={16} className="nav-library-dropdown-check" strokeWidth={2.5} /> : <span className="nav-library-dropdown-check-spacer" />}
</button>
{musicFolders.map(f => (
<button
key={f.id}
type="button"
role="option"
aria-selected={filterId === f.id}
className={`nav-library-dropdown-item ${filterId === f.id ? 'nav-library-dropdown-item--selected' : ''}`}
onClick={() => pickLibrary(f.id)}
>
<span className="nav-library-dropdown-item-label">{f.name}</span>
{filterId === f.id ? <Check size={16} className="nav-library-dropdown-check" strokeWidth={2.5} /> : <span className="nav-library-dropdown-check-spacer" />}
</button>
))}
</div>,
document.body
)}
</>
);
}
+293
View File
@@ -0,0 +1,293 @@
import React from 'react';
import { NavLink } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { AudioLines, ChevronRight, HardDriveDownload, PlayCircle, Settings, Sparkles } from 'lucide-react';
import type { SidebarItemConfig } from '../../store/sidebarStore';
import { ALL_NAV_ITEMS } from '../../config/navItems';
import WhatsNewBanner from '../WhatsNewBanner';
import { displayPlaylistName, isSmartPlaylistName } from '../../utils/sidebarHelpers';
import SidebarLibraryPicker from './SidebarLibraryPicker';
import SidebarActiveJobs from './SidebarActiveJobs';
interface NavDndState {
section: 'library' | 'system';
fromIdx: number;
}
interface MusicFolder { id: string; name: string }
interface Props {
isCollapsed: boolean;
showLibraryPicker: boolean;
filterId: string;
selectedFolderName: string | null;
libraryDropdownOpen: boolean;
setLibraryDropdownOpen: (open: boolean) => void;
dropdownRect: { top: number; left: number; width: number };
libraryTriggerRef: React.RefObject<HTMLButtonElement | null>;
musicFolders: MusicFolder[];
pickLibrary: (id: 'all' | string) => void;
visibleLibraryConfigs: SidebarItemConfig[];
libraryItemsForReorder: SidebarItemConfig[];
visibleSystemConfigs: SidebarItemConfig[];
systemItemsForReorder: SidebarItemConfig[];
playlistsExpanded: boolean;
setPlaylistsExpanded: (v: boolean) => void;
playlists: { id: string; name: string }[];
playlistsLoading: boolean;
newReleasesUnreadCount: number;
navDnd: NavDndState | null;
navDndRowClass: (section: 'library' | 'system', sectionIdx: number) => string;
handleNavRowPointerDown: (e: React.PointerEvent, section: 'library' | 'system', sectionIdx: number) => void;
isPlaying: boolean;
hasNowPlayingTrack: boolean;
hasOfflineContent: boolean;
activeJobsCount: number;
cancelAllDownloads: () => void;
isSyncing: boolean;
syncJobDone: number;
syncJobSkip: number;
syncJobFail: number;
syncJobTotal: number;
}
export default function SidebarNavBody(props: Props) {
const {
isCollapsed, showLibraryPicker, filterId, selectedFolderName,
libraryDropdownOpen, setLibraryDropdownOpen, dropdownRect, libraryTriggerRef,
musicFolders, pickLibrary,
visibleLibraryConfigs, libraryItemsForReorder,
visibleSystemConfigs, systemItemsForReorder,
playlistsExpanded, setPlaylistsExpanded, playlists, playlistsLoading,
newReleasesUnreadCount, navDnd, navDndRowClass, handleNavRowPointerDown,
isPlaying, hasNowPlayingTrack, hasOfflineContent,
activeJobsCount, cancelAllDownloads,
isSyncing, syncJobDone, syncJobSkip, syncJobFail, syncJobTotal,
} = props;
const { t } = useTranslation();
return (
<>
{!isCollapsed && (showLibraryPicker ? (
<SidebarLibraryPicker
filterId={filterId}
selectedFolderName={selectedFolderName}
libraryDropdownOpen={libraryDropdownOpen}
setLibraryDropdownOpen={setLibraryDropdownOpen}
dropdownRect={dropdownRect}
libraryTriggerRef={libraryTriggerRef}
musicFolders={musicFolders}
pickLibrary={pickLibrary}
/>
) : (
<span className="nav-section-label">{t('sidebar.library')}</span>
))}
{visibleLibraryConfigs.map(cfg => {
const item = ALL_NAV_ITEMS[cfg.id];
if (!item) return null;
const sectionIdx = libraryItemsForReorder.findIndex(x => x.id === cfg.id);
const dndRow = !isCollapsed && sectionIdx >= 0;
const rowClass = dndRow ? navDndRowClass('library', sectionIdx) : undefined;
const dndProps = dndRow
? {
'data-sidebar-nav-dnd-row': '',
'data-sidebar-section': 'library' as const,
'data-sidebar-idx': String(sectionIdx),
onPointerDown: (e: React.PointerEvent) =>
handleNavRowPointerDown(e, 'library', sectionIdx),
}
: {};
return item.to === '/playlists' && !isCollapsed ? (
<div
key={item.to}
className={`sidebar-playlists-wrapper${rowClass ? ` ${rowClass}` : ''}`}
{...dndProps}
style={navDnd && dndRow ? { touchAction: 'none' } : undefined}
>
<div className="sidebar-playlists-header-row">
<NavLink
to={item.to}
className={({ isActive }) => `nav-link sidebar-playlists-main-link ${isActive ? 'active' : ''}`}
>
<item.icon size={18} />
<span>{t(item.labelKey)}</span>
</NavLink>
<button
className={`sidebar-playlists-toggle ${playlistsExpanded ? 'expanded' : ''}`}
onClick={() => setPlaylistsExpanded(!playlistsExpanded)}
aria-expanded={playlistsExpanded}
aria-label={playlistsExpanded ? t('sidebar.collapsePlaylists') : t('sidebar.expandPlaylists')}
data-tooltip={playlistsExpanded ? t('sidebar.collapsePlaylists') : t('sidebar.expandPlaylists')}
>
<ChevronRight size={14} />
</button>
</div>
{playlistsExpanded && (
<div className="sidebar-playlists-list">
{playlistsLoading ? (
<div className="sidebar-playlists-loading">
<div className="spinner" style={{ width: 14, height: 14 }} />
</div>
) : playlists.length === 0 ? (
<div className="sidebar-playlists-empty">{t('playlists.empty')}</div>
) : (
playlists.map((pl: { id: string; name: string }) => (
<NavLink
key={pl.id}
to={`/playlists/${pl.id}`}
className={({ isActive }) => `nav-link sidebar-playlist-item ${isActive ? 'active' : ''}`}
>
{isSmartPlaylistName(pl.name) ? <Sparkles size={12} /> : <PlayCircle size={12} />}
<span>{displayPlaylistName(pl.name)}</span>
</NavLink>
))
)}
</div>
)}
</div>
) : isCollapsed ? (
<NavLink
key={item.to}
to={item.to}
end={item.to === '/'}
className={({ isActive }) => `nav-link ${isActive ? 'active' : ''}`}
data-tooltip={isCollapsed ? t(item.labelKey) : undefined}
data-tooltip-pos="bottom"
>
<item.icon size={isCollapsed ? 22 : 18} />
{item.to === '/new-releases' && newReleasesUnreadCount > 0 && (
<span className="sidebar-nav-unread-badge" aria-hidden>
{newReleasesUnreadCount > 99 ? '99+' : newReleasesUnreadCount}
</span>
)}
{!isCollapsed && <span>{t(item.labelKey)}</span>}
</NavLink>
) : (
<div
key={item.to}
className={rowClass}
{...dndProps}
style={navDnd && dndRow ? { touchAction: 'none' } : undefined}
>
<NavLink
to={item.to}
end={item.to === '/'}
className={({ isActive }) => `nav-link ${isActive ? 'active' : ''}`}
data-tooltip={isCollapsed ? t(item.labelKey) : undefined}
data-tooltip-pos="bottom"
>
<item.icon size={isCollapsed ? 22 : 18} />
{!isCollapsed && <span>{t(item.labelKey)}</span>}
{item.to === '/new-releases' && newReleasesUnreadCount > 0 && (
<span className="sidebar-nav-unread-badge" aria-hidden>
{newReleasesUnreadCount > 99 ? '99+' : newReleasesUnreadCount}
</span>
)}
</NavLink>
</div>
);
})}
{/* Spacer: everything from here onward sticks to the bottom of the sidebar. */}
<div className="sidebar-bottom-spacer" />
{/* What's New banner — only visible while the current release hasn't been seen. */}
<WhatsNewBanner collapsed={isCollapsed} />
{/* Now Playing — fixed, always visible */}
<NavLink
to="/now-playing"
className={({ isActive }) => `nav-link nav-link-nowplaying ${isActive ? 'active' : ''}`}
data-tooltip={isCollapsed ? t('sidebar.nowPlaying') : undefined}
data-tooltip-pos="bottom"
>
<span className="nav-np-icon-wrap">
<AudioLines size={isCollapsed ? 22 : 18} />
{isPlaying && hasNowPlayingTrack && <span className="nav-np-dot" />}
</span>
{!isCollapsed && <span>{t('sidebar.nowPlaying')}</span>}
</NavLink>
{hasOfflineContent && (
<NavLink
to="/offline"
className={({ isActive }) => `nav-link nav-link-offline ${isActive ? 'active' : ''}`}
data-tooltip={isCollapsed ? t('sidebar.offlineLibrary') : undefined}
data-tooltip-pos="bottom"
>
<HardDriveDownload size={isCollapsed ? 22 : 18} />
{!isCollapsed && <span>{t('sidebar.offlineLibrary')}</span>}
</NavLink>
)}
{visibleSystemConfigs.length > 0 && !isCollapsed && <span className="nav-section-label">{t('sidebar.system')}</span>}
{visibleSystemConfigs.map(cfg => {
const item = ALL_NAV_ITEMS[cfg.id];
if (!item) return null;
const sectionIdx = systemItemsForReorder.findIndex(x => x.id === cfg.id);
const dndRow = !isCollapsed && sectionIdx >= 0;
const rowClass = dndRow ? navDndRowClass('system', sectionIdx) : undefined;
const dndProps = dndRow
? {
'data-sidebar-nav-dnd-row': '',
'data-sidebar-section': 'system' as const,
'data-sidebar-idx': String(sectionIdx),
onPointerDown: (e: React.PointerEvent) =>
handleNavRowPointerDown(e, 'system', sectionIdx),
}
: {};
return isCollapsed ? (
<NavLink
key={item.to}
to={item.to}
className={({ isActive }) => `nav-link ${isActive ? 'active' : ''}`}
data-tooltip={isCollapsed ? t(item.labelKey) : undefined}
data-tooltip-pos="bottom"
>
<item.icon size={isCollapsed ? 22 : 18} />
{!isCollapsed && <span>{t(item.labelKey)}</span>}
</NavLink>
) : (
<div
key={item.to}
className={rowClass}
{...dndProps}
style={navDnd && dndRow ? { touchAction: 'none' } : undefined}
>
<NavLink
to={item.to}
className={({ isActive }) => `nav-link ${isActive ? 'active' : ''}`}
data-tooltip={isCollapsed ? t(item.labelKey) : undefined}
data-tooltip-pos="bottom"
>
<item.icon size={isCollapsed ? 22 : 18} />
{!isCollapsed && <span>{t(item.labelKey)}</span>}
</NavLink>
</div>
);
})}
<NavLink
to="/settings"
className={({ isActive }) => `nav-link ${isActive ? 'active' : ''}`}
data-tooltip={isCollapsed ? t('sidebar.settings') : undefined}
data-tooltip-pos="bottom"
>
<Settings size={isCollapsed ? 22 : 18} />
{!isCollapsed && <span>{t('sidebar.settings')}</span>}
</NavLink>
<SidebarActiveJobs
isCollapsed={isCollapsed}
activeJobsCount={activeJobsCount}
cancelAllDownloads={cancelAllDownloads}
isSyncing={isSyncing}
syncJobDone={syncJobDone}
syncJobSkip={syncJobSkip}
syncJobFail={syncJobFail}
syncJobTotal={syncJobTotal}
/>
</>
);
}
@@ -0,0 +1,259 @@
import { createPortal } from 'react-dom';
import { X } from 'lucide-react';
import SidebarPerfProbePhase2 from './SidebarPerfProbePhase2';
import { resetPerfProbeFlags, setPerfProbeFlag, type PerfProbeFlags } from '../../utils/perfFlags';
interface PerfCpu {
app: number;
webkit: number;
supported: boolean;
}
interface PerfDiagRates {
progress: number;
waveform: number;
home: number;
}
interface Props {
open: boolean;
onClose: () => void;
perfFlags: PerfProbeFlags;
perfCpu: PerfCpu | null;
perfDiagRates: PerfDiagRates | null;
hotCacheEnabled: boolean;
setHotCacheEnabled: (v: boolean) => void;
normalizationEngine: string;
setNormalizationEngine: (v: 'off' | 'loudness') => void;
loggingMode: string;
setLoggingMode: (v: 'off' | 'normal') => void;
}
export default function SidebarPerfProbeModal({
open, onClose, perfFlags, perfCpu, perfDiagRates,
hotCacheEnabled, setHotCacheEnabled,
normalizationEngine, setNormalizationEngine,
loggingMode, setLoggingMode,
}: Props) {
if (!open) return null;
return createPortal(
<div className="modal-overlay modal-overlay--perf-probe" onClick={() => onClose()} role="dialog" aria-modal="true">
<div
className="modal-content sidebar-perf-modal"
onClick={e => e.stopPropagation()}
style={{ maxWidth: 560 }}
>
<button className="modal-close" onClick={() => onClose()}><X size={18} /></button>
<h3 className="modal-title">Performance Probe</h3>
<p className="sidebar-perf-modal__hint">
Temporary runtime switches to estimate UI effect cost.
</p>
<label className="sidebar-perf-modal__item">
<input
type="checkbox"
checked={perfFlags.showFpsOverlay}
onChange={e => setPerfProbeFlag('showFpsOverlay', e.target.checked)}
/>
<span>Show FPS overlay (requestAnimationFrame rate)</span>
</label>
<div className="sidebar-perf-modal__cpu">
<div className="sidebar-perf-modal__cpu-title">Live CPU (approx)</div>
{perfCpu == null ? (
<div className="sidebar-perf-modal__cpu-row">Collecting samples</div>
) : perfCpu.supported ? (
<>
<div className="sidebar-perf-modal__cpu-row">psysonic: {perfCpu.app.toFixed(1)}%</div>
<div className="sidebar-perf-modal__cpu-row">WebKitWebProcess: {perfCpu.webkit.toFixed(1)}%</div>
{perfDiagRates && (
<>
<div className="sidebar-perf-modal__cpu-row">audio:progress rate: {perfDiagRates.progress.toFixed(1)}/s</div>
<div className="sidebar-perf-modal__cpu-row">waveform draws rate: {perfDiagRates.waveform.toFixed(1)}/s</div>
<div className="sidebar-perf-modal__cpu-row">Home commits rate: {perfDiagRates.home.toFixed(1)}/s</div>
</>
)}
</>
) : (
<div className="sidebar-perf-modal__cpu-row">Unavailable on this platform/build.</div>
)}
</div>
<details className="sidebar-perf-modal__phase">
<summary className="sidebar-perf-modal__phase-title">Phase 1 Global / Shell / Network</summary>
<label className="sidebar-perf-modal__item">
<input
type="checkbox"
checked={perfFlags.disableWaveformCanvas}
onChange={e => setPerfProbeFlag('disableWaveformCanvas', e.target.checked)}
/>
<span>Disable only PlayerBar waveform (`WaveformSeek`)</span>
</label>
<label className="sidebar-perf-modal__item">
<input
type="checkbox"
checked={perfFlags.disablePlayerProgressUi}
onChange={e => setPerfProbeFlag('disablePlayerProgressUi', e.target.checked)}
/>
<span>Disable player live progress UI updates (time + seek/progress bindings)</span>
</label>
<label className="sidebar-perf-modal__item">
<input
type="checkbox"
checked={perfFlags.disableMarqueeScroll}
onChange={e => setPerfProbeFlag('disableMarqueeScroll', e.target.checked)}
/>
<span>Disable marquee text scrolling</span>
</label>
<label className="sidebar-perf-modal__item">
<input
type="checkbox"
checked={perfFlags.disableBackdropBlur}
onChange={e => setPerfProbeFlag('disableBackdropBlur', e.target.checked)}
/>
<span>Disable backdrop blur effects</span>
</label>
<label className="sidebar-perf-modal__item">
<input
type="checkbox"
checked={perfFlags.disableCssAnimations}
onChange={e => setPerfProbeFlag('disableCssAnimations', e.target.checked)}
/>
<span>Disable CSS animations and transitions</span>
</label>
<label className="sidebar-perf-modal__item">
<input
type="checkbox"
checked={perfFlags.disableOverlayScrollbars}
onChange={e => setPerfProbeFlag('disableOverlayScrollbars', e.target.checked)}
/>
<span>Disable overlay scrollbar engine (JS + rail)</span>
</label>
<label className="sidebar-perf-modal__item">
<input
type="checkbox"
checked={perfFlags.disableTooltipPortal}
onChange={e => setPerfProbeFlag('disableTooltipPortal', e.target.checked)}
/>
<span>Disable global tooltip portal/listeners</span>
</label>
<label className="sidebar-perf-modal__item">
<input
type="checkbox"
checked={perfFlags.disableQueuePanelMount}
onChange={e => setPerfProbeFlag('disableQueuePanelMount', e.target.checked)}
/>
<span>Disable QueuePanel mount (desktop right column)</span>
</label>
<label className="sidebar-perf-modal__item">
<input
type="checkbox"
checked={perfFlags.disableBackgroundPolling}
onChange={e => setPerfProbeFlag('disableBackgroundPolling', e.target.checked)}
/>
<span>Disable background polling (connection + radio metadata)</span>
</label>
<div className="sidebar-perf-modal__subhead">Engine/network toggles</div>
<label className="sidebar-perf-modal__item">
<input
type="checkbox"
checked={!hotCacheEnabled}
onChange={e => setHotCacheEnabled(!e.target.checked)}
/>
<span>Disable hot-cache prefetch downloads</span>
</label>
<label className="sidebar-perf-modal__item">
<input
type="checkbox"
checked={normalizationEngine === 'off'}
onChange={e => setNormalizationEngine(e.target.checked ? 'off' : 'loudness')}
/>
<span>Disable normalization engine (set to Off)</span>
</label>
<label className="sidebar-perf-modal__item">
<input
type="checkbox"
checked={loggingMode === 'off'}
onChange={e => setLoggingMode(e.target.checked ? 'off' : 'normal')}
/>
<span>Set runtime logging mode to Off</span>
</label>
</details>
<SidebarPerfProbePhase2 perfFlags={perfFlags} />
<details className="sidebar-perf-modal__phase">
<summary className="sidebar-perf-modal__phase-title">Phase 3 Active diagnostics (quick access)</summary>
<label className="sidebar-perf-modal__item">
<input
type="checkbox"
checked={perfFlags.disablePlayerProgressUi}
onChange={e => setPerfProbeFlag('disablePlayerProgressUi', e.target.checked)}
/>
<span>Disable player live progress UI updates (time + seek/progress bindings)</span>
</label>
<label className="sidebar-perf-modal__item">
<input
type="checkbox"
checked={perfFlags.disableWaveformCanvas}
onChange={e => setPerfProbeFlag('disableWaveformCanvas', e.target.checked)}
/>
<span>Disable only PlayerBar waveform (`WaveformSeek`)</span>
</label>
<label className="sidebar-perf-modal__item">
<input
type="checkbox"
checked={perfFlags.disableHomeRailArtwork}
onChange={e => setPerfProbeFlag('disableHomeRailArtwork', e.target.checked)}
/>
<span>Disable artwork inside Home rows/rails only</span>
</label>
<label className="sidebar-perf-modal__item">
<input
type="checkbox"
checked={perfFlags.disableMainstageRailArtwork}
onChange={e => setPerfProbeFlag('disableMainstageRailArtwork', e.target.checked)}
/>
<span>Disable artwork inside Home rows/rails</span>
</label>
<label className="sidebar-perf-modal__item">
<input
type="checkbox"
checked={perfFlags.disableMainstageRails}
onChange={e => setPerfProbeFlag('disableMainstageRails', e.target.checked)}
/>
<span>Disable Home rows/rails (`AlbumRow` + `SongRail`)</span>
</label>
<label className="sidebar-perf-modal__item">
<input
type="checkbox"
checked={perfFlags.disableMainstageHeroBackdrop}
onChange={e => setPerfProbeFlag('disableMainstageHeroBackdrop', e.target.checked)}
/>
<span>Disable Hero backdrop/crossfade only</span>
</label>
<label className="sidebar-perf-modal__item">
<input
type="checkbox"
checked={perfFlags.disableHomeArtworkFx}
onChange={e => setPerfProbeFlag('disableHomeArtworkFx', e.target.checked)}
/>
<span>Keep artwork, disable Home card visual effects (hover/overlay/shadows)</span>
</label>
<label className="sidebar-perf-modal__item">
<input
type="checkbox"
checked={perfFlags.disableHomeArtworkClip}
onChange={e => setPerfProbeFlag('disableHomeArtworkClip', e.target.checked)}
/>
<span>Diagnostic: flatten Home artwork clipping (no rounded corners/masks)</span>
</label>
</details>
<div className="sidebar-perf-modal__actions">
<button type="button" className="btn btn-ghost" onClick={() => resetPerfProbeFlags()}>
Reset
</button>
<button type="button" className="btn btn-primary" onClick={() => onClose()}>
Close
</button>
</div>
</div>
</div>,
document.body,
);
}
@@ -0,0 +1,176 @@
import { setPerfProbeFlag, type PerfProbeFlags } from '../../utils/perfFlags';
export default function SidebarPerfProbePhase2({ perfFlags }: { perfFlags: PerfProbeFlags }) {
return (
<details className="sidebar-perf-modal__phase">
<summary className="sidebar-perf-modal__phase-title">Phase 2 Mainstage (Center Content)</summary>
<label className="sidebar-perf-modal__item">
<input
type="checkbox"
checked={perfFlags.disableMainRouteContentMount}
onChange={e => setPerfProbeFlag('disableMainRouteContentMount', e.target.checked)}
/>
<span>Disable central route content mount</span>
</label>
<details className="sidebar-perf-modal__phase sidebar-perf-modal__phase--nested">
<summary className="sidebar-perf-modal__phase-title">Shared mainstage layers (multiple pages)</summary>
<label className="sidebar-perf-modal__item">
<input
type="checkbox"
checked={perfFlags.disableMainstageStickyHeader}
onChange={e => setPerfProbeFlag('disableMainstageStickyHeader', e.target.checked)}
/>
<span>Disable sticky headers (Tracks + Albums)</span>
</label>
</details>
<details className="sidebar-perf-modal__phase sidebar-perf-modal__phase--nested">
<summary className="sidebar-perf-modal__phase-title">Home (`/`)</summary>
<label className="sidebar-perf-modal__item">
<input
type="checkbox"
checked={perfFlags.disableMainstageHero}
onChange={e => setPerfProbeFlag('disableMainstageHero', e.target.checked)}
/>
<span>Disable Home hero block</span>
</label>
<label className="sidebar-perf-modal__item">
<input
type="checkbox"
checked={perfFlags.disableMainstageHeroBackdrop}
onChange={e => setPerfProbeFlag('disableMainstageHeroBackdrop', e.target.checked)}
/>
<span>Disable Hero backdrop/crossfade only</span>
</label>
<label className="sidebar-perf-modal__item">
<input
type="checkbox"
checked={perfFlags.disableMainstageRails}
onChange={e => setPerfProbeFlag('disableMainstageRails', e.target.checked)}
/>
<span>Disable Home rows/rails (`AlbumRow` + `SongRail`)</span>
</label>
<label className="sidebar-perf-modal__item">
<input
type="checkbox"
checked={perfFlags.disableHomeAlbumRows}
onChange={e => setPerfProbeFlag('disableHomeAlbumRows', e.target.checked)}
/>
<span>Disable Home `AlbumRow` sections only</span>
</label>
<label className="sidebar-perf-modal__item">
<input
type="checkbox"
checked={perfFlags.disableHomeSongRails}
onChange={e => setPerfProbeFlag('disableHomeSongRails', e.target.checked)}
/>
<span>Disable Home `SongRail` sections only</span>
</label>
<label className="sidebar-perf-modal__item">
<input
type="checkbox"
checked={perfFlags.disableMainstageRailArtwork}
onChange={e => setPerfProbeFlag('disableMainstageRailArtwork', e.target.checked)}
/>
<span>Disable artwork inside Home rows/rails</span>
</label>
<label className="sidebar-perf-modal__item">
<input
type="checkbox"
checked={perfFlags.disableHomeRailArtwork}
onChange={e => setPerfProbeFlag('disableHomeRailArtwork', e.target.checked)}
/>
<span>Disable artwork inside Home rows/rails only</span>
</label>
<label className="sidebar-perf-modal__item">
<input
type="checkbox"
checked={perfFlags.disableHomeArtworkFx}
onChange={e => setPerfProbeFlag('disableHomeArtworkFx', e.target.checked)}
/>
<span>Keep artwork, disable Home card visual effects (hover/overlay/shadows)</span>
</label>
<label className="sidebar-perf-modal__item">
<input
type="checkbox"
checked={perfFlags.disableHomeArtworkClip}
onChange={e => setPerfProbeFlag('disableHomeArtworkClip', e.target.checked)}
/>
<span>Diagnostic: flatten Home artwork clipping (no rounded corners/masks)</span>
</label>
<label className="sidebar-perf-modal__item">
<input
type="checkbox"
checked={perfFlags.disableMainstageRailInteractivity}
onChange={e => setPerfProbeFlag('disableMainstageRailInteractivity', e.target.checked)}
/>
<span>Disable Home rail scroll/nav handlers</span>
</label>
<label className="sidebar-perf-modal__item">
<input
type="checkbox"
checked={perfFlags.disableMainstageGridCards}
onChange={e => setPerfProbeFlag('disableMainstageGridCards', e.target.checked)}
/>
<span>Disable Home discover artists chip-grid</span>
</label>
</details>
<details className="sidebar-perf-modal__phase sidebar-perf-modal__phase--nested">
<summary className="sidebar-perf-modal__phase-title">Tracks (`/tracks`)</summary>
<label className="sidebar-perf-modal__item">
<input
type="checkbox"
checked={perfFlags.disableMainstageHero}
onChange={e => setPerfProbeFlag('disableMainstageHero', e.target.checked)}
/>
<span>Disable Tracks hero block</span>
</label>
<label className="sidebar-perf-modal__item">
<input
type="checkbox"
checked={perfFlags.disableMainstageRails}
onChange={e => setPerfProbeFlag('disableMainstageRails', e.target.checked)}
/>
<span>Disable Tracks rails (Highly Rated + Random)</span>
</label>
<label className="sidebar-perf-modal__item">
<input
type="checkbox"
checked={perfFlags.disableMainstageRailArtwork}
onChange={e => setPerfProbeFlag('disableMainstageRailArtwork', e.target.checked)}
/>
<span>Disable artwork inside Tracks rails</span>
</label>
<label className="sidebar-perf-modal__item">
<input
type="checkbox"
checked={perfFlags.disableMainstageRailInteractivity}
onChange={e => setPerfProbeFlag('disableMainstageRailInteractivity', e.target.checked)}
/>
<span>Disable Tracks rail scroll/nav handlers</span>
</label>
<label className="sidebar-perf-modal__item">
<input
type="checkbox"
checked={perfFlags.disableMainstageVirtualLists}
onChange={e => setPerfProbeFlag('disableMainstageVirtualLists', e.target.checked)}
/>
<span>Disable Tracks virtual browse list (`VirtualSongList`)</span>
</label>
</details>
<details className="sidebar-perf-modal__phase sidebar-perf-modal__phase--nested">
<summary className="sidebar-perf-modal__phase-title">Albums (`/albums`)</summary>
<label className="sidebar-perf-modal__item">
<input
type="checkbox"
checked={perfFlags.disableMainstageGridCards}
onChange={e => setPerfProbeFlag('disableMainstageGridCards', e.target.checked)}
/>
<span>Disable Albums card grid (`AlbumCard` list)</span>
</label>
</details>
</details>
);
}