# Plan review card

Package: @commenda/pathfinder@0.1.0
Page: /docs/components/plan-review-card
Figma: https://www.figma.com/design/dn7BkGYEfIyP0o2zfesfVh/The-System?node-id=607-4610

```tsx
import { PlanReviewCard } from '@commenda/pathfinder/plan-review-card';

<PlanReviewCard
  key={request.id}
  title="Approve this plan?"
  description="Onboard 5 entities · 6 steps"
  planConfig={{ onOpen: openPlanTab }}
  onSuggestChanges={focusComposer}
  onApprove={approvePlan}
/>;
```


## Usage [#usage]

```tsx
import { PlanReviewCard } from '@commenda/pathfinder/plan-review-card';

<PlanReviewCard
  key={request.id}
  title="Approve this plan?"
  description="Onboard 5 entities · 6 steps"
  planConfig={{ onOpen: openPlanTab }}
  onSuggestChanges={focusComposer}
  onApprove={approvePlan}
/>;
```

## Inline plan review [#inline-plan-review]


```tsx
import { PlanReviewCard } from '@commenda/pathfinder/plan-review-card';

<PlanReviewCard
  placement="inline"
  key={request.id}
  title="Approve this plan?"
  description="Onboard 5 entities · 6 steps"
  planConfig={{ onOpen: openPlanTab }}
  onSuggestChanges={focusComposer}
  onApprove={approvePlan}
/>;
```


## Failure and retry [#failure-and-retry]


```tsx
// Return { status: 'failed', message: 'Please try again.' } from the handler to retain this card.
import { PlanReviewCard } from '@commenda/pathfinder/plan-review-card';

<PlanReviewCard
  key={request.id}
  title="Approve this plan?"
  description="Onboard 5 entities · 6 steps"
  planConfig={{ onOpen: openPlanTab }}
  onSuggestChanges={focusComposer}
  onApprove={approvePlan}
/>;
```


Reset restores the example. Simulate failure retains the card for retry; no backend request is made.

## API reference [#api-reference]

| Prop | Type | Requirement | Behavior |
| --- | --- | --- | --- |
| `planConfig` | `{ label?: string; onOpen: () => void; }` | required | Opens the application plan tab; label defaults to Open plan |
| `onApprove` | `RequestHandler` | required | Explicit plan approval; return an attributed plan receipt |
| `onSuggestChanges` | `(() => void)` | required | Opens/focuses the host's change workflow without accepting the plan |
| `approveLabel` | `string` | optional | Defaults to Approve plan |
| `title` | `string` | required |  |
| `description` | `string` | optional | Supporting guidance associated with the field. |
| `placement` | `"docked" \| "inline"` | optional |  |
| `state` | `RequestState` | optional |  |
| `onDismiss` | `(() => void)` | optional |  |
| `className` | `string` | optional | Additional classes for the component root. |

### planConfig fields

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `label` | `string` | No |  |
| `onOpen` | `() => void` | Yes |  |

### state fields

Choose one shape; excluded fields cannot be combined.

#### Option 1

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `status` | `"ready" \| "pending" \| "loading"` | Yes |  |

#### Option 2

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `status` | `"resolved"` | Yes |  |
| `recap` | `ResponseRecapProps` | Yes |  |

### Shared request props [#shared-request-props]

| Prop          | Type                   | Behavior                                                                 |
| ------------- | ---------------------- | ------------------------------------------------------------------------ |
| `title`       | `string`               | Required card title.                                                     |
| `description` | `string`               | Optional supporting text.                                                |
| `placement`   | `'docked' \| 'inline'` | Docked by default; parent controls width and placement                   |
| `state`       | `RequestState`         | Ready, loading skeleton, externally pending, or resolved with a recap    |
| `onDismiss`   | `() => void`           | Temporary dismissal via Not now or scoped Escape; not a submitted answer |
| `className`   | `string`               | Layout override                                                          |

`RequestHandler<T>` returns `{ status: 'accepted', recap: ResponseRecapProps }` or `{ status: 'failed', message: string }`, directly or as a Promise. A thrown error shows a retry message and preserves the draft. The host persists accepted responses; the component displays the returned receipt. `state={{ status: 'resolved', recap }}` restores history. Use a stable request ID as the React key, and change the key when the request changes. External pending is a rendering state; authorization and idempotency remain server responsibilities.

**REQUEST-01:** Docked tray uses pearl100, a raised shadow, radius16 and a white radius10 inset with 16px padding. Header40, footer at least44; controls wrap on narrow screens.

**REQUEST-02:** The parent places the request 12px above Composer at the same width. Inline requests are flat and fill their message column. Escape only temporarily dismisses; no global listener is installed.

**REQUEST-03:** Reading content does not animate. Existing Button and Popover motion respects reduced motion. Pending retains the draft and exposes a polite status. Acceptance never precedes the host result.

## Design rules [#design-rules]

- **PLAN-01:** Keep the card compact: title, short scope summary, Open plan, Suggest changes, Approve plan. No Skip and no embedded plan editor.
- **PLAN-02:** Opening the plan or asking for changes does not approve it. Return a `kind: 'plan'` recap with actor and timestamp after explicit server acceptance.

The host owns the plan version. Key each review by request/version ID so an in-flight response cannot approve a newly replaced plan.

## Integration and accessibility [#integration-and-accessibility]

Buttons have explicit names and keyboard focus. No network transport or AI SDK provider is required. Requests, tool execution, permission checks, durable outcomes and composer coordination stay in the application. See [AI content integration](/docs/guides/ai-content) for migration mappings; application migration is outside this build.
