===== /home/yeff/public_html/devon/panel/assets/js/panel.js =====
'use strict';

const BUCKETS = [
  'prerequisites',
  'installation',
  'configuration',
  'validation',
  'observable_evidence',
  'failure_modes_recovery',
  'completion_promotion'
];

const BUCKET_DESC = {
  prerequisites:         'Required conditions, blockers and hard dependencies',
  installation:          'File and artifact installation steps',
  configuration:         'Configuration and binding steps',
  validation:            'Validation gates and pass/fail criteria',
  observable_evidence:   'Filesystem and runtime observable evidence',
  failure_modes_recovery:'Known failure modes and recovery actions',
  completion_promotion:  'Done criteria and promotion gate'
};

const G = {
  phases: [], cats: [], data: {},
  openPhases: new Set(), openCats: new Set(),
  sel: { phaseId: null, catId: null, bucket: null }
};

async function init() {
  try {
    const [hub, host, docker, runtime, progress] = await Promise.all([
      fetch('data/hub_index.json').then(r => r.json()),
      fetch('data/host_runtime.json').then(r => r.json()).catch(() => null),
      fetch('data/docker_runtime.json').then(r => r.json()).catch(() => null),
      fetch('data/runtime_status.json').then(r => r.json()).catch(() => null),
      fetch('data/project_progress.json').then(r => r.json()).catch(() => null)
    ]);
    G.phases = hub.phases || [];
    G.cats = hub.categories || [];
    G.data = { host, docker, runtime, progress };
    renderTree();
    renderDetail();
    renderFooter();
  } catch (e) {
    document.getElementById('dp-tree').innerHTML =
      '<div class="tree-err">Load error: ' + e.message + '</div>';
  }
}

function phaseOfCat(cat) {
  const m = (cat.badge || '').match(/\d+/);
  if (!m) return 'phase-01';
  return 'phase-' + String(parseInt(m[0])).padStart(2, '0');
}

function catsForPhase(phaseId) {
  return G.cats.filter(c => phaseOfCat(c) === phaseId);
}

/* ── TREE ─────────────────────────────────────────── */

function renderTree() {
  let html = '';
  for (const ph of G.phases) {
    const isOpen = G.openPhases.has(ph.id);
    const phActive = G.sel.phaseId === ph.id ? ' ph-active' : '';
    const num = ph.step.replace('Phase ', '');
    html += `<div class="ph-row${phActive}" data-ph="${ph.id}">
      <span class="ph-arrow${isOpen ? ' open' : ''}">${isOpen ? '▾' : '▸'}</span>
      <span class="ph-num">${num}</span>
      <span class="ph-name">${ph.name}</span>
      <span class="ph-dot d-plan"></span>
    </div>`;
    if (isOpen) {
      for (const cat of catsForPhase(ph.id)) {
        const isCatOpen = G.openCats.has(cat.id);
        const catActive = G.sel.catId === cat.id ? ' cat-active' : '';
        html += `<div class="cat-row${catActive}" data-ph="${ph.id}" data-cat="${cat.id}">
          <span class="cat-arrow${isCatOpen ? ' open' : ''}">${isCatOpen ? '▾' : '▸'}</span>
          <span class="cat-name">${cat.title}</span>
          <span class="cat-dot d-plan"></span>
        </div>`;
        if (isCatOpen) {
          BUCKETS.forEach((bk, i) => {
            const bkActive = G.sel.catId === cat.id && G.sel.bucket === bk ? ' bk-active' : '';
            html += `<div class="bk-row${bkActive}" data-ph="${ph.id}" data-cat="${cat.id}" data-bk="${bk}">
              <span class="bk-name">${bk}</span>
            </div>`;
          });
        }
      }
    }
  }
  document.getElementById('dp-tree').innerHTML = html;
  bindTree();
}

