# Ship review

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.

- Registry name: `ship-review` (`@candor/ship-review`)
- Tier: Pro. Installing it needs a Pro, Premium or Team licence key: the person runs `npx candor-ui key <key>` themselves; never ask for the key in chat or write it to a file.
- Docs: https://candor.themethodic.com/docs/ship-review

## Install

```bash
npx candor-ui add ship-review
```

Run `npx candor-ui@latest init` once first (https://candor.themethodic.com/get-started.md). Without the CLI: `npx shadcn@latest add @candor/ship-review`.
It brings in: button, chat, entity-chip, locale, locked, work, utils.
npm packages: lucide-react@^1.67.0.

## Example

```tsx
"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>

Props:

- `environment: string`: The production environment's name, e.g. "Shop".
- `components: { entity: EntityRef; change: "create" | "update" | "delete" }[]`: What ships, and whether each is new, changed or deleted.
- `checks: ShipCheck[]`: The checks it must pass before it can ship.
- `rollback: 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` (default `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`

## Strings (`shipReview`)

The fixed words, English by default. Override any of them under `shipReview` in `<LocaleProvider strings={{ shipReview: { … } }}>`; the table is exported as `shipReviewStrings`.

- `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

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

## Rules for using Candor UI

- Components take plain typed data. Never pass model output as HTML or render it as a link; messages are typed blocks.
- A control that can't be used says why: wrap it in `<Locked reason="…">`, don't just disable it.
- Don't show a value you don't have: leave it out (it renders as —) rather than guess. An estimate is labelled as one.
- Nothing is pre-selected in a decision a person makes, and the agent is named from your app's data, never from model output.
- Your app's words go in `LocaleProvider` (fixed strings) and `TermsProvider` (nouns); entity kinds and agent roles in `EntityKindsProvider` and `AgentRolesProvider`.
