/* Editorial — the shows and episodes made about matters (E2a: read-only screens).
   Assigns window.LedgerEditorial. Spec: specs/editorial-layer-v1-proposal.md
   ("Editorial app", operator layout 2026-10-01). Data: window.LedgerAPI.ed.

   Five nav routes, one component:
     editorial  — Shows → a show's episodes by month → Episode page
     ed-sponsors — booked ad reads per show / host / sponsor for a month (Sponsors grant, 0143)
     ed-day     — one date across every show, with a week strip (#ed-day?d=YYYY-MM-DD); day = view 0140
     ed-search  — one box across titles, descriptions, house #, people, linked matters
     ed-review  — Link Review (E2c), its own file: EditorialReview.jsx (#ed-review)
   Inside `editorial` the drill-down lives in the hash so Back works and a link can be
   pasted: #ed (Shows) · #ed/show/<show_key> · #ed/ep/<EP-00001>. Search keeps its
   query in #ed-search?q=…  app.jsx routes any #ed hash here (load and hashchange).

   Every date shown is the story's day (0140): YouTube upload day, else Airtable Due Date,
   else pieces.air_date (the Metadata Master date, usually the next business day).

   Grouping is by AIR MONTH, not season. Season is blank on three in four episodes
   (Airtable-only rows, Bodycam Channel, the legacy YouTube rows), and where it is set
   it tracks the month anyway (10/01 = S10E1), so the month header names the season
   when its episodes carry one.

   Proposed links render here marked as proposed; they are decided in Link Review. */