function bindTree() {
  document.querySelectorAll('.ph-row').forEach(el => {
    el.addEventListener('click', () => {
      const id = el.dataset.ph;
      if (G.openPhases.has(id)) { G.openPhases.delete(id); G.openCats.clear(); G.sel.catId = null; G.sel.bucket = null; }
      else { G.openPhases.add(id); }
      G.sel.phaseId = id;
      renderTree(); renderDetail();
    });
  });
  document.querySelectorAll('.cat-row').forEach(el => {
    el.addEventListener('click', e => {
      e.stopPropagation();
      const id = el.dataset.cat;
      if (G.openCats.has(id)) { G.openCats.delete(id); G.sel.bucket = null; }
      else { G.openCats.add(id); }
      G.sel.phaseId = el.dataset.ph;
      G.sel.catId = id;
      G.sel.bucket = null;
      renderTree(); renderDetail();
    });
  });
  document.querySelectorAll('.bk-row').forEach(el => {
    el.addEventListener('click', e => {
      e.stopPropagation();
      G.sel.phaseId = el.dataset.ph;
      G.sel.catId = el.dataset.cat;
      G.sel.bucket = el.dataset.bk;
      renderTree(); renderDetail();
    });
  });
}

/* ── DETAIL ───────────────────────────────────────── */

function renderDetail() {
  const el = document.getElementById('dp-detail');
  if (!G.sel.phaseId) { el.innerHTML = renderSummary(); return; }
  const ph = G.phases.find(p => p.id === G.sel.phaseId);
  if (!G.sel.catId) { el.innerHTML = renderPhaseOverview(ph); return; }
  const cat = G.cats.find(c => c.id === G.sel.catId);
  if (!G.sel.bucket) { el.innerHTML = renderCatOverview(ph, cat); return; }
  el.innerHTML = renderBucketDetail(ph, cat, G.sel.bucket);
}

function renderSummary() {
  const pp = G.data.progress;
  const pct = Math.round(pp?.progress_pct ?? pp?.global_pct ?? 0);
  const host = G.data.host;
  const cpu  = fmt(host?.cpu?.usage_pct);
  const ram  = fmt(host?.memory?.usage_pct ?? host?.ram?.usage_pct);
  const disk = fmt(host?.disk?.usage_pct);
  return `<div class="summary-wrap">
    <div class="sum-title">Devon Operator Panel</div>
    <div class="sum-sub">Select a phase in the tree to navigate. Canon leads. Runtime validates. UI renders.</div>
    <div class="sum-stats">
      <div class="sum-stat"><div class="ss-lbl">Progress</div><div class="ss-val">${pct}%</div></div>
      <div class="sum-stat"><div class="ss-lbl">CPU</div><div class="ss-val">${cpu}</div></div>
      <div class="sum-stat"><div class="ss-lbl">Memory</div><div class="ss-val">${ram}</div></div>
      <div class="sum-stat"><div class="ss-lbl">Disk</div><div class="ss-val">${disk}</div></div>
    </div>
  </div>`;
}

function renderPhaseOverview(ph) {
  const cats = catsForPhase(ph.id);
  const cards = cats.map(c => `<div class="cat-card" data-ph="${ph.id}" data-cat="${c.id}">
    <div class="cat-card-title">${c.title}</div>
    <div class="cat-card-sub">${c.sub || ''}</div>
  </div>`).join('');
  return `<div class="ph-overview">
    <div class="ph-ov-header">
      <div class="ph-ov-eyebrow">${ph.step}</div>
      <div class="ph-ov-title">${ph.name}</div>
      <div class="ph-ov-sub">${ph.summary || ''}</div>
    </div>
    <div class="ph-ov-cats">${cards}</div>
  </div>`;
}

function renderCatOverview(ph, cat) {
  const bkCards = BUCKETS.map((bk, i) => `<div class="bk-card" data-ph="${ph.id}" data-cat="${cat.id}" data-bk="${bk}">
    <span class="bk-card-num">${String(i+1).padStart(2,'0')}</span>
    <span class="bk-card-name">${bk}</span>
    <span class="bk-card-desc">${BUCKET_DESC[bk]}</span>
  </div>`).join('');
  return `<div class="cat-overview">
    <div class="cat-ov-header">
      <div class="cat-bc">${ph.step} / <b>${cat.title}</b></div>
      <div class="cat-ov-title">${cat.title}</div>
      <div class="cat-ov-sub">${cat.sub || ''}</div>
    </div>
    <div class="bk-list">
      <div class="bk-list-label">Process Buckets</div>
      ${bkCards}
    </div>
  </div>`;
}

function renderBucketDetail(ph, cat, bucket) {
  const phIdx = G.phases.findIndex(p => p.id === ph.id);
  const bkIdx = BUCKETS.indexOf(bucket);
  const docs = cat.docs || [];
  const items = docs.map(doc => {
    const st = docStatus(doc);
    const cls = st === 'PASS' ? 'item-ok' : st === 'FAIL' ? 'item-fail' : '';
    const ico = st === 'PASS' ? '✓' : st === 'FAIL' ? '✗' : '○';
    return `<div class="item-card ${cls}">
      <span class="item-ico">${ico}</span>
      <div class="item-body">
        <div class="item-title">${doc.title}</div>
        <div class="item-role">${doc.role || ''}</div>
        <div class="item-ev">${doc.path || ''}</div>
      </div>
      <span class="item-badge st-${st.toLowerCase()}">${st}</span>
    </div>`;
  }).join('');

  const phPct = Math.round((phIdx / G.phases.length) * 100);
  const bkPct = Math.round((bkIdx / BUCKETS.length) * 100);

  return `<div class="det-topbar">
    <span class="det-bc">${ph.step} / ${cat.title} / <b>${bucket}</b></span>
    <div class="det-pills"><span class="pl pl-v">${ph.step}</span><span class="pl pl-bk">${bucket}</span></div>
  </div>
  <div class="det-header">
    <div class="det-hd-top"><span class="det-phase">${ph.step}</span><span class="det-sep">/</span><span class="det-cat">${cat.title}</span></div>
    <div class="det-title">${cat.title}</div>
    <div class="det-sub">${BUCKET_DESC[bucket]}</div>
  </div>
  <div class="det-progress">
    <div class="prog-card">
      <div class="prog-lbl">Phase ${phIdx + 1} of ${G.phases.length}</div>
      <div class="prog-bar-w"><div class="prog-bar" style="width:${phPct}%"></div></div>
      <div class="prog-nums">${ph.name}</div>
    </div>
    <div class="prog-card">
      <div class="prog-lbl">Bucket ${bkIdx + 1} of ${BUCKETS.length}</div>
      <div class="prog-bar-w"><div class="prog-bar prog-bk" style="width:${bkPct}%"></div></div>
      <div class="prog-nums">${bucket}</div>
    </div>
  </div>
  <div class="det-items">
    <div class="items-label">Authority Documents — ${cat.title}</div>
    ${items || '<div class="tree-err">No documents registered for this category.</div>'}
  </div>`;
}

function docStatus(doc) {
  const rows = G.data.runtime?.rows || G.data.runtime?.runtime_rows || [];
  if (!rows.length) return 'PLANNED';
  const name = (doc.path || '').split('/').pop().replace(/\.(json|md)$/, '').toLowerCase();
  const match = rows.find(r =>
    (r.label || r.subcategory || r.component || '').toLowerCase().includes(name)
  );
  if (!match) return 'PLANNED';
  const s = (match.status || '').toUpperCase();
  return s === 'PASS' || s === 'FAIL' || s === 'MISSING' ? s : 'PLANNED';
}

function fmt(v) {
  if (v == null) return '—';
  return Math.round(v) + '%';
}

function renderFooter() {
  const el = document.getElementById('dp-footer');
  if (el) el.textContent = 'loaded: ' + new Date().toLocaleTimeString();
}

/* ── SIDEBAR & DRAWER ─────────────────────────────── */

function bindSidebar() {
  document.querySelectorAll('.ib[data-view]').forEach(btn => {
    btn.addEventListener('click', () => openDrawer(btn.dataset.view));
  });
  document.getElementById('btn-docs')?.addEventListener('click', () => {
    window.open('../docs/', '_blank');
  });
  document.getElementById('btn-monitor')?.addEventListener('click', () => {
    const ph10 = G.phases.find(p => p.id === 'phase-10');
    if (!ph10) return;
    G.openPhases.add('phase-10');
    G.sel.phaseId = 'phase-10';
    G.sel.catId = null; G.sel.bucket = null;
    renderTree(); renderDetail();
  });
  document.getElementById('dp-drawer-close')?.addEventListener('click', closeDrawer);
}

