SPB Git forge

spb/websensor

Public
33commits 1branches 0releases
3.4 MBsize
maindefault branch
10 days agolast push
TypeScript 55.4% Python 43.2% SQL 1.2%
7.2 KB · 128 lines tsx
Raw Blame History
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