Skip to content
Scrim UI

AI Coding Agent

A coding run with agent status, tool calls, diffs and a human-in-the-loop approval gate.

coding-agent.tsx162 lines3 componentsReact + Tailwind + shadcn/ui

npx shadcn@latest add @scrimui/coding-agent
Agent promptClaude Code · Cursor · any agent
Add the AI Coding Agent pattern from the Scrim UI registry to this project — a complete screen, not a single component.

AI Coding Agent — A coding run with agent status, tool calls, diffs and a human-in-the-loop approval gate.

## 1. Install

```bash
npx shadcn@latest add @scrimui/coding-agent
```

This writes the screen to `components/blocks/coding-agent.tsx` and pulls in the components it is built from, each landing at `components/ui/`. Everything is plain React + Tailwind with no runtime dependencies. The imports in the block already point at those paths, so it compiles as installed.

## 2. What it is made of

- `agent-status` — Agent Status
- `tool-call` — Tool Call
- `approval-request` — Approval Request

Each is a separate file you can edit or replace on its own; the block is the arrangement, not a monolith.

## 3. Rules this layout depends on

Keep these when adapting the screen — they are the reasons it works, and they are easy to break while restyling:

- Lead with the agent's current action — a coding agent that narrates builds trust fast.
- Show diffs before they're applied, especially when approval is involved.
- Gate deploys and migrations behind approval with the exact command shown.
- Report pass/fail counts concretely, not 'all good'.
- Confirm completion with a summary of what actually changed.

## 4. Do not do these

- Applying changes before asking — a surprise diff erodes all trust.
- Hiding the failing test or error that motivated the change.
- Asking for approval without showing cost or blast radius.
- Declaring 'done' without test results to back it up.

The demo content in the file — messages, file names, model names — is placeholder. Replace it with this project's real data and wire the handlers to real state rather than shipping the stubs.

Reference: https://scrimui.dev/patterns/coding-agent

Installs the screen and every component it is built from, and carries the layout rules from this page so an agent does not restyle them away.

Live Preview

Task

Fix the token refresh race condition

Coding AgentRunning18.6s

Analyzing token lifecycle, then applying patch to 3 files

Completed
Input
{"command": "npm test -- --filter=auth"}
Output
{"passed": 8, "failed": 1, "skipped": 0}
Completed
Input
{"file": "src/lib/auth/token.ts", "range": "1-60"}

Proposed change

