Ship review
ProThe last look before a change reaches production: what ships, each check it must pass (validation, tests, approval), and how to undo it. The ship button stays locked until every check passes, and the lock names the ones that haven't. Asking for changes instead (when the app offers it) takes an optional note saying what to change.
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/ship-review
Brings in: button, chat, entity-chip, locale, locked, work, utils · npm: lucide-react@^1.67.0
Usage
"use client"
import { ShipReview, type ShipCheck } from "@/components/ship-review"
const checks: ShipCheck[] = [
{ id: "c1", label: "Migration dry run", state: "pass", detail: "Ran against a Production snapshot" },
{ id: "c2", label: "CI tests", state: "fail", detail: "1 failure; coverage 71%, Production needs 75%" },
{ id: "c3", label: "Approval", state: "pending", detail: "Waiting for an approver" },
]
export default function Example() {
return (
<ShipReview
className="max-w-2xl"
environment="Acme web"
components={[
{ entity: { kind: "migration", name: "0142_add_customer_region", label: "Add customers.region" }, change: "create" },
{ entity: { kind: "component", name: "web/src/customers/CustomerHeader.tsx", label: "CustomerHeader" }, change: "update" },
{ entity: { kind: "file", name: "api/src/customers/region.ts", label: "region.ts" }, change: "update" },
]}
checks={checks}
rollback={{ available: true, how: "Revert the pull request and run the migration's down step." }}
window="Tonight 22:00–23:00"
onShip={() => {}}
onRequestChanges={() => {}}
/>
)
}<ShipReview>
| Prop | Type | Default | Description |
|---|---|---|---|
| environmentrequired | string | — | The production environment's name, e.g. "Shop". |
| componentsrequired | { entity: EntityRef; change: "create" | "update" | "delete" }[] | — | What ships, and whether each is new, changed or deleted. |
| checksrequired | ShipCheck[] | — | The checks it must pass before it can ship. |
| rollbackrequired | Rollback | — | How to undo it, or why it can't be undone. |
| window | string | undefined | — | When it may ship (review level R4). |
| onShip | (() => void) | undefined | — | Ship it. Without it, there's no ship button. |
| shipping | boolean | undefined | false | Shipping is under way. |
| onRequestChanges | ((note?: string) => void) | undefined | — | Send it back instead of shipping, with an optional note on what to change. Without it, there's no button. |
| locked | string | undefined | — | Why neither action can be used right now, e.g. the agent is still replying. |
| className | string | undefined | — | Extra classes for the outer element. |
Strings
The fixed words, in English by default. Change any of them under shipReview in <LocaleProvider strings={{ … }}>; the table is exported as shipReviewStrings.
| Key | English |
|---|---|
| pass | Passed |
| fail | Failed |
| running | Running |
| pending | Not run |
| waitingOn | Waiting on: {checks}. |
| title | Ship to {environment} production |
| components | { one: "{count} component", other: "{count} components" } |
| summary | {components} · {create} new · {update} changed · {delete} deleted |
| whatShips | What ships |
| create | New |
| update | Changed |
| delete | Deleted |
| checks | Checks |
| undo | Undo |
| cantUndo | Can’t be undone: {reason} |
| askForChanges | Ask for changes |
| whatShouldChange | What should change? |
| optional | (optional) |
| nothingShips | Nothing ships. It goes back for more work. |
| cancel | Cancel |
| sendBack | Send back |
| inWindow | Ships in your window: {window} |
| onConfirm | Ships as soon as you confirm |
| shipping | Shipping… |
| ship | Ship to production |
Types
- ShipCheck
{ id: string label: string state: "pass" | "fail" | "running" | "pending" /** Shown under the label; required when it fails. */ detail?: string | undefined }