    MON.es.addEventListener("connected", () => $("#liveInd").classList.remove("hidden"));
    MON.es.addEventListener("progress", ev => { try { paintProgress(JSON.parse(ev.data)); } catch {} });
    MON.es.addEventListener("done", ev => {
      try { paintProgress(JSON.parse(ev.data)); } catch {}
      stopStream();
      toast(`Campaign finished — status: ${(JSON.parse(ev.data) || {}).status || "?"}`, "info", 6000);
      drawControls();
    });
    MON.es.onerror = () => { $("#liveInd").classList.add("hidden"); };
  }
  async function drawProgress() {
    try { const p = await GET(`/campaigns/${MON.id}/progress`); paintProgress(p); }
    catch (e) { $("#monBody").innerHTML = `<div class="notice err"><span class="n-ico">✕</span><div>${esc(e.message)}</div></div>`; }
  }
  function paintProgress(p) {
    let box = $("#progBox");
    if (!box) {
      box = el("div", { id: "progBox" });
      $("#monBody").prepend(box);
    }
    const meterSegs = Math.min(p.total || 0, 400);
    const seg = (cls, n) => `<span class="${cls}" style="flex:${Math.max(n, 0)}"></span>`;
    const sum = p.sent + p.perm_failed + p.suppressed + p.skipped;
    box.innerHTML = `
      <div class="card mb-2"><div class="card-bd">
        <div class="flex-between mb-1">
          <div class="flex">
            <strong id="monName">#${p.campaign_id}</strong>
            ${stBadge(p.status)}
            <span class="small muted">${p.started_at ? "started " + fmtDT(p.started_at) : "not started"}</span>
            ${p.finished_at ? `<span class="small muted">· finished ${fmtDT(p.finished_at)}</span>` : ""}
          </div>
          <span class="small muted">${p.progress_pct}% complete</span>
        </div>
        <div class="pbar ${p.status === "completed" ? "ok" : ""}"><div style="width:${Math.min(p.progress_pct, 100)}%"></div></div>
        <div class="meter mt-2" title="sent / permanent fail / suppressed / skipped / in-flight">${meterSegs("sent", p.sent)}${meterSegs("fail", p.perm_failed)}${meterSegs("supp", p.suppressed)}${meterSegs("skip", p.skipped)}${meterSegs("proc", (p.total || 0) - sum)}</div>
        <div class="stat-grid mt-2">
          <div class="cell"><div class="v">${p.sent}</div><div class="l">Accepted & sent</div></div>
          <div class="cell"><div class="v">${p.temp_failed}</div><div class="l">Temp failed</div></div>
          <div class="cype cell"><div class="v">${p.perm_failed}</div><div class="l">Perm failed</div></div>
          <div class="cell"><div class="v">${p.suppressed}</div><div class="l">Suppressed</div></div>
          <div class="cell"><div class="v">${p.skipped}</div><div class="l">Skipped</div></div>
          <div class="cell"><div class="v">${p.pending}</div><div class="l">Pending</div></div>
          <div class="cell"><div class="v">${p.queued}</div><div class="l">Queued</div></div>
          <div class="cell"><div class="v">${p.total}</div><div class="l">Total</div></div>
        </div>
        <div class="small muted mt-1">${p.jobs_total} send jobs · delivery attempts are retried with backoff; temporary failures never masquerade as success.</div>
      </div></div>`;
    drawControls();
  }
  function drawControls() {
    const ctl = $("#monCtl");
    if (!ctl) return;
    fetch(`/campaigns/${MON.id}`).then(r => r.json()).then(c => {
      ctl.innerHTML = "";
      const mk = (label, cls, fn, confirmMsg) => {
        const b = el("button", { class: "btn sm " + cls, onclick: () =>
          confirmMsg ? confirmedAction(confirmMsg, fn, { title: label }) : fn() }, label);
        ctl.append(b);
      };
      if (c.status === "draft") mk("Start", "", async () => { await POST(`/campaigns/${MON.id}/start`); toast("Campaign started", "ok"); startMonitor(); });
      if (c.status === "running") mk("Pause", "secondary", async () => { await POST(`/campaigns/${MON.id}/pause`); toast("Campaign paused", "ok"); startMonitor(); });
      if (c.status === "paused") mk("Resume", "", async () => { await POST(`/campaigns/${MON.id}/resume`); toast("Campaign resumed", "ok"); startMonitor(); });
      if (["running", "paused", "queued", "draft"].includes(c.status))
        mk("Cancel", "danger-o", async () => { await POST(`/campaigns/${MON.id}/cancel`); toast("Campaign cancelled", "warn"); startMonitor(); },
          `Cancel campaign #${MON.id}? In-flight messages already accepted by Gmail cannot be recalled.`);
      const ex = el("button", { class: "btn sm secondary", onclick: () => { window.location = `/campaigns/${MON.id}/export`; } }, "Export CSV");
      const pv = el("button", { class: "btn sm secondary", onclick: () => campaignPreview(MON.id) }, "Preview");
      ctl.append(ex, pv);
    }).catch(() => {});
  }
  async function drawRecipients() {
    let box = $("#recBox");
    if (!box) { box = el("div", { id: "recBox" }); $("#monBody").append(box); }
    await loadRecipients(box);
  }
  async function loadRecipients(box) {
    const p = new URLSearchParams({ status: MON.statusF, search: MON.q,
                                    page: MON.page, page_size: 50 });
    let d;
    try { d = await GET(`/campaigns/${MON.id}/recipients?` + p); }
    catch (e) { box.innerHTML = `<div class="notice err"><span class="n-ico">✕</span><div>${esc(e.message)}</div></div>`; return; }
    const rows = d.recipients || [];
    const RS_BADGE = { pending: "b-gray", queued: "b-blue", processing: "b-amber", sent: "b-green",
                       temp_failed: "b-amber", perm_failed: "b-red", suppressed: "b-violet",
                       skipped: "b-gray", bounced: "b-red" };
    box.innerHTML = `
      <div class="card"><div class="card-hd"><h2>Per-recipient monitor</h2></div><div class="card-bd">
      <div class="filters flex mb-1">
        <select id="recF"><option value="">All statuses</option>
          ${["pending", "queued", "processing", "sent", "temp_failed", "perm_failed", "suppressed", "skipped", "bounced"].map(s => `<option ${MON.statusF === s ? "selected" : ""}>${s}</option>`).join("")}
        </select>
        <input id="recQ" placeholder="Search email…" value="${esc(MON.q || "")}">
        <button class="btn sm" id="recGo">Filter</button>
        <span class="small muted" style="margin-left:auto">${d.total} recipients</span>
      </div>
      ${rows.length ? `<div class="tbl-wrap"><table class="tbl">
        <thead><tr><th>Seq</th><th>Email</th><th>Status</th><th>Account</th><th>Subject</th><th>Invoice</th><th>Amount</th><th>Attempts</th><th>Sent at</th><th>Message ID</th><th>Last error</th></tr></thead>
        <tbody>${rows.map(r => `<tr>
          <td class="center mono-s">${r.seq}</td>
          <td>${esc(r.email)}</td>
          <td><span class="badge ${RS_BADGE[r.status] || "b-gray"}">${esc(r.status)}</span></td>
    <td class="small">${esc(r.account || "—")}</td>
          <td class="err-cell" title="${esc(r.subject || "")}">${esc((r.subject || "—").slice(0, 60))}</td>
          <td class="mono-s">${esc(r.invoice_number || "—")}</td>
          <td class="nowrap">${r.amount != null ? fmtMoney(r.amount) : "—"}</td>
          <td class="center">${r.attempts ?? 0}</td>
          <td class="nowrap small">${fmtDT(r.sent_at)}</td>
          <td class="mono-s" title="${esc(r.message_id || "")}">${r.message_id ? esc(r.message_id.slice(0, 14)) + "…" : "—"}</td>
          <td class="err-cell" title="${esc(r.last_error || "")}">${esc((r.last_error || "—").slice(0, 80))}</td>
        </tr>`).join("")}</tbody></table></div>` : `<div class="empty-state"><div class="e-ico">◷</div><p>No recipients materialized yet — they are created when the campaign starts.</p></div>`}
      <div class="pager" id="recPager"></div>
      </div></div>`;
    $("#recF").onchange = () => { MON.statusF = $("#recF").value; MON.page = 1; loadRecipients(box); };
    $("#recQ").onkeydown = e => { if (e.key === "Enter") { MON.q = $("#recQ").value.trim(); MON.page = 1; loadRecipients(box); } };
    $("#recGo").onclick = () => { MON.statusF = $("#recF").value; MON.q = $("#recQ").value.trim(); MON.page = 1; loadRecipients(box); };
    const pages = Math.max(1, Math.ceil(d.total / 50));
    $("#recPager").innerHTML = pages > 1 ?
      `<button class="btn sm secondary" ${MON.page <= 1 ? "disabled" : ""} data-pg="${MON.page - 1}">‹ Prev</button>
       <span class="small muted">Page ${MON.page} of ${pages}</span>
       <button class="btn sm secondary" ${MON.page >= pages ? "disabled" : ""} data-pg="${MON.page + 1}">Next ›</button>` : "";
    $$("#recPager [data-pg]").forEach(b => b.onclick = () => { MON.page = +b.dataset.pg; loadRecipients(box); });
  }

  /* ==================== CAMPAIGN PREVIEW MODAL ==================== */
  async function campaignPreview(cid, size) {
    let d;
    try { d = await POST(`/campaigns/${cid}/preview${size ? "?sample_size=" + size : ""}`, {}); }
    catch (e) { toast(e.message, "err", 6000); return; }
    const msgs = d.messages || [];
    const node = el("div", {},
      el("p", { class: "muted small" },
        `Real rendered messages for ${d.sample_size} sample recipient(s) — variables substituted, attachments planned. Nothing is sent.`),
      msgs.length ? msgs.map(m => el("details", { class: "pv-acc" },
        el("summary", {},
          `${m.recipient.email} — ${m.subject}`,
          m.warnings && m.warnings.length ? el("span", { class: "badge b-amber", style: "margin-left:8px" }, `${m.warnings.length} warning(s)`) : null),
        el("div", {},
          el("div", { class: "kv-inline" },
            el("span", { class: "badge b-blue" }, "from " + (m.from_account || "?")),
            m.raw_variables && m.raw_variables.invoice_number ? el("span", { class: "badge" }, esc(m.raw_variables.invoice_number)) : null,
