PixelDosa UI

AI Triage Table

Review many AI-proposed changes at once — organised by where attention is needed, with what didn't change collapsed to a single line.

Enrich 48 accounts

0 of 8 reviewed · 40 unchanged

  • Northwind Traders · Industry

    Retail changes to Wholesale distribution

    via a single press mention

  • Globex · Headcount

    50–100 changes to 1,000–5,000

    via an unverified LinkedIn estimate

  • Initech · Billing email

    billing@initech.co changes to accounts@initech.com

    via last 3 invoices

  • Umbrella · Country

    United Kingdom

    via registered address

Open to review before accepting
  • Acme Robotics · Legal name

    acme robotics changes to Acme Robotics, Inc.

    via acme-robotics.com

  • Stark Industries · Website

    starkindustries.com

    via company registry

  • Wayne Enterprises · Phone

    5550100 changes to +1 555 0100

    via formatting only

  • Hooli · Legal name

    hooli changes to Hooli, Inc.

    via hooli.com

  • Account 101 · all fields already correct
  • Account 102 · all fields already correct
  • Account 103 · all fields already correct
  • Account 104 · all fields already correct
  • Account 105 · all fields already correct
  • Account 106 · all fields already correct
  • Account 107 · all fields already correct
  • Account 108 · all fields already correct
  • Account 109 · all fields already correct
  • Account 110 · all fields already correct
  • Account 111 · all fields already correct
  • Account 112 · all fields already correct
  • Account 113 · all fields already correct
  • Account 114 · all fields already correct
  • Account 115 · all fields already correct
  • Account 116 · all fields already correct
  • Account 117 · all fields already correct
  • Account 118 · all fields already correct
  • Account 119 · all fields already correct
  • Account 120 · all fields already correct
  • Account 121 · all fields already correct
  • Account 122 · all fields already correct
  • Account 123 · all fields already correct
  • Account 124 · all fields already correct
  • Account 125 · all fields already correct
  • Account 126 · all fields already correct
  • Account 127 · all fields already correct
  • Account 128 · all fields already correct
  • Account 129 · all fields already correct
  • Account 130 · all fields already correct
  • Account 131 · all fields already correct
  • Account 132 · all fields already correct
  • Account 133 · all fields already correct
  • Account 134 · all fields already correct
  • Account 135 · all fields already correct
  • Account 136 · all fields already correct
  • Account 137 · all fields already correct
  • Account 138 · all fields already correct
  • Account 139 · all fields already correct
  • Account 140 · all fields already correct
8 not reviewed will stay as they are.

Installation

bash
npx shadcn@latest add @pixeldosa/ai-triage-table

Requires `cn` at `@/lib/utils`. Installing this pulls in AI Action Toolbar and Confidence Meter. Pass `unchanged` whenever you have it — showing what the AI deliberately left alone is half of what makes review cheap.

Usage

"use client";

import { AITriageTable } from "@/components/ui/ai-triage-table";

<AITriageTable
  title="Enrich 48 accounts"
  items={proposals}          // { id, label, before?, after, confidence, provenance? }[]
  unchanged={untouched}      // { id, label }[]
  onApply={(acceptedIds) => save(acceptedIds)}
/>

The problem it solves

AI now produces changes faster than people can check them. Research on AI products calls this the audit burden: people aren't doing less work, they're supervising the machine.

Every other review component helps someone check AI output. This one reduces how much checking there is — without ever quietly accepting anything.

Features

  • Attention order, not arrival order. Low-confidence changes open first because they're where errors live. High-confidence changes start collapsed to a count.
  • What didn't change is one line. "40 checked and left unchanged" — nobody scans forty rows to confirm nothing happened. Expand it if you want to see them.
  • Before and after, side by side. The old value struck through, the proposal beside it, the source underneath.
  • Nothing is written until Apply. Decisions are staged; every row has a one-step Undo. Apply states exactly how many changes it will commit, and that anything unreviewed stays as it is.

What it refuses to do

It is not "Accept all" with better styling.

  • Bulk accept exists only for the high-confidence group — low and medium changes must be decided one by one.
  • And only after that group has been opened. Until then it says "Open to review before accepting" in plain text, rather than showing a disabled button that explains nothing.

Accepting is where risk lives, so that's where friction stays. Everything else gets out of the way.

Examples

Only pass what actually changed as items. If the AI produced a value identical to the current one, it belongs in unchanged:

const items = results.filter((r) => r.after !== r.before);
const unchanged = results.filter((r) => r.after === r.before).map(({ id, label }) => ({ id, label }));

Gating a consequential apply behind an approval:

const [pending, setPending] = React.useState<string[] | null>(null);

<AITriageTable items={items} unchanged={unchanged} onApply={setPending} />

{pending ? (
  <AIApprovalGate
    action={`Update ${pending.length} customer records`}
    reversible
    impact={[{ label: "Records", value: String(pending.length) }]}
    onApprove={() => save(pending)}
    onReject={() => setPending(null)}
  />
) : null}

Props

PropTypeDescription
itemsTriageItem[]Required. { id, label, before?, after, confidence, provenance? }.
unchanged{ id, label }[]What the AI examined and left alone.
onApply(acceptedIds: string[]) => voidRequired. The only point anything is committed.
titlestringDefaults to "Review proposed changes".
headingLevel2 | 3 | 4 | 5 | 6Heading level, so the component fits your page's outline. Defaults to 3.

Accessibility

  • A labelled <section> with a live "N of M reviewed" count.
  • Each group header is a real disclosure with aria-expanded and aria-controls.
  • Collapsed groups are inert — hidden rows can't be tabbed into or read out.
  • Every row's decision controls are a keyboard toolbar; Undo is always reachable.
  • Before/after is marked by strikethrough and underline plus a screen-reader "changes to", never colour alone.

When not to use

One field is Smart Field. One form is AI Form Fill. A text rewrite is Diff Accept. Use this when AI output arrives in volume.

why it's built this way

Engineering Notes

Built in direct response to the audit burden: AI now produces changes faster than people can check them, and every earlier component in this system enables review without reducing it, which at volume turns the user into a full-time auditor. Triage reduces review in three ways. It orders by attention rather than by arrival — low confidence first, high confidence collapsed — so effort lands where errors are likely. It collapses what the AI left unchanged into a count, since confirming that nothing happened is otherwise the most tedious part of reviewing a batch. And it states consequences at the point of commitment ('Apply 5', 'not reviewed will stay as they are'). The hard boundary is that it must not become Accept-all by another name. Bulk acceptance is limited to the high-confidence group, and gated on that group having been opened — tracked as an explicit inspected set, with the reason stated in text rather than hidden behind a disabled button. Decisions are staged rather than committed, so every row's Undo is simply removing a staged decision, with no undo timer to manage. Collapsed groups use the house grid-template-rows pattern with overflow-hidden, and are marked inert while closed: a 0fr row hides content visually but otherwise leaves its buttons focusable and exposed to assistive technology.

how it moves, and when it doesn't

Motion Notes

Deliberately quiet: this is a user-turn surface, so nothing moves on its own and nothing shimmers. Groups expand and collapse through the house grid-template-rows transition at `--pd-duration-fast`, the disclosure chevrons rotate at `--pd-duration-instant`, and a decided row fades to reduced opacity over `--pd-duration-fast` so the eye moves on to what remains without the row disappearing — its Undo stays reachable. Action colours transition through the composed AI Action Toolbar. There is no entrance animation and no motion tied to counts changing, since numbers updating in a review surface should read as information rather than event. Under prefers-reduced-motion every transition drops to instant and the grouping, counts and decided-state opacity still carry all the information.