  // ── Settings dialog ────────────────────────────────────────────
  async function openSlimSettings() {
    var modal = document.getElementById('slim-settings-modal');
    var body = document.getElementById('slim-settings-body');
    body.innerHTML = '<p>Loading flags&hellip;</p>';
    modal.classList.add('open');
    try {
      var res = await fetch('/api/features');
      if (!res.ok) throw new Error('HTTP ' + res.status);
      var data = await res.json();
      var flags = (data && data.features) || [];

      // Build the body as a document fragment so we avoid .innerHTML for
      // the dynamic parts (SEC-03 ratchet — see DYNAMIC_INNERHTML_BASELINE
      // in test/unit.test.js). Static intro paragraph uses innerHTML
      // because it carries a <code> tag and is a pure string literal
      // (exempted by _isStaticInnerHtmlRhs).
      var intro = document.createElement('p');
      intro.innerHTML = 'Toggle <code>slim-ux</code> off to return to the original dashboard. Advanced settings (agents, projects, runtime) live there for now.';
      var frag = document.createDocumentFragment();
      frag.appendChild(intro);

      if (flags.length === 0) {
        var none = document.createElement('p');
        none.innerHTML = 'No experimental flags registered. Add entries to <code>engine/features.js</code>.';
        frag.appendChild(none);
      } else {
        for (var i = 0; i < flags.length; i++) {
          var f = flags[i];
          var row = document.createElement('div');
          row.className = 'flag-row';
          var info = document.createElement('div');
          var name = document.createElement('div');
          name.className = 'flag-name';
          name.textContent = f.id;
          var desc = document.createElement('div');
          desc.className = 'flag-desc';
          desc.textContent = f.description || '';
          info.appendChild(name);
          info.appendChild(desc);
          var lbl = document.createElement('label');
          lbl.className = 'flag-toggle';
          var input = document.createElement('input');
          input.type = 'checkbox';
          input.setAttribute('data-flag', f.id);
          if (f.enabled) input.checked = true;
          var stateSpan = document.createElement('span');
          stateSpan.textContent = f.enabled ? 'On' : 'Off';
          lbl.appendChild(input);
          lbl.appendChild(stateSpan);
          row.appendChild(info);
          row.appendChild(lbl);
          frag.appendChild(row);
        }
      }

      // TEMP: deep-link to the full dashboard's settings page. The slim
      // intentionally doesn't reimplement that whole UI — Carlos asked us
      // to keep settings sprawl off the main slim view.
      var link = document.createElement('a');
      link.className = 'settings-link';
      link.href = '/?fullDashboard=1';
      link.title = 'Open the full dashboard’s settings page';
      link.textContent = 'Advanced settings → full dashboard';
      frag.appendChild(link);

      body.replaceChildren(frag);
      var inputs = body.querySelectorAll('input[type=checkbox][data-flag]');
      for (var j = 0; j < inputs.length; j++) {
        inputs[j].addEventListener('change', onFlagToggle);
      }
    } catch (e) {
      var err = document.createElement('p');
      err.style.color = 'var(--red)';
      err.textContent = 'Failed to load flags: ' + (e && e.message ? e.message : 'unknown error');
      body.replaceChildren(err);
    }
  }

  function closeSlimSettings() {
    document.getElementById('slim-settings-modal').classList.remove('open');
  }

  (function bindSettingsUi() {
    var btn = document.getElementById('slim-settings-btn');
    var closeBtn = document.getElementById('slim-settings-close');
    var backdrop = document.getElementById('slim-settings-modal');
    if (btn) btn.addEventListener('click', openSlimSettings);
    if (closeBtn) closeBtn.addEventListener('click', closeSlimSettings);
    if (backdrop) {
      backdrop.addEventListener('click', function(ev) {
        if (ev.target === backdrop) closeSlimSettings();
      });
    }
    document.addEventListener('keydown', function(ev) {
      if (ev.key !== 'Escape') return;
      if (backdrop && backdrop.classList.contains('open')) closeSlimSettings();
    });
  })();

  // POST a feature-flag toggle; throws on a non-2xx so callers can branch on
  // failure. Shared by onFlagToggle() and the one-click returnToClassicDashboard().
  async function postFeatureToggle(id, enabled) {
    var res = await fetch('/api/features/toggle', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ id: id, enabled: enabled }),
    });
    if (!res.ok) throw new Error('HTTP ' + res.status);
  }

  // ── Return to the classic dashboard ──────────────────────────────
  // Flips the slim-ux flag OFF then reloads. The root route's slim takeover is
  // re-checked per request, so the reload lands on the full SPA with no engine
  // restart. A one-click affordance (topbar button + welcome popup) over the
  // same toggle the settings list drives via onFlagToggle().
  async function returnToClassicDashboard(btn) {
    if (btn) btn.disabled = true;
    try {
      await postFeatureToggle('slim-ux', false);
      location.reload();
    } catch (e) {
      if (btn) btn.disabled = false;
      alert('Could not switch back: ' + (e && e.message ? e.message : 'unknown error'));
    }
  }

  // ── First-visit welcome popup ────────────────────────────────────
  // Shows exactly once per browser. The key lives on the shared origin, so
  // flipping slim-ux off and on again won't re-show it — "only the first time".
  var SLIM_WELCOME_KEY = 'minions:slim-ux-welcomed';
  function markSlimWelcomeSeen() {
    try { localStorage.setItem(SLIM_WELCOME_KEY, '1'); } catch (e) { /* private mode — popup re-shows next visit, acceptable */ }
  }
  function maybeShowSlimWelcome() {
    // Gated by the slim-ux-promo flag (off by default) — when the promo is off,
    // never advertise Slim UX via the welcome popup, even on a first visit.
    if (!slimFeatureOn('slim-ux-promo')) return;
    try { if (localStorage.getItem(SLIM_WELCOME_KEY) === '1') return; } catch (e) { /* private mode — show anyway */ }
    var modal = document.getElementById('slim-welcome-modal');
    if (modal) modal.classList.add('open');
  }

  // ── Report a bug ─────────────────────────────────────────────────
  // Files a GitHub issue on the Minions repo via POST /api/issues/create —
  // the same endpoint the classic dashboard's "Report Bug" button uses.
  function openSlimBugReport() {
    var modal = document.getElementById('slim-bug-modal');
    if (!modal) return;
    var msg = document.getElementById('slim-bug-msg');
    if (msg) { msg.textContent = ''; msg.style.color = 'var(--muted)'; }
    var submit = document.getElementById('slim-bug-submit');
    if (submit) { submit.disabled = false; submit.textContent = 'File Bug'; }
    modal.classList.add('open');
    setTimeout(function() { var t = document.getElementById('slim-bug-title'); if (t) t.focus(); }, 40);
  }
  function closeSlimBugReport() {
    var modal = document.getElementById('slim-bug-modal');
    if (modal) modal.classList.remove('open');
  }
  async function submitSlimBugReport() {
    var titleEl = document.getElementById('slim-bug-title');
    var descEl = document.getElementById('slim-bug-desc');
    var msg = document.getElementById('slim-bug-msg');
    var submit = document.getElementById('slim-bug-submit');
    var title = ((titleEl && titleEl.value) || '').trim();
    var desc = ((descEl && descEl.value) || '').trim();
    if (!title) {
      if (msg) { msg.style.color = 'var(--red)'; msg.textContent = 'Title is required.'; }
      return;
    }
    if (submit) { submit.disabled = true; submit.textContent = 'Filing…'; }
    if (msg) { msg.style.color = 'var(--muted)'; msg.textContent = 'Filing…'; }
    try {
      var res = await fetch('/api/issues/create', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ title: title, description: desc }),
      });
      var d = await res.json().catch(function() { return {}; });
      if (!res.ok) throw new Error(d.error || ('HTTP ' + res.status));
      if (titleEl) titleEl.value = '';
      if (descEl) descEl.value = '';
      if (msg) {
        msg.style.color = 'var(--green)';
        msg.textContent = '🐛 Bug filed! ';
        // Only trust https URLs from the server (guards against javascript: etc.).
        if (d.url && /^https:\/\//.test(d.url)) {
          var a = document.createElement('a');
          a.href = d.url;
          a.target = '_blank';
          a.rel = 'noopener noreferrer';
          a.style.color = 'var(--blue)';
          a.textContent = 'View issue';
          msg.appendChild(a);
        }
      }
      if (submit) { submit.disabled = false; submit.textContent = 'File another'; }
    } catch (e) {
      if (msg) { msg.style.color = 'var(--red)'; msg.textContent = 'Error: ' + (e && e.message ? e.message : 'failed to file'); }
      if (submit) { submit.disabled = false; submit.textContent = 'File Bug'; }
    }
  }

  (function bindSlimChromeExtras() {
    var backBtn = document.getElementById('slim-back-classic-btn');
    if (backBtn) backBtn.addEventListener('click', function() { returnToClassicDashboard(backBtn); });
    var welcomeBack = document.getElementById('slim-welcome-back');
    if (welcomeBack) welcomeBack.addEventListener('click', function() { returnToClassicDashboard(welcomeBack); });
    // Dismiss button, backdrop click, and Esc all close the popup AND mark it
    // seen — reusing the shared modal binder (no bespoke listener wiring).
    bindModalClose('slim-welcome-modal', 'slim-welcome-dismiss', markSlimWelcomeSeen);
    maybeShowSlimWelcome();

    // Report Bug — header button opens the modal; submit/cancel + the shared
    // modal binder (close X, backdrop, Esc) handle the rest.
    var bugBtn = document.getElementById('slim-report-bug-btn');
    if (bugBtn) bugBtn.addEventListener('click', openSlimBugReport);
    var bugSubmit = document.getElementById('slim-bug-submit');
    if (bugSubmit) bugSubmit.addEventListener('click', submitSlimBugReport);
    var bugCancel = document.getElementById('slim-bug-cancel');
    if (bugCancel) bugCancel.addEventListener('click', closeSlimBugReport);
    bindModalClose('slim-bug-modal', 'slim-bug-close');
  })();

  async function onFlagToggle(ev) {
    var input = ev.currentTarget;
    var id = input.getAttribute('data-flag');
    var enabled = input.checked;
    var label = input.parentElement.querySelector('span');
    if (label) label.textContent = enabled ? 'On' : 'Off';
    try {
      await postFeatureToggle(id, enabled);
      if (id === 'slim-ux' && !enabled) {
        location.reload();
      }
    } catch (e) {
      input.checked = !enabled;
      if (label) label.textContent = !enabled ? 'On' : 'Off';
      alert('Toggle failed: ' + e.message);
    }
  }

  // ── Action prompt modal (TEMP: routes through chat) ──────────────
  // Each of the 4 action buttons opens this modal with prompt text
  // tailored to the concept. On submit we drop the user's text into the
  // chat input prefixed with intent text and submit it through the
  // existing CC stream pipeline. Direct /api/work-items style wiring is a
  // round-2 task — we want CC's natural-language handling for round 1
  // because it's already working end to end.
