# Agent Timeline

> Vertical plan tracker for multi-step agents: queued/active/done/failed/skipped steps, nested sub-steps, live durations and a connector that fills.

- **Slug:** `agent-timeline` · **Category:** agent · **Status:** beta
- **Import:** `import { AgentStepStatus, AgentTimeline, AgentTimelineStep } from "@kura-ui/react/agent-timeline";`
- **Docs:** https://kuro.abstergo.fyi/components/agent-timeline
- **Registry item:** https://kuro.abstergo.fyi/r/agent-timeline.json
- **Tags:** agent, plan, timeline, steps, progress, todo, stepper, tasks

## Install

```bash
# shadcn registry (copies source into your project)
npx shadcn@latest add https://kuro.abstergo.fyi/r/agent-timeline.json
# Kura CLI (same, plus dependency resolution and --json output)
npx @kura-ui/cli add agent-timeline
# npm package
npm install @kura-ui/react
```

## When to use

- Show an agent's plan and its progress (coding agents, research agents, deploy pipelines driven by a model).
- Show a todo list the model maintains (Claude Code / Cursor style task lists) with the current step highlighted.
- Nest sub-steps under a step, or attach a ToolCall / CodeBlock to a step via `content`.

## When not to use

- A single tool invocation — use tool-call.
- Free-form model thinking — use reasoning.
- Generic (non-agent) wizards or checkout steppers — use a regular stepper; the active state here uses the AI signal colour.

## Props

| Prop | Type | Default | Required | Description |
| --- | --- | --- | --- | --- |
| `steps` | `readonly AgentTimelineStep[]` | — | yes | The plan, in display order. AgentTimelineStep = { id, title, status: "queued" \| "active" \| "done" \| "failed" \| "skipped", description?, duration? (ms), startedAt?, steps? (nested), content? }. |
| `label` | `string` | `"Agent progress"` | no | Accessible name of the list (e.g. the task name). |
| `size` | `"sm" \| "md"` | `"md"` | no | md for plan panels, sm for dense sidebars. Nested sub-steps always render at sm. |

Native attributes of the root element, `className` and `ref` are passed through.

## Anatomy

- **AgentTimeline** (`data-slot="agent-timeline"`) — Root. `data-size`, `data-progress="done/total"`; contains a polite status region.
- **List** (`data-slot="agent-timeline-list"`) — The <ol> (nested lists for sub-steps).
- **Step** (`data-slot="agent-timeline-step"`) — <li> with `data-status`; the active step has `aria-current="step"`.
- **Node** (`data-slot="agent-timeline-node"`) — Status node: hollow ring (queued), jade pulse (active), check (done), cross (failed), dash (skipped).
- **Connector** (`data-slot="agent-timeline-connector"`) — Hairline track to the next step; its fill scales in from the top (`data-filled`) once the step is done or skipped.
- **Title** (`data-slot="agent-timeline-title"`) — Shimmers while active; struck through when skipped.
- **Duration** (`data-slot="agent-timeline-duration"`) — Mono tabular duration; ticks live for the active step.
- **Description** (`data-slot="agent-timeline-description"`) — Muted secondary line (danger-tinted on failure).
- **Content** (`data-slot="agent-timeline-content"`) — The step's `content` slot.

## Accessibility

- Role: `list`
- Rendered as an ordered list; each step's status is spoken after its title (“Edit files (in progress)”) and the active step has aria-current="step".
- A polite status region announces “Step 3 of 5”, “A step failed” or “All steps complete” — the live timers are never announced.
- Nodes and connectors are decorative (aria-hidden); status is never conveyed by colour alone.
- Reduced motion: The active pulse and title shimmer stop; connectors fade in instead of growing; check/cross icons appear without scale.

## Notes for agents

- Keep `id`s stable across updates so timers and connector transitions animate instead of remounting.
- Only one step should be `active` at a time per level; it carries the jade signal.
- Pass `duration` (ms) for finished steps; omit it for the active step to get a live timer (or pass `startedAt` from your server).
- Map a model-maintained todo list: pending → queued, in_progress → active, completed → done.

## Examples

### Three steps (minimal)

Done, active and queued.

```tsx
import { AgentTimeline } from "@kura-ui/react/agent-timeline";

export function AgentTimelineMinimal() {
  return (
    <AgentTimeline
      className="w-full max-w-md"
      steps={[
        { id: "read", title: "Read the failing test", status: "done", duration: 1200 },
        { id: "fix", title: "Patch the date parser", status: "active" },
        { id: "verify", title: "Re-run the test suite", status: "queued" },
      ]}
    />
  );
}
```

### Live agent run (full)

A plan that progresses on its own: live timers, nested sub-steps, a description, filling connectors.

