  // Completions Card helpers (W-betterHistory) — see the CSS block of the
  // same name in dashboard/slim.html for the component layout reference.
  // ─────────────────────────────────────────────────
  // The type chip renders a monochrome Fluent System Icon via CSS mask (see the
  // .completions-card-type-chip[data-type="…"] rules in styles.css), keyed on
  // the WORK_TYPE constant. This set lists every type that HAS an icon (14
  // WORK_TYPE values + the synthetic 'qa-validate' routed via routing.md);
  // 'review' is intentionally absent — it renders a "REVIEW" word chip instead.
  // Unknown/absent types fall back to a generic "•" text glyph and still get the
  // raw type name in the hover tooltip.
  var TYPE_ICON_SET = {
    'implement': 1, 'implement:large': 1, 'fix': 1, 'verify': 1, 'test': 1,
    'plan': 1, 'plan-to-prd': 1, 'decompose': 1, 'meeting': 1, 'explore': 1,
    'ask': 1, 'docs': 1, 'setup': 1, 'qa-validate': 1,
  };

  // Pick the title for a completion card. Uses the dispatch description
  // (c.task) — that's what the agent was actually told to do — falling back
  // to the linked WI title only when no task description is present.
  function completionTitle(c) {
    var item = (c.meta && c.meta.item) || {};
    return c.task || item.title || c.title || c.workItemTitle || c.id || '(untitled)';
  }

  // Status: 'ok' (success) | 'fail' (error) | 'warn' (partial managed-spawn).
  function completionStatus(c) {
    if (c._managedSpawnPartial) return 'warn';
    if (c.result === 'success' || c.status === 'done') return 'ok';
    return 'fail';
  }

  // Build one lineage chip with a fixed kind label + value (or "-" when
  // absent). DOM-built (no innerHTML) so the SEC-03 baseline stays clean.
  function buildLineageChip(kindLabel, value, tooltip) {
    var chip = document.createElement('span');
    chip.className = 'completions-card-chip';
    chip.title = tooltip || kindLabel;
    var labelEl = document.createElement('span');
    labelEl.className = 'completions-card-chip-label';
    labelEl.textContent = kindLabel + ':';
    var valueEl = document.createElement('span');
    valueEl.className = 'completions-card-chip-value';
    valueEl.textContent = value == null || value === '' ? '-' : String(value);
    chip.appendChild(labelEl);
    chip.appendChild(valueEl);
    return chip;
  }

  // Render a single history card (DOM node). Returns the .history-item
  // element with attached click handlers. Used by renderHistoryFeed for both
  // finished completions AND in-flight dispatches — a running dispatch is just
  // a dispatch without a result yet, so `isActive` swaps the terminal ✓/✕ for a
  // live ● and anchors the time on `started_at` instead of `completed_at`.
  function renderCompletionCard(c, isActive) {
    var status = isActive ? 'active' : completionStatus(c);
    var ts = isActive
      ? (c.started_at || c.created_at || c._ts)
      : (c.completed_at || c.completedAt || c.endedAt || c.started_at || c.startedAt || c._ts);
    var title = completionTitle(c);
    var item = (c.meta && c.meta.item) || {};
    var pr = (c.meta && c.meta.pr) || null;
    var kindClass = isActive ? 'dispatch' : (status === 'fail' ? 'failure' : 'completion');

    var card = document.createElement('div');
    card.className = 'history-item completions-card kind-' + kindClass;
    card.setAttribute('role', 'button');
    card.setAttribute('tabindex', '0');
    card.title = title;

    // Left rail: two rows. Top 70% = type emoji alone (prominent);
    // bottom 30% = `<status-icon> · <relative-time>`, all in muted gray.
    var rail = document.createElement('div');
    rail.className = 'completions-card-rail';
    var railTop = document.createElement('div');
    railTop.className = 'completions-card-rail-top';
    // Review cards render the word "REVIEW" as a chip (same box treatment as
    // the WI/Plan/PR lineage chips, larger font) instead of the type emoji.
    // Every other type keeps its single-glyph emoji.
    var chip = document.createElement('div');
    if (c.type === 'review') {
      chip.className = 'completions-card-type-word';
      chip.textContent = 'REVIEW';
      chip.title = 'review';
    } else {
      chip.className = 'completions-card-type-chip';
      // Known types → Fluent icon via CSS mask (data-type); unknown → "•" glyph.
      if (c.type && TYPE_ICON_SET[c.type]) {
        chip.setAttribute('data-type', c.type);
      } else {
        chip.textContent = '•';
      }
      if (c.type) chip.title = c.type;
    }
    railTop.appendChild(chip);

    var railBottom = document.createElement('div');
    railBottom.className = 'completions-card-rail-bottom';
    var icon = document.createElement('span');
    icon.className = 'completions-card-status-icon ' + status;
    // ok/warn/fail render as Fluent icons via the status class (CSS mask in
    // styles.css); the live 'active' state keeps its pulsing dot.
    if (status === 'active') icon.textContent = '●';
    icon.title = status === 'active' ? 'Running' : (status === 'ok' ? 'Success' : (status === 'warn' ? 'Partial' : 'Failure'));
    var sep = document.createElement('span');
    sep.className = 'completions-card-rail-sep';
    sep.textContent = '·';
    sep.setAttribute('aria-hidden', 'true');
    var when = document.createElement('span');
    when.className = 'completions-card-time';
    // Active dispatches read as "running 5m" (elapsed since start); finished
    // rows read as "5m ago" (relative to completion). Both reuse relTime.
    when.textContent = ts
      ? (status === 'active' ? 'running ' + relTime(ts).replace(' ago', '') : relTime(ts))
      : '';
    if (ts) when.title = new Date(ts).toLocaleString();
    railBottom.appendChild(icon);
    railBottom.appendChild(sep);
    railBottom.appendChild(when);
    rail.appendChild(railTop);
    rail.appendChild(railBottom);

    // Body-top — title + optional failure line. Lives in grid cell (col 2,
    // row 1); rail-top centers vertically against this block via subgrid.
    var bodyTop = document.createElement('div');
    bodyTop.className = 'completions-card-body-top';
    var titleEl = document.createElement('div');
    titleEl.className = 'completions-card-title';
    titleEl.textContent = title;
    bodyTop.appendChild(titleEl);

    // Byline — which team member ran it (name · role). Present on every row so
    // the feed answers "who did this" at a glance, not just "what / result".
    var who = c.agentName || c.agent;
    if (who) {
      var byline = document.createElement('div');
      byline.className = 'completions-card-byline';
      byline.textContent = c.agentRole ? who + ' · ' + c.agentRole : who;
      bodyTop.appendChild(byline);
    }

    if (status === 'fail' && (c.failureClass || c.reason)) {
      var failLine = document.createElement('div');
      failLine.className = 'completions-card-fail-line';
      var truncatedReason = c.reason ? String(c.reason).slice(0, 120) : '';
      if (c.failureClass) {
        var fc = document.createElement('span');
        fc.className = 'completions-card-fail-class';
        fc.textContent = c.failureClass;
        failLine.appendChild(fc);
        if (truncatedReason) failLine.appendChild(document.createTextNode(' — ' + truncatedReason));
      } else {
        failLine.textContent = truncatedReason;
      }
      if (c.reason && c.reason.length > 120) failLine.title = c.reason;
      bodyTop.appendChild(failLine);
    }

    // Bottom row: fixed 3-column lineage grid (WI / Plan / PR). Lives in
    // grid cell (col 2, row 2); rail-bottom sits in the same parent row
    // so the status·time line aligns with this chip row.
    var lineage = document.createElement('div');
    lineage.className = 'completions-card-lineage';
    lineage.appendChild(buildLineageChip('WI', item.id || null,
      item.id ? 'Work item: ' + item.id + (item.title ? ' — ' + item.title : '') : 'Work item: none'));
    var planValue = item.sourcePlan ? String(item.sourcePlan).replace(/\.(md|json)$/i, '') : null;
    lineage.appendChild(buildLineageChip('Plan', planValue,
      item.sourcePlan ? 'Source plan: ' + item.sourcePlan : 'Source plan: none'));
    var prRef = pr && (pr.id || pr.number) ? String(pr.id || pr.number) : null;
    var prChip = buildLineageChip('PR', prRef ? '!' + prRef : null,
      prRef ? 'Pull request: !' + prRef + (pr.url ? ' (' + pr.url + ')' : '') : 'Pull request: none');
    if (prRef && pr && pr.url) {
      prChip.classList.add('completions-card-chip-pr');
      prChip.addEventListener('click', function(ev) {
        ev.stopPropagation();
        window.open(pr.url, '_blank', 'noopener');
      });
    }
    lineage.appendChild(prChip);

    // Three direct children of the 2x2 card grid: rail (spans both rows
    // via subgrid), bodyTop (cell 2,1), lineage (cell 2,2).
    card.appendChild(rail);
    card.appendChild(bodyTop);
    card.appendChild(lineage);

    // Whole-card click → completion details modal.
    card.addEventListener('click', function() { showCompletionDetails(c); });
    card.addEventListener('keydown', function(ev) {
      if (ev.key === 'Enter' || ev.key === ' ') {
        ev.preventDefault();
        showCompletionDetails(c);
      }
    });

    return card;
  }

  // Modal: full task, resultSummary, linked WI, completion report contents.
  // Closes on backdrop click or × button (wired below).
  var _completionReportCache = {};
  function showCompletionDetails(c) {
    var modal = document.getElementById('slim-completions-modal');
    var body = document.getElementById('slim-completions-body');
    var titleEl = document.getElementById('slim-completions-title');
    if (!modal || !body) return;

    var status = completionStatus(c);
    var statusText = status === 'ok' ? 'Success' : (status === 'warn' ? 'Partial' : 'Failure');
    titleEl.textContent = statusText + ' · ' + (c.type || 'completion');

    // Build modal body as DOM nodes (no innerHTML — keeps SEC-03 baseline happy).
    body.replaceChildren();

    var addSection = function(label, valueNode) {
      var section = document.createElement('div');
      section.className = 'completions-modal-section';
      var lab = document.createElement('div');
      lab.className = 'completions-modal-label';
      lab.textContent = label;
      section.appendChild(lab);
      section.appendChild(valueNode);
      body.appendChild(section);
    };
    var textValue = function(text) {
      var v = document.createElement('div');
      v.className = 'completions-modal-value';
      v.textContent = text;
      return v;
    };

    var title = completionTitle(c);
    if (title) addSection('Task', textValue(title));

    var taskFull = c.task && c.task !== title ? c.task : null;
    if (taskFull) addSection('Dispatch description', textValue(taskFull));

    var agentLine = (c.agentName || c.agent || '?') +
      (c.agentRole ? ' (' + c.agentRole + ')' : '') +
      (c.runtimeName ? ' · ' + c.runtimeName : '');
    addSection('Agent', textValue(agentLine));

    var startedAt = c.started_at || c.startedAt;
    var completedAt = c.completed_at || c.completedAt;
    if (startedAt || completedAt) {
      var lines = [];
      if (startedAt) lines.push('Started: ' + new Date(startedAt).toLocaleString());
      if (completedAt) lines.push('Completed: ' + new Date(completedAt).toLocaleString());
      if (startedAt && completedAt) {
        var dur = Date.parse(completedAt) - Date.parse(startedAt);
        if (dur > 0) lines.push('Duration: ' + formatDuration(dur));
      }
      addSection('Timing', textValue(lines.join('\n')));
    }

    if (status === 'fail' && (c.failureClass || c.reason)) {
      var failBlock = c.failureClass ? (c.failureClass + (c.reason ? '\n\n' + c.reason : '')) : c.reason;
      addSection('Failure', textValue(failBlock));
    }

    if (c.resultSummary) addSection('Result summary', textValue(c.resultSummary));

    // Lineage: WI, source plan, PR.
    var item = (c.meta && c.meta.item) || {};
    var pr = (c.meta && c.meta.pr) || null;
    if (item.id || item.sourcePlan || pr) {
      var lineageWrap = document.createElement('div');
      lineageWrap.className = 'completions-modal-value';
      var lines2 = [];
      if (item.id) lines2.push('Work item: #' + item.id + (item.title ? ' — ' + item.title : ''));
      if (item.sourcePlan) lines2.push('Source plan: ' + item.sourcePlan);
      if (pr) {
        var prLine = 'PR: !' + (pr.id || pr.number || '?');
        if (pr.url) prLine += ' (' + pr.url + ')';
        if (pr.branch) prLine += '\n  branch: ' + pr.branch;
        if (pr.buildStatus) prLine += '\n  build: ' + pr.buildStatus;
        if (pr.reviewStatus) prLine += '\n  review: ' + pr.reviewStatus;
        lines2.push(prLine);
      }
      lineageWrap.textContent = lines2.join('\n');
      addSection('Lineage', lineageWrap);
    }

    // Completion report: lazy-fetch from meta.completionReportPath when present.
    var reportPath = c.meta && c.meta.completionReportPath;
    if (reportPath) {
      var reportSection = document.createElement('div');
      reportSection.className = 'completions-modal-section';
      var reportLab = document.createElement('div');
      reportLab.className = 'completions-modal-label';
      reportLab.textContent = 'Completion report';
      reportSection.appendChild(reportLab);
      var reportBox = document.createElement('div');
      reportBox.className = 'completions-modal-report';
      reportBox.textContent = 'Loading…';
      reportSection.appendChild(reportBox);
      body.appendChild(reportSection);
      var cacheKey = c.id || reportPath;
      if (_completionReportCache[cacheKey]) {
        reportBox.textContent = _completionReportCache[cacheKey];
      } else if (!c.id) {
        reportBox.textContent = '(report unavailable — no dispatch id)';
      } else {
        fetch('/api/dispatch/' + encodeURIComponent(c.id) + '/completion-report', {
          headers: { 'Accept': 'application/json' },
        }).then(function(r) {
          if (!r.ok) throw new Error('HTTP ' + r.status);
          return r.json();
        }).then(function(payload) {
          var pretty = JSON.stringify(payload, null, 2);
          _completionReportCache[cacheKey] = pretty;
          reportBox.textContent = pretty;
        }).catch(function() {
          reportBox.textContent = '(report unavailable — ' + reportPath + ')';
        });
      }
    }

    modal.classList.add('open');
  }

  function formatDuration(ms) {
    if (ms < 1000) return ms + 'ms';
    var s = Math.floor(ms / 1000);
    if (s < 60) return s + 's';
    var m = Math.floor(s / 60);
    var rs = s % 60;
    if (m < 60) return m + 'm ' + rs + 's';
    var h = Math.floor(m / 60);
    var rm = m % 60;
    return h + 'h ' + rm + 'm';
  }

  // Bind completions-modal close handlers once (shared helper above).
  bindModalClose('slim-completions-modal', 'slim-completions-close');

  // Render a PR as a meta-rail card matching the dispatch/completion shape:
  // a "PR" word chip in the rail (left), the title in the body, the activity
  // time in the rail-bottom, and the PR status as a tile-chip — the SAME
  // component the /pull-requests tile list uses — in the bottom row. The whole
  // card is the click target (opens the PR URL); there's no separate "open"
  // link anymore.
  function renderPrCard(p) {
    var num = p.prNumber || p.number || p.id;
    var title = '#' + (num || '?') + ' — ' + (p.title || '(untitled PR)');
    var ts = p.updatedAt || p.lastReviewedAt || p.lastBuildCheck || p._attachedAt || p.created || p.createdAt;
    var build = p.buildStatus || '';
    // Mirror renderPrTileBody's color logic so the chip matches the PR list:
    // merged → green, failing build → red, active/linked → blue, else neutral.
    var cls = p.status === 'merged' ? 'green'
            : (build === 'failing' ? 'red'
            : ((p.status === 'active' || p.status === 'linked') ? 'blue' : ''));

    var card = document.createElement('div');
    card.className = 'history-item completions-card kind-pr';
    card.setAttribute('role', 'button');
    card.setAttribute('tabindex', '0');
    card.title = title;

    var rail = document.createElement('div');
    rail.className = 'completions-card-rail';
    var railTop = document.createElement('div');
    railTop.className = 'completions-card-rail-top';
    var word = document.createElement('div');
    word.className = 'completions-card-type-word';
    word.textContent = 'PR';
    word.title = 'pull request';
    railTop.appendChild(word);
    var railBottom = document.createElement('div');
    railBottom.className = 'completions-card-rail-bottom';
    var when = document.createElement('span');
    when.className = 'completions-card-time';
    when.textContent = ts ? relTime(ts) : '';
    if (ts) when.title = new Date(ts).toLocaleString();
    railBottom.appendChild(when);
    rail.appendChild(railTop);
    rail.appendChild(railBottom);

    var bodyTop = document.createElement('div');
    bodyTop.className = 'completions-card-body-top';
    var titleEl = document.createElement('div');
    titleEl.className = 'completions-card-title';
    titleEl.textContent = title;
    bodyTop.appendChild(titleEl);

    // Status row — grid col 2, row 2 (aligns with rail-bottom). A single
    // tile-chip, the same pill component (and color classes) as the PR list.
    var statusRow = document.createElement('div');
    statusRow.className = 'completions-card-pr-status';
    var chip = document.createElement('span');
    chip.className = 'tile-chip ' + cls;
    chip.textContent = p.status || '—';
    statusRow.appendChild(chip);

    card.appendChild(rail);
    card.appendChild(bodyTop);
    card.appendChild(statusRow);

    // Whole-card click (and keyboard activation) opens the PR — replaces the
    // old inline "open" hyperlink.
    if (p.url) {
      var openPr = function() { window.open(p.url, '_blank', 'noopener'); };
      card.addEventListener('click', openPr);
      card.addEventListener('keydown', function(ev) {
        if (ev.key === 'Enter' || ev.key === ' ') { ev.preventDefault(); openPr(); }
      });
    }
    return card;
  }

  function renderHistoryFeed(input) {
    var listEl = document.getElementById('history-list');
    if (!listEl) return;
    var events = [];

    // Active dispatches (blue) — rendered with the same meta-rail card as
    // completions (a running dispatch is just a dispatch without a result yet).
    // Carry the raw record so the card renderer reads task/agent/lineage from
    // the canonical field paths. NOTE: the engine stamps `started_at`
    // (snake_case) — reading `startedAt` here is the old bug that made every
    // dispatch show "0s ago". Sorted purely chronologically by start time.
    (input.active || []).forEach(function(d) {
      var ts = d.started_at || d.created_at || d._ts;
      events.push({
        kind: 'dispatch',
        raw: d,
        ts: ts ? Date.parse(ts) || 0 : 0,
      });
    });

    // Recent completions (green or red) — last 12 most recent first.
    // Carry the raw record on the event so the new card renderer has access
    // to lineage / failure / report fields without re-flattening.
    (input.completed || []).slice().reverse().slice(0, 12).forEach(function(c) {
      var ok = completionStatus(c) === 'ok';
      var ts = c.completed_at || c.completedAt || c.endedAt || c.started_at || c.startedAt || c._ts;
      events.push({
        kind: ok ? 'completion' : 'failure',
        raw: c,
        ts: ts ? Date.parse(ts) || 0 : 0,
      });
    });

    // PR events (amber) — shows PRs we're tracking, anchored on the most
    // recent activity. The slim PR shape does NOT carry updatedAt/createdAt —
    // reading those (the old code) yielded ts=0 and pinned every PR to the
    // bottom of the feed regardless of recency. Fall through the timestamps the
    // payload actually provides, newest-meaning first.
    // Carry the raw PR record so renderPrCard reads its own fields (the card
    // opens the PR on click — no inline link).
    (input.prs || []).slice(0, 6).forEach(function(p) {
      var ts = p.updatedAt || p.lastReviewedAt || p.lastBuildCheck || p._attachedAt || p.created || p.createdAt;
      events.push({
        kind: 'pr',
        raw: p,
        ts: ts ? Date.parse(ts) || 0 : 0,
      });
    });

    events.sort(function(a, b) { return (b.ts || 0) - (a.ts || 0); });
    events = events.slice(0, 20);

    if (events.length === 0) {
      var empty = document.createElement('div');
      empty.className = 'history-empty';
      empty.textContent = 'Nothing has happened yet — Command Center will fill this in.';
      listEl.replaceChildren(empty);
      return;
    }
    // Build the feed as DOM nodes so SEC-03's .innerHTML ratchet stays happy
    // (test/unit.test.js DYNAMIC_INNERHTML_BASELINE). Every event type now
    // renders as a meta-rail card: dispatch / completion / failure via
    // renderCompletionCard, PRs via renderPrCard.
    var frag = document.createDocumentFragment();
    for (var i = 0; i < events.length; i++) {
      var e = events[i];
      if (e.kind === 'dispatch' && e.raw) {
        frag.appendChild(renderCompletionCard(e.raw, true));
      } else if ((e.kind === 'completion' || e.kind === 'failure') && e.raw) {
        frag.appendChild(renderCompletionCard(e.raw, false));
      } else if (e.kind === 'pr' && e.raw) {
        frag.appendChild(renderPrCard(e.raw));
      }
    }
    listEl.replaceChildren(frag);
  }

  async function pollStatusOnce() {
    try {
      var res = await fetch('/api/status', { headers: { 'Accept': 'application/json' } });
      if (!res.ok) throw new Error('HTTP ' + res.status);
      var data = await res.json();
      applyStatus(data);
    } catch (e) {
      // Soft failure — leave previous values, surface in stamp
      var stamp = document.getElementById('cockpit-stamp');
      if (stamp) stamp.textContent = 'status fetch failed: ' + (e.message || e);
    }
  }

  function startStatusPolling() {
    pollStatusOnce();
    if (pollTimer) clearInterval(pollTimer);
    pollTimer = setInterval(pollStatusOnce, STATUS_POLL_MS);
  }
  // Pause polling when the tab is hidden; resume when it's visible again.
  // Saves us 12 fetches/min on background tabs.
  document.addEventListener('visibilitychange', function() {
    if (document.hidden) {
      if (pollTimer) { clearInterval(pollTimer); pollTimer = null; }
    } else {
      startStatusPolling();
    }
  });
  startStatusPolling();