PURPOSE

Dev-only performance analytics dashboard mounted at /dev/analytics. Visualizes telemetry pulled from Supabase to expose FPS trends, render-pass breakdowns, weapon/hull performance impact, spike frequency, and device/session health, segmented by version and by platform (mobile vs desktop). Uses the Supabase service_role key to bypass RLS and read every player’s telemetry; production builds and any environment without VITE_SUPABASE_SERVICE_KEY render a stub instead, so the inner component (and the key reference) is tree-shaken out of the production bundle.

OWNS

  • Default export PerfDashboardScreen — guard wrapper that gates on import.meta.env.DEV && SERVICE_KEY.
  • Internal component _PerfDashboardScreenInner — owns the dashboard’s React state: rangeDays, platform, allRuns, allSessions, allRenderPerfRuns, runsState, sessionsState, error.
  • Section components FpsOverviewSection, BottleneckSection, GameplayImpactSection, DeviceHealthSection.
  • Inline presentational components ChartCard and MetricCard, plus the TOOLTIP_STYLE / TOOLTIP_LABEL constants.
  • Local type declarations: TelemetryRun, RenderPerfRun, TelemetrySession, LoadState, Platform.
  • Pure helpers: dateGte, fetchSupabase, fetchRuns, fetchRenderPerf, fetchSessions, avg, round1, sortVersions, groupBy, classifyDevice, dayOf, fpsColor, filterRunsByPlatform, filterRenderPerfByPlatform, filterSessionsByPlatform.
  • Constants SUPABASE_URL, SERVICE_KEY, DATE_RANGES, FPS_BUCKET_COLORS.

READS FROM

  • import.meta.env.VITE_SUPABASE_SERVICE_KEY — Supabase service-role key, read once at module load into SERVICE_KEY. Empty string fallback when unset.
  • import.meta.env.DEV — Vite static constant used by the guard wrapper.
  • Hardcoded SUPABASE_URL constant pointing at the project Supabase instance.
  • Supabase REST endpoints via fetch:
    • telemetry_runs (filtered: cause_of_death=neq.heartbeat, started_at=gte.<range>, order=started_at.desc, limit=500) — full run columns.
    • telemetry_runs again with render_perf=not.is.null and limit=200 — render-perf-only column subset, fetched on demand.
    • telemetry_sessions (filtered: started_at=gte.<range>, order=started_at.desc, limit=500).
  • recharts primitives BarChart, Bar, LineChart, Line, ScatterChart, Scatter, XAxis, YAxis, CartesianGrid, Tooltip, Legend, Cell, ResponsiveContainer.
  • React hooks useState, useEffect, useMemo, and the ReactNode type.

PUSHES TO

  • Component-local React state via setRangeDays, setPlatform, setAllRuns, setAllSessions, setAllRenderPerfRuns, setRunsState, setSessionsState, setError.
  • The DOM: rendered chart cards, metric cards, platform/date toggles, loading/error views, and the Back to Dev link (href="/dev").
  • console.error channel [perf-dashboard] when the on-demand render-perf fetch fails.
  • No writes to Zustand stores, no calls into the game engine, no Supabase writes.

DOES NOT

  • Does not mutate any telemetry row — reads only, no POST/PATCH/DELETE.
  • Does not authenticate users; relies on the service-role key being present in the local .env.local.
  • Does not render anything sensitive in production: when import.meta.env.DEV is false or SERVICE_KEY is empty it returns a static “Analytics only available in local dev” stub, and _PerfDashboardScreenInner is tree-shaken.
  • Does not import from the game engine, the metagame stores, the bridge layer, or the Supabase JS client. The fetch path is raw REST with apikey + Authorization: Bearer headers, matching the telemetry sender pattern.
  • Does not depend on routing context — the back link is a plain anchor, not a router navigation call.
  • Does not persist UI state across reloads (range, platform, loaded-render-perf flag are component-local).
  • Does not paginate beyond the hard limit=500 / limit=200 ceilings on each query.

