Skip to content
ResearchResearch progressMenu

Research progress

A deep research run from plan to report: four phases, the sources read, elapsed time, the page being read right now, and Cancel.

Plan, phases, sources read, the page being read right now, then the report.

Installation

$ pnpm dlx shadcn@latest add https://chatcn.dev/r/research-progress.json

Usage

tsx
import { ResearchProgress } from "@/components/ui/chatcn/research-progress"

<ResearchProgress
  title="How long should a line of text be on screen?"
  status="running"
  phase="reading"
  plan={plan}
  sourcesRead={37}
  sourcesFound={112}
  current={{ kind: "read", text: "Readability: the optimal line length", url }}
  startedAt={run.startedAt}
  onCancel={cancel}
  onOpenReport={openReport}
/>

Research agents usually stream their state as a data part. Send one data-research part and replace its data on every update, then pass the fields straight through. The demo builds exactly that with the chatcn script() builder.

tsx
const part = message.parts.find((p) => p.type === "data-research")
return part ? <ResearchProgress title={question} {...part.data} onCancel={stop} /> : null

Examples

Every state

A cancelled run that has read enough can still offer a partial report. A failed run says what went wrong and how to recover.

Queued, complete, cancelled with a partial report, and failed.

States

StatusShows
queuedAn empty circle, when the run will start, and Cancel
runningSpinner, elapsed time, phases, the live line, plan and Cancel
completeCheck, duration, sources read, a summary and Open report
cancelledStop icon, where it stopped, and Open partial report when onOpenReport is set
failedRed cross, Failed, the error and Retry research

Props

PropTypeNotes
titleReactNodeThe question or topic
statusResearchStatusqueued, running, complete, cancelled or failed
phaseResearchPhaseplanning, searching, reading or writing. Earlier phases read as done
phaseLabelsPartial<Record<ResearchPhase, string>>Rename phases
planResearchPlanStep[]{ id, title, status? } with pending, active, done or skipped
sourcesRead, sourcesFoundnumberRendered as is on every update. No count-up
currentResearchCurrent{ kind, text, url? } for the live line
activityResearchActivity[]Searches and reads for the collapsed log
startedAt, durationMsnumberOptional. Measured when omitted
error, onRetryFor failed
summaryReactNodeOne line on complete
onCancel() => voidShows Cancel while queued or running
onOpenReport, reportLabelThe button on complete and cancelled

Keyboard

KeyAction
TabCancel, the activity log toggle and the report button
Enter, SpaceOpen or close the activity log

Accessibility

  • Phases and plan steps are ordered lists. The current one carries aria-current="step", and done items say done to screen readers.
  • A polite live region announces each phase change and the result once. The source count is never announced per update.
  • The live line's shimmer is static under reduced motion.

Motion

  • The page being read crossfades in from a 2 px blur over 150 ms: one line changing, not a list scrolling past. Opacity only under reduced motion.
  • Status and plan icons swap with the 150 ms blur crossfade. Counters and the timer change in place, once per update and once per second.
  • The activity log opens with a 200 ms height and opacity transition and follows new entries only while you are at its bottom.