Bulk Runner

Runs a bulk job with live done, failed and remaining counts, so a stopped or failed job never ends in "some of it happened, probably".

StopsBulk job half done, nobody knows

Installation

pnpm dlx shadcn@latest add https://bollard-ui.vercel.app/r/bulk-runner.json

The files land in your project. They're yours to edit.

Usage

bulk-runner-usage.tsx
import { BulkRunner } from "@/components/ui/bulk-runner"

<BulkRunner
  key={run}
  title="Email 500 users: October invoice"
  items={users}
  run={sendEmail}
  autoStart
  doneVerb="emailed"
  untouchedText="were not emailed"
/>

What it catches

  • Unclear state after a stop, by reporting exactly what was done and left untouched
  • Repeated failures, grouped by reason and sorted by count
  • Retrying everything, by offering retry of failed items only
  • Runaway parallelism, via a concurrency limit

Props

BulkRunner props
PropTypeDefault
items* (required)

Everything the operation will touch.

T[]—
run* (required)

Processes one item. Throw (or reject) with an Error whose message is the reason it failed.

(item: T, signal: AbortSignal) => Promise<void>—
getLabel

Name shown for an item in the failure list. Defaults to `String(item)`.

(item: T) => stringString
concurrency

How many items run at once.

number4
autoStart

Start as soon as it mounts instead of showing a Start button.

booleanfalse
title* (required)

Heading, e.g. "Email 500 users".

string—
doneVerb

Past tense for finished items: "emailed", "deleted".

string"done"
untouchedText

What the operation didn't do to items it never reached: "were not emailed".

string"were not touched"
startLabel

Start button text.

string"Start"
pauseLabel

Pause button text.

string"Pause"
resumeLabel

Resume button text.

string"Resume"
stopLabel

Stop button text.

string"Stop"
retryLabel

Retry-failed button text ({failed} is filled in).

string"Retry {failed} failed"
pausingText

Templates: `{done}`, `{failed}`, `{left}`, `{total}`, `{inflight}`, `{verb}` and `{untouched}` are filled in.

string"Pausing after {inflight} in progress finish…"
stoppingText

Shown while in-flight items finish after Stop.

string"Stopping after {inflight} in progress finish…"
stoppedText

Shown when the job was stopped.

string"Stopped. {done} {verb}, {failed} failed, {left} {untouch…
finishedText

Shown when every item has run.

string"Finished. {done} {verb}, {failed} failed."

* required. Also accepts the native props of <div> (className, aria-*, data-*…), passed through to it.

Accessibility

  • role="progressbar" with aria-valuenow/max and an aria-valuetext reading done, failed and remaining
  • Phase changes are announced in an aria-live="polite" paragraph
  • All controls are native buttons with visible text
  • Motion respects prefers-reduced-motion

Only what the source does. Test with your own screen reader and content before shipping.

Limitations

This is a UX guard rail. Enforce on the server: Make each item operation idempotent and record per-item results on the server, so a retry or a reloaded tab doesn't repeat completed work.

  • Runs in the browser tab: closing or reloading the tab stops the job and loses its progress
  • Pause and Stop wait for in-flight items; it cannot cancel a request already sent
  • Retries call your run function again, so it must be safe to repeat

What it stops

Bulk job half done, nobody knows

Also known as

bulk job · batch progress · progress bar · queue · retry failed · import job · bulk email · background job

Field type

Dependencies

Pairs well with