Plan review card
Review a host-owned plan and explicitly approve or request changes.
Approve this plan?
Onboard 5 entities · 6 steps
Usage
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
Approve this plan?
Onboard 5 entities · 6 steps
Failure and retry
Approve this plan?
Onboard 5 entities · 6 steps
Reset restores the example. Simulate failure retains the card for retry; no backend request is made.
API reference
| Prop | Type | Behavior |
|---|---|---|
planConfigRequired | ObjectView fields | Opens the application plan tab; label defaults to Open plan |
onApproveRequired | RequestHandler | Explicit plan approval; return an attributed plan receipt |
onSuggestChangesRequired | () => void | Opens/focuses the host's change workflow without accepting the plan |
approveLabel | string | Defaults to Approve plan |
titleRequired | string | — |
description | string | Supporting guidance associated with the field. |
placement | "docked"|"inline" | — |
state | RequestStateView fields · 2 shapes | — |
onDismiss | () => void | — |
className | string | Additional classes for the component root. |
planConfigObject fields
| Field | Type | Requirement |
|---|---|---|
label | string | Optional |
onOpen | () => void | Required |
stateObject fields
Choose one of these 2 shapes. Do not combine fields excluded by the selected shape.
Option 1
| Field | Type | Requirement |
|---|---|---|
status | "ready" | "pending" | "loading" | Required |
Option 2
| Field | Type | Requirement |
|---|---|---|
status | "resolved" | Required |
recap | ResponseRecapProps | Required |
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
- 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
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 for migration mappings; application migration is outside this build.