Ship review

Pro

The 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.

Markdown

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

TSX · The example above
"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>

PropTypeDefaultDescription
environmentrequiredstring—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.
checksrequiredShipCheck[]—The checks it must pass before it can ship.
rollbackrequiredRollback—How to undo it, or why it can't be undone.
windowstring | undefined—When it may ship (review level R4).
onShip(() => void) | undefined—Ship it. Without it, there's no ship button.
shippingboolean | undefinedfalseShipping 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.
lockedstring | undefined—Why neither action can be used right now, e.g. the agent is still replying.
classNamestring | 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.

KeyEnglish
passPassed
failFailed
runningRunning
pendingNot run
waitingOnWaiting on: {checks}.
titleShip to {environment} production
components{ one: "{count} component", other: "{count} components" }
summary{components} · {create} new · {update} changed · {delete} deleted
whatShipsWhat ships
createNew
updateChanged
deleteDeleted
checksChecks
undoUndo
cantUndoCan’t be undone: {reason}
askForChangesAsk for changes
whatShouldChangeWhat should change?
optional(optional)
nothingShipsNothing ships. It goes back for more work.
cancelCancel
sendBackSend back
inWindowShips in your window: {window}
onConfirmShips as soon as you confirm
shippingShipping…
shipShip to production

Types

ShipCheck
{ id: string label: string state: "pass" | "fail" | "running" | "pending" /** Shown under the label; required when it fails. */ detail?: string | undefined }