Timeline Agent Lanes

Agent

Live multi-agent turn tracker — one hairline lane per agent, the lane holding the turn brightened to foreground, handoffs drawn as stepped connectors with mono duration labels.

Install
npx shadcn add https://design.helpmarq.com/r/timeline-agent-lanes.json
Source
registry/core/timeline-agent-lanes/component.tsx
"use client";

import { useEffect, useMemo, useState } from "react";

// A live multi-agent turn tracker: one hairline lane per agent, the lane
// currently holding the turn brightened to --foreground, idle lanes resting
// at --muted. This shows the PRESENT moment on a rolling `windowMs` window
// (a "now" line pins the right edge) — it is not a post-hoc trace viewer, so
// there is deliberately no scrub/seek control: adding one would turn this
// back into exactly the span-tree debugger it's meant to differ from. Past
// turns stay visible inside the window (dimmer than the live one) only so a
// handoff connector has two ends to draw between; once a turn's end falls
// behind the window it silently ages off the left, same as a real monitor.
//
// Concurrency (two+ lanes active at once, the normal shape of a parallel
// fan-out) needs no special case: any number of turns with no `end` can
// coexist across different agents and each just renders its own bar.
//
// A handoff connector (stepped DOM line + a Geist Mono duration chip) is
// drawn from a finished turn to the next turn on a DIFFERENT agent that
// starts at or after it ends (small slack for real-world event jitter) —
// i.e. a genuine baton pass, not two turns that happen to overlap. One
// finished turn can feed more than one connector (a fan-out reads as one
// source splitting into two stepped lines). Labels are packed against real
// collisions, not just parity-staggered: walking left→right, each label
// drops into the lowest stacking level whose last-placed label sits at
// least LABEL_MIN_SEP_PCT away in x (even levels above the rail, odd below,
// each extra pair stepping one row further out), so three or more handoffs
// close together in x fan into that many legible rows instead of one smear.
//
// Beyond MAX_LANES agents the extra rows collapse into one aggregate "+N
// more" lane that brightens with a live count badge when any of them are
// active, but never participates in handoff connectors (there's no single
// row to draw a line to) — a stated degrade, not a silent one.
//
// Pure DOM: every bar/hairline/connector segment is a plain absolutely
// positioned div, positioned with independent left:%/top:px so nothing goes
// through a scaled SVG viewBox (which would stretch mono text). No canvas,
// no SVG. Zero dependencies.

export interface RelayLaneAgent {
  id: string;
  label: string;
}

export interface RelayLaneTurn {
  /** stable id, unique across the whole run */
  id: string;
  agentId: string;
  /** ms timestamp the turn began */
  start: number;
  /** ms timestamp the turn ended — omit while the turn is still live */
  end?: number;
}

export interface RelayLaneProps {
  /** lane order, top to bottom */
  agents: RelayLaneAgent[];
  /** the run so far — append/mutate over time, oldest first is not required */
  turns: RelayLaneTurn[];
  /** accessible name for the region */
  label?: string;
  /** width of the rolling visible window, in ms */
  windowMs?: number;
  /** controlled "now" in ms — omit to let the component tick its own clock */
  now?: number;
  className?: string;
}

const MAX_LANES = 12;
const ROW_PITCH = 40;
const BAR_H = 4;
// Wide enough for a realistic agent name *plus* its running-duration chip. At
// 132px (and still at 156px for the longer names) the two together overflowed and truncated the label ("Orchestra…") —
// and only ever on the lane that was currently in flight, i.e. the one lane a
// reader most needs to identify.
const LABEL_W = 176;
const MIN_BAR_PCT = 1.2;
const HANDOFF_SLACK_MS = 400;
const LABEL_MIN_SEP_PCT = 10;

function formatDuration(ms: number): string {
  const total = Math.max(0, Math.round(ms / 1000));
  const m = Math.floor(total / 60);
  const s = total % 60;
  return `${m}:${String(s).padStart(2, "0")}`;
}