async function openDrawer(url) {
  const drawer = document.getElementById('dp-drawer');
  const pre = document.getElementById('dp-drawer-pre');
  drawer.classList.add('open');
  pre.textContent = 'Loading…';
  try {
    const data = await fetch(url).then(r => r.json());
    pre.textContent = JSON.stringify(data, null, 2);
  } catch (e) {
    pre.textContent = 'Error: ' + e.message;
  }
}

function closeDrawer() {
  document.getElementById('dp-drawer').classList.remove('open');
}

/* ── CAT CARD CLICK (phase overview) ─────────────── */

document.addEventListener('click', e => {
  const cc = e.target.closest('.cat-card');
  if (cc) {
    G.sel.phaseId = cc.dataset.ph;
    G.sel.catId = cc.dataset.cat;
    G.sel.bucket = null;
    G.openPhases.add(cc.dataset.ph);
    G.openCats.add(cc.dataset.cat);
    renderTree(); renderDetail();
  }
  const bc = e.target.closest('.bk-card');
  if (bc) {
    G.sel.phaseId = bc.dataset.ph;
    G.sel.catId = bc.dataset.cat;
    G.sel.bucket = bc.dataset.bk;
    G.openPhases.add(bc.dataset.ph);
    G.openCats.add(bc.dataset.cat);
    renderTree(); renderDetail();
  }
});

document.addEventListener('DOMContentLoaded', () => {
  init();
  bindSidebar();
});

