To-do List

The user's own to-do list, from their tasks app or drafted by the agent, fully editable in the chat. Edits go back as one change set when the user saves.

Pattern

≈ 1,603 tokens per use (tool 958 · guidance 316 · typical payload 329)How token costs are estimated

Example: To-do List

What's on my to-do list today?

Today

5 to do · 2 done · 1 overdue

Press Space or Enter to pick up this task. Then use the up and down arrow keys to move it within its group, Space or Enter to drop it, or Escape to cancel.

You can type a day like tomorrow or friday, and !high or !low for priority.

Overdue, 1

  • Renew the car registration
    High priorityOverdue, was due Tuesday, October 6

Today, 3

  • Send the Q4 budget to Dana
    High priorityDue Wednesday, October 7, 3:00 PM
  • Prep slides for Thursday's review
    Medium priorityTags: Work

    Notes: Omar wants the churn chart first.

  • Pick up the dry cleaning
    Tags: Errands

Tomorrow, 1

  • Pay the electricity bill
    Medium priorityTags: HomeAssigned to Sam Ortiz

No changes

When to use

  • The user asks about their own to-do list: "what's on my list today?", "what's overdue?".
  • The user asks to add, reschedule or tick off tasks in their tasks app.
  • The user wants a list they'll keep and edit, such as a packing or shopping list.

When not to use

  • The steps are the agent's instructions for one task, like a recipe or a setup guide. Use a Checklist.
  • The items are things the agent will do itself. Show a plan instead.
  • Work items move between stages on a team board. Use a Kanban Board.
  • The agent can't read the user's list. Fetch it first; never invent their tasks.

How the agent should use it

  • Pass items exactly as the tasks app returns them, in order. Never change their text, dates or done state yourself.
  • To add a task the user asked for, include it with isNew: true and a new id. It shows as "Added" and is saved only when the user saves.
  • Put your own ideas in suggestions, each with a short reason. They show under "Suggested" with Add and a dismiss button; the reason is a tooltip and is read to screen readers. Never mix them into items.
  • Pick groupBy for the question: due for "today" or "overdue", tag for categorized lists like packing, none or priority when the user's own order matters (only those two can be reordered by hand).
  • Write nothing until the result arrives, then apply exactly the returned changes. On discard, write nothing.
  • Lifecycle: the list is the user's own and they keep working in it, so don't set expiresAt. Set fetchedAt and staleAfterMs (e.g. an hour) when the list was read from a tasks app that other devices also edit, so the Renderer offers a refresh. When the agent shows the same list again with fresh data, supersede the old call.

Editing

Every edit is collected locally and counted in the footer ("3 changes"). Nothing is written until the user presses Save to Tasks app (the label uses source.label, or just "Save"). Discard goes back to the last saved state. The header shows only the title: the counts are read to screen readers, and the Save button names the source.

