// dashboard-dynamic-sidebar-builder.js — extracted verbatim from dashboard.js (pure move).

// ── Dynamic sidebar builder ──────────────────────────────────────

async function buildSidebar() {
  const sidebar = $("#sidebar");
  if (!sidebar) return;

  // Fetch accounts via the TTL cache (60s). The manual cache field is kept
  // populated for callers that read it directly (e.g., AccountPicker), but the
  // fresh apiCached fetch is the source of truth.
  const fetched = await apiCached("accounts-all").catch(() => ({}));
  dashboardState.cache.accounts = fetched;
  // Top-bar account picker depends on this cache; refresh its label/dot.
  try {
    refreshAccountPickerLabel();
  } catch {}
  const accts = fetched || {};
  const xAccts = accts.twitter || [];
  const liAccts = accts.linkedin || [];
  const tgAccts = accts.telegram || [];
  const smtpAccts = accts.smtp || [];

  // Load collapse states + IA feature flag.
  // _navIaV3 = true / false / unset
  //   true  → compact Vercel/Linear-hybrid sidebar (5 primary + context pane)
  //   false → user explicitly opted out — fall through to V2/V1
  //   unset → new user / never opened the toggle → DEFAULT V3 (post-2026-05).
  //           This is the flip-to-default switch. Existing users who toggled
  //           V3 off retain their preference because we check `=== false`.
  // _navIaV2 = true → JTBD sidebar (legacy beta) — only when V3 is explicitly off
  // V1 (classic) is now reachable only by opting out of V3 AND V2.
  // Toggle either flag in Settings.
  const saved = await chrome.storage.local.get(["_sidebarCollapse", "_navIaV2", "_navIaV3"]);
  const collapseState = saved._sidebarCollapse || {};
  const v3Pref = saved._navIaV3;
  const useV3 = v3Pref === true || v3Pref === undefined; // default ON
  const useV2 = !useV3 && saved._navIaV2 === true;

  function saveCollapse() {
    chrome.storage.local.set({ _sidebarCollapse: collapseState });
  }

  function accountDotColor(status) {
    if (status === "active") return "green";
    if (status === "rate_limited") return "yellow";
    if (status === "logged_out" || status === "expired") return "gray";
    return "red";
  }

  // Helper: global nav item
  function navItem(icon, label, route, badgeId) {
    const a = el(
      "a",
      {
        class: "nav-item",
        "data-route": route,
        href: `#${route}`,
        onclick: (e) => {
          e.preventDefault();
          logTelemetry("sidebar", "click", { route, label });
          navigate(route);
        },
      },
      [
        el("span", { class: "nav-icon" }, icon),
        el("span", { class: "nav-label" }, label),
        badgeId ? el("span", { class: "nav-badge", id: badgeId }) : null,
      ],
    );
    return a;
  }

  // Helper: platform feature nav item
  function featureItem(icon, label, route, badge) {
    return el(
      "a",
      {
        class: "nav-item",
        "data-route": route,
        href: `#${route}`,
        onclick: (e) => {
          e.preventDefault();
          navigate(route);
        },
      },
      [
        el("span", { class: "nav-icon" }, icon),
        el("span", { class: "nav-label" }, label),
        badge ? el("span", { class: "nav-badge" }, String(badge)) : null,
      ],
    );
  }

  // Helper: platform section
  function platformSection(key, name, accounts, features, maxSlots) {
    const count = accounts.length;
    const isLocked = count === 0;
    // Default collapsed (features hidden) — only show accounts
    const isCollapsed = collapseState[key] !== false;

    const section = el("div", {
      class: `sidebar-platform${isLocked ? " locked" : ""}${isCollapsed ? " collapsed" : ""}`,
    });

    // Header
    const header = el(
      "div",
      {
        class: "platform-header",
        onclick: () => {
          collapseState[key] = !section.classList.contains("collapsed");
          section.classList.toggle("collapsed");
          saveCollapse();
        },
      },
      [
        el("span", { class: "platform-name" }, name),
        el("span", { class: "platform-badge" }, `${count}/${maxSlots}`),
        el("span", { class: "platform-arrow" }, "\u25bc"),
      ],
    );
    section.appendChild(header);

    // Body (accounts + features)
    const body = el("div", { class: "platform-body" });

    if (isLocked) {
      // Connect prompt
      body.appendChild(
        el(
          "a",
          {
            class: "nav-item-connect",
            onclick: (e) => {
              e.preventDefault();
              navigate(`accounts/${key}`);
            },
          },
          `\ud83d\udd17 Connect ${name} account \u2192`,
        ),
      );
    } else {
      // Account rows
      const accountsWrap = el("div", { class: "platform-accounts" });
      for (const a of accounts) {
        const dotColor = accountDotColor(a.status);
        const label = a.account_identifier
          ? `@${a.account_identifier}`
          : a.label || a.identifier || a.email || a.phone || "Account";
        const detail =
          a.warmup_day != null
            ? `d${a.warmup_day}`
            : a.status === "active"
              ? "active"
              : a.status || "";
        accountsWrap.appendChild(
          el(
            "a",
            {
              class: "account-row",
              href: `#${key}/${a.id}`,
              onclick: (e) => {
                e.preventDefault();
                navigate(`${key}/${a.id}`);
              },
            },
            [
              el("span", { class: `account-dot ${dotColor}` }),
              el("span", { class: "account-name" }, label),
              el("span", { class: "account-detail" }, detail),
            ],
          ),
        );
      }
      body.appendChild(accountsWrap);

      // Separator + features
      if (features.length > 0) {
        body.appendChild(el("div", { class: "platform-sep" }));
        const featWrap = el("div", { class: "platform-features" });
        for (const f of features) featWrap.appendChild(f);
        body.appendChild(featWrap);
      }
    }

    section.appendChild(body);
    return section;
  }

  // Helper: section header label
  function sectionHeader(label) {
    return el("div", { class: "sidebar-section-header" }, label);
  }

  // Build the sidebar
  sidebar.innerHTML = "";

  // ── V2 IA: read-mostly sidebar (NOUNS only — verbs live in header "+ New") ──
  // Philosophy:
  //   - Sidebar = things that already exist (Inbox, Pipeline, Campaigns running, Intelligence data)
  //   - Header "+ New" + ⌘K + contextual row buttons = launching operations
  //   - All operations converge on the wizard with prefill
  // Opt-in via Settings -> "Try new navigation (beta)". Default off.

  // V3 (compact, recommended): Vercel/Linear hybrid — 5 primary + context pane
  // + collapsed footer. Renders into the sidebar and returns early. The
  // context pane re-renders on every route change via updateSidebarActive.
  if (useV3) {
    sidebar.innerHTML = "";
    sidebar.style.display = "flex";
    sidebar.style.flexDirection = "column";
    buildV3Sidebar(sidebar);
    updateSidebarActive();
    return;
  }

  if (useV2) {
    // Beta indicator pill at top
    sidebar.appendChild(
      el(
        "div",
        { class: "sidebar-beta-pill", title: "New navigation (beta) — toggle in Settings" },
        "NEW NAV · BETA",
      ),
    );

    // ── PRIMARY: highest-frequency read surfaces ──
    // Inbox is duplicated in the header (with badge), but kept here for muscle memory.
    sidebar.appendChild(
      el("div", { class: "sidebar-section" }, [
        el("nav", { class: "sidebar-nav" }, [
          navItem("📬", "Inbox", "inbox"),
          navItem("🎯", "Today", "mission"), // Mission Control = "what needs my attention right now"
          navItem("🏠", "Home", "home"),
        ]),
      ]),
    );

    // PEOPLE ─ pipeline & relationships (the CRM nouns)
    sidebar.appendChild(
      el("div", { class: "sidebar-section" }, [
        sectionHeader("People"),
        el("nav", { class: "sidebar-nav" }, [
          navItem("🗂", "Contacts & Companies", "crm", "nav-badge-crm"),
          navItem("👥", "Recommended", "people"),
          navItem("💰", "Deals & Pipeline", "deals"),
          navItem("📡", "Activity Feed", "activity"),
        ]),
      ]),
    );

    // OPERATIONS ─ unified entry point for all operations (Phase 2 IA).
    // Catalog = browse/launch any op. Running = active campaigns/workflows.
    // Queue = pending sends across channels.
    sidebar.appendChild(
      el("div", { class: "sidebar-section" }, [
        sectionHeader("Operations"),
        el("nav", { class: "sidebar-nav" }, [
          navItem("🎛", "Catalog", "operations"),
          navItem("📣", "Running", "campaigns"),
          navItem("📤", "Outreach Queue", "outreach-queue"),
          navItem("🧬", "Automations", "automations"),
        ]),
      ]),
    );

    // INTELLIGENCE ─ browseable data: signals, competitors, VC, ICP, etc.
    sidebar.appendChild(
      el("div", { class: "sidebar-section" }, [
        sectionHeader("Intelligence"),
        el("nav", { class: "sidebar-nav" }, [
          navItem("🔥", "Signals", "signals", "nav-badge-signals"),
          navItem("🕵", "Competitors", "competitors"),
          navItem("🏛", "VC / Investors", "vc"),
          navItem("📊", "ICP Targeting", "icp"),
          navItem("🧠", "Service Match", "service-match"),
        ]),
      ]),
    );

    // LIBRARY ─ ASSETS only (not operations). Strategies + Group Ops were
    // moved into Operations Center as proper ops; surfacing them here too
    // would be the duplication we're trying to eliminate.
    sidebar.appendChild(
      el("div", { class: "sidebar-section" }, [
        sectionHeader("Library"),
        el("nav", { class: "sidebar-nav" }, [
          navItem("⭐", "Bookmarks", "bookmarks"),
          navItem("📈", "Reports", "report"),
          navItem("🏆", "Scoreboard", "scoreboard"),
        ]),
      ]),
    );

    // ACCOUNTS & SETTINGS
    sidebar.appendChild(
      el("div", { class: "sidebar-section" }, [
        sectionHeader("Accounts & Settings"),
        el("nav", { class: "sidebar-nav" }, [
          navItem("👤", "Connected Accounts", "accounts"),
          navItem("🛡", "Account Health", "health"),
          navItem("💎", "Premium", "premium"),
          navItem("📘", "Tutorial", "tutorial"),
          navItem("⚙", "Settings", "settings"),
          navItem("🪪", "Profile", "profile"),
        ]),
      ]),
    );

    // PLATFORM POWER TOOLS ─ collapsed by default.
    // Preserves discoverability of Telegram's 12 native features and other platform-specific mechanics
    // for power users without cluttering the JTBD sidebar above.
    const ppCollapsed = collapseState._powerTools !== false; // default collapsed
    const ppSection = el("div", {
      class: `sidebar-section sidebar-power-tools${ppCollapsed ? " collapsed" : ""}`,
    });
    const ppHeader = el(
      "div",
      {
        class: "sidebar-section-header power-tools-header",
        onclick: () => {
          collapseState._powerTools = !ppSection.classList.contains("collapsed");
          ppSection.classList.toggle("collapsed");
          saveCollapse();
        },
      },
      [el("span", { class: "power-tools-arrow" }, "▼"), el("span", null, "Platform Power Tools")],
    );
    ppSection.appendChild(ppHeader);

    const ppBody = el("div", { class: "power-tools-body" });
    ppBody.appendChild(
      platformSection(
        "x",
        "X / Twitter",
        xAccts,
        [
          featureItem("🏠", "Overview", "x/overview"),
          featureItem("🐦", "Post & Engage", "x"),
          featureItem("📊", "Monitor", "x/monitor"),
        ],
        5,
      ),
    );
    ppBody.appendChild(
      platformSection(
        "linkedin",
        "LinkedIn",
        liAccts,
        [
          featureItem("🏠", "Overview", "linkedin/overview"),
          featureItem("📊", "Monitor", "linkedin/monitor"),
        ],
        5,
      ),
    );
    ppBody.appendChild(
      platformSection(
        "telegram",
        "Telegram",
        tgAccts,
        [
          featureItem("🏠", "Overview", "telegram/overview"),
          featureItem("📢", "Broadcast", "telegram/broadcast"),
          featureItem("📥", "Mass Invite", "telegram/invite"),
          featureItem("➡", "Forward", "telegram/forward"),
          featureItem("💬", "Active Member", "telegram/activemember"),
          featureItem("🤖", "Auto Reply", "telegram/autoreply"),
          featureItem("👁", "Listener", "telegram/passive"),
          featureItem("🧲", "Scrape", "telegram/scrape"),
          featureItem("📸", "Stories", "telegram/stories"),
          featureItem("📊", "Monitor", "telegram/monitor"),
        ],
        5,
      ),
    );
    ppBody.appendChild(
      platformSection(
        "email",
        "Email",
        smtpAccts,
        [
          featureItem("🏠", "Overview", "email/overview"),
          featureItem("🔄", "Sequences", "email/sequences"),
          featureItem("🔥", "Warmup", "email/warmup"),
          featureItem("📊", "Monitor", "email/monitor"),
        ],
        5,
      ),
    );
    ppSection.appendChild(ppBody);
    sidebar.appendChild(ppSection);

    return; // V2 done — skip classic render below
  }

  // ── Core navigation
  // HUB \u2500\u2500 Day-to-day workspace: replies, contacts, deals.
  const hubSection = el("div", { class: "sidebar-section" }, [
    sectionHeader("Hub"),
    el("nav", { class: "sidebar-nav" }, [
      navItem("\ud83c\udfe0", "Home", "home"),
      navItem("\ud83d\udcec", "Inbox", "inbox"),
      navItem("\ud83d\udce1", "Activity", "activity"),
      navItem("\ud83d\udce4", "Outreach Queue", "outreach-queue"),
      navItem("\ud83d\udc65", "People", "people"),
      navItem("\ud83d\uddc2", "CRM", "crm", "nav-badge-crm"),
      navItem("\ud83d\udcb0", "Deals", "deals"),
      // "Growth" replaced by Operations \u2014 the per-platform hub view is
      // subsumed by Operations Center's catalog (filterable by platform).
      navItem("\ud83c\udf9b", "Operations", "operations"),
      navItem("\ud83e\uddec", "Automations", "automations"),
      navItem("🎯", "Goals", "objectives"),
      navItem("🚀", "Boost", "boost"),
    ]),
  ]);
  sidebar.appendChild(hubSection);

  // INTELLIGENCE \u2500\u2500 Decision-support: who/what to focus on.
  // "Strategies" removed \u2014 passive automations live in Operations Center now,
  // alongside their active counterparts. Avoiding the duplicate entry point.
  const intelSection = el("div", { class: "sidebar-section" }, [
    sectionHeader("Intelligence"),
    el("nav", { class: "sidebar-nav" }, [
      navItem("\ud83c\udfaf", "Mission Control", "mission"),
      navItem("\ud83d\udd25", "Signals", "signals", "nav-badge-signals"),
      navItem("\ud83d\udd75", "Competitor Intel", "competitors"),
      navItem("\ud83e\udde0", "Service Match", "service-match"),
      navItem("\ud83d\udcca", "ICP", "icp"),
      navItem("\ud83c\udfdb", "VC Outreach", "vc"),
    ]),
  ]);
  sidebar.appendChild(intelSection);

  // PLATFORMS \u2500\u2500 per-platform sections rendered below.
  sidebar.appendChild(sectionHeader("Platforms"));

  // ── X / Twitter
  sidebar.appendChild(
    platformSection(
      "x",
      "X / Twitter",
      xAccts,
      [
        featureItem("\ud83c\udfe0", "Overview", "x/overview"),
        featureItem("\ud83d\udce3", "Campaigns", "campaigns"),
        featureItem("\ud83d\udc26", "Post & Engage", "x"),
        featureItem("\ud83d\udcca", "Monitor", "x/monitor"),
      ],
      5,
    ),
  );

  // ── LinkedIn
  sidebar.appendChild(
    platformSection(
      "linkedin",
      "LinkedIn",
      liAccts,
      [
        featureItem("📜", "Activity", "action-log/linkedin"),
        featureItem("\ud83c\udfe0", "Overview", "linkedin/overview"),
        featureItem("\ud83d\udce3", "Campaigns", "campaigns"),
        featureItem("\ud83d\udcca", "Monitor", "linkedin/monitor"),
      ],
      5,
    ),
  );

  // ── Telegram
  sidebar.appendChild(
    platformSection(
      "telegram",
      "Telegram",
      tgAccts,
      [
        featureItem("\ud83c\udfe0", "Overview", "telegram/overview"),
        featureItem("\ud83d\udce8", "DM Campaigns", "campaigns"),
        featureItem("\ud83d\udce2", "Broadcast", "telegram/broadcast"),
        featureItem("\ud83d\udce5", "Mass Invite", "telegram/invite"),
        featureItem("\u27a1", "Forward", "telegram/forward"),
        featureItem("\ud83d\udcac", "Active Member", "telegram/activemember"),
        featureItem("\ud83e\udd16", "Auto Reply", "telegram/autoreply"),
        featureItem("\ud83d\udc41", "Listener", "telegram/passive"),
        featureItem("\ud83e\uddf2", "Scrape", "telegram/scrape"),
        featureItem("\ud83d\udcf8", "Stories", "telegram/stories"),
        featureItem("\ud83d\udccb", "Group Ops", "group-ops"),
        featureItem("\ud83d\udcca", "Monitor", "telegram/monitor"),
      ],
      5,
    ),
  );

  // ── Email
  sidebar.appendChild(
    platformSection(
      "email",
      "Email",
      smtpAccts,
      [
        featureItem("\ud83c\udfe0", "Overview", "email/overview"),
        featureItem("\u2709\ufe0f", "Campaigns", "campaigns"),
        featureItem("\ud83d\udd04", "Sequences", "email/sequences"),
        featureItem("\ud83d\udd25", "Warmup", "email/warmup"),
        featureItem("\ud83d\udcca", "Monitor", "email/monitor"),
      ],
      5,
    ),
  );

  // ── Bottom
  // SYSTEM \u2500\u2500 Onboarding, accounts, billing, settings.
  const sysSection = el("div", { class: "sidebar-section sidebar-system" }, [
    sectionHeader("System"),
    el("nav", { class: "sidebar-nav" }, [
      navItem("\ud83e\udeaa", "Your Profile", "profile"),
      navItem("\u2b50", "Bookmarks", "bookmarks"),
      navItem("\ud83d\udc64", "Accounts", "accounts"),
      navItem("\ud83d\udee1", "Health & Safety", "health"),
      navItem("\ud83d\udcd8", "Tutorial", "tutorial"),
      navItem("\ud83d\udc8e", "Premium", "premium"),
      navItem("\ud83d\udcc8", "Report", "report"),
      navItem("\ud83c\udfc6", "Scoreboard", "scoreboard"),
      navItem("\u2699", "Settings", "settings"),
    ]),
  ]);
  sidebar.appendChild(sysSection);

  // After a (re)build, sync the active-class state to the current route.
  // Important when buildSidebar runs concurrently with handleRoute on init \u2014
  // handleRoute's call to updateSidebarActive may have run before any nav items
  // existed. Calling it again here guarantees the active state is correct
  // regardless of which finished first.
  updateSidebarActive();
}

