Skip to main content
Clever Ops

Session Timeline Scrub

A horizontal session scrubber, turn markers on a hairline rail with drag and arrow-key navigation under role=slider semantics, and a preview chip summarizing the focused turn.

Preview & code

Loading Session Timeline Scrub preview

Installation

pnpm dlx shadcn@latest add @cleverui/session-timeline-scrub

Requires the @cleverui registry in your components.json, a one-off, two-line setup.

Usage

tsx
import { SessionTimelineScrub } from '@/components/ui/session-timeline-scrub'
Exampletsx
'use client'

import { useEffect, useState } from 'react'
import { useReducedMotion } from 'motion/react'
import {
  SessionTimelineScrub,
  type SessionTimelineTurn,
} from '@/components/ui/session-timeline-scrub'

/** 24-turn Meridian reconciliation session, deterministic scrub sequence */
const TURNS: SessionTimelineTurn[] = [
  { id: 't0', role: 'user', summary: 'Reconcile May invoices for Apex Holdings', at: '09:12' },
  { id: 't1', role: 'agent', summary: 'Loaded workspace context, 3 open ledgers', at: '09:12' },
  { id: 't2', role: 'agent', summary: 'Pulled 128 AUTHORISED invoices from Xero', at: '09:13' },
  { id: 't3', role: 'user', summary: 'Focus Acme Logistics and flag over $50', at: '09:13' },
  { id: 't4', role: 'agent', summary: 'Filtered to 14 Acme Logistics invoices', at: '09:14' },
  { id: 't5', role: 'agent', summary: 'Matched 12 payments against Stripe charges', at: '09:14' },
  { id: 't6', role: 'agent', summary: 'Found duplicate charge on inv_2026_03_8841', at: '09:15' },
  { id: 't7', role: 'agent', summary: 'Reconciled 128 invoices against Xero', at: '09:15' },
  { id: 't8', role: 'user', summary: 'Draft a refund proposal for the duplicate', at: '09:16' },
  { id: 't9', role: 'agent', summary: 'Proposed $1,240 refund on ch_1Q8k2m', at: '09:16' },
  { id: 't10', role: 'agent', summary: 'Attached bank memo draft for controller review', at: '09:17' },
  { id: 't11', role: 'user', summary: 'Also check June prepayments', at: '09:18' },
  { id: 't12', role: 'agent', summary: 'Scanned June prepayment journal, 6 entries', at: '09:18' },
  { id: 't13', role: 'agent', summary: 'Two prepayments still unapplied to invoices', at: '09:19' },
  { id: 't14', role: 'user', summary: 'Apply them and re-run the trial balance', at: '09:19' },
  { id: 't15', role: 'agent', summary: 'Applied prepayments PRE_0602 and PRE_0611', at: '09:20' },
  { id: 't16', role: 'agent', summary: 'Trial balance balanced within $0.02', at: '09:20' },
  { id: 't17', role: 'user', summary: 'Export the exception report for audit', at: '09:21' },
  { id: 't18', role: 'agent', summary: 'Wrote exceptions-may-apex.csv, 2 rows', at: '09:21' },
  { id: 't19', role: 'agent', summary: 'Posted summary to #ops-finance thread', at: '09:22' },
  { id: 't20', role: 'user', summary: 'Hold the refund until Sarah approves', at: '09:22' },
  { id: 't21', role: 'agent', summary: 'Parked refund behind HITL approval gate', at: '09:23' },
  { id: 't22', role: 'agent', summary: 'Notified Sarah via Linear MER-441', at: '09:23' },
  { id: 't23', role: 'agent', summary: 'Session idle, awaiting controller sign-off', at: '09:24' },
]

/** Indices stepped by deterministic timers in the demo */
const SCRUB_PATH = [0, 3, 7, 9, 14, 18, 21, 23, 7, 0]

export default function SessionTimelineScrubDemo() {
  const reduced = useReducedMotion()
  const [value, setValue] = useState(7)
  const [pathIndex, setPathIndex] = useState(0)

  useEffect(() => {
    if (reduced) {
      setValue(7)
      return
    }
    const hold = pathIndex === 0 ? 1800 : 1400
    const id = window.setTimeout(() => {
      const next = (pathIndex + 1) % SCRUB_PATH.length
      setPathIndex(next)
      setValue(SCRUB_PATH[next] ?? 0)
    }, hold)
    return () => window.clearTimeout(id)
  }, [pathIndex, reduced])

  return (
    <div className="mx-auto flex min-h-[360px] w-full max-w-2xl flex-col justify-center px-4 py-12 sm:px-6 sm:py-16">
      <div className="overflow-hidden rounded-2xl border border-border/60 bg-card px-4 py-6 sm:px-6 sm:py-8">
        <p className="mb-6 font-mono text-[11px] uppercase tracking-[0.2em] text-muted-foreground sm:text-xs">
          meridian-ops · live session
        </p>
        <SessionTimelineScrub
          label="Session"
          turns={TURNS}
          value={value}
          onChange={setValue}
        />
      </div>
    </div>
  )
}

API reference

SessionTimelineScrub accepts the following props.

PropTypeDefaultDescription
turns*SessionTimelineTurn[]the session's turns in order; summary feeds the preview chip and aria-valuetext
value*numberindex of the current turn, fully controlled
onChange*(index: number) => voidfires with the nearest turn index during drag, marker click, and keys
labelstring'Session'mono micro-label naming the instrument

…plus everything from Omit< React.HTMLAttributes<HTMLDivElement>, 'onChange' | 'onDrag' | 'onDragStart' | 'onDragEnd' >: className, event handlers, aria attributes and the rest pass straight through.

Referenced types

Typestsx
export interface SessionTimelineTurn {
  id: string
  role: 'user' | 'agent'
  summary: string
  at?: string
}

Frequently asked questions

How do I install the Session Timeline Scrub component?

Register the @cleverui registry in your components.json ("@cleverui": "https://cleverops.com.au/r/{name}.json"), then run `npx shadcn@latest add @cleverui/session-timeline-scrub`. The shadcn CLI copies the source into your project, you own the code from that point. You can also copy the source directly from this page.

What dependencies does Session Timeline Scrub require?

Session Timeline Scrub uses motion on top of React and Tailwind CSS. You don't need to install it manually, the shadcn CLI resolves and installs npm dependencies automatically when you add the component.

Can I theme Session Timeline Scrub to match my brand?

Yes. The component is styled entirely with Tailwind utilities over standard design tokens (primary, background, muted, border), so it inherits your existing shadcn/ui theme automatically. Change your CSS variables and the component follows, no source edits needed for palette, radius, or fonts.

Want this built for you?

CleverUI is built by the Clever Ops web team. If you'd rather ship a hand-coded, fast, custom website than assemble one, we build them for Australian businesses every week.

Explore web development services