ActionHow
AddType in "Add a task" and press Enter. "tomorrow", "friday" (the next one, so a week out on a Friday), "next week" (next Monday), "in 3 days", and !high, !low or !! are read into a due date and priority. They show as chips before adding, each with a button to keep the words as plain text instead
Tick offThe item is struck through and fades, then moves to a collapsed "Done" group after a second (at once with reduced motion). Screen readers hear "Marked … done"
Edit textClick the text or the pencil button (on devices with hover, the row's buttons appear on hover or focus). Enter or leaving the field saves; Escape cancels
Due date, priority, deleteThe "More options" button opens a panel with a date field, priority buttons and Delete. A deleted item leaves a "Deleted … · Undo" row in place
ReorderWith groupBy none or priority, drag the handle, or focus it and press Space, the arrow keys, then Space again (Escape cancels). Items move within their group; each step is announced
Subtasks and notesShown under the item with a "2/5" progress toggle; subtasks can be ticked

Due groups are computed in timeZone (or the viewer's): Overdue, Today, Tomorrow, This week (2 to 6 days out), Later and No date. A date-time that has passed today counts as overdue. Pass now to fix the moment they're computed from; it defaults to the viewer's clock after mount. Setting a date from the options panel stores a plain date.

Done items are the one place the list starts collapsed, so the open tasks stay in view. Set hideDone to leave them out.

Installation

npx shadcn@latest add https://your-site.com/r/todo-list.json

Usage

app/api/chat/route.ts
import { tool } from "ai"
import { todoListTool } from "@/components/genui/todo-list.schema"

const tools = {
  // No execute(): the call is completed by the user's save or discard.
  [todoListTool.name]: tool({
    description: todoListTool.description,
    inputSchema: todoListTool.inputSchema,
  }),
}
components/message.tsx
<TodoList
  {...part.input}
  status={part.state === "input-streaming" ? "streaming" : undefined}
  progress={savedProgress}
  onProgress={(p) => saveProgress(part.toolCallId, p)}
  result={part.state === "output-available" ? part.output : undefined}
  onResult={(result) =>
    part.state === "output-available"
      ? saveTasks(result) // later saves: write directly
      : addToolResult({ tool: "show_todos", toolCallId: part.toolCallId, output: result })
  }
/>

The agent receives one of:

{
  action: "save",
  changes: {
    added: [{ id: "new-1", text: "Call the bank", done: false, due: "2026-10-09" }],
    updated: [{ id: "t-112", fields: { due: "2026-10-12", priority: null } }],
    completed: ["t-110"],
    reopened: [],
    deleted: ["t-113"],
    reordered: ["t-111"],
    order: ["t-111", "t-110", "t-112", "new-1"],
  },
}
{ action: "discard" }

changes is the smallest set that turns the list as loaded into the user's. A cleared field is null. reordered lists the fewest items whose move explains the new order (moving one item and back again isn't a change), and order gives every id in the final order whenever anything moved. Ids in added are the component's own (new-1, …); the tasks app assigns real ones.

A to-do list is ongoing, so the list stays usable after a save: it shows "Saved · 3 changes" and counts later edits against the saved state. Each later save calls onResult again with only the changes since the previous one. Send the first as the tool result and write later ones straight to the tasks app.

With LangGraph

Push it from a graph node with ui.push({ name: "show_todos", props: { toolCallId, ...args } }, { message }), and render the thread's UI messages with the Renderer.

Restoring from history

There are two ways to restore the list:

PropBehavior
Working copyonProgress → progressCalled on every edit with { items, baseline, handledSuggestions }. Pass it back and the list picks up where the user left off, still editable
A decisionresultPresent when the list mounts (and without progress): restores the saved list read-only with "Saved · 3 changes", or the original list with "Changes discarded". Never calls onResult

A result that arrives after mount (the user just saved) doesn't lock the list.

Schema

PropTypeNotes
titlestringe.g. "Today" or "Packing list"
source{ label }?Data-bound. Where the list lives, e.g. "Tasks app". Omit for a drafted list
itemsTodoItem[]Data-bound. Up to 100, in the tasks app's order
groupBy"none" | "due" | "priority" | "tag"Default none
timeZonestring?IANA zone for "today" and "overdue". Defaults to the viewer's
allowAddbooleanShow "Add a task" and suggestions. Default true
allowEditbooleanEdit, reorder, reschedule and delete. Ticking is always allowed. Default true
hideDonebooleanHide done items instead of collapsing them. Default false
suggestions{ text, due?, tags?, reason? }[]Up to 5 tasks the agent suggests adding

TodoItem

FieldTypeNotes
idstringData-bound. Returned in changes
textstring
doneboolean
duestring?ISO date (2026-10-09) or date-time (2026-10-09T15:00, read in timeZone, or with an offset)
priority"high" | "medium" | "low"An icon, with the level as text for screen readers
notesstring?
tagsstring[]?Up to 3. groupBy: "tag" uses the first
subtasks{ id, text, done }[]?Up to 10
assignee{ name, initials }?Shared lists. Shown as initials
isNewboolean?Added by the agent at the user's request; not in the tasks app yet

Host props: status (streaming, ready, saving, saved, discarded), onResult, onProgress, progress, result, now and className.

Source

On this page