===== /home/yeff/public_html/devon/panel/assets/js/panel.runtime.fix.20260408_1.js =====
(function () {
  const DATA_BASE = "data/";

  const els = {
    projectDonut: document.getElementById("project-donut"),
    globalPills: document.getElementById("global-pills"),
    stageNav: document.getElementById("stage-nav"),
    heroTitle: document.getElementById("hero-title"),
    heroSubtitle: document.getElementById("hero-subtitle"),
    stageOrderBadge: document.getElementById("stage-order-badge"),
    selectedStageName: document.getElementById("selected-stage-name"),
    selectedStageDesc: document.getElementById("selected-stage-desc"),
    selectedStageTotal: document.getElementById("selected-stage-total"),
    selectedStageDonut: document.getElementById("selected-stage-donut"),
    selectedRuntimeBadge: document.getElementById("selected-runtime-badge"),
    selectedPipelineSteps: document.getElementById("selected-pipeline-steps"),
    hostStatusBadge: document.getElementById("host-status-badge"),
    dockerStatusBadge: document.getElementById("docker-status-badge"),
    hostRuntimeGrid: document.getElementById("host-runtime-grid"),
    dockerRuntimeGrid: document.getElementById("docker-runtime-grid"),
    pipelineRuntimeCount: document.getElementById("pipeline-runtime-count"),
    pipelineRuntimeBoard: document.getElementById("pipeline-runtime-board"),
    drawerLabel: document.getElementById("drawer-label"),
    jsonViewer: document.getElementById("json-viewer"),
    btnOpenManifest: document.getElementById("btn-open-manifest"),
    btnOpenContract: document.getElementById("btn-open-contract"),
    btnOpenHost: document.getElementById("btn-open-host"),
    btnOpenDocker: document.getElementById("btn-open-docker"),
    btnOpenRuntime: document.getElementById("btn-open-runtime"),
    dataViewerModal: document.getElementById("data-viewer-modal")
  };

  const state = {
    manifest: null,
    contentIndex: null,
    navigationSpec: null,
    pipelines: null,
    hostRuntime: null,
    dockerRuntime: null,
    runtimeStatus: null,
    projectProgress: null,
    selectedStage: null
  };

  function safeUpper(v) {
    return String(v || "MISSING").trim().toUpperCase();
  }

  function getBadgeClass(status) {
    const s = safeUpper(status);
    if (s === "PASS" || s === "OK" || s === "SUCCESS") return "pass";
    if (s === "FAIL" || s === "ERROR") return "fail";
    if (s === "RUNNING") return "running";
    if (s === "PENDING") return "pending";
    return "missing";
  }

  function setBadge(el, status, text) {
    if (!el) return;
    el.className = "badge " + getBadgeClass(status);
    el.textContent = text || safeUpper(status);
  }

  function normStageKey(value) {
    return String(value || "")
      .trim()
      .toLowerCase()
      .replace(/[^a-z0-9]+/g, "_")
      .replace(/^_+|_+$/g, "");
  }

  function titleize(value) {
    return String(value || "")
      .replace(/[_-]+/g, " ")
      .replace(/\b\w/g, function (m) { return m.toUpperCase(); });
  }

  async function getJson(name) {
    const res = await fetch(DATA_BASE + name, { cache: "no-store" });
    if (!res.ok) throw new Error(name + " -> HTTP " + res.status);
    return res.json();
  }

  async function getJsonOptional(name) {
    try {
      return await getJson(name);
    } catch (err) {
      return null;
    }
  }

  function openJson(label, payload) {
    if (els.drawerLabel) els.drawerLabel.textContent = label || "data";
    if (els.jsonViewer) {
      try {
        els.jsonViewer.textContent = JSON.stringify(payload, null, 2);
      } catch (e) {
        els.jsonViewer.textContent = String(payload);
      }
    }
    if (els.dataViewerModal) {
      els.dataViewerModal.hidden = false;
      els.dataViewerModal.style.display = "block";
    }
  }

  function donutMarkup(pct, label) {
    const value = Math.max(0, Math.min(100, Number(pct || 0)));
    const r = 54;
    const c = 2 * Math.PI * r;
    const dash = (value / 100) * c;
    return [
      '<div class="donut">',
      '<svg viewBox="0 0 140 140" aria-hidden="true">',
      '<circle cx="70" cy="70" r="' + r + '" fill="none" stroke="rgba(255,255,255,.08)" stroke-width="10"></circle>',
      '<circle cx="70" cy="70" r="' + r + '" fill="none" stroke="currentColor" stroke-width="10" stroke-linecap="round" stroke-dasharray="' + dash + ' ' + c + '"></circle>',
      '</svg>',
      '<div class="donut-center">',
      '<strong>' + value + '%</strong>',
      '<span>' + (label || "progress") + '</span>',
      '</div>',
      '</div>'
    ].join("");
  }

  function missingDonutMarkup(label) {
    return donutMarkup(0, label || "missing");
  }

  function metricBox(label, value, sub) {
    return [
      '<div class="metric-box">',
      '<div class="metric-label">' + label + '</div>',
      '<div class="metric-value">' + value + '</div>',
      '<div class="metric-sub">' + sub + '</div>',
      '</div>'
    ].join("");
  }

  function valueOrDash(value, suffix) {
    if (value === null || value === undefined || value === "") return "—";
    return String(value) + String(suffix || "");
  }

  function runtimeStatusOf(row) {
    if (!row || typeof row !== "object") return "MISSING";
    return safeUpper(
      row.runtime_status ??
      row.status ??
      row.state ??
      row.overall_status ??
      "MISSING"
    );
  }

  function stageRows(stageKey) {
    const snapshot = ((state.runtimeStatus && state.runtimeStatus.runtime_snapshot) || []);
    return snapshot.filter(function (row) {
      return normStageKey(row.deployment_stage) === normStageKey(stageKey);
    });
  }

  function stageRollup(stageKey) {
    return stageRows(stageKey).find(function (row) {
      return String(row.row_kind || "") === "stage_rollup";
    }) || null;
  }

  function renderGlobal() {
    const snapshot = ((state.runtimeStatus && state.runtimeStatus.runtime_snapshot) || []);
    const counts = snapshot.reduce(function (acc, row) {
      const s = runtimeStatusOf(row);
      acc[s] = (acc[s] || 0) + 1;
      return acc;
    }, { PASS: 0, FAIL: 0, MISSING: 0, RUNNING: 0, PENDING: 0 });

    if (els.globalPills) {
      els.globalPills.innerHTML = [
        '<span class="pill pass">PASS ' + (counts.PASS || 0) + '</span>',
        '<span class="pill fail">FAIL ' + (counts.FAIL || 0) + '</span>',
        '<span class="pill missing">MISSING ' + (counts.MISSING || 0) + '</span>',
        '<span class="pill running">RUNNING ' + (counts.RUNNING || 0) + '</span>',
        '<span class="pill pending">PENDING ' + (counts.PENDING || 0) + '</span>'
      ].join("");
    }

    const gp = state.projectProgress && state.projectProgress.global_project_progress;
    if (els.projectDonut) {
      if (gp && typeof gp.progress_pct === "number") {
        els.projectDonut.innerHTML = donutMarkup(Math.round(gp.progress_pct), "project completion");
      } else {
        els.projectDonut.innerHTML = missingDonutMarkup("project completion");
      }
    }
  }

  function renderNav() {
    if (!els.stageNav) return;
    const items = ((state.contentIndex && state.contentIndex.content_index) || [])
      .filter(function (item) { return !!item.stage_key; })
      .sort(function (a, b) {
        return Number(a.deployment_order || 0) - Number(b.deployment_order || 0);
      });

    els.stageNav.innerHTML = "";

    items.forEach(function (item) {
      const stageKey = normStageKey(item.stage_key);
      const btn = document.createElement("button");
      btn.type = "button";
      btn.className = "stage-btn";
      btn.dataset.stageKey = stageKey;
      btn.innerHTML = [
        '<span>',
        '<strong>' + item.label + '</strong>',
        '<small>Order ' + item.deployment_order + '</small>',
        '</span>'
      ].join("");
      btn.addEventListener("click", function () {
        selectStage(stageKey);
      });
      els.stageNav.appendChild(btn);
    });
  }

  function renderHostRuntime() {
    const host = (state.hostRuntime && state.hostRuntime.host_snapshot) || {};
    setBadge(els.hostStatusBadge, host.overall_status || "MISSING", safeUpper(host.overall_status || "MISSING"));

    const cpu = host.cpu || {};
    const memory = host.memory || {};
    const disk = host.disk || {};
    const load = host.load || {};

    if (els.hostRuntimeGrid) {
      els.hostRuntimeGrid.innerHTML = [
        metricBox("CPU", valueOrDash(cpu.usage_pct, "%"), "cores: " + valueOrDash(cpu.core_count, "")),
        metricBox("Memory", valueOrDash(memory.usage_pct, "%"), valueOrDash(memory.used_mb, " MB") + " / " + valueOrDash(memory.total_mb, " MB")),
        metricBox("Disk", valueOrDash(disk.usage_pct, "%"), valueOrDash(disk.used_gb, " GB") + " / " + valueOrDash(disk.total_gb, " GB")),
        metricBox("Load 1m", valueOrDash(load.load_1m, ""), "5m: " + valueOrDash(load.load_5m, "") + " | 15m: " + valueOrDash(load.load_15m, ""))
      ].join("");
    }
  }

  function renderDockerRuntime() {
    const runtime = (state.dockerRuntime && state.dockerRuntime.runtime_snapshot) || {};
    setBadge(els.dockerStatusBadge, runtime.overall_status || "MISSING", safeUpper(runtime.overall_status || "MISSING"));

    const engine = runtime.docker_engine || {};
    const compose = runtime.compose || {};
    const images = runtime.images || {};
    const volumes = runtime.volumes || {};
    const networks = runtime.networks || {};
    const containers = Array.isArray(runtime.containers) ? runtime.containers : [];

    if (els.dockerRuntimeGrid) {
      els.dockerRuntimeGrid.innerHTML = [
        metricBox("Engine", engine.installed === true ? "installed" : (engine.installed === false ? "not installed" : "MISSING"), "version: " + valueOrDash(engine.version, "")),
        metricBox("Compose", compose.installed === true ? "installed" : (compose.installed === false ? "not installed" : "MISSING"), "version: " + valueOrDash(compose.version, "")),
        metricBox("Containers", String(containers.length), "observable list"),
        metricBox("Images", valueOrDash(images.total, ""), "Volumes: " + valueOrDash(volumes.total, "") + " | Networks: " + valueOrDash(networks.total, ""))
      ].join("");
    }
  }

  function renderPipelineBoard() {
    const items = ((state.runtimeStatus && state.runtimeStatus.runtime_snapshot) || []);
    if (els.pipelineRuntimeCount) {
      els.pipelineRuntimeCount.textContent = items.length + " runtime rows";
    }
    if (!els.pipelineRuntimeBoard) return;
    if (!items.length) {
      els.pipelineRuntimeBoard.innerHTML = '<div class="pipeline-item"><h4>No runtime rows</h4><p>Status is MISSING.</p></div>';
      return;
    }
    els.pipelineRuntimeBoard.innerHTML = items.slice(0, 24).map(function (item) {
      const status = runtimeStatusOf(item);
      return [
        '<div class="pipeline-item">',
        '<h4>' + titleize(item.deployment_stage || "runtime") + ' / ' + titleize(item.technology || item.subcategory || "item") + '</h4>',
        '<p>Kind: ' + titleize(item.row_kind || "runtime_row") + '</p>',
        '<div class="pipeline-kv">',
        '<span class="pill ' + getBadgeClass(status) + '">' + status + '</span>',
        '<span class="pill">' + valueOrDash(item.progress_pct, "%") + '</span>',
        '</div>',
        '</div>'
      ].join("");
    }).join("");
  }

  function renderSelectedPipeline(stageKey, rollup) {
    if (!els.selectedPipelineSteps) return;
    const pipelines = (state.pipelines && state.pipelines.pipelines) || {};
    const contract = pipelines[stageKey] || {};
    const seq = Array.isArray(contract.sequence) ? contract.sequence : [];
    const stepStatuses = rollup && Array.isArray(rollup.step_statuses) ? rollup.step_statuses : [];

    if (!seq.length) {
      els.selectedPipelineSteps.innerHTML = '<li><span>No pipeline contract found</span><span class="step-state">missing</span></li>';
      return;
    }

    els.selectedPipelineSteps.innerHTML = seq.map(function (step) {
      const observed = stepStatuses.find(function (x) {
        return normStageKey(x.step || x.name) === normStageKey(step);
      });
      const status = observed ? safeUpper(observed.status) : "MISSING";
      return [
        '<li>',
        '<span>' + titleize(step) + '</span>',
        '<span class="pill ' + getBadgeClass(status) + '">' + status + '</span>',
        '</li>'
      ].join("");
    }).join("");
  }

  function selectStage(stageKey) {
    state.selectedStage = normStageKey(stageKey);
    const items = ((state.contentIndex && state.contentIndex.content_index) || []);
    const navItem = items.find(function (item) {
      return normStageKey(item.stage_key) === state.selectedStage;
    }) || null;
    const rollup = stageRollup(state.selectedStage);
    const pct = rollup && typeof rollup.progress_pct === "number" ? Math.round(rollup.progress_pct) : 0;
    const totalSteps = rollup && Array.isArray(rollup.step_statuses) ? rollup.step_statuses.length : 0;

    if (els.heroTitle) els.heroTitle.textContent = navItem ? navItem.label : titleize(state.selectedStage);
    if (els.heroSubtitle) {
      els.heroSubtitle.textContent = "Runtime-backed stage view. Contract and runtime data are rendered from panel exports only.";
    }
    if (els.stageOrderBadge) {
      els.stageOrderBadge.textContent = navItem ? ("ORDER " + navItem.deployment_order) : "—";
    }
    if (els.selectedStageName) {
      els.selectedStageName.textContent = navItem ? navItem.label : titleize(state.selectedStage);
    }
    if (els.selectedStageDesc) {
      els.selectedStageDesc.textContent = "Stage completion is read from runtime_status stage_rollup.progress_pct.";
    }
    if (els.selectedStageTotal) {
      els.selectedStageTotal.innerHTML = totalSteps
        ? (totalSteps + ' required steps<br>in contract')
        : 'MISSING stage rollup<br>in runtime';
    }
    if (els.selectedStageDonut) {
      els.selectedStageDonut.innerHTML = donutMarkup(pct, "stage completion");
    }
    if (els.selectedRuntimeBadge) {
      setBadge(els.selectedRuntimeBadge, rollup ? runtimeStatusOf(rollup) : "MISSING", rollup ? runtimeStatusOf(rollup) : "MISSING");
    }

    renderSelectedPipeline(state.selectedStage, rollup);

    document.querySelectorAll(".stage-btn").forEach(function (btn) {
      btn.classList.toggle("active", btn.dataset.stageKey === state.selectedStage);
    });
  }

  function wireButtons() {
    [

