diff --git a/frontend/app/globals.css b/frontend/app/globals.css index e1631ed..63f4771 100644 --- a/frontend/app/globals.css +++ b/frontend/app/globals.css @@ -127,6 +127,16 @@ --color-tracker-badge-bg: #2a233a; --color-tracker-active: #836ef9; + /* Block time tracker colors - new design */ + --color-block-exec-bar: #38bdf8; + --color-tx-exec-bar: #6e54ff; + --color-text-secondary-new: #a8a3b8; + --color-text-muted-new: #52525e; + --color-bg-primary: #0e100f; + --color-bg-secondary: #18181b; + --color-border-primary: #27272a; + --color-border-active: #e5e5e5; + /* Block state colors */ --color-block-proposed: #342b6f; --color-block-voted: #6e54ff; diff --git a/frontend/app/page.tsx b/frontend/app/page.tsx index f606ddc..98455c5 100644 --- a/frontend/app/page.tsx +++ b/frontend/app/page.tsx @@ -9,18 +9,21 @@ import { SwapTransferTracker } from '@/components/swap-transfer-tracker' export default function Home() { return (
-
+
- + {/* Sections container with continuous left/right borders */} +
+ - + - + - + +
-
+
diff --git a/frontend/components/block-state-tracker/index.tsx b/frontend/components/block-state-tracker/index.tsx index 6a5d08f..3abfa1c 100644 --- a/frontend/components/block-state-tracker/index.tsx +++ b/frontend/components/block-state-tracker/index.tsx @@ -1,14 +1,14 @@ 'use client' -import { Info, Pause, Play } from 'lucide-react' +import { Pointer } from 'lucide-react' import { ExternalLink } from '@/components/ui/external-link' import { SectionHeader } from '@/components/ui/section-header' +import { Switch } from '@/components/ui/switch' import { BLOCK_STATE_LEGEND } from '@/constants/block-state' import { useBlockStateTracker } from '@/hooks/use-block-state-tracker' import { useMouseHover } from '@/hooks/use-mouse-hover' import { cn } from '@/lib/utils' import { Blockchain } from './blockchain' -import { SlowMotionControl } from './slow-motion-control' /** * Visualizes the blockchain with blocks progressing through states: @@ -18,7 +18,6 @@ export function BlockStateTracker() { const { blocks, isSlowMotion, - remainingSeconds, startSlowMotion, stopSlowMotion, isFollowingChain, @@ -29,78 +28,80 @@ export function BlockStateTracker() { const isPaused = !isFollowingChain || isHovering return ( -
+
- Blocks advancing through speculative finality states according to - Monad BFT. More information{' '} + Shows how Monad blocks progress toward final confirmation.{' '} - here + Learn more . } - > -
+ /> + + {/* Main container - no rounded corners */} +
+ {/* Legend bar with slow mode toggle */} +
+
+ {BLOCK_STATE_LEGEND.map((item) => ( +
+
+ {item.label} +
+ ))} +
+
+ Slow mode + + checked ? startSlowMotion() : stopSlowMotion() + } + /> +
+
+ + {/* Blockchain visualization */} +
+ {/* Left fade gradient - only on sm and above */} +
+ +
+ + {/* Footer with hover pause info (desktop) and pause button (mobile) */} +
+ {/* Mobile pause/resume button */} - -
- -
- {BLOCK_STATE_LEGEND.map((item) => ( -
-
- - {item.label} + {/* Desktop hover pause info */} +
+ + + Hovering on the Block stream pauses the update.
- ))} -
- - {/* Info copy - only for mobile */} -
- - Tap Pause to freeze and scroll through blocks -
- - - - {/* Info copy - only for desktop */} -
- - Hover to pause +
) diff --git a/frontend/components/block-time-tracker/block-time-legend.tsx b/frontend/components/block-time-tracker/block-time-legend.tsx index 0185c92..5b4926a 100644 --- a/frontend/components/block-time-tracker/block-time-legend.tsx +++ b/frontend/components/block-time-tracker/block-time-legend.tsx @@ -1,41 +1,15 @@ -import { Info } from 'lucide-react' - export const BlockTimeLegend = () => { return ( -
-
- -
- Height = execution time - - Purple glow = concurrent transactions observed - -
+
+
+
+ Block execution time
-
-
-
-

Block Execution Time

-

Block Exec. Time

-
-
-
-
-
-

Transaction Execution Time

-

Tx Exec. Time

-
-
-
-

Parallel Execution

-

Parallel Exec.

-
+
+
+ + Total transaction execution +
) diff --git a/frontend/components/block-time-tracker/block-time-timeline.tsx b/frontend/components/block-time-tracker/block-time-timeline.tsx index c139b17..2477e03 100644 --- a/frontend/components/block-time-tracker/block-time-timeline.tsx +++ b/frontend/components/block-time-tracker/block-time-timeline.tsx @@ -8,8 +8,8 @@ import type { Block } from '@/types/block' import { BlockTime } from './block-time' const BLOCK_DIMENSIONS = { - small: { itemWidth: 120, gridHeight: 280 }, - large: { itemWidth: 140, gridHeight: 280 }, + small: { itemWidth: 180, gridHeight: 340 }, + large: { itemWidth: 220, gridHeight: 340 }, } const getResponsiveDimensions = () => { @@ -27,28 +27,25 @@ const getResponsiveDimensions = () => { interface BlockTimeTimelineProps { blocks: Block[] isFollowingChain: boolean - normalizedBlockExecutionTime: number + normalizedTimeScaleMs: number } interface BlockCellData { blocks: Block[] - normalizedBlockExecutionTime: number + normalizedTimeScaleMs: number } function BlockCell({ columnIndex, style, blocks, - normalizedBlockExecutionTime, + normalizedTimeScaleMs, }: CellComponentProps) { const block = blocks[columnIndex] return (
- +
) } @@ -61,7 +58,7 @@ function BlockCell({ export function BlockTimeTimeline({ blocks, isFollowingChain, - normalizedBlockExecutionTime, + normalizedTimeScaleMs, }: BlockTimeTimelineProps) { const containerRef = useRef(null) const [containerWidth, setContainerWidth] = useState(0) @@ -111,9 +108,9 @@ export function BlockTimeTimeline({ }, [isFollowingChain]) return ( -
+
{sortedBlocks.length === 0 ? ( -
+
) : ( @@ -127,7 +124,7 @@ export function BlockTimeTimeline({ defaultWidth={containerWidth} overscanCount={3} cellComponent={BlockCell} - cellProps={{ blocks: sortedBlocks, normalizedBlockExecutionTime }} + cellProps={{ blocks: sortedBlocks, normalizedTimeScaleMs }} className="scrollbar-none" style={{ overflowX: isFollowingChain ? 'hidden' : 'auto', diff --git a/frontend/components/block-time-tracker/block-time.tsx b/frontend/components/block-time-tracker/block-time.tsx index deaab20..987359a 100644 --- a/frontend/components/block-time-tracker/block-time.tsx +++ b/frontend/components/block-time-tracker/block-time.tsx @@ -6,171 +6,191 @@ import { TooltipTrigger, } from '@/components/ui/tooltip' import { EXPLORER_URL } from '@/constants/common' -import { calculateBarMetrics, fromNsToMsPrecise } from '@/lib/block-metrics' +import { PARALLEL_EXECUTION_RATIO_THRESHOLD } from '@/hooks/use-block-execution-tracker' +import { calculateBarMetrics } from '@/lib/block-metrics' import { formatBlockNumber } from '@/lib/ui' import { cn } from '@/lib/utils' import type { Block } from '@/types/block' -import { ExternalLink } from '../ui/external-link' interface BlockTimeProps { block: Block - normalizedBlockExecutionTime: number + normalizedTimeScaleMs: number } -export const BlockTime = ({ - block, - normalizedBlockExecutionTime, -}: BlockTimeProps) => { +const BAR_CONTAINER_HEIGHT = 200 + +export const BlockTime = ({ block, normalizedTimeScaleMs }: BlockTimeProps) => { const { - barHeightPercentage, - fillPercentage, + blockHeightPct, + txHeightPct, + blockMs, totalTransactionTime, - isHighlyParallel, + parallelizationRatio, + isParallelExecution, + timeSavedMs, + parallelEfficiencyPct, } = useMemo( - () => calculateBarMetrics(block, normalizedBlockExecutionTime), - [block, normalizedBlockExecutionTime], + () => + calculateBarMetrics( + block, + normalizedTimeScaleMs, + PARALLEL_EXECUTION_RATIO_THRESHOLD, + ), + [block, normalizedTimeScaleMs], ) - const formattedBlockExecutionTime = fromNsToMsPrecise( - block.executionTime ?? BigInt(0), - ).toFixed(3) - const formattedTotalTransactionTime = totalTransactionTime.toFixed(3) + const formattedBlockExecutionTime = blockMs.toFixed(2) + const formattedTotalTransactionTime = totalTransactionTime.toFixed(2) const numberOfTransactions = (block.transactions ?? []).length - const parallelPercentage = isHighlyParallel - ? (Number(formattedTotalTransactionTime) * 100) / - Number(formattedBlockExecutionTime) - - 100 - : 0 // Compute actual percentage of difference tx time and execution between block - const timeSaved = - Number(formattedTotalTransactionTime) - Number(formattedBlockExecutionTime) + const parallelRatioLabel = `${parallelizationRatio.toFixed(2)}×` + const timeSaved = timeSavedMs return ( -
- {/* Block Bar Container */} -
- {/* Block Time Container (represents total block execution time) */} +
+ {/* Bar chart area */} +
- - {/* Transaction Time Fill */} - - +
+ {/* Left bar column (cyan) - label + bar */} +
+ + {formattedBlockExecutionTime}ms + + +
+ + {/* Right bar column (purple) - label + bar */} +
+ + {formattedTotalTransactionTime}ms + + +
+
+ + {/* Tooltip content */} -
-
- +
+ {/* Tooltip header */} +
+ Block {formatBlockNumber(block.number)} - -
-
-

- Block Execution Time -

-

+ + + {/* Stats rows */} +

+
+ + Block execution time: + + {formattedBlockExecutionTime}ms -

+
-
-

- Transaction Execution Time -

-

+

+ + Transaction execution time: + + {formattedTotalTransactionTime}ms -

+
-
-

- Transactions -

-

+

+ + Transactions: + + {numberOfTransactions} -

+
-
-

+

+ Time Saved -

-

- {timeSaved < 0 ? 0 : timeSaved.toFixed(3)} - ms -

+
+ + {timeSaved.toFixed(2)}ms +
-
-

- Parallel Efficiency -

-

- {parallelPercentage.toFixed(3)}% -

+
+ + Parallel Efficiency: + + + {parallelEfficiencyPct.toFixed(2)}% +
- {isHighlyParallel && ( -
-
-
-
-

- High parallel execution detected -

-
-
- )} + + {/* Tooltip footer */} +
+ + {numberOfTransactions} tx + {numberOfTransactions !== 1 ? 's' : ''} + + + View on explorer + +
- {/* Block Stats */} -
-

- {formattedBlockExecutionTime}ms -

-

{numberOfTransactions} tx

+ {/* Block info below bars */} +
+ + {formatBlockNumber(block.number)} + + + Parallel {parallelRatioLabel} +
- - {/* Separator */} -
- - {/* Block number Label */} - - {formatBlockNumber(block.number)} -
) } diff --git a/frontend/components/block-time-tracker/index.tsx b/frontend/components/block-time-tracker/index.tsx index 7b8d213..c5cd90a 100644 --- a/frontend/components/block-time-tracker/index.tsx +++ b/frontend/components/block-time-tracker/index.tsx @@ -1,6 +1,6 @@ 'use client' -import { Clock, Info, Pause, Play, TrendingUp } from 'lucide-react' +import { Clock, Pointer, TrendingUp } from 'lucide-react' import { useMemo, useState } from 'react' import { SectionHeader } from '@/components/ui/section-header' import { StatCard } from '@/components/ui/stat-card' @@ -22,11 +22,8 @@ import { BlockTimeTimeline } from './block-time-timeline' * and calculates execution timing metrics for visualization. */ export function BlockTimeExecutionTracker() { - const { - finalizedBlocks, - maxBlockExecutionTime, - normalizedBlockExecutionTime, - } = useBlockExecutionTracker() + const { finalizedBlocks, maxBlockExecutionTime, normalizedTimeScaleMs } = + useBlockExecutionTracker() const [isFollowingChain, setIsFollowingChain] = useState(true) const { isHovering, hoverProps } = useMouseHover() const isPaused = !isFollowingChain || isHovering @@ -77,56 +74,61 @@ export function BlockTimeExecutionTracker() {
{/* Block Execution Timeline */} -
+
+ description="Visualize block and transaction execution times. Taller transaction bars indicate parallel execution within block." + /> + + {/* Mobile pause/resume button - below section header */} +
- - {/* Info copy - only for mobile */} -
- - Tap Pause to freeze and scroll through blocks + + {isFollowingChain + ? 'Pause to freeze and scroll' + : 'Resume to follow chain'} +
-
- {/* Scrollable Blocks Container */} - - - {/* Seperator */} -
- - {/* Legend */} - -
+
- {/* Info copy - only for desktop */} -
- - Hover to pause + {/* Footer with hover pause info - desktop only */} +
+
+ + + Hovering on the Block stream pauses the update. + +
+
diff --git a/frontend/components/swap-transfer-tracker/index.tsx b/frontend/components/swap-transfer-tracker/index.tsx index 10f0e5e..dddee04 100644 --- a/frontend/components/swap-transfer-tracker/index.tsx +++ b/frontend/components/swap-transfer-tracker/index.tsx @@ -1,6 +1,6 @@ 'use client' -import { ArrowLeftRight, Info, Pause, Play, Send } from 'lucide-react' +import { ArrowLeftRight, Pointer, Send } from 'lucide-react' import { useState } from 'react' import { LiveBadge } from '@/components/common/live-badge' import { SectionHeader } from '@/components/ui/section-header' @@ -30,37 +30,13 @@ export function SwapTransferTracker() { const [isFollowingData, setIsFollowingData] = useState(true) return ( -
+
- - + title="Live Transaction Log" + description="Real-time swaps and transfers observed directly from execution events." + /> - {/* Info copy - only for mobile */} -
- - Tap Pause to freeze and scroll through data -
- -
+
@@ -97,12 +73,31 @@ export function SwapTransferTracker() { /> -
- {/* Info copy - only for desktop */} -
- - Hover to pause + {/* Footer with hover pause info */} +
+ {/* Mobile pause/resume button */} + + + {/* Desktop hover pause info */} +
+ + + Hovering on the data stream pauses the update. + +
+
) diff --git a/frontend/components/ui/button.tsx b/frontend/components/ui/button.tsx new file mode 100644 index 0000000..aaf3256 --- /dev/null +++ b/frontend/components/ui/button.tsx @@ -0,0 +1,55 @@ +import { cva, type VariantProps } from 'class-variance-authority' +import { forwardRef } from 'react' +import { cn } from '@/lib/utils' + +const buttonVariants = cva( + 'inline-flex items-center justify-center font-mono text-sm uppercase cursor-pointer transition-all duration-200 disabled:pointer-events-none disabled:opacity-50', + { + variants: { + variant: { + primary: [ + 'rounded-md text-white', + 'bg-[radial-gradient(50%_50%_at_50%_50%,rgba(110,84,255,0)_0%,rgba(255,255,255,0.12)_100%),#6E54FF]', + 'shadow-[0_1px_2px_0_rgba(0,0,0,0.20),0_1px_0.5px_0_rgba(255,255,255,0.25)_inset,0_-1px_0.5px_0_rgba(255,255,255,0.25)_inset,0_0_0_1px_rgba(79,71,235,0.90)]', + 'hover:bg-[radial-gradient(50%_50%_at_50%_50%,rgba(110,84,255,0.66)_29.81%,rgba(255,255,255,0.50)_100%),#6E54FF]', + 'hover:shadow-[0_1px_2px_0_rgba(0,0,0,0.20),0_0.75px_0.66px_0_rgba(255,255,255,0.80)_inset,0_-0.75px_0.66px_0_rgba(255,255,255,0.80)_inset,0_0_0_1px_rgba(79,71,235,0.50)]', + ], + secondary: [ + 'rounded-md text-white', + 'bg-[radial-gradient(50%_50%_at_50%_50%,rgba(23,23,23,0.20)_0%,rgba(163,163,163,0.16)_100%),#0A0A0A]', + 'shadow-[0_1px_2px_0_rgba(0,0,0,0.20),0_0.5px_0.5px_0_rgba(255,255,255,0.25)_inset,0_-0.5px_0.5px_0_rgba(255,255,255,0.25)_inset,0_0_0_1px_rgba(0,0,0,0.80)]', + 'hover:bg-[radial-gradient(50%_50%_at_50%_50%,rgba(23,23,23,0.66)_0%,rgba(163,163,163,0.53)_100%),#0A0A0A]', + 'hover:shadow-[0_1px_2px_0_rgba(0,0,0,0.20),0_0.5px_0.5px_0_rgba(255,255,255,0.25)_inset,0_-0.5px_0.5px_0_rgba(255,255,255,0.25)_inset,0_0_0_1px_rgba(0,0,0,0.80)]', + ], + }, + size: { + default: 'h-9 px-4 py-2', + sm: 'h-8 px-3 py-1.5', + lg: 'h-10 px-6 py-2.5', + }, + }, + defaultVariants: { + variant: 'primary', + size: 'default', + }, + }, +) + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps {} + +const Button = forwardRef( + ({ className, variant, size, ...props }, ref) => { + return ( +