Document progress queue
ProPer-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.
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
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
"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>
| Prop | Type | Default | Description |
|---|---|---|---|
| documentsrequired | DocumentProgress[] | — | The batch's documents, in the order to show them. |
| adding | boolean | undefined | false | The 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. |
| retrying | readonly 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. |
| title | string | undefined | — | A heading over the queue, also its accessible name. Defaults to "Documents in this batch" for the name, with no heading shown. |
| headingLevel | HeadingLevel | undefined | — | The heading's level when `title` is set. Defaults to one below the nearest `HeadingScope`, else 2. |
| className | string | 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.
| Key | English |
|---|---|
| label | Documents 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." } |
| announceFinished | The batch has finished: {done} done, {failed} failed. |
| finished | {finished} of {total} finished |
| queued | Queued |
| working | Working |
| done | Done |
| failed | Failed |
| stageStep | {stage}: {done} of {total} |
| progressName | {name}: {step} |
| took | Took {time} |
| noReason | No reason was given. |
| retry | Retry |
| retryName | Retry {name} |
| retryFailed | { one: "Retry {count} failed", other: "Retry {count} failed" } |
| retrying | Retrying this document. |
| retryingAll | The failed documents are being retried. |
| open | Open |
| openName | Open {name} |
| show | Show |
| all | All |
| option | {state} {count} |
| empty | No documents in this batch. |
| noneShown | No 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