// EpochCore · Knowledge Base — the grounding source for the front-desk agent.
// Everything the front desk is allowed to say lives here: add a URL, drop files,
// write text, or sync a folder. Each source is chunked, embedded and grounded —
// nothing the agent says comes from outside this set. (Prototype · in-browser only.)

const KB_TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "face": "light",
  "density": "regular",
  "accent": "#6a3df0",
  "groundingBanner": "on"
}/*EDITMODE-END*/;

window.__mountKB = function (DS) {
const { Badge, Button, Input } = DS;
const { useState, useEffect, useRef, useMemo } = React;

const MONO = "var(--font-mono)";
const SERIF = "var(--font-serif)";
const RULE = "1px solid var(--line)";
const RADIUS = "var(--radius)";
const CAP = 21 * 1024 * 1024; // 21.0 MB RAG cap (free tier)

// ── helpers ──────────────────────────────────────────────
function fmtBytes(n) {
  if (n == null) return "—";
  if (n === 0) return "0 B";
  if (n >= 1048576) return (n / 1048576).toFixed(1) + " MB";
  if (n >= 1024) return (n / 1024).toFixed(1) + " kB";
  return n + " B";
}
function fmtDate(d) {
  return d.toLocaleString("en-US", { month: "short", day: "numeric", year: "numeric", hour: "numeric", minute: "2-digit" });
}
function uid() { return "k" + Math.random().toString(36).slice(2, 9); }
function byteLen(s) { try { return new Blob([s || ""]).size; } catch (e) { return (s || "").length; } }
function hostFromUrl(u) {
  try { const x = new URL(/^https?:/.test(u) ? u : "https://" + u); return (x.hostname.replace(/^www\./, "") + (x.pathname !== "/" ? x.pathname : "")).replace(/\/$/, ""); }
  catch (e) { return u.replace(/^https?:\/\//, "").replace(/\/$/, ""); }
}

// ── icons (simple line glyphs, stroke = currentColor) ────
function Svg(p) { return <svg width={p.s || 18} height={p.s || 18} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={p.w || 1.7} strokeLinecap="round" strokeLinejoin="round">{p.children}</svg>; }
function IconGlobe(p) { return <Svg s={p.s}><circle cx="12" cy="12" r="9" /><path d="M3 12h18M12 3c2.5 2.6 2.5 15.4 0 18M12 3c-2.5 2.6-2.5 15.4 0 18" /></Svg>; }
function IconFile(p) { return <Svg s={p.s}><path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z" /><path d="M14 3v5h5" /></Svg>; }
function IconText(p) { return <Svg s={p.s}><path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z" /><path d="M14 3v5h5" /><path d="M8.5 13h7M8.5 16.5h5" /></Svg>; }
function IconFolder(p) { return <Svg s={p.s}><path d="M3 7a2 2 0 0 1 2-2h4l2 2.4h8a2 2 0 0 1 2 2V18a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" /></Svg>; }
function IconFolderPlus(p) { return <Svg s={p.s}><path d="M3 7a2 2 0 0 1 2-2h4l2 2.4h8a2 2 0 0 1 2 2V18a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" /><path d="M12 11v5M9.5 13.5h5" /></Svg>; }
function IconTextNew(p) { return <Svg s={p.s}><path d="M5 6h14M5 6v-.5M9 6v12M9 18H6.5M9 18h2.5" /><path d="M15 11h5M17.5 11v7M17.5 18H16M17.5 18h1.5" /></Svg>; }
function IconSync(p) { return <Svg s={p.s}><path d="M3 7a2 2 0 0 1 2-2h4l2 2.4h8a2 2 0 0 1 2 2V18a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" /><path d="M9 13.5a3 3 0 0 1 5-1.2l1 1M15 14.5a3 3 0 0 1-5 1.2l-1-1" /><path d="M15 11.5v1.8h-1.8M9 16v-1.8h1.8" /></Svg>; }
function IconSearch(p) { return <Svg s={p.s}><circle cx="11" cy="11" r="7" /><path d="M21 21l-4.3-4.3" /></Svg>; }
function IconDots(p) { return <Svg s={p.s}><circle cx="5" cy="12" r="1" /><circle cx="12" cy="12" r="1" /><circle cx="19" cy="12" r="1" /></Svg>; }
function IconBook(p) { return <Svg s={p.s}><path d="M4 5a2 2 0 0 1 2-2h6v16H6a2 2 0 0 0-2 2z" /><path d="M20 5a2 2 0 0 0-2-2h-6v16h6a2 2 0 0 1 2 2z" /></Svg>; }
function IconUrlNew(p) { return <Svg s={p.s}><circle cx="12" cy="12" r="9" /><path d="M3 12h18M12 3c2.5 2.6 2.5 15.4 0 18M12 3c-2.5 2.6-2.5 15.4 0 18" /></Svg>; }
function IconFileNew(p) { return <Svg s={p.s}><path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z" /><path d="M14 3v5h5" /><path d="M12 18v-5M9.5 15.5 12 13l2.5 2.5" /></Svg>; }
function IconX(p) { return <Svg s={p.s}><path d="M6 6l12 12M18 6 6 18" /></Svg>; }
function IconTrash(p) { return <Svg s={p.s} w={1.6}><path d="M4 7h16M9 7V5a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2M6 7l1 13a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1l1-13" /></Svg>; }
function IconReindex(p) { return <Svg s={p.s} w={1.6}><path d="M20 11a8 8 0 1 0-.5 4" /><path d="M20 4v5h-5" /></Svg>; }
function IconOpen(p) { return <Svg s={p.s} w={1.6}><path d="M14 4h6v6M20 4l-9 9M18 13v5a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h5" /></Svg>; }
function IconRename(p) { return <Svg s={p.s} w={1.6}><path d="M12 20h9" /><path d="M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4z" /></Svg>; }

const TYPES = {
  text:   { label: "Text",   Icon: IconText },
  url:    { label: "URL",    Icon: IconGlobe },
  file:   { label: "File",   Icon: IconFile },
  folder: { label: "Folder", Icon: IconFolder },
};

// ── seed (the screenshot's Dear Dad's grounding set) ─────
const SEED_DATE = new Date(2026, 5, 28, 0, 50);
function seed() {
  return [
    { id: uid(), name: "Dear Dad's identity, story, FAQ and links", type: "text", size: 7100, creator: "John Ryan", updated: SEED_DATE, indexed: true, preview: "Dear Dad's is a family BBQ joint in Cornelius, NC. Tone: warm, Southern, unhurried. FAQ covers hours, reservations, catering, allergens, parking, and the story behind the name." },
    { id: uid(), name: "Sona recommendation engine", type: "text", size: 23900, creator: "John Ryan", updated: SEED_DATE, indexed: true, preview: "Pairing logic that suggests sides, drinks and desserts from the live menu. Only recommends items currently in stock; never invents a dish or a price." },
    { id: uid(), name: "Dear Dad's full menu with prices", type: "text", size: 13800, creator: "John Ryan", updated: SEED_DATE, indexed: true, preview: "Brisket plate $18 · Pulled pork sandwich $12 · Half rack ribs $24 · Mac & cheese $6 · Banana pudding $7. Prices are the ONLY ones the front desk may quote." },
  ];
}

// ── small shared bits ────────────────────────────────────
function Lab({ children, style }) {
  return <span style={{ fontFamily: MONO, fontSize: 10, letterSpacing: "1px", textTransform: "uppercase", color: "var(--mute)", ...style }}>{children}</span>;
}
function TypePill({ type }) {
  const t = TYPES[type] || TYPES.file; const I = t.Icon;
  return (
    <span style={{ display: "inline-flex", alignItems: "center", gap: 6, fontFamily: MONO, fontSize: 9, letterSpacing: ".6px", textTransform: "uppercase", color: "var(--dim)", border: RULE, borderRadius: 999, padding: "2px 8px 2px 6px" }}>
      <span style={{ color: "var(--accent)", display: "inline-flex" }}><I s={11} /></span>{t.label}
    </span>
  );
}

// ── storage meter ────────────────────────────────────────
function StorageMeter({ used }) {
  const pct = Math.max(used > 0 ? 1.5 : 0, Math.min(100, (used / CAP) * 100));
  const tone = pct > 95 ? "var(--err)" : pct > 80 ? "var(--warn)" : "var(--ok)";
  return (
    <div style={{ minWidth: 248, background: "var(--field)", border: RULE, borderRadius: RADIUS, padding: "10px 14px" }}>
      <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12 }}>
        <span style={{ display: "inline-flex", alignItems: "center", gap: 7 }}>
          <span style={{ width: 7, height: 7, borderRadius: "50%", background: tone, boxShadow: "0 0 7px " + tone }} />
          <Lab>RAG Storage</Lab>
        </span>
        <span style={{ fontFamily: MONO, fontSize: 12, color: "var(--ink)", fontVariantNumeric: "tabular-nums" }}>
          <b style={{ fontWeight: 700 }}>{fmtBytes(used)}</b><span style={{ color: "var(--mute)" }}> / 21.0 MB</span>
        </span>
      </div>
      <div style={{ marginTop: 9, height: 5, borderRadius: 999, background: "var(--surface-2)", overflow: "hidden" }}>
        <div style={{ width: pct + "%", height: "100%", background: tone, borderRadius: 999, transition: "width .5s cubic-bezier(.2,.8,.3,1)" }} />
      </div>
    </div>
  );
}

// ── action card ──────────────────────────────────────────
function ActionCard({ Icon, label, hint, onClick }) {
  return (
    <button className="kb-action" onClick={onClick} style={{
      textAlign: "left", cursor: "pointer", background: "var(--field)", border: RULE, borderRadius: RADIUS,
      padding: "16px 16px 15px", display: "flex", flexDirection: "column", gap: 12, font: "inherit", color: "var(--ink)",
    }}>
      <span className="kb-action-tile" style={{ width: 38, height: 38, borderRadius: RADIUS, border: RULE, background: "var(--surface)", display: "flex", alignItems: "center", justifyContent: "center", color: "var(--ink)" }}><Icon s={19} /></span>
      <span>
        <span style={{ display: "block", fontFamily: SERIF, fontSize: 16.5, fontWeight: 600, color: "var(--ink)", lineHeight: 1.1 }}>{label}</span>
        <span style={{ display: "block", marginTop: 5, fontFamily: MONO, fontSize: 9, letterSpacing: ".7px", textTransform: "uppercase", color: "var(--mute)" }}>{hint}</span>
      </span>
    </button>
  );
}

// ── filter chip + popover ────────────────────────────────
function Chip({ children, active, onClick }) {
  return (
    <button className="kb-chip" onClick={onClick} style={{
      fontFamily: MONO, fontSize: 11, letterSpacing: ".4px", color: active ? "var(--accent)" : "var(--dim)",
      background: active ? "var(--accent-soft)" : "var(--field)", border: "1px solid " + (active ? "var(--accent-line)" : "var(--line)"),
      borderRadius: 999, padding: "5px 11px", cursor: "pointer", display: "inline-flex", alignItems: "center", gap: 6, whiteSpace: "nowrap",
    }}>{children}</button>
  );
}
function Popover({ items, value, onPick, onClose }) {
  const ref = useRef(null);
  useEffect(() => {
    function h(e) { if (ref.current && !ref.current.contains(e.target)) onClose(); }
    document.addEventListener("mousedown", h); return () => document.removeEventListener("mousedown", h);
  }, []);
  return (
    <div ref={ref} style={{ position: "absolute", top: "calc(100% + 6px)", left: 0, zIndex: 30, minWidth: 168, background: "var(--field)", border: RULE, borderRadius: RADIUS, boxShadow: "var(--shadow-lg, 0 12px 32px rgba(20,14,8,.18))", padding: 5 }}>
      {items.map((it) => (
        <button key={it.v} className="kb-menuitem" onClick={() => { onPick(it.v); onClose(); }} style={{
          width: "100%", textAlign: "left", border: "none", background: value === it.v ? "var(--accent-soft)" : "transparent", cursor: "pointer",
          fontFamily: SERIF, fontSize: 14, color: "var(--ink)", padding: "8px 10px", borderRadius: 4, display: "flex", alignItems: "center", justifyContent: "space-between", gap: 10,
        }}>
          <span style={{ display: "inline-flex", alignItems: "center", gap: 8 }}>{it.icon}{it.l}</span>
          {it.n != null && <span style={{ fontFamily: MONO, fontSize: 10.5, color: "var(--mute)" }}>{it.n}</span>}
        </button>
      ))}
    </div>
  );
}

// ── dropzone (Add Files) ─────────────────────────────────
function Dropzone({ onFiles }) {
  const [hot, setHot] = useState(false);
  const ref = useRef(null);
  return (
    <div onClick={() => ref.current && ref.current.click()}
      onDragOver={(e) => { e.preventDefault(); setHot(true); }}
      onDragLeave={() => setHot(false)}
      onDrop={(e) => { e.preventDefault(); setHot(false); const f = [...(e.dataTransfer.files || [])]; if (f.length) onFiles(f); }}
      style={{ border: "1.5px dashed " + (hot ? "var(--accent)" : "var(--accent-line)"), borderRadius: RADIUS, background: hot ? "color-mix(in srgb, var(--accent) 7%, var(--field))" : "var(--accent-soft)", padding: "34px 20px", textAlign: "center", cursor: "pointer", transition: "border-color .18s, background .18s" }}>
      <input ref={ref} type="file" multiple style={{ display: "none" }} onChange={(e) => { const f = [...(e.target.files || [])]; e.target.value = ""; if (f.length) onFiles(f); }} />
      <span style={{ width: 46, height: 46, margin: "0 auto", borderRadius: RADIUS, background: "var(--field)", border: RULE, display: "flex", alignItems: "center", justifyContent: "center", color: "var(--accent)" }}><IconFileNew s={22} /></span>
      <div style={{ marginTop: 11, fontFamily: SERIF, fontSize: 16, color: "var(--ink)" }}>Drop files, or click to browse</div>
      <div style={{ marginTop: 4, fontFamily: MONO, fontSize: 9.5, letterSpacing: ".5px", color: "var(--mute)", textTransform: "uppercase" }}>PDF · DOCX · CSV · TXT · MD — up to 21 MB total</div>
    </div>
  );
}

// ── modal shell ──────────────────────────────────────────
function Modal({ title, sub, onClose, children, width }) {
  useEffect(() => {
    function k(e) { if (e.key === "Escape") onClose(); }
    document.addEventListener("keydown", k); return () => document.removeEventListener("keydown", k);
  }, []);
  return (
    <div onMouseDown={onClose} style={{ position: "fixed", inset: 0, zIndex: 100, background: "color-mix(in srgb, var(--ink) 34%, transparent)", backdropFilter: "blur(3px)", display: "flex", alignItems: "flex-start", justifyContent: "center", padding: "8vh 18px 18px", overflowY: "auto" }}>
      <div onMouseDown={(e) => e.stopPropagation()} style={{ width: "100%", maxWidth: width || 520, background: "var(--bg)", border: RULE, borderRadius: RADIUS, boxShadow: "0 24px 64px rgba(20,14,8,.30)", overflow: "hidden", animation: "kbrise .2s ease both" }}>
        <div style={{ display: "flex", alignItems: "flex-start", justifyContent: "space-between", gap: 16, padding: "18px 20px 14px", borderBottom: RULE }}>
          <div>
            <div style={{ fontFamily: SERIF, fontSize: 21, fontWeight: 700, color: "var(--ink)", letterSpacing: "-.3px" }}>{title}</div>
            {sub && <div style={{ marginTop: 3, fontFamily: MONO, fontSize: 10, letterSpacing: ".4px", textTransform: "uppercase", color: "var(--mute)" }}>{sub}</div>}
          </div>
          <button onClick={onClose} aria-label="Close" style={{ flex: "none", width: 30, height: 30, borderRadius: RADIUS, border: RULE, background: "var(--field)", color: "var(--dim)", cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center" }}><IconX s={16} /></button>
        </div>
        <div style={{ padding: "18px 20px 20px" }}>{children}</div>
      </div>
    </div>
  );
}

// ── composers ────────────────────────────────────────────
function AddUrlBody({ onAdd, onClose }) {
  const [url, setUrl] = useState("");
  const [crawl, setCrawl] = useState(true);
  const [busy, setBusy] = useState(false);
  const ok = url.trim().length > 3;
  function go() {
    if (!ok || busy) return; setBusy(true);
    setTimeout(() => {
      const name = hostFromUrl(url.trim());
      onAdd({ name: name, type: "url", size: 4000 + Math.floor(Math.random() * 11000), preview: "Crawled from " + name + (crawl ? " (and linked pages)" : "") + ". Text extracted, chunked and embedded for grounding." });
      onClose();
    }, 750);
  }
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
      <Input label="Business URL or any link" placeholder="dear-dads.com/menu" value={url} autoFocus onChange={(e) => setUrl(e.target.value)} onKeyDown={(e) => e.key === "Enter" && go()} />
      <label style={{ display: "flex", alignItems: "center", gap: 10, cursor: "pointer" }}>
        <span onClick={() => setCrawl(!crawl)} style={{ width: 38, height: 22, flex: "none", borderRadius: 999, border: RULE, background: crawl ? "var(--accent)" : "var(--surface-2)", position: "relative", transition: "background .18s" }}>
          <span style={{ position: "absolute", top: 2, left: crawl ? 18 : 2, width: 16, height: 16, borderRadius: "50%", background: "#fff", transition: "left .18s", boxShadow: "0 1px 3px rgba(0,0,0,.25)" }} />
        </span>
        <span style={{ fontFamily: SERIF, fontSize: 14, color: "var(--ink)" }}>Crawl pages linked from this URL</span>
      </label>
      <div style={{ fontFamily: MONO, fontSize: 10.5, color: "var(--mute)", lineHeight: 1.6, borderTop: RULE, paddingTop: 12 }}>
        Only text on pages you add here is grounded. The front desk never browses the open web at answer time.
      </div>
      <div style={{ display: "flex", gap: 10, justifyContent: "flex-end" }}>
        <Button variant="ghost" size="sm" icon={null} onClick={onClose}>Cancel</Button>
        <Button variant="primary" size="sm" icon={null} onClick={go} disabled={!ok || busy} style={{ opacity: !ok || busy ? .5 : 1 }}>{busy ? "Fetching…" : "Fetch & add"}</Button>
      </div>
    </div>
  );
}
function CreateTextBody({ onAdd, onClose, initial }) {
  const [title, setTitle] = useState(initial ? initial.name : "");
  const [body, setBody] = useState(initial ? initial.preview || "" : "");
  const ok = title.trim().length > 0;
  function go() {
    if (!ok) return;
    onAdd({ name: title.trim(), type: "text", size: Math.max(byteLen(body), 1), preview: body.slice(0, 240) }, initial && initial.id);
    onClose();
  }
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
      <Input label="Title" placeholder="Hours, holidays & closures" value={title} autoFocus onChange={(e) => setTitle(e.target.value)} />
      <Input label="Content" textarea placeholder="Write exactly what the front desk may say. It will quote only what's here." value={body} onChange={(e) => setBody(e.target.value)} style={{ minHeight: 150, resize: "vertical", lineHeight: 1.6 }} />
      <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12 }}>
        <Lab>{fmtBytes(byteLen(body))}</Lab>
        <span style={{ display: "flex", gap: 10 }}>
          <Button variant="ghost" size="sm" icon={null} onClick={onClose}>Cancel</Button>
          <Button variant="primary" size="sm" icon={null} onClick={go} disabled={!ok} style={{ opacity: ok ? 1 : .5 }}>{initial ? "Save" : "Create source"}</Button>
        </span>
      </div>
    </div>
  );
}
function CreateFolderBody({ onAdd, onClose, initial }) {
  const [name, setName] = useState(initial ? initial.name : "");
  const ok = name.trim().length > 0;
  function go() { if (!ok) return; onAdd({ name: name.trim(), type: "folder", size: 0 }, initial && initial.id); onClose(); }
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
      <Input label={initial ? "Rename" : "Folder name"} placeholder="Catering & events" value={name} autoFocus onChange={(e) => setName(e.target.value)} onKeyDown={(e) => e.key === "Enter" && go()} />
      <div style={{ display: "flex", gap: 10, justifyContent: "flex-end" }}>
        <Button variant="ghost" size="sm" icon={null} onClick={onClose}>Cancel</Button>
        <Button variant="primary" size="sm" icon={null} onClick={go} disabled={!ok} style={{ opacity: ok ? 1 : .5 }}>{initial ? "Save" : "Create folder"}</Button>
      </div>
    </div>
  );
}
const CONNECTORS = [
  { k: "gdrive", l: "Google Drive", d: "Docs, Sheets & PDFs" },
  { k: "dropbox", l: "Dropbox", d: "Shared folders" },
  { k: "onedrive", l: "OneDrive", d: "SharePoint & Office" },
  { k: "s3", l: "S3 bucket", d: "Raw object store" },
];
function SyncFolderBody({ onAdd, onClose }) {
  const [busy, setBusy] = useState(null);
  function pick(c) {
    if (busy) return; setBusy(c.k);
    setTimeout(() => {
      const files = 5 + Math.floor(Math.random() * 18);
      onAdd({ name: c.l + " — synced", type: "folder", size: files * (12000 + Math.floor(Math.random() * 40000)), preview: files + " files synced from " + c.l + ". Re-syncs automatically when sources change." });
      onClose();
    }, 950);
  }
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
      <div style={{ fontFamily: MONO, fontSize: 10.5, color: "var(--mute)", lineHeight: 1.6, marginBottom: 2 }}>Connect a source folder. New and changed files re-sync and re-embed on their own.</div>
      {CONNECTORS.map((c) => (
        <button key={c.k} className="kb-connector" onClick={() => pick(c)} disabled={!!busy} style={{
          textAlign: "left", cursor: busy ? "default" : "pointer", background: "var(--field)", border: RULE, borderRadius: RADIUS, padding: "13px 14px",
          display: "flex", alignItems: "center", gap: 12, font: "inherit",
        }}>
          <span style={{ width: 34, height: 34, flex: "none", borderRadius: RADIUS, border: RULE, background: "var(--surface)", display: "flex", alignItems: "center", justifyContent: "center", color: "var(--accent)" }}><IconFolder s={18} /></span>
          <span style={{ flex: 1, minWidth: 0 }}>
            <span style={{ display: "block", fontFamily: SERIF, fontSize: 15, color: "var(--ink)" }}>{c.l}</span>
            <span style={{ display: "block", fontFamily: MONO, fontSize: 9.5, letterSpacing: ".4px", textTransform: "uppercase", color: "var(--mute)", marginTop: 2 }}>{c.d}</span>
          </span>
          {busy === c.k
            ? <span style={{ width: 16, height: 16, border: "2px solid var(--accent-line)", borderTopColor: "var(--accent)", borderRadius: "50%", animation: "kbspin .7s linear infinite" }} />
            : <span style={{ fontFamily: MONO, fontSize: 10.5, letterSpacing: ".5px", textTransform: "uppercase", color: "var(--accent)" }}>Connect →</span>}
        </button>
      ))}
    </div>
  );
}
function DetailBody({ doc, onClose }) {
  const t = TYPES[doc.type] || TYPES.file;
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
      <div style={{ display: "flex", gap: 12, alignItems: "center", flexWrap: "wrap" }}>
        <TypePill type={doc.type} />
        <Badge tone={doc.indexed === false ? "amber" : "ok"} dot>{doc.indexed === false ? "Indexing" : "Grounded"}</Badge>
      </div>
      <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "12px 18px" }}>
        <div><Lab>Size</Lab><div style={{ fontFamily: MONO, fontSize: 13, color: "var(--ink)", marginTop: 4 }}>{fmtBytes(doc.size)}</div></div>
        <div><Lab>Created by</Lab><div style={{ fontFamily: SERIF, fontSize: 14, color: "var(--ink)", marginTop: 4 }}>{doc.creator}</div></div>
        <div><Lab>Last updated</Lab><div style={{ fontFamily: MONO, fontSize: 12.5, color: "var(--dim)", marginTop: 4 }}>{fmtDate(doc.updated)}</div></div>
        <div><Lab>Type</Lab><div style={{ fontFamily: SERIF, fontSize: 14, color: "var(--ink)", marginTop: 4 }}>{t.label}</div></div>
      </div>
      {doc.preview && (
        <div style={{ borderTop: RULE, paddingTop: 12 }}>
          <Lab>Grounded content</Lab>
          <div style={{ marginTop: 7, fontFamily: SERIF, fontSize: 14, color: "var(--dim)", lineHeight: 1.65, background: "var(--field)", border: RULE, borderRadius: RADIUS, padding: "12px 14px" }}>{doc.preview}</div>
        </div>
      )}
      <div style={{ display: "flex", justifyContent: "flex-end" }}>
        <Button variant="primary" size="sm" icon={null} onClick={onClose}>Close</Button>
      </div>
    </div>
  );
}

