import { ChevronLeft, ChevronRight, Circle, Pause, Play, RotateCcw } from 'lucide-react'; import { PLAYBACK_SPEEDS } from '@/lib/demo-kit/player'; import type { PlaybackSpeed } from '@/lib/demo-kit/player'; import { Button } from '@/components/ui/button'; import { cn } from '@/lib/utils'; import { formatDate } from './format'; export interface TracePlayerProps { playing: boolean; onPlayingChange: (playing: boolean) => void; speed: PlaybackSpeed; onSpeedChange: (speed: PlaybackSpeed) => void; onRestart: () => void; step: number; stepCount: number; onStepChange: (next: number) => void; /** 0..1 through the current step, from the player. Drives the hairline. */ progress?: number; /** * False when any step's dwell was invented because the trace carried no * duration. Surfaced, not hidden: the player's whole claim is that the * pacing is the model's, and where it is not, it says so. */ timingIsReal?: boolean; /** Straight off the run. Never a marketing name for the model. */ model: string; capturedAt: string; /** Present only on an `intervened` run; the contract requires it there. */ intervention?: string; className?: string; } /** * Transport controls for a recorded rollout. * * There is no spinner in this component and there never should be. A spinner * implies a request is in flight; nothing here is live, and an exec who * believes they are watching a model think in real time has been misled by the * UI rather than the copy. Hence the permanent badge — not a disclosure tucked * into a footnote, but a fixture of the transport bar for the whole session. */ export function TracePlayer({ playing, onPlayingChange, speed, onSpeedChange, onRestart, step, stepCount, onStepChange, progress = 0, timingIsReal = true, model, capturedAt, intervention, className, }: TracePlayerProps) { const canPlay = stepCount > 1; return (
Step {Math.min(step + 1, stepCount)} of{' '} {stepCount}
Recorded run {model} {formatDate(capturedAt)} {intervention ? ( {intervention} ) : null} {timingNote ? ( {timingNote} ) : null}
); }