affectUI › Components › Agents

Review Center

A focused workspace for what your agents want to do – the queue sorted by risk, one item at a time with the shaky spans marked, Approve, Edit, Reject and Skip on single keys, a stamp as each one leaves and the next one rising in.

Part of affectUI Pro. Install: npx shadcn@latest add https://www.affectui.com/r/review-center.json · Import from @/components/ui/review-center

When to use

  • Human in the loop at volume: a person signs off what agents produced – code changes, migrations, plans, contract edits, refunds, payments – one item after another
  • Code review for agents: a multi-file change as file rows with +/- counts and a unified diff with line numbers, the risky lines flagged with the agent's own note
  • When the reviewer has to see why the agent wants something and where it is unsure, not only what it wants
  • Review queues with a service level: oldest items and items close to their deadline need to stand out

When not to use

  • A board of background tasks with their status – use Agent Inbox
  • A handful of yes/no questions answered in passing – use Decision Inbox
  • One action that waits for a go, with a countdown – use Approval Card
  • An agent asking for access to a tool or scope – use Permission Request
  • An agent stuck on a decision that lays out its options and their consequences – use Decision Tree

Usage

import { ReviewCenter, type ReviewItem } from "@/components/ui/review-center";

const items: ReviewItem[] = [
  {
    id: "refund-4821",
    title: "Refund order #4821 for Maya Chen",
    agent: "Support agent", model: "Sonnet 5.5",
    type: "Refund", risk: "high", confidence: 0.62,
    createdAt: Date.now() - 26 * 60_000,
    reasoning: "Her blender arrived cracked – damaged items are covered within 30 days.",
    fields: [
      // A flagged span: marked in the proposal, its note shows on hover or with F
      { label: "Amount", value: [{ text: "$184.00", note: "Includes $20 express shipping.", source: "Refund policy §4" }] },
      { label: "Order", value: "#4821" },
    ],
    bodyLabel: "Reply to Maya",
    body: "Hi Maya, I'm so sorry your blender arrived cracked …",
  },
];

export function Reviews() {
  return (
    <ReviewCenter
      items={items}
      reviewed={24}
      onDecide={(item, result) => save(item.id, result)} // { decision, reason?, text?, fields? }
      onUndo={(item) => revert(item.id)}
    />
  );
}

Props

NameTypeDefaultDescription
itemsReviewItem[]{ id, title, agent, model?, icon?, type, risk, confidence, createdAt, reasoning?, fields?, body?, bodyLabel?, format?, sources? } – everything waiting; decided items hide themselves, new ids slide into the queue
risk"high" | "medium" | "low"Per item – sorts the queue (high first), feeds the High risk filter and shows as a small tag next to the agent (high and medium)
confidencenumberPer item, 0–1 – below lowConfidence the item counts as Unsure
fields / bodyReviewTextPer item – a string, or an array of strings and { text, note, source? } flags; flags are marked and explain themselves on hover, focus or F. Edit (E) works on these two
filesReviewFile[]Per item – a code change: { path, added?, removed?, diff?, start? } – a row per file, a unified diff with line numbers and a light syntax tint under the files that have one
stepsReviewStep[]Per item – a plan: { title, tool?, icon?, detail? } as numbered steps; a step with a flag gets an amber number
format"text" | "code""text"Per item – "code" shows the body as a diff, like a file's
onDecide(item, result) => voidApproved or rejected – result is { decision, reason?, text?, fields? }; text and fields only when the reviewer edited them
onUndo(item, result) => voidThe last decision was taken back – put the item back if you dropped it from items
onSkip(item) => voidThe reviewer moved on without deciding; the row is marked skipped
reviewednumber0How many were reviewed before (e.g. today) – the header counts on from here
totalnumberreviewed + waitingThe total in the header
slanumber1800000How long an item may wait, in ms – waiting times turn amber at 75% and red past it
nownumberThe clock (epoch ms) – defaults to the real one, ticking every 10s
lowConfidencenumber0.7Below this an item is Unsure
reasonsstring[]["Wrong info", "Tone", "Not allowed", "Other"]The quick reasons for a rejection, picked with 1–4
order"risk" | "oldest" | "given""risk"High risk first then oldest, oldest first, or as passed
filter / defaultFilter"all" | "high" | "unsure""all"The queue's filter, controlled or uncontrolled
onFilterChange(filter) => voidThe filter changed
refRef<ReviewCenterHandle>approve, reject(reason?), edit, setDraft, save, cancel, skip, next, previous, undo, peek(index), select(id)
titlestring"Review center"Heading of the bar
classNamestringExtra class on the root

Keyboard

  • A: Approves the current item
  • E: Edits the proposal in place – Cmd/Ctrl+Enter approves with the edits, Esc cancels
  • R: Rejects – then 1–4 picks the reason, Esc cancels
  • S: Skips to the next item without deciding
  • J / K: Next and previous item (ArrowDown and ArrowUp work too)
  • F: Shows the agent's note on the next flagged span
  • Z: Undoes the last decision (Cmd/Ctrl+Z too)
  • Esc: Closes the reason picker, the editor or a note

Accessibility

  • Shortcuts work while the focus is anywhere inside – a click on plain content keeps them working; buttons carry aria-keyshortcuts
  • Every action is a real button; the reason picker is a labelled group
  • Queue rows show only the title, the type and the waiting time; as buttons they are labelled with title, type, risk, confidence and waiting time; the current one has aria-current
  • Flagged spans are focusable and point to their note with aria-describedby (role tooltip)
  • Decisions, skips and undos are announced in a polite live region, with the new count
  • When the row or field you were on leaves, the focus moves to the next item instead of getting lost

Responsive

  • Built on a container query: wide, the queue sits on the left and the item on the right, in a fixed-height workspace
  • Under 640px the queue goes on top, three rows high, the item under it; its box glides to each item's height; buttons share the width
  • Under 420px the agent line wraps between its parts instead of cutting them, button icons step aside and the flag counter hides – flags are tapped directly; diffs scroll sideways
  • Dark mode built in: switches when an ancestor (usually <html>) has the class dark or data-theme="dark"

Requirements

  • React 19

Related

  • Decision Tree – An agent stuck on a decision lays out its options as a tree – the consequences of each branch, the risky ones marked, its own pick highlighted – and you choose a path or write your own
  • Agent Inbox – All your background agent tasks in one calm list – live clocks and status tags, filter tabs with rolling counts, rows that unfold to the last steps with Approve, Reject, Retry or Open, and glide to their new place
  • Decision Inbox – Open decisions with two quick answers each – answered ones confirm and fold away
  • Approval Card – An agent asks before it acts – risk badge, preview, Approve and Deny with shortcuts, a pausing countdown, and a smooth morph into a resolved row

Guides