shadimage
mischief
exampleagent-checklist-demosource: ok · hit

Agent Checklist Demo

A worked example of Agent Checklist: A task list whose items change state as work proceeds, announcing what changed instead of re-reading the whole list.

npx shadcn add "https://ui.tinkererslabs.com/r/agent-checklist-demo.json"
free

Where it goes

stats

Interaction

animated-counter

Tags auto-generated by rules on 9/24/2026 — empty means no evidence, not lack of support.

Dependencies

Bundle weight · light
lucide-react@^1.31.0

Registry dependencies

https://ui.tinkererslabs.com/r/agent-checklist.json

Files4 · source cached 10/14/2026, 2:32:04 PM

components/demos/agent-checklist-demo.tsx
"use client"

import { DemoVariants } from "@/components/demos/demo-variants"
import { RestartButton } from "@/components/demos/restart-button"
import {
  useScriptedTimeline,
  type TimelineStep,
} from "@/components/demos/use-scripted-timeline"
import {
  AgentChecklist,
  type AgentChecklistItem,
} from "@/registry/default/agent-checklist/agent-checklist"

const labels = [
  "Read the migration",
  "Check the column constraints",
  "Reorder the statements",
  "Run the test suite",
]

function frame(activeIndex: number): AgentChecklistItem[] {
  return labels.map((label, index) => ({
    id: String(index),
    label,
    status:
      index < activeIndex
        ? "done"
        : index === activeIndex
          ? "active"
          : "pending",
  }))
}

const steps: [
  TimelineStep<AgentChecklistItem[]>,
  ...TimelineStep<AgentChecklistItem[]>[],
] = [
  { state: frame(0), holdMs: 1600 },
  { state: frame(1), holdMs: 1600 },
  { state: frame(2), holdMs: 1600 },
  { state: frame(3), holdMs: 1600 },
  { state: frame(4), holdMs: 0 },
]

function LiveRun() {
  const { state, isFinished, restart, runId } = useScriptedTimeline(steps)

  return (
    <div className="grid gap-4">
      <AgentChecklist key={runId} items={state} title="Fixing the migration" />
      {isFinished ? <RestartButton onClick={restart} /> : null}
    </div>
  )
}

export function AgentChecklistDemo() {
  return (
    <DemoVariants
      label="Checklist state"
      variants={[
        { id: "live", label: "Live run", render: () => <LiveRun /> },
        {
          id: "failure",
          label: "With a failure",
          render: () => (
            <AgentChecklist
              title="Fixing the migration"
              items={[
                { id: "0", label: labels[0], status: "done" },
                { id: "1", label: labels[1], status: "done" },
                {
                  id: "2",
                  label: labels[2],
                  status: "error",
                  detail: "Permission denied on migrations/.",
                },
                { id: "3", label: labels[3], status: "skipped" },
              ]}
            />
          ),
        },
      ]}
    />
  )
}
components/demos/demo-variants.tsx
"use client"

import * as React from "react"

export type DemoVariant = {
  id: string
  label: string
  render: () => React.ReactNode
}

export function DemoVariants({
  variants,
  label = "Demo variant",
}: {
  variants: [DemoVariant, ...DemoVariant[]]
  label?: string
}) {
  const [activeId, setActiveId] = React.useState(variants[0].id)
  const active =
    variants.find((variant) => variant.id === activeId) ?? variants[0]

  return (
    <div className="grid w-full justify-items-center gap-6">
      {/* Centres whatever the variant renders. Without this a demo that caps
          its own width sits against the left edge while the switch below it
          is centred, which reads as a mistake. */}
      <div className="flex w-full justify-center">{active.render()}</div>

      <div
        role="group"
        aria-label={label}
        className="border-border bg-background/70 inline-flex flex-wrap justify-center gap-1 rounded-full border p-1"
      >
        {variants.map((variant) => {
          const isActive = variant.id === active.id

          return (
            <button
              key={variant.id}
              type="button"
              aria-pressed={isActive}
              className={
                isActive
                  ? "bg-foreground text-background rounded-full px-3 py-1.5 text-xs font-semibold"
                  : "text-muted-foreground hover:text-foreground rounded-full px-3 py-1.5 text-xs font-semibold"
              }
              onClick={() => setActiveId(variant.id)}
            >
              {variant.label}
            </button>
          )
        })}
      </div>
    </div>
  )
}
components/demos/restart-button.tsx
"use client"

import { RotateCcw } from "lucide-react"

export function RestartButton({ onClick }: { onClick: () => void }) {
  return (
    <button className="restore-button" type="button" onClick={onClick}>
      <RotateCcw aria-hidden="true" size={15} /> Replay
    </button>
  )
}
components/demos/use-scripted-timeline.ts
"use client"

import * as React from "react"

const TICK_MS = 100

export type TimelineStep<TState> = {
  state: TState
  holdMs: number
}

export function useScriptedTimeline<TState>(
  steps: [TimelineStep<TState>, ...TimelineStep<TState>[]]
) {
  const [index, setIndex] = React.useState(0)
  const [elapsedMs, setElapsedMs] = React.useState(0)
  const [runId, setRunId] = React.useState(0)

  const isFinished = index >= steps.length - 1

  React.useEffect(() => {
    if (isFinished) return

    const timer = window.setTimeout(
      () => setIndex((current) => current + 1),
      (steps[index] ?? steps[0]).holdMs
    )

    return () => window.clearTimeout(timer)
  }, [index, isFinished, steps])

  React.useEffect(() => {
    if (isFinished) return

    const timer = window.setInterval(
      () => setElapsedMs((current) => current + TICK_MS),
      TICK_MS
    )

    return () => window.clearInterval(timer)
  }, [isFinished])

  const restart = React.useCallback(() => {
    setIndex(0)
    setElapsedMs(0)
    setRunId((current) => current + 1)
  }, [])

  return {
    state: (steps[index] ?? steps[0]).state,
    elapsedMs,
    isFinished,
    restart,
    runId,
  }
}

More from mischief

Browse all components in this library →