Skip to content
Tier

Document progress queue

Pro

Per-document progress for a batch an agent works through: each document queued, working (with the stage it's at, and a bar only when the pipeline measured how far that stage is), done (with what it came to), or failed (with the reason, and Retry). A summary over the list counts each state and fills a bar with the documents finished out of the batch. That is all it claims: no time left and no overall percentage, because documents differ and only the counts are measured. While the app is still adding documents (adding), the summary says "so far" and shows no bar, since the total isn't known.

The list filters by state when there is more than one, so the failed ones are a click away, and onRetry with several failed adds "Retry 3 failed". A finished document says how long it took when the app gives both times; nothing is shown when it doesn't.

Markdown
Version 5.2.0· other versions

This page is for 5.2.0, the current release. An earlier release’s page is the Markdown that release published, so the docs match the version you have installed (candor-lock.json records it).

Not in the docs of 1.3.0, 1.4.0, 1.5.0, 1.5.1, 1.6.0, 1.7.0, 1.8.0, 1.8.1, 1.9.0, 1.9.1, 2.0.0, 2.1.0, 2.2.0, 2.3.0, 2.4.0, 2.5.0, 2.5.1, 2.6.0, 2.6.1, 2.6.2, 2.7.0, 2.8.0 and 2.9.0: the page came later, or had another name then.

Docs pages weren’t kept for 1.0.0, 1.0.1, 1.1.0, 1.1.1, 1.2.0 and 1.2.1: they are kept from 1.3.0 on, and before that only llms.txt was published.

Preview

Loading the preview

Install

With the Candor CLI

First time? Get started sets up the registry and theme. Or with shadcn directly: npx shadcn@latest add @candor/document-progress-queue

Brings in: bidi, button, format-elapsed, heading-level, heading-scope, locale, locked, progress, status, status-dot, tokens, utils · npm: lucide-react@^1.67.0

Usage

TSX · The example above
"use client"

import * as React from "react"
import { DocumentProgressQueue, type DocumentProgress } from "@/components/candor/document-progress-queue"

const at = (s: number) => new Date(Date.UTC(2026, 10, 10, 9, 0, s)).toISOString()

/* What the app's pipeline reports for each document. A stage's bar appears only where the pipeline counted pages. */
const batch: DocumentProgress[] = [
  { id: "d-1", name: "March statement.pdf", state: "done", result: "18 fields extracted", startedAt: at(0), finishedAt: at(42) },
  { id: "d-2", name: "Invoice INV-2207.pdf", state: "done", result: "11 fields extracted, 2 to check", startedAt: at(3), finishedAt: at(31) },
  { id: "d-3", name: "Invoice INV-2211.pdf", state: "working", stage: "Reading pages", progress: { done: 3, total: 12 } },
  { id: "d-4", name: "Supplier contract.pdf", state: "working", stage: "Extracting fields" },
  { id: "d-5", name: "Receipt 88.png", state: "failed", error: "The scan is too blurry to read." },
  { id: "d-6", name: "Invoice INV-2190.pdf", state: "failed", error: "The file is password-protected." },
  { id: "d-7", name: "Invoice INV-2215.pdf", state: "queued" },
  { id: "d-8", name: "Invoice INV-2219.pdf", state: "queued" },
]

export default function Example() {
  const [documents, setDocuments] = React.useState(batch)
  const [retrying, setRetrying] = React.useState<string[]>([])
  return (
    <DocumentProgressQueue
      title="Invoices from the March upload"
      className="max-w-2xl"
      documents={documents}
      retrying={retrying}
      onRetry={(ids) => {
        setRetrying((r) => [...r, ...ids])
        // Your app asks its pipeline to run them again; they come back queued.
        window.setTimeout(() => {
          setDocuments((ds) => ds.map((d) => (ids.includes(d.id) ? { id: d.id, name: d.name, state: "queued" } : d)))
          setRetrying((r) => r.filter((id) => !ids.includes(id)))
        }, 800)
      }}
      onOpen={() => {}}
    />
  )
}

<DocumentProgressQueue>

PropTypeDefaultDescription
documentsrequiredDocumentProgress[]—The batch's documents, in the order to show them.
addingboolean | undefinedfalseThe app is still adding documents to the batch: the summary counts "so far" and shows no bar.
onRetry((ids: string[]) => void) | undefined—Called with the ids of failed documents to try again: one from its row, or, with two or more that can be retried, every one of them that isn't being retried already. Without it there is no Retry.
retryingreadonly string[] | undefined—Ids the app is retrying now: their Retry is locked with the reason until their state changes.
onOpen((id: string) => void) | undefined—Called with a done document's id, to open what it came to. Without it, done documents have no Open.
titlestring | undefined—A heading over the queue, also its accessible name. Defaults to "Documents in this batch" for the name, with no heading shown.
headingLevelHeadingLevel | undefined—The heading's level when `title` is set. Defaults to one below the nearest `HeadingScope`, else 2.
classNamestring | undefined—Extra classes for the outer element.

Strings

The fixed words, in English by default. Change any of them under documentProgressQueue in <LocaleProvider strings={{ … }}>; the table is exported as documentProgressQueueStrings.

KeyEnglish
labelDocuments in this batch
documents{ one: "{count} document", other: "{count} documents" }
documentsSoFar{ one: "{count} document so far", other: "{count} documents so far" }
countDone{done} done
countWorking{working} working
countQueued{queued} queued
countFailed{failed} failed
announceFailed{ one: "{count} document failed.", other: "{count} documents failed." }
announceFinishedThe batch has finished: {done} done, {failed} failed.
finished{finished} of {total} finished
queuedQueued
workingWorking
doneDone
failedFailed
stageStep{stage}: {done} of {total}
progressName{name}: {step}
tookTook {time}
noReasonNo reason was given.
retryRetry
retryNameRetry {name}
retryFailed{ one: "Retry {count} failed", other: "Retry {count} failed" }
retryingRetrying this document.
retryingAllThe failed documents are being retried.
openOpen
openNameOpen {name}
showShow
allAll
option{state} {count}
emptyNo documents in this batch.
noneShownNo documents in this state.

Types

DocumentProgressState
Where one document is: waiting its turn, being worked on, finished, or stopped by a failure.
"queued" | "working" | "done" | "failed"
DocumentProgress
One document in the batch, as the app's pipeline reports it.
{ /** Stable for this document in the batch. */ id: string; /** The document's name, as the app shows it ("March statement.pdf"). */ name: string; state: DocumentProgressState; /** What is happening to it now, in the app's words ("Reading pages", "Extracting fields"). Shown while working. */ stage?: string | undefined; /** * How far the current stage is, as the pipeline measured it: 3 of 12 pages. Shown as a bar while working. Left out, * there is no bar and no percentage: nothing is estimated. */ progress?: { done: number; total: number; } | undefined; /** Why it failed, in the app's words. Shown on a failed document; without it, it says no reason was given. */ error?: string | undefined; /** What it came to, in the app's words ("18 fields extracted"). Shown on a done document. */ result?: string | undefined; /** When work on it started (ISO). With `finishedAt`, a finished document says how long it took. */ startedAt?: string | undefined; /** When it finished, done or failed (ISO). */ finishedAt?: string | undefined; /** Why it can't be retried, when it can't (a file that can't be read again). Locks its Retry with the reason. */ cannotRetry?: string | undefined; }

Types from other items

The props above take these. Each is defined by another item; import it from the path shown.

HeadingLevel from @/lib/candor/heading-level
A heading's level in the page's outline: `1` is `<h1>`, `6` is `<h6>`. Every Candor item that draws a heading takes its `headingLevel` as this type; the level is where the heading sits in the outline, never how large it looks.
1 | 2 | 3 | 4 | 5 | 6