# Diff Apply Feature

> A feature section about reviewing what an agent writes. Beside three plain points, a Diff Apply editor plays the agent's tracking change into a real component, line by line, then waits for Keep or Undo on each change, with a running count of what was kept underneath.

A shadcn/ui block from the [@keyframeui](https://keyframeui.com/?utm_source=shadcn.exchange) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/keyframeui/diff-apply-feature).

- Page, with a live preview: https://shadcn.exchange/r/keyframeui/diff-apply-feature
- Screenshot: https://shadcn.exchange/shot/keyframeui/diff-apply-feature/light.png
- Registry item: https://keyframeui.com/r/diff-apply-feature.json
- Categories: blocks, feature-sections
- Listed: 2026-11-02, source last changed 2026-11-02

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/keyframeui/diff-apply-feature.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- Registry items: `https://keyframeui.com/r/diff-apply.json`

## Notes from the registry

Live preview and the components it's built with: https://keyframeui.com/blocks/feature-sections/diff-apply-feature

## Files

### `registry/blocks/diff-apply-feature.tsx`

```tsx
"use client";

import * as React from "react";

import { DiffApply, type DiffApplyDecision } from "@/components/ui/diff-apply";

/* ── Content ─────────────────────────────────────────────────
 * Everything you'd change to make the section yours: the copy,
 * the points beside the editor, and the file the agent edits
 * (BEFORE and AFTER, one string per line; the component works
 * out the diff).
 * ─────────────────────────────────────────────────────────── */

/** The one accent: the point markers and the review summary. */
const ACCENT = { light: "#ea580c", dark: "#fb923c" };

const HEADLINE = "Read every line";
const HEADLINE_QUIET = "before it lands.";
const SUBLINE =
  "Tern's agent edits your components the way a teammate would: a diff you can read, keep or undo, change by change.";

const POINTS = [
  {
    title: "Tracking that lives in your code",
    body: "No snippet in the page head. Calls sit next to the button they measure, typed and tested with the rest.",
  },
  {
    title: "Nothing lands without you",
    body: "Each change waits for Keep or Undo, so a stray event name never reaches production.",
  },
  {
    title: "Undo is just undo",
    body: "Undo a change and it plays backwards, leaving the file exactly as it was.",
  },
];

const PROMPT = "Track signup_submitted with the chosen plan";
const FILENAME = "app/signup/form.tsx";

const BEFORE = [
  '"use client";',
  "",
  'import { useState } from "react";',
  'import { createAccount } from "./actions";',
  "",
  "export function SignupForm() {",
  '  const [plan, setPlan] = useState<Plan>("starter");',
  "",
  "  async function onSubmit(data: FormData) {",
  "    const account = await createAccount(data, plan);",
  "    window.location.assign(account.welcomeUrl);",
  "  }",
  "",
  "  return <Form plan={plan} onPlanChange={setPlan} onSubmit={onSubmit} />;",
  "}",
].join("\n");

const AFTER = [
  '"use client";',
  "",
  'import { useState } from "react";',
  'import { track } from "@/lib/tern";',
  'import { createAccount } from "./actions";',
  "",
  "export function SignupForm() {",
  '  const [plan, setPlan] = useState<Plan>("starter");',
  "",
  "  async function onSubmit(data: FormData) {",
  '    track("signup_submitted", { plan });',
  "    const account = await createAccount(data, plan);",
  '    track("account_created", { plan, id: account.id });',
  "    window.location.assign(account.welcomeUrl);",
  "  }",
  "",
  "  return <Form plan={plan} onPlanChange={setPlan} onSubmit={onSubmit} />;",
  "}",
].join("\n");

/* ── Section ─────────────────────────────────────────────── */

export function DiffApplyFeature() {
  const [decisions, setDecisions] = React.useState<DiffApplyDecision[]>([]);
  const kept = decisions.filter((decision) => decision === "kept").length;
  const undone = decisions.filter((decision) => decision === "undone").length;
  const waiting = decisions.length - kept - undone;

  return (
    <section
      style={{ "--accent-light": ACCENT.light, "--accent-dark": ACCENT.dark } as React.CSSProperties}
      className="bg-zinc-50 text-zinc-950 [--accent:var(--accent-light)] dark:bg-zinc-950 dark:text-white dark:[--accent:var(--accent-dark)]"
    >
      <div className="mx-auto grid max-w-6xl gap-x-14 gap-y-12 px-6 py-24 sm:py-32 lg:grid-cols-12 lg:items-center">
        <div className="lg:col-span-5">
          <h2 className="text-4xl/[1.05] font-semibold tracking-[-0.035em] text-balance sm:text-5xl/[1.02]">
            {HEADLINE} <span className="block text-zinc-400 dark:text-zinc-500">{HEADLINE_QUIET}</span>
          </h2>
          <p className="mt-5 max-w-md text-base/7 text-pretty text-zinc-600 dark:text-zinc-400">{SUBLINE}</p>

          <ul className="mt-10 border-t border-zinc-950/10 dark:border-white/10">
            {POINTS.map((point) => (
              <li key={point.title} className="flex gap-4 border-b border-zinc-950/10 py-5 dark:border-white/10">
                <span aria-hidden className="mt-2 size-1.5 shrink-0 rounded-full bg-(--accent)" />
                <div>
                  <h3 className="text-sm font-semibold">{point.title}</h3>
                  <p className="mt-1 text-sm/6 text-pretty text-zinc-600 dark:text-zinc-400">{point.body}</p>
                </div>
              </li>
            ))}
          </ul>
        </div>

        <div className="min-w-0 lg:col-span-7">
          <div className="mb-3 flex items-center gap-2.5 text-sm">
            <span className="grid size-6 shrink-0 place-items-center rounded-full bg-zinc-950 text-white dark:bg-white dark:text-zinc-950">
              <SparkIcon className="size-3" />
            </span>
            <p className="min-w-0 truncate text-zinc-700 dark:text-zinc-300">{PROMPT}</p>
          </div>
          <DiffApply
            before={BEFORE}
            after={AFTER}
            filename={FILENAME}
            onReview={setDecisions}
            className="shadow-[0_24px_60px_-32px_rgb(9_9_11/0.35)] dark:shadow-[0_24px_60px_-24px_rgb(0_0_0/0.8)]"
          />
          <p className="mt-3 h-5 text-sm text-zinc-500 tabular-nums" aria-live="polite">
            {decisions.length > 0 &&
              `${kept} kept · ${undone} undone${waiting > 0 ? ` · ${waiting} waiting for you` : ""}`}
          </p>
        </div>
      </div>
    </section>
  );
}

function SparkIcon({ className }: { className?: string }) {
  return (
    <svg viewBox="0 0 16 16" className={className} aria-hidden>
      <path d="M8 1.5 9.6 6.4 14.5 8 9.6 9.6 8 14.5 6.4 9.6 1.5 8 6.4 6.4Z" fill="currentColor" />
    </svg>
  );
}
```
