// Small UI primitives shared across pages. const { useState, useEffect, useMemo, useRef } = React; function Avatar({ agentId, size = 36, lead = false }) { const a = teamById(agentId); if (!a) { return
; } const ring = lead || a.isLead; return (
); } function SeverityChip({ severity }) { const labels = { critical: "Critical", warning: "Warning", info: "Info" }; return ( {labels[severity] || severity} ); } function SeverityBar({ severity }) { return
; } // Tiny sparkline svg function Sparkline({ data, width = 120, height = 28, color = "var(--accent)", fill = true }) { const max = Math.max(...data, 1); const min = Math.min(...data, 0); const range = max - min || 1; const stepX = width / Math.max(data.length - 1, 1); const pts = data.map((v, i) => [i * stepX, height - ((v - min) / range) * (height - 4) - 2]); const d = pts.map(([x, y], i) => (i === 0 ? `M${x},${y}` : `L${x},${y}`)).join(" "); const dFill = `${d} L${width},${height} L0,${height} Z`; return ( {fill && } ); } // Bar mini (todayLoad) function Bars({ data, width = 120, height = 22, color = "var(--accent)" }) { const max = Math.max(...data, 1); const w = width / data.length - 2; return ( {data.map((v, i) => { const h = (v / max) * (height - 2); return ; })} ); } // Context gauge (semicircular) function Gauge({ pct, size = 84 }) { const r = size / 2 - 8; const cx = size / 2; const cy = size / 2; const circumference = 2 * Math.PI * r; const halfCirc = circumference / 2; const clamped = Math.max(0, Math.min(100, pct)); const dash = (clamped / 100) * halfCirc; const color = pct >= 70 ? "var(--ok)" : pct >= 30 ? "var(--warn)" : "var(--crit)"; return (
{Math.round(pct)}%
context
); } // Severity / kind icon (inline svg, monochrome) function KindIcon({ kind, size = 14 }) { const s = size; const stroke = "currentColor"; switch (kind) { case "state_changed": return ; case "new_task": return ; case "labels_changed": return ; case "assignees_changed": return ; case "comment": return ; case "alert": return ; default: return ; } } function kindColor(kind) { switch (kind) { case "state_changed": return "var(--ok)"; case "new_task": return "var(--accent)"; case "labels_changed":return "var(--warn)"; case "assignees_changed": return "var(--info)"; case "alert": return "var(--crit)"; case "comment": return "var(--text-dim)"; default: return "var(--text-muted)"; } } function kindLabel(kind) { return { state_changed: "state", new_task: "created", labels_changed: "label", assignees_changed: "assignee", alert: "alert", comment: "comment", }[kind] || kind; } function TaskRef({ seq, title, onClick }) { return ( PB-{seq} {title && {title}} ); } // Side panel (drawer) for details function SidePanel({ open, onClose, title, children, width = 520 }) { useEffect(() => { if (!open) return; const onKey = (e) => { if (e.key === "Escape") onClose(); }; window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, [open, onClose]); if (!open) return null; return (
); } // Confirm dialog function ConfirmDialog({ open, title, message, danger, onConfirm, onCancel }) { if (!open) return null; return (
e.stopPropagation()} style={{ width: 420, background: "var(--surface)", border: "1px solid var(--border)", borderRadius: 10, overflow: "hidden" }}>
{title}
{message}
); } // Toast (simple) function useToasts() { const [toasts, setToasts] = useState([]); const push = (text, tone = "info") => { const id = Math.random().toString(36).slice(2); setToasts((t) => [...t, { id, text, tone }]); setTimeout(() => setToasts((t) => t.filter((x) => x.id !== id)), 3000); }; const node = (
{toasts.map((t) => (
{t.text}
))}
); return { push, node }; } // Module color function moduleColor(m) { const map = { orders: "#a78bfa", kalk: "#f472b6", "tg-bot": "#60a5fa", seo: "#34d399", auth: "#fb923c", docs: "#94a3b8", scaffold: "#cbd5e1", watchdog: "#ec4899", notifications: "#f59e0b", spec: "#a3e635", }; return map[m] || "#a1a1aa"; } function ModuleTag({ name, small }) { return ( {name} ); } // Tiny syntax-highlighted YAML function highlightYaml(src) { return src .split("\n") .map((ln) => { // comment let m = ln.match(/^(\s*)(#.*)$/); if (m) return `${m[1]}${escapeHtml(m[2])}`; // key: value m = ln.match(/^(\s*-?\s*)([A-Za-z_][\w-]*)(\s*:\s*)(.*)$/); if (m) { const val = m[4]; const valHtml = val ? (val.startsWith('"') || val.startsWith("'") ? `${escapeHtml(val)}` : (/^-?\d+(\.\d+)?$/.test(val) ? `${escapeHtml(val)}` : escapeHtml(val))) : ""; return `${escapeHtml(m[1])}${escapeHtml(m[2])}${escapeHtml(m[3])}${valHtml}`; } return escapeHtml(ln); }) .join("\n"); } function escapeHtml(s) { return s.replace(/[&<>]/g, (c) => ({ "&": "&", "<": "<", ">": ">" }[c])); } function YamlBlock({ code }) { return
;
}

