Scrim UI

AI Coding Agent

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

coding-agent.tsx163 lines3 componentsReact + Tailwind, no dependencies

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 * 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="2" 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="2" 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="2.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-zinc-800 bg-zinc-950 dark:border-zinc-700">
      <div className="flex items-center gap-2 border-b border-zinc-800 px-3 py-2 text-xs text-zinc-400 dark:border-zinc-700">
        <FileIcon />
        src/lib/auth/token.ts
        <span className="ml-auto tabular-nums">+21</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"
                    ? /* zinc-400: on the zinc-950 code surface, zinc-500 is
                         4.12:1. -400 is 7.76:1 and still reads dimmer than the
                         zinc-300 code lines around it. */
                      "text-zinc-400"
                    : "text-zinc-300"
            }`}
          >
            {l.text}
          </span>
        ))}
      </pre>
    </div>
  );
}

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

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

  return (
    <div className="space-y-3 rounded-2xl border border-zinc-200 bg-white p-5 dark:border-zinc-800 dark:bg-zinc-900">
      {/* Header */}
      <div>
        <p className="text-[11px] font-medium uppercase tracking-wide text-zinc-500 dark:text-zinc-400">Task</p>
        <h3 className="mt-1 text-lg font-semibold text-zinc-900 dark:text-zinc-100">
          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-zinc-500 dark:text-zinc-400">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>
      )}
    </div>
  );
}

Usage Guidelines

  • 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.

Common UX Mistakes

  • 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