```tsx
"use client";

import { RotateCcw } from "lucide-react";
import * as React from "react";
import { Button } from "@kura-ui/react/button";
import { AgentTimeline, type AgentTimelineStep } from "@kura-ui/react/agent-timeline";

const PLAN: Plan[] = [
  { id: "explore", title: "Explore the repository", ms: 1400 },
  {
    id: "plan",
    title: "Plan the migration",
    ms: 2200,
    sub: [
      { id: "plan-a", title: "List call sites of fetchUser()" },
      { id: "plan-b", title: "Check the auth middleware" },
    ],
  },
  { id: "edit", title: "Edit 4 files", ms: 2600 },
  { id: "test", title: "Run the test suite", ms: 2000 },
  { id: "pr", title: "Open a pull request", ms: 1200 },
];

function stepsAt(cursor: number, startedAt: number | undefined): AgentTimelineStep[] {
  return PLAN.map((p, i) => ({
    id: p.id,
    title: p.title,
    status: i < cursor ? "done" : i === cursor ? "active" : "queued",
    duration: i < cursor ? p.ms : undefined,
    startedAt: i === cursor ? startedAt : undefined,
    description: p.id === "edit" && i <= cursor ? "src/api/user.ts, src/lib/auth.ts +2" : undefined,
    steps: p.sub?.map((s, j) => ({
      id: s.id,
      title: s.title,
      status:
        i < cursor ? "done" : i === cursor ? (j === 0 ? "done" : "active") : ("queued" as const),
      duration: i < cursor || (i === cursor && j === 0) ? 800 + j * 400 : undefined,
    })),
  }));
}

export function AgentTimelineFull() {
  const [cursor, setCursor] = React.useState(0);
  const [startedAt, setStartedAt] = React.useState<number>();
  const [run, setRun] = React.useState(0);

  // biome-ignore lint/correctness/useExhaustiveDependencies: `run` replays the plan
  React.useEffect(() => {
    let i = 0;
    setCursor(0);
    setStartedAt(Date.now());
    let id: ReturnType<typeof setTimeout>;
    const next = () => {
      id = setTimeout(() => {
        i++;
        setCursor(i);
        setStartedAt(Date.now());
        if (i < PLAN.length) next();
      }, PLAN[i]!.ms);
    };
    next();
    return () => clearTimeout(id);
  }, [run]);

  const done = cursor >= PLAN.length;
  return (
    <div className="flex w-full max-w-md flex-col gap-4">
      <AgentTimeline steps={stepsAt(cursor, startedAt)} label="Migration plan" size="md" />
      <div className="flex items-center justify-between">
        <span className="font-kura-mono text-kura-fg-subtle text-xs tabular-nums">
          {Math.min(cursor, PLAN.length)}/{PLAN.length} steps
        </span>
        <Button variant="secondary" size="sm" disabled={!done} onClick={() => setRun((r) => r + 1)}>
          <RotateCcw /> Replay
        </Button>
      </div>
    </div>
  );
}
```

### Every state, small (state)

Done, skipped, failed and queued at size sm.

```tsx
import { AgentTimeline } from "@kura-ui/react/agent-timeline";

export function AgentTimelineStates() {
  return (
    <AgentTimeline
      size="sm"
      className="w-full max-w-sm"
      steps={[
        { id: "1", title: "Install dependencies", status: "done", duration: 8300 },
        { id: "2", title: "Generate types", status: "skipped", description: "Already up to date" },
        {
          id: "3",
          title: "Build the docs",
          status: "failed",
          duration: 21_400,
          description: "next build exited with code 1",
        },
        { id: "4", title: "Deploy preview", status: "queued" },
      ]}
    />
  );
}
```

### Steps with tool calls (composition)

Inline ToolCalls attached to steps through `content`, inside a task card.

```tsx
import { FileText, Terminal } from "lucide-react";
import { ToolCall } from "@kura-ui/react/tool-call";
import { AgentTimeline } from "@kura-ui/react/agent-timeline";

export function AgentTimelineWithTools() {
  return (
    <div className="flex w-full max-w-lg flex-col gap-4 rounded-kura-xl bg-kura-surface p-4 shadow-kura-sm">
      <div className="flex items-baseline justify-between">
        <h3 className="font-medium text-kura-fg text-sm">Fix flaky date test</h3>
        <span className="font-kura-mono text-kura-fg-subtle text-xs tabular-nums">2/3</span>
      </div>
      <AgentTimeline
        label="Fix flaky date test"
        steps={[
          {
            id: "read",
            title: "Read the failing test",
            status: "done",
            duration: 900,
            content: (
              <ToolCall
                variant="inline"
                name="read_file"
                icon={<FileText />}
                status="success"
                duration={120}
                args={{ path: "src/date.test.ts" }}
              />
            ),
          },
          {
            id: "run",
            title: "Reproduce with a fixed timezone",
            status: "active",
            content: (
              <ToolCall
                variant="inline"
                name="run_command"
                icon={<Terminal />}
                status="running"
                args={{ command: "TZ=Pacific/Auckland pnpm test date" }}
              />
            ),
          },
          { id: "fix", title: "Patch and verify", status: "queued" },
        ]}
      />
    </div>
  );
}
```

## Dependencies

- npm: `lucide-react`
- Kura registry items: `kura-theme`, `shimmer-text`, `utils`
- Related: `tool-call`, `reasoning`, `approval-prompt`, `shimmer-text`
