Connect an agent plan to live task progress in React

Let people inspect and revise a plan, approve it, and follow the resulting work through a separate execution checklist.

Live example

Proposed plan · 3 steps

Fix session expiry

Install the components

Add these to a React project with Tailwind CSS configured.

npx shadcn@latest add https://gradientdeck.com/ai-components/r/plan-default.json
npx shadcn@latest add https://gradientdeck.com/ai-components/r/todo-list-default.json

Components used: Plan, Todo list.

1. Give the plan stable step IDs

Use the same step IDs in the proposed plan and task updates. Plan displays the proposed work and supporting detail. In this example, Edit opens an actual form that changes the first step's instructions before approval.

2. Separate approval from execution

Approving the plan changes its state to approved and starts the sample execution. It does not complete any tasks. TodoList receives todo, doing and done states independently, so the reader can distinguish agreement about the approach from evidence that the work happened.

3. Update the checklist from run events

The sample completes one task every 1.4 seconds and stops after the final task. Replace that timer with events keyed by run and step ID. Persist the approved version so a later revision cannot change what an earlier approval meant.

Complete example

Save this as a client component after installing the components above.

"use client";

import { useEffect, useState } from "react";
import { Plan, type PlanStep } from "@/components/ai/plan-default";
import { TodoList, type Todo } from "@/components/ai/todo-list-default";

const initial: PlanStep[] = [
  { id: "inspect", title: "Inspect the session handler", detail: "Trace token validation and session expiry.", files: ["src/auth/session.ts"] },
  { id: "change", title: "Add the expiry check", detail: "Return null when the session has expired.", files: ["src/auth/session.ts"] },
  { id: "verify", title: "Run authentication tests", detail: "Verify expired, missing and valid tokens.", files: ["src/auth/session.test.ts"] },
];

export default function AgentPlanExample() {
  const [approved, setApproved] = useState(false);
  const [active, setActive] = useState(0);
  const [editing, setEditing] = useState(false);
  const [note, setNote] = useState(initial[0].detail);
  const [steps, setSteps] = useState(initial);
  useEffect(() => {
    if (!approved || active >= steps.length) return;
    // Simulate task events. Approval alone must not mark a task complete.
    const timer = setTimeout(() => setActive(value => value + 1), 1400);
    return () => clearTimeout(timer);
  }, [approved, active, steps.length]);
  const todos: Todo[] = steps.map((step, index) => ({ id: step.id, text: step.title, status: index < active ? "done" : index === active ? "doing" : "todo" }));
  return <div className="w-full max-w-xl space-y-5">
    <Plan title="Fix session expiry" steps={steps} state={approved ? "approved" : "proposed"} onApprove={() => { setEditing(false); setApproved(true); }} onEdit={() => setEditing(true)} />
    {editing && <form onSubmit={event => { event.preventDefault(); setSteps(items => items.map((step, index) => index === 0 ? { ...step, detail: note.trim() } : step)); setEditing(false); }}>
      <label className="block text-sm">First step instructions<textarea value={note} onChange={event => setNote(event.target.value)} className="mt-2 block w-full rounded-md border border-zinc-500 bg-transparent p-2" /></label>
      <button type="submit" disabled={!note.trim()} className="mt-2 text-sm underline">Save changes</button>
    </form>}
    {approved && <TodoList todos={todos} title="Execution" />}
    {approved && active === steps.length && <button type="button" onClick={() => { setApproved(false); setActive(0); }} className="text-xs underline">Reset example</button>}
  </div>;
}

Connect your backend

Plan approval is not a replacement for tool-specific authorization. Handle failed, skipped and cancelled execution states in your application; this checklist's three-state API cannot express every run outcome by itself. Preserve an error or recovery message beside it instead of marking a failed task done.

All guides →