{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "live-feed",
  "type": "registry:component",
  "title": "Live Feed",
  "description": "Rolling activity stream for surfacing incidents, deploys, and operational signals in real time.",
  "dependencies": [
    "@vllnt/ui@^0.2.1"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/default/live-feed/live-feed.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { cn } from \"@vllnt/ui\";\nimport { Badge } from \"@vllnt/ui\";\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle,\n} from \"@vllnt/ui\";\nimport {\n  SeverityBadge,\n  type SeverityBadgeLevel,\n} from \"@vllnt/ui\";\n\nexport type LiveFeedEvent = {\n  id: string;\n  message?: string;\n  severity: SeverityBadgeLevel;\n  source?: string;\n  timestamp: Date | number | string;\n  title: string;\n};\n\nexport type LiveFeedProps = React.ComponentPropsWithoutRef<\"div\"> & {\n  description?: string;\n  emptyLabel?: string;\n  events: LiveFeedEvent[];\n  maxItems?: number;\n  now?: Date | number | string;\n  tickMs?: number;\n  title?: string;\n};\n\nconst SECOND_MS = 1000;\nconst MINUTE_MS = 60 * SECOND_MS;\nconst HOUR_MS = 60 * MINUTE_MS;\nconst DAY_MS = 24 * HOUR_MS;\n\nfunction normalizeDate(input: Date | number | string): Date {\n  if (input instanceof Date) {\n    return new Date(input.getTime());\n  }\n\n  return new Date(input);\n}\n\nfunction useLiveDate(now: LiveFeedProps[\"now\"], tickMs: number) {\n  const fixedNow = React.useMemo(\n    () => (now ? normalizeDate(now) : undefined),\n    [now],\n  );\n  const [liveNow, setLiveNow] = React.useState<Date>(fixedNow ?? new Date());\n\n  React.useEffect(() => {\n    if (fixedNow) {\n      setLiveNow(fixedNow);\n      return;\n    }\n\n    const interval = window.setInterval(() => {\n      setLiveNow(new Date());\n    }, tickMs);\n\n    return () => {\n      window.clearInterval(interval);\n    };\n  }, [fixedNow, tickMs]);\n\n  return liveNow;\n}\n\nfunction formatRelative(eventDate: Date, now: Date): string {\n  const deltaMs = now.getTime() - eventDate.getTime();\n\n  if (deltaMs < 5 * SECOND_MS) {\n    return \"just now\";\n  }\n\n  if (deltaMs < MINUTE_MS) {\n    return `${Math.floor(deltaMs / SECOND_MS)}s ago`;\n  }\n\n  if (deltaMs < HOUR_MS) {\n    return `${Math.floor(deltaMs / MINUTE_MS)}m ago`;\n  }\n\n  if (deltaMs < DAY_MS) {\n    return `${Math.floor(deltaMs / HOUR_MS)}h ago`;\n  }\n\n  if (deltaMs < 7 * DAY_MS) {\n    return `${Math.floor(deltaMs / DAY_MS)}d ago`;\n  }\n\n  return SHORT_DATE_FORMATTER.format(eventDate);\n}\n\nconst SHORT_DATE_FORMATTER = new Intl.DateTimeFormat(\"en-US\", {\n  day: \"numeric\",\n  month: \"short\",\n});\n\nconst ABSOLUTE_FORMATTER = new Intl.DateTimeFormat(\"en-US\", {\n  hour: \"numeric\",\n  minute: \"2-digit\",\n  month: \"short\",\n  second: \"2-digit\",\n});\n\nfunction formatAbsolute(eventDate: Date): string {\n  return ABSOLUTE_FORMATTER.format(eventDate);\n}\n\nfunction sortEventsDesc(events: LiveFeedEvent[]): LiveFeedEvent[] {\n  return [...events].sort(\n    (a, b) =>\n      normalizeDate(b.timestamp).getTime() -\n      normalizeDate(a.timestamp).getTime(),\n  );\n}\n\nfunction LiveFeedRow({\n  event,\n  isLatest,\n  now,\n}: {\n  event: LiveFeedEvent;\n  isLatest: boolean;\n  now: Date;\n}) {\n  const eventDate = normalizeDate(event.timestamp);\n\n  return (\n    <li className=\"flex gap-3 border-b border-border/60 py-3 last:border-b-0\">\n      <div className=\"pt-1\">\n        <SeverityBadge\n          level={event.severity}\n          pulse={isLatest ? event.severity === \"critical\" : undefined}\n          tone=\"soft\"\n        />\n      </div>\n      <div className=\"min-w-0 flex-1\">\n        <div className=\"flex items-baseline justify-between gap-2\">\n          <p className=\"truncate text-sm font-medium\">{event.title}</p>\n          <time\n            className=\"whitespace-nowrap text-xs text-muted-foreground\"\n            dateTime={eventDate.toISOString()}\n            title={formatAbsolute(eventDate)}\n          >\n            {formatRelative(eventDate, now)}\n          </time>\n        </div>\n        {event.message ? (\n          <p className=\"mt-0.5 text-sm text-muted-foreground\">\n            {event.message}\n          </p>\n        ) : null}\n        {event.source ? (\n          <p className=\"mt-1 text-xs uppercase tracking-[0.14em] text-muted-foreground\">\n            {event.source}\n          </p>\n        ) : null}\n      </div>\n    </li>\n  );\n}\n\nexport const LiveFeed = React.forwardRef<HTMLDivElement, LiveFeedProps>(\n  (\n    {\n      className,\n      description,\n      emptyLabel = \"No events yet\",\n      events,\n      maxItems = 50,\n      now,\n      tickMs = 30_000,\n      title = \"Live feed\",\n      ...props\n    },\n    ref,\n  ) => {\n    const liveNow = useLiveDate(now, tickMs);\n    const visibleEvents = React.useMemo(\n      () => sortEventsDesc(events).slice(0, maxItems),\n      [events, maxItems],\n    );\n\n    return (\n      <Card className={cn(\"shadow-sm\", className)} ref={ref} {...props}>\n        <CardHeader className=\"flex flex-row items-start justify-between gap-3 space-y-0 pb-3\">\n          <div className=\"space-y-1\">\n            <CardTitle className=\"text-base\">{title}</CardTitle>\n            {description ? (\n              <CardDescription>{description}</CardDescription>\n            ) : null}\n          </div>\n          <Badge variant=\"outline\">\n            <span\n              aria-hidden=\"true\"\n              className=\"relative mr-1.5 inline-flex size-2\"\n            >\n              <span className=\"absolute inline-flex size-2 animate-ping rounded-full bg-emerald-500 opacity-70\" />\n              <span className=\"relative inline-flex size-2 rounded-full bg-emerald-500\" />\n            </span>{\" \"}\n            Live\n          </Badge>\n        </CardHeader>\n        <CardContent className=\"pt-0\">\n          {visibleEvents.length === 0 ? (\n            <div className=\"py-8 text-center text-sm text-muted-foreground\">\n              {emptyLabel}\n            </div>\n          ) : (\n            <ul className=\"max-h-[360px] divide-y divide-border/60 overflow-y-auto\">\n              {visibleEvents.map((event, index) => (\n                <LiveFeedRow\n                  event={event}\n                  isLatest={index === 0}\n                  key={event.id}\n                  now={liveNow}\n                />\n              ))}\n            </ul>\n          )}\n        </CardContent>\n      </Card>\n    );\n  },\n);\n\nLiveFeed.displayName = \"LiveFeed\";\n",
      "type": "registry:component"
    }
  ],
  "version": "0.2.1",
  "stability": "stable"
}
