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: