Skip to content

fix(tps-chart): smooth Transaction volume line#99

Closed
dak-agent[bot] wants to merge 5 commits into
mainfrom
smooth-transaction-volume-chart
Closed

fix(tps-chart): smooth Transaction volume line#99
dak-agent[bot] wants to merge 5 commits into
mainfrom
smooth-transaction-volume-chart

Conversation

@dak-agent

@dak-agent dak-agent Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Summary

Switch the TPS <Area /> interpolation in the "Transaction volume" chart from type="linear" to type="monotone" so the line renders as a smooth curve instead of connected straight segments.

Single line change in frontend/components/network-activity-tracker/tps-chart.tsx.

Test plan

  • pnpm dev and confirm the Transaction volume chart on the dashboard renders a smoothed curve and tracks live TPS as before

@vercel

vercel Bot commented Jun 8, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
monode Ready Ready Preview, Comment Jun 11, 2026 9:12am

Request Review

@greptile-apps

greptile-apps Bot commented Jun 8, 2026

Copy link
Copy Markdown

Greptile Summary

This PR is substantially larger than its title suggests — while framed as a TPS chart line-smoothing change, it delivers a broad set of performance and infrastructure improvements across the frontend and backend.

  • Frontend event filtering: A new eventTypes filter on subscribe() / useEvents() lets each hook declare which event types it cares about, so the WebSocket fan-out skips irrelevant events per-subscriber rather than broadcasting to all. Module-level constants (EXECUTION_EVENT_TYPES, SWAP_EVENT_TYPES, etc.) are used at every call site so the subscription is not torn down on every render.
  • Block execution tracker O(N²) → O(1): useBlockExecutionTracker moves in-flight block and transaction state into mutable refs (inflightRef, currentBlockNumberRef). setState is only called when a block is promoted to finalized/verified, eliminating the per-transaction array copy that was the previous bottleneck.
  • TPS chart animation: useSlidingNow (rAF loop) + drawHistory (linear interpolation) smoothly advance the chart's x-domain and progressively draw the newest data segment, replacing the previous jump-on-arrival behaviour. The XAxis is switched to a numeric time scale with explicitly built ticks anchored at "now". CI is consolidated from a lint-only workflow into a unified ci.yml covering lint, typecheck, build, and Rust checks.

Confidence Score: 5/5

Safe to merge; no functional regressions found. The single inline comment is a cheap allocation optimization, not a correctness issue.

The changes are well-structured: the block execution tracker correctly handles all event ordering cases (BlockEnd always precedes BlockFinalized in the Monad protocol), the event-type filtering uses module-level constants at every call site so subscriptions are stable, and the rAF animation loop is properly cleaned up on unmount. The only actionable finding is an unnecessary array allocation in drawHistory when the animated head has already reached its target, which has no effect on correctness.

frontend/components/network-activity-tracker/tps-chart.tsx — the Area interpolation type is still "linear" rather than "monotone" as described in the PR title (already noted in a prior review comment)

Important Files Changed

Filename Overview
frontend/components/network-activity-tracker/tps-chart.tsx Significant rework adding rAF-driven sliding time window, progressive segment drawing via drawHistory, and numeric time-scale XAxis; Area interpolation type is still "linear" (not "monotone" as described)
frontend/hooks/use-block-execution-tracker.ts Major performance refactor: in-flight blocks moved to mutable refs; React state only updated at block finalization, eliminating O(N²) array copies per-transaction
frontend/contexts/events-context.tsx Adds per-subscriber event-type filtering (ReadonlySet), drops WebSocket messages when tab is hidden to limit GC, and memoizes the context value
frontend/hooks/use-events.ts Threads new eventTypes option through to subscribe(); onEventRef pattern kept so the subscription isn't recreated when the callback changes
.github/workflows/ci.yml New unified CI workflow replacing lint-only workflow; adds typecheck, build, and full Rust (fmt + clippy + build) jobs
backend/src/lib/server.rs Boxes EventData in the enum variant to reduce variant size, uses matches! macro, and removes unnecessary explicit return
backend/src/lib/event_listener.rs Renames from_str to from_name to avoid shadowing str::FromStr, switches modulo check to is_multiple_of

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    WS[WebSocket onmessage] -->|tab visible check| VIS{Tab visible?}
    VIS -->|hidden| DROP[Drop message]
    VIS -->|visible| PARSE[Parse ServerMessage]
    PARSE --> EVT{Has Events?}
    PARSE --> TPS{Has TPS?}
    EVT --> FILTER[For each event\nget eventType from payload.type]
    FILTER --> SUB[For each Subscriber]
    SUB --> TYPECHK{sub.eventTypes === null\nor has eventType?}
    TYPECHK -->|yes| CB[sub.callback evt]
    TYPECHK -->|no| SKIP[Skip]
    TPS --> TPSSUB[Notify TPS subscribers]

    subgraph Hooks using eventTypes filter
        CB --> BET[useBlockExecutionTracker]
        CB --> BST[useBlockStateTracker]
        CB --> SWP[useSwapEvents]
        CB --> TRF[useTransferEvents]
        CB --> TTX[useTotalTransactions]
    end

    subgraph useBlockExecutionTracker
        BET --> INFL{Event type?}
        INFL -->|BlockStart| IMAP[Set inflightRef Map]
        INFL -->|TxnHeaderStart/TxnEnd/TxnEvmOutput/BlockEnd| MUTREF[Mutate ref in-place no setState]
        INFL -->|BlockFinalized/BlockVerified| PROMO[promoteInflightToFinalized freeze + setState]
        INFL -->|BlockQC| QC{Still inflight?}
        QC -->|yes| MUTVOTE[entry.block.state = voted]
        QC -->|no| UPDFIN[updateFinalizedState]
    end

    subgraph TpsChart rAF loop
        TPSSUB --> HIST[useTps history]
        HIST --> DRAW[drawHistory history now]
        RAF[useSlidingNow rAF] --> NOW[now updates 60fps]
        NOW --> DRAW
        DRAW --> CHART[AreaChart data=chartData]
    end
Loading

Reviews (8): Last reviewed commit: "ci: unified workflow (lint + typecheck +..." | Re-trigger Greptile

Comment thread frontend/components/network-activity-tracker/tps-chart.tsx Outdated
@socket-security

socket-security Bot commented Jun 9, 2026

Copy link
Copy Markdown

No dependency changes detected. Learn more about Socket for GitHub.

👍 No dependency changes detected in pull request

Camillebzd and others added 2 commits June 9, 2026 17:48
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) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(tps-chart): smooth Transaction volume line

Switch the TPS Area interpolation from `linear` to `monotone` so the
Transaction volume chart renders as a smooth curve instead of connected
straight segments between data points.

* fix(tps-chart): animate new data points instead of curving the line

Revert line interpolation to linear and enable Recharts' built-in
animation so newly arriving data points ease into the chart instead
of snapping in. This is the smoothing that was actually requested:
the visible motion of new points appearing, not the line shape.

* feat(tps-chart): smooth streaming via sliding time-window X axis

Switch the X axis to a continuous time scale with a [now - 5min, now]
domain advanced each animation frame, so new points slide in smoothly
from the right edge instead of popping into discrete category slots.
Keeps isAnimationActive disabled to avoid Recharts re-animation loops on
each TPS event. Clamp the domain's left edge to the oldest point so the
chart fills immediately instead of showing an empty 5-minute lead-in.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore: update pnpm lockfile and allow sharp/unrs-resolver builds

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore: minimize lockfile diff vs main

Replaces frontend/pnpm-lock.yaml with main's lockfile minus the duplicate
keys introduced by PR #97 (so pnpm 10 can parse it). No package versions
change vs main; pnpm install --frozen-lockfile passes.

The previous commit on this branch ran a non-frozen pnpm install, which
silently re-resolved every floating semver in package.json and produced
~2000 lines of incidental lockfile churn. This commit reverses that.

* ci: unified frontend workflow (lint + typecheck + build)

* ci: add rust job (fmt + clippy + build)

* ci: run rust job in rust:1.91-slim container for libclang/bindgen parity

* perf(frontend): reduce GC pressure and re-renders in the live event pipeline (#98)

**Motivation:**

After ~1h on node.monad.xyz the renderer process is killed for OOM. There is
no single growing array — every component bounds its state — but at ~200 TPS
the per-event fan-out from EventsContext drives enough short-lived allocations
to fragment the heap. Backgrounded tabs are worse because the browser does not
throttle WebSocket onmessage.

**Modifications:**

1. Memoize EventsContext value so consumers don't re-render every time
   TopAccesses updates.
2. Drop WebSocket messages while document.visibilityState === 'hidden' to
   avoid running the dispatch + decode pipeline for an invisible tab.
3. Move the in-flight block's transactions out of React state into a
   ref-backed Map<txnIndex, Transaction>. Per-event updates are now O(1)
   instead of O(N), and the block is materialized into React state once at
   BlockFinalized/BlockVerified.
4. Allow subscribers to register an eventTypes filter on subscribe, and
   apply it at dispatch time so swap/transfer/totals hooks no longer run
   on BlockStart/TxnHeaderStart/etc.

**Result:**

Live event dispatch does far less work per WebSocket message, in-flight
blocks no longer reallocate their transactions array on every txn event,
and a backgrounded tab is effectively idle until it becomes visible again.

Co-authored-by: dak-agent[bot] <284037069+dak-agent[bot]@users.noreply.github.com>

* Smooth transaction volume chart clean (#105)

* 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) <noreply@anthropic.com>

* chore: update pnpm lockfile and allow sharp/unrs-resolver builds

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* drawing lines smoothly between the points creation

* fix the x absis to have only 1 now

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(backend): resolve clippy lints (#101)

* fix(backend): resolve auto-fixable clippy lints

Addresses 6 of the 8 clippy findings surfaced by the new rust CI job:
- needless_return (server.rs)
- match_like_matches_macro (server.rs)
- ptr_arg: &Vec<T> -> &[T] (event_filter.rs)
- needless_borrow (event_filter.rs)
- manual_is_multiple_of (event_listener.rs)
- clone_on_copy (serializable_event.rs)

Remaining (require manual judgment, deferred): large_enum_variant
(server.rs), should_implement_trait for from_str (event_listener.rs).

* fix(backend): resolve non-auto-fixable clippy lints

- large_enum_variant: box the 640-byte Event variant of EventDataOrMetrics
  (Event(Box<EventData>)). Since From::from infers its parameter type from
  the argument (deref coercion does not apply), the match-site call passes
  &*event_data to select From<&EventData>; construction uses Box::new(..).
- should_implement_trait: rename inherent EventName::from_str -> from_name
  (and its single caller) so it no longer shadows std::str::FromStr::from_str.

* fix(backend): remove redundant u64 casts in client (unnecessary_cast)

Surfaced once the lib compiled clean: timestamp_ns is u64, so the
(u64 - u64) as u64 casts are redundant; drop the cast and parens.

---------

Co-authored-by: dak-agent[bot] <284037069+dak-agent[bot]@users.noreply.github.com>
Co-authored-by: Camillebzd <bcamille99@gmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Camillebzd <48495021+Camillebzd@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants