From de3c0d9da14b43c2b445adcf3e58717e7ed06115 Mon Sep 17 00:00:00 2001 From: cucadmuh <49571317+cucadmuh@users.noreply.github.com> Date: Wed, 6 May 2026 02:13:59 +0300 Subject: [PATCH] Feat/performance probe fps overlay (#472) * feat(perf-probe): add FPS overlay toggle and tidy probe modal Add optional rAF-based FPS readout controlled by a persisted probe flag. Remove the separate keyboard shortcut. Collapse all phase sections by default. * perf(fps-overlay): subscribe only to showFpsOverlay flag Add usePerfProbeFlag so the overlay does not re-render when other probe toggles change. Track the animation frame id with a loop-local variable. --- src/App.tsx | 3 +++ src/components/FpsOverlay.tsx | 48 +++++++++++++++++++++++++++++++++++ src/components/Sidebar.tsx | 18 +++++++++---- src/styles/components.css | 24 ++++++++++++++++++ src/utils/perfFlags.ts | 12 +++++++++ 5 files changed, 100 insertions(+), 5 deletions(-) create mode 100644 src/components/FpsOverlay.tsx diff --git a/src/App.tsx b/src/App.tsx index 6e080cad..37abd14b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -106,6 +106,7 @@ import { usePreviewStore } from './store/previewStore'; import { DEFAULT_IN_APP_BINDINGS, canRunShortcutActionInMiniWindow, executeCliPlayerCommand, executeRuntimeAction, isGlobalShortcutActionId, isShortcutAction } from './config/shortcutActions'; import { matchInAppShortcutAction } from './shortcuts/runtime'; import ZipDownloadOverlay from './components/ZipDownloadOverlay'; +import FpsOverlay from './components/FpsOverlay'; import PasteClipboardHandler from './components/PasteClipboardHandler'; import { usePerfProbeFlags } from './utils/perfFlags'; @@ -1328,6 +1329,7 @@ export default function App() { {!perfFlags.disableTooltipPortal && } + ); } @@ -1418,6 +1420,7 @@ export default function App() { {exportPickerOpen && setExportPickerOpen(false)} />} + ); diff --git a/src/components/FpsOverlay.tsx b/src/components/FpsOverlay.tsx new file mode 100644 index 00000000..7bb36d61 --- /dev/null +++ b/src/components/FpsOverlay.tsx @@ -0,0 +1,48 @@ +import { useEffect, useState } from 'react'; +import { createPortal } from 'react-dom'; +import { usePerfProbeFlag } from '../utils/perfFlags'; + +const SAMPLE_MS = 500; + +/** FPS from rAF callbacks over sliding ~500ms windows; only runs when Performance Probe enables the overlay. */ +export default function FpsOverlay() { + const showFpsOverlay = usePerfProbeFlag('showFpsOverlay'); + const [fps, setFps] = useState(0); + + useEffect(() => { + if (!showFpsOverlay) { + setFps(0); + return; + } + + let frames = 0; + let lastReport = performance.now(); + let rafId = 0; + + const loop = () => { + frames++; + const now = performance.now(); + if (now - lastReport >= SAMPLE_MS) { + const elapsedSec = (now - lastReport) / 1000; + setFps(Math.round(frames / elapsedSec)); + frames = 0; + lastReport = now; + } + rafId = requestAnimationFrame(loop); + }; + + rafId = requestAnimationFrame(loop); + return () => cancelAnimationFrame(rafId); + }, [showFpsOverlay]); + + if (!showFpsOverlay) return null; + + return createPortal( + , + document.body, + ); +} diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index 61a96fc7..b3bdb11e 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -1112,6 +1112,14 @@ export default function Sidebar({

Temporary runtime switches to estimate UI effect cost.

+
Live CPU (approx)
{perfCpu == null ? ( @@ -1242,7 +1250,7 @@ export default function Sidebar({ /> Disable central route content mount -
+
Shared mainstage layers (multiple pages)
-
+
Home (`/`)
-
+
Tracks (`/tracks`)
-
+
Albums (`/albums`)
-
+
Phase 3 — Active diagnostics (quick access)