// ── document row ─────────────────────────────────────────
const COLS = "34px minmax(0,1fr) 200px 220px 44px";
function Checkbox({ on, onClick, indeterminate }) {
  return (
    <button onClick={(e) => { e.stopPropagation(); onClick(); }} aria-label="select" style={{
      width: 18, height: 18, borderRadius: 4, border: "1.5px solid " + (on || indeterminate ? "var(--accent)" : "var(--line2)"),
      background: on || indeterminate ? "var(--accent)" : "var(--field)", cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center", padding: 0,
    }}>
      {on && <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="3.4" strokeLinecap="round" strokeLinejoin="round"><path d="m5 13 4 4L19 7" /></svg>}
      {indeterminate && !on && <span style={{ width: 9, height: 2.4, background: "#fff", borderRadius: 2 }} />}
    </button>
  );
}
function DocRow({ doc, selected, onSelect, onOpen, menuOpen, onMenuToggle, onMenu, last }) {
  const t = TYPES[doc.type] || TYPES.file; const I = t.Icon;
  return (
    <div className="kb-row" style={{ display: "grid", gridTemplateColumns: COLS, alignItems: "center", borderBottom: last ? "none" : RULE, background: selected ? "var(--accent-soft)" : doc._fresh ? undefined : "transparent", animation: doc._fresh ? "kbfresh 2.2s ease both" : "none", transition: "background .15s" }}>
      <div style={{ padding: "0 0 0 4px", display: "flex", alignItems: "center", justifyContent: "center" }}><Checkbox on={selected} onClick={onSelect} /></div>
      <div onClick={onOpen} style={{ padding: "13px 14px 13px 4px", minWidth: 0, cursor: "pointer", display: "flex", alignItems: "center", gap: 12 }}>
        <span style={{ width: 30, height: 30, flex: "none", borderRadius: RADIUS, border: RULE, background: "var(--field)", display: "flex", alignItems: "center", justifyContent: "center", color: doc.type === "folder" ? "var(--accent)" : "var(--dim)" }}><I s={16} /></span>
        <span style={{ minWidth: 0 }}>
          <span style={{ display: "block", fontFamily: SERIF, fontSize: 15.5, color: "var(--ink)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", fontWeight: 500 }}>{doc.name}</span>
          <span style={{ display: "flex", alignItems: "center", gap: 8, marginTop: 2 }}>
            <span style={{ fontFamily: MONO, fontSize: 10.5, color: "var(--mute)" }}>{doc.type === "folder" ? "Folder" : fmtBytes(doc.size)}</span>
            {doc.indexed === false && <span style={{ fontFamily: MONO, fontSize: 9, letterSpacing: ".4px", textTransform: "uppercase", color: "var(--warn)" }}>· indexing</span>}
          </span>
        </span>
      </div>
      <div className="kb-col-creator" style={{ padding: "0 12px", fontFamily: SERIF, fontSize: 14, color: "var(--dim)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{doc.creator}</div>
      <div className="kb-col-updated" style={{ padding: "0 12px", fontFamily: MONO, fontSize: 11.5, color: "var(--mute)", whiteSpace: "nowrap", fontVariantNumeric: "tabular-nums" }}>{fmtDate(doc.updated)}</div>
      <div style={{ position: "relative", display: "flex", justifyContent: "center" }}>
        <button onClick={(e) => { e.stopPropagation(); onMenuToggle(); }} aria-label="actions" style={{ width: 30, height: 30, borderRadius: RADIUS, border: "none", background: menuOpen ? "var(--hi)" : "transparent", color: "var(--dim)", cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center" }}><IconDots s={18} /></button>
        {menuOpen && (
          <div style={{ position: "absolute", top: "calc(100% - 2px)", right: 0, zIndex: 40, minWidth: 156, background: "var(--field)", border: RULE, borderRadius: RADIUS, boxShadow: "0 12px 32px rgba(20,14,8,.20)", padding: 5 }}>
            {[
              { k: "open", l: "Open", icon: <IconOpen s={15} /> },
              { k: "rename", l: "Rename", icon: <IconRename s={15} /> },
              { k: "reindex", l: "Re-index", icon: <IconReindex s={15} /> },
              { k: "delete", l: "Delete", icon: <IconTrash s={15} />, danger: true },
            ].map((m) => (
              <button key={m.k} className="kb-menuitem" onClick={(e) => { e.stopPropagation(); onMenu(m.k); }} style={{
                width: "100%", textAlign: "left", border: "none", background: "transparent", cursor: "pointer",
                fontFamily: SERIF, fontSize: 14, color: m.danger ? "var(--err)" : "var(--ink)", padding: "8px 10px", borderRadius: 4, display: "flex", alignItems: "center", gap: 9,
              }}><span style={{ color: m.danger ? "var(--err)" : "var(--dim)", display: "inline-flex" }}>{m.icon}</span>{m.l}</button>
            ))}
          </div>
        )}
      </div>
    </div>
  );
}

// ── toast ────────────────────────────────────────────────
function Toasts({ items }) {
  return (
    <div style={{ position: "fixed", left: "50%", bottom: 28, transform: "translateX(-50%)", zIndex: 200, display: "flex", flexDirection: "column", gap: 8, alignItems: "center", pointerEvents: "none" }}>
      {items.map((t) => (
        <div key={t.id} style={{ display: "flex", alignItems: "center", gap: 9, background: "var(--ink)", color: "var(--on-ink)", borderRadius: 999, padding: "9px 16px", fontFamily: MONO, fontSize: 11.5, letterSpacing: ".3px", boxShadow: "0 8px 28px rgba(20,14,8,.30)", animation: "kbrise .2s ease both" }}>
          <span style={{ width: 6, height: 6, borderRadius: "50%", background: t.tone || "var(--ok)" }} />{t.msg}
        </div>
      ))}
    </div>
  );
}

// ── ROOT ─────────────────────────────────────────────────
function KBApp() {
  const [tw, setTweak] = useTweaks(KB_TWEAK_DEFAULTS);
  useEffect(() => {
    document.body.classList.toggle("dark", tw.face === "dark");
    document.body.dataset.density = tw.density;
    const r = document.documentElement.style;
    r.setProperty("--accent", tw.accent);
    r.setProperty("--accent-line", "color-mix(in srgb, " + tw.accent + " 38%, transparent)");
    r.setProperty("--accent-soft", "color-mix(in srgb, " + tw.accent + " 11%, transparent)");
  }, [tw.face, tw.density, tw.accent]);

  const [docs, setDocs] = useState(seed);
  const [q, setQ] = useState("");
  const [scope, setScope] = useState("title"); // title | content
  const [typeF, setTypeF] = useState(null);
  const [creatorF, setCreatorF] = useState(null);
  const [openPop, setOpenPop] = useState(null); // 'type' | 'creator'
  const [sel, setSel] = useState(() => new Set());
  const [modal, setModal] = useState(null); // {type, doc?}
  const [menuId, setMenuId] = useState(null);
  const [toasts, setToasts] = useState([]);

  function toast(msg, tone) {
    const id = uid();
    setToasts((t) => [...t, { id, msg, tone }]);
    setTimeout(() => setToasts((t) => t.filter((x) => x.id !== id)), 2400);
  }
  function addDoc(partial, editId) {
    if (editId) {
      setDocs((d) => d.map((x) => x.id === editId ? { ...x, ...partial, updated: new Date() } : x));
      toast("Saved");
      return;
    }
    const doc = { id: uid(), creator: "You", updated: new Date(), indexed: false, _fresh: true, ...partial };
    setDocs((d) => [doc, ...d]);
    toast(TYPES[doc.type] ? TYPES[doc.type].label + " added · indexing" : "Added");
    // finish "indexing" shortly after
    setTimeout(() => setDocs((d) => d.map((x) => x.id === doc.id ? { ...x, indexed: true, _fresh: false } : x)), 1500);
  }
  function addFiles(files) {
    const now = Date.now();
    const made = files.map((f, i) => ({ id: uid(), name: f.name, type: "file", size: f.size, creator: "You", updated: new Date(now + i), indexed: false, _fresh: true, preview: "Uploaded file · text extracted and embedded for grounding." }));
    setDocs((d) => [...made, ...d]);
    toast(files.length === 1 ? "File added · indexing" : files.length + " files added");
    const ids = new Set(made.map((m) => m.id));
    setTimeout(() => setDocs((d) => d.map((x) => ids.has(x.id) ? { ...x, indexed: true, _fresh: false } : x)), 1500);
    setModal(null);
  }
  function rowMenu(doc, action) {
    setMenuId(null);
    if (action === "open") setModal({ type: "detail", doc });
    else if (action === "rename") setModal({ type: doc.type === "text" ? "text" : "folder", doc });
    else if (action === "reindex") { setDocs((d) => d.map((x) => x.id === doc.id ? { ...x, indexed: false } : x)); toast("Re-indexing " + doc.name.slice(0, 22) + (doc.name.length > 22 ? "…" : "")); setTimeout(() => setDocs((d) => d.map((x) => x.id === doc.id ? { ...x, indexed: true } : x)), 1400); }
    else if (action === "delete") { setDocs((d) => d.filter((x) => x.id !== doc.id)); setSel((s) => { const n = new Set(s); n.delete(doc.id); return n; }); toast("Deleted", "var(--err)"); }
  }

  const used = useMemo(() => docs.reduce((a, d) => a + (d.size || 0), 0), [docs]);
  const creators = useMemo(() => [...new Set(docs.map((d) => d.creator))], [docs]);
  const filtered = useMemo(() => {
    const term = q.trim().toLowerCase();
    return docs.filter((d) => {
      if (typeF && d.type !== typeF) return false;
      if (creatorF && d.creator !== creatorF) return false;
      if (!term) return true;
      const hay = scope === "content" ? (d.name + " " + (d.preview || "")) : d.name;
      return hay.toLowerCase().includes(term);
    });
  }, [docs, q, scope, typeF, creatorF]);

  const allSel = filtered.length > 0 && filtered.every((d) => sel.has(d.id));
  const someSel = filtered.some((d) => sel.has(d.id));
  function toggleAll() { setSel((s) => { const n = new Set(s); if (allSel) filtered.forEach((d) => n.delete(d.id)); else filtered.forEach((d) => n.add(d.id)); return n; }); }
  function toggleOne(id) { setSel((s) => { const n = new Set(s); n.has(id) ? n.delete(id) : n.add(id); return n; }); }
  function bulkDelete() { setDocs((d) => d.filter((x) => !sel.has(x.id))); toast(sel.size + " deleted", "var(--err)"); setSel(new Set()); }
  function bulkReindex() { const ids = new Set(sel); setDocs((d) => d.map((x) => ids.has(x.id) ? { ...x, indexed: false } : x)); toast("Re-indexing " + sel.size + " sources"); setTimeout(() => setDocs((d) => d.map((x) => ids.has(x.id) ? { ...x, indexed: true } : x)), 1400); setSel(new Set()); }

  const ACTIONS = [
    { Icon: IconUrlNew, label: "Add URL", hint: "Website · menu · policies", on: () => setModal({ type: "url" }) },
    { Icon: IconFileNew, label: "Add Files", hint: "PDF · DOCX · CSV · TXT", on: () => setModal({ type: "files" }) },
    { Icon: IconTextNew, label: "Create Text", hint: "FAQ · script · hours", on: () => setModal({ type: "text" }) },
    { Icon: IconFolderPlus, label: "Create Folder", hint: "Group sources", on: () => setModal({ type: "folder" }) },
    { Icon: IconSync, label: "Sync Folder", hint: "Drive · Dropbox · S3", on: () => setModal({ type: "sync" }) },
  ];

  return (
    <div style={{ minHeight: "100vh", display: "flex", flexDirection: "column", fontFamily: SERIF }}>
      <TweaksPanel>
        <TweakSection label="Face" />
        <TweakRadio label="Theme" value={tw.face} options={["light", "dark"]} onChange={(v) => setTweak("face", v)} />
        <TweakRadio label="Density" value={tw.density} options={["compact", "regular"]} onChange={(v) => setTweak("density", v)} />
        <TweakSection label="Accent" />
        <TweakColor label="Accent" value={tw.accent} options={["#6a3df0", "#2a6fdb", "#1f8a5b", "#c2691c"]} onChange={(v) => setTweak("accent", v)} />
        <TweakSection label="Grounding" />
        <TweakRadio label="Grounding banner" value={tw.groundingBanner} options={["on", "off"]} onChange={(v) => setTweak("groundingBanner", v)} />
      </TweaksPanel>

      <div style={{ flex: 1, width: "100%", maxWidth: 1180, margin: "0 auto", padding: "40px 28px 56px" }}>
        {/* header */}
        <div style={{ display: "flex", alignItems: "flex-start", justifyContent: "space-between", gap: 24, flexWrap: "wrap" }}>
          <div style={{ minWidth: 0 }}>
            <Lab style={{ color: "var(--accent)", letterSpacing: "1.6px" }}>Front desk · grounding source</Lab>
            <div style={{ margin: "9px 0 0", display: "flex", alignItems: "center", gap: 12 }}>
              <span style={{ color: "var(--accent)", display: "inline-flex", flex: "none" }}><IconBook s={30} /></span>
              <h1 style={{ margin: 0, lineHeight: 1.04, fontFamily: SERIF, fontWeight: 800, fontSize: "clamp(30px,4vw,40px)", letterSpacing: "-1px", color: "var(--ink)" }}>Knowledge Base</h1>
            </div>
            <p style={{ margin: "12px 0 0", maxWidth: 540, fontFamily: SERIF, fontSize: 16, lineHeight: 1.55, color: "var(--dim)" }}>
              Everything your front desk is allowed to say lives here. Add a source and it's chunked, embedded, and grounded — <em style={{ color: "var(--accent)", fontStyle: "italic" }}>nothing else</em> reaches a caller.
            </p>
          </div>
          <StorageMeter used={used} />
        </div>

        {/* action cards */}
        <div className="kb-actions-grid" style={{ marginTop: 26, display: "grid", gridTemplateColumns: "repeat(5,1fr)", gap: 12 }}>
          {ACTIONS.map((a) => <ActionCard key={a.label} Icon={a.Icon} label={a.label} hint={a.hint} onClick={a.on} />)}
        </div>

        {/* search + scope */}
        <div style={{ marginTop: 22, display: "flex", gap: 12, alignItems: "stretch", flexWrap: "wrap" }}>
          <div style={{ flex: 1, minWidth: 240, position: "relative", display: "flex", alignItems: "center" }}>
            <span style={{ position: "absolute", left: 14, color: "var(--mute)", display: "flex", pointerEvents: "none" }}><IconSearch s={17} /></span>
            <input value={q} onChange={(e) => setQ(e.target.value)} placeholder="Search knowledge base…" style={{
              width: "100%", height: 46, padding: "0 14px 0 42px", fontFamily: SERIF, fontSize: 15, color: "var(--ink)",
              background: "var(--field)", border: RULE, borderRadius: RADIUS, outline: "none",
            }} onFocus={(e) => e.target.style.borderColor = "var(--accent)"} onBlur={(e) => e.target.style.borderColor = "var(--line)"} />
          </div>
          <div style={{ display: "inline-flex", border: RULE, borderRadius: RADIUS, overflow: "hidden", flex: "none" }}>
            {[{ k: "title", l: "Title" }, { k: "content", l: "Content" }].map((s, i) => (
              <button key={s.k} onClick={() => setScope(s.k)} style={{
                fontFamily: MONO, fontSize: 11, letterSpacing: ".5px", textTransform: "uppercase", padding: "0 16px", height: 46, minWidth: 78,
                border: "none", borderLeft: i ? RULE : "none", cursor: "pointer",
                background: scope === s.k ? "var(--accent)" : "var(--field)", color: scope === s.k ? "#fff" : "var(--dim)",
              }}>{s.l}</button>
            ))}
          </div>
        </div>

        {/* filters */}
        <div style={{ marginTop: 14, display: "flex", gap: 9, alignItems: "center", flexWrap: "wrap" }}>
          <div style={{ position: "relative" }}>
            <Chip active={!!typeF} onClick={() => setOpenPop(openPop === "type" ? null : "type")}>{typeF ? "Type · " + TYPES[typeF].label : "+ Type"}{typeF && <span onClick={(e) => { e.stopPropagation(); setTypeF(null); }} style={{ display: "inline-flex" }}><IconX s={12} /></span>}</Chip>
            {openPop === "type" && <Popover value={typeF} onClose={() => setOpenPop(null)} onPick={setTypeF} items={Object.keys(TYPES).map((k) => ({ v: k, l: TYPES[k].label, icon: <span style={{ color: "var(--accent)", display: "inline-flex" }}>{React.createElement(TYPES[k].Icon, { s: 14 })}</span>, n: docs.filter((d) => d.type === k).length }))} />}
          </div>
          <div style={{ position: "relative" }}>
            <Chip active={!!creatorF} onClick={() => setOpenPop(openPop === "creator" ? null : "creator")}>{creatorF ? "Creator · " + creatorF : "+ Creator"}{creatorF && <span onClick={(e) => { e.stopPropagation(); setCreatorF(null); }} style={{ display: "inline-flex" }}><IconX s={12} /></span>}</Chip>
            {openPop === "creator" && <Popover value={creatorF} onClose={() => setOpenPop(null)} onPick={setCreatorF} items={creators.map((c) => ({ v: c, l: c, n: docs.filter((d) => d.creator === c).length }))} />}
          </div>
          <div style={{ flex: 1 }} />
          <Lab>{filtered.length} of {docs.length} source{docs.length === 1 ? "" : "s"}</Lab>
        </div>

        {/* table */}
        <div style={{ marginTop: 12, border: RULE, borderRadius: RADIUS, overflow: "hidden", background: "var(--field)" }}>
          {someSel ? (
            <div style={{ display: "flex", alignItems: "center", gap: 14, padding: "10px 14px", borderBottom: "1.5px solid var(--ink)", background: "var(--accent-soft)" }}>
              <Checkbox on={allSel} indeterminate={someSel && !allSel} onClick={toggleAll} />
              <span style={{ fontFamily: MONO, fontSize: 11.5, letterSpacing: ".4px", textTransform: "uppercase", color: "var(--accent)" }}>{[...sel].filter((id) => filtered.some((d) => d.id === id)).length} selected</span>
              <div style={{ flex: 1 }} />
              <Button variant="ghost" size="sm" icon={null} onClick={bulkReindex}>Re-index</Button>
              <Button variant="ghost" size="sm" icon={null} onClick={bulkDelete} style={{ color: "var(--err)" }}>Delete</Button>
            </div>
          ) : (
            <div style={{ display: "grid", gridTemplateColumns: COLS, alignItems: "center", padding: "11px 0", borderBottom: "1.5px solid var(--ink)", background: "var(--surface)" }}>
              <div style={{ display: "flex", justifyContent: "center", paddingLeft: 4 }}><Checkbox on={allSel} indeterminate={someSel && !allSel} onClick={toggleAll} /></div>
              <div style={{ paddingLeft: 4 }}><Lab>Name</Lab></div>
              <div className="kb-col-creator" style={{ paddingLeft: 12 }}><Lab>Created by</Lab></div>
              <div className="kb-col-updated" style={{ paddingLeft: 12 }}><Lab>Last updated</Lab></div>
              <div />
            </div>
          )}
          {filtered.length ? filtered.map((d, i) => (
            <DocRow key={d.id} doc={d} selected={sel.has(d.id)} last={i === filtered.length - 1}
              onSelect={() => toggleOne(d.id)} onOpen={() => setModal({ type: "detail", doc: d })}
              menuOpen={menuId === d.id} onMenuToggle={() => setMenuId(menuId === d.id ? null : d.id)} onMenu={(a) => rowMenu(d, a)} />
          )) : (
            <div style={{ padding: "48px 20px", textAlign: "center" }}>
              <div style={{ width: 44, height: 44, margin: "0 auto", borderRadius: RADIUS, border: RULE, background: "var(--surface)", display: "flex", alignItems: "center", justifyContent: "center", color: "var(--faint)" }}><IconSearch s={20} /></div>
              <div style={{ marginTop: 12, fontFamily: SERIF, fontSize: 16, color: "var(--dim)" }}>{docs.length ? "No sources match your filters." : "No sources yet — add your first above."}</div>
              {(q || typeF || creatorF) && <button onClick={() => { setQ(""); setTypeF(null); setCreatorF(null); }} style={{ marginTop: 10, fontFamily: MONO, fontSize: 11, letterSpacing: ".5px", textTransform: "uppercase", color: "var(--accent)", background: "none", border: "none", cursor: "pointer" }}>Clear filters</button>}
            </div>
          )}
        </div>
      </div>

      {/* grounding banner */}
      {tw.groundingBanner === "on" && (
        <div style={{ position: "sticky", bottom: 0, borderTop: RULE, background: "color-mix(in srgb, var(--bg) 86%, transparent)", backdropFilter: "blur(8px)" }}>
          <div style={{ maxWidth: 1180, margin: "0 auto", padding: "12px 28px", display: "flex", alignItems: "center", gap: 12, flexWrap: "wrap" }}>
            <span style={{ width: 7, height: 7, borderRadius: "50%", background: "var(--ok)", boxShadow: "0 0 8px var(--ok)", flex: "none" }} />
            <span style={{ fontFamily: MONO, fontSize: 11, letterSpacing: ".4px", textTransform: "uppercase", color: "var(--ink)", fontWeight: 600 }}>Grounded</span>
            <span style={{ fontFamily: SERIF, fontSize: 14, color: "var(--dim)" }}>Your front desk answers only from these {docs.length} source{docs.length === 1 ? "" : "s"}. Anything off-script escalates to a human — it never guesses.</span>
            <div style={{ flex: 1 }} />
            <Badge tone="ok" dot>No hallucinations</Badge>
          </div>
        </div>
      )}

      {/* modals */}
      {modal && modal.type === "url" && <Modal title="Add a URL" sub="Crawl & ground" onClose={() => setModal(null)}><AddUrlBody onAdd={addDoc} onClose={() => setModal(null)} /></Modal>}
      {modal && modal.type === "files" && <Modal title="Add files" sub="Upload & ground" onClose={() => setModal(null)}><Dropzone onFiles={addFiles} /></Modal>}
      {modal && modal.type === "text" && <Modal title={modal.doc ? "Edit text source" : "Create text source"} sub="Write exactly what's allowed" onClose={() => setModal(null)}><CreateTextBody initial={modal.doc} onAdd={addDoc} onClose={() => setModal(null)} /></Modal>}
      {modal && modal.type === "folder" && <Modal title={modal.doc ? "Rename folder" : "Create folder"} onClose={() => setModal(null)} width={460}><CreateFolderBody initial={modal.doc} onAdd={addDoc} onClose={() => setModal(null)} /></Modal>}
      {modal && modal.type === "sync" && <Modal title="Sync a folder" sub="Connect a source" onClose={() => setModal(null)}><SyncFolderBody onAdd={addDoc} onClose={() => setModal(null)} /></Modal>}
      {modal && modal.type === "detail" && <Modal title={modal.doc.name} onClose={() => setModal(null)} width={560}><DetailBody doc={modal.doc} onClose={() => setModal(null)} /></Modal>}

      <Toasts items={toasts} />
    </div>
  );
}

  var rootEl = document.getElementById("root");
  if (!window.__kbRoot) window.__kbRoot = ReactDOM.createRoot(rootEl);
  window.__kbRoot.render(<KBApp />);
};
// Freshly-loaded app always wins: if the bundle is already present, re-mount now.
(function () {
  var NS = "EpochCoreDesignSystem_1d529c";
  if (!window.__SUITE && window[NS] && window[NS].Button) { try { window.__mountKB(window[NS]); } catch (e) {} }
})();
