Domain

ApprovalCard

Durable human-in-the-loop approval surface for reviewing risky tool operations before they execute.

import { ApprovalCard } from '@lostgradient/cinder/approval-card';
approvalhuman-in-the-looptool
01

Overview

Presentational human-in-the-loop approval surface for reviewing a tool operation before the host application executes it.

Usage

svelte
<script lang="ts">
  import { ApprovalCard } from '@lostgradient/cinder/approval-card';
</script>

<ApprovalCard
  tool={{ name: 'deploy-cloud', risk: 'medium' }}
  sandbox={{
    provider: 'codex',
    name: 'workspace-write',
    workingDir: '/workspace/project',
  }}
  operation={{
    kind: 'command',
    command: 'bun run --filter=@lostgradient/cinder validate',
    argsPreview: { package: '@lostgradient/cinder' },
  }}
  env={['DATABASE_URL', 'OPENAI_API_KEY']}
  policyVersion="policy-2026-06"
  idempotencyKey="approval-01JZ8T"
  state="pending"
  onResolve={(resolution) => resolveApproval(resolution)}
/>

ApprovalCard is fully controlled. It does not execute commands, apply patches, persist policy decisions, or read environment values. The component only renders the supplied context and invokes callback props for host-owned actions.

Resolution callback

onResolve is the single decision contract. It fires for every action — Approve, Approve with edits, Deny, and Dismiss — with the complete ApprovalResolution payload: the selected decision, parsed editedArgs for edited approvals, optional reason text, and the remember checkbox state. Action buttons render only while the request is actionable and onResolve is wired; a card without the callback is purely presentational.

decision: 'deny' means the approver actively refused the operation. decision: 'cancel' (the Dismiss button) means the prompt was dismissed without a decision.

Operation rendering

  • tool.name renders in monospace inside the title, distinguishing the identifier from the surrounding sentence.
  • tool.risk renders as a stacked-bar signal icon (bar count scales with risk, so it doesn't rely on color alone) with a tooltip carrying the risk label; the icon itself is the accessible name via aria-label.
  • operation.kind: 'command' renders the command as a syntax-highlighted shell CodeBlock.
  • operation.kind: 'patch' renders the supplied unified patch as a syntax-highlighted diff.
  • operation.filesTouched renders one row per unique path, each with a copy button. Duplicate paths are collapsed.
  • operation.argsPreview renders through PayloadInspector; oversized previews are replaced with a bounded truncation notice before rendering.
  • env renders variable names only, as plain text. Values are not accepted and are stripped if a caller accidentally passes NAME=value.
  • Sandbox context, environment names, and the policy version / idempotency key / snapshot id all live in a single collapsed "Details" disclosure so supporting context stays out of the approver's way until they ask for it. The action buttons are the last element of a pending card.

Approval states

Pending requests render action buttons. Non-pending requests render a read-only, state-tinted summary. When expiresAt passes while state is still pending, the effective state becomes expired, actions disappear, and no callback fires automatically.

Heading levels

The card title defaults to an h3 with section headings one level deeper. Pass headingLevel to fit the card into the host page's document outline, matching the Card convention.

Live preview
02

When to use

Use when
  • A tool call, command, file write, or patch needs explicit human approval before execution.
  • Showing policy, sandbox, idempotency, environment-name, and argument context for an approval request.
Avoid when
  • The action has already completed and only needs historical display — use event-stream-viewer or run-step-timeline instead.
  • Collecting arbitrary form input for a workflow — compose form controls directly instead.
03

Examples

Command approval

A pending command approval with sandbox context, arguments, environment names, and editable arguments.

Expired approval

A read-only approval request whose pending decision window has expired.

Patch approval

A high-risk patch approval that previews the proposed diff, touched files, and arguments.

04

Props

Props for approval-card
Name Type Default Required Bindable Description
tool { /** Human-readable tool name requesting approval. */ name: string; /** Risk level assigned by the policy evaluator. */ risk: ApprovalToolRisk; } req Tool requesting approval.
sandbox { /** Sandbox provider, such as Codex or a remote execution backend. */ provider: string; /** Sandbox profile or policy name. */ name: string; /** Working directory for the pending operation. */ workingDir: string; } Optional sandbox context in which the operation will run.
operation | ApprovalCommandOperationApprovalFileWriteOperationApprovalPatchOperationApprovalOtherOperation req Operation details shown to the approver.
env string[] [] Environment variable names only. Values are ignored if accidentally supplied as NAME=value.
snapshotId text Snapshot identifier for the pending approval context.
policyVersion text req Policy version that produced the approval request.
idempotencyKey text req Idempotency key that makes repeated decisions durable.
expiresAt text Optional ISO timestamp after which a pending approval is treated as expired.
state 'pending''approved''approved_with_edits''denied''expired''cancelled' req Persisted approval state.
editableArgs boolean false Whether approving with edited JSON arguments is available. Default false.
headingLevel 23456 3 Heading level for the card title; section headings render one level deeper. Default 3.
onResolve (resolution: ApprovalResolution) => void Called for approve, approve-with-edits, deny, and dismiss with the complete resolution payload.
id unknown req
05

Accessibility

Renders as an article with native button controls so approval actions are discoverable without polluting the landmark list.
Expiration changes update the visible state but do not fire approval callbacks automatically.