{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "agent-02",
  "title": "Agent lanes",
  "description": "A lane per agent that fills from its start on a fixed window of time as one now line advances, with hand-offs curving from lane to lane and an approval waiting on its marker.",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "https://ui.ndekocode.dev/r/agent-run.json",
    "https://ui.ndekocode.dev/r/button.json",
    "https://ui.ndekocode.dev/r/chat-motion.json",
    "https://ui.ndekocode.dev/r/demonstration.json",
    "https://ui.ndekocode.dev/r/hatch.json",
    "https://ui.ndekocode.dev/r/icons.json",
    "https://ui.ndekocode.dev/r/live-duration.json",
    "https://ui.ndekocode.dev/r/live-region.json",
    "https://ui.ndekocode.dev/r/mesh-tile.json",
    "https://ui.ndekocode.dev/r/motion-presets.json",
    "https://ui.ndekocode.dev/r/motion-word-roll.json",
    "https://ui.ndekocode.dev/r/popover.json",
    "https://ui.ndekocode.dev/r/replay-button.json",
    "https://ui.ndekocode.dev/r/run-session.json",
    "https://ui.ndekocode.dev/r/theme.json",
    "https://ui.ndekocode.dev/r/tooltip.json",
    "https://ui.ndekocode.dev/r/use-kept-height.json",
    "https://ui.ndekocode.dev/r/use-prefers-reduced-motion.json",
    "https://ui.ndekocode.dev/r/use-session.json",
    "https://ui.ndekocode.dev/r/utils.json"
  ],
  "files": [
    {
      "path": "registry/ndeko/components/agent/agent-02.tsx",
      "content": "'use client';\n\nimport { AnimatePresence, motion } from 'motion/react';\nimport { useLayoutEffect, useState } from 'react';\n\nimport { useKeptHeight } from '@/registry/ndeko/hooks/use-kept-height';\nimport { usePrefersReducedMotion } from '@/registry/ndeko/hooks/use-prefers-reduced-motion';\nimport { useSession } from '@/registry/ndeko/hooks/use-session';\nimport {\n  emptyRun,\n  pendingApprovalsOf,\n  seriesColorOf,\n  type ApprovalRequest,\n  type RunAgent,\n  type RunEvent,\n  type RunSnapshot,\n  type SeriesSlot,\n  type ToolCall,\n} from '@/registry/ndeko/lib/agent-run';\nimport {\n  pressedQuietOf,\n  spokenUnless,\n} from '@/registry/ndeko/lib/demonstration';\nimport { hatchOf } from '@/registry/ndeko/lib/hatch';\nimport { CheckIcon, HandIcon } from '@/registry/ndeko/lib/icons';\nimport { fadePresence } from '@/registry/ndeko/lib/motion-presets';\nimport {\n  applyBatchAt,\n  batch,\n  batchOf,\n  reduceBatch,\n  restingOf,\n  type RunStep,\n} from '@/registry/ndeko/lib/run-session';\nimport { cn } from '@/registry/ndeko/lib/utils';\nimport { Button } from '@/registry/ndeko/ui/button';\nimport { Swap } from '@/registry/ndeko/ui/chat-motion';\nimport {\n  ClockGate,\n  LiveDuration,\n  useLiveNow,\n} from '@/registry/ndeko/ui/live-duration';\nimport { LiveRegion } from '@/registry/ndeko/ui/live-region';\nimport {\n  Popover,\n  PopoverContent,\n  PopoverDescription,\n  PopoverTitle,\n  PopoverTrigger,\n} from '@/registry/ndeko/ui/popover';\nimport { ReplayButton } from '@/registry/ndeko/ui/replay-button';\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipTrigger,\n} from '@/registry/ndeko/ui/tooltip';\n\nimport {\n  AGENT_STATUS_WORD,\n  AgentStatusDot,\n  pulsingIdsOf,\n} from './_parts/agent-status';\nimport { AgentTile } from './_parts/agent-tile';\nimport {\n  CODER,\n  PLANNER,\n  RESEARCHER,\n  REVIEWER,\n  RUN_TITLE,\n  WRITER,\n} from './_parts/crew';\nimport { DECIDE_AFTER_MS } from './_parts/demo';\n\ntype Orientation = 'across' | 'down';\n\n/** Under this width the lanes stand side by side and time runs down. */\nconst WIDE_PX = 560;\n\nconst LANE_PX = 44;\n\nconst DOWN_HEIGHT_PX = 300;\n\nconst DEFAULT_WINDOW_MS = 24_000;\n\n/** The demonstration takes about sixteen seconds with its approval: the whole run fits, so no lane ever moves. */\nconst DEMO_WINDOW_MS = 18_000;\n\n/** Tasks that left the window a moment ago stay mounted while the lagging track carries them out. */\nconst KEEP_MS = 2500;\n\n/**\n * The now line is drawn one step ahead and travels there at a constant speed, so one transition\n * plays per step. `GROW` below lasts exactly as long.\n */\nconst GROW_STEP_MS = 1000;\n\nconst GROW =\n  'transition-transform duration-1000 ease-linear motion-reduce:transition-none';\n\nconst GLIDE =\n  'transition-transform duration-300 ease-(--ease-in-out-strong) motion-reduce:transition-none';\n\nconst HATCH_WAITING: React.CSSProperties = {\n  backgroundColor: 'color-mix(in oklab, var(--warning) 16%, transparent)',\n  backgroundImage: hatchOf('var(--warning)', 60),\n};\n\nfunction orientationOf(isWide: boolean): Orientation {\n  if (isWide) {\n    return 'across';\n  }\n  return 'down';\n}\n\n/** The lanes turn with their own width, never with the window: a narrow tile on a wide screen runs down too. */\nfunction useOrientation() {\n  const [element, setElement] = useState<HTMLDivElement | null>(null);\n  const [isWide, setIsWide] = useState(true);\n\n  useLayoutEffect(() => {\n    if (!element) {\n      return;\n    }\n    const observer = new ResizeObserver(([entry]) => {\n      const isWideEnough = entry.contentRect.width >= WIDE_PX;\n      setIsWide(isWideEnough);\n    });\n    observer.observe(element);\n    return () => observer.disconnect();\n  }, [element]);\n\n  return { setElement, orientation: orientationOf(isWide) };\n}\n\n/** Without motion nothing travels, so nothing is drawn ahead of the present. */\nfunction leadOf(isLive: boolean, shouldReduceMotion: boolean | null) {\n  if (shouldReduceMotion) {\n    return 0;\n  }\n  if (isLive) {\n    return GROW_STEP_MS;\n  }\n  return 0;\n}\n\nfunction travelOf(isLive: boolean) {\n  if (isLive) {\n    return GROW;\n  }\n  return GLIDE;\n}\n\ntype Frame = {\n  origin: number;\n  time: number;\n  windowMs: number;\n};\n\n/** Where a moment sits on the track, in percent of the window from the start of the run. */\nfunction alongOf(frame: Frame, time: number) {\n  const offset = time - frame.origin;\n  const ratio = offset / frame.windowMs;\n  return ratio * 100;\n}\n\nfunction spanOf(frame: Frame, from: number, to: number) {\n  const span = to - from;\n  const ratio = span / frame.windowMs;\n  return ratio * 100;\n}\n\nfunction livedOf(frame: Frame) {\n  const lived = frame.time - frame.origin;\n  return Math.max(0, lived);\n}\n\n/** How far the run has outgrown the window, in percent of it. While the run fits, nothing drifts. */\nfunction driftOf(frame: Frame) {\n  const lived = livedOf(frame);\n  const overflow = Math.max(0, lived - frame.windowMs);\n  const ratio = overflow / frame.windowMs;\n  return ratio * 100;\n}\n\n/** Where the now line stands in the window, in percent. It stops at the far edge once the lanes drift. */\nfunction nowShareOf(frame: Frame) {\n  const lived = livedOf(frame);\n  const shown = Math.min(lived, frame.windowMs);\n  const ratio = shown / frame.windowMs;\n  return ratio * 100;\n}\n\nfunction trackStyleOf(\n  frame: Frame,\n  orientation: Orientation\n): React.CSSProperties {\n  const drift = driftOf(frame);\n  if (orientation === 'down') {\n    return { transform: `translateY(${-drift}%)` };\n  }\n  return { transform: `translateX(${-drift}%)` };\n}\n\nfunction coverStyleOf(\n  frame: Frame,\n  orientation: Orientation\n): React.CSSProperties {\n  const share = nowShareOf(frame);\n  if (orientation === 'down') {\n    return { transform: `translateY(${share}%)` };\n  }\n  return { transform: `translateX(${share}%)` };\n}\n\nfunction placeOf(\n  orientation: Orientation,\n  start: number,\n  size: number\n): React.CSSProperties {\n  if (orientation === 'down') {\n    return { top: `${start}%`, height: `${size}%` };\n  }\n  return { left: `${start}%`, width: `${size}%` };\n}\n\n/**\n * What is still open (a running task, a request that waits) is drawn to the end of the window after\n * the one being filled. The cover ahead of the now line hides the part not lived yet, so its growing\n * end is the now line itself, and its own box changes once per window instead of once per tick.\n */\nfunction openEndOf(frame: Frame) {\n  const lived = livedOf(frame);\n  const windowsLived = Math.floor(lived / frame.windowMs);\n  const windowsDrawn = windowsLived + 2;\n  return frame.origin + windowsDrawn * frame.windowMs;\n}\n\nfunction endOf(task: ToolCall, frame: Frame) {\n  if (task.endedAt !== undefined) {\n    return task.endedAt;\n  }\n  return openEndOf(frame);\n}\n\nfunction isInWindow(task: ToolCall, frame: Frame) {\n  const end = endOf(task, frame);\n  const windowStart = frame.time - frame.windowMs - KEEP_MS;\n  return end >= windowStart;\n}\n\nfunction tintOf(task: ToolCall) {\n  if (task.status === 'running') {\n    return 20;\n  }\n  return 13;\n}\n\nfunction startEdgeOf(orientation: Orientation, color: string) {\n  if (orientation === 'down') {\n    return `inset 0 2px 0 ${color}`;\n  }\n  return `inset 2px 0 0 ${color}`;\n}\n\nfunction barStyleOf(\n  task: ToolCall,\n  series: SeriesSlot,\n  orientation: Orientation\n): React.CSSProperties {\n  const color = seriesColorOf(series);\n  const tint = tintOf(task);\n  const startEdge = startEdgeOf(orientation, color);\n  return {\n    backgroundColor: `color-mix(in oklab, ${color} ${tint}%, transparent)`,\n    boxShadow: `${startEdge}, inset 0 0 0 var(--hairline) color-mix(in oklab, ${color} 40%, transparent)`,\n  };\n}\n\nfunction laneStyleOf(\n  orientation: Orientation,\n  index: number,\n  count: number\n): React.CSSProperties {\n  if (orientation === 'down') {\n    const share = 100 / count;\n    const left = index * share;\n    return { left: `${left}%`, width: `${share}%`, top: 0, bottom: 0 };\n  }\n  const top = index * LANE_PX;\n  return { top, height: LANE_PX, left: 0, right: 0 };\n}\n\nconst BAR_INSET: Record<Orientation, string> = {\n  across: 'inset-y-2',\n  down: 'inset-x-1.5',\n};\n\nfunction TaskBar({\n  task,\n  agent,\n  frame,\n  orientation,\n  isLive,\n}: {\n  task: ToolCall;\n  agent: RunAgent;\n  frame: Frame;\n  orientation: Orientation;\n  isLive: boolean;\n}) {\n  const start = task.startedAt ?? frame.time;\n  const end = endOf(task, frame);\n  const along = alongOf(frame, start);\n  const size = spanOf(frame, start, end);\n  const place = placeOf(orientation, along, size);\n  const isRunning = task.status === 'running';\n  const isTicking = isRunning && isLive;\n  const label = `${task.label}, ${agent.name}`;\n  const barStyle = barStyleOf(task, agent.series, orientation);\n\n  return (\n    <Tooltip>\n      <TooltipTrigger\n        render={\n          <button\n            type='button'\n            aria-label={label}\n            className={cn(\n              'focus-visible:ring-ring/40 absolute flex min-w-1 cursor-default items-center overflow-hidden rounded-md text-start outline-none focus-visible:z-10 focus-visible:ring-[3px]',\n              BAR_INSET[orientation]\n            )}\n            style={{ ...place, ...barStyle }}\n          />\n        }\n      >\n        {orientation === 'across' && (\n          <span className='text-foreground/80 truncate px-2 text-[11px] leading-none font-medium'>\n            {task.label}\n          </span>\n        )}\n      </TooltipTrigger>\n      <TooltipContent align='start'>\n        <span className='block'>{task.label}</span>\n        <span className='text-muted-foreground block font-normal tabular-nums'>\n          <LiveDuration item={task} isRunning={isTicking} />\n          {isRunning && ', still running'}\n        </span>\n      </TooltipContent>\n    </Tooltip>\n  );\n}\n\nfunction WaitingHatch({\n  approval,\n  frame,\n  orientation,\n}: {\n  approval: ApprovalRequest;\n  frame: Frame;\n  orientation: Orientation;\n}) {\n  const start = approval.requestedAt ?? frame.time;\n  const openEnd = openEndOf(frame);\n  const end = approval.resolvedAt ?? openEnd;\n  const along = alongOf(frame, start);\n  const size = spanOf(frame, start, end);\n  const place = placeOf(orientation, along, size);\n\n  return (\n    <span\n      aria-hidden='true'\n      className={cn(\n        'pointer-events-none absolute rounded-md',\n        BAR_INSET[orientation]\n      )}\n      style={{ ...place, ...HATCH_WAITING }}\n    />\n  );\n}\n\nfunction markerPlaceOf(\n  orientation: Orientation,\n  along: number\n): React.CSSProperties {\n  if (orientation === 'down') {\n    return { top: `${along}%`, left: 2, transform: 'translateY(-50%)' };\n  }\n  return { left: `${along}%`, top: 2, transform: 'translateX(-50%)' };\n}\n\nfunction ApprovalMarker({\n  approval,\n  agent,\n  frame,\n  orientation,\n  isPressed,\n  onApprove,\n}: {\n  approval: ApprovalRequest;\n  agent: RunAgent;\n  frame: Frame;\n  orientation: Orientation;\n  isPressed: boolean;\n  onApprove?: (approvalId: string) => void;\n}) {\n  const fade = fadePresence();\n  const requestedAt = approval.requestedAt ?? frame.time;\n  const along = alongOf(frame, requestedAt);\n  const isPending = approval.status === 'pending';\n  const label = markerLabelOf(approval, agent);\n\n  return (\n    <Popover>\n      <PopoverTrigger\n        render={\n          <button\n            type='button'\n            aria-label={label}\n            className={cn(\n              'surface-raised focus-visible:ring-ring absolute z-20 flex size-5 cursor-pointer items-center justify-center rounded-md transition-[scale,background-color] duration-150 ease-(--ease-out-strong) outline-none focus-visible:ring-2 focus-visible:ring-offset-1 active:scale-[0.97] [&_svg]:size-3',\n              isPending && 'text-warning-foreground',\n              !isPending && 'text-success-foreground',\n              pressedQuietOf(isPressed)\n            )}\n            style={markerPlaceOf(orientation, along)}\n          />\n        }\n      >\n        <Swap swapKey={approval.status}>\n          {isPending && <HandIcon />}\n          {!isPending && <CheckIcon />}\n        </Swap>\n      </PopoverTrigger>\n      <PopoverContent side='top' className='w-64 p-3'>\n        <div className='relative'>\n          <AnimatePresence mode='popLayout' initial={false}>\n            <motion.div\n              key={approval.status}\n              className='flex flex-col gap-2.5'\n              {...fade}\n            >\n              <PopoverTitle className='flex items-center gap-2 text-[13px] leading-5'>\n                <AgentTile name={agent.name} series={agent.series} size='sm' />\n                {agent.name} asks you first\n              </PopoverTitle>\n              <PopoverDescription className='text-foreground text-[13px] leading-5'>\n                {approval.title}\n              </PopoverDescription>\n              {approval.detail && (\n                <p className='text-muted-foreground text-xs leading-5'>\n                  {approval.detail}\n                </p>\n              )}\n              {isPending && (\n                <Button\n                  type='button'\n                  size='sm'\n                  onClick={() => onApprove?.(approval.id)}\n                  className='self-start'\n                >\n                  <CheckIcon />\n                  Approve\n                </Button>\n              )}\n              {!isPending && (\n                <p className='text-muted-foreground flex items-center gap-1.5 text-xs'>\n                  <CheckIcon className='text-success-foreground size-3.5' />\n                  Approved by you\n                </p>\n              )}\n            </motion.div>\n          </AnimatePresence>\n        </div>\n      </PopoverContent>\n    </Popover>\n  );\n}\n\nfunction markerLabelOf(approval: ApprovalRequest, agent: RunAgent) {\n  if (approval.status === 'pending') {\n    return `${agent.name} waits for your approval: ${approval.title}. Open to approve`;\n  }\n  return `Approved by you: ${approval.title}`;\n}\n\ntype Connector = {\n  key: string;\n  from: { lane: number; along: number };\n  to: { lane: number; along: number };\n  series: SeriesSlot;\n};\n\nfunction connectorsOf(\n  tasks: ToolCall[],\n  agents: RunAgent[],\n  frame: Frame\n): Connector[] {\n  const connectors: Connector[] = [];\n  for (const task of tasks) {\n    const parent = tasks.find((candidate) => candidate.id === task.parentId);\n    const toLane = agents.findIndex((agent) => agent.id === task.agentId);\n    const fromLane = agents.findIndex((agent) => agent.id === parent?.agentId);\n    const fromAgent = agents[fromLane];\n    const hasEnds = toLane >= 0 && fromLane >= 0;\n    const isHandoff = hasEnds && fromLane !== toLane;\n    const hasParent = parent !== undefined;\n    const hasFromAgent = fromAgent !== undefined;\n    if (!hasParent || !isHandoff || !hasFromAgent) {\n      continue;\n    }\n    const parentEnd = parent.endedAt ?? parent.startedAt;\n    const handedAt = parentEnd ?? frame.time;\n    const takenAt = task.startedAt ?? frame.time;\n    connectors.push({\n      key: task.id,\n      from: { lane: fromLane, along: alongOf(frame, handedAt) },\n      to: { lane: toLane, along: alongOf(frame, takenAt) },\n      series: fromAgent.series,\n    });\n  }\n  return connectors;\n}\n\n/** SVG units: 10 per percent of the window along time, 100 per lane across. */\nfunction pathOf(connector: Connector, orientation: Orientation) {\n  const fromAlong = connector.from.along * 10;\n  const toAlong = connector.to.along * 10;\n  const fromAcross = connector.from.lane * 100 + 50;\n  const toAcross = connector.to.lane * 100 + 50;\n  const distance = toAlong - fromAlong;\n  const halfway = distance / 2;\n  const bend = Math.max(60, halfway);\n  const fromBend = fromAlong + bend;\n  const toBend = toAlong - bend;\n  if (orientation === 'down') {\n    return `M ${fromAcross} ${fromAlong} C ${fromAcross} ${fromBend}, ${toAcross} ${toBend}, ${toAcross} ${toAlong}`;\n  }\n  return `M ${fromAlong} ${fromAcross} C ${fromBend} ${fromAcross}, ${toBend} ${toAcross}, ${toAlong} ${toAcross}`;\n}\n\nfunction viewBoxOf(orientation: Orientation, laneCount: number) {\n  const across = laneCount * 100;\n  if (orientation === 'down') {\n    return `0 0 ${across} 1000`;\n  }\n  return `0 0 1000 ${across}`;\n}\n\nfunction Connectors({\n  connectors,\n  laneCount,\n  orientation,\n}: {\n  connectors: Connector[];\n  laneCount: number;\n  orientation: Orientation;\n}) {\n  return (\n    <svg\n      aria-hidden='true'\n      viewBox={viewBoxOf(orientation, laneCount)}\n      preserveAspectRatio='none'\n      className='pointer-events-none absolute inset-0 size-full overflow-visible'\n    >\n      {connectors.map((connector) => (\n        <path\n          key={connector.key}\n          d={pathOf(connector, orientation)}\n          fill='none'\n          strokeWidth={1.25}\n          strokeOpacity={0.55}\n          vectorEffect='non-scaling-stroke'\n          style={{ stroke: seriesColorOf(connector.series) }}\n        />\n      ))}\n    </svg>\n  );\n}\n\nfunction LaneLabel({\n  agent,\n  orientation,\n  isStill,\n}: {\n  agent: RunAgent;\n  orientation: Orientation;\n  isStill: boolean;\n}) {\n  if (orientation === 'down') {\n    return (\n      <span className='flex min-w-0 flex-col items-center gap-1 px-0.5'>\n        <span className='relative'>\n          <AgentTile name={agent.name} series={agent.series} size='sm' />\n          <AgentStatusDot\n            status={agent.status}\n            size='sm'\n            isStill={isStill}\n            className='ring-card absolute -end-0.5 -bottom-0.5 rounded-full ring-2'\n          />\n        </span>\n        <span className='text-muted-foreground w-full truncate text-center text-[10px] leading-3'>\n          {agent.name}\n        </span>\n      </span>\n    );\n  }\n  return (\n    <span\n      className='flex min-w-0 items-center gap-2'\n      style={{ height: LANE_PX }}\n    >\n      <AgentTile name={agent.name} series={agent.series} size='sm' />\n      <span className='min-w-0 flex-1 truncate text-xs'>{agent.name}</span>\n      <AgentStatusDot status={agent.status} size='sm' isStill={isStill} />\n    </span>\n  );\n}\n\nfunction secondsOf(windowMs: number, share: number) {\n  const milliseconds = windowMs * share;\n  return Math.round(milliseconds / 1000);\n}\n\n/** While the run fits in the window the marks count from its start. */\nfunction sinceStartWordOf(windowMs: number, share: number) {\n  const seconds = secondsOf(windowMs, share);\n  return `${seconds}s`;\n}\n\n/** Once the lanes drift, the far edge is now and the marks count back from it. */\nfunction agoWordOf(windowMs: number, share: number) {\n  const seconds = secondsOf(windowMs, share);\n  return `${seconds}s ago`;\n}\n\nfunction startWordOf(windowMs: number, isDrifting: boolean) {\n  if (isDrifting) {\n    return agoWordOf(windowMs, 1);\n  }\n  return sinceStartWordOf(windowMs, 0);\n}\n\nconst NOW_LINE: Record<Orientation, string> = {\n  across: 'inset-y-0 left-0 w-px',\n  down: 'inset-x-0 top-0 h-px',\n};\n\n/**\n * What lies ahead of now, drawn as the surface of the lanes: it hides the part of an open task that\n * has not been lived yet and its edge is the now line. Moving this one cover fills every lane, so\n * no bar is resized while it grows.\n */\nfunction NowCover({\n  frame,\n  orientation,\n  isLive,\n}: {\n  frame: Frame;\n  orientation: Orientation;\n  isLive: boolean;\n}) {\n  return (\n    <span\n      aria-hidden='true'\n      className={cn('bg-card absolute inset-0 z-10', travelOf(isLive))}\n      style={coverStyleOf(frame, orientation)}\n    >\n      <span className='bg-foreground/[0.025] dark:bg-foreground/[0.04] absolute inset-0' />\n      <span\n        className={cn('bg-foreground/45 absolute', NOW_LINE[orientation])}\n      />\n      {isLive && (\n        <span className='text-foreground absolute top-1 left-1.5 text-[10px] leading-3 font-medium'>\n          Now\n        </span>\n      )}\n    </span>\n  );\n}\n\nfunction viewportStyleOf(\n  orientation: Orientation,\n  laneCount: number\n): React.CSSProperties {\n  if (orientation === 'down') {\n    return { height: DOWN_HEIGHT_PX };\n  }\n  const height = laneCount * LANE_PX;\n  return { height };\n}\n\nconst NO_APPROVALS: ApprovalRequest[] = [];\n\nexport type AgentLanesProps = {\n  /** One lane per agent, in this order. */\n  agents: RunAgent[];\n  /** Tasks sit in the lane of their `agentId`; a `parentId` in another lane draws a hand-off. */\n  tasks: ToolCall[];\n  /** Requests sit on the lane of their `agentId` at `requestedAt`. */\n  approvals?: ApprovalRequest[];\n  startedAt?: number;\n  endedAt?: number;\n  isLive: boolean;\n  /**\n   * How much time the lanes show. While the run fits in it the lanes fill from the start and no task\n   * moves; once the run outgrows it, the far edge is now and time drifts back.\n   */\n  windowMs?: number;\n  onApprove?: (approvalId: string) => void;\n  /** The request whose marker a demonstration holds down. */\n  pressedApprovalId?: string | null;\n  className?: string;\n};\n\nexport function AgentLanes({\n  agents,\n  tasks,\n  approvals = NO_APPROVALS,\n  startedAt,\n  endedAt,\n  isLive,\n  windowMs = DEFAULT_WINDOW_MS,\n  onApprove,\n  pressedApprovalId = null,\n  className,\n}: AgentLanesProps) {\n  const { setElement, orientation } = useOrientation();\n  const shouldReduceMotion = usePrefersReducedMotion();\n  const now = useLiveNow(GROW_STEP_MS, isLive);\n  const lead = leadOf(isLive, shouldReduceMotion);\n  const reach = now + lead;\n  const time = endedAt ?? reach;\n  const origin = startedAt ?? time;\n  const frame: Frame = { origin, time, windowMs };\n  const laneCount = agents.length;\n  const visibleTasks = tasks.filter((task) => isInWindow(task, frame));\n  const connectors = connectorsOf(visibleTasks, agents, frame);\n  const pulsingIds = pulsingIdsOf(agents);\n  const isAcross = orientation === 'across';\n  const isDown = orientation === 'down';\n  const drift = driftOf(frame);\n  const isDrifting = drift > 0;\n  const showsCover = !isDrifting;\n  const showsNowAtFoot = isDown && isDrifting;\n  const showsTicksFromStart = isAcross && showsCover;\n  const showsTicksBack = isAcross && isDrifting;\n  const trackKey = `${startedAt ?? 'idle'}-${orientation}`;\n  const viewportStyle = viewportStyleOf(orientation, laneCount);\n\n  return (\n    <div ref={setElement} className={cn('flex flex-col gap-1.5', className)}>\n      {isDown && (\n        <div\n          aria-hidden='true'\n          className='grid'\n          style={{\n            gridTemplateColumns: `repeat(${laneCount}, minmax(0, 1fr))`,\n          }}\n        >\n          {agents.map((agent) => (\n            <LaneLabel\n              key={agent.id}\n              agent={agent}\n              orientation={orientation}\n              isStill={!pulsingIds.has(agent.id)}\n            />\n          ))}\n        </div>\n      )}\n      <div className='flex'>\n        {isAcross && (\n          <div aria-hidden='true' className='flex w-28 shrink-0 flex-col pe-2'>\n            {agents.map((agent) => (\n              <LaneLabel\n                key={agent.id}\n                agent={agent}\n                orientation={orientation}\n                isStill={!pulsingIds.has(agent.id)}\n              />\n            ))}\n          </div>\n        )}\n        <div\n          className='bg-foreground/[0.025] dark:bg-foreground/[0.04] relative min-w-0 flex-1 overflow-hidden rounded-lg'\n          style={viewportStyle}\n        >\n          <div\n            key={trackKey}\n            className={cn('absolute inset-0', travelOf(isLive))}\n            style={trackStyleOf(frame, orientation)}\n          >\n            <Connectors\n              connectors={connectors}\n              laneCount={laneCount}\n              orientation={orientation}\n            />\n            <ul aria-label='Lanes' className='absolute inset-0'>\n              {agents.map((agent, index) => {\n                const laneTasks = visibleTasks.filter(\n                  (task) => task.agentId === agent.id\n                );\n                const laneApprovals = approvals.filter(\n                  (approval) => approval.agentId === agent.id\n                );\n                const statusWord = AGENT_STATUS_WORD[agent.status];\n\n                return (\n                  <li\n                    key={agent.id}\n                    className='absolute'\n                    style={laneStyleOf(orientation, index, laneCount)}\n                  >\n                    <span className='sr-only'>\n                      {agent.name}, {statusWord}\n                    </span>\n                    <ul aria-label={`Tasks of ${agent.name}`}>\n                      {laneTasks.map((task) => (\n                        <li key={task.id}>\n                          <TaskBar\n                            task={task}\n                            agent={agent}\n                            frame={frame}\n                            orientation={orientation}\n                            isLive={isLive}\n                          />\n                        </li>\n                      ))}\n                    </ul>\n                    {laneApprovals.map((approval) => (\n                      <WaitingHatch\n                        key={`${approval.id}-hatch`}\n                        approval={approval}\n                        frame={frame}\n                        orientation={orientation}\n                      />\n                    ))}\n                    {laneApprovals.map((approval) => (\n                      <ApprovalMarker\n                        key={approval.id}\n                        approval={approval}\n                        agent={agent}\n                        frame={frame}\n                        orientation={orientation}\n                        isPressed={pressedApprovalId === approval.id}\n                        onApprove={onApprove}\n                      />\n                    ))}\n                  </li>\n                );\n              })}\n            </ul>\n            {showsCover && (\n              <NowCover\n                frame={frame}\n                orientation={orientation}\n                isLive={isLive}\n              />\n            )}\n          </div>\n          {isDrifting && (\n            <span\n              aria-hidden='true'\n              className={cn(\n                'bg-foreground/45 absolute',\n                isAcross && 'inset-y-0 right-0 w-px',\n                isDown && 'inset-x-0 bottom-0 h-px'\n              )}\n            />\n          )}\n          {isDown && (\n            <span\n              aria-hidden='true'\n              className='text-muted-foreground pointer-events-none absolute end-1.5 top-1 z-20 text-[10px] leading-3 tabular-nums'\n            >\n              {startWordOf(windowMs, isDrifting)}\n            </span>\n          )}\n          {showsNowAtFoot && (\n            <span\n              aria-hidden='true'\n              className='text-foreground pointer-events-none absolute end-1.5 bottom-1 text-[10px] leading-3 font-medium'\n            >\n              Now\n            </span>\n          )}\n        </div>\n      </div>\n      {showsTicksFromStart && (\n        <div\n          aria-hidden='true'\n          className='text-muted-foreground ms-28 flex justify-between text-[11px] tabular-nums'\n        >\n          <span>{sinceStartWordOf(windowMs, 0)}</span>\n          <span>{sinceStartWordOf(windowMs, 1 / 3)}</span>\n          <span>{sinceStartWordOf(windowMs, 2 / 3)}</span>\n          <span>{sinceStartWordOf(windowMs, 1)}</span>\n        </div>\n      )}\n      {showsTicksBack && (\n        <div\n          aria-hidden='true'\n          className='text-muted-foreground ms-28 flex justify-between text-[11px] tabular-nums'\n        >\n          <span>{agoWordOf(windowMs, 1)}</span>\n          <span>{agoWordOf(windowMs, 2 / 3)}</span>\n          <span>{agoWordOf(windowMs, 1 / 3)}</span>\n          <span className='text-foreground font-medium'>Now</span>\n        </div>\n      )}\n    </div>\n  );\n}\n\nconst LANES: RunAgent[] = [PLANNER, RESEARCHER, CODER, WRITER, REVIEWER];\n\nconst SEND_APPROVAL: ApprovalRequest = {\n  id: 'send',\n  title: 'Send the credit request to accounts@brightmoor-supply.example',\n  risk: 'medium',\n  status: 'pending',\n  agentId: WRITER.id,\n  detail: '186 words, the comparison attached. It asks for $1,840.20 back.',\n};\n\nconst INITIAL_RUN = emptyRun({\n  id: 'brightmoor-lanes',\n  title: RUN_TITLE,\n  agents: LANES,\n});\n\nfunction begin(\n  id: string,\n  agentId: string,\n  label: string,\n  parentId?: string\n): RunEvent {\n  return {\n    type: 'call.started',\n    call: { id, tool: 'task', label, status: 'running', agentId, parentId },\n  };\n}\n\nfunction finish(id: string): RunEvent {\n  return { type: 'call.updated', id, patch: { status: 'done' } };\n}\n\nfunction agentTo(id: string, status: RunAgent['status']): RunEvent {\n  return { type: 'agent.updated', id, patch: { status } };\n}\n\nfunction approvedBatchOf(approvalId: string) {\n  return batchOf(\n    { type: 'approval.resolved', id: approvalId, decision: 'approved' },\n    agentTo(WRITER.id, 'running')\n  );\n}\n\nfunction hasDecided(run: RunSnapshot) {\n  const pending = pendingApprovalsOf(run);\n  return pending.length === 0;\n}\n\nconst SCRIPT: RunStep[] = [\n  batch(\n    500,\n    { type: 'run.started' },\n    agentTo(PLANNER.id, 'running'),\n    begin('split', PLANNER.id, 'Split the review into tasks')\n  ),\n  batch(1700, finish('split')),\n  batch(\n    250,\n    begin('collect', RESEARCHER.id, 'Collect 14 price lists', 'split'),\n    agentTo(RESEARCHER.id, 'running')\n  ),\n  batch(350, begin('track', PLANNER.id, 'Track the agents')),\n  batch(3200, finish('collect')),\n  batch(\n    250,\n    begin('compare', CODER.id, 'Compare invoices with prices', 'collect'),\n    agentTo(CODER.id, 'running')\n  ),\n  batch(450, begin('terms', RESEARCHER.id, 'Read the contract terms')),\n  batch(1700, finish('terms'), agentTo(RESEARCHER.id, 'done')),\n  batch(1100, finish('compare'), agentTo(CODER.id, 'done')),\n  batch(\n    250,\n    begin('draft', WRITER.id, 'Draft the credit request', 'compare'),\n    agentTo(WRITER.id, 'running')\n  ),\n  batch(1900, finish('draft')),\n  batch(\n    250,\n    begin('check', REVIEWER.id, 'Check the figures', 'draft'),\n    agentTo(REVIEWER.id, 'running')\n  ),\n  batch(\n    250,\n    begin('send', WRITER.id, 'Send the email'),\n    agentTo(WRITER.id, 'waiting'),\n    { type: 'approval.requested', approval: SEND_APPROVAL }\n  ),\n  {\n    after: DECIDE_AFTER_MS,\n    by: 'person',\n    press: SEND_APPROVAL.id,\n    action: approvedBatchOf(SEND_APPROVAL.id),\n    isAnswered: hasDecided,\n  },\n  batch(900, finish('send'), agentTo(WRITER.id, 'done')),\n  batch(700, finish('check'), agentTo(REVIEWER.id, 'done')),\n  batch(500, finish('track'), agentTo(PLANNER.id, 'done'), {\n    type: 'run.finished',\n    status: 'done',\n  }),\n];\n\nconst SESSION = {\n  reducer: reduceBatch,\n  initial: INITIAL_RUN,\n  resting: restingOf(INITIAL_RUN, SCRIPT, applyBatchAt),\n  script: SCRIPT,\n  loopAfter: 3000,\n};\n\nfunction isLiveRun(run: RunSnapshot) {\n  const isRunning = run.status === 'running';\n  const isWaiting = run.status === 'waiting';\n  return isRunning || isWaiting;\n}\n\nfunction announcementOf(run: RunSnapshot) {\n  const [pending] = pendingApprovalsOf(run);\n  if (pending) {\n    return `${WRITER.name} waits for your approval: ${pending.title}`;\n  }\n  if (run.status === 'done') {\n    return 'All lanes are done';\n  }\n  const running = run.calls.filter((call) => call.status === 'running');\n  const latest = running.at(-1);\n  if (!latest) {\n    return '';\n  }\n  const agent = LANES.find((candidate) => candidate.id === latest.agentId);\n  return `${agent?.name ?? 'An agent'} started: ${latest.label}`;\n}\n\nexport default function Agent02() {\n  const { ref, isInView, isAutoplaying, state, act, pressed, restart } =\n    useSession(SESSION);\n  const isEnded = state.endedAt !== undefined;\n  const hasStarted = state.startedAt !== undefined;\n  const canReplay = isEnded && !isAutoplaying;\n  const isLive = isLiveRun(state);\n  const announcement = announcementOf(state);\n\n  function approve(approvalId: string) {\n    act(approvedBatchOf(approvalId));\n  }\n\n  useKeptHeight(ref);\n\n  return (\n    <div\n      ref={ref}\n      className='flex min-h-[21.5rem] w-full max-w-[44rem] flex-col gap-2'\n    >\n      <ClockGate isActive={isInView}>\n        <section\n          aria-label='Agent lanes'\n          className='bg-card hairline-ring ring-border flex flex-col gap-3 rounded-xl p-4 shadow-[0_1px_2px_rgb(0_0_0/0.04)]'\n        >\n          <header className='flex items-baseline justify-between gap-3'>\n            <h3 className='truncate text-sm font-medium'>{state.title}</h3>\n            <span className='text-muted-foreground min-w-8 shrink-0 text-end text-xs'>\n              {hasStarted && <LiveDuration item={state} isRunning={isLive} />}\n            </span>\n          </header>\n          <AgentLanes\n            agents={state.agents}\n            tasks={state.calls}\n            approvals={state.approvals}\n            startedAt={state.startedAt}\n            endedAt={state.endedAt}\n            isLive={isLive}\n            windowMs={DEMO_WINDOW_MS}\n            onApprove={approve}\n            pressedApprovalId={pressed}\n          />\n        </section>\n      </ClockGate>\n      <div className='flex h-7 items-center justify-end'>\n        <ReplayButton isShown={canReplay} onReplay={restart} />\n      </div>\n      <LiveRegion message={spokenUnless(isAutoplaying, announcement)} />\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/agent/agent-02.tsx"
    },
    {
      "path": "registry/ndeko/components/agent/_parts/agent-status.tsx",
      "content": "'use client';\n\nimport { WordRoll } from '@/components/core/word-roll';\nimport type {\n  AgentStatus as AgentState,\n  RunAgent,\n} from '@/registry/ndeko/lib/agent-run';\nimport {\n  CircleCheckIcon,\n  CircleXIcon,\n  HandIcon,\n} from '@/registry/ndeko/lib/icons';\nimport { cn } from '@/registry/ndeko/lib/utils';\nimport { Swap } from '@/registry/ndeko/ui/chat-motion';\n\nexport const AGENT_STATUS_WORD: Record<AgentState, string> = {\n  idle: 'Idle',\n  running: 'Running',\n  waiting: 'Waiting on you',\n  failed: 'Failed',\n  done: 'Done',\n};\n\nexport const AGENT_STATES: AgentState[] = [\n  'idle',\n  'running',\n  'waiting',\n  'failed',\n  'done',\n];\n\nconst STATUS_WORDS = AGENT_STATES.map((state) => AGENT_STATUS_WORD[state]);\n\nconst DOT_TONE: Record<AgentState, string> = {\n  idle: 'bg-muted-foreground/45',\n  running: 'bg-info',\n  waiting: 'bg-warning',\n  failed: 'bg-destructive',\n  done: 'bg-success',\n};\n\nconst LABEL_TONE: Record<AgentState, string> = {\n  idle: 'text-muted-foreground',\n  running: 'text-foreground',\n  waiting: 'text-warning-foreground',\n  failed: 'text-destructive-ink',\n  done: 'text-muted-foreground',\n};\n\nconst DOT_SIZE = {\n  sm: 'size-1.5',\n  md: 'size-2',\n  lg: 'size-2.5',\n} as const;\n\nconst MARK_SIZE = {\n  sm: 'size-3',\n  md: 'size-3.5',\n  lg: 'size-4',\n} as const;\n\nexport type AgentStatusSize = keyof typeof DOT_SIZE;\n\nconst RING_TIMING: React.CSSProperties = { animationDuration: '2s' };\n\n/** A piece lets this many dots pulse at once. Past it, a running agent is told by its colour and its word. */\nexport const MAX_PULSING = 3;\n\n/** The running agents whose dot may pulse: the first ones, in the order given. */\nexport function pulsingIdsOf(\n  agents: Pick<RunAgent, 'id' | 'status'>[],\n  limit = MAX_PULSING\n) {\n  const running = agents.filter((agent) => agent.status === 'running');\n  const pulsing = running.slice(0, limit);\n  const ids = pulsing.map((agent) => agent.id);\n  return new Set(ids);\n}\n\nfunction reservedLabelsOf(shouldReserve: boolean) {\n  if (shouldReserve) {\n    return STATUS_WORDS;\n  }\n  return undefined;\n}\n\ntype AgentStatusDotProps = {\n  status: AgentState;\n  size?: AgentStatusSize;\n  /** Without a visible word next to it, the dot says its state to screen readers. */\n  isLabelled?: boolean;\n  /** Holds a running dot still: where three others already pulse, or where nothing is really running. */\n  isStill?: boolean;\n  className?: string;\n};\n\n/**\n * A running dot sends a soft ring out: one CSS keyframe on transform and opacity, which the theme\n * stops under reduced motion. Every other state holds still and is told by its colour.\n */\nexport function AgentStatusDot({\n  status,\n  size = 'md',\n  isLabelled = false,\n  isStill = false,\n  className,\n}: AgentStatusDotProps) {\n  const isRunning = status === 'running';\n  const isPulsing = isRunning && !isStill;\n  const word = AGENT_STATUS_WORD[status];\n\n  return (\n    <span\n      data-status={status}\n      className={cn('relative inline-flex shrink-0', DOT_SIZE[size], className)}\n    >\n      {isPulsing && (\n        <span\n          aria-hidden='true'\n          style={RING_TIMING}\n          className='bg-info/40 absolute inset-0 animate-ping rounded-full motion-reduce:hidden'\n        />\n      )}\n      <span\n        aria-hidden='true'\n        className={cn(\n          'relative size-full rounded-full transition-colors duration-200',\n          DOT_TONE[status]\n        )}\n      />\n      {isLabelled && <span className='sr-only'>{word}</span>}\n    </span>\n  );\n}\n\ntype StatusGlyphProps = {\n  status: AgentState;\n  size: AgentStatusSize;\n  isStill: boolean;\n};\n\nfunction StatusGlyph({ status, size, isStill }: StatusGlyphProps) {\n  const box = MARK_SIZE[size];\n  if (status === 'waiting') {\n    return <HandIcon className={cn('text-warning-foreground', box)} />;\n  }\n  if (status === 'failed') {\n    return <CircleXIcon className={cn('text-destructive-ink', box)} />;\n  }\n  if (status === 'done') {\n    return <CircleCheckIcon className={cn('text-success-foreground', box)} />;\n  }\n  return (\n    <span className={cn('flex items-center justify-center', box)}>\n      <AgentStatusDot status={status} size={size} isStill={isStill} />\n    </span>\n  );\n}\n\ntype AgentStatusMarkProps = {\n  status: AgentState;\n  size?: AgentStatusSize;\n  isStill?: boolean;\n  className?: string;\n};\n\n/**\n * The mark of a state, drawn so it reads without its colour: a dot while the agent is idle or runs,\n * a hand while it waits, a cross when it failed, a check when it ended. It swaps when the state changes.\n */\nexport function AgentStatusMark({\n  status,\n  size = 'md',\n  isStill = false,\n  className,\n}: AgentStatusMarkProps) {\n  const box = MARK_SIZE[size];\n\n  return (\n    <span\n      data-status={status}\n      aria-hidden='true'\n      className={cn('inline-flex shrink-0', box, className)}\n    >\n      <Swap swapKey={status} className={box}>\n        <StatusGlyph status={status} size={size} isStill={isStill} />\n      </Swap>\n    </span>\n  );\n}\n\nexport type AgentStatusProps = {\n  status: AgentState;\n  /** Leave the word out where space is short: a dot then carries it for screen readers. */\n  showLabel?: boolean;\n  /** Keeps the width of the longest word, so the row beside it never shifts. */\n  reserveWidth?: boolean;\n  size?: AgentStatusSize;\n  /** Holds a running mark still: a piece lets three pulse at most. */\n  isStill?: boolean;\n  className?: string;\n};\n\nexport function AgentStatus({\n  status,\n  showLabel = true,\n  reserveWidth = false,\n  size = 'md',\n  isStill = false,\n  className,\n}: AgentStatusProps) {\n  const word = AGENT_STATUS_WORD[status];\n  const labels = reservedLabelsOf(reserveWidth);\n\n  if (!showLabel) {\n    return (\n      <AgentStatusDot\n        status={status}\n        size={size}\n        isLabelled\n        isStill={isStill}\n        className={className}\n      />\n    );\n  }\n\n  return (\n    <span\n      className={cn(\n        'inline-flex items-center gap-1 text-xs transition-colors duration-200',\n        LABEL_TONE[status],\n        className\n      )}\n    >\n      <AgentStatusMark status={status} size={size} isStill={isStill} />\n      <WordRoll text={word} labels={labels} />\n    </span>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/agent/_parts/agent-status.tsx"
    },
    {
      "path": "registry/ndeko/components/agent/_parts/agent-tile.tsx",
      "content": "import { seriesColorOf, type SeriesSlot } from '@/registry/ndeko/lib/agent-run';\nimport { cn } from '@/registry/ndeko/lib/utils';\nimport { MeshTile } from '@/registry/ndeko/ui/mesh-tile';\n\nconst SIZE_CLASS = {\n  sm: 'size-5 rounded-[5px]',\n  md: 'size-6 rounded-md',\n  lg: 'size-8 rounded-lg',\n} as const;\n\nexport type AgentTileSize = keyof typeof SIZE_CLASS;\n\ntype AgentTileProps = {\n  name: string;\n  series: SeriesSlot;\n  size?: AgentTileSize;\n  className?: string;\n};\n\n/** The picture of an agent: a gradient drawn from its name, in its series colour. Its name always sits beside it. */\nexport function AgentTile({\n  name,\n  series,\n  size = 'md',\n  className,\n}: AgentTileProps) {\n  const color = seriesColorOf(series);\n  const sizeClass = SIZE_CLASS[size];\n\n  return (\n    <MeshTile seed={name} color={color} className={cn(sizeClass, className)} />\n  );\n}\n",
      "type": "registry:component",
      "target": "components/agent/_parts/agent-tile.tsx"
    },
    {
      "path": "registry/ndeko/components/agent/_parts/crew.ts",
      "content": "import type { RunAgent } from '@/registry/ndeko/lib/agent-run';\n\nexport const RUN_TITLE = 'Q3 vendor review for Brightmoor Supply';\n\nexport const PLANNER: RunAgent = {\n  id: 'planner',\n  name: 'Planner',\n  series: 1,\n  status: 'idle',\n  role: 'Lead',\n  model: 'Claude Opus',\n};\n\nexport const RESEARCHER: RunAgent = {\n  id: 'researcher',\n  name: 'Researcher',\n  series: 2,\n  status: 'idle',\n  model: 'Claude Sonnet',\n  parentId: 'planner',\n};\n\nexport const WRITER: RunAgent = {\n  id: 'writer',\n  name: 'Writer',\n  series: 3,\n  status: 'idle',\n  model: 'Claude Haiku',\n  parentId: 'planner',\n};\n\nexport const REVIEWER: RunAgent = {\n  id: 'reviewer',\n  name: 'Reviewer',\n  series: 4,\n  status: 'idle',\n  model: 'Claude Sonnet',\n  parentId: 'planner',\n};\n\nexport const CODER: RunAgent = {\n  id: 'coder',\n  name: 'Coder',\n  series: 5,\n  status: 'idle',\n  model: 'Claude Opus',\n  parentId: 'planner',\n};\n\nexport const CREW: RunAgent[] = [PLANNER, RESEARCHER, WRITER, REVIEWER, CODER];\n\nexport const PAGE_ERROR = 'The pricing page refused the reader (403)';\n",
      "type": "registry:component",
      "target": "components/agent/_parts/crew.ts"
    },
    {
      "path": "registry/ndeko/components/agent/_parts/demo.ts",
      "content": "/** A control that waits on a person is pressed by the demonstration after this long. */\nexport const DECIDE_AFTER_MS = 2500;\n\n/** The row a demonstration presses, when the control it holds down is the one named by `key`. */\nexport function pressedIdOf(pressed: string | null, key: string, id: string) {\n  if (pressed === key) {\n    return id;\n  }\n  return null;\n}\n",
      "type": "registry:component",
      "target": "components/agent/_parts/demo.ts"
    }
  ],
  "categories": [
    "components",
    "agent"
  ],
  "type": "registry:component"
}