/* Shared atoms — top bar, footer, search field, helpers */ const Logo = () => (
L Legal Casebase v0.2 · prototype
); const Topbar = ({ view, onNavigate }) => (
{ e.preventDefault(); onNavigate("home"); }} style={{ color: "inherit" }} >
U.S. Supreme Court · CourtListener corpus
); const Footer = () => ( ); const MAGNIFIER = ( ); const SearchField = ({ query, setQuery, mode, setMode, onSubmit, autoFocus, showModes = true, busy = false }) => { return (
{ e.preventDefault(); onSubmit(); }} >
{MAGNIFIER}
setQuery(e.target.value)} placeholder="Search the casebase — cases, opinions, doctrines…" autoFocus={autoFocus} spellCheck={false} /> {showModes && (
{["fts", "vector", "hybrid"].map((m) => ( ))}
)}
); }; /* Render a snippet with [bracketed] terms (FTS5 snippet() format from * app/retrieval.py) as . Falls back to plain preview. */ const RenderedSnippet = ({ snippet, preview }) => { const text = snippet || preview || ""; if (!snippet) return <>{text}; const parts = []; let i = 0; const re = /\[([^\]]+)\]/g; let m, key = 0; while ((m = re.exec(text)) !== null) { if (m.index > i) parts.push({text.slice(i, m.index)}); parts.push({m[1]}); i = m.index + m[0].length; } if (i < text.length) parts.push({text.slice(i)}); return <>{parts}; }; const MatchChip = ({ matched_by }) => { const cls = matched_by === "both" ? "both" : matched_by === "fts" ? "fts" : "vector"; const label = matched_by === "both" ? "keyword + semantic" : matched_by === "fts" ? "keyword" : "semantic"; return ( {label} ); }; /* Trim the long CourtListener party-name string to a readable case name. * Real data: "Thomas Perttu, Petitioner v. Kyle Brandon Richards" * Render: "Perttu v. Richards" (with full title shown on hover) */ function shortCaseName(full) { if (!full) return ""; // Split on " v. " (CourtListener canonicalizes this) const m = full.match(/^(.+?)\s+v\.\s+(.+)$/i); if (!m) return full; const [, left, right] = m; // Strip CourtListener role suffixes like ", Petitioner" / ", et al." / ", President of..." const cleanSide = (side) => { return side .replace(/,\s*(Petitioner|Respondent|Appellant|Appellee|et al\.).*$/i, "") .replace(/,\s*(President|Secretary|Attorney General|Director|Administrator)[^,]*,?.*$/i, "") .trim(); }; // Take the last meaningful name on each side (surname-style). const surname = (s) => { const cleaned = cleanSide(s); // If it's a corporate/government party with commas, keep first phrase. if (/(Inc\.|LLC|Corp\.|Service|Department|United States|Postal)/i.test(cleaned)) { return cleaned.split(",")[0].trim(); } const parts = cleaned.split(/\s+/); return parts[parts.length - 1]; }; return `${surname(left)} v. ${surname(right)}`; } Object.assign(window, { Topbar, Footer, SearchField, RenderedSnippet, MatchChip, MAGNIFIER, Logo, shortCaseName, });