Search across panels Sixty panels and the reader knows the name of the one they want. Results have to say which dashboard and which row each hit lives on, because half the panel names in an organisation are the same three words. npx shadcn@latest add command button npm i cmdk lucide-react Tokens this needs: --popover, --popover-foreground, --muted-foreground, --border, --accent ──────────────────────────────────────────────────────────────────────── // SearchAcrossPanels.tsx ──────────────────────────────────────────────────────────────────────── import { useEffect, useState } from "react"; import { Command, CommandEmpty, CommandInput, CommandItem, CommandList } from "@/components/ui/command"; import { ago, search, type Dashboard, type Hit } from "./search"; export function SearchAcrossPanels({ dashboards, open, onOpenChange, defaultQuery = "", onOpen, now }: { dashboards: Dashboard[]; open: boolean; onOpenChange: (open: boolean) => void; defaultQuery?: string; onOpen: (hit: Hit) => void; /** The clock to age "edited" against. Pass one to render on a server. */ now?: Date; }) { const [query, setQuery] = useState(defaultQuery); const hits = search(query, dashboards); const at = now ?? new Date(); // ⌘K from anywhere on the page, and the same key closes it. useEffect(() => { const onKey = (e: KeyboardEvent) => { if ((e.metaKey || e.ctrlKey) && e.key === "k") { e.preventDefault(); onOpenChange(!open); } if (e.key === "Escape" && open) onOpenChange(false); }; window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, [open, onOpenChange]); if (!open) return null; return ( // In a page this sits in CommandDialog over the dimmed content; the box // itself is the same. cmd K Nothing in a title, panel, tag or query mentions "{query}". Try one word of it, or a tag. {hits.map((h) => ( onOpen(h)} className="flex items-start gap-3 px-3 py-2.5">

{h.dashboard.title} {h.dashboard.folder}

{/* Why it matched. The word someone remembers is usually a panel or a tag, and this is where it was. */}

{h.on}: {h.text}

{h.dashboard.opens} opens · 30d

edited {ago(h.dashboard.edited, at)}

))}
); } ──────────────────────────────────────────────────────────────────────── // search.ts ──────────────────────────────────────────────────────────────────────── /** One dashboard as the index sees it. Everything search reaches is here, * and nothing else is searchable. */ export type Dashboard = { id: string; title: string; folder: string; tags: string[]; panels: { title: string; query: string }[]; edited: Date; /** Opens in the last thirty days. The only signal of which copy a team * actually uses, so it ranks above recency. */ opens: number; }; /** Where the hit was. Closed, and a hit cannot exist without one: a result * that only names the dashboard makes the reader open it and hunt. */ export type Field = "title" | "panel" | "tag" | "query"; const ORDER: Field[] = ["title", "panel", "tag", "query"]; export type Hit = { dashboard: Dashboard; on: Field; text: string; terms: number }; const fields = (d: Dashboard): [Field, string][] => [ ["title", d.title], ...d.panels.map(({ title }) => ["panel", title] as [Field, string]), ...d.tags.map((t) => ["tag", t] as [Field, string]), ...d.panels.map(({ query }) => ["query", query] as [Field, string]), ]; /** Every term is tried against every field. The field that holds the most * terms is the reason shown; ties go to the field a person would remember. */ export function search(q: string, dashboards: Dashboard[]): Hit[] { const terms = q.toLowerCase().split(/\s+/).filter(Boolean); if (!terms.length) return []; const hits: Hit[] = []; for (const d of dashboards) { let best: Hit | null = null; for (const [on, text] of fields(d)) { const n = terms.filter((t) => text.toLowerCase().includes(t)).length; if (n && (!best || n > best.terms || (n === best.terms && ORDER.indexOf(on) < ORDER.indexOf(best.on)))) best = { dashboard: d, on, text, terms: n }; } if (best) hits.push(best); } // Terms matched, then use, then recency. Recency last on purpose: it // surfaces whatever somebody edited, which is rarely the authoritative copy. return hits.sort((a, b) => b.terms - a.terms || b.dashboard.opens - a.dashboard.opens || b.dashboard.edited.getTime() - a.dashboard.edited.getTime()); } export function ago(then: Date, now: Date) { const days = Math.floor((now.getTime() - then.getTime()) / 86_400_000); if (days < 1) return "today"; if (days === 1) return "yesterday"; if (days < 30) return `${days} days ago`; if (days < 365) return `${Math.floor(days / 30)} months ago`; const y = Math.floor(days / 365); return y === 1 ? "a year ago" : `${y} years ago`; } ──────────────────────────────────────────────────────────────────────── // demo.tsx ──────────────────────────────────────────────────────────────────────── import { useState } from "react"; import { Search } from "lucide-react"; import { Button } from "@/components/ui/button"; import { SearchAcrossPanels } from "./SearchAcrossPanels"; import type { Dashboard } from "./search"; const NOW = new Date("2026-09-15T09:00:00Z"); const daysAgo = (n: number) => new Date(NOW.getTime() - n * 86_400_000); /** A slice of the estate. Two are called API Overview; the opens column is * what tells them apart. */ const DASHBOARDS: Dashboard[] = [ { id: "pay", title: "Payments overview", folder: "Payments", tags: ["payments"], edited: daysAgo(3), opens: 412, panels: [{ title: "Checkout p95 by region", query: "histogram_quantile(0.95, sum by (le, region) (rate(checkout_latency_seconds_bucket[5m])))" }, { title: "Authorisations", query: "sum(rate(payment_auth_total[5m]))" }] }, { id: "slo", title: "Service SLOs", folder: "Platform", tags: ["checkout", "slo"], edited: daysAgo(11), opens: 268, panels: [{ title: "Availability", query: "1 - (sum(rate(http_5xx_total[30d])) / sum(rate(http_requests_total[30d])))" }, { title: "Latency burn rate", query: "slo:latency_burn_rate:1h" }] }, { id: "edge", title: "Edge latency", folder: "Network", tags: ["edge", "cdn"], edited: daysAgo(2), opens: 57, panels: [{ title: "TTFB by POP", query: "histogram_quantile(0.95, sum by (le, pop) (rate(checkout_edge_ttfb_seconds_bucket[5m])))" }] }, { id: "api", title: "API Overview", folder: "Platform", tags: ["api"], edited: daysAgo(120), opens: 390, panels: [{ title: "Requests", query: "sum(rate(http_requests_total[5m]))" }] }, { id: "api-old", title: "API Overview", folder: "Archive", tags: ["api"], edited: daysAgo(730), opens: 2, panels: [{ title: "Requests", query: "sum(rate(http_requests_total[5m]))" }] }, ]; /** Open on arrival with a search already typed. ⌘K closes and opens it; * picking a result would navigate. Type "api" to see the duplicates. */ export default function Demo() { const [open, setOpen] = useState(true); const [went, setWent] = useState(null); return (
{!open && ( )} { setWent(`${h.dashboard.title}, ${h.on} ${h.text}`); setOpen(false); }} now={NOW} /> {went &&

opened {went}

}
); }