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 · lightlucide-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,
}
}