ApprovalGate
Human-approval checkpoint card with risk tier, exact argument payload, and approve/reject/edit actions.
- env
- prod
- service
- billing-api
- replicas
- 4
- flags
- {"canary":true,"drain":30}
Source
"use client"
import { ApprovalGate } from "@/registry/warren/approval-gate/approval-gate"
import type { ApprovalRequest } from "@/registry/warren/approval-gate/approval-gate"
const request: ApprovalRequest = {
id: "req-3",
gate: "deploy:prod",
step: 3,
action: "kubectl.rollout",
args: {
env: "prod",
service: "billing-api",
replicas: 4,
flags: { canary: true, drain: 30 },
},
risk: "critical",
reason: "Writes to the production cluster outside the deploy window.",
requestedAt: "14:02:11",
expiresAt: "14:12:11",
}
export default function ApprovalGateDemo() {
return (
<div className="w-full max-w-xl">
<ApprovalGate
request={request}
state="pending"
onDecision={(decision) => console.log("decision:", decision)}
/>
</div>
)
}
Installation
npx shadcn@latest add https://warren.eduard3v.com/r/approval-gate.jsonUsage
import { ApprovalGate } from "@/components/ui/approval-gate"<ApprovalGate request={request} state="pending" onDecision={decide} />Structure
The supervision primitive for human-in-the-loop pipelines. A paused checkpoint
renders as a bordered card tinted by its state — warning while pending, success
once approved, danger when rejected, muted after expiry. The header carries the
state phrase (APPROVAL REQUIRED), gate coordinates (GATE deploy:prod · STEP 3),
and two chips: the risk tier and the lifecycle state. critical is the only tier
rendered as a solid saturated chip; every other tier stays outlined with text-grade
ink.
The body is a mono key-value table: the proposed action in bold, every argument
from request.args rendered at its exact payload shape (strings verbatim,
everything else through JSON.stringify — never a lossy [object Object]), then
the escalation reason. An audit strip shows REQUESTED/EXPIRES timestamps.
While pending and wired to onDecision, the footer offers APPROVE / EDIT / REJECT.
Any decided state hides the action row and reports itself in the header.
Compact variant
- migration
- 0042_add_ledger_index
- env
- staging
- migration
- 0042_add_ledger_index
- env
- staging
- migration
- 0042_add_ledger_index
- env
- staging
- migration
- 0042_add_ledger_index
- env
- staging
Source
"use client"
import { ApprovalGate } from "@/registry/warren/approval-gate/approval-gate"
import type { ApprovalRequest } from "@/registry/warren/approval-gate/approval-gate"
const base: ApprovalRequest = {
id: "req-7",
gate: "db:migrate",
step: 5,
action: "postgres.migrate",
args: { migration: "0042_add_ledger_index", env: "staging" },
risk: "medium",
reason: "Schema rewrite requires operator sign-off.",
requestedAt: "09:14:03",
}
export default function ApprovalGateStates() {
return (
<div className="flex w-full max-w-xl flex-col gap-3">
<ApprovalGate
request={{ ...base, risk: "high" }}
state="pending"
onDecision={() => {}}
/>
<ApprovalGate request={base} state="approved" />
<ApprovalGate request={base} state="rejected" />
<ApprovalGate
request={{ ...base, expiresAt: "09:24:03" }}
state="expired"
/>
<ApprovalGate request={base} state="pending" compact onDecision={() => {}} />
<ApprovalGate request={base} state="approved" compact />
</div>
)
}
Pass compact for the inline-in-stream record: one hairline row with the status
dot, gate coordinates, risk chip, and — while pending — bare APPROVE/REJECT buttons.
Decided states swap the buttons for the state chip.
API Reference
Prop
Type
Prop
Type
Prop
Type
Prop
Type