Approval Request with Blast Radius
The agent stops and asks: the effect stated before the verb, choices as radios in a fieldset so picking is separate from committing, no pre-focused Approve, and the decision left on screen afterwards.
Where a person sits in an automated run, written as four guarantees rather than as a promise that a human is involved.
components/human-oversight-split.tsxnpx hoverlab add human-oversight-split
Or over MCP, from your editor's agent — no account needed.
Free to read, copy and install, for personal and non-commercial projects. Shipping it in client work or a paid product needs Pro ($79 once). The source lands in your repo and stops being ours — no attribution, nothing to upgrade.
Start a page with this section — add more, order them, and leave with the page source.
Oversight
"A human is in the loop" is a claim with no shape. These are the four points where one really is, and each is a thing the system will not do without them.
Rendered live in your current theme — this is the same component whose source is below, not a screenshot of it.
/**
* <HumanOversightSplit> — Where a person sits in an automated run, written as four guarantees rather than as a promise that a human is involved.
*
* "A human is in the loop" is a claim with no shape, and the layout problem
* is turning it into something falsifiable. The obvious wrong answer is a
* paragraph about responsible AI, which commits to nothing and is
* indistinguishable from every other such paragraph.
*
* Four points, each naming a place where the system will not proceed
* without a person. Written as guarantees rather than as intentions,
* because a guarantee can be checked against the product and an intention
* cannot.
*
* The second point — pausing on low confidence — is the one that separates
* real oversight from theatre. A system that always produces an answer has
* no low-confidence branch, and the human in its loop is only ever
* reviewing outputs that were going to be produced anyway.
*
* The fourth point is deliberately mundane. A stop button that is always on
* screen is worth more than the three principles above it, because it is
* the one that works when the other three have failed, and "always on
* screen" is the part that is usually not true.
*
* Accessibility: the tick `<svg>` carries `aria-hidden` and
* `fill="currentColor"`, so it inherits the text colour and is not
* announced; the section is named by its heading through `aria-labelledby`;
* the points are a genuine `<ul>` so a screen reader announces four items
* before reading them, which is what makes a list of guarantees scannable
* by ear.
*
* The panel is drawn rather than an image, so it themes correctly and adds
* no asset. Pass `media` to show the actual approval card.
*/
import * as React from 'react'
export interface HumanOversightSplitPoint {
label: string
detail?: string
}
export interface HumanOversightSplitProps {
eyebrow?: string
heading?: string
intro?: string
points?: HumanOversightSplitPoint[]
/** Your own visual. Omit for the drawn panel, which needs no asset. */
media?: React.ReactNode
className?: string
}
const POINTS: HumanOversightSplitPoint[] = [
{ label: "Before anything leaves", detail: "External messages and payments stop and wait. The full payload is shown, not a summary of it." },
{ label: "On low confidence", detail: "The run pauses rather than picking the best of four bad options and presenting it as an answer." },
{ label: "After the fact, in the log", detail: "Every unattended action is recorded with its inputs, and the log is exportable." },
{ label: "The stop button", detail: "One press halts a run mid-step and rolls back anything reversible. It is always on screen." },
]
/*
Per-instance id, hashed from props that differ between instances.
A literal id is a latent duplicate the moment this block is rendered
twice on one document -- two pages on a catalog hub, or one page using
the section twice. `aria-labelledby` pointing at a duplicated id resolves
to whichever element comes first, so the second copy is announced with
the first copy's label. Server component, so no `useId`: hashing props
gives each instance its own target and stays stable across server and
client renders in a way a counter would not.
*/
function instanceId(...parts: (string | undefined)[]): string {
const text = parts.filter(Boolean).join('|')
let hash = 0
for (let i = 0; i < text.length; i++) hash = (Math.imul(hash, 31) + text.charCodeAt(i)) | 0
return (hash >>> 0).toString(36).slice(0, 6)
}
export function HumanOversightSplit({
eyebrow = "Oversight",
heading = "Where the person actually is",
intro = "\"A human is in the loop\" is a claim with no shape. These are the four points where one really is, and each is a thing the system will not do without them.",
points = POINTS,
media,
className,
}: HumanOversightSplitProps) {
const uid = instanceId(heading, eyebrow)
return (
<section
aria-labelledby={`human-oversight-split-heading-${uid}`}
className={`w-full bg-background px-6 py-16 sm:py-24 ${className ?? ''}`}
>
<div className="mx-auto grid max-w-6xl items-center gap-12 lg:grid-cols-2">
<div>
<p className="text-sm font-medium text-primary">{eyebrow}</p>
<h2
id={`human-oversight-split-heading-${uid}`}
className="mt-2 text-3xl font-semibold tracking-tight text-foreground sm:text-4xl"
>
{heading}
</h2>
<p className="mt-4 text-base text-muted-foreground">{intro}</p>
<ul className="mt-8 space-y-4">
{points.map((point) => (
<li key={point.label} className="flex gap-3">
{/*
currentColor, not a token in a raw colour function. These
are complete oklch() values, so hsl(var(--primary)) is not
a colour and the declaration is dropped silently.
*/}
<svg
aria-hidden="true"
viewBox="0 0 20 20"
className="mt-0.5 size-5 shrink-0 text-primary"
fill="currentColor"
>
<path d="M16.7 5.3a1 1 0 0 1 0 1.4l-7.5 7.5a1 1 0 0 1-1.4 0L3.3 9.7a1 1 0 1 1 1.4-1.4l3.8 3.8 6.8-6.8a1 1 0 0 1 1.4 0Z" />
</svg>
<span>
<span className="block text-sm font-medium text-foreground">{point.label}</span>
{point.detail ? (
<span className="mt-0.5 block text-sm text-muted-foreground">
{point.detail}
</span>
) : null}
</span>
</li>
))}
</ul>
</div>
{/*
The drawn panel rather than an <img>. No asset to host, no layout
shift while it loads, and it themes with the rest of the page —
which a screenshot of somebody's light-mode dashboard does not.
*/}
<div className="rounded-xl border border-border bg-card p-6 shadow-sm">
{media ?? (
<div aria-hidden="true" className="space-y-3">
<div className="h-3 w-1/3 rounded bg-primary/30 border border-transparent" />
<div className="h-24 rounded-lg bg-muted border border-transparent" />
<div className="grid grid-cols-3 gap-3">
<div className="h-14 rounded-lg bg-muted border border-transparent" />
<div className="h-14 rounded-lg bg-muted border border-transparent" />
<div className="h-14 rounded-lg bg-muted border border-transparent" />
</div>
<div className="h-3 w-2/3 rounded bg-muted border border-transparent" />
<div className="h-3 w-1/2 rounded bg-muted border border-transparent" />
</div>
)}
</div>
</div>
</section>
)
}
bg-card, text-muted-foreground) — it inherits your theme instead of overriding it.Drop it at components/human-oversight-split.tsx and import it where you need the section:
import { HumanOversightSplit } from '@/components/human-oversight-split'2 of this block’s props are simple enough to drive from here. Change them and the block below re-renders — it is the same component whose source is above, not a mock of it. Everything else it accepts is in the table underneath.
Read out of the component’s own type and signature, so this cannot drift from the source below. Every prop has a default — the component renders standalone before you pass it anything.
| Prop | Type | Default |
|---|---|---|
eyebrow | string | "Oversight" |
heading | string | "Where the person actually is" |
intro | string | — |
points | HumanOversightSplitPoint[] | POINTS |
mediaYour own visual. Omit for the drawn panel, which needs no asset. | React.ReactNode | — |
className | string | — |
The same block rendered once to markup, wrapped as a file your framework compiles. Tailwind classes are framework-agnostic, so the design transfers intact — the behaviour does not.
<!--
Human Oversight Split — markup from the Hoverlab catalog.
This is the block rendered once to HTML and wrapped as a component
file. It is not a port of the React source: the Tailwind classes carry
the design, which is the part that took the work, and they are the same
in every framework.
This block has no interactive behaviour, so nothing is missing.
-->
<section aria-labelledby="human-oversight-split-heading-15qnsx" class="w-full bg-background px-6 py-16 sm:py-24 ">
<div class="mx-auto grid max-w-6xl items-center gap-12 lg:grid-cols-2">
<div>
<p class="text-sm font-medium text-primary">Oversight</p>
<h2 id="human-oversight-split-heading-15qnsx" class="mt-2 text-3xl font-semibold tracking-tight text-foreground sm:text-4xl">Where the person actually is</h2>
<p class="mt-4 text-base text-muted-foreground">"A human is in the loop" is a claim with no shape. These are the four points where one really is, and each is a thing the system will not do without them.</p>
<ul class="mt-8 space-y-4">
<li class="flex gap-3">
<svg aria-hidden="true" viewBox="0 0 20 20" class="mt-0.5 size-5 shrink-0 text-primary" fill="currentColor">
<path d="M16.7 5.3a1 1 0 0 1 0 1.4l-7.5 7.5a1 1 0 0 1-1.4 0L3.3 9.7a1 1 0 1 1 1.4-1.4l3.8 3.8 6.8-6.8a1 1 0 0 1 1.4 0Z"></path>
</svg>
<span>
<span class="block text-sm font-medium text-foreground">Before anything leaves</span>
<span class="mt-0.5 block text-sm text-muted-foreground">External messages and payments stop and wait. The full payload is shown, not a summary of it.</span>
</span>
</li>
<li class="flex gap-3">
<svg aria-hidden="true" viewBox="0 0 20 20" class="mt-0.5 size-5 shrink-0 text-primary" fill="currentColor">
<path d="M16.7 5.3a1 1 0 0 1 0 1.4l-7.5 7.5a1 1 0 0 1-1.4 0L3.3 9.7a1 1 0 1 1 1.4-1.4l3.8 3.8 6.8-6.8a1 1 0 0 1 1.4 0Z"></path>
</svg>
<span>
<span class="block text-sm font-medium text-foreground">On low confidence</span>
<span class="mt-0.5 block text-sm text-muted-foreground">The run pauses rather than picking the best of four bad options and presenting it as an answer.</span>
</span>
</li>
<li class="flex gap-3">
<svg aria-hidden="true" viewBox="0 0 20 20" class="mt-0.5 size-5 shrink-0 text-primary" fill="currentColor">
<path d="M16.7 5.3a1 1 0 0 1 0 1.4l-7.5 7.5a1 1 0 0 1-1.4 0L3.3 9.7a1 1 0 1 1 1.4-1.4l3.8 3.8 6.8-6.8a1 1 0 0 1 1.4 0Z"></path>
</svg>
<span>
<span class="block text-sm font-medium text-foreground">After the fact, in the log</span>
<span class="mt-0.5 block text-sm text-muted-foreground">Every unattended action is recorded with its inputs, and the log is exportable.</span>
</span>
</li>
<li class="flex gap-3">
<svg aria-hidden="true" viewBox="0 0 20 20" class="mt-0.5 size-5 shrink-0 text-primary" fill="currentColor">
<path d="M16.7 5.3a1 1 0 0 1 0 1.4l-7.5 7.5a1 1 0 0 1-1.4 0L3.3 9.7a1 1 0 1 1 1.4-1.4l3.8 3.8 6.8-6.8a1 1 0 0 1 1.4 0Z"></path>
</svg>
<span>
<span class="block text-sm font-medium text-foreground">The stop button</span>
<span class="mt-0.5 block text-sm text-muted-foreground">One press halts a run mid-step and rolls back anything reversible. It is always on screen.</span>
</span>
</li>
</ul>
</div>
<div class="rounded-xl border border-border bg-card p-6 shadow-sm">
<div aria-hidden="true" class="space-y-3">
<div class="h-3 w-1/3 rounded bg-primary/30 border border-transparent"></div>
<div class="h-24 rounded-lg bg-muted border border-transparent"></div>
<div class="grid grid-cols-3 gap-3">
<div class="h-14 rounded-lg bg-muted border border-transparent"></div>
<div class="h-14 rounded-lg bg-muted border border-transparent"></div>
<div class="h-14 rounded-lg bg-muted border border-transparent"></div>
</div>
<div class="h-3 w-2/3 rounded bg-muted border border-transparent"></div>
<div class="h-3 w-1/2 rounded bg-muted border border-transparent"></div>
</div>
</div>
</div>
</section>
What each framework gets across the whole catalog — effects convert properly; this rung is markup.
The component, its props, the design tokens it expects and the command that installs it — as one prompt. Paste it into Claude, Cursor, v0 or ChatGPT and what they build around it will match the rest of the catalog instead of inventing its own system.
Want the whole screen instead of this one section? Open a page and copy it entire.
All of them free to read, copy and install — no account, no locked tiles, no watermarked preview. The whole catalog is open, and so are the API and the CLI.
Browse Human in the LoopCopying the code is free. Putting it in client work or a paid product is what Pro is for — the licence, not the access.
The agent stops and asks: the effect stated before the verb, choices as radios in a fieldset so picking is separate from committing, no pre-focused Approve, and the decision left on screen afterwards.
Machine-proposed row edits accepted one at a time, with before and after in real del/ins elements, a tri-state select-all that actually sets indeterminate, and a running count of what will be written.
A suggestion scored with a native meter rather than a styled bar, banded in words as well as numbers, and showing what the model rejected — the fastest way a human catches a bad recommendation.
Read and write scopes separated rather than bundled, writes off by default, a required expiry, and switches that are real checkboxes with role="switch" — so the grant is toggleable by keyboard and announced as on or off.
Forty decisions and an honest way to clear them: bulk approve that cannot reach the high-impact rows, a select-all that says how many it takes, and the auto-approve deadline on every line.
Which agent actions run unattended and which need a signature, stated as a policy the operator can read before anything happens.
What is waiting on a person, ordered by how long it has been waiting rather than when it arrived.