src/lib/auth/token.ts+2 −1
@@ -24,7 +24,8 @@ function refresh() {   const token = await readStoredToken();-  const { access, expiresAt } = await refreshToken(token);+  const result = await refreshWithSingleFlight(token);   if (isExpired(result.expiresAt)) throw new TokenError();+  // Only the first caller refreshes; the rest wait on the same promise.   return result.access;

Apply patch and run migration?

Coding Agent is requesting approval

Applies the fix to 3 files and re-runs the auth suite against the staging database.

- src/lib/auth/token.ts
- src/lib/auth/refresh.ts
- src/test/auth/refresh.test.ts
Auto-deny in 4:32

Built from these components

Pattern code

This file composes the components above. Copy each component from its page, then this pattern file wires them together.

coding-agent.tsx
"use client";

import { Card } from "@/components/ui/card";
import * as React from "react";
import { AgentStatus } from "../../agent-status/agent-status";
import { ToolCall } from "../../tool-call/tool-call";
import { ApprovalRequest } from "../../approval-request/approval-request";

/* ------------------------------------------------------------------ */
/* Icons                                                               */
/* ------------------------------------------------------------------ */

function FileIcon() {
  return (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" width="14" height="14">
      <path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2Z" />
      <path d="M14 2v6h6" />
    </svg>
  );
}

function TerminalIcon() {
  return (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" width="14" height="14">
      <path d="m4 17 6-6-6-6M12 19h8" />
    </svg>
  );
}

function CheckIcon() {
  return (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" width="13" height="13">
      <path d="M20 6 9 17l-5-5" />
    </svg>
  );
}

/* ------------------------------------------------------------------ */
/* Diff block                                                          */
/* ------------------------------------------------------------------ */

function DiffBlock() {
  const lines = [
    { type: "hunk", text: "@@ -24,7 +24,8 @@ function refresh() {" },
    { type: "ctx", text: "   const token = await readStoredToken();" },
    { type: "del", text: "-  const { access, expiresAt } = await refreshToken(token);" },
    { type: "add", text: "+  const result = await refreshWithSingleFlight(token);" },
    { type: "ctx", text: "   if (isExpired(result.expiresAt)) throw new TokenError();" },
    { type: "add", text: "+  // Only the first caller refreshes; the rest wait on the same promise." },
    { type: "ctx", text: "   return result.access;" },
  ];
  return (
    <div className="overflow-hidden rounded-lg border border-border bg-muted">
      <div className="flex items-center gap-2 border-b border-border px-3 py-2 text-xs text-muted-foreground">
        <FileIcon />
        src/lib/auth/token.ts
        <span className="ml-auto tabular-nums">+2 −1</span>
      </div>
      <pre className="overflow-x-auto p-3 font-mono text-xs leading-5">
        {lines.map((l, i) => (
          <span
            key={i}
            className={`block ${
              l.type === "add"
                ? "bg-emerald-950/50 text-emerald-300"
                : l.type === "del"
                  ? "bg-red-950/50 text-red-300"
                  : l.type === "hunk"
                    ?
                      "text-muted-foreground"
                    : "text-muted-foreground"
            }`}
          >
            {l.text}
          </span>
        ))}
      </pre>
    </div>
  );
}

/* ------------------------------------------------------------------ */
/* CodingAgentPattern                                                  */
/* ------------------------------------------------------------------ */

export function CodingAgentPattern() {
  const [approved, setApproved] = React.useState(false);

  return (
    <Card className="gap-0 py-0 space-y-3 rounded-xl border border-border bg-card p-5">
      {/* Header */}
      <div>
        <p className="text-xs font-medium text-muted-foreground">Task</p>
        <h3 className="mt-1 text-lg font-semibold text-foreground">
          Fix the token refresh race condition
        </h3>
      </div>

      {/* Agent status */}
      <AgentStatus
        name="Coding Agent"
        status={approved ? "completed" : "running"}
        action={
          approved
            ? "Applied fix and re-ran the full auth suite"
            : "Analyzing token lifecycle, then applying patch to 3 files"
        }
        elapsed={approved ? "42.0s" : "18.6s"}
        progress={approved ? 100 : 64}
        onStop={() => {}}
      />

      {/* Tool calls */}
      <div className="space-y-2">
        <ToolCall
          name="Run auth tests (auth.test.ts)"
          icon={<TerminalIcon />}
          status="success"
          duration="4.6s"
          defaultOpen
          input={`{"command": "npm test -- --filter=auth"}`}
          output={`{"passed": 8, "failed": 1, "skipped": 0}`}
        />
        <ToolCall
          name="Read source (token.ts)"
          icon={<FileIcon />}
          status="success"
          duration="0.3s"
          input={`{"file": "src/lib/auth/token.ts", "range": "1-60"}`}
        />
      </div>

      {/* Diff */}
      <div>
        <p className="mb-1.5 text-xs font-medium text-muted-foreground">Proposed change</p>
        <DiffBlock />
      </div>

      {/* Approval */}
      <ApprovalRequest
        title="Apply patch and run migration?"
        requester="Coding Agent"
        description="Applies the fix to 3 files and re-runs the auth suite against the staging database."
        detail={`- src/lib/auth/token.ts\n- src/lib/auth/refresh.ts\n- src/test/auth/refresh.test.ts`}
        status={approved ? "approved" : "pending"}
        onAllow={() => setApproved(true)}
        onDeny={() => setApproved(false)}
      />

      {/* Completion */}
      {approved && (
        <div className="flex items-center gap-2 rounded-xl border border-emerald-200 bg-emerald-50 px-4 py-3 text-sm text-emerald-800 dark:border-emerald-900/60 dark:bg-emerald-950/40 dark:text-emerald-300">
          <CheckIcon />
          <span className="font-medium">Task completed.</span>
          <span className="text-emerald-700/80 dark:text-emerald-400/80">
            All 9 tests pass. The race is resolved with single-flight refresh.
          </span>
        </div>
      )}
    </Card>
  );
}

When to use it

  • Lead with the agent's current action — a coding agent that narrates builds trust fast.
  • Show diffs before they're applied, especially when approval is involved.
  • Gate deploys and migrations behind approval with the exact command shown.
  • Report pass/fail counts concretely, not 'all good'.
  • Confirm completion with a summary of what actually changed.

What breaks in production

  • Applying changes before asking — a surprise diff erodes all trust.
  • Hiding the failing test or error that motivated the change.
  • Asking for approval without showing cost or blast radius.
  • Declaring 'done' without test results to back it up.

More Patterns

AI Chat

The canonical chat interface — sidebar, streaming messages, prompt input with model selector, and sources.

AI Research Assistant

A research flow that shows search tool calls, reasoning, sources and a cited final answer.

AI Voice Assistant

A voice-first conversation — live waveform states, a recording input, a spoken transcript and a typed fallback.

Model & Memory Preferences

A preferences screen that picks the model, reasoning level and tools, and manages persistent memory.

Artifact Workspace

Chat on the left, generated output on the right — artifacts open from the answer, stream, version, and fail without breaking the conversation.

Document Q&A Workspace

Ask your own documents — upload and parse, cited answers with inspectable passages, an honest not-found state, and a visible context budget.

Structured Extraction & Review

Upload a document, watch fields fill in, then review the flagged ones — per-field confidence, corrections that keep the original, export earned.

Image Generation Studio

A one-screen generation studio — prompt composer and model picker beside a feed of queued, staged, blocked and ready image results with variants.

Multi-agent Ops Console

Watch a fleet of agents at once — parallel statuses, an inspectable handoff, a waiting approval, a failed child run, and per-run plus fleet cost.

Customer Support Copilot

Grounded reply drafts with citations, honest low-confidence answers, inline corrections, an approval gate on refunds, and a rating row on every draft.

Generative UI Dashboard

The model assembles a dashboard from a controlled widget registry — streamed props, an unsupported-request fallback, and widget clicks that re-enter the chat.