// Tiny markdown renderer (headings, lists, paragraphs, inline `code`).
// We highlight individual "points" — lines/blocks corresponding to subsections —
// by injecting className when a section title matches a known reference.
function renderMarkdown(src, highlights = {}) {
  // highlights: { "3.1": "active", "1.2": "alert" }
  const lines = src.split("\n");
  const out = [];
  let i = 0;
  let currentPt = null; // section like "3.1" or "1"
  let currentTone = null;

  const pushP = (chunk) => {
    const html = chunk
      .replace(/`([^`]+)`/g, '$1')
      .replace(/\*\*([^*]+)\*\*/g, '$1');
    out.push(html);
  };

  while (i < lines.length) {
    const ln = lines[i];
    // Headings — h2 (## …), h3 (### …)
    let m = ln.match(/^##\s+(\d+(?:\.\d+)?)\.\s+(.*)$/);
    if (m) {
      const pt = m[1];
      const title = m[2];
      const tone = highlights[pt];
      currentPt = pt;
      currentTone = tone;
      const ptClass = tone === "active" ? "pt pt-active" : tone === "alert" ? "pt pt-alert" : "pt";
      out.push(`

${pt}. ${escapeHtml(title)}

`); i++; // collect content until next h2/h3 or eof let buf = []; while (i < lines.length && !lines[i].match(/^##\s+\d/) && !lines[i].match(/^###\s+\d/) ) { buf.push(lines[i]); i++; } out.push(renderBlock(buf.join("\n"))); out.push(`
`); continue; } m = ln.match(/^###\s+(\d+\.\d+)\s+(.*)$/); if (m) { const pt = m[1]; const title = m[2]; const tone = highlights[pt]; const ptClass = tone === "active" ? "pt pt-active" : tone === "alert" ? "pt pt-alert" : "pt"; out.push(`

${pt} ${escapeHtml(title)}

`); i++; let buf = []; while (i < lines.length && !lines[i].match(/^##\s+\d/) && !lines[i].match(/^###\s+\d/)) { buf.push(lines[i]); i++; } out.push(renderBlock(buf.join("\n"))); out.push(`
`); continue; } if (ln.startsWith("# ")) { out.push(`

${escapeHtml(ln.slice(2))}

`); i++; continue; } // skip blank i++; } return out.join("\n"); } function renderBlock(src) { const lines = src.split("\n"); let html = ""; let listOpen = false; let para = []; const flushPara = () => { if (para.length) { html += `

${para.join(" ") .replace(/`([^`]+)`/g, '$1') .replace(/\*\*([^*]+)\*\*/g, '$1')}

`; para = []; } }; const closeList = () => { if (listOpen) { html += ""; listOpen = false; } }; for (const ln of lines) { if (/^\s*-\s+/.test(ln)) { flushPara(); if (!listOpen) { html += "
    "; listOpen = true; } html += `
  • ${ln.replace(/^\s*-\s+/, "") .replace(/`([^`]+)`/g, '$1') .replace(/\*\*([^*]+)\*\*/g, '$1')}
  • `; continue; } closeList(); if (ln.trim() === "") { flushPara(); } else { para.push(escapeInline(ln)); } } closeList(); flushPara(); return html; } function escapeInline(s) { return s.replace(/[<>]/g, (c) => ({ "<": "<", ">": ">" }[c])); } function Md({ src, highlights }) { return
    ; } Object.assign(window, { Avatar, SeverityChip, SeverityBar, Sparkline, Bars, Gauge, KindIcon, kindColor, kindLabel, TaskRef, SidePanel, ConfirmDialog, useToasts, ModuleTag, moduleColor, YamlBlock, Md, });