function updateSidebarActive() {
  const sidebar = $("#sidebar");
  // V3 sidebar has a context pane whose contents depend on the active section,
  // so a class-toggle isn't enough — we rebuild on every route change.
  // Cheap (O(15 nodes)) and idempotent.
  if (sidebar && sidebar.classList.contains("sidebar-v3")) {
    buildV3Sidebar(sidebar);
    return;
  }

  const { route } = parseHash();
  const topLevel = route.split("/")[0];
  $$("#sidebar .nav-item, #sidebar .account-row").forEach((n) => {
    const r = n.getAttribute("data-route");
    const href = (n.getAttribute("href") || "").replace("#", "");
    n.classList.toggle(
      "active",
      r === topLevel ||
        (topLevel === "signal-pursue" && r === "signals") ||
        href === location.hash.replace("#", ""),
    );
  });
}

async function handleRoute() {
  const { route, params } = parseHash();
  // r50 — run any registered unmount handlers from the previous view
  // (currently used by linkedin/automations to tear down its 3s poll
  // and the nx-batch-progress listener). Safe no-op when none exist.
  try {
    if (typeof _liAutoRunUnmount === "function") _liAutoRunUnmount();
  } catch {
    /* ignore */
  }
  dashboardState.currentRoute = route;
  dashboardState.currentParams = params;

  // Update sidebar active state
  updateSidebarActive();

  // Update topbar title
  const titles = {
    home: "Home",
    operations: "Operations Center",
    "operations/op": "Operation",
    "new-campaign": "New Campaign",
    "new-campaign-wizard": "New Campaign",
    "new-dm-campaign": "New DM Campaign",
    "new-email-campaign": "New Email Campaign",
    campaigns: "Campaigns",
    "campaigns/detail": "Campaign detail",
    "linkedin/automations": "LinkedIn Automations",
    automations: "Automations",
    "automations/view": "Automation viewer",
    workflows: "Workflows",
    signals: "Buying Signals",
    "signal-pursue": "Pursue Signal",
    vc: "VC Outreach",
    "vc/detail": "Fund detail",
    "vc/pursue": "Pursue Partner",
    crm: "CRM",
    x: "X / Twitter",
    deals: "Deals",
    accounts: "Accounts",
    report: "Weekly Report",
    admin: "Admin Panel",
    settings: "Settings",
    "new-workflow": "New Workflow",
    inbox: "Notifications",
    mission: "Mission Control",
    "email/sequences": "Email Sequences",
    "email/warmup": "Email Warmup",
    import: "Import CSV",
    scan: "Company Scanner",
    "telegram/autoreply": "Auto Reply",
    "telegram/broadcast": "Broadcast",
    "telegram/invite": "Mass Invite",
    "telegram/forward": "Forward Rules",
    "telegram/activemember": "Active Member",
    "telegram/passive": "Passive Listener",
    "telegram/stories": "Stories",
    "telegram/scrape": "Smart Scrape",
    "integrations/luma": "Luma Auto-Sub",
    "x/account": "X Account",
    "telegram/account": "Telegram Account",
    people: "People",
    strategies: "Strategies",
    competitors: "Competitor Intel",
    "service-match": "AI Service Match",
    tutorial: "Tutorial",
    premium: "Premium",
    profile: "Your Profile",
    "group-ops": "Group Ops",
    growth: "Growth",
    activity: "Activity",
    bookmarks: "Bookmarks",
    "outreach-queue": "Outreach Queue",
    health: "Health & Safety",
    telegram: "Telegram",
    linkedin: "LinkedIn",
    email: "Email",
    "x/hub": "X / Twitter",
    "telegram/hub": "Telegram",
    "linkedin/hub": "LinkedIn",
    "email/hub": "Email",
    "linkedin/account": "LinkedIn Account",
    "email/account": "Email Account",
    "linkedin/activity": "LinkedIn Activity",
    notifications: "Notifications",
    agency: "Agencia",
  };
  $("#topbar-title").textContent = titles[route] || route;

  // Close drawer on route change
  closeDrawer();
  // Stop background polling started by other pages so they don't leak.
  if (typeof stopActivityPoll === "function" && route !== "activity") stopActivityPoll();
  if (typeof _stopLinkedInActivityPoll === "function" && route !== "linkedin/activity")
    _stopLinkedInActivityPoll();
  if (typeof _stopXActivityPoll === "function" && route !== "action-log/x") _stopXActivityPoll();

  const fn = routes[route];
  if (!fn) {
    setContent(
      `<div class="empty-state"><div class="empty-state-icon">❓</div><div class="empty-state-title">Unknown route</div><div class="empty-state-hint">${escapeHtml(route)}</div></div>`,
    );
    return;
  }
  try {
    await fn(params);
  } catch (err) {
    console.error("Route render failed:", err);
    // Stale-service-worker case: when a route calls an API action that the
    // extension's background.js doesn't recognise (because it was loaded
    // before the action was added), Chrome closes the message port and we
    // get this exact error. Surface a useful instruction instead of the
    // cryptic stack message.
    const isPortClosed = /message port closed/i.test(err.message || "");
    if (isPortClosed) {
      const hint = el("div", { class: "empty-state-hint", style: "line-height:1.6;" });
      hint.innerHTML =
        `The Nexus Connect extension is running an older background script that doesn't know about a new API route.<br><br>` +
        `<b>Fix in 2 steps:</b><br>` +
        `1. Open <code>chrome://extensions/</code> and click the ↻ icon on Nexus Connect.<br>` +
        `2. Then refresh THIS tab with <code>Ctrl+R</code> (the page is still running old code too).`;
      setContentNode(
        el("div", { class: "empty-state" }, [
          el("div", { class: "empty-state-icon" }, "🔄"),
          el("div", { class: "empty-state-title" }, "Extension needs reload"),
          hint,
          el("div", { style: "display:flex;gap:8px;justify-content:center;margin-top:16px;" }, [
            el(
              "button",
              { class: "btn btn-primary", type: "button", onclick: () => location.reload() },
              "↻ Refresh this tab",
            ),
            el(
              "button",
              {
                class: "btn btn-secondary",
                type: "button",
                onclick: () => {
                  location.hash = "#campaigns";
                },
              },
              "« All campaigns",
            ),
          ]),
        ]),
      );
      return;
    }
    setContent(
      `<div class="empty-state"><div class="empty-state-icon">⚠️</div><div class="empty-state-title">Render error</div><div class="empty-state-hint">${escapeHtml(err.message)}</div></div>`,
    );
  }
}

function navigate(route) {
  // Phase ZC: if a test-all session is running and the user navigates away
  // from a workflows/* route to something unrelated, soft-pause the session
  // so the bar state matches reality (no canvas firing → nothing advancing).
  // Excludes intra-session navigates (we set a flag during _navigateToSessionCurrent
  // and renderTestAllResults).
  try {
    const s = dashboardState.testAllSession;
    if (s && s.status === "running" && !s._intraNav) {
      const newR = String(route || "");
      if (!newR.startsWith("workflows/")) {
        s.status = "paused";
        if (typeof _renderTestAllSessionBar === "function") _renderTestAllSessionBar();
      }
    }
  } catch {
    /* defensive — never break navigation */
  }
  location.hash = "#" + route;
}