(function () {
  const T = window.titleize || ((s) => String(s || "").replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()));
  const icons = () => setTimeout(() => window.lucide && window.lucide.createIcons(), 30);
  // Icons sit in a span React owns, keyed on the name (see ledger-lucide-react-crash).
  const Ic = ({ n }) => <span className="ic-wrap" key={n}><i data-lucide={n} /></span>;
  const API = () => window.LedgerAPI.ed;

  const MONTHS = ["January", "February", "March", "April", "May", "June", "July", "August",
                  "September", "October", "November", "December"];
  const fmtDate = (d) => (d ? window.LedgerTime.fmtDate(d) : "—");
  const fmtShort = (d) => (d ? window.LedgerTime.fmtDate(d).replace(/,? \d{4}$/, "") : "—");
  const monthLabel = (ym) => { const [y, m] = ym.split("-"); return `${MONTHS[+m - 1]} ${y}`; };
  const isUrl = (s) => /^https?:\/\//i.test(String(s || "").trim());
  const today = () => new Date().toISOString().slice(0, 10);

  const PLATFORM = { youtube: "YouTube", peacock: "Peacock", lcplus: "L&C+", spotify: "Spotify" };
  // How a link was made, read after "Confirmed" / "Proposed".
  const SOURCE = { request: "via an asset request", human: "by a person", title_match: "title match",
                   ai: "AI suggestion", inherited: "inherited" };
  const REQ_TYPE = { foia: "FOIA" };
  const SYSTEM = { metadata_master: "Metadata Master Doc", airtable: "Airtable · Social Media", youtube: "YouTube", ledger: "Ledger",
                   show_master: "Show master sheet" };
  // A show master row names its sheet in source_table (E5a, 0155).
  const MASTER_SHEET = { prime_crime_master: "Prime Crime master list", investigates_master: "L&C Investigates production calendar" };
  // Where an episode's "Aired" day came from (0140 editorial_piece_days).
  const DATE_FROM = { youtube: "YouTube upload", due_date: "Airtable due date", air_date: "Metadata Master date" };
  const METHOD = { house_number: "joined on house #", show_date_title: "joined on show + date + title", manual: "joined by hand", origin: "origin row",
                   youtube_id: "joined on the same YouTube video", show_episode: "joined on show + episode number",
                   show_date: "joined on show + publish date", show_name: "joined on show + case name" };

  function parseHash() {
    const h = decodeURIComponent((window.location.hash || "").replace(/^#/, ""));
    if (h.startsWith("ed-search")) return { view: "search", q: new URLSearchParams(h.split("?")[1] || "").get("q") || "" };
    if (h.startsWith("ed-review")) return { view: "review" };
    if (h.startsWith("ed-sponsors")) {
      const m = new URLSearchParams(h.split("?")[1] || "").get("m") || "";
      return { view: "sponsors", m: /^\d{4}-\d{2}$/.test(m) ? m : "" };
    }
    if (h.startsWith("ed-day")) {
      const d = new URLSearchParams(h.split("?")[1] || "").get("d") || "";
      return { view: "day", d: /^\d{4}-\d{2}-\d{2}$/.test(d) ? d : "" };
    }
    const [, view, arg] = h.split("/");
    if (view === "show" && arg) return { view: "show", arg };
    if (view === "ep" && arg) return { view: "ep", arg };
    return { view: "shows" };
  }
  const go = (h) => { window.location.hash = h; };

  // ------------------------------------------------------------------ shared bits
  // Sponsors (0143) are their own grant on top of Editorial; piece_ads RLS enforces it,
  // this only decides whether to ask and draw.
  const canSponsors = () => !!(window.LedgerCaps && window.LedgerCaps.isEditorialSponsors);
  function SponsorChip({ a }) {
    const tip = [a.sponsor_name, a.ad_type, a.availability, a.confirmed_aired ? "confirmed aired" : null].filter(Boolean).join(" · ");
    return <span className="ed-sponsor" title={tip}>{API().sponsorLabel(a)}{a.ad_type ? <i> · {a.ad_type.replace(/\s*(Mid|Pre|Post)-?Roll/i, " $1")}</i> : null}</span>;
  }
  function MatterChip({ m, onOpenMatter }) {
    const proposed = m.status !== "confirmed";
    const tip = `${m.label}${m.display_id ? " · " + m.display_id : ""} · ${proposed ? "Proposed" : "Confirmed"}${SOURCE[m.link_source] ? " · " + SOURCE[m.link_source] : ""}`;
    return (
      <button type="button" className={"ed-mchip" + (proposed ? " is-proposed" : "")} title={tip}
              onClick={(e) => { e.stopPropagation(); onOpenMatter(m.matter_id); }}>
        <i className={"ed-dot " + (proposed ? "proposed" : "confirmed")} />
        <span className="ed-mchip__lab">{m.label}</span>
      </button>
    );
  }

  function Crumbs({ items }) {
    return (
      <div className="ed-crumbs">
        {items.map((it, i) => (
          <span key={i}>
            {i > 0 && " › "}
            {it.href ? <a href={it.href}>{it.label}</a> : it.label}
          </span>
        ))}
      </div>
    );
  }

  function Loading({ text = "Loading…" }) { return <div className="ed-muted" style={{ padding: "18px 0" }}>{text}</div>; }
  function Err({ e }) { return e ? <div className="login__err" style={{ margin: "12px 0" }}>{String(e.message || e)}</div> : null; }

  function EpisodeTable({ rows, onOpenMatter, showShow = false, showWhy = false }) {
    return (
      <div className="ed-tablewrap">
        <table className="ed-table">
          <thead>
            <tr>
              {showShow && <th className="ed-opt" style={{ width: 150 }}>Show</th>}
              <th style={{ width: 96 }}>Aired</th>
              <th className="ed-opt" style={{ width: 78 }}>S · Ep</th>
              <th className="ed-opt" style={{ width: 110 }}>House #</th>
              <th>Title</th>
              <th style={{ width: "30%" }}>Matters</th>
            </tr>
          </thead>
          <tbody>
            {rows.map((p) => (
              <tr key={p.piece_id} className="ed-click" onClick={() => go(`ed/ep/${p.display_id}`)}>
                {showShow && <td className="ed-muted ed-opt">{p.showLabel || T(p.show_key)}</td>}
                <td>{p.story_date ? fmtShort(p.story_date) : <span className="ed-muted">Undated</span>}
                  {p.story_date && p.story_date > today() && <div className="ed-pill warn" style={{ marginTop: 3 }}>Scheduled</div>}</td>
                <td className="ed-mono ed-nowrap ed-opt">{p.season || p.episode_number ? `${p.season || "–"} · ${p.episode_number || "–"}` : "—"}</td>
                <td className="ed-mono ed-opt">{p.house_number || "—"}</td>
                <td>
                  <div className="ed-ttl">{API().pieceTitle(p)}</div>
                  {(p.hosts.length > 0 || p.editors.length > 0) && (
                    <div className="ed-muted">{p.hosts.join(", ")}{p.editors.length ? `${p.hosts.length ? " · " : ""}ed. ${p.editors.join(", ")}` : ""}</div>
                  )}
                  {showWhy && p.why && <div className="ed-why">Matched on {p.why.slice(0, 3).join(" · ")}</div>}
                </td>
                <td>{p.matters.length
                  ? p.matters.map((m) => <MatterChip key={m.matter_id} m={m} onOpenMatter={onOpenMatter} />)
                  : <span className="ed-muted">—</span>}</td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    );
  }

  // ------------------------------------------------------------------ Shows
  function ShowsView() {
    const [rows, setRows] = React.useState(null);
    const [err, setErr] = React.useState(null);
    React.useEffect(() => { API().shows().then(setRows).catch(setErr); }, []);
    React.useEffect(() => { icons(); }, [rows]);

    const withEps = (rows || []).filter((s) => s.episodes > 0);
    const live = withEps.filter((s) => s.enabled);
    const retired = withEps.filter((s) => !s.enabled);
    const total = withEps.reduce((a, s) => a + s.episodes, 0);

    const Card = ({ s }) => {
      const pct = s.episodes ? Math.round((100 * s.linked) / s.episodes) : 0;
      return (
        <a className="ed-card ed-show" href={`#ed/show/${s.show_key}`}>
          <div className="ed-prefix">{s.house_prefix ? `${s.house_prefix} · ` : ""}{T(s.division)}</div>
          <h3>{s.label}</h3>
          <div className="ed-muted">
            {s.last_aired ? `Last aired ${fmtDate(s.last_aired)}` : "No air dates"}
            {s.next_scheduled ? ` · next ${fmtShort(s.next_scheduled)}` : ""}
          </div>
          <div className="ed-stats">
            <div><div className="ed-n">{s.episodes.toLocaleString()}</div><div className="ed-l">Episodes</div></div>
            <div><div className="ed-n">{pct}%</div><div className="ed-l">Linked to a matter</div></div>
          </div>
          <div className="ed-bar" title={`${s.linked} of ${s.episodes} have a confirmed matter link`}><i style={{ width: `${pct}%` }} /></div>
        </a>
      );
    };

    return (
      <div>
        <div className="page-head"><div>
          <div className="eyebrow">Editorial</div>
          <h1 className="page-title">Shows<window.LedgerComment section="Editorial shows" /></h1>
        </div></div>
        <p className="ed-lede">
          Every show with episodes in Ledger{rows ? ` (${total.toLocaleString()} episodes)` : ""}. Open a show to browse its
          episodes by month, and an episode to see the matters it covers and the material it used.
          Synced nightly from the Social Team Metadata Master Doc and the Social Media Airtable base.
        </p>
        <Err e={err} />
        {!rows && !err && <Loading />}
        {rows && <div className="ed-grid">{live.map((s) => <Card key={s.show_key} s={s} />)}</div>}
        {retired.length > 0 && (
          <>
            <h3 className="ed-sec" style={{ marginTop: 26 }}>Retired</h3>
            <div className="ed-muted" style={{ marginBottom: 10 }}>Shows no longer produced, plus planning history from before each show had its own tag.</div>
            <div className="ed-grid">{retired.map((s) => <Card key={s.show_key} s={s} />)}</div>
          </>
        )}
      </div>
    );
  }

  // ------------------------------------------------------------------ Show
  function ShowView({ showKey, onOpenMatter }) {
    const [show, setShow] = React.useState(null);
    const [eps, setEps] = React.useState(null);
    const [err, setErr] = React.useState(null);
    const [q, setQ] = React.useState("");
    const [onlyUnlinked, setOnlyUnlinked] = React.useState(false);
    const [open, setOpen] = React.useState(null);

    React.useEffect(() => {
      setEps(null); setErr(null); setOpen(null);
      API().shows().then((all) => setShow(all.find((s) => s.show_key === showKey) || { show_key: showKey, label: T(showKey) }))
        .catch(setErr);
      API().showEpisodes(showKey).then(setEps).catch(setErr);
    }, [showKey]);

    const ql = q.trim().toLowerCase();
    const shown = (eps || []).filter((p) => (!onlyUnlinked || !p.matters.some((m) => m.status === "confirmed")) && (!ql ||
      [p.title, p.working_title, p.short_title, p.house_number, p.export_filename, ...p.hosts, ...p.editors,
       ...p.matters.map((m) => m.label)].join(" ").toLowerCase().includes(ql)));

    const groups = {};
    shown.forEach((p) => { const k = p.story_date ? p.story_date.slice(0, 7) : "undated"; (groups[k] = groups[k] || []).push(p); });
    const keys = Object.keys(groups).sort((a, b) => (a === "undated" ? 1 : b === "undated" ? -1 : b.localeCompare(a)));
    // First open group: the newest month that has aired (scheduled months sit above it).
    const firstAired = keys.find((k) => k !== "undated" && k <= today().slice(0, 7)) || keys[0];
    const isOpen = (k) => (ql ? true : open ? open.has(k) : k === firstAired);
    const toggle = (k) => setOpen((o) => {
      const n = new Set(o || [firstAired]); n.has(k) ? n.delete(k) : n.add(k); return n;
    });
    React.useEffect(() => { icons(); });

    const seasonOf = (list) => {
      const s = [...new Set(list.map((p) => p.season).filter(Boolean))];
      return s.length === 1 ? `Season ${s[0]}` : s.length > 1 ? `Seasons ${s.join(", ")}` : null;
    };

    return (
      <div>
        <Crumbs items={[{ label: "Shows", href: "#ed" }, { label: show ? show.label : "…" }]} />
        <div className="page-head"><div>
          <div className="eyebrow">{show && show.house_prefix ? `${show.house_prefix} · ` : ""}{show ? T(show.division) : "Editorial"}</div>
          <h1 className="page-title">{show ? show.label : "…"}<window.LedgerComment section="Editorial show" /></h1>
        </div></div>
        <Err e={err} />
        <div className="ed-filters">
          <input className="ed-input" placeholder="Filter: title, person, matter, house #" value={q}
                 onChange={(e) => setQ(e.target.value)} />
          <button type="button" className={"ed-chip" + (onlyUnlinked ? " on" : "")} onClick={() => setOnlyUnlinked((v) => !v)}>
            No confirmed matter
          </button>
          {eps && <span className="ed-muted">{shown.length.toLocaleString()} of {eps.length.toLocaleString()} episodes</span>}
        </div>
        {!eps && !err && <Loading text="Loading episodes…" />}
        {eps && !shown.length && <div className="ed-muted">No episodes match.</div>}
        {keys.map((k) => {
          const list = groups[k];
          const season = k === "undated" ? null : seasonOf(list);
          const linked = list.filter((p) => p.matters.some((m) => m.status === "confirmed")).length;
          return (
            <div className="ed-season" key={k}>
              <button type="button" className="ed-season__h" onClick={() => toggle(k)}>
                <Ic n={isOpen(k) ? "chevron-down" : "chevron-right"} />
                <span className="ed-season__t">{k === "undated" ? "Undated" : monthLabel(k)}</span>
                {season && <span className="ed-muted">{season}</span>}
                <span style={{ flex: 1 }} />
                <span className="ed-muted">{list.length} episode{list.length === 1 ? "" : "s"} · {linked} linked</span>
              </button>
              {isOpen(k) && <EpisodeTable rows={list} onOpenMatter={onOpenMatter} />}
            </div>
          );
        })}
      </div>
    );
  }

  // ------------------------------------------------------------------ Episode
  function KV({ k, children }) {
    return <><div className="ed-k">{k}</div><div>{children || <span className="ed-muted">—</span>}</div></>;
  }
  function LinkOrText({ v }) {
    if (!v) return null;
    return isUrl(v) ? <a href={v} target="_blank" rel="noopener noreferrer" className="ed-ext">Open <Ic n="external-link" /></a> : <span>{v}</span>;
  }

  function EpisodeView({ ref_, onOpenMatter }) {
    const [d, setD] = React.useState(undefined);
    const [err, setErr] = React.useState(null);
    const [ads, setAds] = React.useState(null);
    React.useEffect(() => { setD(undefined); setErr(null); API().episode(ref_).then(setD).catch(setErr); }, [ref_]);
    React.useEffect(() => {
      setAds(null);
      if (d && d.piece && canSponsors()) API().sponsorsFor([d.piece.piece_id]).then((m) => setAds(m[d.piece.piece_id] || [])).catch(() => setAds([]));
    }, [d]);
    React.useEffect(() => { icons(); }, [d]);

    if (err) return <Err e={err} />;
    if (d === undefined) return <Loading />;
    if (d === null) return <div><Crumbs items={[{ label: "Shows", href: "#ed" }, { label: "Episode" }]} /><p>Episode {ref_} was not found.</p></div>;

    const p = d.piece; const s = d.show || { show_key: p.show_key, label: T(p.show_key) };
    const credits = {};
    d.credits.forEach((c) => { (credits[c.role] = credits[c.role] || []).push(c); });
    const title = API().pieceTitle(p);
    const person = (c) => (
      <a key={c.person_name} className="ed-person" href={`#ed-search?q=${encodeURIComponent(c.person_name)}`}
         title={`Every episode crediting ${c.person_name}`}>{c.person_name}</a>
    );

    return (
      <div>
        <Crumbs items={[{ label: "Shows", href: "#ed" }, { label: s.label, href: `#ed/show/${s.show_key}` }, { label: p.display_id }]} />
        <div className="ed-ephead">
          <a className="ed-showtag" href={`#ed/show/${s.show_key}`}>{s.label}</a>
          <h1>{title}</h1>
          {p.working_title && p.title && p.working_title !== p.title && (
            <div className="ed-ephead__sub">Working title: {p.working_title}</div>
          )}
          <div className="ed-ephead__meta">
            <div><div className="k">Aired</div><div className="v">{p.story_date ? fmtDate(p.story_date) : "Undated"}</div>
              {p.story_date && <div className="ed-ephead__note">{DATE_FROM[p.date_source] || ""}
                {p.due_date && p.youtube_date && p.due_date !== p.youtube_date ? ` · due ${fmtShort(p.due_date)}` : ""}</div>}</div>
            <div><div className="k">House #</div><div className="v ed-mono">{p.house_number || "—"}</div></div>
            <div><div className="k">Season · Ep</div><div className="v">{p.season || "–"} · {p.episode_number || "–"}</div></div>
            <div><div className="k">Status</div><div className="v">{p.workflow_status || "—"}</div></div>
            <div><div className="k">Ledger ID</div><div className="v ed-mono">{p.display_id}</div></div>
          </div>
        </div>

        {window.LedgerHoldBanner && (
          <window.LedgerHoldBanner matterIds={d.matters.map((m) => m.matter_id)} currentShow={p.show_key} />
        )}

        <div className="ed-cols">
          <div>
            <div className="ed-card">
              <h3 className="ed-sec"><Ic n="folder-open" />Matters</h3>
              {d.matters.length === 0 && (
                <div className="ed-muted">No matter linked yet. Links come from asset requests attached in Airtable today;
                  the title linker and Link Review come next.</div>
              )}
              {d.matters.map((m) => (
                <div className="ed-linkrow" key={m.matter_id}>
                  <i className={"ed-dot " + (m.status === "confirmed" ? "confirmed" : "proposed")} />
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <a className="ed-lab" href="#" onClick={(e) => { e.preventDefault(); onOpenMatter(m.matter_id); }}>{m.label}</a>
                    <div className="ed-muted">{m.display_id}{m.role && m.role !== "primary" ? ` · ${T(m.role)}` : ""}
                      {` · ${m.status === "confirmed" ? "Confirmed" : "Proposed"}`}{SOURCE[m.link_source] ? ` (${SOURCE[m.link_source]})` : ""}</div>
                  </div>
                </div>
              ))}
            </div>

            <div className="ed-card">
              <h3 className="ed-sec"><Ic n="clipboard-list" />Material used</h3>
              {d.usage.length === 0 && <div className="ed-muted">No asset requests or Iconik material linked.</div>}
              {d.usage.map((u) => {
                // E3b: a producer-attached request that never arrived was planned, not used (E3-C).
                const planned = u.status === "confirmed" && u.grain === "request" && u.request && u.request.status !== "received";
                const pill = u.status === "proposed" ? ["warn", "Unconfirmed"] : planned ? ["n", "Planned"] : ["ok", "Used"];
                return (
                <div className="ed-linkrow" key={u.usage_id}>
                  <Ic n={u.grain === "request" ? "clipboard-list" : u.grain === "folder" ? "folder" : "film"} />
                  <div style={{ flex: 1, minWidth: 0 }}>
                    {u.request && u.request.id ? (
                      <>
                        <a className="ed-lab" href="#" title="Opens the matter this request belongs to"
                           onClick={(e) => { e.preventDefault(); u.request.matter_id && onOpenMatter(u.request.matter_id); }}>
                          {u.request.display_id} · {REQ_TYPE[u.request.request_type] || T(u.request.request_type || u.request.content_kind || "request")}
                        </a>
                        <div className="ed-muted">{u.matterLabel ? `${u.matterLabel} · ` : ""}{T(u.request.status)}</div>
                        {u.request.details && <div className="ed-clip">{u.request.details}</div>}
                      </>
                    ) : (
                      <>
                        <a className="ed-lab" href="#" title="Opens the matter this material belongs to"
                           onClick={(e) => { e.preventDefault(); u.matter_id && onOpenMatter(u.matter_id); }}>
                          {API().materialLabel(u)}
                        </a>
                        <div className="ed-muted">{[u.matterLabel, u.grain === "folder" ? "Case folder" : T((u.content && u.content.content_type) || "asset")].filter(Boolean).join(" · ")}</div>
                        {u.content && u.content.asset_url && u.grain === "folder" && <div className="ed-muted ed-mat-path">{u.content.asset_url}</div>}
                      </>
                    )}
                  </div>
                  <span className={"ed-pill " + pill[0]} title={u.status === "proposed" ? "Proposed from the matter; confirm it in Link Review → Material" : undefined}>{pill[1]}</span>
                </div>
                );
              })}
            </div>

            {(p.description || p.short_description || p.short_title) && (
              <div className="ed-card">
                <h3 className="ed-sec"><Ic n="align-left" />Descriptions</h3>
                <div className="ed-kv">
                  {p.short_title && <KV k="Short title">{p.short_title}</KV>}
                  {p.description && <KV k="Description"><div className="ed-pre">{p.description}</div></KV>}
                  {p.short_description && <KV k="Short (≤90)">{p.short_description}</KV>}
                </div>
              </div>
            )}
          </div>

          <div>
            {canSponsors() && (
              <div className="ed-card">
                <h3 className="ed-sec"><Ic n="badge-dollar-sign" />Sponsors</h3>
                {ads === null && <div className="ed-muted">Loading…</div>}
                {ads && ads.length === 0 && <div className="ed-muted">No booked ad reads tied to this episode.</div>}
                {ads && ads.map((a) => (
                  <div className="ed-linkrow" key={a.piece_ad_id}>
                    <div style={{ flex: 1, minWidth: 0 }}>
                      <div className="ed-lab" style={{ textDecoration: "none" }}>{API().sponsorLabel(a)}</div>
                      <div className="ed-muted">{[a.ad_type, a.availability, a.airdate ? `booked for ${fmtShort(a.airdate)}` : null].filter(Boolean).join(" · ")}</div>
                    </div>
                    <span className={"ed-pill " + (a.confirmed_aired ? "ok" : "n")}>{a.confirmed_aired ? "Aired" : "Booked"}</span>
                  </div>
                ))}
                {ads && ads.length > 0 && <div className="ed-muted" style={{ marginTop: 8 }}>From the sales team's Airtable (Sales (Social)), synced nightly.</div>}
              </div>
            )}
            <div className="ed-card">
              <h3 className="ed-sec"><Ic n="users" />Credits</h3>
              {d.credits.length === 0 && <div className="ed-muted">No credits recorded.</div>}
              <div className="ed-kv">
                {["host", "producer", "writer", "editor"].filter((r) => credits[r]).map((r) => (
                  <KV key={r} k={T(r)}>{credits[r].map((c, i) => <span key={i}>{i > 0 && ", "}{person(c)}</span>)}</KV>
                ))}
              </div>
            </div>

            <div className="ed-card">
              <h3 className="ed-sec"><Ic n="file-text" />Details</h3>
              <div className="ed-kv">
                <KV k="Script">{p.script_url && <LinkOrText v={p.script_url} />}</KV>
                <KV k="Asset folder">{p.asset_folder && <LinkOrText v={p.asset_folder} />}</KV>
                <KV k="Media type">{p.media_type}</KV>
                <KV k="Ad slots">{p.ad_slot_count ? String(p.ad_slot_count) : null}</KV>
                <KV k="Export file">{p.export_filename && <span className="ed-mono ed-break">{p.export_filename}</span>}</KV>
              </div>
            </div>

            <div className="ed-card">
              <h3 className="ed-sec"><Ic n="radio" />Published</h3>
              {d.publications.length === 0 && <div className="ed-muted">No platform links recorded.</div>}
              {d.publications.map((pb, i) => (
                <div className="ed-linkrow" key={i}>
                  <span className="ed-pill n">{PLATFORM[pb.platform] || T(pb.platform)}</span>
                  <div style={{ flex: 1, minWidth: 0 }}>
                    {pb.url ? <a href={pb.url} target="_blank" rel="noopener noreferrer" className="ed-ext">{pb.platform_title || pb.url} <Ic n="external-link" /></a>
                            : <span>{pb.platform_title || T(pb.status || "delivered")}</span>}
                    {pb.published_at && <div className="ed-muted">{fmtDate(String(pb.published_at).slice(0, 10))}</div>}
                  </div>
                </div>
              ))}
            </div>

            <div className="ed-note">
              <b>Where this comes from.</b>{" "}
              {d.sources.length === 0 && "No source rows recorded."}
              {d.sources.map((src, i) => (
                <span key={i}>{i > 0 && "; "}{src.source_system === "show_master"
                    ? (MASTER_SHEET[src.source_table] || SYSTEM.show_master) : (SYSTEM[src.source_system] || src.source_system)}
                  {src.source_system === "metadata_master" ? ` (${src.source_table} tab)` : ""}
                  {src.match_method && src.match_method !== "origin" ? `, ${METHOD[src.match_method] || src.match_method}` : ""}</span>
              ))}{d.sources.length > 0 && "."}
              {" "}Edit it at the source; Ledger syncs nightly.
            </div>
          </div>
        </div>
      </div>
    );
  }

  // ------------------------------------------------------------------ Day
  // Every show's stories for one date (Vanessa, 2026-10-06: "open Ledger tomorrow morning
  // and see what all the shows are doing that day"). A story's day is its YouTube upload
  // day, else its Airtable Due Date, else air_date (view 0140). When the Due Date and the
  // YouTube day differ (a held episode), the row says so. Hash: #ed-day?d=2026-10-07.
  const addDays = (iso, n) => {
    const [y, m, d] = iso.split("-").map(Number);
    return new Date(Date.UTC(y, m - 1, d + n)).toISOString().slice(0, 10);
  };
  const weekdayOf = (iso) => { const [y, m, d] = iso.split("-").map(Number); return new Date(Date.UTC(y, m - 1, d)).getUTCDay(); };
  const DOW = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
  const DONE = /^(done|published|edit finished)$/i;
  const dayShort = (iso) => `${DOW[weekdayOf(iso)]} ${+iso.slice(5, 7)}/${+iso.slice(8)}`;

  function DayView({ initialDate, onOpenMatter }) {
    const todayET = window.LedgerTime.todayEastern();
    const [date, setDate] = React.useState(() => { const h = parseHash(); return (h.view === "day" && h.d) || initialDate || todayET; });
    const [shows, setShows] = React.useState(null);
    const [res, setRes] = React.useState(null);
    const [err, setErr] = React.useState(null);
    const [q, setQ] = React.useState("");
    const [ads, setAds] = React.useState({ byPiece: {}, slots: [] });

    React.useEffect(() => { API().shows().then(setShows).catch(() => setShows([])); }, []);
    // Sponsors (0143): reads tied to these stories, and reads booked for this day with no episode yet.
    React.useEffect(() => {
      setAds({ byPiece: {}, slots: [] });
      if (!res || !canSponsors()) return;
      let live = true;
      Promise.all([API().sponsorsFor(res.rows.map((p) => p.piece_id)), API().sponsorSlotsOn(date)])
        .then(([byPiece, slots]) => { if (live) setAds({ byPiece, slots: slots || [] }); }).catch(() => {});
      return () => { live = false; };
    }, [res]);
    // Follow the hash itself: the date is changed in place with replaceState, so the
    // parent's copy of the hash goes stale, and a link (or Back) to the date it last
    // saw would not change the prop.
    React.useEffect(() => {
      const on = () => { const h = parseHash(); if (h.view === "day") setDate(h.d || window.LedgerTime.todayEastern()); };
      window.addEventListener("hashchange", on);
      return () => window.removeEventListener("hashchange", on);
    }, []);
    // Monday-to-Sunday week around the date, for the strip.
    const weekStart = addDays(date, -((weekdayOf(date) + 6) % 7));
    const week = [0, 1, 2, 3, 4, 5, 6].map((i) => addDays(weekStart, i));
    React.useEffect(() => {
      try { history.replaceState(null, "", window.location.pathname + window.location.search + (date === todayET ? "#ed-day" : `#ed-day?d=${date}`)); } catch (e) { /* convenience */ }
      let live = true;
      setRes(null); setErr(null);
      API().day(date, { weekStart, weekEnd: week[6] })
        .then((r) => { if (live) setRes(r); }).catch((e) => { if (live) setErr(e); });
      return () => { live = false; };
    }, [date]);
    React.useEffect(() => { icons(); });

    const meta = Object.fromEntries((shows || []).map((s) => [s.show_key, s]));
    const ql = q.trim().toLowerCase();
    const rows = (res ? res.rows : []).filter((p) => !ql ||
      [p.title, p.working_title, p.house_number, ...p.hosts, ...p.editors, ...p.writers, ...p.producers,
       ...p.matters.map((m) => m.label), (meta[p.show_key] || {}).label].join(" ").toLowerCase().includes(ql));
    const groups = {};
    rows.forEach((p) => { (groups[p.show_key] = groups[p.show_key] || []).push(p); });
    const keys = Object.keys(groups).sort((a, b) => {
      const sa = (meta[a] || {}).sort_order, sb_ = (meta[b] || {}).sort_order;
      if (sa != null && sb_ != null && sa !== sb_) return sa - sb_;
      return String((meta[a] || {}).label || a).localeCompare(String((meta[b] || {}).label || b));
    });
    const people = (p) => [
      p.hosts.length && `Host ${p.hosts.join(", ")}`,
      p.writers.length && `Writer ${p.writers.join(", ")}`,
      p.editors.length && `Editor ${p.editors.join(", ")}`,
      p.producers.length && `Producer ${p.producers.join(", ")}`,
    ].filter(Boolean).join(" · ");
    const heading = window.LedgerTime.fmtDate(date, { weekday: "long", month: "long", day: "numeric", year: "numeric" });

    return (
      <div>
        <div className="page-head"><div>
          <div className="eyebrow">Editorial · {date === todayET ? "Today" : date < todayET ? "Past day" : "Upcoming"}</div>
          <h1 className="page-title">{heading}<window.LedgerComment section="Editorial" /></h1>
        </div></div>

        <div className="ed-daynav">
          <button type="button" className="ed-chip" onClick={() => setDate(addDays(date, -7))} title="Previous week"><Ic n="chevrons-left" /></button>
          <div className="ed-week">
            {week.map((d) => {
              const n = (res && res.counts[d]) || 0;
              return (
                <button type="button" key={d} onClick={() => setDate(d)}
                        className={"ed-wday" + (d === date ? " on" : "") + (d === todayET ? " is-today" : "")}>
                  <span className="ed-wday__d">{DOW[weekdayOf(d)]} {+d.slice(8)}</span>
                  <span className="ed-wday__n">{res ? `${n} stor${n === 1 ? "y" : "ies"}` : "…"}</span>
                </button>
              );
            })}
          </div>
          <button type="button" className="ed-chip" onClick={() => setDate(addDays(date, 7))} title="Next week"><Ic n="chevrons-right" /></button>
        </div>

        <div className="ed-filters">
          <input className="ed-input" type="date" value={date} aria-label="Date"
                 onChange={(e) => e.target.value && setDate(e.target.value)} />
          {date !== todayET && <button type="button" className="ed-chip" onClick={() => setDate(todayET)}>Today</button>}
          <input className="ed-input" placeholder="Filter: person, title, matter, show" value={q} onChange={(e) => setQ(e.target.value)} />
          {res && <span className="ed-muted">{rows.length} of {res.rows.length} stor{res.rows.length === 1 ? "y" : "ies"} · {keys.length} show{keys.length === 1 ? "" : "s"}</span>}
        </div>

        <Err e={err} />
        {!res && !err && <Loading />}
        {res && !res.rows.length && <div className="ed-muted">No stories for {heading}. A story's day is when it went up on YouTube, or its Airtable Due Date until then.</div>}
        {res && res.rows.length > 0 && !rows.length && <div className="ed-muted">Nothing matches the filter.</div>}

        {keys.map((k) => {
          const list = groups[k]; const s = meta[k] || { label: T(k) };
          return (
            <div className="ed-season" key={k}>
              <a className="ed-season__h" href={`#ed/show/${k}`} title={`Open ${s.label}`}>
                <span className="ed-season__t">{s.label}</span>
                <span style={{ flex: 1 }} />
                <span className="ed-muted">{list.length} stor{list.length === 1 ? "y" : "ies"}</span>
              </a>
              <div className="ed-tablewrap">
                <table className="ed-table">
                  <thead>
                    <tr>
                      <th>Story</th>
                      <th className="ed-opt" style={{ width: 130 }}>Status</th>
                      <th style={{ width: "30%" }}>Matters</th>
                    </tr>
                  </thead>
                  <tbody>
                    {list.map((p) => {
                      const yt = p.publications.find((pb) => pb.url);
                      return (
                        <tr key={p.piece_id} className="ed-click" onClick={() => go(`ed/ep/${p.display_id}`)}>
                          <td>
                            <div className="ed-ttl">{API().pieceTitle(p)}</div>
                            {people(p) && <div className="ed-muted">{people(p)}</div>}
                            {p.due_date && p.youtube_date && p.due_date !== p.youtube_date && (
                              <div className="ed-shift">Due {dayShort(p.due_date)} · on YouTube {dayShort(p.youtube_date)}</div>
                            )}
                            {(ads.byPiece[p.piece_id] || []).length > 0 && (
                              <div className="ed-sponsors">{ads.byPiece[p.piece_id].map((a) => <SponsorChip key={a.piece_ad_id} a={a} />)}</div>
                            )}
                            {p.date_source === "due_date" && !DONE.test(p.workflow_status || "") && !p.publications.some((pb) => pb.platform === "youtube") && (
                              <div className="ed-muted">Due {dayShort(p.due_date)}, not on YouTube yet</div>
                            )}
                            {(p.house_number || yt) && (
                              <div className="ed-muted">
                                {p.house_number && <span className="ed-mono">{p.house_number}</span>}
                                {p.house_number && yt && " · "}
                                {yt && <a href={yt.url} target="_blank" rel="noopener noreferrer" className="ed-ext"
                                          onClick={(e) => e.stopPropagation()}>{PLATFORM[yt.platform] || T(yt.platform)} <Ic n="external-link" /></a>}
                              </div>
                            )}
                          </td>
                          <td className="ed-opt">{p.workflow_status
                            ? <span className={"ed-pill " + (DONE.test(p.workflow_status) ? "ok" : "n")}>{p.workflow_status}</span>
                            : <span className="ed-muted">—</span>}</td>
                          <td>{p.matters.length
                            ? p.matters.map((m) => <MatterChip key={m.matter_id} m={m} onOpenMatter={onOpenMatter} />)
                            : <span className="ed-muted">—</span>}</td>
                        </tr>
                      );
                    })}
                  </tbody>
                </table>
              </div>
            </div>
          );
        })}

        {canSponsors() && ads.slots.length > 0 && (
          <div className="ed-card" style={{ marginTop: 14 }}>
            <h3 className="ed-sec"><Ic n="badge-dollar-sign" />Booked for this day, not on an episode yet</h3>
            <div className="ed-muted" style={{ marginBottom: 8 }}>Sponsor reads the sales team scheduled for {heading} that no episode carries yet (no YouTube title recorded, or none matched).</div>
            {Object.entries(ads.slots.reduce((g, a) => { (g[a.show_key] = g[a.show_key] || []).push(a); return g; }, {})).map(([k, list]) => (
              <div className="ed-linkrow" key={k}>
                <div style={{ width: 170 }} className="ed-ttl">{(meta[k] || {}).label || T(k)}</div>
                <div className="ed-sponsors" style={{ flex: 1 }}>{list.map((a) => <SponsorChip key={a.piece_ad_id} a={a} />)}</div>
              </div>
            ))}
          </div>
        )}
      </div>
    );
  }

  // ------------------------------------------------------------------ Sponsors (0143)
  // Booked ad reads for a month: per show, per host (of the episodes they ran on) and per
  // sponsor. Reporting rule: opens on the last complete month with month-over-month
  // change; the current month is "to date", compared with the same days of last month.
  const monthStart = (ym) => `${ym}-01`;
  const monthEnd = (ym) => { const [y, m] = ym.split("-").map(Number); return new Date(Date.UTC(y, m, 0)).toISOString().slice(0, 10); };
  const prevMonth = (ym) => { const [y, m] = ym.split("-").map(Number); return new Date(Date.UTC(y, m - 2, 1)).toISOString().slice(0, 7); };

  function Delta({ now, before }) {
    if (!before && !now) return <span className="ed-muted">—</span>;
    if (!before) return <span className="ed-delta up">new</span>;
    const pct = Math.round((100 * (now - before)) / before);
    return <span className={"ed-delta " + (pct > 0 ? "up" : pct < 0 ? "down" : "")}>{pct > 0 ? "+" : ""}{pct}%</span>;
  }

  function SponsorsView({ initialMonth }) {
    const todayET = window.LedgerTime.todayEastern();
    const thisMonth = todayET.slice(0, 7);
    const lastComplete = prevMonth(thisMonth);
    const [ym, setYm] = React.useState(() => { const h = parseHash(); return (h.view === "sponsors" && h.m) || initialMonth || lastComplete; });
    const [rows, setRows] = React.useState(null);
    const [shows, setShows] = React.useState([]);
    const [err, setErr] = React.useState(null);
    const months = Array.from({ length: 12 }, (_, i) => { let m = thisMonth; for (let k = 0; k < i; k++) m = prevMonth(m); return m; });
    const partial = ym === thisMonth;
    // Like-for-like: a partial month compares with the same days of the month before.
    const curFrom = monthStart(ym), curTo = partial ? todayET : monthEnd(ym);
    const pm = prevMonth(ym);
    const prevFrom = monthStart(pm);
    const prevTo = partial ? (() => { const d = Math.min(+todayET.slice(8), +monthEnd(pm).slice(8)); return `${pm}-${String(d).padStart(2, "0")}`; })() : monthEnd(pm);

    React.useEffect(() => { API().shows().then(setShows).catch(() => {}); }, []);
    React.useEffect(() => {
      try { history.replaceState(null, "", window.location.pathname + window.location.search + (ym === lastComplete ? "#ed-sponsors" : `#ed-sponsors?m=${ym}`)); } catch (e) { /* convenience */ }
      let live = true; setRows(null); setErr(null);
      API().sponsorReads(prevFrom, curTo).then((r) => { if (live) setRows(r); }).catch((e) => { if (live) setErr(e); });
      return () => { live = false; };
    }, [ym]);
    React.useEffect(() => { icons(); });

    const label = Object.fromEntries(shows.map((s) => [s.show_key, s.label]));
    const cur = (rows || []).filter((r) => r.airdate >= curFrom && r.airdate <= curTo);
    const prev = (rows || []).filter((r) => r.airdate >= prevFrom && r.airdate <= prevTo);
    const tally = (list, keyOf) => {
      const m = new Map();
      list.forEach((r) => { [].concat(keyOf(r)).filter(Boolean).forEach((k) => {
        const e = m.get(k) || { n: 0, onEp: 0, eps: new Set(), shows: new Set() };
        e.n++; if (r.piece_id) { e.onEp++; e.eps.add(r.piece_id); } e.shows.add(r.show_key); m.set(k, e); }); });
      return m;
    };
    const byShow = tally(cur, (r) => r.show_key), byShowPrev = tally(prev, (r) => r.show_key);
    const byHost = tally(cur.filter((r) => r.piece_id), (r) => r.hosts), byHostPrev = tally(prev.filter((r) => r.piece_id), (r) => r.hosts);
    const bySponsor = tally(cur, (r) => API().sponsorLabel(r));
    const sorted = (m) => [...m.entries()].sort((a, b) => b[1].n - a[1].n);
    const monthName = (m) => `${MONTHS[+m.slice(5) - 1]} ${m.slice(0, 4)}`;
    const vs = partial ? `vs ${monthName(pm)} 1–${+prevTo.slice(8)}` : `vs ${monthName(pm)}`;

    return (
      <div>
        <div className="page-head"><div>
          <div className="eyebrow">Editorial · Sponsors</div>
          <h1 className="page-title">{monthName(ym)}{partial ? " to date" : ""}<window.LedgerComment section="Editorial month" /></h1>
        </div></div>
        <div className="ed-filters">
          <select className="ed-input" value={ym} onChange={(e) => setYm(e.target.value)} aria-label="Month">
            {months.map((m) => <option key={m} value={m}>{monthName(m)}{m === thisMonth ? " (to date)" : ""}</option>)}
          </select>
          <span className="ed-muted">Booked ad reads by the sales team's airdate. Change is {vs}. From Airtable Sales (Social), synced nightly.</span>
        </div>
        <Err e={err} />
        {!rows && !err && <Loading />}
        {rows && (
          <>
            <div className="ed-kpis">
              <div className="ed-card"><div className="ed-n">{cur.length}</div><div className="ed-l">Booked reads <Delta now={cur.length} before={prev.length} /></div></div>
              <div className="ed-card"><div className="ed-n">{cur.filter((r) => r.piece_id).length}</div><div className="ed-l">Tied to an episode</div></div>
              <div className="ed-card"><div className="ed-n">{bySponsor.size}</div><div className="ed-l">Sponsors</div></div>
            </div>
            <div className="ed-cols">
              <div className="ed-card">
                <h3 className="ed-sec"><Ic n="tv" />By show</h3>
                <table className="ed-table ed-table--tight"><thead><tr><th>Show</th><th>Reads</th><th>Change</th><th>On an episode</th></tr></thead>
                  <tbody>{sorted(byShow).map(([k, e]) => (
                    <tr key={k}><td><a href={`#ed/show/${k}`}>{label[k] || T(k)}</a></td><td>{e.n}</td>
                      <td><Delta now={e.n} before={(byShowPrev.get(k) || {}).n || 0} /></td><td>{e.onEp}</td></tr>))}
                    {byShow.size === 0 && <tr><td colSpan={4} className="ed-muted">No booked reads.</td></tr>}</tbody></table>
              </div>
              <div className="ed-card">
                <h3 className="ed-sec"><Ic n="mic" />By host</h3>
                <table className="ed-table ed-table--tight"><thead><tr><th>Host</th><th>Reads</th><th>Change</th><th>Episodes</th></tr></thead>
                  <tbody>{sorted(byHost).map(([k, e]) => (
                    <tr key={k}><td><a href={`#ed-search?q=${encodeURIComponent(k)}`}>{k}</a></td><td>{e.n}</td>
                      <td><Delta now={e.n} before={(byHostPrev.get(k) || {}).n || 0} /></td><td>{e.eps.size}</td></tr>))}
                    {byHost.size === 0 && <tr><td colSpan={4} className="ed-muted">No reads tied to an episode with a host.</td></tr>}</tbody></table>
                <div className="ed-muted" style={{ marginTop: 6 }}>Counts reads tied to an episode; a read on an episode with two hosts counts for each.</div>
              </div>
            </div>
            <div className="ed-card">
              <h3 className="ed-sec"><Ic n="badge-dollar-sign" />By sponsor</h3>
              <table className="ed-table ed-table--tight"><thead><tr><th>Sponsor</th><th>Reads</th><th>Shows</th></tr></thead>
                <tbody>{sorted(bySponsor).map(([k, e]) => (
                  <tr key={k}><td>{k}</td><td>{e.n}</td><td className="ed-muted">{[...e.shows].map((x) => label[x] || T(x)).join(", ")}</td></tr>))}</tbody></table>
            </div>
          </>
        )}
      </div>
    );
  }

  // ------------------------------------------------------------------ Search
  function SearchView({ initialQ, onOpenMatter }) {
    // Read the query from the hash at mount. Arriving by link (#ed-search?q=…, e.g. a
    // person on the episode page), app.jsx switches the route before this component's
    // parent has re-read the hash, so `initialQ` is still "" on the first render.
    const [q, setQ] = React.useState(() => { const h = parseHash(); return (h.view === "search" && h.q) || initialQ || ""; });
    const [show, setShow] = React.useState("");
    const [shows, setShows] = React.useState([]);
    const [res, setRes] = React.useState(null);
    const [busy, setBusy] = React.useState(false);
    const [err, setErr] = React.useState(null);
    React.useEffect(() => { API().shows().then((r) => setShows(r.filter((s) => s.episodes > 0))).catch(() => {}); }, []);
    // A later link (another person) updates the box; an empty prop never clears it.
    React.useEffect(() => { if (initialQ) setQ(initialQ); }, [initialQ]);
    React.useEffect(() => {
      const t = q.trim();
      // Keep the query in the hash so a search can be pasted. replaceState: no history entry per keystroke.
      try { history.replaceState(null, "", window.location.pathname + window.location.search + (t ? `#ed-search?q=${encodeURIComponent(t)}` : "#ed-search")); } catch (e) { /* convenience */ }
      if (t.length < 2) { setRes(null); return; }
      let live = true;
      setBusy(true); setErr(null);
      const h = setTimeout(() => {
        API().search(t, { show: show || null })
          .then((r) => { if (live) setRes(r); })
          .catch((e) => { if (live) setErr(e); })
          .finally(() => { if (live) setBusy(false); });
      }, 300);
      return () => { live = false; clearTimeout(h); };
    }, [q, show]);
    React.useEffect(() => { icons(); }, [res]);

    const label = Object.fromEntries(shows.map((s) => [s.show_key, s.label]));
    const rows = res ? res.rows.map((p) => ({ ...p, showLabel: label[p.show_key] })) : [];

    return (
      <div>
        <div className="page-head"><div>
          <div className="eyebrow">Editorial</div>
          <h1 className="page-title">Search episodes<window.LedgerComment section="Editorial search" /></h1>
        </div></div>
        <div className="ed-filters">
          <input className="ed-input ed-input--big" autoFocus placeholder="Title, description, house #, host, writer, editor, or a matter"
                 value={q} onChange={(e) => setQ(e.target.value)} />
          <select className="ed-input" value={show} onChange={(e) => setShow(e.target.value)} aria-label="Show">
            <option value="">All shows</option>
            {shows.map((s) => <option key={s.show_key} value={s.show_key}>{s.label}</option>)}
          </select>
        </div>
        <Err e={err} />
        {q.trim().length < 2 && <div className="ed-muted">Type at least two characters. A person's name finds every episode crediting them; a matter's name finds every episode linked to it.</div>}
        {busy && <Loading text="Searching…" />}
        {!busy && res && (
          <>
            <div className="ed-muted" style={{ margin: "4px 0 10px" }}>
              {res.rows.length ? `${res.total.toLocaleString()} episode${res.total === 1 ? "" : "s"}${res.truncated ? `, newest ${res.rows.length} shown` : ""}` : "No episodes match."}
            </div>
            {rows.length > 0 && <EpisodeTable rows={rows} onOpenMatter={onOpenMatter} showShow showWhy />}
          </>
        )}
      </div>
    );
  }

  // ------------------------------------------------------------------ root
  function LedgerEditorial({ view, onOpenMatter }) {
    const [h, setH] = React.useState(parseHash);
    React.useEffect(() => {
      const on = () => setH(parseHash());
      window.addEventListener("hashchange", on);
      return () => window.removeEventListener("hashchange", on);
    }, []);
    // Scroll to top on every drill-down step.
    React.useEffect(() => { try { window.scrollTo(0, 0); } catch (e) { /* noop */ } }, [h.view, h.arg]);

    if (view === "ed-search") return <SearchView initialQ={h.view === "search" ? h.q : ""} onOpenMatter={onOpenMatter} />;
    if (view === "ed-review") return <window.LedgerEditorialReview onOpenMatter={onOpenMatter} />;
    if (view === "ed-sponsors") return <SponsorsView initialMonth={h.view === "sponsors" ? h.m : ""} />;
    if (view === "ed-day") return <DayView initialDate={h.view === "day" ? h.d : ""} onOpenMatter={onOpenMatter} />;
    if (h.view === "show") return <ShowView showKey={h.arg} onOpenMatter={onOpenMatter} />;
    if (h.view === "ep") return <EpisodeView ref_={h.arg} onOpenMatter={onOpenMatter} />;
    return <ShowsView />;
  }

  window.LedgerEditorial = LedgerEditorial;
})();
