TypeScript 55.4%
Python 43.2%
SQL 1.2%
1import type { Metadata } from "next";2import Link from "next/link";3import { EventRow } from "@/components/event-row";4import { Chip, Empty, PageHeader, Panel, Score, StateLabelText } from "@/components/ui";5import { api } from "@/lib/api";6import { relTime, typeLabel } from "@/lib/format";78export const dynamic = "force-dynamic";9export const metadata: Metadata = { title: "Radar", description: "Weak signals that are not yet breaking: unusual source activity, clusters of silent changes, documentation and repository bursts, fresh status changes." };1011/** Radar (spec §101): indicators, not facts. */12export default async function RadarPage() {13 const r = await api.radar();14 if (!r) return <Empty>Radar is warming up.</Empty>;15 return (16 <>17 <PageHeader compact kicker={<Chip tone="warn">Indicators, not facts</Chip>} title="Radar" description="What might become important next. Each item is a pattern in raw observations — a source far above its baseline, several silent changes around one entity, a burst of documentation or repository edits, a fresh status change — that has not (yet) produced a high-importance event." />18 <div className="grid grid-cols-1 gap-4 lg:grid-cols-2 xl:grid-cols-3 [&>*]:min-w-0">19 <Panel title="Unusual source activity · 3 h" dense>20 {r.unusual_source_activity.length ? (21 <ul className="divide-y divide-line">22 {r.unusual_source_activity.map((s) => (23 <li key={s.id} className="px-3 py-2 text-[13px]">24 <div className="flex items-center gap-2">25 <Link href={`/source/${s.id}`} className="min-w-0 flex-1 truncate font-medium hover:underline">{s.name}</Link>26 <span className="font-mono text-[12px] font-semibold text-high tabular">×{s.ratio}</span>27 </div>28 <div className="text-[11px] text-fg-subtle">{s.changes_3h} raw changes in 3 h · baseline {s.baseline_per_day}/day · no high-importance event yet</div>29 <div className="mt-1 flex flex-wrap gap-1">{(s.categories ?? []).slice(0, 3).map((c) => <Chip key={c} href={`/category/${c}`}>{c}</Chip>)}</div>30 </li>31 ))}32 </ul>33 ) : (34 <Empty>No source is quietly bursting above its baseline.</Empty>35 )}36 </Panel>37 <Panel title="Silent-change clusters · 24 h" dense>38 {r.silent_clusters.length ? (39 <ul className="divide-y divide-line">40 {r.silent_clusters.map((e) => (41 <li key={e.id} className="px-3 py-2 text-[13px]">42 <div className="flex items-center gap-2">43 <Link href={`/entity/${e.id}?tab=silent`} className="min-w-0 flex-1 truncate font-medium hover:underline">{e.name}</Link>44 <span className="font-mono text-[12px] font-semibold text-silent tabular">{e.silent_24h} silent</span>45 </div>46 <div className="mt-1 flex flex-wrap gap-1">{e.types.slice(0, 4).map((t) => <Chip key={t} tone="silent">{typeLabel(t)}</Chip>)}<span className="text-[11px] text-fg-subtle">{relTime(e.last_at)}</span></div>47 </li>48 ))}49 </ul>50 ) : (51 <Empty>No entity accumulated several silent changes.</Empty>52 )}53 </Panel>54 <Panel title="Developing clusters" dense>55 {r.developing.length ? (56 <ul className="divide-y divide-line">57 {r.developing.map((c) => (58 <li key={c.id} className="flex items-start gap-2 px-3 py-2 text-[13px]">59 <Score value={c.max_importance} size="sm" />60 <div className="min-w-0">61 <Link href={`/cluster/${c.slug ?? c.id}`} className="line-clamp-2 font-medium hover:underline">{c.title}</Link>62 <div className="flex flex-wrap gap-x-2 text-[11px] text-fg-subtle"><StateLabelText state={c.state} /><span>{c.event_count} signals · {c.source_count} sources · velocity {Math.round(c.velocity ?? 0)}</span><span>{relTime(c.last_at)}</span></div>63 </div>64 </li>65 ))}66 </ul>67 ) : (68 <Empty>No developing cluster.</Empty>69 )}70 </Panel>71 <Panel title="Documentation & API bursts · 6 h" dense>72 {r.documentation_bursts.length ? (73 <ul className="divide-y divide-line">74 {r.documentation_bursts.map((s) => (75 <li key={s.id} className="px-3 py-2 text-[13px]">76 <div className="flex items-center gap-2">77 <Link href={`/source/${s.id}`} className="min-w-0 flex-1 truncate font-medium hover:underline">{s.name}</Link>78 <span className="font-mono text-[12px] tabular">{s.doc_changes_6h} edits</span>79 </div>80 <div className="mt-1 flex flex-wrap gap-1">{s.types.map((t) => <Chip key={t}>{typeLabel(t)}</Chip>)}<span className="text-[11px] text-fg-subtle">{relTime(s.last_at)}</span></div>81 </li>82 ))}83 </ul>84 ) : (85 <Empty>No documentation burst. A launch often starts with several doc/API edits in a short window.</Empty>86 )}87 </Panel>88 <Panel title="Repository bursts · 6 h" dense>89 {r.repository_bursts.length ? (90 <ul className="divide-y divide-line">91 {r.repository_bursts.map((s) => (92 <li key={s.id} className="flex items-center gap-2 px-3 py-2 text-[13px]">93 <Link href={`/source/${s.id}`} className="min-w-0 flex-1 truncate font-medium hover:underline">{s.name}</Link>94 <span className="font-mono text-[12px] tabular">{s.repo_events_6h} releases/commits</span>95 <span className="text-[11px] text-fg-subtle">{relTime(s.last_at)}</span>96 </li>97 ))}98 </ul>99 ) : (100 <Empty>No repository burst.</Empty>101 )}102 </Panel>103 <Panel title="New coverage lighting up · 24 h" dense>104 {r.new_coverage.length ? (105 <ul className="divide-y divide-line">106 {r.new_coverage.map((s) => (107 <li key={s.id} className="flex items-center gap-2 px-3 py-2 text-[13px]">108 <div className="min-w-0 flex-1">109 <Link href={`/sensor/${s.id}`} className="block truncate font-medium hover:underline">{s.name}</Link>110 <div className="truncate text-[11px] text-fg-subtle">{s.source_name} · first event {relTime(s.first_event_at)}</div>111 </div>112 <span className="font-mono text-[12px] tabular">{s.events}</span>113 </li>114 ))}115 </ul>116 ) : (117 <Empty>No sensor produced its first event today.</Empty>118 )}119 </Panel>120 </div>121 <Panel title="Fresh status changes below the breaking bar · 3 h" dense className="mt-4">122 {r.status_changes.length ? r.status_changes.map((e) => <EventRow key={e.id} ev={e} />) : <Empty>No fresh status-page change.</Empty>}123 </Panel>124 <p className="mt-3 text-[11px] text-fg-subtle">{r.disclaimer} Generated {relTime(r.generated_at)}.</p>125 </>126 );127}128