From 589b1f0f46b14e5d0689e5f90788c7b83b9570d5 Mon Sep 17 00:00:00 2001 From: Camillebzd Date: Tue, 9 Jun 2026 17:48:50 +0100 Subject: [PATCH 1/4] feat(tps-chart): smoothly extend the line to each new point Switch the X axis to a continuous time scale whose right edge eases toward the newest point's timestamp each animation frame. A freshly appended point sits just past the edge (clipped by allowDataOverflow) and is revealed sliding in from the right instead of snapping into a discrete category slot; the animation halts once the edge catches up, so the chart is still between points. isAnimationActive stays disabled to avoid Recharts re-animation loops on every TPS event. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../network-activity-tracker/tps-chart.tsx | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/frontend/components/network-activity-tracker/tps-chart.tsx b/frontend/components/network-activity-tracker/tps-chart.tsx index 1394b5a..a53339e 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, useRef, useState } from 'react' import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from 'recharts' import { type ChartConfig, @@ -21,10 +22,40 @@ const chartConfig = { }, } satisfies ChartConfig +/** Visible time window, matching the TPS history kept by useTps */ +const WINDOW_MS = 5 * 60 * 1000 + export function TpsChart() { const { currentTps, peakTps, history } = useTps() const totalTransactions = useTotalTransactions() const hasData = history.length > 0 + const latest = hasData ? history[history.length - 1].timestamp : 0 + + // The X axis is a continuous time scale whose right boundary, `edge`, eases + // toward the newest point's timestamp. When a point is appended, `edge` lags + // behind it and catches up over a few frames; the freshly added segment lives + // just past the boundary (clipped by allowDataOverflow) and is revealed + // sliding in from the right rather than snapping into place. The loop stops + // once `edge` reaches `latest`, so the chart is still between points. + const [edge, setEdge] = useState(latest) + const edgeRef = useRef(latest) + useEffect(() => { + let frame: number + const animate = () => { + const diff = latest - edgeRef.current + // Close 15% of the remaining gap each frame: fast start, gentle settle. + if (Math.abs(diff) < 1) { + edgeRef.current = latest + setEdge(latest) + return + } + edgeRef.current += diff * 0.15 + setEdge(edgeRef.current) + frame = requestAnimationFrame(animate) + } + frame = requestAnimationFrame(animate) + return () => cancelAnimationFrame(frame) + }, [latest]) return (
@@ -77,6 +108,10 @@ export function TpsChart() { Date: Tue, 9 Jun 2026 17:48:50 +0100 Subject: [PATCH 2/4] chore: update pnpm lockfile and allow sharp/unrs-resolver builds Co-Authored-By: Claude Opus 4.8 (1M context) --- frontend/pnpm-lock.yaml | 126 ----------------------------------- frontend/pnpm-workspace.yaml | 4 ++ 2 files changed, 4 insertions(+), 126 deletions(-) diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 29937d9..6284395 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -274,56 +274,48 @@ packages: cpu: [arm64] os: [linux] libc: [glibc] - libc: [glibc] '@img/sharp-libvips-linux-arm@1.2.4': resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} cpu: [arm] os: [linux] libc: [glibc] - libc: [glibc] '@img/sharp-libvips-linux-ppc64@1.2.4': resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} cpu: [ppc64] os: [linux] libc: [glibc] - libc: [glibc] '@img/sharp-libvips-linux-riscv64@1.2.4': resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} cpu: [riscv64] os: [linux] libc: [glibc] - libc: [glibc] '@img/sharp-libvips-linux-s390x@1.2.4': resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} cpu: [s390x] os: [linux] libc: [glibc] - libc: [glibc] '@img/sharp-libvips-linux-x64@1.2.4': resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} cpu: [x64] os: [linux] libc: [glibc] - libc: [glibc] '@img/sharp-libvips-linuxmusl-arm64@1.2.4': resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} cpu: [arm64] os: [linux] libc: [musl] - libc: [musl] '@img/sharp-libvips-linuxmusl-x64@1.2.4': resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} cpu: [x64] os: [linux] libc: [musl] - libc: [musl] '@img/sharp-linux-arm64@0.34.5': resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} @@ -331,7 +323,6 @@ packages: cpu: [arm64] os: [linux] libc: [glibc] - libc: [glibc] '@img/sharp-linux-arm@0.34.5': resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} @@ -339,7 +330,6 @@ packages: cpu: [arm] os: [linux] libc: [glibc] - libc: [glibc] '@img/sharp-linux-ppc64@0.34.5': resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} @@ -347,7 +337,6 @@ packages: cpu: [ppc64] os: [linux] libc: [glibc] - libc: [glibc] '@img/sharp-linux-riscv64@0.34.5': resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} @@ -355,7 +344,6 @@ packages: cpu: [riscv64] os: [linux] libc: [glibc] - libc: [glibc] '@img/sharp-linux-s390x@0.34.5': resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} @@ -363,7 +351,6 @@ packages: cpu: [s390x] os: [linux] libc: [glibc] - libc: [glibc] '@img/sharp-linux-x64@0.34.5': resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} @@ -371,7 +358,6 @@ packages: cpu: [x64] os: [linux] libc: [glibc] - libc: [glibc] '@img/sharp-linuxmusl-arm64@0.34.5': resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} @@ -379,7 +365,6 @@ packages: cpu: [arm64] os: [linux] libc: [musl] - libc: [musl] '@img/sharp-linuxmusl-x64@0.34.5': resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} @@ -387,7 +372,6 @@ packages: cpu: [x64] os: [linux] libc: [musl] - libc: [musl] '@img/sharp-wasm32@0.34.5': resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} @@ -431,8 +415,6 @@ packages: '@napi-rs/wasm-runtime@0.2.12': resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} - '@next/env@16.2.6': - resolution: {integrity: sha512-gd8HoHN4ufj73WmR3JmVolrpJR47ILK6LouP5xElPglaVxir6e1a7VzvTvDWkOoPXT9rkkTzyCxBu4yeZfZwcw==} '@next/env@16.2.6': resolution: {integrity: sha512-gd8HoHN4ufj73WmR3JmVolrpJR47ILK6LouP5xElPglaVxir6e1a7VzvTvDWkOoPXT9rkkTzyCxBu4yeZfZwcw==} @@ -441,75 +423,27 @@ packages: '@next/swc-darwin-arm64@16.2.6': resolution: {integrity: sha512-ZJGkkcNfYgrrMkqOdZ7zoLa1TOy0qpcMfk/z4Mh/FKUz40gVO+HNQWqmLxf67Z5WB64DRp0dhEbyHfel+6sJUg==} - '@next/swc-darwin-arm64@16.2.6': - resolution: {integrity: sha512-ZJGkkcNfYgrrMkqOdZ7zoLa1TOy0qpcMfk/z4Mh/FKUz40gVO+HNQWqmLxf67Z5WB64DRp0dhEbyHfel+6sJUg==} - engines: {node: '>= 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: From 2300a6a867dedba597ec514b7a4471d9f5c6a8f0 Mon Sep 17 00:00:00 2001 From: Camillebzd Date: Tue, 9 Jun 2026 19:07:31 +0100 Subject: [PATCH 3/4] drawing lines smoothly between the points creation --- .../network-activity-tracker/tps-chart.tsx | 102 ++++++++++++------ 1 file changed, 68 insertions(+), 34 deletions(-) diff --git a/frontend/components/network-activity-tracker/tps-chart.tsx b/frontend/components/network-activity-tracker/tps-chart.tsx index a53339e..3ce2082 100644 --- a/frontend/components/network-activity-tracker/tps-chart.tsx +++ b/frontend/components/network-activity-tracker/tps-chart.tsx @@ -1,7 +1,7 @@ 'use client' import Image from 'next/image' -import { useEffect, useRef, useState } from 'react' +import { useEffect, useState } from 'react' import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from 'recharts' import { type ChartConfig, @@ -10,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' @@ -22,40 +22,74 @@ const chartConfig = { }, } satisfies ChartConfig -/** Visible time window, matching the TPS history kept by useTps */ -const WINDOW_MS = 5 * 60 * 1000 +/** 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] +} export function TpsChart() { const { currentTps, peakTps, history } = useTps() const totalTransactions = useTotalTransactions() const hasData = history.length > 0 - const latest = hasData ? history[history.length - 1].timestamp : 0 - - // The X axis is a continuous time scale whose right boundary, `edge`, eases - // toward the newest point's timestamp. When a point is appended, `edge` lags - // behind it and catches up over a few frames; the freshly added segment lives - // just past the boundary (clipped by allowDataOverflow) and is revealed - // sliding in from the right rather than snapping into place. The loop stops - // once `edge` reaches `latest`, so the chart is still between points. - const [edge, setEdge] = useState(latest) - const edgeRef = useRef(latest) - useEffect(() => { - let frame: number - const animate = () => { - const diff = latest - edgeRef.current - // Close 15% of the remaining gap each frame: fast start, gentle settle. - if (Math.abs(diff) < 1) { - edgeRef.current = latest - setEdge(latest) - return - } - edgeRef.current += diff * 0.15 - setEdge(edgeRef.current) - frame = requestAnimationFrame(animate) - } - frame = requestAnimationFrame(animate) - return () => cancelAnimationFrame(frame) - }, [latest]) + 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) return (
@@ -90,7 +124,7 @@ export function TpsChart() { className="h-full min-w-2xl w-full p-0" > @@ -110,8 +144,8 @@ export function TpsChart() { dataKey="timestamp" type="number" scale="time" - domain={[Math.max(edge - WINDOW_MS, history[0].timestamp), edge]} - allowDataOverflow + domain={[windowStart, now]} + allowDataOverflow={true} tickLine={false} axisLine={false} tickMargin={8} From 1f55e3a2adb82aa3229ff19f2396976f319fc301 Mon Sep 17 00:00:00 2001 From: Camillebzd Date: Tue, 9 Jun 2026 19:15:38 +0100 Subject: [PATCH 4/4] fix the x absis to have only 1 now --- .../network-activity-tracker/tps-chart.tsx | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/frontend/components/network-activity-tracker/tps-chart.tsx b/frontend/components/network-activity-tracker/tps-chart.tsx index 3ce2082..05906ec 100644 --- a/frontend/components/network-activity-tracker/tps-chart.tsx +++ b/frontend/components/network-activity-tracker/tps-chart.tsx @@ -74,6 +74,27 @@ function drawHistory(history: TpsDataPoint[], now: number): TpsDataPoint[] { 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() @@ -90,6 +111,7 @@ export function TpsChart() { // Progressively draw the newest segment rather than snapping it into place. const chartData = drawHistory(history, now) + const ticks = buildTicks(windowStart, now) return (
@@ -145,6 +167,7 @@ export function TpsChart() { type="number" scale="time" domain={[windowStart, now]} + ticks={ticks} allowDataOverflow={true} tickLine={false} axisLine={false}