Elements

69 / 122 · Agents

Checkpoints

Points you can fall back to, with what each one would give back.

Checkpoints
Before the converter change09:41 · 0 files
Converter guarded09:58 · 2 files
Tests added10:12 · 4 filescurrent
Formatting sweep10:20 · 31 files

Installation

npx shadcn@latest add "@assistant-ui/elements-checkpoint-history"

Usage

import { CheckpointHistory } from "@/components/elements/checkpoint-history";

<CheckpointHistory checkpoints={checkpoints} currentId="3" onRestore={restore} />

Props

CheckpointHistory

checkpoints*Checkpoint[]Points in the run you can fall back to, oldest first.
currentId*stringWhere the run stands. Everything after it dims, since restoring would discard it.
onRestore(id: string) => voidCalled with the checkpoint to return to.
classNamestringExtra classes merged onto the root.

Checkpoint

id*stringStable identity, compared against currentId and reported by onRestore.
label*stringWhat the checkpoint captured.
files*numberHow many files that checkpoint touched.
at*stringPre-formatted time.

Source

checkpoint-history.tsx
"use client";

import { RotateCcwIcon } from "lucide-react";
import { cn } from "@/lib/utils";
import { mono, paper } from "./surfaces";

export interface Checkpoint {
  id: string;
  label: string;
  at: string;
  files: number;
}

export function CheckpointHistory({
  checkpoints,
  currentId,
  onRestore,
  className,
}: {
  checkpoints: readonly Checkpoint[];
  currentId: string;
  onRestore?: (id: string) => void;
  className?: string;
}) {
  const currentIndex = checkpoints.findIndex(
    (checkpoint) => checkpoint.id === currentId,
  );

  return (
    <div
      className={cn(
        paper,
        "flex w-full max-w-sm flex-col gap-1 rounded-2xl p-3",
        className,
      )}
    >
      <span className="px-1.5 pb-1 text-[13.5px] font-medium">Checkpoints</span>

      {checkpoints.map((checkpoint, i) => {
        const ahead = currentIndex >= 0 && i > currentIndex;
        const current = checkpoint.id === currentId;

        return (
          <div
            key={checkpoint.id}
            className={cn(
              "group flex items-center gap-2.5 rounded-xl px-1.5 py-1.5 transition-colors",
              current ? "bg-foreground/[0.05]" : "hover:bg-foreground/[0.03]",
              ahead && "opacity-40",
            )}
          >
            <span
              aria-hidden
              className={cn(
                "size-1.5 shrink-0 rounded-full",
                current
                  ? "bg-blue-500 dark:bg-blue-400"
                  : ahead
                    ? "border-foreground/25 border"
                    : "bg-foreground/25",
              )}
            />

            <span className="flex min-w-0 flex-1 flex-col">
              <span className="truncate text-[13px]">{checkpoint.label}</span>
              <span className={cn(mono, "text-foreground/30")}>
                {checkpoint.at} · {checkpoint.files} files
              </span>
            </span>

            {current ? (
              <span className={cn(mono, "text-foreground/35 shrink-0")}>
                current
              </span>
            ) : (
              <button
                type="button"
                aria-label={`Restore to ${checkpoint.label}`}
                onClick={() => onRestore?.(checkpoint.id)}
                className="text-foreground/45 hover:bg-foreground/[0.06] hover:text-foreground/90 flex h-6 shrink-0 items-center gap-1 rounded-full px-2 text-[11px] font-medium opacity-0 transition-[background-color,color,opacity,scale] duration-150 group-hover:opacity-100 focus-visible:opacity-100 active:scale-[0.96]"
              >
                <RotateCcwIcon className="size-2.5" />
                Restore
              </button>
            )}
          </div>
        );
      })}
    </div>
  );
}