Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 49 additions & 23 deletions frontend/components/block-state-tracker/slow-motion-control.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
'use client'

import { Timer, X } from 'lucide-react'
import type { ReactElement } from 'react'
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from '@/components/ui/tooltip'

interface SlowMotionControlProps {
isActive: boolean
Expand All @@ -9,6 +15,23 @@ interface SlowMotionControlProps {
onStop: () => void
}

function SlowModeTooltipWrapper({ children }: { children: ReactElement }) {
return (
<Tooltip delayDuration={1000}>
<TooltipTrigger asChild>{children}</TooltipTrigger>
<TooltipContent
sideOffset={6}
className="bg-tooltip-bg border border-tooltip-border text-tooltip-text rounded-lg p-2 shadow-xl text-xs leading-snug max-w-52.5"
>
<p>
Slow mode doesn't affect the chain. It only slows UI updates, so the
display may lag behind the chain tip.
</p>
</TooltipContent>
</Tooltip>
)
}

export function SlowMotionControl({
isActive,
remainingSeconds,
Expand All @@ -17,33 +40,36 @@ export function SlowMotionControl({
}: SlowMotionControlProps) {
if (isActive) {
return (
<div className="h-10.5 flex items-center gap-1.5 px-3 py-2.5 rounded-lg border bg-purple-500/20 border-purple-400 text-purple-300 text-sm font-medium">
<div className="flex items-center gap-1">
<Timer className="w-4 h-4" />
<span>Slow Mode</span>
<span className="tabular-nums">({remainingSeconds}s)</span>
<SlowModeTooltipWrapper>
<div className="h-10.5 flex items-center gap-1.5 px-3 py-2.5 rounded-lg border bg-purple-500/20 border-purple-400 text-purple-300 text-sm font-medium">
<div className="flex items-center gap-1">
<Timer className="w-4 h-4" />
<span>Slow Mode</span>
<span className="tabular-nums">({remainingSeconds}s)</span>
</div>
<button
type="button"
onClick={onStop}
className="m-0 p-1 rounded-md text-purple-300 hover:text-red-400 hover:border-red-400/50 hover:bg-red-500/10 cursor-pointer transition-all duration-200"
title="Stop slow mode"
>
<X className="w-4 h-4" />
</button>
</div>
<button
type="button"
onClick={onStop}
className="m-0 p-1 rounded-md text-purple-300 hover:text-red-400 hover:border-red-400/50 hover:bg-red-500/10 cursor-pointer transition-all duration-200"
title="Stop slow mode"
>
<X className="w-4 h-4" />
</button>
</div>
</SlowModeTooltipWrapper>
)
}

return (
<button
type="button"
onClick={onStart}
className="flex items-center gap-2 px-4 py-2.5 rounded-lg border bg-zinc-900 border-zinc-700 text-zinc-400 hover:bg-purple-500/10 hover:border-purple-500/30 hover:text-purple-400 text-sm font-medium whitespace-nowrap cursor-pointer transition-all duration-200"
title="Slow down block updates to observe state transitions"
>
<Timer className="w-4 h-4" />
Slow Mode (30s)
</button>
<SlowModeTooltipWrapper>
<button
type="button"
onClick={onStart}
className="flex items-center gap-2 px-4 py-2.5 rounded-lg border bg-zinc-900 border-zinc-700 text-zinc-400 hover:bg-purple-500/10 hover:border-purple-500/30 hover:text-purple-400 text-sm font-medium whitespace-nowrap cursor-pointer transition-all duration-200"
>
<Timer className="w-4 h-4" />
<span>Slow Mode (30s)</span>
</button>
</SlowModeTooltipWrapper>
)
}
3 changes: 2 additions & 1 deletion frontend/constants/block-state.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import type { BlockState } from '@/types/block'

export const SLOW_MOTION_DURATION_SECONDS = 30
export const SLOW_MOTION_EVENT_INTERVAL_MS = 75
export const SLOW_MOTION_CHUNK_INTERVAL_MS = 2000
export const SLOW_MOTION_CHUNK_NEW_BLOCKS = 2

/**
* Color tokens for block states (CSS variables for consistency).
Expand Down
29 changes: 23 additions & 6 deletions frontend/hooks/use-blockchain-slow-motion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

import { useCallback, useEffect, useRef, useState } from 'react'
import {
SLOW_MOTION_CHUNK_INTERVAL_MS,
SLOW_MOTION_CHUNK_NEW_BLOCKS,
SLOW_MOTION_DURATION_SECONDS,
SLOW_MOTION_EVENT_INTERVAL_MS,
} from '@/constants/block-state'
import type { SerializableEventData } from '@/types/events'

Expand Down Expand Up @@ -93,14 +94,30 @@ export function useBlockchainSlowMotion({
eventQueueRef.current = []

// Start the slow processing interval
// Process queued events in chunks so the UI advances in visible steps.
// A "chunk" aims to include ~N new events (BlockStart events) plus any subsequent state transitions for those blocks that are already queued.
processIntervalRef.current = setInterval(() => {
if (eventQueueRef.current.length > 0) {
const event = eventQueueRef.current.shift()
if (event) {
onProcessEventRef.current(event)
if (eventQueueRef.current.length === 0) return

const chunk: SerializableEventData[] = []
let newBlocksInChunk = 0

while (
eventQueueRef.current.length > 0 &&
newBlocksInChunk < SLOW_MOTION_CHUNK_NEW_BLOCKS
) {
const next = eventQueueRef.current.shift()
if (!next) break
chunk.push(next)
if (next.payload.type === 'BlockStart') {
newBlocksInChunk += 1
}
}
}, SLOW_MOTION_EVENT_INTERVAL_MS)

if (chunk.length > 0) {
onFlushEventsRef.current(chunk)
}
}, SLOW_MOTION_CHUNK_INTERVAL_MS)

// Start the countdown timer using timestamp-based calculation
countdownIntervalRef.current = setInterval(updateRemainingSeconds, 1000)
Expand Down