"use client"; import Link from "next/link"; import type { MouseEvent } from "react"; import type { EventItem } from "@/lib/api"; import { relTime, utcTime, utcDate, utcDateTime, localDateTime } from "@/lib/format"; import { useEventDrawer } from "./event-drawer"; import { FieldChangeInline } from "./field-changes"; import { usePrefs } from "./prefs"; import { Badge, Chip, Flag, Score, TypeChip } from "./ui"; const CHANNEL_GROUPS: Record = { finance: ["finance", "payments", "crypto", "commerce"], health: ["health", "pharma"], government: ["government", "statistics"], science: ["science", "space"], products: ["consumer-tech", "automotive", "semiconductors", "enterprise"], infrastructure: ["cloud", "developer", "internet", "standards"], news: ["news", "media"] }; function sameChannel(a: string, b: string): boolean { return CHANNEL_GROUPS[a]?.includes(b) ?? false; } /** * Feed row (spec §31): TIME · ENTITY/SOURCE · SIGNAL · TYPE · TITLE · STATUS · TAGS, with badges * BREAKING / DEVELOPING / SILENT / FIRST PARTY / CONFIRMED / ANOMALOUS. Density comes from CSS * variables (spec §32). Clicking the title opens the intelligence drawer (spec §33); the link keeps * a real href so middle-click / crawlers / no-JS still reach the permanent page. */ export function EventRow({ ev, flash = false, showDate = false, now, fresh = false, replayed = false }: { ev: EventItem; flash?: boolean; showDate?: boolean; now?: number; fresh?: boolean; replayed?: boolean }) { const { open } = useEventDrawer(); const { exactTime, density, mounted } = usePrefs(); const iso = (() => { const d = new Date(ev.detected_at); return Number.isNaN(d.getTime()) ? ev.detected_at : d.toISOString(); })(); const cats = (ev.categories ?? []).filter((c) => !Object.keys(CHANNEL_GROUPS).includes(c) || !ev.categories.some((x) => x !== c && sameChannel(c, x))).slice(0, density === "compact" ? 1 : 2); const signal = ev.signal_score ?? ev.importance; const state = ev.cluster?.state; const isBreaking = state === "breaking" || (signal >= 80 && !state); const isDeveloping = state === "developing"; const anomalous = (ev.anomaly_score ?? 0) >= 60; const clusterHref = ev.cluster?.slug ? `/cluster/${ev.cluster.slug}` : ev.cluster_id ? `/cluster/${ev.cluster_id}` : null; const onClick = (e: MouseEvent): void => { if (e.metaKey || e.ctrlKey || e.shiftKey || e.button !== 0) return; e.preventDefault(); open(ev.slug, ev); }; return (
{showDate ? utcDate(ev.detected_at) : exactTime ? "UTC" : relTime(ev.detected_at, now)}
{ev.source?.name ?? ev.source_id} {ev.country && } {isBreaking && } {isDeveloping && } {ev.silent_change && } {ev.first_party === false && } {ev.evidence_label === "CONFIRMED" && } {anomalous && } {replayed && }
{ev.title} {density !== "compact" && ev.field_changes && ev.field_changes.length > 0 && (
)} {density === "comfortable" && ev.summary &&

{ev.summary}

}
{cats.map((c) => ( {c} ))} {ev.entities?.filter((x) => x.role === "subject").slice(0, 1).map((x) => ( {x.name} ))} {ev.cluster_size && ev.cluster_size > 1 && clusterHref && ( {ev.cluster_size} signals{ev.cluster?.source_count && ev.cluster.source_count > 1 ? ` · ${ev.cluster.source_count} sources` : ""} )}
); }