Scroll Reveal
ScrollEntrance on scroll that plays once and stays, staggers its children, scrubs with the scroll position when asked, honours reduced motion, and keeps working after every ClientRouter navigation.
An AI agent's run, replayed in the page: the task at the top, then four lanes that fill in order (the plan ticks off, tool calls spin and finish with their time, a diff writes in line by line, a person is asked to approve and the approve reads Approved), then the result with its numbers. The server HTML is the end state, so without JavaScript and under reduced motion every lane is filled; the animation only plays the path to it, once, in 8 to 12 s. A native checkbox drawn as a small Pause button holds the run, and so does a pointer over it. Zero JavaScript of its own; replay adds a button with a small script.
Sample run
An imaginary agent on an imaginary repository. The lanes fill in order; under reduced motion, or without JavaScript, they are filled from the start.
Task Fix the failing checkout test in apps/web
Done
read_log(job 4182)done0.4 sgrep("toBe(19.99)" apps/web)done0.2 srun_tests(apps/web)done38 sexport function total(items: Item[]) { const sum = items.reduce(add, 0); return sum * 1.2; const gross = sum * 1.2; return Math.round(gross * 100) / 100;}Maya Kim
Ready to merge. The suite passes and the change is two lines.
Approved
Checkout totals round to cents
Live, from the library itself, at full size: scroll, hover, navigate away and back. The demo is never gated.
Variants · each row writes the attribute the prop writes; the section's own CSS answers
JavaScript of its own
0 B
Uses Reveal and the shared runtime (1.9 kB raw, once per site). With those included: 3.0 kB raw.
With replay: 324 B raw of its own on Reveal and the runtime, measured on its own fixture. The default above ships none of it.
CSS 21.9 kB raw including the base tokens. Measured from a production build, and again in CI for every change that can move it.
Browser support
Chrome 105 · Firefox 121 · Safari 15.4; needs has. Elsewhere: without :has() the run plays once in view and cannot be held; it still ends on the filled lanes.
The demo above is never gated; the source is. With a key in components.json (see account), the registry serves it like any other item:
npx shadcn@latest add @moonarc-pro/agent-run---
import AgentRun from '@/components/moonarc/pro/AgentRun.astro';
---
<AgentRun
headline="Watch a run from task to merge"
task="Fix the failing checkout test in apps/web"
steps={{
items: ['Read the failing job', 'Find the cause', 'Change the code', 'Run the tests again'],
calls: [
{ name: 'read_log', args: 'job 4182', took: '0.4 s' },
{ name: 'run_tests', args: 'apps/web', took: '38 s' },
],
diff: {
path: 'apps/web/src/cart.ts',
lines: [
{ kind: 'del', text: 'return total * 100;' },
{ kind: 'add', text: 'return Math.round(total * 100);' },
],
},
review: { who: 'Maya Kim', note: 'Ready to merge. 48 tests pass.' },
}}
result={{ title: 'Checkout total rounds to cents', stats: [{ label: 'Tests', value: '48 passed' }, { label: 'Time', value: '2 min 41 s' }] }}
replay
/>Without a key the registry answers 401 with an upgrade message. Everything this section composes is free: reveal.
| Prop | Type | Default | Description |
|---|---|---|---|
task | string | none | The task the run is for, in a person's words, at the top of the run. |
steps | { items, calls, diff, review } | none | The four lanes. items: the plan, three to five lines. calls: two to four tool calls, { name, args?, took? }, printed as name(args) in the monospace face with the time taken. diff: { path, lines: { kind: 'add' | 'del' | 'ctx', text }[] }, two to eight lines. review: { who, note?, avatar? }: who is asked (their initials are drawn unless avatar is an image URL) and what the agent says to them. |
result | { title, stats? } | none | The end of the run: what changed, and numbers such as tests passed, time and tokens, each { label, value }. |
headline | string | none | The headline above the run. The headline slot wins over it. |
eyebrow | string | none | Small label above the headline. The eyebrow slot wins over it. |
copy | string | none | The paragraph under the headline. The copy slot wins over it. |
lanes | { plan?, tools?, changes?, review? } | Plan · Tools · Changes · Review | The four lane titles. |
labels | { task?, running?, done?, call?, approve?, approved?, replay? } | Task · Running · Done · done · Approve · Approved · Replay | The words the run prints around its content: the task's label, the status while it runs and once it is done, what a screen reader hears after each finished call, the approve before and after it is pressed, and the replay button. |
pauseLabel | string | 'Pause' | The pause control's label: a checkbox drawn as a small button, checked while the run is held. |
replay | boolean | false | A button that plays the run again from its first beat: an internal script, measured on its own fixture. Hidden without JavaScript and under reduced motion. |
level | 1 | 2 | 2 | Heading level of the headline: 2 under the page's h1, 1 when the section opens the page. The lane titles sit one level under it. |
tone | 'surface' | 'muted' | 'ink' | 'brand' | unset (paints nothing) | The ground: the page surface, a muted band, an always-dark ink panel, or a deep brand panel. With theme.css the run's panel takes the tone's card. On brand what is live is marked in the text colour, since the accent is the ground. |
density | 'compact' | 'cozy' | 'roomy' | 'cozy' | Block padding and the gap above the run ×0.8 / ×1 / ×1.25 through --ma-density. |
The section reads the theme contract (shadcn's variable names with the base tokens as fallback), so a pasted theme or one --ma-brand recolours it. Its class names are the override surface and stay stable; a named slot replaces the matching prop with your own markup.
.ma-agent-run.ma-agent-run__approve.ma-agent-run__approved.ma-agent-run__args.ma-agent-run__aside.ma-agent-run__ask.ma-agent-run__avatar.ma-agent-run__bars.ma-agent-run__beat.ma-agent-run__call.ma-agent-run__calls.ma-agent-run__check.ma-agent-run__code.ma-agent-run__controls.ma-agent-run__copy.ma-agent-run__cue.ma-agent-run__decision.ma-agent-run__diff.ma-agent-run__done.ma-agent-run__dot.ma-agent-run__draw.ma-agent-run__eyebrow.ma-agent-run__fn.ma-agent-run__head.ma-agent-run__headline.ma-agent-run__inner.ma-agent-run__item.ma-agent-run__items.ma-agent-run__lane.ma-agent-run__lane-title.ma-agent-run__lanes.ma-agent-run__line.ma-agent-run__live.ma-agent-run__mark.ma-agent-run__note.ma-agent-run__ok.ma-agent-run__path.ma-agent-run__pause.ma-agent-run__pause-control.ma-agent-run__pause-icon.ma-agent-run__pip.ma-agent-run__play.ma-agent-run__replay.ma-agent-run__result.ma-agent-run__result-title.ma-agent-run__ring.ma-agent-run__run.ma-agent-run__running.ma-agent-run__scroll.ma-agent-run__sign.ma-agent-run__spin.ma-agent-run__state.ma-agent-run__stats.ma-agent-run__status.ma-agent-run__task.ma-agent-run__task-label.ma-agent-run__task-text.ma-agent-run__tick.ma-agent-run__took.ma-agent-run__top.ma-agent-run__who.ma-in
slot="eyebrow"slot="headline"slot="copy"slot="aside"
The end state at once: every lane filled, every call done, the diff written, the approval given, the result shown. Nothing travels and the controls are not drawn.
ClientRouterCSS only (the replay button's script rebinds through the shared runtime). A new page starts a new run when it comes into view.
Replaces: Agent plan and tool call demo (Aceternity) · AI workflow animation (Magic UI). See the migration table.
Entrance on scroll that plays once and stays, staggers its children, scrubs with the scroll position when asked, honours reduced motion, and keeps working after every ClientRouter navigation.
Sample conversation
An imaginary agent on an imaginary repository. Open a call to read what it returned.
You
Fix the failing checkout test in apps/web
Sample agent
One assertion fails: the total keeps a fraction of a cent. I will find where it is computed.
read_log(job 4182)0.4 sFAIL apps/web/cart.test.ts
total > rounds to cents
expected 23.99, received 23.988grep("* 1.2" apps/web/src)0.2 sapps/web/src/cart/total.ts:3 return sum * 1.2;run_tests(apps/web)38 sTests 48 passed (48)
Time 37.9 sSample agent
Push the change to fix/checkout-rounding and open a pull request?
Approved
Sample agent
Done. The total now rounds to cents after tax, and all 48 tests in apps/web pass. The pull request is open for review.
You
Update the lockfile and fix what breaks
Sample agent
Three packages have new minor releases. I will update them one at a time and run the suite after each.
outdated()1.1 sdate-lib 3.2.0 → 3.4.1
http-kit 8.0.2 → 8.1.0
ui-icons 1.9.0 → 1.12.0install(date-lib@3.4.1)6.2 slockfile updated, 1 package changedrun_tests(--changed)52 sTests 211 passed (211)Sample agent
Commit the three updates to deps/2026-09 and open a pull request?
Approved
Sample agent
Updated three packages. One test needed a new date format, which I changed; the other 211 passed as they were.
You
Explain why main is red
Sample agent
The last merge changed a shared type. I will compare the two runs.
list_runs(main, 2)0.3 s#4190 failed 3 min ago
#4189 passed 41 min agodiff_runs(4189, 4190)0.8 snew failure: api/orders.test.ts
Order.total is string, expected numberSample agent
Open an issue for the author of the merge with this explanation?
Approved
Sample agent
Main is red because a merge 40 minutes ago made Order.total a string. One test in api/orders expects a number. Reverting that line or parsing it fixes the build.
A conversation with an AI agent, replayed in the page: the reader's message, the agent's reply with its tool calls folded into <details> (the call and its result in the monospace face), a request for approval that is approved, and the final answer, typed, with a button that copies it; a prompt box at the foot as a demo form.
A hero whose entry point is a prompt box: a small label, a short headline, a lead, a large PromptInput with Enter to send, a status line and example tasks.
A vertical timeline: the line draws itself down the page in step with the scroll, each step revealing from the line's side as it is reached, markers numbered or dated.
Also: view transitions · how costs are measured · browser support · accessibility policy · five-minute setup · MCP for agents · this page as Markdown