{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"channel-timeline","type":"registry:component","title":"ChannelTimeline","description":"Reactive multichannel conversation timeline over the channel_messages InstantDB schema.","files":[{"path":"registry/default/ekairos/channel/channel-timeline.tsx","type":"registry:component","content":"\"use client\";\n\nimport { useMemo } from \"react\";\nimport type { ChannelMessage } from \"@ekairos/channel\";\nimport { cn } from \"@/lib/utils\";\nimport { ChannelMessageBubble } from \"@/components/ekairos/channel/channel-message\";\n\nexport type ChannelTimelineProps = {\n  /**\n   * InstantDB react client with the channel domain schema. When provided with\n   * `contextId`, the timeline queries `channel_messages` reactively — no\n   * fetch, no API, no glue. This is the plug & play path.\n   */\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  db?: { useQuery: (query: any) => { data?: Record<string, unknown[]> } | undefined } | null;\n  /** Agent context to read the conversation from. */\n  contextId?: string | null;\n  /** Static rows (demos, tests, server snapshots). Used when db is absent. */\n  messages?: ChannelMessage[];\n  /** Restrict to specific channels (default: all). */\n  channels?: string[];\n  className?: string;\n  emptyState?: React.ReactNode;\n};\n\nfunction useChannelMessages(\n  db: ChannelTimelineProps[\"db\"],\n  contextId: string | null | undefined,\n  fallback: ChannelMessage[] | undefined,\n): ChannelMessage[] {\n  const query = db?.useQuery(\n    contextId\n      ? {\n          channel_messages: {\n            $: {\n              where: { \"context.id\": contextId },\n              order: { createdAt: \"asc\" },\n            },\n          },\n        }\n      : null,\n  );\n  const rows = query?.data?.channel_messages;\n  if (Array.isArray(rows)) {\n    return rows as ChannelMessage[];\n  }\n  return fallback ?? [];\n}\n\nfunction dayKey(value: string): string {\n  try {\n    return new Intl.DateTimeFormat(undefined, {\n      day: \"2-digit\",\n      month: \"short\",\n      year: \"numeric\",\n    }).format(new Date(value));\n  } catch {\n    return \"\";\n  }\n}\n\n/**\n * The whole conversation across every channel. Messages arrive interleaved in\n * chronological order with day separators — whatsapp next to email next to\n * web — because they all share one canonical schema on InstantDB.\n */\nexport function ChannelTimeline({\n  db,\n  contextId,\n  messages,\n  channels,\n  className,\n  emptyState,\n}: ChannelTimelineProps) {\n  const allMessages = useChannelMessages(db, contextId, messages);\n\n  const visible = useMemo(() => {\n    const filtered = channels?.length\n      ? allMessages.filter((message) => channels.includes(message.channel))\n      : allMessages;\n    return [...filtered].sort(\n      (a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime(),\n    );\n  }, [allMessages, channels]);\n\n  if (visible.length === 0) {\n    return (\n      <div className={cn(\"grid place-items-center py-12 text-sm text-muted-foreground\", className)}>\n        {emptyState ?? \"No messages yet — every channel lands here.\"}\n      </div>\n    );\n  }\n\n  let lastDay = \"\";\n\n  return (\n    <div className={cn(\"grid gap-3\", className)} role=\"log\" aria-label=\"Channel timeline\">\n      {visible.map((message) => {\n        const day = dayKey(message.createdAt);\n        const showDay = day && day !== lastDay;\n        lastDay = day || lastDay;\n        return (\n          <div key={message.id} className=\"grid gap-3\">\n            {showDay ? (\n              <div className=\"flex items-center gap-3 py-1\">\n                <span className=\"h-px flex-1 bg-border\" />\n                <span className=\"font-mono text-[10px] uppercase tracking-[0.18em] text-muted-foreground\">\n                  {day}\n                </span>\n                <span className=\"h-px flex-1 bg-border\" />\n              </div>\n            ) : null}\n            <ChannelMessageBubble message={message} />\n          </div>\n        );\n      })}\n    </div>\n  );\n}\n","target":"components/ekairos/channel/channel-timeline.tsx"}],"dependencies":["@ekairos/channel@beta"],"devDependencies":[],"registryDependencies":["https://registry.ekairos.dev/r/channel-message.json"]}