A spec for building keyboard-driven human-in-the-loop decision UIs. Give this to any AI coding tool and it should produce a working prototype.
The idea: AI prepares decisions, humans make the call. Every override teaches the system something.
A card stack. Each card is one case that needs a human decision. The AI has already done the investigation: scored the case, surfaced the key signals, written a draft action (email, routing decision, approval, whatever fits your domain). The human reviews and either accepts or overrides.
The whole thing runs on arrow keys. No mouse needed.
Each card contains, top to bottom:
Header — case title, metadata line (tier, value, tenure), score badge, severity label.
Signal bar — exactly 4 signals in a horizontal row. Each signal is a value + label. These are the numbers the human scans first. Pick the 4 most decision-relevant metrics for your domain. The signal bar must not scroll or resize.
Draft action — the thing AI is proposing. In outreach this is an email draft (subject + body). In support routing this is a suggested queue + priority. In approvals this is a recommendation + rationale. The draft should be editable inline.
Evidence panel (collapsed by default) — the "why." Expands on press of ↓. Each row: a metric value on the left, a headline + detail paragraph on the right. One row per signal. This is where the human goes when something feels off.
Override menu (hidden by default) — appears on press of ←. Four domain-specific options in a 2x2 grid. Each option has a letter key (A/B/C/D) and a label. Selecting one swipes the card left.
Action bar — three buttons: Override (←), Why (↓), Accept/Send (→). Each button shows its keyboard shortcut. These are visual affordances for the arrow keys, not the primary interaction method.
Footer — queue position ("3 of 14").
This is the core of the pattern. Everything is operable without a mouse.
DEFAULT:
→ Accept the AI recommendation. Card swipes right.
← Open override menu.
↓ Expand evidence panel. If already expanded, scroll down.
↑ Scroll evidence panel up. If at top, collapse it.
OVERRIDE MENU OPEN:
A/B/C/D Select override option. Card swipes left.
Escape Close menu, back to default.
EDIT MODE (when editing the draft inline):
Shift+Enter Send the edited version. Card swipes right.
Escape Cancel edit, discard changes.
Important: disable arrow key navigation during card exit animations to prevent double-fires. Skip keyboard events when focus is in an input/textarea (except edit mode shortcuts).
3 cards visible at a time. The stack communicates "there's more" without showing a count.
- Position 0 (current): full size, fully interactive.
- Position 1 (next): slightly narrower (scaleX ~0.96), dimmed, shifted down ~8px, no pointer events.
- Position 2 (third): narrower still (scaleX ~0.92), more dimmed, shifted down ~16px, no pointer events.
Use transform-origin: top center so the scaling pulls the sides in symmetrically while the top edge stays aligned.
Cards beyond position 2 are hidden entirely (display: none).
- Accept: card slides to 120% right, rotates ~8deg, fades out. ~350ms ease-out.
- Override: card slides to 120% left, rotates ~-8deg, fades out. ~350ms ease-out.
- Promotion: remaining cards animate up to their new positions. Use CSS class swapping, not DOM destruction/recreation.
The exiting card must have a higher z-index than the stack (z-index: 50) so it animates above, not behind.
Build all cards once on init. Assign CSS classes (stack-0, stack-1, stack-2, stack-hidden) to control visibility and position. On each decision, add an exit class to the current card, then promote the remaining cards by reassigning classes. Never destroy and recreate card DOM nodes mid-session.
The default card view should be scannable in under 5 seconds: header, signals, draft, action buttons. That's enough for ~80% of decisions.
The evidence panel is for the other 20%. It expands with a height/opacity animation on ↓. It should be scrollable if content overflows.
Layout constraint: header, signal bar, action buttons, and footer must never scroll or shrink. Only the middle section (draft + evidence panel) scrolls. Use flex layout with flex-shrink: 0 on the fixed sections.
Case {
id: string
title: string
meta: string
score: number (0-100)
label: string ("HOT LEAD", "WARM", "NEUTRAL", etc.)
signals: [{ value: string, label: string }] // exactly 4
action: string // recommended action label
draft: {
subject: string
body: string // can contain HTML for highlights
}
findings: [{
headline: string
detail: string
}] // one per signal
}
Override {
key: string // "a", "b", "c", "d"
label: string // domain-specific action name
}
Minimal. No state management library needed.
currentIndex: number
isAnimating: boolean
deepDiveOpen: boolean
overrideMenuOpen: boolean
editMode: boolean
totalAccepted: number
totalOverridden: number
Every decision triggers:
- A toast notification (bottom-right, auto-dismiss after ~2s).
- A running counter in the top bar that increments on accept.
- Queue position updates in the card footer.
When the queue is empty, show a completion screen with summary stats: total reviewed, total accepted, total overridden.
This is what makes the pattern compound. Every human decision is a labeled data point.
Track per decision:
- Case ID + all signals at time of decision
- Accepted or overridden
- If overridden: which option was selected
- Time spent on the card before deciding
- If draft was edited: before/after text
What this gives you over time:
- Override rate by signal pattern (which combinations does the human disagree with?)
- Override rate by case type (are borderline cases getting overridden more?)
- Decision velocity (which cases take longer? those are where the AI summary isn't sufficient)
- Edited drafts as fine-tuning data for the draft generator
The override rate should decline over time as the system learns from disagreements.
The interaction pattern is the same everywhere. What changes:
| Component | Outreach | Support routing | Invoice approval | Content moderation |
|---|---|---|---|---|
| Signals | Usage growth, plan cap, login freq, feature requests | Sentiment, topic, urgency, customer tier | Amount, vendor, GL code, policy match | Toxicity score, user history, report count, context |
| Draft | Email with subject + body | Suggested queue + priority + response template | Approve/reject recommendation + rationale | Keep/remove recommendation + explanation |
| Override options | Edit draft, Skip, Reassign to AE, Snooze | Re-route, Escalate, Merge, Defer | Flag for review, Request docs, Split, Reject | Warn user, Reduce visibility, Escalate, Ignore |
Swap the case data, signals, draft format, and override options. Keep the card layout, keyboard nav, and stack behavior.