TypeScript 55.4%
Python 43.2%
SQL 1.2%
1export function fmtInt(n: number | string | null | undefined): string {2 const v = typeof n === "string" ? Number(n) : n;3 if (v === null || v === undefined || Number.isNaN(v)) return "—";4 return new Intl.NumberFormat("en-US").format(Math.round(v));5}67export function fmtCompact(n: number | string | null | undefined): string {8 const v = typeof n === "string" ? Number(n) : n;9 if (v === null || v === undefined || Number.isNaN(v)) return "—";10 return new Intl.NumberFormat("en-US", { notation: "compact", maximumFractionDigits: 1 }).format(v);11}1213export function fmtBytes(n: number | string | null | undefined): string {14 const v = typeof n === "string" ? Number(n) : n;15 if (v === null || v === undefined || Number.isNaN(v)) return "—";16 const units = ["B", "KB", "MB", "GB", "TB"];17 let i = 0;18 let x = v;19 while (x >= 1024 && i < units.length - 1) {20 x /= 1024;21 i++;22 }23 return `${x.toFixed(i === 0 ? 0 : 1)} ${units[i]}`;24}2526export function fmtScore(n: number | null | undefined): string {27 if (n === null || n === undefined || Number.isNaN(n)) return "—";28 return n >= 99.95 ? "100" : n.toFixed(n >= 10 ? 0 : 1);29}3031export function fmtPct(n: number | null | undefined, digits = 0): string {32 if (n === null || n === undefined || Number.isNaN(n)) return "—";33 return `${(n * 100).toFixed(digits)}%`;34}3536export function fmtMs(ms: number | null | undefined): string {37 if (ms === null || ms === undefined || Number.isNaN(ms)) return "—";38 if (ms < 1000) return `${Math.round(ms)} ms`;39 if (ms < 60_000) return `${(ms / 1000).toFixed(1)} s`;40 if (ms < 3_600_000) return `${Math.round(ms / 60_000)} min`;41 if (ms < 86_400_000) return `${(ms / 3_600_000).toFixed(1)} h`;42 return `${(ms / 86_400_000).toFixed(1)} d`;43}4445export function fmtDuration(seconds: number | null | undefined): string {46 if (seconds === null || seconds === undefined) return "—";47 if (seconds < 60) return `${seconds}s`;48 if (seconds < 3600) return `${Math.round(seconds / 60)}m`;49 if (seconds < 86400) return `${(seconds / 3600).toFixed(1)}h`;50 return `${(seconds / 86400).toFixed(1)}d`;51}5253const pad = (n: number): string => String(n).padStart(2, "0");5455export function utcTime(iso: string | Date | null | undefined, seconds = true): string {56 if (!iso) return "—";57 const d = typeof iso === "string" ? new Date(iso) : iso;58 if (Number.isNaN(d.getTime())) return "—";59 return `${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}${seconds ? ":" + pad(d.getUTCSeconds()) : ""}`;60}6162export function utcDate(iso: string | Date | null | undefined): string {63 if (!iso) return "—";64 const d = typeof iso === "string" ? new Date(iso) : iso;65 if (Number.isNaN(d.getTime())) return "—";66 return `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())}`;67}6869export function utcDateTime(iso: string | Date | null | undefined): string {70 if (!iso) return "—";71 return `${utcDate(iso)} ${utcTime(iso)} UTC`;72}7374const MONTHS = ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"];75export function dayHeader(iso: string): string {76 const d = new Date(iso);77 return `${MONTHS[d.getUTCMonth()]} ${pad(d.getUTCDate())} · ${d.getUTCFullYear()}`;78}7980export function relTime(iso: string | Date | null | undefined, now = Date.now()): string {81 if (!iso) return "";82 const t = (typeof iso === "string" ? new Date(iso) : iso).getTime();83 if (Number.isNaN(t)) return "";84 const s = Math.max(0, Math.round((now - t) / 1000));85 if (s < 5) return "just now";86 if (s < 60) return `${s}s ago`;87 const m = Math.round(s / 60);88 if (m < 60) return `${m}m ago`;89 const h = Math.round(m / 60);90 if (h < 48) return `${h}h ago`;91 const d = Math.round(h / 24);92 if (d < 30) return `${d}d ago`;93 return `${Math.round(d / 30)}mo ago`;94}9596export function untilTime(iso: string | null | undefined, now = Date.now()): string {97 if (!iso) return "—";98 const t = new Date(iso).getTime();99 const s = Math.round((t - now) / 1000);100 if (s <= 0) return "due";101 if (s < 60) return `in ${s}s`;102 if (s < 3600) return `in ${Math.round(s / 60)}m`;103 return `in ${(s / 3600).toFixed(1)}h`;104}105106export function importanceBand(score: number): "hot" | "high" | "mid" | "low" {107 if (score >= 90) return "hot";108 if (score >= 75) return "high";109 if (score >= 50) return "mid";110 return "low";111}112113export function typeLabel(t: string): string {114 return t.replace(/_/g, " ");115}116117export function hostOf(url: string): string {118 try {119 return new URL(url).hostname.replace(/^www\./, "");120 } catch {121 return url;122 }123}124125export function shortHash(h: string | null | undefined, n = 12): string {126 return h ? h.slice(0, n) : "—";127}128129export const CHANNELS: { key: string; label: string; query: Record<string, string | number | boolean>; ws: string }[] = [130 { key: "all", label: "Everything", query: {}, ws: "events:global" },131 { key: "breaking", label: "Breaking", query: { importance_min: 80 }, ws: "events:breaking" },132 { key: "ai", label: "AI", query: { category: "ai" }, ws: "events:ai" },133 { key: "cyber", label: "Cyber", query: { category: "cyber" }, ws: "events:cyber" },134 { key: "finance", label: "Markets", query: { category: "finance" }, ws: "events:finance" },135 { key: "health", label: "Healthcare", query: { category: "health" }, ws: "events:health" },136 { key: "government", label: "Government", query: { category: "government" }, ws: "events:government" },137 { key: "science", label: "Science", query: { category: "science" }, ws: "events:science" },138 { key: "products", label: "Products", query: { category: "products" }, ws: "events:products" },139 { key: "infrastructure", label: "Infrastructure", query: { category: "infrastructure" }, ws: "events:infrastructure" },140 { key: "news", label: "News", query: { category: "news" }, ws: "events:news" },141 { key: "silent", label: "Silent", query: { silent_change: true }, ws: "events:silent" },142];143144export const CHANNEL_KEYS = ["ai", "cyber", "finance", "health", "government", "science", "products", "infrastructure", "news"] as const;145146// ---------------------------------------------------------------------------------------147// 0.2 additions148// ---------------------------------------------------------------------------------------149150/** Lead time / offsets: "+14 s", "+2 min", "+3.2 h" */151export function fmtOffset(ms: number | null | undefined): string {152 if (ms === null || ms === undefined || Number.isNaN(ms)) return "—";153 const sign = ms < 0 ? "−" : "+";154 const a = Math.abs(ms);155 if (a < 1000) return `${sign}${Math.round(a)} ms`;156 if (a < 60_000) return `${sign}${Math.round(a / 1000)} s`;157 if (a < 3_600_000) return `${sign}${Math.round(a / 60_000)} min`;158 if (a < 86_400_000) return `${sign}${(a / 3_600_000).toFixed(1)} h`;159 return `${sign}${(a / 86_400_000).toFixed(1)} d`;160}161162export function fmtPctDelta(pct: number | null | undefined): string {163 if (pct === null || pct === undefined || Number.isNaN(pct)) return "";164 return `${pct > 0 ? "+" : ""}${Math.abs(pct) >= 100 ? Math.round(pct) : pct}%`;165}166167export function signalBand(score: number | null | undefined): "hot" | "high" | "mid" | "low" {168 return importanceBand(score ?? 0);169}170171export function localDateTime(iso: string | Date | null | undefined): string {172 if (!iso) return "—";173 const d = typeof iso === "string" ? new Date(iso) : iso;174 if (Number.isNaN(d.getTime())) return "—";175 return new Intl.DateTimeFormat(undefined, { year: "numeric", month: "short", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit", timeZoneName: "short" }).format(d);176}177178export const GROUP_LABELS: Record<string, string> = { security: "Security", reliability: "Reliability", product: "Product & API", commercial: "Pricing & terms", corporate: "Corporate", government: "Government & legal", science: "Science & health", transport: "Transport", sports: "Sports", web: "Web" };179180export const CLASS_LABELS: Record<string, string> = { meaningful: "Content", pricing: "Pricing", policy: "Policy", product: "Product", personnel: "Personnel", cosmetic: "Cosmetic", navigation: "Navigation", timestamp: "Timestamp", advertisement: "Advertising", boilerplate: "Boilerplate" };181182export function stateLabel(s: string | null | undefined): string {183 switch (s) {184 case "breaking":185 return "BREAKING";186 case "developing":187 return "DEVELOPING";188 case "confirmed":189 return "CONFIRMED";190 case "watching":191 return "WATCHING";192 case "closed":193 return "CLOSED";194 default:195 return "";196 }197}198199/** Build the live-feed URL for a filter set (spec §99). */200export function feedHref(q: Record<string, string | number | boolean | undefined | null>, base = "/live"): string {201 const p = new URLSearchParams();202 for (const [k, v] of Object.entries(q)) if (v !== undefined && v !== null && v !== "" && v !== false) p.set(k, String(v));203 const s = p.toString();204 return s ? `${base}?${s}` : base;205}206207export const SAVED_VIEWS: { key: string; label: string; query: Record<string, string | number | boolean> }[] = [208 { key: "ai-releases", label: "AI releases", query: { category: "ai", event_type: "model_release,software_release,product_launch,API_change,api_change" } },209 { key: "cyber-critical", label: "Cyber critical", query: { group: "security", signal_min: 70 } },210 { key: "canada-gov", label: "Canadian government", query: { country: "CA", category: "government" } },211 { key: "market-breaking", label: "Market breaking", query: { category: "finance", signal_min: 75 } },212 { key: "cloud-outages", label: "Cloud outages", query: { category: "infrastructure", group: "reliability" } },213 { key: "silent-pricing", label: "Silent pricing & terms", query: { silent_change: true, group: "commercial" } },214 { key: "first-party-confirmed", label: "First-party & confirmed", query: { first_party: true, confirmed: true } },215];216217/** ISO timestamp `ms` milliseconds ago — keeps `Date.now()` out of component bodies (react-hooks/purity). */218export function agoIso(ms: number): string {219 return new Date(Date.now() - ms).toISOString();220}221222/** True when `iso` falls within the last `windowMs` milliseconds. */223export function withinLast(iso: string | null | undefined, windowMs: number): boolean {224 if (!iso) return false;225 const t = new Date(iso).getTime();226 return Number.isFinite(t) && Date.now() - t < windowMs;227}228