/* Matters — the legal-reality half of the lifecycle: Create → Manage/Enrich.
   Editorial lives in a deferred per-team layer and is intentionally NOT here.
   list ▸ create ▸ detail(overview/participants/events/foia&assets/content). */
(function () {
  const DS = window.LawCrimeDesignSystem_e18d68;
  const V = window.LEDGER_VOCAB;
  const T = window.titleize;
  const opts = (arr, withEmpty) =>
    (withEmpty ? [{ value: "", label: "—" }] : []).concat(arr.map((v) => ({ value: v, label: T(v) })));
  // Vocabulary pickers: display labels + the narrower pickable list (config.js
  // LEDGER_VALUE_LABELS / LEDGER_VOCAB_PICKABLE). The current value is always kept.
  const VL = window.vocabLabel;
  const vopts = (field, current, withEmpty) =>
    (withEmpty ? [{ value: "", label: "—" }] : []).concat(window.vocabOptions(field, current));
  const icons = () => setTimeout(() => window.lucide && window.lucide.createIcons(), 30);

  // Minimal, safe markdown → React (headings, bold, bullets). No raw HTML injection.
  function mdInline(text) {
    return String(text).split(/(\*\*[^*]+\*\*)/g).map((p, i) => {
      const b = /^\*\*([^*]+)\*\*$/.exec(p);
      return b ? <strong key={i}>{b[1]}</strong> : <React.Fragment key={i}>{p}</React.Fragment>;
    });
  }
  function Markdown({ text }) {
    if (!text) return <div className="briefing-empty">No briefing yet — run <b>Enrich with AI</b> to generate one.</div>;
    const blocks = []; let list = null;
    const flush = () => { if (list) { blocks.push(<ul key={"u" + blocks.length}>{list}</ul>); list = null; } };
    String(text).replace(/\r/g, "").split("\n").forEach((raw, i) => {
      const line = raw.trimEnd();
      if (/^###\s+/.test(line)) { flush(); blocks.push(<h4 key={i} className="brief-h4">{mdInline(line.replace(/^###\s+/, ""))}</h4>); }
      else if (/^##\s+/.test(line)) { flush(); blocks.push(<h3 key={i} className="brief-h3">{mdInline(line.replace(/^##\s+/, ""))}</h3>); }
      else if (/^[-*]\s+/.test(line)) { (list = list || []).push(<li key={i}>{mdInline(line.replace(/^[-*]\s+/, ""))}</li>); }
      else if (line.trim() === "") { flush(); }
      else { flush(); blocks.push(<p key={i}>{mdInline(line)}</p>); }
    });
    flush();
    return <div className="briefing">{blocks}</div>;
  }

  // Textarea that grows to fit its content — no scrollbar for short summaries.
  function AutoTextarea({ value, onChange, placeholder }) {
    const ref = React.useRef(null);
    const resize = () => { const el = ref.current; if (el) { el.style.height = "auto"; el.style.height = el.scrollHeight + "px"; } };
    React.useLayoutEffect(() => { resize(); }, [value]);
    return <textarea ref={ref} className="lc-input auto-textarea" rows={2}
      value={value} onChange={(e) => { onChange(e); resize(); }} placeholder={placeholder} />;
  }

  // Two-click delete: first click arms, second confirms (auto-disarms after 3s).
  // Safer than a single button, less jarring than a native confirm() dialog.
  function DeleteLink({ label = "Delete", armedLabel = "Click again to confirm", onDelete, onError }) {
    const [armed, setArmed] = React.useState(false);
    const [busy, setBusy] = React.useState(false);
    React.useEffect(() => {
      if (!armed) return;
      const t = setTimeout(() => setArmed(false), 3000);
      return () => clearTimeout(t);
    }, [armed]);
    React.useEffect(() => { icons(); }, [armed, busy]);
    async function go(e) {
      e.stopPropagation();
      if (!armed) { setArmed(true); return; }
      setBusy(true);
      try { await onDelete(); }
      catch (err) { setArmed(false); onError && onError(err.message || String(err)); }
      finally { setBusy(false); }
    }
    return (
      <button type="button" className={"del-btn" + (armed ? " del-btn--armed" : "")} onClick={go} disabled={busy} title="Remove">
        <i data-lucide="trash-2" /> {busy ? "Removing…" : armed ? armedLabel : label}
      </button>
    );
  }

  // ---- Field typing (legal reality only) -----------------------------------
  const FIELD_KIND = {
    working_title: "text", display_alias: "text", matter_type: "select", legal_status: "select",
    coverage_status: "select", verdict: "select",
    crime_type: "text", crime_location_state: "text", crime_location_county: "text",
    num_victims: "number", went_to_trial: "bool", defendant_took_stand: "bool",
    docket_number: "text", legal_citation: "text", jurisdiction_abbrev: "text",
    jurisdiction_entity_id: "entity", court_url: "url", statute_of_limitations: "date",
    check_back_interval: "select", last_checked: "date", footage_exists: "bool",
    footage_link: "url", story_url: "url", next_action: "text",
    matter_summary: "text", summary_of_facts: "text", briefing_report: "text",
  };

  // Plain-language help for fields the task force found ambiguous (Lacey/Sara
  // feedback). Rendered as a hoverable info icon next to the label.
  const FIELD_HELP = {
    jurisdiction_entity_id: "The court of record handling this matter (add courts under the Agencies tab).",
    jurisdiction_abbrev: "Short form of the court/venue, e.g. S.D.N.Y. or 11th Cir. — not the same as the court of record.",
    court_url: "Link to the court's docket or case page, for following this matter.",
    statute_of_limitations: "The legal deadline to bring charges. After this date, prosecution may be barred.",
    coverage_status: "Where this case sits with the Content Center: Tracking (watching it), Covering (we are on it), Adjudicated (done). What each SHOW is doing with it (in production, published) is tracked per team, not here.",
    docket_number: "The number the court files this case under. Courts call it a case number or a docket number; they are the same thing.",
    legal_citation: "How the case is cited in court opinions, e.g. State v. Smith, 123 So. 3d 456 (Fla. 2026). Usually only appeals have one. Leave blank if there isn't one.",
    check_back_interval: "How often to re-check a quiet or cold case for new developments.",
    last_checked: "When someone last reviewed this case for updates. Pairs with the check-back interval.",
    // footage_link / story_url are no longer rendered here — both moved to the tab
    // that owns them (0107/0108). Help text kept so a legacy render still explains
    // itself, and says where the field went.
    footage_link: "Retired from this page — material now lives on the Content tab, one row per asset or folder.",
    story_url: "Retired from this page — published coverage now lives on the Coverage tab, and there can be more than one link.",
    prior_coverage_log: "What we've already covered — migrated from the Airtable Fact Sheet and Replay Sheet.",
  };

  // Hoverable info icon — native title tooltip, no JS wiring needed.
  function Hint({ field }) {
    const t = FIELD_HELP[field];
    if (!t) return null;
    return <span className="fhelp" title={t}><i data-lucide="info" /></span>;
  }

  function coerce(k, v) {
    const kind = FIELD_KIND[k] || "text";
    if (v === "" || v == null) return null;
    if (kind === "number") return parseInt(v, 10);
    if (kind === "bool") return v === "true";
    return v;
  }
  function toStr(k, raw) {
    const kind = FIELD_KIND[k] || "text";
    if (raw == null) return "";
    if (kind === "bool") return raw === true ? "true" : raw === false ? "false" : "";
    return String(raw);
  }
  const BOOL_OPTS = [{ value: "", label: "—" }, { value: "true", label: "Yes" }, { value: "false", label: "No" }];

  function FieldControl({ field, value, onChange, entities }) {
    const { Input, Select } = DS;
    const kind = FIELD_KIND[field] || "text";
    if (kind === "select") return <Select value={value} onChange={onChange} options={vopts(field, value, true)} />;
    if (kind === "bool") return <Select value={value} onChange={onChange} options={BOOL_OPTS} />;
    if (kind === "entity")
      return <Select value={value} onChange={onChange}
        options={[{ value: "", label: "—" }].concat((entities || []).map((e) => ({ value: e.entity_id, label: window.agencyLabel(e) })))} />;
    if (kind === "date") return <Input type="date" value={value} onChange={onChange} />;
    if (kind === "number") return <Input type="number" value={value} onChange={onChange} />;
    return <Input value={value} onChange={onChange} />;
  }

  // Grouped layout — legal reality, organized around: what / where / process / material.
  // Where & court leads (Lacey 2026-10-07: "Move this to the top please"): it is
  // what the desk looks up first. Statute of limitations is no longer shown (Lacey
  // ×3, "NO one will use Statute of Limitations"); the column stays in the DB.
  const GROUPS = [
    ["Where & court", [
      ["jurisdiction_entity_id", "Court of record"], ["jurisdiction_abbrev", "Court abbrev."],
      ["court_url", "Court website / docket"],
      ["docket_number", "Case / docket number"], ["legal_citation", "Legal citation"],
      ["crime_location_state", "State"], ["crime_location_county", "County"],
    ]],
    ["What it is", [
      ["matter_type", "Matter type"], ["crime_type", "Crime type"],
      ["num_victims", "Number of victims"], ["verdict", "Verdict"],
      ["went_to_trial", "Went to trial"], ["defendant_took_stand", "Defendant took stand"],
    ]],
    ["Legal process & what's next", [
      ["legal_status", "Legal status"], ["next_action", "Next action"],
    ]],
    // "Material & tracking" was two unrelated jobs in one box. The material half
    // moved to the tabs that own it (2026-09-17): Footage → Content, Our story →
    // Coverage. What is left is tracking, which is three fields about whether this
    // case is being watched — and coverage_status is now set HERE and nowhere else,
    // since the Coverage tab's decision card is gone.
    ["Tracking", [
      ["coverage_status", "Coverage status"],
      ["check_back_interval", "Check-back interval"], ["last_checked", "Last checked"],
    ]],
  ];
  const LONG = [
    ["summary_of_facts", "Summary of facts", "AI-generated — what happened"],
    ["prior_coverage_log", "Prior coverage", "fact sheet + replay sheet, migrated from Airtable"],
    ["briefing_report", "AI briefing report", "full web-researched Case Readiness Report"],
  ];

  function useEntities() {
    const [entities, setEntities] = React.useState([]);
    const reload = React.useCallback(() => {
      window.LedgerAPI.listEntities().then(setEntities).catch(() => {});
    }, []);
    React.useEffect(() => { reload(); }, [reload]);
    return { entities, reloadEntities: reload };
  }

  // ---- List -----------------------------------------------------------------
  function MattersList({ onOpen, onNew, tagFilter, onClearTag, tagLabel, lane, onLane }) {
    const { Button, Select } = DS;
    const [rows, setRows] = React.useState(null);
    const [covDefaults, setCovDefaults] = React.useState({});
    const [status, setStatus] = React.useState("all");
    const [state, setState] = React.useState("all");
    const [hasMedia, setHasMedia] = React.useState(false);
    const [q, setQ] = React.useState("");
    const [qLive, setQLive] = React.useState("");   // what's in the box
    const [err, setErr] = React.useState(null);
    const [page, setPage] = React.useState(0);
    const [total, setTotal] = React.useState(0);
    const [allLanesTotal, setAllLanesTotal] = React.useState(null);
    const PAGE_SIZE = 50;
    // Default order is unchanged — most recently touched first. The point of the
    // headers is that it is now A default rather than THE order.
    // Resolved inside the component (not at module scope) so the lookup happens at
    // render time — the same idiom as <window.LedgerCoveragePill>, and it does not
    // depend on SortKit.jsx having finished before this file's IIFE runs.
    const SortTh = window.LedgerSortKit.Th;
    const { sort, toggle } = window.LedgerSortKit.useSort("updated_at", "desc");
    const laneMeta = (window.LEDGER_LANES || []).find((l) => l.id === lane) || null;

    // Filtering is a server round-trip now, so don't fire one per keystroke.
    React.useEffect(() => {
      const t = setTimeout(() => setQ(qLive), 250);
      return () => clearTimeout(t);
    }, [qLive]);

    // Any filter change invalidates the current page — and so does the lane, which
    // is a scope rather than a filter but narrows the same result set.
    // A sort change invalidates the page for the same reason a filter does: page 4
    // of the old order is a different 50 rows in the new one, so staying there
    // shows a slice nobody asked for. Re-sorting always returns you to the top.
    React.useEffect(() => { setPage(0); }, [status, state, hasMedia, q, tagFilter, lane, sort]);

    // Switching into Police Videos hides the legal-status control. Clear the value
    // too — a hidden filter that is still applied is a list quietly lying about why
    // it is short.
    React.useEffect(() => { if (lane === "police_video") setStatus("all"); }, [lane]);

    const load = React.useCallback(async () => {
      setRows(null); setErr(null);
      try {
        const [res, cd] = await Promise.all([
          window.LedgerAPI.listMatters({ status, state, hasMedia, q, tag: tagFilter, lane, sort, page, pageSize: PAGE_SIZE }),
          window.LedgerAPI.coverageDefaults().catch(() => ({})),
        ]);
        setRows(res.rows); setTotal(res.total); setCovDefaults(cd || {});
      } catch (e) { setErr(e.message || String(e)); }
    }, [status, state, hasMedia, q, tagFilter, lane, sort, page]);

    React.useEffect(() => { load(); }, [load]);

    // The lane is a filter here, while the topbar and Search page only rank by it.
    // So a text search from inside a lane can hide the very matter you typed. Count
    // the same query across every lane and say how many the lane is hiding.
    // Text search only: a state or media filter inside a lane is a browse, not a
    // hunt, and "4,000 more in other lanes" would be noise.
    React.useEffect(() => {
      setAllLanesTotal(null);
      if (!q || !lane || lane === "all") return;
      let live = true;
      window.LedgerAPI.listMatters({ status, state, hasMedia, q, tag: tagFilter, lane: "all", countOnly: true })
        .then((r) => { if (live) setAllLanesTotal(r.total); })
        .catch(() => {});   // the hint is advisory; never block the list on it
      return () => { live = false; };
    }, [status, state, hasMedia, q, tagFilter, lane]);
    const otherLanes = rows && allLanesTotal != null ? Math.max(0, allLanesTotal - total) : 0;

    React.useEffect(() => { icons(); }, [rows, sort, otherLanes]);

    const filtered = status !== "all" || state !== "all" || hasMedia || q || tagFilter;

    return (
      <div>
        <div className="page-head">
          <div>
            <div className="eyebrow">
              Lifecycle · Create → Manage
              {laneMeta && laneMeta.id !== "all" && <> · scoped to <b>{laneMeta.label}</b></>}
            </div>
            <h1 className="page-title">Matters<window.LedgerComment section="Matters list" /></h1>
          </div>
          <Button variant="primary" icon="circle-plus" onClick={onNew}>New Matter</Button>
        </div>

        {/* Filters are per lane (v1 §3.4). Stage is the trials question — a police-video
            matter has no proceeding, so its stage column is mostly empty and offering
            the filter implies data that is not there. `Has media` is Alex Brisson's
            single most-repeated ask and was impossible before 0084 computed it. */}
        <div style={{ display: "flex", gap: 12, alignItems: "flex-end", marginBottom: 18, maxWidth: 860, flexWrap: "wrap" }}>
          {lane !== "police_video" && (
            <div style={{ width: 200 }}>
              <Select label="Legal status" value={status} onChange={(e) => setStatus(e.target.value)}
                options={[{ value: "all", label: "All legal statuses" }].concat(V.legal_status.map((v) => ({ value: v, label: VL("legal_status", v) })))} />
            </div>
          )}
          <div style={{ width: 150 }}>
            <Select label="State" value={state} onChange={(e) => setState(e.target.value)}
              options={[{ value: "all", label: "All states" }]
                .concat((window.LEDGER_US_STATES || []).map((s) => ({ value: s, label: s })))} />
          </div>
          <div style={{ flex: 1, minWidth: 220 }}>
            <DS.Input label="Filter" icon="search" placeholder="Title, ID, type, crime, state, docket…"
              value={qLive} onChange={(e) => setQLive(e.target.value)} />
          </div>
          <label className="chkfilter" title="Matters with a footage link or an asset on the server">
            <input type="checkbox" checked={hasMedia} onChange={(e) => setHasMedia(e.target.checked)} />
            <span>Has media</span>
          </label>
        </div>

        {tagFilter && (
          <div className="tagfilter">
            <i data-lucide="tag" /> Tag: <b>{(tagLabel && tagLabel(tagFilter)) || T(tagFilter)}</b>
            <button type="button" className="link-btn" onClick={onClearTag}><i data-lucide="x" /> Clear</button>
          </div>
        )}

        {err && <div className="login__err" style={{ marginBottom: 16 }}>Couldn’t load: {err}</div>}

        {otherLanes > 0 && (
          <div className="lanehint">
            <span className="ic-wrap"><i data-lucide="layers" /></span>
            <span>
              <b>{otherLanes.toLocaleString()} more match{otherLanes === 1 ? "" : "es"}</b> outside {laneMeta ? laneMeta.label : "this lane"}.
            </span>
            {onLane && (
              <button type="button" className="link-btn" onClick={() => onLane("all")}>Show all lanes</button>
            )}
          </div>
        )}

        <DS.Card>
          <DS.Card.Body style={{ padding: 0 }}>
            {rows === null && <div style={{ padding: 24, color: "var(--text-muted)", fontSize: 13 }}>Loading…</div>}
            {rows && rows.length === 0 && (
              <div className="ledger-empty" style={{ border: "none" }}>
                <i data-lucide="folder-plus" />
                <b>No matters{filtered ? " match" : " yet"}</b>
                <span>{filtered ? "Try a broader filter." : "Create the first matter to begin."}</span>
              </div>
            )}
            {rows && rows.length > 0 && (
              <table className="tbl">
                {/* Every column here is a real column on `matters_list`, so the sort
                    is an ORDER BY over all 11,738 rows — not a re-ordering of the 50
                    already fetched. `firstDir` is the useful first answer per column:
                    a date opens newest-first, a name opens A–Z. */}
                <thead><tr>
                  <SortTh k="display_id" sort={sort} onSort={toggle}>ID</SortTh>
                  <SortTh k="working_title" sort={sort} onSort={toggle}>Title</SortTh>
                  <SortTh k="matter_type" sort={sort} onSort={toggle}>Type</SortTh>
                  <SortTh k="legal_status" sort={sort} onSort={toggle}>Legal</SortTh>
                  <SortTh k="coverage_status" sort={sort} onSort={toggle}>Coverage</SortTh>
                  <SortTh k="crime_location_state" sort={sort} onSort={toggle}>State</SortTh>
                  <SortTh k="updated_at" sort={sort} onSort={toggle} firstDir="desc">Updated</SortTh>
                </tr></thead>
                <tbody>
                  {rows.map((m) => {
                    const cd = covDefaults[m.matter_id];
                    return (
                    <tr key={m.matter_id} onClick={() => onOpen(m.matter_id)}>
                      <td className="cdef" style={{ fontVariantNumeric: "tabular-nums" }}>{m.display_id || "—"}</td>
                      <td className="cname">{m.display_alias || m.working_title}{m.matter_summary && <span className="row-sub">{m.matter_summary}</span>}</td>
                      <td className="cdef">{VL("matter_type", m.matter_type)}</td>
                      <td className="cdef">{m.legal_status ? T(m.legal_status) : "—"}</td>
                      <td className="cdef">{m.coverage_status
                        ? <window.LedgerCoveragePill status={m.coverage_status} method={cd && cd.ingest_method} small />
                        : "—"}</td>
                      <td className="cdef">{m.crime_location_state || "—"}</td>
                      <td style={{ color: "var(--text-muted)", fontSize: 12.5 }}>
                        {m.updated_at ? new Date(m.updated_at).toLocaleDateString() : "—"}
                      </td>
                    </tr>
                    );
                  })}
                </tbody>
              </table>
            )}
          </DS.Card.Body>
        </DS.Card>

        {rows && total > 0 && (
          <div className="pager">
            <span className="pager__count">
              {total <= PAGE_SIZE
                ? `${total} matter${total === 1 ? "" : "s"}`
                : `${page * PAGE_SIZE + 1}–${Math.min((page + 1) * PAGE_SIZE, total)} of ${total.toLocaleString()}`}
            </span>
            {total > PAGE_SIZE && (
              <span className="pager__nav">
                <Button variant="ghost" icon="chevron-left" disabled={page === 0}
                  onClick={() => setPage((p) => Math.max(0, p - 1))}>Previous</Button>
                <Button variant="ghost" disabled={(page + 1) * PAGE_SIZE >= total}
                  onClick={() => setPage((p) => p + 1)}>Next</Button>
              </span>
            )}
          </div>
        )}
      </div>
    );
  }

  // ---- Create ---------------------------------------------------------------
  const CREATE_DEFAULTS = {
    working_title: "", matter_summary: "", matter_type: "", legal_status: "",
    coverage_status: "",
    crime_type: "", crime_location_state: "", crime_location_county: "", num_victims: "",
    verdict: "", went_to_trial: "", defendant_took_stand: "",
    docket_number: "", legal_citation: "", jurisdiction_abbrev: "", jurisdiction_entity_id: "",
    court_url: "", statute_of_limitations: "", next_action: "", footage_link: "",
    check_back_interval: "", last_checked: "",
    summary_of_facts: "",
  };

  function CreateMatter({ onCreated, onCancel }) {
    const { Button, Input } = DS;
    const { entities } = useEntities();
    const [f, setF] = React.useState(CREATE_DEFAULTS);
    const [busy, setBusy] = React.useState(false);
    const [err, setErr] = React.useState(null);
    const set = (k) => (e) => setF((s) => ({ ...s, [k]: e.target.value }));
    const valid = f.working_title.trim() && f.matter_type;

    React.useEffect(() => { icons(); });

    async function submit() {
      if (!valid) { setErr("Working title and matter type are required."); return; }
      setBusy(true); setErr(null);
      const payload = {};
      for (const [k, v] of Object.entries(f)) {
        const c = coerce(k, v);
        if (c === null) continue;
        payload[k] = c;
      }
      try {
        const created = await window.LedgerAPI.createMatter(payload);
        onCreated(created.matter_id);
      } catch (e) { setErr(e.message || String(e)); setBusy(false); }
    }

    return (
      <div>
        <div className="crumb" onClick={onCancel}><i data-lucide="arrow-left" /> Matters</div>
        <div className="page-head">
          <div><div className="eyebrow">Lifecycle · Create</div><h1 className="page-title">New Matter<window.LedgerComment section="New Matter form" /></h1></div>
        </div>
        {err && <div className="login__err" style={{ marginBottom: 16 }}>{err}</div>}

        <DS.Card><DS.Card.Body>
          <div className="form-grid">
            <div className="full">
              <Input label="Working title *" placeholder="e.g. Florida v. George Pino"
                value={f.working_title} onChange={set("working_title")} />
              <div style={{ fontSize: 11, color: "var(--text-muted)", marginTop: 4 }}>
                Stable identifier — never changes after creation. A human-readable ID is assigned automatically.
                Tip: create it, then <b>Enrich with AI</b> to auto-fill the rest from the web.
              </div>
            </div>
            <div className="full">
              <Input label="One-line summary" placeholder="e.g. Speedboat crash that killed teen on Biscayne Bay; manslaughter trial underway"
                value={f.matter_summary} onChange={set("matter_summary")} />
              <div style={{ fontSize: 11, color: "var(--text-muted)", marginTop: 4 }}>
                A single scannable line that follows the matter everywhere it’s listed — or leave blank and let AI fill it.
              </div>
            </div>
            <DS.Select label="Matter type *" value={f.matter_type} onChange={set("matter_type")} options={vopts("matter_type", f.matter_type, true)} />
            <div />
          </div>

          {GROUPS.map(([title, fields]) => (
            <div key={title} style={{ marginTop: 18 }}>
              <div className="form-subhead">{title}</div>
              <div className="form-grid">
                {fields.filter(([k]) => k !== "matter_type").map(([key, label]) => (
                  <div key={key}>
                    <label className="field-label">{label} <Hint field={key} /></label>
                    <FieldControl field={key} value={f[key]} onChange={set(key)} entities={entities} />
                  </div>
                ))}
              </div>
            </div>
          ))}

          <div style={{ marginTop: 18 }}>
            <div className="form-subhead">Narrative</div>
            <div className="form-grid">
              <div className="full">
                <label className="field-label">Summary of facts <span style={{ color: "var(--text-muted)", textTransform: "none", letterSpacing: 0 }}>— or leave blank and let AI fill it</span></label>
                <textarea className="lc-input ledger-textarea" value={f.summary_of_facts} onChange={set("summary_of_facts")} />
              </div>
            </div>
          </div>

          <div className="form-actions">
            <Button variant="ghost" onClick={onCancel} disabled={busy}>Cancel</Button>
            <Button variant="primary" icon="check" onClick={submit} disabled={busy || !valid}>
              {busy ? "Creating…" : "Create matter"}
            </Button>
          </div>
        </DS.Card.Body></DS.Card>
      </div>
    );
  }

  // ---- Detail ---------------------------------------------------------------
  function Prov({ meta, field }) {
    const m = meta && meta[field];
    if (!m || !m.source) return null;
    const ai = m.source === "ai";
    return (
      <span className={"prov " + (ai ? "prov--ai" : "prov--human")} title={m.populated_at ? `${m.source} · ${m.populated_at}` : m.source}>
        <i data-lucide={ai ? "sparkles" : "user"} />{ai ? "AI" : "Human"}
      </span>
    );
  }

  // Milestone timeline built from dated case_events. Evenly-spaced nodes on a
  // connecting rail (the "demo site version" the newsroom asked to bring forward):
  // past = done (filled), the nearest upcoming = now (red pulse), rest = future.
  function Timeline({ events }) {
    // Filing deadlines (0116) are not milestones: the red "now" dot means the next
    // court date, and a motions deadline is not one. They stay on Case events.
    const dated = (events || []).filter((e) => e.event_date && e.event_type !== "deadline").sort((a, b) => a.event_date.localeCompare(b.event_date));
    if (!dated.length) return <div className="mtl-empty">No dated events yet — add hearings/trial dates under the <b>Case events</b> tab, or Enrich with AI.</div>;
    const today = new Date().toISOString().slice(0, 10);
    let nextIdx = dated.findIndex((e) => e.event_date >= today);
    if (nextIdx === -1) nextIdx = dated.length; // all in the past → all done
    return (
      <div className="mtl">
        {dated.map((e, i) => {
          const cls = i < nextIdx ? "done" : i === nextIdx ? "now" : "future";
          return (
            <div key={e.event_id || i} className={"mtl-ev mtl-ev--" + cls}>
              <div className="mtl-dot" />
              <div className="mtl-dt">{window.LedgerTime.fmtDate(e.event_date, { month: "short", day: "numeric" })}</div>
              <div className="mtl-nm">{T(e.event_type)}</div>
            </div>
          );
        })}
      </div>
    );
  }

  // Victim age: prefer the entered age; fall back to computing from DOB.
  function victimAge(ind) {
    if (ind == null) return null;
    if (ind.age != null && ind.age !== "") return ind.age;
    if (!ind.date_of_birth) return null;
    const d = window.LedgerTime.localDate(ind.date_of_birth); if (!d) return null;
    const now = new Date(); let a = now.getFullYear() - d.getFullYear();
    const mo = now.getMonth() - d.getMonth();
    if (mo < 0 || (mo === 0 && now.getDate() < d.getDate())) a--;
    return a >= 0 && a <= 130 ? a : null;
  }

  // A matter is "adjudicated" once the legal process is closed or the Content
  // Center has marked it adjudicated. Drives the demoted timeline + status tone.
  const isClosed = (m) => m.legal_status === "closed" || m.coverage_status === "adjudicated";
  // Legal-status → badge tone (see .mstatus--*). Hot = in the thick of it.
  const LEGAL_TONE = {
    incident: "neutral", investigation: "neutral", arrested: "active",
    charged: "active", pre_trial: "active",
    trial: "hot", verdict: "hot", sentencing: "hot", appeal: "active",
    closed: "done", cold_case: "muted",
  };

  // Content Center owner of the matter (0159, B3). Anyone in Workspace sees it;
  // Content Ops (and admins) set it, to themselves or a colleague. Only staff
  // marked Assignable are offered (same list as request assignees; Rule 36).
  function CcAssignee({ matter, onSaved }) {
    const [assignable, setAssignable] = React.useState(null);
    const [staff, setStaff] = React.useState([]);
    const [busy, setBusy] = React.useState(false);
    const [err, setErr] = React.useState(null);
    const caps = window.LedgerCaps || {};
    const canSet = !!(caps.isContentOps || caps.isAdmin);
    const cur = matter.cc_assignee_staff_id || "";
    React.useEffect(() => {
      let live = true;
      if (canSet) window.LedgerAPI.listAssignableStaff().then((r) => { if (live) setAssignable(r); }).catch(() => { if (live) setAssignable([]); });
      if (cur) window.LedgerAPI.listStaff().then((r) => { if (live) setStaff(r); }).catch(() => {});
      return () => { live = false; };
    }, [canSet, cur]);
    const nameOf = (id) => {
      const s = (assignable || []).concat(staff).find((x) => x.staff_id === id);
      return s ? `${s.first_name || ""} ${s.last_name || ""}`.trim() : "someone no longer on the team";
    };
    async function save(v) {
      setBusy(true); setErr(null);
      try { await window.LedgerAPI.updateMatter(matter.matter_id, { cc_assignee_staff_id: v || null }); onSaved && onSaved(); }
      catch (e) { setErr(e.message || String(e)); }
      finally { setBusy(false); }
    }
    if (!canSet) {
      return cur ? <span className="mstatus mstatus--neutral cc-owner" title="Content Center owner"><i data-lucide="user-round" /> {nameOf(cur)}</span> : null;
    }
    if (assignable === null) return null;
    const opts = [{ value: "", label: "No Content Center owner" }]
      .concat(assignable.map((s) => ({ value: s.staff_id, label: `${s.first_name || ""} ${s.last_name || ""}`.trim() })));
    if (cur && !assignable.some((s) => s.staff_id === cur)) opts.push({ value: cur, label: nameOf(cur) });
    return (
      <span className="cc-owner-edit" title="Content Center owner">
        <i data-lucide="user-round" />
        <select className="cc-owner-select" value={cur} disabled={busy} onChange={(e) => save(e.target.value)} aria-label="Content Center owner">
          {opts.map((o) => <option key={o.value} value={o.value}>{o.label}</option>)}
        </select>
        {err && <span className="cc-owner-err">{err}</span>}
      </span>
    );
  }

  function MatterDetail({ id, onBack, onTag }) {
    const { Button, Tabs } = DS;
    const { entities, reloadEntities } = useEntities();
    const [m, setM] = React.useState(null);
    const [counts, setCounts] = React.useState({});
    const [agencyCount, setAgencyCount] = React.useState(0);
    // Documents (attachments A1): counted on open so the tab shows its number before it is visited.
    const [docCount, setDocCount] = React.useState(0);
    React.useEffect(() => {
      let live = true;
      const att = window.LedgerAPI && window.LedgerAPI.att;
      if (att) att.count("matter", id).then((n) => { if (live) setDocCount(n); }).catch(() => {});
      return () => { live = false; };
    }, [id]);
    const [events, setEvents] = React.useState([]);
    const [tags, setTags] = React.useState([]); // taxonomy
    const [victims, setVictims] = React.useState([]); // participant rows, role=victim
    const [tab, setTab] = React.useState("overview");
    const [editSection, setEditSection] = React.useState(null); // locked-by-default; one section at a time
    const [copied, setCopied] = React.useState(false);
    const [fixName, setFixName] = React.useState(false);
    const [nameDraft, setNameDraft] = React.useState("");
    const [editAlias, setEditAlias] = React.useState(false);
    const [aliasDraft, setAliasDraft] = React.useState("");
    const [draft, setDraft] = React.useState({});
    const [busy, setBusy] = React.useState(false);
    const [msg, setMsg] = React.useState(null);
    const [err, setErr] = React.useState(null);

    const load = React.useCallback(async () => {
      setErr(null);
      try {
        const [mm, cc, ev, ag, tg, pp] = await Promise.all([
          window.LedgerAPI.getMatter(id), window.LedgerAPI.counts(id), window.LedgerAPI.listEvents(id),
          window.LedgerAPI.listMatterAgencies(id).catch(() => []),
          window.LedgerAPI.listTags().catch(() => []),
          window.LedgerAPI.listParticipants(id).catch(() => []),
        ]);
        setM(mm); setCounts(cc); setEvents(ev); setAgencyCount((ag || []).length); setTags(tg || []);
        setVictims((pp || []).filter((r) => r.individual_id && r.role === "victim"));
        setDraft({}); setEditSection(null);
      } catch (e) { setErr(e.message || String(e)); }
    }, [id]);

    React.useEffect(() => { load(); }, [load]);
    // After the activity rail applies an accepted fact (0112): re-read what the fact can
    // change — status, verdict, events, people — WITHOUT load()'s reset of draft and
    // editSection, so accepting a suggestion never discards an edit in progress.
    const refreshFacts = React.useCallback(async () => {
      try {
        const [mm, cc, ev, pp] = await Promise.all([
          window.LedgerAPI.getMatter(id), window.LedgerAPI.counts(id), window.LedgerAPI.listEvents(id),
          window.LedgerAPI.listParticipants(id).catch(() => []),
        ]);
        setM(mm); setCounts(cc); setEvents(ev);
        setVictims((pp || []).filter((r) => r.individual_id && r.role === "victim"));
      } catch (e) { /* the rail already reported the decision; a stale overview is recoverable */ }
    }, [id]);
    React.useEffect(() => { icons(); });
    // DEMO-ONLY: tag the open matter as feedback context so per-section comments
    // record which matter they're about (removed at go-live with the Feedback module).
    React.useEffect(() => {
      if (m && window.LedgerFeedbackCtx) {
        window.LedgerFeedbackCtx.context = m.display_id ? `${m.display_id} · ${m.working_title}` : m.working_title;
      }
    }, [m]);

    if (err) return <div><div className="crumb" onClick={onBack}><i data-lucide="arrow-left" /> Matters</div><div className="login__err">{err}</div></div>;
    if (!m) return <div style={{ color: "var(--text-muted)", fontSize: 13 }}>Loading…</div>;

    const val = (k) => (k in draft ? draft[k] : toStr(k, m[k]));
    const setF = (k) => (e) => setDraft((s) => ({ ...s, [k]: e.target.value }));
    const dirty = Object.keys(draft).length > 0;
    const briefText = () => ("briefing_report" in draft ? draft.briefing_report : m.briefing_report) || "";
    const briefDirty = "briefing_report" in draft;
    async function copyBrief() {
      try { await navigator.clipboard.writeText(briefText()); setCopied(true); setTimeout(() => setCopied(false), 1500); }
      catch (e) { setErr("Copy failed — your browser blocked clipboard access."); }
    }

    async function save() {
      setBusy(true); setMsg(null); setErr(null);
      const patch = {};
      for (const [k, v] of Object.entries(draft)) patch[k] = coerce(k, v);
      try { const updated = await window.LedgerAPI.updateMatter(id, patch); setM(updated); setDraft({}); setMsg("Saved."); }
      catch (e) { setErr(e.message || String(e)); }
      finally { setBusy(false); }
    }

    async function enrich(matterObj) {
      const target = matterObj || m;
      setBusy(true); setMsg(null); setErr(null);
      try {
        const r = await window.LedgerAPI.enrichMatter(target);
        const parts = [];
        if (r.updated && r.updated.length) parts.push(`${r.updated.length} fields`);
        if (r.created_participants) parts.push(`${r.created_participants} participants`);
        if (r.created_events) parts.push(`${r.created_events} events`);
        setMsg(`Enriched (${r.web_searches || 0} web searches): ${parts.join(", ") || "no changes"}. ~$${r.est_cost_usd}`);
        await load();
      } catch (e) { setErr(`Enrichment unavailable: ${e.message || String(e)}.`); }
      finally { setBusy(false); }
    }

    async function dismissDisambig() {
      try { const u = await window.LedgerAPI.updateMatter(id, { disambiguation_note: null }); setM(u); }
      catch (e) { setErr(e.message || String(e)); }
    }
    async function saveNameAndReenrich() {
      if (!nameDraft.trim()) return;
      setBusy(true); setMsg(null); setErr(null);
      try {
        const updated = await window.LedgerAPI.updateMatter(id, { working_title: nameDraft.trim(), disambiguation_note: null });
        setM(updated); setFixName(false);
        await enrich(updated);
      } catch (e) { setErr(e.message || String(e)); setBusy(false); }
    }
    // Editable display name (slug). working_title stays the immutable anchor
    // (Decision 1); the alias is what's shown in lists + the header when set.
    async function saveAlias() {
      const v = aliasDraft.trim();
      setBusy(true); setMsg(null); setErr(null);
      try {
        const updated = await window.LedgerAPI.updateMatter(id, { display_alias: v || null });
        setM(updated); setEditAlias(false); setMsg("Display name saved.");
      } catch (e) { setErr(e.message || String(e)); }
      finally { setBusy(false); }
    }

    const tabs = [
      { id: "overview", label: "Overview" },
      { id: "participants", label: "Participants", count: counts.matter_participants || 0 },
      { id: "agencies", label: "Agencies", count: agencyCount || 0 },
      { id: "events", label: "Case events", count: counts.case_events || 0 },
      { id: "coverage", label: "Coverage", count: (counts.coverage_links || 0) + (counts.coverage_pieces || 0) },
      { id: "foia", label: "Asset requests", count: counts.asset_requests || 0 },
      { id: "content", label: "Content", count: counts.matter_content || 0 },
      { id: "documents", label: "Documents", count: docCount },
    ];

    // Tag taxonomy lookups + current selection (draft-aware).
    const tagMap = {}; tags.forEach((t) => { tagMap[t.tag_key] = t.label; });
    const tagLabel = (k) => tagMap[k] || T(k);
    const tagsSel = "tag_words" in draft ? draft.tag_words : (m.tag_words || []);
    const toggleTag = (key) => setDraft((d) => {
      const cur = "tag_words" in d ? d.tag_words : (m.tag_words || []);
      const next = cur.includes(key) ? cur.filter((x) => x !== key) : [...cur, key];
      return { ...d, tag_words: next };
    });

    // Read-only formatting for the locked view.
    function readValue(key) {
      const kind = FIELD_KIND[key] || "text";
      const raw = m[key];
      if (raw == null || raw === "") return "—";
      if (kind === "bool") return raw === true ? "Yes" : raw === false ? "No" : "—";
      if (kind === "select") return VL(key, raw);
      if (kind === "date") return window.LedgerTime.fmtDate(raw) || String(raw);
      if (kind === "entity") { const e = (entities || []).find((x) => x.entity_id === raw); return e ? window.agencyLabel(e) : "—"; }
      if (kind === "url") {
        const href = /^https?:\/\//i.test(raw) ? raw : "https://" + raw;
        const text = String(raw).replace(/^https?:\/\//i, "").replace(/\/$/, "");
        return <a className="ov-link" href={href} target="_blank" rel="noreferrer"><i data-lucide="external-link" /> {text}</a>;
      }
      return String(raw);
    }
    function fieldCell([key, label]) {
      return (
        <div key={key}>
          <label className="field-label">{label} <Hint field={key} /><Prov meta={m.ai_field_metadata} field={key} /></label>
          <FieldControl field={key} value={val(key)} onChange={setF(key)} entities={entities} />
        </div>
      );
    }
    function readCell([key, label]) {
      return (
        <div key={key}>
          <label className="field-label">{label} <Hint field={key} /><Prov meta={m.ai_field_metadata} field={key} /></label>
          <div className="ov-value">{readValue(key)}</div>
        </div>
      );
    }

    // Section locking: read-only by default, one section editable at a time.
    function cancelSection(fields) {
      setDraft((d) => { const n = { ...d }; (fields || []).forEach(([k]) => delete n[k]); return n; });
      setEditSection(null); setMsg(null); setErr(null);
    }
    async function saveSection() { await save(); setEditSection(null); }
    function SectionHead({ title, sectionKey, fields, badge }) {
      const editing = editSection === sectionKey;
      return (
        <div className="ov-head">
          <span className="ov-title">{title} {badge}<window.LedgerComment section={title} /></span>
          {editing ? (
            <span className="ov-actions">
              <button type="button" className="link-btn" onClick={() => cancelSection(fields)} disabled={busy}>Cancel</button>
              <button type="button" className="link-btn ov-save" onClick={saveSection} disabled={busy}><i data-lucide="check" /> {busy ? "Saving…" : "Save"}</button>
            </span>
          ) : (
            <button type="button" className="link-btn" onClick={() => { setEditSection(sectionKey); setMsg(null); }} disabled={!!editSection}><i data-lucide="pencil" /> Edit</button>
          )}
        </div>
      );
    }

    return (
      <div className="matter-layout">
        <div className="matter-main">
        <div className="crumb" onClick={onBack}><i data-lucide="arrow-left" /> Matters</div>
        <div className="page-head">
          <div>
            <div className="eyebrow">{m.display_id ? m.display_id + " · " : ""}{VL("matter_type", m.matter_type)}</div>
            {editAlias ? (
              <div className="alias-edit">
                <input className="lc-input" value={aliasDraft} onChange={(e) => setAliasDraft(e.target.value)}
                  placeholder={m.working_title} autoFocus />
                <button type="button" className="link-btn ov-save" onClick={saveAlias} disabled={busy}><i data-lucide="check" /> {busy ? "Saving…" : "Save"}</button>
                <button type="button" className="link-btn" onClick={() => setEditAlias(false)} disabled={busy}>Cancel</button>
              </div>
            ) : (
              <h1 className="page-title">{m.display_alias || m.working_title}
                <button type="button" className="alias-pencil" title="Edit display name" onClick={() => { setAliasDraft(m.display_alias || m.working_title || ""); setEditAlias(true); setMsg(null); }}><i data-lucide="pencil" /></button>
                <window.LedgerComment section="Matter (overall)" />
              </h1>
            )}
            {m.display_alias && <div className="alias-anchor">Working title (stable): <b>{m.working_title}</b></div>}
            <div className="mstatus-row">
              {m.legal_status && <span className={"mstatus mstatus--" + (LEGAL_TONE[m.legal_status] || "neutral")}>{T(m.legal_status)}</span>}
              {isClosed(m) && m.legal_status !== "closed" && <span className="mstatus mstatus--done">Adjudicated</span>}
              {m.crime_location_state && <span className="mstatus mstatus--state"><i data-lucide="map-pin" /> {m.crime_location_state}</span>}
              {m.coverage_status && <window.LedgerCoveragePill status={m.coverage_status} small />}
              <CcAssignee matter={m} onSaved={() => window.LedgerAPI.getMatter(id).then(setM).catch(() => {})} />
            </div>
            {m.matter_summary && <div className="page-lede">{m.matter_summary}</div>}
          </div>
          <Button variant="secondary" icon="sparkles" onClick={() => enrich()} disabled={busy}>{busy ? "Working…" : "Enrich with AI"}</Button>
        </div>

        {m.disambiguation_note && (
          <div className="disambig">
            <i data-lucide="alert-triangle" className="disambig__ic" />
            <div className="disambig__body">
              <b>Possible mismatch — review</b>
              <div className="disambig__msg">{m.disambiguation_note}</div>
              {!fixName ? (
                <div className="disambig__actions">
                  <button type="button" className="link-btn" onClick={() => { setNameDraft(m.working_title || ""); setFixName(true); }}><i data-lucide="pencil" /> Correct case name &amp; re-enrich</button>
                  <button type="button" className="link-btn" onClick={dismissDisambig}><i data-lucide="check" /> Looks right — dismiss</button>
                </div>
              ) : (
                <div className="disambig__fix">
                  <input className="lc-input" value={nameDraft} onChange={(e) => setNameDraft(e.target.value)} placeholder="Correct case name" />
                  <Button variant="primary" icon="sparkles" onClick={saveNameAndReenrich} disabled={busy || !nameDraft.trim()}>{busy ? "Working…" : "Save & re-enrich"}</Button>
                  <button type="button" className="link-btn" onClick={() => setFixName(false)}>Cancel</button>
                </div>
              )}
            </div>
          </div>
        )}

        {msg && <div className="ledger-ok">{msg}</div>}
        {err && <div className="login__err" style={{ marginBottom: 14 }}>{err}</div>}

        {window.LedgerHoldBanner && <window.LedgerHoldBanner matterIds={[id]} />}

        <div className="matter-tabs" style={{ marginBottom: 18 }}><Tabs items={tabs} value={tab} onChange={setTab} /></div>

        {tab === "overview" && !isClosed(m) && (
          <div className="mtl-card">
            <div className="mtl-card-head"><i data-lucide="milestone" /> Case timeline<window.LedgerComment section="Case timeline" /></div>
            <Timeline events={events} />
          </div>
        )}

        {tab === "overview" && (
          <DS.Card><DS.Card.Body>
            <div className="ov-lockhint"><i data-lucide="lock" /> Fields are locked to prevent accidental edits. Use <b>Edit</b> on a section to change it.</div>

            <div className="ov-section">
              <SectionHead title="One-line summary" sectionKey="matter_summary" fields={[["matter_summary"]]}
                badge={<Prov meta={m.ai_field_metadata} field="matter_summary" />} />
              {editSection === "matter_summary"
                ? <AutoTextarea value={val("matter_summary")} onChange={setF("matter_summary")} placeholder="One scannable line — who/what, where, current posture. Shown wherever this matter is listed." />
                : <div className="ov-value ov-lede-value">{m.matter_summary || "— add a one-liner so this case is recognizable in lists, or Enrich with AI."}</div>}
            </div>

            {GROUPS.map(([title, fields]) => {
              const editing = editSection === title;
              return (
                <div key={title} className="ov-section">
                  <SectionHead title={title} sectionKey={title} fields={fields} />
                  {title === "Legal process & what's next" && isClosed(m) && <Timeline events={events} />}
                  <div className="form-grid">{fields.map((f) => (editing ? fieldCell(f) : readCell(f)))}</div>
                  {title === "What it is" && (
                    <div className="ov-victims">
                      <label className="field-label">Victims <span className="fnote">names &amp; ages — manage under Participants</span></label>
                      {victims.length ? (
                        <div className="victim-chips">
                          {victims.map((r) => {
                            const ind = r.individuals || {};
                            const nm = [ind.first_name, ind.last_name].filter(Boolean).join(" ") || "Unknown";
                            const a = victimAge(ind);
                            return <span key={r.participant_id} className="victim-chip">{nm}{a != null ? <span className="victim-age">{a}</span> : null}</span>;
                          })}
                        </div>
                      ) : (
                        <div className="ov-value" style={{ color: "var(--text-muted)" }}>
                          {m.num_victims != null ? `${m.num_victims} victim${m.num_victims === 1 ? "" : "s"} recorded — add names & ages under Participants.` : "None named yet — add victims under Participants."}
                        </div>
                      )}
                    </div>
                  )}
                </div>
              );
            })}

            <div className="ov-section">
              <SectionHead title="Tags" sectionKey="tags" fields={[["tag_words"]]}
                badge={<Prov meta={m.ai_field_metadata} field="tag_words" />} />
              {editSection === "tags" ? (
                <div className="tag-pick">
                  {tags.length === 0 && <span style={{ fontSize: 12.5, color: "var(--text-muted)" }}>No tags in the taxonomy yet — add them under AI Config.</span>}
                  {tags.map((t) => {
                    const on = tagsSel.includes(t.tag_key);
                    return (
                      <button key={t.tag_key} type="button" className={"tagopt" + (on ? " tagopt--on" : "")} onClick={() => toggleTag(t.tag_key)}>
                        {on && <span className="ic-wrap"><i data-lucide="check" /></span>}{t.label}
                      </button>
                    );
                  })}
                </div>
              ) : (
                <div className="chips-row">
                  {(m.tag_words && m.tag_words.length)
                    ? m.tag_words.map((k) => (
                        <button key={k} type="button" className="chip chip--tag" title="Show matters with this tag" onClick={() => onTag && onTag(k)}>{tagLabel(k)}</button>
                      ))
                    : <span style={{ fontSize: 12.5, color: "var(--text-muted)" }}>None — Enrich with AI to suggest.</span>}
                </div>
              )}
            </div>

            <div className="ov-section">
              <SectionHead title="Summary of facts" sectionKey="summary" fields={[["summary_of_facts"]]}
                badge={<Prov meta={m.ai_field_metadata} field="summary_of_facts" />} />
              {editSection === "summary"
                ? <AutoTextarea value={val("summary_of_facts")} onChange={setF("summary_of_facts")} placeholder="What happened — a tight factual summary." />
                : <div className="ov-value ov-prose">{m.summary_of_facts || "—"}</div>}
            </div>

            {/* Courthouse logistics (parking, audio, where to set up). Synced nightly from
                Airtable's "Courthouse/Courtroom Logisitics" into coverage_notes, so it is
                read-only here: an edit would be overwritten by the next sync. */}
            {m.coverage_notes && (
              <div className="ov-section">
                <div className="ov-head">
                  <span className="ov-title">Courthouse logistics</span>
                  <span className="ov-actions" style={{ fontSize: 12, color: "var(--text-muted)" }}>From Airtable · edit there</span>
                </div>
                <div className="ov-value ov-prose" style={{ whiteSpace: "pre-wrap" }}>{m.coverage_notes}</div>
              </div>
            )}

            <div className="ov-section">
              <div className="ov-head">
                <span className="ov-title">AI briefing report <Prov meta={m.ai_field_metadata} field="briefing_report" />
                  {briefDirty && <span className="unsaved" style={{ marginLeft: 8 }}><i data-lucide="alert-circle" /> Unsaved</span>}
                </span>
                <span className="ov-actions">
                  <button type="button" className="link-btn" onClick={copyBrief} disabled={!briefText()}>
                    <i data-lucide={copied ? "check" : "copy"} /> {copied ? "Copied" : "Copy"}
                  </button>
                  {editSection === "briefing" ? (
                    <React.Fragment>
                      <button type="button" className="link-btn" onClick={() => cancelSection([["briefing_report"]])} disabled={busy}>Cancel</button>
                      <button type="button" className="link-btn ov-save" onClick={saveSection} disabled={busy}><i data-lucide="check" /> {busy ? "Saving…" : "Save"}</button>
                    </React.Fragment>
                  ) : (
                    <button type="button" className="link-btn" onClick={() => { setEditSection("briefing"); setMsg(null); }} disabled={!!editSection}><i data-lucide="pencil" /> Edit</button>
                  )}
                </span>
              </div>
              {editSection === "briefing"
                ? <textarea className="lc-input ledger-textarea briefing-edit" value={val("briefing_report")} onChange={setF("briefing_report")} placeholder="Markdown supported — ## heading, ### subhead, **bold**, * bullet" />
                : <Markdown text={("briefing_report" in draft ? draft.briefing_report : m.briefing_report)} />}
            </div>
          </DS.Card.Body></DS.Card>
        )}

        {tab === "participants" && <ParticipantsTab matterId={id} onChanged={load} onJump={setTab} />}
        {tab === "agencies" && <AgenciesTab matterId={id} entities={entities} reloadEntities={reloadEntities} onChanged={load} />}
        {tab === "events" && <EventsTab matterId={id} onChanged={load} />}
        {tab === "coverage" && <CoverageTab matterId={id} onChanged={refreshFacts} />}
        {tab === "foia" && <AssetRequestsTab matterId={id} matter={m} entities={entities} reloadEntities={reloadEntities} onChanged={load} />}
        {tab === "content" && <ContentTab matterId={id} matter={m} events={events} onChanged={refreshFacts} />}
        {tab === "documents" && window.LedgerAttachments && <window.LedgerAttachments entityType="matter" entityId={id} onCount={setDocCount} />}
        </div>
        {window.LedgerActivityRail ? <window.LedgerActivityRail matterId={id} onMatterChanged={refreshFacts} /> : null}
      </div>
    );
  }

  // ---- Participants tab -----------------------------------------------------
  // People are grouped by their side of the case so the cast reads top-to-bottom
  // the way the case does: the defense, then victims, then the bench/prosecution/
  // law enforcement, then witnesses. Organizations live under the Agencies tab.
  const PARTICIPANT_GROUPS = [
    { key: "defense", label: "Defendant & defense", accent: "var(--lc-slate-600, #5b6678)", roles: ["defendant", "defense_attorney"] },
    { key: "victims", label: "Victims & family", accent: "var(--lc-yellow-deep, #8a7400)", roles: ["victim", "family_member"] },
    { key: "bench", label: "Prosecution, bench & law enforcement", accent: "#2f6fbf", roles: ["prosecutor", "judge", "law_enforcement"] },
    { key: "witnesses", label: "Witnesses", accent: "#2e8b74", roles: ["witness"] },
    { key: "other", label: "Other parties", accent: "var(--lc-slate-400, #9aa3b2)", roles: ["other"] },
  ];
  const GROUP_OF = {};
  PARTICIPANT_GROUPS.forEach((g) => g.roles.forEach((r) => { GROUP_OF[r] = g; }));

  function PersonCard({ r, accent, onOpen }) {
    const ind = r.individuals || {};
    const name = ind.first_name ? [ind.first_name, ind.last_name].filter(Boolean).join(" ") : "Unknown";
    const initials = (name.match(/\b\w/g) || ["?"]).slice(0, 2).join("").toUpperCase();
    const aiSuggested = !!(r.ai_field_metadata && r.ai_field_metadata._origin === "ai");
    return (
      <div className="rec-card rec-card--btn" style={{ borderLeft: "3px solid " + accent }}
        onClick={() => onOpen && onOpen(r)} title="Open details">
        <div className="rec-top">
          <span className="avatar">{initials}</span>
          <div style={{ minWidth: 0 }}>
            <div className="rec-name">{name} {r.is_primary && <span className="ic-wrap"><i data-lucide="star" style={{ width: 12, height: 12, color: "var(--lc-yellow-deep)" }} /></span>}</div>
            <span className="chip chip--role">{T(r.role)}</span>
            {aiSuggested && <span className="prov prov--ai" style={{ marginLeft: 6 }}><i data-lucide="sparkles" />AI</span>}
          </div>
          <i data-lucide="chevron-right" className="rec-open" />
        </div>
        <div className="rec-contact">
          {r.representation && <span><i data-lucide="gavel" /> {r.representation}</span>}
          {ind.phone && <span><i data-lucide="phone" /> {ind.phone}</span>}
          {ind.email && <span><i data-lucide="mail" /> {ind.email}</span>}
          {r.notes && <span><i data-lucide="sticky-note" /> {r.notes.length > 60 ? r.notes.slice(0, 60) + "…" : r.notes}</span>}
          {!r.representation && !ind.phone && !ind.email && !r.notes && <span style={{ color: "var(--text-faint)" }}>No contact on file</span>}
        </div>
      </div>
    );
  }

  // Detail + edit panel for a single participant (person). Edits both the
  // person record (individuals) and the role on this matter (matter_participants).
  function ParticipantDetail({ row, onClose, onSaved, onRemoved }) {
    const { Button, Input, Select } = DS;
    const ind = row.individuals || {};
    const [editing, setEditing] = React.useState(false);
    const init = {
      first_name: ind.first_name || "", last_name: ind.last_name || "",
      individual_type: ind.individual_type || "", age: ind.age != null ? String(ind.age) : "",
      phone: ind.phone || "", email: ind.email || "", address: ind.address || "",
      role: row.role || "other", representation: row.representation || "", is_primary: !!row.is_primary, notes: row.notes || "",
    };
    const [f, setF] = React.useState(init);
    const [busy, setBusy] = React.useState(false);
    const [err, setErr] = React.useState(null);
    React.useEffect(() => { icons(); }, [editing]);
    const set = (k) => (e) => setF((s) => ({ ...s, [k]: e.target.value }));
    const name = [f.first_name, f.last_name].filter(Boolean).join(" ") || "Unknown";
    const aiSuggested = !!(row.ai_field_metadata && row.ai_field_metadata._origin === "ai");

    async function save() {
      if (!f.first_name.trim()) { setErr("First name is required."); return; }
      setBusy(true); setErr(null);
      try {
        if (row.individual_id) {
          await window.LedgerAPI.updateIndividual(row.individual_id, {
            first_name: f.first_name.trim(), last_name: f.last_name.trim() || null,
            individual_type: f.individual_type || null,
            age: f.age === "" || f.age == null ? null : parseInt(f.age, 10),
            phone: f.phone || null, email: f.email || null, address: f.address || null,
          });
        }
        await window.LedgerAPI.updateParticipant(row.participant_id, {
          role: f.role, representation: f.representation || null, is_primary: f.is_primary, notes: f.notes || null,
        });
        setEditing(false); onSaved && onSaved();
      } catch (e) { setErr(e.message || String(e)); }
      finally { setBusy(false); }
    }

    const Row = ({ icon, label, value }) => value ? (
      <div className="pd-row"><i data-lucide={icon} /><div><div className="pd-label">{label}</div><div className="pd-val">{value}</div></div></div>
    ) : null;

    return (
      <div className="modal-backdrop" onClick={onClose}>
        <div className="modal" onClick={(e) => e.stopPropagation()}>
          <div className="modal-head">
            <div>
              <div className="eyebrow">{T(f.role)}{f.is_primary ? " · Primary" : ""}{aiSuggested ? " · AI-suggested" : ""}</div>
              <h3 className="modal-title">{name}</h3>
            </div>
            <button type="button" className="link-btn" onClick={onClose}><i data-lucide="x" /></button>
          </div>

          {err && <div className="login__err" style={{ margin: "0 0 12px" }}>{err}</div>}

          {!editing ? (
            <div className="pd-body">
              <Row icon="badge" label="Role" value={T(f.role)} />
              <Row icon="gavel" label="Representation / attorney" value={f.representation} />
              <Row icon="phone" label="Phone" value={f.phone} />
              <Row icon="mail" label="Email" value={f.email} />
              <Row icon="map-pin" label="Address" value={f.address} />
              <Row icon="cake" label="Age" value={f.age} />
              <Row icon="user" label="Person type" value={f.individual_type ? T(f.individual_type) : ""} />
              <Row icon="sticky-note" label="Notes" value={f.notes} />
              {!f.representation && !f.phone && !f.email && !f.address && !f.notes &&
                <div style={{ fontSize: 12.5, color: "var(--text-muted)" }}>No detail on file yet — Edit to add contact info and notes.</div>}
            </div>
          ) : (
            <div className="form-grid">
              <Input label="First name *" value={f.first_name} onChange={set("first_name")} />
              <Input label="Last name" value={f.last_name} onChange={set("last_name")} />
              <Select label="Role" value={f.role} onChange={set("role")} options={opts(V.participant_role)} />
              <Select label="Person type" value={f.individual_type} onChange={set("individual_type")} options={opts(V.individual_type, true)} />
              <Input type="number" label="Age" value={f.age} onChange={set("age")} />
              <Input label="Representation / attorney" value={f.representation} onChange={set("representation")} />
              <Input label="Phone" value={f.phone} onChange={set("phone")} />
              <Input label="Email" value={f.email} onChange={set("email")} />
              <Input label="Address" value={f.address} onChange={set("address")} />
              <label className="field-label" style={{ display: "flex", alignItems: "center", gap: 8, marginTop: 22 }}>
                <input type="checkbox" checked={f.is_primary} onChange={(e) => setF((s) => ({ ...s, is_primary: e.target.checked }))} /> Primary participant
              </label>
              <div className="full">
                <label className="field-label">Notes</label>
                <textarea className="lc-input ledger-textarea" value={f.notes} onChange={set("notes")} placeholder="Role context, leads, do-not-contact, etc." />
              </div>
            </div>
          )}

          <div className="form-actions form-actions--split">
            {!editing && (
              <DeleteLink label="Remove from matter" armedLabel="Click again to remove"
                onError={setErr}
                onDelete={async () => { await window.LedgerAPI.removeParticipant(row.participant_id, row.individual_id); onRemoved ? onRemoved() : (onSaved && onSaved()); }} />
            )}
            <div className="fa-right">
              {!editing ? (
                <React.Fragment>
                  <Button variant="ghost" onClick={onClose}>Close</Button>
                  <Button variant="primary" icon="pencil" onClick={() => setEditing(true)}>Edit</Button>
                </React.Fragment>
              ) : (
                <React.Fragment>
                  <Button variant="ghost" onClick={() => { setF(init); setEditing(false); setErr(null); }} disabled={busy}>Cancel</Button>
                  <Button variant="primary" icon="check" onClick={save} disabled={busy}>{busy ? "Saving…" : "Save"}</Button>
                </React.Fragment>
              )}
            </div>
          </div>
        </div>
      </div>
    );
  }

  function ParticipantsTab({ matterId, onChanged, onJump }) {
    const { Button, Input, Select } = DS;
    const [rows, setRows] = React.useState(null);
    const [adding, setAdding] = React.useState(false);
    const [selected, setSelected] = React.useState(null); // participant row open in detail modal
    const blank = { name: "", role: "defendant", representation: "", phone: "", email: "", age: "", is_primary: false };
    const [f, setF] = React.useState(blank);
    const [busy, setBusy] = React.useState(false);
    const [err, setErr] = React.useState(null);

    const load = React.useCallback(() => {
      window.LedgerAPI.listParticipants(matterId).then(setRows).catch(() => setRows([]));
    }, [matterId]);
    React.useEffect(() => { load(); }, [load]);
    React.useEffect(() => { icons(); }, [rows, adding, selected]);

    const set = (k) => (e) => setF((s) => ({ ...s, [k]: e.target.value }));
    async function submit() {
      if (!f.name.trim()) { setErr("Name is required."); return; }
      setBusy(true); setErr(null);
      try {
        await window.LedgerAPI.createParticipant({ matter_id: matterId, ...f, is_primary: f.is_primary });
        setF(blank); setAdding(false); load(); onChanged && onChanged();
      } catch (e) { setErr(e.message || String(e)); }
      finally { setBusy(false); }
    }

    // People only (individual_id set); organizations are surfaced under Agencies.
    const people = (rows || []).filter((r) => r.individual_id);
    const orgCount = (rows || []).filter((r) => r.entity_id).length;
    const grouped = PARTICIPANT_GROUPS.map((g) => ({
      g,
      members: people
        .filter((r) => (GROUP_OF[r.role] || GROUP_OF.other) === g)
        .sort((a, b) => (b.is_primary ? 1 : 0) - (a.is_primary ? 1 : 0)),
    })).filter((x) => x.members.length);

    return (
      <div>
        <div className="tab-head">
          <h3>Participants<window.LedgerComment section="Participants" /></h3>
          <Button size="sm" variant="primary" icon="user-plus" onClick={() => setAdding((a) => !a)}>{adding ? "Close" : "Add participant"}</Button>
        </div>

        {adding && (
          <DS.Card style={{ marginBottom: 14 }}><DS.Card.Body>
            {err && <div className="login__err" style={{ marginBottom: 12 }}>{err}</div>}
            <div className="form-grid">
              <Input label="Name *" placeholder="e.g. George Pino" value={f.name} onChange={set("name")} />
              <Select label="Role" value={f.role} onChange={set("role")} options={opts(V.participant_role)} />
              <Input label="Representation / attorney" value={f.representation} onChange={set("representation")} />
              <Input label="Phone" value={f.phone} onChange={set("phone")} />
              <Input label="Email" value={f.email} onChange={set("email")} />
              <Input type="number" label="Age" value={f.age} onChange={set("age")} />
              <label className="field-label" style={{ display: "flex", alignItems: "center", gap: 8, marginTop: 22 }}>
                <input type="checkbox" checked={f.is_primary} onChange={(e) => setF((s) => ({ ...s, is_primary: e.target.checked }))} /> Primary participant
              </label>
            </div>
            <div style={{ fontSize: 11.5, color: "var(--text-muted)", marginTop: 2 }}>
              Adding a court, police department, or other organization? Use the <b>Agencies</b> tab instead.
            </div>
            <div className="form-actions">
              <Button variant="primary" icon="check" onClick={submit} disabled={busy}>{busy ? "Saving…" : "Save participant"}</Button>
            </div>
          </DS.Card.Body></DS.Card>
        )}

        {rows === null && <div style={{ color: "var(--text-muted)", fontSize: 13 }}>Loading…</div>}
        {rows && people.length === 0 && <div className="ledger-empty"><i data-lucide="users" /><b>No people yet</b><span>Add one above, or Enrich with AI.</span></div>}

        {grouped.map(({ g, members }) => (
          <div key={g.key} style={{ marginBottom: 18 }}>
            <div className="part-grouphead">
              <span className="part-dot" style={{ background: g.accent }} />
              {g.label}
              <span className="part-count">{members.length}</span>
            </div>
            <div className="rec-grid">
              {members.map((r) => <PersonCard key={r.participant_id} r={r} accent={g.accent} onOpen={setSelected} />)}
            </div>
          </div>
        ))}

        {orgCount > 0 && (
          <div className="part-orglink" onClick={() => onJump && onJump("agencies")}>
            <i data-lucide="building-2" />
            {orgCount} organization{orgCount > 1 ? "s" : ""} linked to this matter — see the <b>Agencies</b> tab
            <i data-lucide="arrow-right" />
          </div>
        )}

        {selected && (
          <ParticipantDetail row={selected} onClose={() => setSelected(null)}
            onSaved={() => { setSelected(null); load(); onChanged && onChanged(); }}
            onRemoved={() => { setSelected(null); load(); onChanged && onChanged(); }} />
        )}
      </div>
    );
  }

  // ---- Agencies tab ---------------------------------------------------------
  // Entities-as-FOIA-agencies, first-class. Shows every organization involved in
  // the matter (court of record, participant orgs, FOIA targets) with the agency
  // contact fields the Content Center needs for records requests.
  function AgenciesTab({ matterId, entities, reloadEntities, onChanged }) {
    const { Button, Input, Select } = DS;
    const [rows, setRows] = React.useState(null);
    const [adding, setAdding] = React.useState(false);
    const newBlank = {
      mode: "existing", entity_id: "", role: "investigating_agency",
      entity_name: "", entity_type: "police_department", state: "", city: "",
      website: "", contact_name: "", email: "", phone: "", notes: "",
    };
    const [f, setF] = React.useState(newBlank);
    const [busy, setBusy] = React.useState(false);
    const [err, setErr] = React.useState(null);

    const load = React.useCallback(() => {
      window.LedgerAPI.listMatterAgencies(matterId).then(setRows).catch(() => setRows([]));
    }, [matterId]);
    React.useEffect(() => { load(); }, [load]);
    React.useEffect(() => { icons(); }, [rows, adding, f.mode]);

    const set = (k) => (e) => setF((s) => ({ ...s, [k]: e.target.value }));
    const openAdd = () => { setF(newBlank); setErr(null); setAdding((a) => !a); };
    const openEdit = (ent) => {
      setErr(null);
      setF({
        mode: "edit", entity_id: ent.entity_id, role: "",
        entity_name: ent.entity_name || "", entity_type: ent.entity_type || "other",
        state: ent.state || "", city: ent.city || "", website: ent.website || "",
        contact_name: ent.contact_name || "", email: ent.email || "", phone: ent.phone || "",
        notes: ent.notes || "",
      });
      setAdding(true);
    };

    const entityPatch = () => ({
      entity_name: f.entity_name.trim(), entity_type: f.entity_type || null,
      state: f.state || null, city: f.city || null, website: f.website || null,
      contact_name: f.contact_name || null, email: f.email || null, phone: f.phone || null,
      notes: f.notes || null,
    });

    async function submit() {
      setBusy(true); setErr(null);
      try {
        if (f.mode === "edit") {
          await window.LedgerAPI.updateEntity(f.entity_id, entityPatch());
          reloadEntities && reloadEntities();
        } else if (f.mode === "existing") {
          if (!f.entity_id) { setErr("Pick an agency, or switch to “New agency”."); setBusy(false); return; }
          await window.LedgerAPI.addAgencyToMatter({ matter_id: matterId, entity_id: f.entity_id, role: f.role });
        } else {
          if (!f.entity_name.trim()) { setErr("Agency name is required."); setBusy(false); return; }
          const ent = await window.LedgerAPI.createEntity(entityPatch());
          reloadEntities && reloadEntities();
          await window.LedgerAPI.addAgencyToMatter({ matter_id: matterId, entity_id: ent.entity_id, role: f.role });
        }
        setF(newBlank); setAdding(false); load(); onChanged && onChanged();
      } catch (e) { setErr(e.message || String(e)); }
      finally { setBusy(false); }
    }

    const isEdit = f.mode === "edit", isNew = f.mode === "new";
    const showFields = isEdit || isNew; // contact-detail fields only when creating/editing the entity itself

    return (
      <div>
        <div className="tab-head">
          <h3>Agencies &amp; organizations<window.LedgerComment section="Agencies & organizations" /></h3>
          <Button size="sm" variant="primary" icon="plus" onClick={openAdd}>{adding ? "Close" : "Add agency"}</Button>
        </div>

        {adding && (
          <DS.Card style={{ marginBottom: 14 }}><DS.Card.Body>
            {err && <div className="login__err" style={{ marginBottom: 12 }}>{err}</div>}
            {!isEdit && (
              <div className="seg" style={{ marginBottom: 12 }}>
                <button className={f.mode === "existing" ? "seg--on" : ""} onClick={() => setF((s) => ({ ...s, mode: "existing" }))}>Link existing</button>
                <button className={f.mode === "new" ? "seg--on" : ""} onClick={() => setF((s) => ({ ...s, mode: "new" }))}>New agency</button>
              </div>
            )}
            <div className="form-grid">
              {f.mode === "existing" ? (
                <Select label="Agency" value={f.entity_id} onChange={set("entity_id")}
                  options={[{ value: "", label: "— select an agency —" }].concat((entities || []).map((e) => ({ value: e.entity_id, label: window.agencyLabel(e) })))} />
              ) : (
                <Input label="Agency name *" value={f.entity_name} onChange={set("entity_name")} placeholder="e.g. Miami-Dade Police Dept" />
              )}
              {!isEdit && (
                <Select label="Role on this matter" value={f.role} onChange={set("role")} options={opts(V.agency_role)} />
              )}
              {showFields && (
                <React.Fragment>
                  <Select label="Agency type" value={f.entity_type} onChange={set("entity_type")} options={opts(V.entity_type)} />
                  <Input label="State" value={f.state} onChange={set("state")} placeholder="e.g. FL" />
                  <Input label="City" value={f.city} onChange={set("city")} />
                  <Input label="Website / records portal" value={f.website} onChange={set("website")} placeholder="https://" />
                  <Input label="Contact at agency" value={f.contact_name} onChange={set("contact_name")} placeholder="FOIA officer / records clerk" />
                  <Input label="Email" value={f.email} onChange={set("email")} />
                  <Input label="Phone" value={f.phone} onChange={set("phone")} />
                  <div className="full">
                    <label className="field-label">Notes</label>
                    <textarea className="lc-input ledger-textarea" value={f.notes} onChange={set("notes")} />
                  </div>
                </React.Fragment>
              )}
            </div>
            <div className="form-actions">
              <Button variant="primary" icon="check" onClick={submit} disabled={busy}>
                {busy ? "Saving…" : isEdit ? "Save agency" : f.mode === "existing" ? "Link agency" : "Create & link"}
              </Button>
            </div>
          </DS.Card.Body></DS.Card>
        )}

        {rows === null && <div style={{ color: "var(--text-muted)", fontSize: 13 }}>Loading…</div>}
        {rows && rows.length === 0 && <div className="ledger-empty"><i data-lucide="building-2" /><b>No agencies yet</b><span>Link the court or a police department, or add one when filing a FOIA request.</span></div>}
        {rows && rows.length > 0 && (
          <div className="ag-grid">
            {rows.map((a) => (
              <div className="ag-card" key={a.entity_id}>
                <div className="ag-head">
                  <div style={{ minWidth: 0 }}>
                    <div className="ag-name">
                      <span><i data-lucide="building-2" /></span>
                      {a.entity_id && window.LedgerOpenAgency
                        ? <button type="button" className="ag-namelink" title="Open this agency's profile"
                            onClick={() => window.LedgerOpenAgency(a.entity_id)}>{a.entity_name}</button>
                        : a.entity_name}
                    </div>
                    <div className="ag-meta">
                      {a.entity_type && <span className="chip">{T(a.entity_type)}</span>}
                      {(a.city || a.state) && <span className="ag-loc"><i data-lucide="map-pin" /> {[a.city, a.state].filter(Boolean).join(", ")}</span>}
                    </div>
                  </div>
                  <div className="ag-actions">
                    <button type="button" className="link-btn" onClick={() => openEdit(a)}><i data-lucide="pencil" /> Edit</button>
                    {a.participant_id && (
                      <DeleteLink label="Remove" armedLabel="Confirm remove" onError={setErr}
                        onDelete={async () => { await window.LedgerAPI.removeAgencyFromMatter(a.participant_id); load(); onChanged && onChanged(); }} />
                    )}
                  </div>
                </div>
                <div className="ag-contact">
                  {a.contact_name && <span><i data-lucide="user" /> {a.contact_name}</span>}
                  {a.phone && <span><i data-lucide="phone" /> {a.phone}</span>}
                  {a.email && <span><i data-lucide="mail" /> {a.email}</span>}
                  {a.website && <span><i data-lucide="globe" /> <a href={a.website} target="_blank" rel="noreferrer">{a.website.replace(/^https?:\/\//, "")}</a></span>}
                  {!a.contact_name && !a.phone && !a.email && !a.website && <span style={{ color: "var(--text-faint)" }}>No contact on file — Edit to add records-request details.</span>}
                </div>
                {a.notes && <div className="ag-notes">{a.notes}</div>}
                {a.involvement && a.involvement.length > 0 && (
                  <div className="ag-inv">{a.involvement.map((t, i) => <span key={i} className="chip chip--inv">{t}</span>)}</div>
                )}
              </div>
            ))}
          </div>
        )}
      </div>
    );
  }

  // ---- Case events tab ------------------------------------------------------
  const EVENT_BLANK = { event_type: "hearing", event_date: "", end_date: "", event_time: "",
    judge_name: "", courthouse: "", address: "", courtroom: "", location: "",
    defendant_present: "", camera_allowed: "", public_access: "", outcome_summary: "" };
  // case_events row -> form values (normalize dates/time/bools for the inputs).
  const boolStr = (v) => (v === true ? "true" : v === false ? "false" : "");
  const eventToForm = (r) => ({
    event_type: r.event_type || "hearing",
    event_date: r.event_date || "", end_date: r.end_date || "",
    event_time: (r.event_time || "").slice(0, 5),
    judge_name: r.judge_name || "", courthouse: r.courthouse || "",
    address: r.address || "", courtroom: r.courtroom || "", location: r.location || "",
    defendant_present: boolStr(r.defendant_present),
    camera_allowed: boolStr(r.camera_allowed),
    public_access: boolStr(r.public_access),
    outcome_summary: r.outcome_summary || "",
  });

  // A date moved in Ledger (move_case_event, 0158) is Ledger's from then on.
  const movedInLedger = (r) => !!(r.ai_field_metadata && r.ai_field_metadata.event_date
                                  && r.ai_field_metadata.event_date.source === "human_confirmed");
  const movedTitle = (r) => {
    const m = r.ai_field_metadata.event_date;
    return "Date set in Ledger" + (m.by ? " by " + m.by : "") + (m.populated_at ? " on " + window.LedgerTime.fmtDate(m.populated_at) : "")
      + ". The nightly Airtable sync won't change it.";
  };

  function EventsTab({ matterId, onChanged }) {
    const { Button, Input, Select } = DS;
    const [rows, setRows] = React.useState(null);
    // mode: null (list only) | 'add' | 'edit'; editId holds the row being edited.
    const [mode, setMode] = React.useState(null);
    const [editId, setEditId] = React.useState(null);
    const [orig, setOrig] = React.useState(null);   // the row as loaded, to tell a date move from other edits
    const [f, setF] = React.useState(EVENT_BLANK);
    const [busy, setBusy] = React.useState(false);
    const [err, setErr] = React.useState(null);

    const load = React.useCallback(() => {
      window.LedgerAPI.listEvents(matterId).then(setRows).catch(() => setRows([]));
    }, [matterId]);
    React.useEffect(() => { load(); }, [load]);
    React.useEffect(() => { icons(); }, [rows, mode, f.event_type]);

    const set = (k) => (e) => setF((s) => ({ ...s, [k]: e.target.value }));
    const spanType = f.event_type === "trial_start" || f.event_type === "trial_day";
    const boolOrNull = (v) => (v === "" ? null : v === "true");

    function startAdd() { setF(EVENT_BLANK); setEditId(null); setErr(null); setMode("add"); }
    function startEdit(r) { setF(eventToForm(r)); setOrig(eventToForm(r)); setEditId(r.event_id); setErr(null); setMode("edit"); }
    function cancel() { setMode(null); setEditId(null); setErr(null); }

    async function submit() {
      if (!f.event_date) { setErr("A start date is required."); return; }
      if (f.end_date && f.end_date < f.event_date) { setErr("End date can’t be before the start date."); return; }
      setBusy(true); setErr(null);
      const payload = {
        event_type: f.event_type,
        event_date: f.event_date, end_date: (spanType && f.end_date) ? f.end_date : null,
        event_time: f.event_time || null,
        judge_name: f.judge_name || null, courthouse: f.courthouse || null,
        address: f.address || null, courtroom: f.courtroom || null, location: f.location || null,
        defendant_present: boolOrNull(f.defendant_present),
        camera_allowed: boolOrNull(f.camera_allowed),
        public_access: boolOrNull(f.public_access),
        outcome_summary: f.outcome_summary || null,
      };
      try {
        if (mode === "edit") {
          // Dates go through move_case_event (0158) so the change is logged and the
          // nightly Airtable sync can't put the old date back. Everything else is a
          // plain update.
          const { event_date, end_date, event_time, ...rest } = payload;
          const moved = orig && (f.event_date !== orig.event_date || (spanType ? f.end_date : "") !== (spanType ? orig.end_date : "")
                                 || f.event_time !== orig.event_time);
          if (moved) await window.LedgerAPI.moveCaseEvent(editId, { date: event_date, endDate: end_date, time: event_time });
          await window.LedgerAPI.updateEvent(editId, rest);
        }
        else await window.LedgerAPI.createEvent({ matter_id: matterId, ...payload });
        cancel(); load(); onChanged && onChanged();
      } catch (e) { setErr(e.message || String(e)); }
      finally { setBusy(false); }
    }

    return (
      <div>
        <div className="tab-head">
          <h3>Case events<window.LedgerComment section="Case events" /></h3>
          <Button size="sm" variant="primary" icon="calendar-plus" onClick={() => (mode ? cancel() : startAdd())}>{mode ? "Close" : "Add event"}</Button>
        </div>

        {mode && (
          <DS.Card style={{ marginBottom: 14 }}><DS.Card.Body>
            <div style={{ fontWeight: 600, marginBottom: 10 }}>{mode === "edit" ? "Edit event" : "New event"}</div>
            {err && <div className="login__err" style={{ marginBottom: 12 }}>{err}</div>}
            <div className="form-grid">
              <Select label="Event type" value={f.event_type} onChange={set("event_type")} options={opts(V.event_type)} />
              <Input type="date" label={spanType ? "Trial start date" : "Date"} value={f.event_date} onChange={set("event_date")} />
              {spanType && <Input type="date" label="Trial end date" value={f.end_date} onChange={set("end_date")} />}
              <Input type="time" label="Time (court’s local time)" value={f.event_time} onChange={set("event_time")} />
              <Input label="Judge" value={f.judge_name} onChange={set("judge_name")} />
              <Input label="Courthouse" value={f.courthouse} onChange={set("courthouse")} />
              <Input label="Address" value={f.address} onChange={set("address")} />
              <Input label="Courtroom" value={f.courtroom} onChange={set("courtroom")} />
              <Select label="Defendant present" value={f.defendant_present} onChange={set("defendant_present")} options={BOOL_OPTS} />
              <Select label="Cameras allowed" value={f.camera_allowed} onChange={set("camera_allowed")} options={BOOL_OPTS} />
              <div title="Is the hearing open to the public, so we can get a person into the courtroom? Separate from whether cameras are allowed.">
                <Select label="Public access (open to the public?)" value={f.public_access} onChange={set("public_access")} options={BOOL_OPTS} />
              </div>
              <div className="full">
                <label className="field-label">Outcome summary</label>
                <textarea className="lc-input ledger-textarea" value={f.outcome_summary} onChange={set("outcome_summary")} />
              </div>
            </div>
            <div className="form-actions">
              <button type="button" className="link-btn" onClick={cancel} disabled={busy}>Cancel</button>
              <Button variant="primary" icon="check" onClick={submit} disabled={busy}>{busy ? "Saving…" : mode === "edit" ? "Save changes" : "Save event"}</Button>
            </div>
          </DS.Card.Body></DS.Card>
        )}

        {rows === null && <div style={{ color: "var(--text-muted)", fontSize: 13 }}>Loading…</div>}
        {rows && rows.length === 0 && <div className="ledger-empty"><i data-lucide="calendar" /><b>No events yet</b><span>Add hearings/trial dates, or Enrich with AI.</span></div>}
        {rows && rows.length > 0 && (
          <DS.Card><DS.Card.Body><ul className="linklist">
            {rows.map((r) => (
              <li key={r.event_id} className={editId === r.event_id ? "is-editing" : ""}>
                <div className="li-main">
                  {/* A deadline reads as what is due, in the court's words — "Plea
                      Agreement due by" — rather than the bare type name. */}
                  <b>{r.event_type === "deadline"
                        ? <><span className="evtag evtag--deadline"><i data-lucide="alarm-clock" />Deadline</span>{" "}{(r.notes || "").replace(/^Deadline:\s*/i, "") || "Filing deadline"}</>
                        : T(r.event_type)}
                     {r.ai_field_metadata && r.ai_field_metadata._origin === "ai" && <span className="prov prov--ai"><i data-lucide="sparkles" />AI</span>}
                     {movedInLedger(r) && <span className="prov" title={movedTitle(r)}><i data-lucide="calendar-check" />Moved in Ledger</span>}</b>
                  <span>{r.event_date ? window.LedgerTime.fmtDate(r.event_date) : "no date"}{r.end_date && r.end_date !== r.event_date ? " – " + window.LedgerTime.fmtDate(r.end_date) : ""}{r.event_time ? " · " + r.event_time.slice(0, 5) : ""}{r.judge_name ? " · " + r.judge_name : ""}{[r.courthouse, r.courtroom, r.address].filter(Boolean).join(", ") ? " · " + [r.courthouse, r.courtroom, r.address].filter(Boolean).join(", ") : (r.location ? " · " + r.location : "")}{r.outcome_summary ? " — " + r.outcome_summary : ""}</span>
                </div>
                {/* G4: the building's production notes. A bare id outside Content Ops (spec D12). */}
                {r.courthouse_id && window.LedgerCourthouses && window.LedgerCourthouses.canUse() && (
                  <button type="button" className="link-btn" onClick={() => window.LedgerOpenCourthouse(r.courthouse_id)}><span className="ic-wrap"><i data-lucide="building" /></span> Courthouse</button>
                )}
                <button type="button" className="link-btn" onClick={() => startEdit(r)}><i data-lucide="pencil" /> Edit</button>
                <DeleteLink label="" armedLabel="Confirm" onError={setErr}
                  onDelete={async () => { await window.LedgerAPI.deleteEvent(r.event_id); if (editId === r.event_id) cancel(); load(); onChanged && onChanged(); }} />
              </li>
            ))}
          </ul></DS.Card.Body></DS.Card>
        )}
      </div>
    );
  }

  // ---- Coverage tab ---------------------------------------------------------
  // WHAT WE AND OTHERS HAVE PUBLISHED (2026-10-08, operator). Two sections:
  //   1. Law&Crime coverage — our articles and video (N links) AND the show episodes
  //      about this case. Episodes used to be a section of their own; they are our
  //      coverage, so they sit with it. News articles land here too.
  //   2. Outside coverage — everyone else's. N links.
  // Trial information (how the trial comes in) moved to the Content tab the same day:
  // it describes the material we get, not what we published. See TrialInfo below.
  function CoverageTab({ matterId, onChanged }) {
    return (
      <div>
        <CoverageLinks matterId={matterId} origin="lnc" onChanged={onChanged}
          title="Law&Crime coverage"
          blurb="Our articles, video and show episodes about this case. Add every link — there can be more than one."
          emptyText="No articles or video linked yet.">
          <MatterEpisodes matterId={matterId} />
        </CoverageLinks>

        <CoverageLinks matterId={matterId} origin="outside" onChanged={onChanged}
          title="Outside coverage"
          blurb="Articles, video and reference links from anyone else."
          emptyText="No outside coverage linked yet." />
      </div>
    );
  }

  // ---- Trial information (shown at the top of the Content tab) ---------------
  // How this trial comes in. READ-ONLY. It is the Content Center's record, and it is
  // edited on the Content Center's own coverage board, which is where that desk
  // already works. A second editor on the matter page is a second place for the same
  // fact to drift (specs/matter-page-cleanup-v1.md §4). Lived on the Coverage tab
  // until 2026-10-08. requesting_shows renders here read-only for the same reason.
  function TrialInfo({ matterId, matter, events }) {
    const [setups, setSetups] = React.useState(null);
    const [fre, setFre] = React.useState([]);
    const [pkgs, setPkgs] = React.useState([]);
    const [shows, setShows] = React.useState([]);
    const [err, setErr] = React.useState(null);

    const load = React.useCallback(() => {
      setErr(null);
      Promise.all([
        window.LedgerAPI.listMatterCoverage(matterId),
        window.LedgerAPI.listFreelancers().catch(() => []),
        window.LedgerAPI.listFieldPackages().catch(() => []),
      ]).then(([s, f, p]) => { setSetups(s); setFre(f); setPkgs(p); })
        .catch((e) => setErr(e.message || String(e)));
    }, [matterId]);
    React.useEffect(() => { load(); }, [load]);
    React.useEffect(() => { window.LedgerAPI.showTaxonomy().then(setShows).catch(() => setShows([])); }, []);
    React.useEffect(() => { icons(); }, [setups]);

    const freName = (id) => { const f = fre.find((x) => x.freelancer_id === id); return f ? `${f.first_name} ${f.last_name}`.trim() : null; };
    const pkgName = (id) => { const p = pkgs.find((x) => x.id === id); return p ? p.name : null; };
    const evLabel = (ev) => {
      if (!ev) return "—";
      const d = ev.event_date ? window.LedgerTime.fmtDate(ev.event_date) : "";
      return `${T(ev.event_type)}${d ? " · " + d : ""}${ev.event_time ? " " + ev.event_time.slice(0, 5) : ""}`;
    };
    const evById = (id) => (events || []).find((e) => e.event_id === id);

    const def = (setups || []).find((s) => !s.event_id) || null;
    const overrides = (setups || []).filter((s) => s.event_id);

    // requesting_shows holds show_taxonomy keys since 0108. Fall back to titleizing
    // the raw value so a legacy free-text label still renders as words.
    const showLabel = (k) => { const s = shows.find((x) => x.show_key === k); return s ? s.label : T(k); };
    const reqShows = matter.requesting_shows || [];

    const CU = window.LedgerCoverageUtil;
    const setupSummary = (s) => (
      <span>
        <b style={{ color: "var(--text)" }}>{CU.methodShort(s.ingest_method)}</b>
        {s.delivery_mode ? " · " + CU.deliveryLabel(s.delivery_mode) : ""}
        {s.feed_location ? " · " + s.feed_location : ""}
        {s.photographer_freelancer_id ? " · " + (freName(s.photographer_freelancer_id) || "photographer") : ""}
        {s.field_package_id ? " · " + (pkgName(s.field_package_id) || "kit") : ""}
        {s.status ? " · " + T(s.status) : ""}
      </span>
    );

    return (
      <div>
        {err && <div className="login__err" style={{ marginBottom: 12 }}>{err}</div>}

        <div className="tab-head">
          <h3>Trial information<window.LedgerComment section="Trial information" /></h3>
          <span className="cov-owner"><i data-lucide="lock" /> Content Center</span>
        </div>
        <div className="cov-ownernote">
          How this trial comes in. Recorded by the Content Center on the <b>Coverage</b> board and
          shown here for reference — it isn&rsquo;t edited from the matter.
        </div>

        {setups === null && <div style={{ color: "var(--text-muted)", fontSize: 13 }}>Loading…</div>}
        {setups !== null && (
          <DS.Card style={{ marginBottom: 18 }}><DS.Card.Body>
            {def ? (
              <div>
                <div style={{ fontSize: 13.5 }}>{setupSummary(def)}</div>
                {def.court_restrictions && <div className="row-sub" style={{ marginTop: 6 }}><b>Restrictions:</b> {def.court_restrictions}</div>}
                {def.setup_notes && <div className="row-sub" style={{ marginTop: 4 }}>{def.setup_notes}</div>}
              </div>
            ) : (
              <div style={{ color: "var(--text-muted)", fontSize: 13 }}>
                No trial information recorded for this matter yet.
              </div>
            )}

            {overrides.length > 0 && (
              <div className="cov-overrides">
                <div className="cov-subhead">Per-event exceptions</div>
                {overrides.map((o) => (
                  <div key={o.id} className="cov-override">
                    <b>{evLabel(evById(o.event_id))}</b>
                    <div style={{ fontSize: 13, marginTop: 3 }}>{setupSummary(o)}</div>
                    {o.court_restrictions && <div className="row-sub" style={{ marginTop: 3 }}><b>Restrictions:</b> {o.court_restrictions}</div>}
                  </div>
                ))}
              </div>
            )}

            <div className="cov-reqshows">
              <label className="field-label">Requesting shows</label>
              {reqShows.length ? (
                <div className="chips-row">
                  {reqShows.map((k) => <span key={k} className="chip">{showLabel(k)}</span>)}
                </div>
              ) : (
                <div style={{ fontSize: 12.5, color: "var(--text-muted)" }}>None recorded.</div>
              )}
            </div>
          </DS.Card.Body></DS.Card>
        )}

      </div>
    );
  }

  // Episodes about this matter, from the Editorial layer (matter_coverage_pieces, 0131).
  // Every Workspace user sees the list; the episode opens in the Editorial app only for
  // people who hold it, since that is where the episode page lives. Proposed links show
  // with a dashed marker so nobody mistakes a suggestion for a fact.
  function MatterEpisodes({ matterId }) {
    const [rows, setRows] = React.useState(null);
    const [err, setErr] = React.useState(null);
    const [all, setAll] = React.useState(false);
    React.useEffect(() => {
      setRows(null); setErr(null);
      const ed = window.LedgerAPI.ed;
      if (!ed) { setRows([]); return; }
      ed.matterEpisodes(matterId).then(setRows).catch((e) => setErr(e.message || String(e)));
    }, [matterId]);
    const canOpen = !!(window.LedgerCaps && window.LedgerCaps.isEditorial);
    const LIMIT = 12;
    const shown = rows ? (all ? rows : rows.slice(0, LIMIT)) : [];
    return (
      <div style={{ marginBottom: 18 }}>
        {/* A subsection of Law&Crime coverage since 2026-10-08, so a subhead, not a tab head. */}
        <div className="cov-subhead">
          Episodes{rows && rows.length > 0 ? ` · ${rows.length}` : ""}<window.LedgerComment section="Episodes" />
        </div>
        <div className="cov-ownernote">
          From the Social team&rsquo;s Metadata Master Doc and Airtable, synced nightly.
        </div>
        {err && <div className="login__err" style={{ marginBottom: 12 }}>{err}</div>}
        <DS.Card><DS.Card.Body>
          {rows === null && !err && <div style={{ color: "var(--text-muted)", fontSize: 13 }}>Loading…</div>}
          {rows && rows.length === 0 && <div style={{ color: "var(--text-muted)", fontSize: 13 }}>No episodes linked to this matter yet.</div>}
          {shown.map((r) => (
            <div key={r.piece_id} className="ed-cov-row">
              <span className="ed-cov-date">{r.story_date ? window.LedgerTime.fmtDate(r.story_date) : "Undated"}</span>
              <span className="ed-cov-show">{r.show_label}</span>
              <span className="ed-cov-title">
                {canOpen
                  ? <a className="ed-lab" href={`#ed/ep/${r.display_id}`}>{r.title || r.display_id}</a>
                  : <b>{r.title || r.display_id}</b>}
                {r.link_status !== "confirmed" && <span className="ed-pill warn" style={{ marginLeft: 8 }}>Proposed</span>}
              </span>
            </div>
          ))}
          {rows && rows.length > LIMIT && (
            <button type="button" className="ed-chip" style={{ marginTop: 8 }} onClick={() => setAll((v) => !v)}>
              {all ? "Show fewer" : `Show all ${rows.length}`}
            </button>
          )}
        </DS.Card.Body></DS.Card>
      </div>
    );
  }

  // One link list, used twice. The two sections differ by `origin` and by whether a
  // publisher makes sense, which is not enough difference to justify two components
  // — or two tables (see migration 0107).
  function CoverageLinks({ matterId, origin, title, blurb, emptyText, onChanged, children }) {
    const { Button, Input, Select } = DS;
    const [rows, setRows] = React.useState(null);
    const [editing, setEditing] = React.useState(null); // link_id | 'new' | null
    const [err, setErr] = React.useState(null);

    const load = React.useCallback(() => {
      window.LedgerAPI.listCoverageLinks(matterId)
        .then((all) => setRows((all || []).filter((r) => r.origin === origin)))
        .catch((e) => { setErr(e.message || String(e)); setRows([]); });
    }, [matterId, origin]);
    React.useEffect(() => { load(); }, [load]);
    React.useEffect(() => { icons(); }, [rows, editing]);

    async function remove(id) {
      await window.LedgerAPI.deleteCoverageLink(id);
      load(); onChanged && onChanged();
    }

    function LinkForm({ initial }) {
      const isNew = !initial.link_id;
      const [f, setF] = React.useState(() => ({
        url: initial.url || "",
        title: initial.title || "",
        media_type: initial.media_type || "article",
        published_at: initial.published_at || "",
        publisher: initial.publisher || "",
      }));
      const [busy, setBusy] = React.useState(false);
      const set = (k) => (e) => setF((s) => ({ ...s, [k]: e.target.value }));

      async function go() {
        const url = f.url.trim();
        if (!url) { setErr("A link needs a URL."); return; }
        setBusy(true); setErr(null);
        try {
          await window.LedgerAPI.upsertCoverageLink({
            link_id: initial.link_id || undefined,
            matter_id: matterId,
            origin,
            // Bare domains typed without a scheme are the common case and would
            // otherwise resolve relative to the app.
            url: /^https?:\/\//i.test(url) ? url : "https://" + url,
            title: f.title.trim() || null,
            media_type: f.media_type || null,
            published_at: f.published_at || null,
            publisher: origin === "outside" ? (f.publisher.trim() || null) : null,
            source: "manual",
          });
          setEditing(null); load(); onChanged && onChanged();
        } catch (e) {
          // The partial unique index on (matter_id, url) is the guard against the
          // same story being added twice from two sections of the same page.
          setErr(String(e.message || e).includes("mcl_matter_url_uniq")
            ? "That link is already on this matter."
            : (e.message || String(e)));
        } finally { setBusy(false); }
      }

      return (
        <DS.Card style={{ marginBottom: 10 }}><DS.Card.Body>
          <div className="form-grid">
            <div className="full">
              <Input label="Link *" value={f.url} onChange={set("url")}
                placeholder="https://lawandcrime.com/…" />
            </div>
            <div className="full">
              <Input label="Title" value={f.title} onChange={set("title")}
                placeholder="Headline, or what this is" />
            </div>
            <Select label="Type" value={f.media_type} onChange={set("media_type")}
              options={opts(V.coverage_link_media_type)} />
            <Input type="date" label="Published" value={f.published_at} onChange={set("published_at")} />
            {origin === "outside" &&
              <Input label="Publisher" value={f.publisher} onChange={set("publisher")} placeholder="AP, NY Post…" />}
          </div>
          <div className="form-actions">
            <button type="button" className="link-btn" onClick={() => { setEditing(null); setErr(null); }} disabled={busy}>Cancel</button>
            <Button variant="primary" icon="check" onClick={go} disabled={busy}>
              {busy ? "Saving…" : isNew ? "Add link" : "Save"}
            </Button>
          </div>
        </DS.Card.Body></DS.Card>
      );
    }

    const typeIcon = (t) => t === "video" ? "play-circle"
      : t === "social" ? "message-circle"
      : t === "podcast" ? "mic" : "file-text";

    return (
      <div className="cov-links">
        <div className="tab-head">
          <h3>{title}<window.LedgerComment section={title} /></h3>
          {editing !== "new" && (
            <Button size="sm" variant="secondary" icon="plus" onClick={() => { setEditing("new"); setErr(null); }}>
              Add link
            </Button>
          )}
        </div>
        <div className="cov-ownernote">{blurb}</div>

        {err && <div className="login__err" style={{ marginBottom: 10 }}>{err}</div>}
        {editing === "new" && <LinkForm initial={{}} />}

        {rows === null && <div style={{ color: "var(--text-muted)", fontSize: 13 }}>Loading…</div>}
        {rows && rows.length === 0 && editing !== "new" && (
          <div className="ledger-empty"><i data-lucide="link" /><b>{emptyText}</b>
            <span>Paste the URL and it shows up here for everyone.</span></div>
        )}

        {rows && rows.length > 0 && (
          <DS.Card style={{ marginBottom: 18 }}><DS.Card.Body><ul className="linklist">
            {rows.map((r) => (
              editing === r.link_id
                ? <li key={r.link_id}><LinkForm initial={r} /></li>
                : (
                  <li key={r.link_id}>
                    <div className="li-main">
                      <a className="ov-link" href={r.url} target="_blank" rel="noreferrer">
                        <i data-lucide={typeIcon(r.media_type)} />
                        {/* Untitled rows are the backfilled ones — show the URL
                            rather than a blank, so the row is still identifiable. */}
                        {r.title || r.url.replace(/^https?:\/\//i, "").replace(/\/$/, "")}
                      </a>
                      <span>
                        {T(r.media_type || "other")}
                        {r.publisher ? " · " + r.publisher : ""}
                        {/* A DATE column: parse as local midnight. `new Date("2026-09-17")` is UTC
                            midnight, which prints as 9/16 anywhere west of Greenwich. */}
                        {r.published_at ? " · " + new Date(r.published_at + "T00:00:00").toLocaleDateString() : ""}
                        {r.source === "editorial_sync" ? " · auto" : ""}
                        {r.source === "trial_tracker" ? " · Trial Tracker" : ""}
                      </span>
                    </div>
                    <div style={{ display: "flex", gap: 8, flex: "none" }}>
                      <button type="button" className="link-btn" onClick={() => { setEditing(r.link_id); setErr(null); }}>
                        <i data-lucide="pencil" /> Edit
                      </button>
                      <DeleteLink label="" armedLabel="Confirm" onError={setErr} onDelete={() => remove(r.link_id)} />
                    </div>
                  </li>
                )
            ))}
          </ul></DS.Card.Body></DS.Card>
        )}
        {/* Law&Crime coverage carries the episode list here (CoverageTab). */}
        {children}
      </div>
    );
  }

  // ---- FOIA & assets tab ----------------------------------------------------
  function daysSince(d) { if (!d) return null; return Math.floor((Date.now() - new Date(d).getTime()) / 86400000); }

  // Asset Requests tab — the matter's slice of the merged Content Center worklist
  // (migrations 0048/0051). Self-serve "Request material": pick what you need
  // (content_kind), which show (defaults to the filer's), the rights basis, a
  // need-by date, and an agency (scoped to this matter's agencies first). State &
  // county default from the matter (§3.7). Each request expands to the agency
  // playbook + residency banner (RequestKit). The old FOIA/Asset toggle is retired.
  function AssetRequestsTab({ matterId, matter, entities, reloadEntities, onChanged }) {
    const { Button, Input, Select } = DS;
    const K = window.LedgerRequestKit;
    const [rows, setRows] = React.useState(null);
    const [matterAgencies, setMatterAgencies] = React.useState([]);
    const [shows, setShows] = React.useState([]);       // show_taxonomy (source of truth)
    const [stateRules, setStateRules] = React.useState([]);
    const [playbooks, setPlaybooks] = React.useState({}); // entity_id -> playbook (lazy)
    const [defaultShow, setDefaultShow] = React.useState("");
    const [adding, setAdding] = React.useState(false);
    const [openId, setOpenId] = React.useState(null);
    const [busy, setBusy] = React.useState(false);
    const [err, setErr] = React.useState(null);

    const mkBlank = React.useCallback(() => ({
      content_kind: [], requesting_shows: defaultShow ? [defaultShow] : [], request_type: "foia",
      agency_id: "", new_agency: "", due_date: "",
      state: (matter && matter.crime_location_state) || "",
      county: (matter && matter.crime_location_county) || "",
      details: "",
    }), [defaultShow, matter]);
    const [f, setF] = React.useState(mkBlank);

    const load = React.useCallback(() => {
      window.LedgerAPI.listAssetRequests(matterId).then(setRows).catch(() => setRows([]));
      window.LedgerAPI.listMatterAgencies(matterId).then(setMatterAgencies).catch(() => setMatterAgencies([]));
    }, [matterId]);
    React.useEffect(() => { load(); }, [load]);
    React.useEffect(() => {
      window.LedgerAPI.showTaxonomy().then(setShows).catch(() => setShows([]));
      window.LedgerAPI.stateFoiaRules().then(setStateRules).catch(() => setStateRules([]));
      window.LedgerAPI.myPrimaryShow().then((s) => { if (s) setDefaultShow(s); }).catch(() => {});
    }, []);
    // Seed the filer's default show into the form once it resolves (if untouched).
    React.useEffect(() => {
      if (defaultShow) setF((x) => (x.requesting_shows.length ? x : { ...x, requesting_shows: [defaultShow] }));
    }, [defaultShow]);
    React.useEffect(() => { icons(); }, [rows, adding, openId, matterAgencies]);

    const showLabel = K.showLabelMap(shows);
    const stateRuleFor = (st) => (stateRules || []).find((r) => r.state === st) || null;
    const entMap = {};
    (entities || []).forEach((e) => { entMap[e.entity_id] = window.agencyLabel(e); });
    (matterAgencies || []).forEach((a) => { entMap[a.entity_id] = window.agencyLabel(a); });
    const set = (k) => (e) => setF((s) => ({ ...s, [k]: e.target.value }));
    const toggleKind = (k) => setF((s) => ({ ...s, content_kind: s.content_kind.includes(k) ? s.content_kind.filter((x) => x !== k) : s.content_kind.concat(k) }));

    // Lazily fetch the agency playbook when a request is expanded.
    async function ensurePlaybook(entityId) {
      if (!entityId || playbooks[entityId]) return;
      try { const p = await window.LedgerAPI.getAgencyPlaybook(entityId); setPlaybooks((m) => ({ ...m, [entityId]: p || {} })); }
      catch { setPlaybooks((m) => ({ ...m, [entityId]: {} })); }
    }
    function toggleOpen(r) {
      const next = openId === r.id ? null : r.id;
      setOpenId(next);
      if (next && r.agency_id) ensurePlaybook(r.agency_id);
    }

    async function submit() {
      if (!f.content_kind.length) { setErr("Pick at least one type of material."); return; }
      setBusy(true); setErr(null);
      try {
        let agencyId = f.agency_id && !K.isHeaderVal(f.agency_id) ? f.agency_id : null;
        if (!agencyId && f.new_agency.trim()) {
          const ent = await window.LedgerAPI.createEntity({ entity_name: f.new_agency.trim(), entity_type: "government_agency", state: f.state || null });
          agencyId = ent.entity_id; reloadEntities && reloadEntities();
        }
        // Associate the agency to the matter so future requests inherit it (§3.7).
        if (agencyId) { try { await window.LedgerAPI.addMatterAgency({ matter_id: matterId, entity_id: agencyId, role: "records_custodian" }); } catch (e) { /* non-fatal */ } }
        await window.LedgerAPI.createAssetRequest({
          matter_id: matterId,
          content_kind: f.content_kind,
          requesting_shows: f.requesting_shows,
          request_type: f.request_type || null,
          agency_id: agencyId,
          state: f.state || null,
          county: f.county || null,
          due_date: f.due_date || null,
          details: f.details || null,
          status: "new",
        });
        setF(mkBlank()); setAdding(false); load(); onChanged && onChanged();
      } catch (e) { setErr(e.message || String(e)); }
      finally { setBusy(false); }
    }

    const agencyOpts = K.buildAgencyOptions(matterAgencies, entities);

    return (
      <div>
        <div className="tab-head">
          <h3>Asset requests<window.LedgerComment section="Asset requests" /></h3>
          <Button size="sm" variant="primary" icon="plus" onClick={() => { setErr(null); setAdding((a) => !a); }}>{adding ? "Close" : "Request material"}</Button>
        </div>

        {adding && (
          <DS.Card style={{ marginBottom: 14 }}><DS.Card.Body>
            {err && <div className="login__err" style={{ marginBottom: 12 }}>{err}</div>}
            <label className="field-label">What do you need?</label>
            <K.ContentKindChips value={f.content_kind} onToggle={toggleKind} />
            <div className="form-grid" style={{ marginTop: 14 }}>
              <K.ShowPicker value={f.requesting_shows} shows={shows} onChange={(v) => setF((s) => ({ ...s, requesting_shows: v }))} />
              <Select label="Rights basis" value={f.request_type} onChange={set("request_type")} options={opts(V.request_type)} />
              <Select label="Agency" value={f.agency_id}
                onChange={(e) => { const v = e.target.value; if (!K.isHeaderVal(v)) set("agency_id")(e); }}
                options={agencyOpts} />
              <Input label="…or new agency name" value={f.new_agency} onChange={set("new_agency")} placeholder="e.g. Miami-Dade PD" />
              <Input label="State" value={f.state} onChange={set("state")} placeholder="e.g. FL" />
              <Input label="County" value={f.county} onChange={set("county")} />
              <Input type="date" label="Need by" value={f.due_date} onChange={set("due_date")} />
              <div className="full">
                <label className="field-label">Notes</label>
                <textarea className="lc-input ledger-textarea" value={f.details} onChange={set("details")} placeholder="What exactly is needed, any report/case numbers, context…" />
              </div>
            </div>
            {f.state && stateRuleFor(f.state) && stateRuleFor(f.state).residency_required && (
              <div className="rq-resid rq-resid--form">
                <i data-lucide="alert-triangle" />
                <div><b>{f.state} requires residency</b> — this will need an outside requester to file.</div>
              </div>
            )}
            <div className="form-actions">
              <Button variant="primary" icon="check" onClick={submit} disabled={busy}>{busy ? "Saving…" : "File request"}</Button>
            </div>
          </DS.Card.Body></DS.Card>
        )}

        {rows === null && <div style={{ color: "var(--text-muted)", fontSize: 13 }}>Loading…</div>}
        {rows && rows.length === 0 && <div className="ledger-empty"><i data-lucide="clipboard-list" /><b>No requests yet</b><span>File a FOIA or asset request with <b>Request material</b> — it lands on the Content Center queue.</span></div>}
        {rows && rows.length > 0 && (
          <DS.Card><DS.Card.Body style={{ padding: 0 }}>
            <ul className="rq-list">
              {rows.map((r) => {
                const overdue = K.isOverdue(r), due = r.due_date, dleft = K.daysUntil(due);
                const isOpenRow = openId === r.id;
                return (
                  <li key={r.id} className={"rq-item" + (isOpenRow ? " rq-item--open" : "")}>
                    <div className="rq-row" onClick={() => toggleOpen(r)}>
                      <div className="rq-main">
                        <div className="rq-title">
                          <span className="rq-id">{r.display_id}</span>
                          {(r.content_kind || []).map((k) => <span key={k} className="chip">{T(k)}</span>)}
                          {r.request_type && <span className="chip chip--basis">{T(r.request_type)}</span>}
                        </div>
                        <div className="rq-sub">
                          {r.requesting_show && <span>{(r.requesting_shows && r.requesting_shows.length ? r.requesting_shows : [r.requesting_show]).map(showLabel).join(" · ")}</span>}
                          {r.agency_id && <span><i data-lucide="building-2" /> {entMap[r.agency_id] || "Agency"}</span>}
                          {due && <span className={overdue ? "overdue" : ""}><i data-lucide="calendar" /> {overdue ? `${Math.abs(dleft)}d overdue` : `due ${due}`}</span>}
                          {r.expecting_more && <span className="rq-more">expecting more</span>}
                        </div>
                      </div>
                      <K.StatusPill status={r.status} />
                      <i data-lucide={isOpenRow ? "chevron-up" : "chevron-down"} className="rq-caret" />
                    </div>
                    {isOpenRow && (
                      <div className="rq-detail">
                        {r.details && <div className="rq-details-text">{r.details}</div>}
                        {r.agency_id
                          ? <K.AgencyPanel playbook={playbooks[r.agency_id]} stateRule={stateRuleFor(r.state)} requestState={r.state} requestCounty={r.county} />
                          : (stateRuleFor(r.state) && stateRuleFor(r.state).residency_required
                              ? <K.AgencyPanel playbook={{}} stateRule={stateRuleFor(r.state)} requestState={r.state} requestCounty={r.county} />
                              : <div className="rq-play-empty">No agency set on this request yet.</div>)}
                        <div className="rq-desk-hint"><i data-lucide="arrow-up-right" /> Deliveries &amp; cost are worked on the <b>Asset Requests</b> desk (Content Center / Media Ops).</div>
                        <div className="rq-detail-actions">
                          <DeleteLink label="Delete request" armedLabel="Confirm delete" onError={setErr}
                            onDelete={async () => { await window.LedgerAPI.deleteAssetRequest(r.id); setOpenId(null); load(); onChanged && onChanged(); }} />
                        </div>
                      </div>
                    )}
                  </li>
                );
              })}
            </ul>
          </DS.Card.Body></DS.Card>
        )}
      </div>
    );
  }

  // ---- Content tab (evidentiary, read-only for now) -------------------------
  // ---- Content tab ----------------------------------------------------------
  // WHERE THE MEDIA ACTUALLY IS. Until 0108 this listed a title and a type for every
  // row and no link on any of them — a list of media you could not open, which is
  // the opposite of what this system is for. The cause was structural: the table's
  // only address was `iconik_asset_id NOT NULL`, and the material is on EVO and in
  // Drive today.
  //
  // Target shape is one row per asset, typed, each with its own link, so trial video
  // is distinguishable from bodycam at a glance. The near-term shape is a link to the
  // folder that holds it — `is_folder`, sorted to the top by the API, because when
  // there is only one answer to "where is this case's material", that is it.
  function ContentTab({ matterId, matter, events, onChanged }) {
    const { Button, Input, Select } = DS;
    const [rows, setRows] = React.useState(null);
    const [editing, setEditing] = React.useState(null); // content_id | 'new' | null
    const [err, setErr] = React.useState(null);

    const load = React.useCallback(() => {
      window.LedgerAPI.listContent(matterId)
        .then(setRows).catch((e) => { setErr(e.message || String(e)); setRows([]); });
    }, [matterId]);
    React.useEffect(() => { load(); }, [load]);
    React.useEffect(() => { icons(); }, [rows, editing]);

    async function remove(id) { await window.LedgerAPI.deleteContent(id); load(); onChanged && onChanged(); }

    const STORAGE_ICON = { iconik: "database", evo: "hard-drive", gdrive: "folder", s3: "cloud", other: "link" };

    function ContentForm({ initial }) {
      const isNew = !initial.content_id;
      const [f, setF] = React.useState(() => ({
        asset_title: initial.asset_title || "",
        content_type: initial.content_type || "other",
        asset_url: initial.asset_url || "",
        storage_system: initial.storage_system || "evo",
        is_folder: initial.is_folder ? "true" : "false",
        description: initial.description || "",
        asset_source: initial.asset_source || "",
      }));
      const [busy, setBusy] = React.useState(false);
      const set = (k) => (e) => setF((s) => ({ ...s, [k]: e.target.value }));

      async function go() {
        const url = f.asset_url.trim();
        // mc_has_address_chk enforces this in the database; saying so here avoids a
        // constraint name reaching the user.
        if (!url && !initial.iconik_asset_id) { setErr("A row needs a link or an Iconik asset id — otherwise nobody can reach the material."); return; }
        setBusy(true); setErr(null);
        try {
          await window.LedgerAPI.upsertContent({
            content_id: initial.content_id || undefined,
            matter_id: matterId,
            asset_title: f.asset_title.trim() || null,
            content_type: f.content_type,
            // A server path (Z:\…) is not a URL and must not be given a scheme.
            asset_url: url ? (/^([a-z]:\\|\\\\|https?:\/\/)/i.test(url) ? url : "https://" + url) : null,
            storage_system: f.storage_system || null,
            is_folder: f.is_folder === "true",
            description: f.description.trim() || null,
            asset_source: f.asset_source.trim() || null,
          });
          setEditing(null); load(); onChanged && onChanged();
        } catch (e) { setErr(e.message || String(e)); }
        finally { setBusy(false); }
      }

      return (
        <DS.Card style={{ marginBottom: 10 }}><DS.Card.Body>
          <div className="form-grid">
            <div className="full">
              <Input label="Link or path *" value={f.asset_url} onChange={set("asset_url")}
                placeholder="Z:\__TRIALS\Case Name  ·  https://…" />
            </div>
            <Input label="Title" value={f.asset_title} onChange={set("asset_title")} placeholder="Day 3 PM session" />
            <Select label="What it is" value={f.content_type} onChange={set("content_type")} options={opts(V.content_type)} />
            <Select label="Where it lives" value={f.storage_system} onChange={set("storage_system")}
              options={opts(V.content_storage_system)} />
            <Select label="Folder or asset" value={f.is_folder} onChange={set("is_folder")}
              options={[{ value: "false", label: "A single asset" }, { value: "true", label: "A folder of material" }]} />
            <Input label="Source" value={f.asset_source} onChange={set("asset_source")} placeholder="Agency, court, AP…" />
            <div className="full">
              <label className="field-label">Description</label>
              <textarea className="lc-input ledger-textarea" value={f.description} onChange={set("description")}
                placeholder="What this is, in words — arresting officer bodycam, verdict day pool feed…" />
            </div>
          </div>
          <div className="form-actions">
            <button type="button" className="link-btn" onClick={() => { setEditing(null); setErr(null); }} disabled={busy}>Cancel</button>
            <Button variant="primary" icon="check" onClick={go} disabled={busy}>
              {busy ? "Saving…" : isNew ? "Add" : "Save"}
            </Button>
          </div>
        </DS.Card.Body></DS.Card>
      );
    }

    function row(r) {
      const href = r.asset_url && /^https?:\/\//i.test(r.asset_url) ? r.asset_url : null;
      const label = r.asset_title || r.description || T(r.content_type);
      return (
        <li key={r.content_id}>
          <div className="li-main">
            {href
              ? <a className="ov-link" href={href} target="_blank" rel="noreferrer">
                  <i data-lucide={r.is_folder ? "folder-open" : (STORAGE_ICON[r.storage_system] || "link")} /> {label}
                </a>
              // A server path is not clickable from a browser, so it renders as
              // selectable text rather than a link that would do nothing.
              : <span className="ct-path">
                  <i data-lucide={r.is_folder ? "folder-open" : "hard-drive"} /> <b>{label}</b>
                  {r.asset_url && <code className="ct-code">{r.asset_url}</code>}
                </span>}
            <span>
              {T(r.content_type)}
              {r.is_folder ? " · folder" : ""}
              {r.storage_system ? " · " + T(r.storage_system) : ""}
              {r.asset_source ? " · " + r.asset_source : ""}
              {r.iconik_asset_id ? " · Iconik" : ""}
            </span>
            {r.description && r.asset_title && <span className="ct-desc">{r.description}</span>}
          </div>
          <div style={{ display: "flex", gap: 8, flex: "none" }}>
            <button type="button" className="link-btn" onClick={() => { setEditing(r.content_id); setErr(null); }}>
              <i data-lucide="pencil" /> Edit
            </button>
            <DeleteLink label="" armedLabel="Confirm" onError={setErr} onDelete={() => remove(r.content_id)} />
          </div>
        </li>
      );
    }

    const folders = (rows || []).filter((r) => r.is_folder);
    const assets = (rows || []).filter((r) => !r.is_folder);

    return (
      <div>
        {/* Trial information first (2026-10-08): how the trial comes in, then what we hold. */}
        {matter && <TrialInfo matterId={matterId} matter={matter} events={events} />}
        <IconikAssets matterId={matterId} />

        <div className="tab-head">
          <h3>Content<window.LedgerComment section="Content" /></h3>
          {editing !== "new" && (
            <Button size="sm" variant="secondary" icon="plus" onClick={() => { setEditing("new"); setErr(null); }}>
              Add material
            </Button>
          )}
        </div>
        <div className="cov-ownernote">
          Every asset we hold for this case and where to find it — Iconik, EVO, Drive. A folder
          link is fine until the assets are listed individually.
        </div>

        {err && <div className="login__err" style={{ marginBottom: 10 }}>{err}</div>}
        {editing === "new" && <ContentForm initial={{}} />}

        {rows === null && <div style={{ color: "var(--text-muted)", fontSize: 13 }}>Loading…</div>}
        {rows && rows.length === 0 && editing !== "new" && (
          <div className="ledger-empty"><i data-lucide="film" /><b>No material linked yet</b>
            <span>Add the folder this case&rsquo;s footage lives in, or link assets one by one.</span></div>
        )}

        {folders.length > 0 && (
          <React.Fragment>
            <div className="cov-subhead">Where the material lives</div>
            <DS.Card style={{ marginBottom: 16 }}><DS.Card.Body>
              <ul className="linklist">{folders.map((r) => (editing === r.content_id ? <li key={r.content_id}><ContentForm initial={r} /></li> : row(r)))}</ul>
            </DS.Card.Body></DS.Card>
          </React.Fragment>
        )}

        {assets.length > 0 && (
          <React.Fragment>
            <div className="cov-subhead">Assets</div>
            <DS.Card><DS.Card.Body>
              <ul className="linklist">{assets.map((r) => (editing === r.content_id ? <li key={r.content_id}><ContentForm initial={r} /></li> : row(r)))}</ul>
            </DS.Card.Body></DS.Card>
          </React.Fragment>
        )}
      </div>
    );
  }

  // ---- In Iconik (0156, shown on the Content tab) ----------------------------
  // How many Iconik assets this matter has and what kind they are, plus a link to
  // each Iconik folder. Read-only: the counts come from what the stamper wrote into
  // Iconik (iconik_stamp_log), because Iconik search gives no facet counts on our
  // tier. Ledger holds the counts and links, never the media (spec:
  // specs/matter-iconik-assets-v1.md). Renders nothing when there is nothing to say.
  const ICONIK_KIND_LABEL = {
    body_cam: "bodycam", dashcam: "dashcam", "911_call": "911 call", trial_footage: "trial",
    crime_scene_photos: "crime scene photos", court_records: "court records",
    interrogation: "interrogation", interview: "interview", evidence: "evidence",
    mugshot: "mugshot", surveillance: "surveillance",
  };
  const iconikKind = (v) => ICONIK_KIND_LABEL[v] || String(v || "").replace(/_/g, " ");

  function IconikAssets({ matterId }) {
    const [d, setD] = React.useState(null);
    const [err, setErr] = React.useState(null);
    React.useEffect(() => {
      let live = true;
      setD(null); setErr(null);
      window.LedgerAPI.matterIconikAssets(matterId)
        .then((x) => live && setD(x)).catch((e) => live && setErr(e.message || String(e)));
      return () => { live = false; };
    }, [matterId]);
    React.useEffect(() => { icons(); }, [d]);

    if (err) return <div className="login__err" style={{ marginBottom: 12 }}>In Iconik: {err}</div>;
    if (!d) return null;
    const folders = d.folders || [];
    if (!d.assets && !folders.length) return null;

    const untyped = d.assets - d.typed;
    const stamped = d.last_stamped_at
      ? new Date(d.last_stamped_at).toLocaleDateString(undefined, { timeZone: "America/New_York", month: "short", day: "numeric", year: "numeric" })
      : null;
    const last = (p) => String(p || "").split("/").pop();

    return (
      <div className="ica">
        <div className="tab-head">
          <h3>In Iconik<window.LedgerComment section="In Iconik" /></h3>
          {d.assets > 0 && <span className="ica-total">{d.assets.toLocaleString()} {d.assets === 1 ? "asset" : "assets"}</span>}
        </div>
        <DS.Card style={{ marginBottom: 18 }}><DS.Card.Body>
          {d.assets > 0
            ? <div className="ica-kinds">
                {(d.kinds || []).map((k) => (
                  <span key={k.value} className="ica-kind"><b>{k.n.toLocaleString()}</b> {iconikKind(k.value)}</span>
                ))}
                {untyped > 0 && <span className="ica-kind ica-untyped"><b>{untyped.toLocaleString()}</b> not typed</span>}
              </div>
            : <div className="ica-note">Folder linked; its assets have not been counted yet.</div>}
          {folders.length > 0 && (
            <ul className="linklist ica-folders">
              {folders.map((f) => (
                <li key={f.canonical_path}>
                  <div className="li-main">
                    {f.iconik_collection_id
                      ? <a className="ov-link" href={"https://app.iconik.io/collection/" + f.iconik_collection_id} target="_blank" rel="noreferrer">
                          <span><i data-lucide="folder-open" /></span> {last(f.canonical_path)}
                        </a>
                      : <span className="ct-path">
                          <span><i data-lucide="folder-open" /></span> <b>{last(f.canonical_path)}</b>
                          <code className="ct-code">{f.canonical_path}</code>
                        </span>}
                    <span>
                      Iconik folder{f.storage_name ? " · " + f.storage_name : ""}
                      {f.direct_children != null ? " · " + f.direct_children + " " + (f.direct_children === 1 ? "item" : "items") + " at top level" : ""}
                    </span>
                  </div>
                </li>
              ))}
            </ul>
          )}
          {d.assets > 0 && (
            <div className="ica-foot">
              Counted by Ledger from the types stamped in Iconik{stamped ? ", last stamped " + stamped : ""}.
              {(d.kinds || []).reduce((a, k) => a + k.n, 0) > d.typed ? " An asset can be more than one kind." : ""}
            </div>
          )}
        </DS.Card.Body></DS.Card>
      </div>
    );
  }

  // ---- Router ---------------------------------------------------------------
  function Matters({ intent, lane, onLane }) {
    const [mode, setMode] = React.useState("list");
    const [curId, setCurId] = React.useState(null);
    const [tagFilter, setTagFilter] = React.useState(null);
    const [tagMap, setTagMap] = React.useState({});

    React.useEffect(() => {
      window.LedgerAPI.listTags().then((ts) => {
        const map = {}; (ts || []).forEach((t) => { map[t.tag_key] = t.label; }); setTagMap(map);
      }).catch(() => {});
    }, []);

    React.useEffect(() => {
      if (!intent) { setMode("list"); return; }
      if (intent.type === "create") { setMode("create"); }
      else if (intent.type === "open" && intent.id) { setCurId(intent.id); setMode("detail"); }
      else if (intent.type === "tag" && intent.tag) { setTagFilter(intent.tag); setMode("list"); }
    }, [intent]);

    const tagLabel = (k) => tagMap[k] || T(k);
    const openTag = (k) => { setTagFilter(k); setMode("list"); };

    if (mode === "create") return <CreateMatter onCancel={() => setMode("list")} onCreated={(id) => { setCurId(id); setMode("detail"); }} />;
    if (mode === "detail" && curId) return <MatterDetail id={curId} onBack={() => setMode("list")} onTag={openTag} />;
    return <MattersList onNew={() => setMode("create")} onOpen={(id) => { setCurId(id); setMode("detail"); }}
      tagFilter={tagFilter} onClearTag={() => setTagFilter(null)} tagLabel={tagLabel} lane={lane} onLane={onLane} />;
  }

  window.LedgerMatters = Matters;
  // Test seam for matter-tabs-harness.html. These two tabs are the only
  // surfaces in the app that cannot be reached without Google sign-in AND the
  // live database, so this is how they get looked at before a deploy.
  window.LedgerMattersTabsForHarness = { CoverageTab, ContentTab, CoverageLinks };
})();