Signals

  • LoadState union ('idle' | 'loading' | 'ready' | 'error') tracks the runs and sessions fetches independently.
  • Per-section memoization signals: versions, byVersion, avgByVersion, histogramData, dailyData, spikeData, causeData, passData, weaponData, hullData, durationScatter, killScatter, screenData — all derived from filtered runs / sessions / renderPerfRuns via useMemo.
  • Platform filter signal: perf_flags.isMobile decides mobile vs desktop for run and render-perf records; classifyDevice(device_ua) (Mobile/Tablet/Desktop) decides for sessions, with Mobile and Tablet both counted as mobile.
  • FPS classification thresholds for color: green at >=50, yellow at >=30, red below 30 (fpsColor).
  • FPS histogram bucket palette: 0-15 red, 15-30 orange, 30-45 yellow, 45-60 light-green, 60+ green (FPS_BUCKET_COLORS).
  • Date-range options: 1d / 7d / 30d / all-time (DATE_RANGES); dateGte(0) returns 1970-01-01T00:00:00Z.
  • Aggregation min-sample gate: weapon and hull groupings require >=3 runs before they appear.
  • Render-pass display gate: passes with avgMs <= 0.1 are filtered out.
  • Survived-vs-died bucketing: any cause_of_death other than 'survived' is treated as 'died'.

Entry points

  • Default export PerfDashboardScreen is the only consumer-facing entry.
  • Wired into the metagame routing layer at the /dev/analytics path (the screen header includes a &larr; Back to Dev anchor to /dev).
  • Mount sequence: guard wrapper checks import.meta.env.DEV && SERVICE_KEY _PerfDashboardScreenInner mounts useEffect keyed on rangeDays kicks off Promise.all([fetchRuns, fetchSessions]) sections render once runsState === 'ready'.
  • The “Load Render Perf Data” button inside BottleneckSection invokes loadRenderPerf, which fires fetchRenderPerf(rangeDays) and stores the result into allRenderPerfRuns.
  • Changing the platform toggle or the date-range buttons updates state; range changes trigger a refetch (and clear allRenderPerfRuns), platform changes only recompute the memoized filtered views.

Pattern notes

  • Two-component guard pattern: PerfDashboardScreen (default export) renders the stub or the inner component. All hooks live in _PerfDashboardScreenInner so React never sees a hook count mismatch between the two branches.
  • Production safety relies on Vite tree-shaking the inner component when import.meta.env.DEV is the static false constant and SERVICE_KEY is ''. The comments in the file call this out explicitly because shipping service_role to the browser would be catastrophic.
  • Inline styling: every visual is a literal style={{ ... }} object — no CSS modules, no styled-components, no shared theme tokens beyond the local TOOLTIP_STYLE / TOOLTIP_LABEL constants and the FPS_BUCKET_COLORS table.
  • All data shaping happens in useMemo blocks colocated with the section that consumes them; raw fetched arrays live at the top level and are filtered by platform once before being passed down.
  • Charts use recharts ResponsiveContainer with fixed heights (mostly 280, or Math.max(280, rows * 28) for vertical bar charts) so they reflow to the parent grid columns.
  • Fetch helpers use a single shared fetchSupabase<T> that throws on non-OK responses with the status code and body text — surfaced to the user via the inline error banner with a Retry button.
  • The useEffect fetch uses a cancelled flag to avoid setting state after unmount or after a stale range change.
  • Memo dependency hygiene: every memoized derivation lists its inputs explicitly, and the platform-filtered runs, sessions, renderPerfRuns are themselves memoized to keep section memos stable when only unrelated state changes.
  • Display rules: numbers shown in the UI are rounded to one decimal via round1; days are sliced to MM-DD for the daily-FPS line chart x-axis.
  • Aesthetic palette: dark navy background (#0a0e18), card surfaces #111128 with #2a2a4a borders, accent #44ffcc for primary headings and active controls, mobile platform colored orange #ff8844 and desktop colored blue #4488ff.