# Zeno Progress: Deploy Log

> A deploy streams its build log under a bar in the header, labelled with the current phase. Waiting for the health check hangs for seconds with nothing new in the log, and the ruler zooms the whole time until the 200 lands.

A shadcn/ui block from the [@keyframeui](https://keyframeui.com/?utm_source=shadcn.exchange) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/keyframeui/zeno-progress-deploy-demo).

- Page, with a live preview: https://shadcn.exchange/r/keyframeui/zeno-progress-deploy-demo
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/keyframeui/zeno-progress-deploy-demo-light-3b0340295db525d6.png
- Registry item: https://keyframeui.com/r/zeno-progress-deploy-demo.json
- Categories: progress, loading, ai, canvas
- Listed: 2026-10-23, source last changed 2026-10-23

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/keyframeui/zeno-progress-deploy-demo.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/zeno-progress.json`

## Notes from the registry

Live preview, examples and props: https://keyframeui.com/components/zeno-progress#zeno-progress-deploy-demo

## Files

### `registry/demos/zeno-progress-deploy-demo.tsx`

```tsx
"use client";

import * as React from "react";
import { AnimatePresence, motion, useInView } from "motion/react";

import { ZenoProgress } from "@/components/ui/zeno-progress";

type Line = { at: number; text: string; phase: string; tone?: "dim" | "ok" | "wait" };

// ms from the start of the run. The health check hangs on purpose: nothing
// arrives for seconds, and the bar's ruler zooms the whole time.
const script: Line[] = [
  { at: 300, text: "Cloning acme/storefront@8f3c2a1", phase: "Cloning" },
  { at: 900, text: "Restored build cache (412 MB)", phase: "Cloning", tone: "dim" },
  { at: 1500, text: "$ npm ci", phase: "Installing" },
  { at: 2300, text: "added 1284 packages in 2.7s", phase: "Installing", tone: "dim" },
  { at: 2900, text: "$ next build", phase: "Building" },
  { at: 3700, text: "✓ Compiled successfully in 2.9s", phase: "Building", tone: "ok" },
  { at: 4300, text: "✓ Generating static pages (42/42)", phase: "Building", tone: "ok" },
  { at: 4900, text: "Uploading build output (18.4 MB)", phase: "Uploading" },
  { at: 5600, text: "Starting 3 instances in iad1", phase: "Starting" },
  { at: 6200, text: "Waiting for health check…", phase: "Waiting for health check", tone: "wait" },
  { at: 13800, text: "✓ GET /api/health 200 in 41ms", phase: "Waiting for health check", tone: "ok" },
];
const DONE_AT = 14300;

const toneClass = {
  dim: "text-zinc-500",
  ok: "text-emerald-400",
  wait: "text-amber-300",
} as const;

export function ZenoProgressDeployDemo() {
  const ref = React.useRef<HTMLDivElement>(null);
  const inView = useInView(ref, { once: true, amount: 0.4 });
  const [run, setRun] = React.useState(0);
  const [elapsed, setElapsed] = React.useState(0);

  React.useEffect(() => {
    if (!inView) return;
    const start = performance.now();
    const timer = window.setInterval(() => {
      const t = performance.now() - start;
      setElapsed(Math.min(t, DONE_AT));
      if (t >= DONE_AT) window.clearInterval(timer);
    }, 100);
    return () => window.clearInterval(timer);
  }, [inView, run]);

  const lines = script.filter((line) => line.at <= elapsed);
  const done = elapsed >= DONE_AT;
  const phase = lines.at(-1)?.phase ?? "Queued";

  const redeploy = () => {
    setElapsed(0);
    setRun((r) => r + 1);
  };

  return (
    <div
      ref={ref}
      className="w-full max-w-xl overflow-hidden rounded-2xl border border-zinc-800 bg-zinc-950 font-mono text-[13px] text-zinc-300 shadow-sm"
    >
      <div className="border-b border-zinc-800/80 px-4 pt-3.5 pb-3">
        <div className="mb-3 flex items-center justify-between gap-3 font-sans">
          <div className="flex min-w-0 items-center gap-2">
            <span className="size-2 shrink-0 rounded-full bg-emerald-400/90" aria-hidden />
            <span className="truncate text-sm font-medium text-zinc-100">storefront</span>
            <span className="shrink-0 rounded-full border border-zinc-800 px-2 py-0.5 text-xs text-zinc-400">
              production
            </span>
          </div>
          <button
            type="button"
            onClick={redeploy}
            disabled={!done}
            className="shrink-0 rounded-full border border-zinc-700 px-3 py-1 text-xs font-medium text-zinc-200 transition-[opacity,transform] hover:bg-zinc-900 active:scale-[0.97] disabled:opacity-40"
          >
            Redeploy
          </button>
        </div>
        <ZenoProgress
          key={inView ? "live" : "idle"}
          done={done}
          label={phase}
          doneLabel="Deployed to storefront.acme.app"
          interval={1500}
          className="font-sans text-zinc-400"
          barClassName="text-zinc-100"
        />
      </div>

      <div className="flex h-64 flex-col justify-end overflow-hidden px-4 py-3 leading-6">
        <AnimatePresence initial={false}>
          {lines.map((line) => (
            <motion.div
              key={`${run}-${line.at}`}
              layout="position"
              initial={{ opacity: 0, y: 6 }}
              animate={{ opacity: 1, y: 0 }}
              transition={{ type: "spring", visualDuration: 0.3, bounce: 0 }}
              className={`truncate ${line.tone ? toneClass[line.tone] : ""}`}
            >
              {line.text}
            </motion.div>
          ))}
          {done ? (
            <motion.div
              key={`${run}-done`}
              layout="position"
              initial={{ opacity: 0, y: 6 }}
              animate={{ opacity: 1, y: 0 }}
              transition={{ type: "spring", visualDuration: 0.3, bounce: 0 }}
              className="truncate text-zinc-100"
            >
              ▲ Deployed in 14.3s
            </motion.div>
          ) : null}
        </AnimatePresence>
      </div>
    </div>
  );
}
```