interface Handoff {
  key: string;
  x1: number;
  y1: number;
  x2: number;
  y2: number;
  durationMs: number;
  band: "above" | "below";
  /** how many stagger rows out from the rail this label sits, per its band */
  tier: number;
}

export function RelayLane({
  agents,
  turns,
  label = "Agent turn tracker",
  windowMs = 20000,
  now: nowProp,
  className = "",
}: RelayLaneProps) {
  // uncontrolled clock: seeded to 0 so server/client first paint match, then
  // set to the real time and ticked once mounted. Fully controlled (nowProp
  // set) runs no timer at all.
  const [internalNow, setInternalNow] = useState(0);
  useEffect(() => {
    if (nowProp !== undefined) return;
    setInternalNow(Date.now());
    const id = window.setInterval(() => setInternalNow(Date.now()), 1000);
    return () => window.clearInterval(id);
  }, [nowProp]);
  const now = nowProp ?? internalNow;
  const windowStart = now - windowMs;

  const visibleAgents = agents.length > MAX_LANES ? agents.slice(0, MAX_LANES - 1) : agents;
  const overflowAgents = agents.length > MAX_LANES ? agents.slice(MAX_LANES - 1) : [];
  const hasOverflow = overflowAgents.length > 0;

  const rowIndex = useMemo(() => {
    const m = new Map<string, number>();
    visibleAgents.forEach((a, i) => m.set(a.id, i));
    return m;
  }, [visibleAgents]);

  const rowCount = visibleAgents.length + (hasOverflow ? 1 : 0);

  const openTurnByAgent = useMemo(() => {
    const m = new Map<string, RelayLaneTurn>();
    for (const t of turns) {
      if (t.end === undefined) m.set(t.agentId, t);
    }
    return m;
  }, [turns]);

  const overflowActiveCount = overflowAgents.filter((a) =>
    openTurnByAgent.has(a.id)
  ).length;

  // handoffs: for every turn, its predecessor is the most-recently-ended
  // OTHER-agent turn that finished at or before it starts (+ slack)
  const handoffs = useMemo<Handoff[]>(() => {
    const finished = turns.filter((t) => t.end !== undefined);
    const sorted = [...turns].sort((a, b) => a.start - b.start);
    const raw: { from: RelayLaneTurn; to: RelayLaneTurn; midX: number }[] = [];

    for (const t of sorted) {
      let best: RelayLaneTurn | undefined;
      for (const f of finished) {
        if (f.id === t.id || f.agentId === t.agentId) continue;
        if (f.end! <= t.start + HANDOFF_SLACK_MS) {
          if (!best || f.end! > best.end!) best = f;
        }
      }
      if (!best) continue;
      if (best.end! < windowStart) continue; // aged fully out of view
      const fromRow = rowIndex.get(best.agentId);
      const toRow = rowIndex.get(t.agentId);
      if (fromRow === undefined || toRow === undefined) continue; // overflowed lane

      const x1 = Math.min(100, Math.max(0, ((best.end! - windowStart) / windowMs) * 100));
      const x2 = Math.min(100, Math.max(0, ((t.start - windowStart) / windowMs) * 100));
      raw.push({
        from: best,
        to: t,
        midX: (x1 + x2) / 2,
      });
    }

    // Real de-overlap, not a parity stagger: three or more handoffs whose
    // labels land close together in x (a fan-out from one source is exactly
    // this shape) all overprinted when the only escape was a single above/
    // below flip. Instead, walk the labels left→right and drop each into the
    // lowest stacking LEVEL whose most-recently-placed label is at least
    // LABEL_MIN_SEP_PCT away in x — the classic occupied-range packer. Even
    // levels ride above the rail, odd below; every additional pair of levels
    // (its `tier`) steps one row further out, so N coincident labels fan into
    // N legible rows instead of a single smear.
    const lastXByLevel: number[] = [];
    const levelByIndex = new Map<number, number>();
    [...raw.keys()]
      .sort((a, b) => raw[a]!.midX - raw[b]!.midX)
      .forEach((idx) => {
        let level = 0;
        while (
          lastXByLevel[level] !== undefined &&
          raw[idx]!.midX - lastXByLevel[level]! < LABEL_MIN_SEP_PCT
        ) {
          level++;
        }
        lastXByLevel[level] = raw[idx]!.midX;
        levelByIndex.set(idx, level);
      });

    return raw.map((h, i): Handoff => {
      const level = levelByIndex.get(i) ?? 0;
      const band: "above" | "below" = level % 2 === 0 ? "above" : "below";
      const tier = Math.floor(level / 2);

      const fromRow = rowIndex.get(h.from.agentId)!;
      const toRow = rowIndex.get(h.to.agentId)!;
      const x1 = Math.min(100, Math.max(0, ((h.from.end! - windowStart) / windowMs) * 100));
      const x2 = Math.min(100, Math.max(0, ((h.to.start - windowStart) / windowMs) * 100));

      return {
        key: `${h.from.id}->${h.to.id}`,
        x1,
        y1: fromRow * ROW_PITCH + ROW_PITCH / 2,
        x2,
        y2: toRow * ROW_PITCH + ROW_PITCH / 2,
        durationMs: h.from.end! - h.from.start,
        band,
        tier,
      };
    });
  }, [turns, rowIndex, windowStart, windowMs]);

  const activeAgents = visibleAgents.filter((a) => openTurnByAgent.has(a.id));
  const summary =
    activeAgents.length === 0
      ? overflowActiveCount > 0
        ? `${overflowActiveCount} of the overflow agents are active.`
        : "No agent currently has the turn."
      : activeAgents.length === 1
        ? `${activeAgents[0]!.label} has the turn.`
        : `${activeAgents.map((a) => a.label).join(", ")} are active concurrently.`;

  return (
    <div
      role="region"
      aria-label={label}
      className={`rounded-md border border-border bg-surface p-4 ${className}`}
    >
      <div
        aria-hidden="true"
        className="mb-1 flex font-mono text-[9px] uppercase tracking-[0.14em] text-muted"
        style={{ paddingLeft: LABEL_W }}
      >
        <span className="flex-1 text-right">now</span>
      </div>
      <div
        className="grid"
        style={{
          gridTemplateColumns: `${LABEL_W}px 1fr`,
          gridAutoRows: `${ROW_PITCH}px`,
        }}
      >
        {visibleAgents.map((a, i) => {
          const open = openTurnByAgent.get(a.id);
          const elapsed = open ? now - open.start : null;
          return (
            <div
              key={a.id}
              className="flex min-w-0 items-center gap-2 pr-3"
              style={{ gridColumn: 1, gridRow: i + 1 }}
            >
              <span
                aria-hidden="true"
                className={`h-1.5 w-1.5 shrink-0 rounded-full ${
                  open
                    ? "bg-foreground motion-safe:animate-pulse"
                    : "bg-muted"
                }`}
              />
              <span
                className={`min-w-0 flex-1 truncate text-sm ${
                  open ? "text-foreground" : "text-muted"
                }`}
              >
                {a.label}
              </span>
              {elapsed !== null && (
                <span className="shrink-0 font-mono text-[10px] tabular-nums text-muted">
                  {formatDuration(elapsed)}
                </span>
              )}
            </div>
          );
        })}

        {hasOverflow && (
          <div
            className="flex min-w-0 items-center gap-2 pr-3"
            style={{ gridColumn: 1, gridRow: visibleAgents.length + 1 }}
          >
            <span
              aria-hidden="true"
              className={`h-1.5 w-1.5 shrink-0 rounded-full ${
                overflowActiveCount > 0
                  ? "bg-foreground motion-safe:animate-pulse"
                  : "bg-muted"
              }`}
            />
            <span
              className={`min-w-0 flex-1 truncate text-sm ${
                overflowActiveCount > 0 ? "text-foreground" : "text-muted"
              }`}
            >
              +{overflowAgents.length} more
            </span>
            {overflowActiveCount > 0 && (
              <span className="shrink-0 font-mono text-[10px] tabular-nums text-muted">
                {overflowActiveCount} active
              </span>
            )}
          </div>
        )}

        {/* track overlay: spans the full lane stack, everything inside is
            positioned with independent left:%/top:px so no axis distorts
            the other */}
        <div
          className="relative overflow-hidden"
          style={{
            gridColumn: 2,
            gridRow: `1 / span ${rowCount}`,
          }}
        >
          {/* idle baseline: one full-width hairline per lane */}
          {visibleAgents.map((a, i) => (
            <div
              key={a.id}
              aria-hidden="true"
              className="absolute left-0 h-px w-full bg-muted"
              style={{ top: i * ROW_PITCH + ROW_PITCH / 2 }}
            />
          ))}
          {hasOverflow && (
            <div
              aria-hidden="true"
              className="absolute left-0 h-px w-full bg-muted"
              style={{ top: visibleAgents.length * ROW_PITCH + ROW_PITCH / 2 }}
            />
          )}

          {/* turn bars: finished turns dimmer, the live/open turn full
              brightness with a breathing pulse */}
          {turns.map((t) => {
            const row = rowIndex.get(t.agentId);
            if (row === undefined) return null;
            const effectiveEnd = t.end ?? now;
            if (effectiveEnd < windowStart) return null;
            const clampedStart = Math.max(t.start, windowStart);
            const clampedEnd = Math.min(effectiveEnd, now);
            if (clampedEnd <= clampedStart && t.end !== undefined) return null;
            const leftPct = ((clampedStart - windowStart) / windowMs) * 100;
            const widthPct = Math.max(
              ((clampedEnd - clampedStart) / windowMs) * 100,
              MIN_BAR_PCT
            );
            const isOpen = t.end === undefined;
            return (
              <div
                key={t.id}
                aria-hidden="true"
                className={`absolute rounded-full transition-[left,width] duration-500 ease-out motion-reduce:transition-none ${
                  isOpen
                    ? "bg-foreground motion-safe:animate-pulse"
                    : "bg-foreground/45"
                }`}
                style={{
                  left: `${leftPct}%`,
                  width: `${widthPct}%`,
                  top: row * ROW_PITCH + ROW_PITCH / 2 - BAR_H / 2,
                  height: BAR_H,
                }}
              />
            );
          })}

          {/* handoff connectors: stepped (horizontal/vertical/horizontal)
              lines built from plain divs, plus a mono duration chip */}
          {handoffs.map((h) => {
            const midX = (h.x1 + h.x2) / 2;
            const yTop = Math.min(h.y1, h.y2);
            const yBottom = Math.max(h.y1, h.y2);
            // each extra tier steps the label one 15px row further from the
            // rail, in its band's direction, so stacked labels never overlap
            const TIER_STEP = 18;
            const rawChipTop =
              h.band === "above"
                ? Math.min(h.y1, h.y2) - 20 - h.tier * TIER_STEP
                : Math.max(h.y1, h.y2) + 8 + h.tier * TIER_STEP;
            // clamp inside the (now overflow-hidden) track box so a handoff
            // touching the first or last lane never clips its own label
            const chipTop = Math.min(
              Math.max(rawChipTop, 0),
              rowCount * ROW_PITCH - 18
            );
            return (
              <div key={h.key} aria-hidden="true">
                <div
                  className="absolute h-px bg-foreground/25"
                  style={{
                    top: h.y1,
                    left: `${Math.min(h.x1, midX)}%`,
                    width: `${Math.abs(midX - h.x1)}%`,
                  }}
                />
                <div
                  className="absolute w-px bg-foreground/25"
                  style={{
                    left: `${midX}%`,
                    top: yTop,
                    height: yBottom - yTop,
                  }}
                />
                <div
                  className="absolute h-px bg-foreground/25"
                  style={{
                    top: h.y2,
                    left: `${Math.min(h.x2, midX)}%`,
                    width: `${Math.abs(h.x2 - midX)}%`,
                  }}
                />
                <div
                  className="absolute -translate-x-1/2 whitespace-nowrap rounded-sm border border-border bg-background px-1 py-0.5 font-mono text-[10px] tabular-nums text-muted"
                  style={{ left: `${midX}%`, top: chipTop }}
                >
                  {formatDuration(h.durationMs)}
                </div>
              </div>
            );
          })}

          {/* now line: the right edge is always the present moment */}
          <div
            aria-hidden="true"
            className="absolute right-0 top-0 w-px bg-border"
            style={{ height: rowCount * ROW_PITCH }}
          />
        </div>
      </div>

      <div aria-live="polite" aria-atomic="true" className="sr-only">
        {summary}
      </div>
    </div>
  );
}
Build spec

