Show Agent Steps with shadcn Trace
Build a Next.js panel that visualizes agent steps and code diffs.
Introduction
30 Second Summary
Waiting on an AI assistant can feel like staring at a loading spinner. You cannot tell whether it is making progress or stuck.
In this project, you will build a Next.js page that uses the Trace component from shadcn/ui to show an AI agent's work. Sample agent message data drives the status indicators plus an inline Code Block diff.
What You'll Build
Once it is ready, your page shows every agent step with its current status plus an inline diff of the code it changed.
By the end of this project, you'll have:
- Trace the full run from reasoning through tool calls to subagent work, with a title plus description for every step.
- Read each status at a glance as the indicators show running, done, pending, or error.
- Inspect the code change in a line-numbered diff placed directly beneath the relevant tool-call step.
- Secret Mission: Take on an optional challenge to push your skills further.
Are there any prerequisites?
You'll need Node plus a package manager on your computer. Basic React familiarity helps you follow the page code.
Before We Start
Your agent activity panel runs inside a local Next.js app. The app depends on Node.js for its development tools.
This step prepares your local toolkit before any project files exist. You will finish with everything needed to build the page on your computer.
In this step, get ready to:
- Install Node.js with npm.
- Prepare Visual Studio Code for editing.
- Confirm your local development toolkit works.
Install Node.js and npm
Node.js runs the JavaScript tools behind Next.js. Its installer includes npm for managing project packages.
- Press Command-Space bar (macOS) or Windows key+S (Windows) to open search.
- Type Terminal on macOS or PowerShell on Windows into the search field.
- Select the matching application.
- Check whether Node.js is available by running this command:
node --version
What Does This Command Do?
The --version flag asks Node.js to print its installed version. A version number confirms that your terminal can find the runtime.
✔️ I see a version number
Good result. Node.js is installed and reachable from your terminal.
ⓧ Command not found
Node.js needs to be installed before you can create the Next.js app. The official installer also adds npm to your computer.
- Open the official Node.js download page.
- Select the latest LTS release.
- Download the Installer for your computer.
What Will the Installer Ask?
The installer asks you to confirm its installation options. The default options install Node.js with npm for your user account.
- Run the downloaded installer.
- Keep the default installation options.
That completes the runtime setup. Node.js and npm are now installed together.
- Close your current terminal window.
- Press Command-Space bar (macOS) or Windows key+S (Windows) to open search.
- Type Terminal on macOS or PowerShell on Windows into the search field.
- Select the matching application.
- Confirm the installation by running the version check again:
node --version
What Does This Check Prove?
This command asks the new terminal session to find Node.js. A version number proves that the installation is available to future commands.
Still Missing a Version Number?
Restart your computer if the new terminal session still cannot find Node.js. This allows your operating system to reload the updated command locations.
If the check still fails, ask for help with your Node.js installation.
Before the next check, pause to consider whether npm will report a separate version number.
- Confirm npm is available by running this command:
npm --version
What Does This Command Do?
This command asks npm to print its installed version. The result confirms that the package manager included with Node.js is ready.
You should see a version number for npm. Your computer can now install the packages used by the project.
npm Does Not Print a Version?
Close the terminal after installing Node.js. Open a new terminal before running the npm check again.
If Node.js works but npm still does not, ask for help checking your npm installation.
Prepare Your Editor and Browser
Visual Studio Code gives you one place to edit the project files. Your web browser displays the local page while you build it.
- Select the tab that matches your Visual Studio Code setup.
✔️ It's already installed
- Press Command-Space bar (macOS) or Windows key+S (Windows) to open search.
- Type Visual Studio Code into the search field.
- Select Visual Studio Code from the results.
You should see the Visual Studio Code window. That takes care of the workspace you will use for every project file.
ⓧ I need to install it
macOS
- Open the official Visual Studio Code download page.
- Download the .dmg file for your Mac.
- Open the downloaded .dmg file.
- Drag Visual Studio Code.app into the Applications folder.
- Press Command-Space bar to open Spotlight.
- Type Visual Studio Code into the search field.
- Select Visual Studio Code from the results.
Your editor window should open. Visual Studio Code is ready for the project files you create next.
Windows
- Open the official Visual Studio Code download page.
- Download the User Installer for your Windows computer.
- Run the downloaded installer.
- Keep the default installation options.
- Press Windows key+S to open search.
- Type Visual Studio Code into the search field.
- Select Visual Studio Code from the results.
Your editor window should open. Visual Studio Code is ready for the project files you create next.
The browser check confirms that you can view the web pages used throughout the project. It also gives you a clear success signal before you build the local app.
- Confirm your browser can load a page by opening the official Node.js download page.
You should see the Node.js download page with an LTS release listed. Your browser is ready to display the local Next.js app.
Confirm Your Toolkit
A final terminal check proves that the runtime and package manager remain available after setup. This is the terminal state you will use to create the Next.js app.
Before you run the final check, pause to predict whether both commands will print version numbers.
- Verify Node.js and npm together by running these commands:
node --version
npm --version
What Does This Final Check Do?
The first command checks the Node.js runtime. The second command checks the npm package manager.
Running them together confirms that both tools are available in the same terminal session.
You should see one version number for Node.js. You should see another version number for npm directly below it.
Version Check Failing?
Close every terminal window after installation. Open a fresh terminal so it can load the updated command locations.
If either command still fails, ask for help checking your local development toolkit.
That's the setup complete. Your computer can now run the development tools used throughout the project.
Your local toolkit is ready. Next, you will create the Next.js app that becomes your agent activity panel.
Create the Next.js App
Your agent activity panel needs a working web page before it can display reasoning steps or tool calls. A local Next.js app provides that foundation.
In this step, you will scaffold the default application. You will also initialize shadcn/ui so its CLI can add the Trace components in the next step.
In this step, get ready to:
- Create a Next.js application with the default project structure.
- Initialize shadcn and generate its project configuration.
- Run the development server to view the starter page.
Create the app scaffold
The create-next-app CLI generates the folders and configuration that Next.js needs. It also installs the project dependencies for you.
- Move to your Desktop by running this command:
cd ~/Desktop
What does this command do?
This moves your terminal to the Desktop. Your new my-app folder will be easy to find there.
- Prepare for a possible npm package download prompt.
- Approve the package download if npm asks for confirmation.
- Create the default Next.js app and enter its folder by running these commands:
npx create-next-app@latest my-app --yes
cd my-app
What does this command do?
The first line creates my-app with the recommended defaults. The --yes flag skips the configuration prompts.
- TypeScript provides type checking for the application.
- Tailwind CSS provides utility classes for styling the activity panel.
- The App Router maps app/page.tsx to the home page.
- The second line leaves your terminal inside the new project folder.
The first foundation is in place. Your terminal is now connected to a complete Next.js project.
App scaffold did not finish?
Check that your terminal remains connected to the internet. Remove any incomplete my-app folder before trying the scaffold command again.
If npm reports a permissions or download problem, help me debug the Next.js scaffold.
Initialize shadcn
The components.json file records how shadcn should generate components for this project. The CLI reads it when you add Trace and Code Block later.
- Prepare for a possible npm package download prompt.
- Approve the package download if npm asks for confirmation.
- Initialize shadcn in the current Next.js project by running:
npx shadcn@latest init
What does this command do?
The shadcn CLI detects the existing Next.js setup. It creates components.json so future component commands know where to place generated files.
The initialization also adds the utility code and dependencies required by shadcn components.
- Confirm the default page file and shadcn configuration exist by running:
ls app/page.tsx components.json
What does this check prove?
The command asks your terminal to list both required files. Seeing both paths confirms that the application scaffold and shadcn initialization completed.
You should see app/page.tsx and components.json in the terminal output.
Missing components.json?
Make sure the terminal path ends with my-app before running the initialization command. Running it from the Desktop places the configuration outside the application.
If initialization still fails, help me debug the shadcn setup.
✔️ Awesome, I've got everything!
Great. Your Next.js scaffold and shadcn configuration are ready.
ⓧ I'd like to double check the full code
Compare your project with this end state before starting the development server.
- Next.js project: Created locally with the standard Next.js app structure and a runnable development-server script.
- app/page.tsx: Default Next.js page file created with the application scaffold.
- components.json: Created by shadcn initialization and configured for the Next.js project.
Run the starter page
The development server turns the generated files into a local website. Keeping it running gives you a live preview for every component you add.
Before you start the server, ask yourself what the browser will show at the local address.
- Start the Next.js development server by running:
npm run dev
What does this command do?
The dev script starts the local Next.js development server. It watches your project files so saved changes can appear during development.
You should see terminal output containing the local address. The running server keeps control of this terminal until you stop it.
- Switch to your web browser.
- Enter http://localhost:3000 in the address bar.
You should see the default Next.js starter page. That confirms the scaffold is running successfully in your browser.
Starter page not loading?
Check that the development server is still running in the terminal. Confirm that you started it from inside the my-app folder.
If the browser still cannot connect, help me debug the local Next.js server.
Your local app is ready for its activity interface. Next up, you will add the Trace and Code Block components with the shadcn CLI.
Add Trace Components
Your Next.js app already runs locally. Its shadcn configuration is ready to copy components into the project.
A spinner hides the sequence of work inside an agent run. Trace gives each activity its own line with a visible status.
The Code Block component displays code changes under an activity. In this step, you will add both components through the shadcn CLI.
In this step, get ready to:
- Add the Trace and Code Block components with the shadcn CLI.
- Confirm both generated component files exist.
- Prove the default page still loads.
Add the components
Shadcn components live inside your project as editable source files. The CLI uses components.json to place those files in the configured directories.
The same command installs the packages required by both components.
- Switch back to the terminal connected to your Next.js project.
- Stop the development server by pressing Ctrl+C if it is still running.
The CLI may ask for confirmation before it changes your project files.
- Approve any confirmation prompt.
- Add both components by running this command:
npx shadcn@latest add trace code-block
What does this command do?
The command reads components.json to determine where UI components belong. It adds Trace and Code Block in one pass.
The CLI also installs the packages imported by the generated files. This keeps the component source aligned with its dependencies.
- Confirm the command finishes without an error.
That is the component layer installed. Your project now owns the source that the activity panel will use.
Component installation failed?
Check that the terminal is still connected to the folder containing components.json. The CLI needs that configuration to choose the correct paths.
Retry the command after your network connection stabilizes if the package download stopped early.
Help me diagnose why the shadcn CLI cannot add Trace and Code Block to my initialized Next.js project.
Confirm the generated files
A successful registry installation leaves visible source files in the project. Checking their exact paths confirms that both components landed where the next steps expect them.
- Confirm both component files exist by running this command:
ls components/ui/trace.tsx components/ui/code-block.tsx
What does this command check?
The ls command receives both component paths as file operands. It prints each path when the corresponding file exists.
You should see components/ui/trace.tsx and components/ui/code-block.tsx in the terminal output. This proves that both registry additions reached your project.
Missing a component file?
Return to the installation output to check which component failed. Run the add command again from the folder containing components.json.
Help me find why one of my generated shadcn component files is missing.
Check the app still runs
The CLI added source files and dependencies without changing app/page.tsx. A clean restart confirms that the existing app remains healthy after the installation.
The first page load can take several seconds while Next.js compiles the route.
Before you run this check, do you expect the original page to load unchanged after the CLI added the components?
- Start the development server by running this command:
npm run dev
What does this command do?
The command starts the development script already defined by your Next.js application. The server keeps this terminal occupied while it runs.
- Switch back to the browser tab from earlier.
- Refresh the page.
You should see the default Next.js page exactly as before. You have proved that the component installation preserved the working app.
Development server not starting?
Check that the component installation finished before you started the server. An interrupted dependency install can prevent the project from launching.
Confirm the terminal is connected to the same project folder that contains app/page.tsx.
Help me debug why my Next.js development server stopped working after I added the shadcn components.
Your Trace and Code Block primitives are ready. Next, you will replace the default page with the first visible version of your agent activity panel.
Render Agent Steps
Your shadcn components are ready. A spinner would still hide the reasoning steps that make an agent run understandable.
In this step, you will replace the default Next.js page with a Trace built from sample agent activity. Each line will identify a reasoning step, a tool call, or a subagent run.
In this step, get ready to:
- Start the development server for immediate browser feedback.
- Model reasoning, tool-call, and subagent activity in a hard-coded list.
- Render an icon, title, and description for every trace line.
Prepare for instant feedback
The development server turns each saved edit into a browser result. Keeping it running gives you a short loop between changing app/page.tsx and checking the activity panel.
- Return to the project terminal from earlier.
- Start the development server by running this command:
npm run dev
The development server keeps this terminal occupied while it watches your project files. Leave the process running for the rest of this step.
- Return to the browser from earlier.
- Refresh the page.
You should see the default Next.js page. This confirms the browser is connected to your local project before you replace the page.
Development server not starting?
Check that the project terminal is inside the folder containing your Next.js app. A terminal opened in a different folder cannot find the development script.
If another development process is already running, return to its browser tab instead of starting a duplicate process.
help me diagnose why my Next.js development server is not starting
Model the agent activity
The panel needs structured sample data before it can repeat a consistent row for each activity. The agentSteps list gives every entry a type, title, description, and icon.
- In the code editor from earlier, open app/page.tsx.
- Add the trace imports and sample data at the beginning of app/page.tsx by pasting this code before its first line:
import { BotIcon, BrainIcon, WrenchIcon } from "lucide-react";
import { Trace, TraceDescription, TraceIcon, TraceLine, TraceTitle } from "@/components/ui/trace";
// Each object represents one activity line shown by the agent.
const agentSteps = [
{ type: "reasoning", title: "Reasoning about the request", description: "Identifying the page structure and the files that need to change.", icon: BrainIcon },
{ type: "tool-call", title: "Updating the page", description: "Writing the activity panel into app/page.tsx.", icon: WrenchIcon },
{ type: "subagent", title: "Delegating accessibility review", description: "Asking a subagent to review labels and reading order.", icon: BotIcon },
];
How does the activity model work?
- The trace imports expose the container and line primitives used to assemble the panel.
- Each agentSteps object represents one activity performed during the sample run.
- The icon property stores a React component that can be rendered for that activity.
- Save app/page.tsx.
Your development terminal should complete a page rebuild without a compile error. The default page remains available while the new data waits above its existing component.
Seeing a compile error after adding the data?
Make sure the new imports are outside the existing page component. Import declarations must stay at the top level of the file.
Check that every sample object has matching braces. Confirm that the agentSteps list ends with its closing bracket.
help me find the syntax problem in my agentSteps data
Turn the data into trace lines
A TraceLine represents one action taken by the agent. Mapping over agentSteps keeps the visual structure consistent as the activity list grows.
- Replace everything below the closing ]; line of agentSteps with this page component:
export default function Home() {
return (
<main className="min-h-screen bg-muted/30 px-6 py-16">
<section className="mx-auto w-full max-w-2xl space-y-8 rounded-xl border bg-background p-8 shadow-sm">
<div className="space-y-2">
<p className="text-sm font-medium text-muted-foreground">Agent activity</p>
<h1 className="text-2xl font-semibold">Agent run trace</h1>
<p className="text-sm text-muted-foreground">Follow each action the agent takes while completing the request.</p>
</div>
<Trace>
{/* Convert each sample item into one visible trace line. */}
{agentSteps.map((step) => {
const Icon = step.icon;
return (
<TraceLine key={step.type}>
<TraceIcon>
<Icon />
</TraceIcon>
<TraceTitle>{step.title}</TraceTitle>
<TraceDescription>{step.description}</TraceDescription>
</TraceLine>
);
})}
</Trace>
</section>
</main>
);
}
What does this component do?
- The page creates a bordered activity panel with a heading above the trace.
- The map() call turns every sample object into a TraceLine.
- The TraceIcon, TraceTitle, and TraceDescription primitives give every row the same readable structure.
- Before you save, think about which three activity types you expect the panel to show.
- Save app/page.tsx.
- Return to the browser from earlier.
- Refresh the page.
You should see an Agent run trace panel with three rows. The rows represent reasoning, a tool call, and a subagent run.
Each row should show its own icon. You should also see a title followed by a description.
Trace lines not appearing?
Confirm that the page component maps over agentSteps inside the opening and closing Trace tags.
Check that the trace import path matches the installed component file at components/ui/trace.tsx.
help me debug why my Trace rows are missing from the page
✔️ Awesome, I've got everything!
Great. Your activity panel now renders all three sample agent steps with their icons, titles, and descriptions.
ⓧ I'd like to double check the full code
Compare your complete app/page.tsx file with this reference:
import { BotIcon, BrainIcon, WrenchIcon } from "lucide-react";
import { Trace, TraceDescription, TraceIcon, TraceLine, TraceTitle } from "@/components/ui/trace";
// Each object represents one activity line shown by the agent.
const agentSteps = [
{ type: "reasoning", title: "Reasoning about the request", description: "Identifying the page structure and the files that need to change.", icon: BrainIcon },
{ type: "tool-call", title: "Updating the page", description: "Writing the activity panel into app/page.tsx.", icon: WrenchIcon },
{ type: "subagent", title: "Delegating accessibility review", description: "Asking a subagent to review labels and reading order.", icon: BotIcon },
];
export default function Home() {
return (
<main className="min-h-screen bg-muted/30 px-6 py-16">
<section className="mx-auto w-full max-w-2xl space-y-8 rounded-xl border bg-background p-8 shadow-sm">
<div className="space-y-2">
<p className="text-sm font-medium text-muted-foreground">Agent activity</p>
<h1 className="text-2xl font-semibold">Agent run trace</h1>
<p className="text-sm text-muted-foreground">Follow each action the agent takes while completing the request.</p>
</div>
<Trace>
{/* Convert each sample item into one visible trace line. */}
{agentSteps.map((step) => {
const Icon = step.icon;
return (
<TraceLine key={step.type}>
<TraceIcon>
<Icon />
</TraceIcon>
<TraceTitle>{step.title}</TraceTitle>
<TraceDescription>{step.description}</TraceDescription>
</TraceLine>
);
})}
</Trace>
</section>
</main>
);
}
This reference combines the sample activity model with the page component that renders it.
Your agent activity is visible as a structured trace. Next up, you will assign different statuses so each indicator communicates what the agent is doing.
Set Step Statuses
Your activity panel already shows every part of the agent run. Each row currently carries the same visual weight.
A Trace status makes progress visible at a glance. In this step, you will model completed work alongside running, pending, or failed work.
In this step, get ready to:
- Assign explicit statuses to the sample agent steps.
- Connect each status to its Trace line.
- Confirm that every state has a distinct visual treatment.
Assign statuses to the agent steps
The sample data needs to describe the state of each activity. An explicit status value keeps that state separate from the text shown to the viewer.
- In app/page.tsx, find the existing agentSteps array.
- Replace its declaration with this status-aware version:
// Sample agent steps mirror reasoning, tool calls, and subagent activity.
const agentSteps = [
{
type: "reasoning",
icon: BrainIcon,
title: "Understood the request",
description: "Identified the dashboard sections that need updating.",
status: "done",
},
{
type: "tool-call",
icon: WrenchIcon,
title: "Updating the dashboard",
description: "Editing the page layout and summary cards.",
status: "running",
},
{
type: "subagent",
icon: BotIcon,
title: "Check accessibility",
description: "Review the finished interface for accessibility issues.",
status: "pending",
},
{
type: "tool-call",
icon: WrenchIcon,
title: "Failed to run tests",
description: "The test command stopped after finding a configuration error.",
status: "error",
},
] as const
What do these statuses capture?
- The done value records work that the agent completed.
- The running value records the activity happening now.
- The pending value records work waiting to start.
- The error value records an activity that failed.
Connect the statuses to Trace
The status data now needs to reach each TraceLine. Completed work uses the component's default state.
- Find the Trace block that maps over agentSteps.
- Replace that block with the status-aware rendering below:
<Trace>
{agentSteps.map((step) => {
const Icon = step.icon
// Trace uses its default state to show completed work.
const traceStatus = step.status === "done" ? undefined : step.status
return (
<TraceLine key={step.title} status={traceStatus}>
<TraceIcon>
<Icon />
</TraceIcon>
<TraceTitle>{step.title}</TraceTitle>
<TraceDescription>{step.description}</TraceDescription>
</TraceLine>
)
})}
</Trace>
Why map the completed state?
The sample data uses an explicit done value so every activity has a recorded state. TraceLine represents completed work when its status prop is absent.
The traceStatus variable converts done to undefined. The other three values pass directly to the component.
- Save app/page.tsx.
Before you check, consider which status should draw your attention first.
- Switch back to the browser from earlier.
- Inspect the visual treatment of each activity row.
You should see four rows with different states. The running row shimmers while the pending row looks subdued.
The failed row shows an error treatment. The completed row remains stable in the default state.
Statuses look the same?
Check that status={traceStatus} is attached to TraceLine. Confirm that each object contains the matching status property.
If the page stops compiling, compare the spelling of running, pending, or error with the values above.
help me debug why my TraceLine statuses are not rendering differently
✔️ Awesome, I've got everything!
Great. Double check that app/page.tsx is saved before moving on.
ⓧ I'd like to double check the full code
Compare your complete app/page.tsx file with this version:
import { BotIcon, BrainIcon, WrenchIcon } from "lucide-react"
import {
Trace,
TraceDescription,
TraceIcon,
TraceLine,
TraceTitle,
} from "@/components/ui/trace"
// Sample agent steps mirror reasoning, tool calls, and subagent activity.
const agentSteps = [
{
type: "reasoning",
icon: BrainIcon,
title: "Understood the request",
description: "Identified the dashboard sections that need updating.",
status: "done",
},
{
type: "tool-call",
icon: WrenchIcon,
title: "Updating the dashboard",
description: "Editing the page layout and summary cards.",
status: "running",
},
{
type: "subagent",
icon: BotIcon,
title: "Check accessibility",
description: "Review the finished interface for accessibility issues.",
status: "pending",
},
{
type: "tool-call",
icon: WrenchIcon,
title: "Failed to run tests",
description: "The test command stopped after finding a configuration error.",
status: "error",
},
] as const
function AgentActivityPanel() {
return (
<section className="w-full max-w-2xl rounded-xl border bg-card p-6 shadow-sm">
<div className="mb-6 space-y-1">
<h1 className="text-2xl font-semibold">Agent activity</h1>
<p className="text-sm text-muted-foreground">
Follow the agent as it reasons, uses tools, and delegates work.
</p>
</div>
<Trace>
{agentSteps.map((step) => {
const Icon = step.icon
// Trace uses its default state to show completed work.
const traceStatus = step.status === "done" ? undefined : step.status
return (
<TraceLine key={step.title} status={traceStatus}>
<TraceIcon>
<Icon />
</TraceIcon>
<TraceTitle>{step.title}</TraceTitle>
<TraceDescription>{step.description}</TraceDescription>
</TraceLine>
)
})}
</Trace>
</section>
)
}
export default function Home() {
return (
<main className="flex min-h-screen items-center justify-center bg-muted/30 p-6">
<AgentActivityPanel />
</main>
)
}
That confirms your activity panel can communicate progress without extra explanation. Each agent step now tells the viewer what needs attention.
Your status-driven panel is ready. Next, you will place a formatted diff inside the tool-call step with the Code Block component.
Show the Code Diff
Your Next.js agent activity panel already distinguishes completed work from running work. It also identifies pending work plus failed work.
The final missing detail is the code produced by a tool call. In this step, you will place a line-numbered diff beneath that step with the shadcn/ui CodeBlock component.
In this step, get ready to:
- Attach sample diff lines to the hard-coded agent message data.
- Render the diff beneath the selected tool-call step.
- Verify the removal markers plus the addition markers with visible line numbers.
Add the diff to the tool-call step
A tool-call title confirms that the agent edited a file. A diff exposes the exact lines the agent removed or added.
- Switch back to app/page.tsx in your editor.
- Replace the contents of app/page.tsx with the code below to attach a line-numbered diff to the selected tool call:
import { BrainIcon, FilePenIcon, TerminalIcon } from "lucide-react"
import { CodeBlock, CodeBlockContent, CodeBlockLine } from "@/components/ui/code-block"
import { Trace, TraceCollapsible, TraceCollapsibleContent, TraceCollapsibleTrigger, TraceDescription, TraceIcon, TraceLine, TraceTitle } from "@/components/ui/trace"
type AgentStep = { type: "reasoning" | "tool-call" | "subagent", title: string, description: string, status: "done" | "running" | "pending" | "error", icon: "brain" | "file" | "terminal", diff?: true }
// Model each sample message part with the fields the Trace needs.
const agentSteps: AgentStep[] = [
{ type: "reasoning", title: "Planned the dashboard update", description: "Mapped the request to the existing page.", status: "done", icon: "brain" },
{ type: "tool-call", title: "Edited app/page.tsx", description: "Improved the activity panel button.", status: "done", icon: "file", diff: true },
{ type: "subagent", title: "Reviewing accessibility", description: "Checking labels and visual contrast.", status: "running", icon: "brain" },
{ type: "tool-call", title: "Run the test suite", description: "Waits for the accessibility review.", status: "pending", icon: "terminal" },
{ type: "reasoning", title: "Failed to verify deployment", description: "No deployment target exists for this local sample.", status: "error", icon: "brain" },
]
// Keep the code edit as line data so each row can carry a diff state.
const diffLines = [
{ text: " return <button>Run agent</button>", variant: "removed" },
{ text: " return <button className=\"rounded-md bg-primary px-4 py-2 text-primary-foreground\">", variant: "added" },
{ text: " Run agent</button>", variant: "added" },
] as const
function AgentActivityPanel() {
// Render every message part from data so this panel stays reusable.
return (
<section className="space-y-6 rounded-xl border p-6"><h1 className="text-2xl font-semibold">Agent activity</h1><p className="text-sm text-muted-foreground">Follow each step from planning through verification.</p><Trace>
{agentSteps.map((step) => {
const Icon = step.icon === "file" ? FilePenIcon : step.icon === "terminal" ? TerminalIcon : BrainIcon
const line = <TraceLine key={step.title} status={step.status === "done" ? undefined : step.status}><TraceIcon><Icon /></TraceIcon><TraceTitle>{step.title}</TraceTitle><TraceDescription>{step.description}</TraceDescription></TraceLine>
return step.diff ? <TraceCollapsible key={step.title} defaultOpen><TraceCollapsibleTrigger>{line}</TraceCollapsibleTrigger><TraceCollapsibleContent><CodeBlock size="sm"><CodeBlockContent lineNumbers>{diffLines.map((item) => <CodeBlockLine key={item.text} variant={item.variant}>{item.text}</CodeBlockLine>)}</CodeBlockContent></CodeBlock></TraceCollapsibleContent></TraceCollapsible> : line
})}
</Trace></section>) }
export default function Home() { return <main className="mx-auto min-h-screen max-w-3xl px-6 py-16"><AgentActivityPanel /></main> }
How does this render the diff?
- The agentSteps list keeps each sample message type plus its title plus its description plus its status.
- The diff field selects the tool call that owns the code change.
- TraceCollapsible makes that tool call expandable.
- defaultOpen reveals the code change when the page loads.
- lineNumbers displays a number beside every CodeBlockLine.
- variant="removed" marks the old line. variant="added" marks each replacement line.
- The done value stays explicit in the sample data. The completed line passes no status to TraceLine because its default appearance represents finished work.
- Save app/page.tsx.
- Return to the browser tab from earlier.
Your running development server refreshes the page. You will see a compact code panel beneath Edited app/page.tsx.
You have the key visual payoff now. The selected tool call shows the code it changed.
Diff missing or unstyled?
Confirm that the Edited app/page.tsx entry includes diff: true. The conditional renderer only adds the code panel to that entry.
Check that CodeBlockLine comes from @/components/ui/code-block. Confirm that each diff object uses either added or removed as its variant.
You can also ask for help debugging the missing diff.
✔️ Awesome, I've got everything!
Your saved page now combines the agent steps with an inline code diff.
ⓧ I'd like to double check the full code
Compare your complete app/page.tsx file with this reference.
import { BrainIcon, FilePenIcon, TerminalIcon } from "lucide-react"
import { CodeBlock, CodeBlockContent, CodeBlockLine } from "@/components/ui/code-block"
import { Trace, TraceCollapsible, TraceCollapsibleContent, TraceCollapsibleTrigger, TraceDescription, TraceIcon, TraceLine, TraceTitle } from "@/components/ui/trace"
type AgentStep = { type: "reasoning" | "tool-call" | "subagent", title: string, description: string, status: "done" | "running" | "pending" | "error", icon: "brain" | "file" | "terminal", diff?: true }
// Model each sample message part with the fields the Trace needs.
const agentSteps: AgentStep[] = [
{ type: "reasoning", title: "Planned the dashboard update", description: "Mapped the request to the existing page.", status: "done", icon: "brain" },
{ type: "tool-call", title: "Edited app/page.tsx", description: "Improved the activity panel button.", status: "done", icon: "file", diff: true },
{ type: "subagent", title: "Reviewing accessibility", description: "Checking labels and visual contrast.", status: "running", icon: "brain" },
{ type: "tool-call", title: "Run the test suite", description: "Waits for the accessibility review.", status: "pending", icon: "terminal" },
{ type: "reasoning", title: "Failed to verify deployment", description: "No deployment target exists for this local sample.", status: "error", icon: "brain" },
]
// Keep the code edit as line data so each row can carry a diff state.
const diffLines = [
{ text: " return <button>Run agent</button>", variant: "removed" },
{ text: " return <button className=\"rounded-md bg-primary px-4 py-2 text-primary-foreground\">", variant: "added" },
{ text: " Run agent</button>", variant: "added" },
] as const
function AgentActivityPanel() {
// Render every message part from data so this panel stays reusable.
return (
<section className="space-y-6 rounded-xl border p-6"><h1 className="text-2xl font-semibold">Agent activity</h1><p className="text-sm text-muted-foreground">Follow each step from planning through verification.</p><Trace>
{agentSteps.map((step) => {
const Icon = step.icon === "file" ? FilePenIcon : step.icon === "terminal" ? TerminalIcon : BrainIcon
const line = <TraceLine key={step.title} status={step.status === "done" ? undefined : step.status}><TraceIcon><Icon /></TraceIcon><TraceTitle>{step.title}</TraceTitle><TraceDescription>{step.description}</TraceDescription></TraceLine>
return step.diff ? <TraceCollapsible key={step.title} defaultOpen><TraceCollapsibleTrigger>{line}</TraceCollapsibleTrigger><TraceCollapsibleContent><CodeBlock size="sm"><CodeBlockContent lineNumbers>{diffLines.map((item) => <CodeBlockLine key={item.text} variant={item.variant}>{item.text}</CodeBlockLine>)}</CodeBlockContent></CodeBlock></TraceCollapsibleContent></TraceCollapsible> : line
})}
</Trace></section>) }
export default function Home() { return <main className="mx-auto min-h-screen max-w-3xl px-6 py-16"><AgentActivityPanel /></main> }
Verify the finished agent panel
The final check covers the whole panel. It confirms that the new code output still fits beside every status from the previous step.
Before you check the finished panel, make a quick prediction about the status treatments you expect to remain visible around the new diff:
- Refresh the browser page.
You will see five agent steps with completed plus running plus pending plus error treatments. Each entry retains its icon plus its description.
Beneath Edited app/page.tsx, you will see three numbered lines. One line has removal styling. Two lines have addition styling.
Strong finish. Your reusable panel now shows every agent state plus the exact code change inline.
Secret mission
Create an Agent Run Handoff
Turn your existing activity panel into an incident-review tool without changing its code. Use the status boundary to reconstruct the sample run. Cite the line-numbered diff to create a recovery handoff for another developer.
Clean Up Your Resources
Clean Up Your Resources
Everything you built stays on your computer. The hard-coded sample data creates no cloud charges.
Choose whether to keep the panel available, pause its development server, or delete its files from your computer.
Resources you used:
- The running local Next.js development server that serves your agent activity panel.
- The local Next.js project directory containing app/page.tsx, components.json, components/ui/code-block.tsx, plus components/ui/trace.tsx.
Keep everything running
No action needed. Choose this if you want to reuse the activity panel or keep experimenting with its sample data.
- Keep the development server running while you test the different Trace statuses.
- Keep the local project directory so you can reuse the activity panel in another agent interface.
- Expect no cloud charges because every part of the project remains local.
Pause - I'll come back to this later
Stop the local development server to free its port while preserving every project file.
- Switch back to the terminal where the development server is running.
- Press Ctrl+C to stop the process.
The returned terminal prompt confirms that the local development server has stopped.
- Keep the local project directory on your computer until you are ready to continue.
Delete - I don't want to use this again
Deleting your finished panel can feel drastic. This option permanently removes the local project directory plus its running development server.
- Switch back to the terminal where the development server is running.
- Press Ctrl+C to stop the process.
The returned terminal prompt confirms that the local development server has stopped.
The correct project directory contains components.json. It also contains app/page.tsx plus components/ui/trace.tsx.
macOS
- Press Cmd+Space to open your search bar.
- Type Finder into the search bar.
- Press Enter to open Finder.
- Locate the project directory that contains the files listed above.
- Select the project directory.
- Press Cmd+Delete to move it to Trash.
The project directory now sits in Trash. It no longer appears in its original location.
- Open Trash from the Dock.
- Review its contents for anything you still need.
- Empty Trash to remove the project directory permanently.
- Return to the directory's previous location.
- Confirm that the project directory is no longer listed.
Windows
- Press the Windows key to open your search bar.
- Type File Explorer into the search bar.
- Press Enter to open File Explorer.
- Locate the project directory that contains the files listed above.
- Select the project directory.
- Press Delete to move it to Recycle Bin.
The project directory now sits in Recycle Bin. It no longer appears in its original location.
- Open Recycle Bin from the Desktop.
- Review its contents for anything you still need.
- Empty Recycle Bin to remove the project directory permanently.
- Return to the directory's previous location.
- Confirm that the project directory is no longer listed.
Nice Work!
Nice Work!
You made it! Your reusable agent activity panel now turns hard-coded message data into a clear Next.js view of an agent run.
You've learned how to:
- Build a reusable agent activity panel that presents reasoning steps, tool calls, and subagent work in one structured view.
- Render done, running, pending, and error statuses with the shadcn Trace component so each step communicates its current state.
- Display an inline code diff with visible line numbers using the shadcn Code Block component.
- Complete an optional Secret Mission to push your skills further.
Ready to quiz yourself?