diff --git a/frontend/components/network-activity-tracker/tps-chart.tsx b/frontend/components/network-activity-tracker/tps-chart.tsx
index 1394b5a..05906ec 100644
--- a/frontend/components/network-activity-tracker/tps-chart.tsx
+++ b/frontend/components/network-activity-tracker/tps-chart.tsx
@@ -1,6 +1,7 @@
'use client'
import Image from 'next/image'
+import { useEffect, useState } from 'react'
import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from 'recharts'
import {
type ChartConfig,
@@ -9,7 +10,7 @@ import {
ChartTooltipContent,
} from '@/components/ui/chart'
import { useTotalTransactions } from '@/hooks/use-total-transactions'
-import { useTps } from '@/hooks/use-tps'
+import { type TpsDataPoint, useTps } from '@/hooks/use-tps'
import { formatRelativeTime, formatTimeHMS } from '@/lib/timestamp'
import { formatIntNumber } from '@/lib/ui'
import { NetworkActivityStats } from './network-activity-stats'
@@ -21,10 +22,96 @@ const chartConfig = {
},
} satisfies ChartConfig
+/** Maximum visible time window of the chart, matching the TPS history retained. */
+const CHART_WINDOW_MS = 5 * 60 * 1000
+
+/** Smallest window to show early on, so the chart starts zoomed in rather than mostly empty. */
+const MIN_WINDOW_MS = 3 * 1000
+
+/**
+ * Drives a smoothly advancing "now" so the chart's x-domain slides
+ * continuously instead of jumping by one slot as each point arrives.
+ * Updates every animation frame for the smoothest motion; rAF auto-pauses
+ * when the tab is hidden.
+ */
+function useSlidingNow(): number {
+ const [now, setNow] = useState(() => Date.now())
+
+ useEffect(() => {
+ let raf: number
+ const tick = () => {
+ setNow(Date.now())
+ raf = requestAnimationFrame(tick)
+ }
+ raf = requestAnimationFrame(tick)
+ return () => cancelAnimationFrame(raf)
+ }, [])
+
+ return now
+}
+
+/**
+ * Returns the history with its newest segment progressively "drawn": instead of
+ * the last point appearing fully-formed, a head vertex travels from the previous
+ * point to the newest one over the inter-arrival interval. `now` advances every
+ * frame, so the head moves smoothly. Once it reaches the newest point the segment
+ * is complete and the next arrival starts drawing the following one.
+ */
+function drawHistory(history: TpsDataPoint[], now: number): TpsDataPoint[] {
+ if (history.length < 2) return history
+
+ const target = history[history.length - 1]
+ const from = history[history.length - 2]
+ const duration = target.timestamp - from.timestamp
+ if (duration <= 0) return history
+
+ const progress = Math.min(1, Math.max(0, (now - target.timestamp) / duration))
+ const head: TpsDataPoint = {
+ timestamp: from.timestamp + (target.timestamp - from.timestamp) * progress,
+ tps: from.tps + (target.tps - from.tps) * progress,
+ }
+
+ return [...history.slice(0, -1), head]
+}
+
+/** Nice, human-friendly tick steps in ms for the relative-time x-axis. */
+const TICK_STEPS_MS = [1, 2, 5, 10, 15, 30, 60, 120, 300].map((s) => s * 1000)
+
+/**
+ * Builds evenly-spaced ticks anchored at the sliding edge (`end`) and stepping
+ * backwards. Anchoring at `end` keeps a single "now" pinned to the far right;
+ * supplying ticks explicitly also avoids recharts' auto-generated ticks landing
+ * both at the edge and at the current second (which both format as "now").
+ */
+function buildTicks(start: number, end: number): number[] {
+ const step =
+ TICK_STEPS_MS.find((s) => s >= (end - start) / 6) ??
+ TICK_STEPS_MS[TICK_STEPS_MS.length - 1]
+
+ const ticks: number[] = []
+ for (let t = end; t >= start; t -= step) {
+ ticks.push(t)
+ }
+ return ticks.reverse()
+}
+
export function TpsChart() {
const { currentTps, peakTps, history } = useTps()
const totalTransactions = useTotalTransactions()
const hasData = history.length > 0
+ const now = useSlidingNow()
+
+ // Start zoomed in to the earliest data point and expand the window as data
+ // accumulates, capping at CHART_WINDOW_MS once we have 5 minutes of history.
+ const earliest = history[0]?.timestamp ?? now
+ const windowStart = Math.max(
+ now - CHART_WINDOW_MS,
+ Math.min(earliest, now - MIN_WINDOW_MS),
+ )
+
+ // Progressively draw the newest segment rather than snapping it into place.
+ const chartData = drawHistory(history, now)
+ const ticks = buildTicks(windowStart, now)
return (
@@ -59,7 +146,7 @@ export function TpsChart() {
className="h-full min-w-2xl w-full p-0"
>
@@ -77,6 +164,11 @@ export function TpsChart() {
= 10'}
- cpu: [arm64]
- os: [darwin]
'@next/swc-darwin-x64@16.2.6':
resolution: {integrity: sha512-v/YLBHIY132Ced3puBJ7YJKw1lqsCrgcNo2aRJlCEyQrrCeRJlvGlnmxhPxNQI3KE3N1DN5r9TPNPvka3nq5RQ==}
- '@next/swc-darwin-x64@16.2.6':
- resolution: {integrity: sha512-v/YLBHIY132Ced3puBJ7YJKw1lqsCrgcNo2aRJlCEyQrrCeRJlvGlnmxhPxNQI3KE3N1DN5r9TPNPvka3nq5RQ==}
- engines: {node: '>= 10'}
- cpu: [x64]
- os: [darwin]
'@next/swc-linux-arm64-gnu@16.2.6':
resolution: {integrity: sha512-RPOvqlYBbcQjkz9VQQDZ2T2bARIjXZV1KFlt+V2Mr6SW/e4I9fcKsaA0hdyf2FHoTlsV2xnBd5Y912rP/1Ce6w==}
- '@next/swc-linux-arm64-gnu@16.2.6':
- resolution: {integrity: sha512-RPOvqlYBbcQjkz9VQQDZ2T2bARIjXZV1KFlt+V2Mr6SW/e4I9fcKsaA0hdyf2FHoTlsV2xnBd5Y912rP/1Ce6w==}
- engines: {node: '>= 10'}
- cpu: [arm64]
- os: [linux]
- libc: [glibc]
- libc: [glibc]
'@next/swc-linux-arm64-musl@16.2.6':
resolution: {integrity: sha512-URUTu1+dMkxJsPFgm+OeEvq9wf5sujw0EvgYy80TDGHTSLTnIHeqb0Eu8A3sC95IRgjejQL+kC4mw+4yPxiAXA==}
- '@next/swc-linux-arm64-musl@16.2.6':
- resolution: {integrity: sha512-URUTu1+dMkxJsPFgm+OeEvq9wf5sujw0EvgYy80TDGHTSLTnIHeqb0Eu8A3sC95IRgjejQL+kC4mw+4yPxiAXA==}
- engines: {node: '>= 10'}
- cpu: [arm64]
- os: [linux]
- libc: [musl]
- libc: [musl]
'@next/swc-linux-x64-gnu@16.2.6':
resolution: {integrity: sha512-DOj182mPV8G3UkrayLoREM5YEYI+Dk5wv7Ox9xl1fFibAELEsFD0lDPfHIeILlutMMfdyhlzYPELG3peuKaurw==}
- '@next/swc-linux-x64-gnu@16.2.6':
- resolution: {integrity: sha512-DOj182mPV8G3UkrayLoREM5YEYI+Dk5wv7Ox9xl1fFibAELEsFD0lDPfHIeILlutMMfdyhlzYPELG3peuKaurw==}
- engines: {node: '>= 10'}
- cpu: [x64]
- os: [linux]
- libc: [glibc]
- libc: [glibc]
'@next/swc-linux-x64-musl@16.2.6':
resolution: {integrity: sha512-HKQ5SP/V/ub73UvF7n/zeJlxk2kLmtL7Wzrg4WfmkjmNos5onJ2tKu7yZOPdL18A6Svfn3max29ym+ry7NkK4g==}
- '@next/swc-linux-x64-musl@16.2.6':
- resolution: {integrity: sha512-HKQ5SP/V/ub73UvF7n/zeJlxk2kLmtL7Wzrg4WfmkjmNos5onJ2tKu7yZOPdL18A6Svfn3max29ym+ry7NkK4g==}
- engines: {node: '>= 10'}
- cpu: [x64]
- os: [linux]
- libc: [musl]
- libc: [musl]
'@next/swc-win32-arm64-msvc@16.2.6':
resolution: {integrity: sha512-LZXpTlPyS5v7HhSmnvsLGP3iIYgYOBnc8r8ArlT55sGHV89bR2HlDdBjWQ+PY6SJMmk8TuVGFuxalnP3k/0Dwg==}
- '@next/swc-win32-arm64-msvc@16.2.6':
- resolution: {integrity: sha512-LZXpTlPyS5v7HhSmnvsLGP3iIYgYOBnc8r8ArlT55sGHV89bR2HlDdBjWQ+PY6SJMmk8TuVGFuxalnP3k/0Dwg==}
- engines: {node: '>= 10'}
- cpu: [arm64]
- os: [win32]
'@next/swc-win32-x64-msvc@16.2.6':
resolution: {integrity: sha512-F0+4i0h9J6C4eE3EAPWsoCk7UW/dbzOjyzxY0qnDUOYFu6FFmdZ6l97/XdV3/Nz3VYyO7UWjyEJUXkGqcoXfMA==}
- '@next/swc-win32-x64-msvc@16.2.6':
- resolution: {integrity: sha512-F0+4i0h9J6C4eE3EAPWsoCk7UW/dbzOjyzxY0qnDUOYFu6FFmdZ6l97/XdV3/Nz3VYyO7UWjyEJUXkGqcoXfMA==}
- engines: {node: '>= 10'}
- cpu: [x64]
- os: [win32]
'@noble/ciphers@1.3.0':
resolution: {integrity: sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==}
@@ -878,7 +812,6 @@ packages:
cpu: [arm64]
os: [linux]
libc: [glibc]
- libc: [glibc]
'@tailwindcss/oxide-linux-arm64-musl@4.1.17':
resolution: {integrity: sha512-HvZLfGr42i5anKtIeQzxdkw/wPqIbpeZqe7vd3V9vI3RQxe3xU1fLjss0TjyhxWcBaipk7NYwSrwTwK1hJARMg==}
@@ -886,7 +819,6 @@ packages:
cpu: [arm64]
os: [linux]
libc: [musl]
- libc: [musl]
'@tailwindcss/oxide-linux-x64-gnu@4.1.17':
resolution: {integrity: sha512-M3XZuORCGB7VPOEDH+nzpJ21XPvK5PyjlkSFkFziNHGLc5d6g3di2McAAblmaSUNl8IOmzYwLx9NsE7bplNkwQ==}
@@ -894,7 +826,6 @@ packages:
cpu: [x64]
os: [linux]
libc: [glibc]
- libc: [glibc]
'@tailwindcss/oxide-linux-x64-musl@4.1.17':
resolution: {integrity: sha512-k7f+pf9eXLEey4pBlw+8dgfJHY4PZ5qOUFDyNf7SI6lHjQ9Zt7+NcscjpwdCEbYi6FI5c2KDTDWyf2iHcCSyyQ==}
@@ -902,7 +833,6 @@ packages:
cpu: [x64]
os: [linux]
libc: [musl]
- libc: [musl]
'@tailwindcss/oxide-wasm32-wasi@4.1.17':
resolution: {integrity: sha512-cEytGqSSoy7zK4JRWiTCx43FsKP/zGr0CsuMawhH67ONlH+T79VteQeJQRO/X7L0juEUA8ZyuYikcRBf0vsxhg==}
@@ -1084,56 +1014,48 @@ packages:
cpu: [arm64]
os: [linux]
libc: [glibc]
- libc: [glibc]
'@unrs/resolver-binding-linux-arm64-musl@1.11.1':
resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==}
cpu: [arm64]
os: [linux]
libc: [musl]
- libc: [musl]
'@unrs/resolver-binding-linux-ppc64-gnu@1.11.1':
resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==}
cpu: [ppc64]
os: [linux]
libc: [glibc]
- libc: [glibc]
'@unrs/resolver-binding-linux-riscv64-gnu@1.11.1':
resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==}
cpu: [riscv64]
os: [linux]
libc: [glibc]
- libc: [glibc]
'@unrs/resolver-binding-linux-riscv64-musl@1.11.1':
resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==}
cpu: [riscv64]
os: [linux]
libc: [musl]
- libc: [musl]
'@unrs/resolver-binding-linux-s390x-gnu@1.11.1':
resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==}
cpu: [s390x]
os: [linux]
libc: [glibc]
- libc: [glibc]
'@unrs/resolver-binding-linux-x64-gnu@1.11.1':
resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==}
cpu: [x64]
os: [linux]
libc: [glibc]
- libc: [glibc]
'@unrs/resolver-binding-linux-x64-musl@1.11.1':
resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==}
cpu: [x64]
os: [linux]
libc: [musl]
- libc: [musl]
'@unrs/resolver-binding-wasm32-wasi@1.11.1':
resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==}
@@ -1275,10 +1197,6 @@ packages:
engines: {node: '>=6.0.0'}
hasBin: true
- baseline-browser-mapping@2.10.24:
- resolution: {integrity: sha512-I2NkZOOrj2XuguvWCK6OVh9GavsNjZjK908Rq3mIBK25+GD8vPX5w2WdxVqnQ7xx3SrZJiCiZFu+/Oz50oSYSA==}
- engines: {node: '>=6.0.0'}
- hasBin: true
baseline-browser-mapping@2.8.32:
resolution: {integrity: sha512-OPz5aBThlyLFgxyhdwf/s2+8ab3OvT7AdTNvKHBwpXomIYeXqpUUuT8LrdtxZSsWJ4R4CU1un4XGh5Ez3nlTpw==}
@@ -2024,7 +1942,6 @@ packages:
cpu: [arm64]
os: [linux]
libc: [glibc]
- libc: [glibc]
lightningcss-linux-arm64-musl@1.30.2:
resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==}
@@ -2032,7 +1949,6 @@ packages:
cpu: [arm64]
os: [linux]
libc: [musl]
- libc: [musl]
lightningcss-linux-x64-gnu@1.30.2:
resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==}
@@ -2040,7 +1956,6 @@ packages:
cpu: [x64]
os: [linux]
libc: [glibc]
- libc: [glibc]
lightningcss-linux-x64-musl@1.30.2:
resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==}
@@ -2048,7 +1963,6 @@ packages:
cpu: [x64]
os: [linux]
libc: [musl]
- libc: [musl]
lightningcss-win32-arm64-msvc@1.30.2:
resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==}
@@ -2137,26 +2051,6 @@ packages:
next@16.2.6:
resolution: {integrity: sha512-qOVgKJg1+At15NpeUP+eJgCHvTCgXsogweq87Ri/Ix7PkqQHg4sdaXmSFqKlgaIXE4kW0g25LE68W87UANlHtw==}
- next@16.2.6:
- resolution: {integrity: sha512-qOVgKJg1+At15NpeUP+eJgCHvTCgXsogweq87Ri/Ix7PkqQHg4sdaXmSFqKlgaIXE4kW0g25LE68W87UANlHtw==}
- engines: {node: '>=20.9.0'}
- hasBin: true
- peerDependencies:
- '@opentelemetry/api': ^1.1.0
- '@playwright/test': ^1.51.1
- babel-plugin-react-compiler: '*'
- react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0
- react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0
- sass: ^1.3.0
- peerDependenciesMeta:
- '@opentelemetry/api':
- optional: true
- '@playwright/test':
- optional: true
- babel-plugin-react-compiler:
- optional: true
- sass:
- optional: true
node-releases@2.0.27:
resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==}
@@ -2946,41 +2840,32 @@ snapshots:
optional: true
'@next/env@16.2.6': {}
- '@next/env@16.2.6': {}
'@next/eslint-plugin-next@16.0.6':
dependencies:
fast-glob: 3.3.1
- '@next/swc-darwin-arm64@16.2.6':
'@next/swc-darwin-arm64@16.2.6':
optional: true
- '@next/swc-darwin-x64@16.2.6':
'@next/swc-darwin-x64@16.2.6':
optional: true
- '@next/swc-linux-arm64-gnu@16.2.6':
'@next/swc-linux-arm64-gnu@16.2.6':
optional: true
- '@next/swc-linux-arm64-musl@16.2.6':
'@next/swc-linux-arm64-musl@16.2.6':
optional: true
- '@next/swc-linux-x64-gnu@16.2.6':
'@next/swc-linux-x64-gnu@16.2.6':
optional: true
- '@next/swc-linux-x64-musl@16.2.6':
'@next/swc-linux-x64-musl@16.2.6':
optional: true
- '@next/swc-win32-arm64-msvc@16.2.6':
'@next/swc-win32-arm64-msvc@16.2.6':
optional: true
- '@next/swc-win32-x64-msvc@16.2.6':
'@next/swc-win32-x64-msvc@16.2.6':
optional: true
@@ -3661,7 +3546,6 @@ snapshots:
baseline-browser-mapping@2.10.24: {}
- baseline-browser-mapping@2.10.24: {}
baseline-browser-mapping@2.8.32: {}
@@ -4613,25 +4497,15 @@ snapshots:
next@16.2.6(@babel/core@7.28.5)(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
dependencies:
- '@next/env': 16.2.6
'@next/env': 16.2.6
'@swc/helpers': 0.5.15
baseline-browser-mapping: 2.10.24
- baseline-browser-mapping: 2.10.24
caniuse-lite: 1.0.30001759
postcss: 8.4.31
react: 19.2.6
react-dom: 19.2.6(react@19.2.6)
styled-jsx: 5.1.6(@babel/core@7.28.5)(react@19.2.6)
optionalDependencies:
- '@next/swc-darwin-arm64': 16.2.6
- '@next/swc-darwin-x64': 16.2.6
- '@next/swc-linux-arm64-gnu': 16.2.6
- '@next/swc-linux-arm64-musl': 16.2.6
- '@next/swc-linux-x64-gnu': 16.2.6
- '@next/swc-linux-x64-musl': 16.2.6
- '@next/swc-win32-arm64-msvc': 16.2.6
- '@next/swc-win32-x64-msvc': 16.2.6
'@next/swc-darwin-arm64': 16.2.6
'@next/swc-darwin-x64': 16.2.6
'@next/swc-linux-arm64-gnu': 16.2.6
diff --git a/frontend/pnpm-workspace.yaml b/frontend/pnpm-workspace.yaml
index 56dbf93..b0c9078 100644
--- a/frontend/pnpm-workspace.yaml
+++ b/frontend/pnpm-workspace.yaml
@@ -1,3 +1,7 @@
+allowBuilds:
+ sharp: true
+ unrs-resolver: true
+
minimumReleaseAge: 10080
minimumReleaseAgeExclude: