Serve the dashboard
The dashboard is a Next.js app that reads from the collector’s query API and renders heatmaps, mesh rankings, performance panels, and session replay.
Run it locally
Section titled “Run it locally”pnpm dev:dashboardThe dashboard authenticates to the collector with a project API key. Configure it via the
environment (e.g. NEXT_PUBLIC_API_KEY and the collector URL) so the browser can call the read API
with x-api-key. The key only ever reads its own project’s data.
What it shows
Section titled “What it shows”- View-direction & pointer heatmaps — 2D panels and full 3D against a scene proxy backdrop.
- View coverage — a 360° coverage gauge that buckets sessions by how much of the object they actually looked at (0–25 / 25–50 / 50–75 / 75–100%), derived from the same camera view-direction grid as the dome — the “did they rotate it to see the back?” number.
- Flow Sankey — gaze→click flow links rendered in 3D, with position-aware standpoint gating, a two-stage (standpoint → gaze → mesh) view, and a camera-mode toggle (see below).
- Mesh rankings — most-interacted meshes, a part-popularity leaderboard (trend sparkline + per-mesh input-source split, scoped to active interactions so passive gaze doesn’t inflate the ranking), interaction-kind breakdown, object dwell, hover hesitation, and a blind-spots report (meshes rendered but never noticed — high visibility, little engagement; #143).
- Configurator variants — a variant → conversion leaderboard ranking
customvariant events by views, with dwell and an in-panel picker for per-variant conversion rate (see below). - Input — an input-modality split (mouse/touch/keyboard/gamepad/XR share) with the most-used app-level shortcuts.
- Performance — FPS/frame-time summaries with percentile aggregation, a performance distribution histogram (p05/p50/p95 bands), compile stalls, resource footprint.
- Navigation & XR — camera gestures, coverage and a dead-zone report (proxy meshes visitors
never approach), XR rotation and source usage, a VR comfort & locomotion panel (teleport
vs. smooth-locomotion vs. navigate mix for XR sessions, correlated with early exits — a
motion-discomfort proxy), a guardian boundary-contacts panel (per-session near-boundary
approach counts + near-zone time — a room-scale comfort signal; #157), and a tracking quality
panel (share of XR session time spent with degraded or lost spatial tracking, split by hand vs.
controller — from
capability_changetrackingtransitions). - Scene retention funnel — a canned level-retention preset: session counts flowing scene → scene
in observed order (from
scene_changemarkers), with drop-off visible per source scene and no funnel steps to author (#147). - Error heatmap (3D) — voxel-binned world position of positioned
runtime_error+graphics_diagnosticevents against the scene proxy backdrop, so you can see where in the scene things break. Empty is the healthy case; only errors carrying a best-effort camera position appear. - Boundary-touch heatmap (3D) — voxel-binned world position of
xr_boundary_proximityevents against the scene proxy backdrop, so you can see where room-scale VR visitors reach their play-space boundary (#157). The boundary polygon / room geometry is never transmitted — only the on-device outcome (coarse position + duration) per approach. - Session replay — re-drive a recorded session inside a scene (requires raw retention; see below).
- Analytics assistant — an in-browser LLM assistant (an Ask the assistant card on the overview) that answers natural-language questions of the connected project, grounded in read-only tool calls against the same query API. It runs locally on your GPU (WebLLM) or via your own hosted key, and is fully code-split — the panel and model runtime load only when you open it, so the main bundle is unchanged otherwise. See the assistant guide.
Flow Sankey panel
Section titled “Flow Sankey panel”The Flow Sankey panel visualizes how viewers move from where they look to what they click, in 3D.
It reads GET /api/v1/heatmaps/flow and exposes three controls:
- Standpoints — when the scene is walkable, clicks are binned by the camera standpoint they
were made from (position-aware mode,
groupByOrigin). Pick a standpoint to gate the flow to clicks made from that spot, or keep All to collapse origins together. A standpoint marker is drawn at the averaged camera position so you can see where the audience stood. - Aggregate / Two-stage — Aggregate draws direct gaze→mesh ribbons; Two-stage inserts the standpoint as a first column (standpoint → gaze → mesh) so you can read the full path.
- Camera mode (Walk / Orbit / All) — restrict to free/walkable (
first-person) or orbit (viewer) sessions, or show both. The panel defaults to Walk when the scene has first-person data, and shows an “orbit-dominated” hint linking to the view-direction dome when it doesn’t.
Part-popularity leaderboard panel
Section titled “Part-popularity leaderboard panel”The Part-popularity leaderboard (#74) ranks meshes by how often people act on them, and adds
two things the plain Mesh-rankings list doesn’t: a per-row trend sparkline (rising/falling over
the active range) and an input-source split (mouse / touch / keyboard / gamepad / XR) you can
expand per row. It reads GET /api/v1/meshes/sources for the source split and
GET /api/v1/meshes/trend for the sparkline.
Variant → conversion leaderboard panel
Section titled “Variant → conversion leaderboard panel”The Variant → conversion leaderboard (#150) is built for 3D product configurators: the
colour, material, and trim swaps a scene emits as custom events. It ranks each variant (a
custom event grouped by its name) by views, and shows distinct sessions and the mean
dwell before the shopper switched variant or converted. It reads
GET /api/v1/variant-leaderboard.
Pick a success event from the in-panel dropdown — its options are the variant names discovered
in the current range — to reveal each variant’s conversion rate: the share of sessions that
fired the success event at or after first viewing that variant. Until one is chosen, the conversion
column shows —. Nothing is authored or persisted; the variant and success events are the ones your
scene already emits, so there is no schema change.
Dead-zone report panel
Section titled “Dead-zone report panel”The Dead-zone report (#76) surfaces the negative space of a walkable scene: registered proxy meshes that visitors’ cameras never approached. It answers “which parts of my scene is nobody getting near?” — the inverse of the coverage heatmap.
How it works. The panel reads two things and intersects them in the browser (no extra server join):
- Scene coverage —
GET /api/v1/coveragereturns the occupied camera-position voxels (a 1 world-unit grid) with a visitcounteach: where the audience actually stood. - The registered scene proxy — the per-mesh bounding boxes (AABBs) from
GET /api/v1/scenes/:id/representation(ADR 0014): the geometry that could be visited.
For each proxy mesh it sums the visit counts of every coverage voxel whose centre falls inside that mesh’s AABB, padded outward by one cell so “standing right next to it” still counts. That sum is the mesh’s nearby-samples score. Meshes are ranked coldest first; a mesh with zero nearby camera samples is flagged dead. The header reads e.g. “5 of 42 proxy meshes are dead” — meaning 5 registered meshes had no camera-position voxel land within a padded cell of their bounds over the selected range.
The panel needs both a walkable/first-person scene (so there are camera-position samples to bin)
and a registered proxy (so there’s reference geometry to call dead). It renders a graceful
empty-state when no proxy is registered, and a “widen the range” hint when there are no camera
samples in range. See Scene proxy for
registering a proxy with scanSceneProxy.
Backtracking hotspots panel
Section titled “Backtracking hotspots panel”The Backtracking hotspots panel (#153) ranks scenes/areas by how often visitors re-walk the same area — a confusion signal that desire lines alone don’t show. A player who keeps returning to the same spot is a strong hint of a dead end, a missed cue, or a puzzle that isn’t reading clearly.
How it works. It reads GET /api/v1/backtrack, which reuses the same camera_sample position
stream that feeds desire lines. Each session’s positions are binned onto a coarse X/Z grid
(cellSize, default 2 world units); consecutive samples in one cell are collapsed into a single
cell entry, so standing still or dwelling never counts. Re-entering a cell you had already left is
a revisit. Per scene the panel pools backtrack_ratio = revisits ÷ entries across sessions and
shows the ratio (bar + percent) alongside the raw revisits / entries and session count, worst
first.
This is the coarse-grid revisit proxy — a deliberately cheap first cut, not true reverse-segment
path retracing. Tune cellSize to your scene: a larger cell only flags returning to a broad area,
a smaller cell catches tighter out-and-back loops.
Reachability report panel
Section titled “Reachability report panel”The Reachability report (#151) answers “was anything interactive placed out of comfortable reach?” — critical for VR/first-person UIs where a panel or button can end up too far from where the visitor actually stood.
How it works. It reads GET /api/v1/meshes/reachability, which ASOF-joins each
mesh_interaction world hit-point to the nearest preceding camera_sample in the same session
and bins the standpoint→interaction distance into bucketSize-wide world-unit bands per mesh
(default 0.5). The panel folds each mesh’s bands into a count-weighted mean distance, ranks
meshes farthest-mean first, and flags any whose mean clears a comfortable-reach threshold (≈2
world units by default) so out-of-reach UI stands out.
Build for production
Section titled “Build for production”pnpm --filter @uptimizr/dashboard buildHost it wherever you run Next.js. Point it at your collector’s public URL and provide the project API key through the environment. Because reads are scoped to the key’s project, you can run one dashboard per project or switch keys per environment.
Replay prerequisites
Section titled “Replay prerequisites”Session replay reads the raw, ordered event stream from /api/v1/sessions/:id/events, which is
gated behind ENABLE_RAW_SESSION_RETENTION on the collector. Enable it only if you
intend to retain raw per-session events — see Privacy & configuration.