Todo List
The agent's plan as one line above the composer: the live task and a count, opening into the whole checklist.
Installation
npx shadcn@latest add miskibin/chat-components/todo-listAlso installs collapsible.
Usage
"use client"
import { TodoPanel, parseTodoItems } from "@/components/ui/todo-list"
export function Plan({ toolInput }: { toolInput?: string }) {
const items = parseTodoItems(toolInput) ?? []
return <TodoPanel items={items} />
}Examples
Above the composer
The panel carries the same max-w-3xl measure as ChatInput, so the two stack without a call-site override. parseTodoItems turns a todo tool call's arguments straight into the list.
When the turn is over
running is what tells the bar the turn behind it has settled. An agent that stops without writing its last step off is the common case, and a spinner that outlives the run reads as work still happening — so an unfinished step goes quiet, and a plan whose steps are all done says Plan done. Live, abandoned, and finished, in that order.
- Read the failing test
- Patch applyPatch
- Re-run the suite
- Read the failing test
- Patch applyPatch
- Re-run the suite
- Read the failing test
- Patch applyPatch
- Re-run the suite
The checklist alone
TodoList is the list with no chrome — for a plan that is already framed by something else, like a tool row or a sidebar section.
- Reproduce the failing turn
- Bisect the reducer
- Add a regression test
Notes
API reference
TodoPanel
items*TodoItem[]- The plan, in order. An empty array renders nothing at all.
defaultOpenboolean = false- Start expanded. Collapsed, the panel is a single status line.
openboolean- Controlled open state; pair with onOpenChange.
onOpenChange(open: boolean) => void- Fired when the disclosure is toggled.
labelReactNode- Replaces the collapsed headline, which is otherwise the in-progress task (or the next unfinished one).
runningboolean = true- False once the turn behind the plan has settled: nothing spins, and a plan with every step done reads as done.
TodoList
items*TodoItem[]- The same list, rendered as a bare ordered list.
runningboolean = true- False draws an unfinished step as the step nobody got to, instead of one still being worked on.
Helpers
parseTodoItems(input?: string | null) => TodoItem[] | null- Parses a todo tool call's JSON arguments into items. Null when the payload holds no list.
isTodoToolName(name: string) => boolean- True for todo_write, TodoWrite, write_todos, update_plan and the like.
todoProgress(items: TodoItem[]) => { completed, total, active, current, done }- Counts plus the in-progress item and the one worth naming in a single line.
normalizeTodoStatus(value: unknown) => TodoStatus- Folds any spelling onto pending | in_progress | completed.
Data slots
Every part below is addressable from a parent, so you can restyle internals without forking the component.
todo-paneltodo-panel-roottodo-panel-triggertodo-panel-headlinetodo-panel-counttodo-panel-listtodo-listtodo-item