A live multi-agent turn tracker for orchestration UIs — 'who is doing what right now'. Renders one horizontal hairline lane per agent (label column left, track column right, laid out on a CSS grid so both share row geometry with zero manual sync). Idle lanes sit at --muted (dim dot, dim label, thin 1px hairline); the lane whose agent currently holds an open turn brightens its dot and label to --foreground and gets a breathing pulse, with a live font-mono tabular-nums elapsed readout (mm:ss) ticking beside the name. Data model: consumer owns `agents` ({id,label}[], lane order) and `turns` ({id, agentId, start, end?}[]) — a turn with no `end` is the one currently in flight; the component holds no orchestration logic of its own, only layout, a 1s wall clock (skipped entirely when a `now` prop is supplied, making the component fully controlled/deterministic), and announcement text. TIME AXIS: a rolling `windowMs` window (default 20000) with a 'now' line pinned to the right edge — this is the load-bearing design decision: timeline-agent-lanes shows the present moment, not a post-hoc trace, so turns older than the window silently age off the left exactly like a real monitor strip, and there is deliberately NO scrub/seek control — adding one would turn it back into the span-tree debugger this component exists to differ from. Past (finished) turns stay visible inside the window at reduced opacity (foreground/45) only so a handoff connector has two ends to draw between; the live turn's bar is full-opacity foreground with the pulse. CONCURRENCY: any number of turns with no `end` can coexist across different agents with no special-casing — each renders its own bar independently, which is exactly how a parallel fan-out (one planner spawning three subagents at once) reads: three lanes bright at the same x-range. HANDOFFS: a stepped (horizontal/vertical/horizontal) connector plus a Geist Mono tabular-nums duration chip is drawn from a finished turn to the next turn on a DIFFERENT agent that starts at or after it ends (400ms slack for real event-timestamp jitter) — a genuine baton pass, not two turns that happen to overlap; one finished turn can feed multiple connectors, so a fan-out reads as one source splitting into several stepped lines. Connector labels alternate above/below the rail by arrival order and a handoff greedily flips to the other band if the previous same-band label sits within 10% of track width — the answer to two handoffs landing close together in time never drawing label-on-label. LANE COUNT: readable from 2 up to 12 lanes; beyond 12 the extra agents collapse into one aggregate '+N more' row that brightens with a live count badge ('3 active') when any of them hold a turn, but never participates in handoff connectors since there's no single fixed row to draw a line to or from — a stated degrade, not a silent one. ACCESSIBILITY: role=region with an accessible name; a single aria-live=polite aria-atomic visually-hidden region always holds the CURRENT resting summary ('Planner has the turn.' / 'Search agent, Codegen agent are active concurrently.' / 'No agent currently has the turn.') recomputed every render, so the browser's own live-region diffing announces genuine turn changes and a screen reader landing mid-run still hears an accurate state rather than only a diff log. SCOPE: no interactive controls at all (no scrub, no pause, no expand) — a deliberate choice, not an omission for time; it's a passive live status board, so it renders no controls and is exempt from the tabbability rule. REDUCED MOTION: the breathing pulse and bar-position transition both drop out (motion-reduce:animate-none / transition-none equivalents) — the resting brightness contrast between active and idle lanes already carries the full signal without any motion. Pure DOM: every bar, hairline, and connector segment is a plain absolutely positioned div using independent left:%/top:px axes (never a scaled SVG viewBox, which would stretch the mono duration text). No canvas, no SVG. Zero dependencies. Props: agents, turns, label, windowMs, now (controlled clock), className.

Tags
agenttimelineorchestrationstatusaria-liveconcurrencydashboardmono