import Link from "next/link"; import type { Metadata } from "next"; import { EventRow } from "@/components/event-row"; import { Badge, Chip, Empty, Flag, Kbd, PageHeader, Panel, Score, StateBadge, TierBadge } from "@/components/ui"; import { api } from "@/lib/api"; import { relTime, typeLabel, utcDateTime } from "@/lib/format"; export const dynamic = "force-dynamic"; export const metadata: Metadata = { title: "Search", robots: { index: false } }; const SYNTAX: { key: string; example: string; help: string }[] = [ { key: "entity", example: "entity:openai", help: "events linked to an entity" }, { key: "type", example: "type:pricing_change", help: "event type" }, { key: "after", example: "after:7d", help: "detected after (7d, 24h, 2026-09-01)" }, { key: "silent", example: "silent:true", help: "silent changes only" }, { key: "importance", example: "importance:>70", help: "minimum importance" }, { key: "country", example: "country:CA", help: "source country" }, { key: "first_party", example: "first_party:true", help: "first-party evidence only" }, ]; function fmtFilter(k: string, v: unknown): string { if (v === null || v === undefined) return ""; if (typeof v === "string" && /^\d{4}-\d{2}-\d{2}T/.test(v)) return utcDateTime(v); if (Array.isArray(v)) return v.join(", "); return String(v); } /** Search (spec §30): full-text + structured filters, results across events, clusters, entities, sources and URLs. */ export default async function SearchPage({ searchParams }: { searchParams: Promise<{ q?: string }> }) { const { q = "" } = await searchParams; const query = q.trim(); const r = query.length >= 2 ? await api.search(query) : null; const filters = Object.entries(r?.parsed?.filters ?? {}).filter(([, v]) => v !== undefined && v !== null && v !== ""); const text = r?.parsed?.text?.trim() ?? ""; const clusters = r?.clusters ?? []; const total = r ? r.events.length + clusters.length + r.entities.length + r.sources.length + r.urls.length : 0; return ( <> Results for “{query}” : "Search"} description="Events, clusters, entities, sources and URLs. Full-text over titles, summaries and keywords; structured filters narrow the event set." />
Syntax {SYNTAX.map((s) => ( {s.example} ))} ⌘K anywhere
{r && ( <>
{total} result{total === 1 ? "" : "s"} {text && “{text}”} {filters.map(([k, v]) => ( {k}: {fmtFilter(k, v)} ))} {filters.length > 0 && clear filters}
live → : undefined}> {r.events.length ? r.events.map((e) => ) : No event matches{filters.length ? " these filters" : ""}. Try a broader term, remove a filter, or extend the window with after:30d.} {clusters.length ? (
    {clusters.map((c) => (
  • {c.event_count} signal{c.event_count === 1 ? "" : "s"} · {c.source_count} source{c.source_count === 1 ? "" : "s"} {relTime(c.last_at)}
    {c.title}
  • ))}
) : ( No cluster matches. Clusters group related signals across sources. )}
)} {!r && query.length > 0 && query.length < 2 && Type at least two characters.} ); }