  function repeatedSeries(count, value) {
    var pts = []; for (var i = 0; i < count; i++) pts.push(value); return pts;
  }

  var PROVIDER_COLORS = { anthropic: '#f59e0b', openai: '#22c55e', google: '#06b6d4', moonshot: '#a78bfa', openrouter: '#ec4899', ollama: '#8b5cf6', 'ollama-gpu': '#7c3aed', mistral: '#f97316', deepseek: '#14b8a6', groq: '#ef4444', xai: '#64748b', together: '#84cc16', 'docker-model-runner': '#6366f1', 'llama-cpp': '#d946ef' };
  var PROVIDERS = ['anthropic', 'openai', 'google'];

  var sparklineId = 0;
  function renderSparkCanvas(series, opts) {
    opts = opts || {};
    var id = 'spark-' + (++sparklineId);
    var color = opts.color || '#c180ff';
    var fillFrom = opts.fillFrom != null ? opts.fillFrom : 0.25;
    var lineWidth = opts.lineWidth || 2;
    var height = opts.height || 64;
    var axisTopLabel = opts.axisTopLabel || null;
    var axisBottomLabel = opts.axisBottomLabel || null;
    setTimeout(function() {
      var canvas = document.getElementById(id); if (!canvas) return;
      var dpr = window.devicePixelRatio || 1;
      var rect = canvas.parentElement ? canvas.parentElement.getBoundingClientRect() : { width: 340 };
      var W = rect.width, H = height;
      canvas.width = W * dpr; canvas.height = H * dpr;
      canvas.style.width = W + 'px'; canvas.style.height = H + 'px';
      var ctx = canvas.getContext('2d'); ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
      var min = opts.min != null ? Number(opts.min) : Math.min.apply(null, series);
      var max = opts.max != null ? Number(opts.max) : Math.max.apply(null, series);
      if (!isFinite(min)) min = 0;
      if (!isFinite(max)) max = 1;
      if (max < min) { var t = max; max = min; min = t; }
      var range = max - min || 1; var padTop = 6, padBot = 4, usableH = H - padTop - padBot;
      function toX(i) { return (i / (series.length - 1)) * W; }
      function toY(v) {
        var n = (v - min) / range;
        if (n < 0) n = 0;
        if (n > 1) n = 1;
        return padTop + usableH - n * usableH;
      }
      var grad = ctx.createLinearGradient(0, padTop, 0, H);
      grad.addColorStop(0, color + Math.round(fillFrom * 255).toString(16).padStart(2, '0'));
      grad.addColorStop(1, color + '00');
      ctx.beginPath(); ctx.moveTo(toX(0), H);
      for (var i = 0; i < series.length; i++) {
        if (i === 0) ctx.lineTo(toX(0), toY(series[0]));
        else { var cx1 = (toX(i-1)+toX(i))/2; ctx.bezierCurveTo(cx1, toY(series[i-1]), cx1, toY(series[i]), toX(i), toY(series[i])); }
      }
      ctx.lineTo(W, H); ctx.closePath(); ctx.fillStyle = grad; ctx.fill();
      ctx.beginPath();
      for (var i = 0; i < series.length; i++) {
        if (i === 0) ctx.moveTo(toX(0), toY(series[0]));
        else { var cx1 = (toX(i-1)+toX(i))/2; ctx.bezierCurveTo(cx1, toY(series[i-1]), cx1, toY(series[i]), toX(i), toY(series[i])); }
      }
      ctx.strokeStyle = color; ctx.lineWidth = lineWidth; ctx.stroke();
      var lastX = toX(series.length - 1), lastY = toY(series[series.length - 1]);
      ctx.fillStyle = color; ctx.beginPath(); ctx.arc(lastX, lastY, 3, 0, Math.PI * 2); ctx.fill();
      ctx.fillStyle = '#060e20'; ctx.beginPath(); ctx.arc(lastX, lastY, 1.5, 0, Math.PI * 2); ctx.fill();
    }, 0);
    if (axisTopLabel || axisBottomLabel) {
      var top = axisTopLabel ? '<span class="cc-chart-axis top">' + esc(axisTopLabel) + '</span>' : '';
      var bottom = axisBottomLabel ? '<span class="cc-chart-axis bottom">' + esc(axisBottomLabel) + '</span>' : '';
      return '<div class="cc-chart-wrap"><canvas id="' + id + '" class="cc-chart"></canvas>' + top + bottom + '</div>';
    }
    return '<canvas id="' + id + '" class="cc-chart"></canvas>';
  }

  var stackedId = 0;
  function renderStackedArea(seriesMap, keys, colors, opts) {
    opts = opts || {};
    var id = 'stacked-' + (++stackedId);
    var height = opts.height || 180;
    var showLabels = opts.labels !== false;
    setTimeout(function() {
      var canvas = document.getElementById(id); if (!canvas) return;
      var dpr = window.devicePixelRatio || 1;
      var rect = canvas.parentElement ? canvas.parentElement.getBoundingClientRect() : { width: 600 };
      var W = rect.width, H = height;
      canvas.width = W * dpr; canvas.height = H * dpr;
      canvas.style.width = W + 'px'; canvas.style.height = H + 'px';
      var ctx = canvas.getContext('2d'); ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
      var len = seriesMap[keys[0]].length;
      var padTop = 8, padBot = showLabels ? 22 : 6, padLeft = opts.yAxis ? 48 : 6, padRight = 20;
      var chartW = W - padLeft - padRight, chartH = H - padTop - padBot;
      var stacked = [];
      for (var i = 0; i < len; i++) {
        var cum = 0, layers = [];
        for (var k = 0; k < keys.length; k++) { var val = seriesMap[keys[k]][i]; layers.push({ bottom: cum, top: cum + val, key: keys[k] }); cum += val; }
        stacked.push({ layers: layers, total: cum });
      }
      var maxTotal = opts.fixedMax != null ? opts.fixedMax : Math.max.apply(null, stacked.map(function(s) { return s.total; }));
      if (maxTotal === 0) maxTotal = 1;
      function toX(i) { return padLeft + (i / (len - 1)) * chartW; }
      function toY(v) { return padTop + chartH - (v / maxTotal) * chartH; }
      for (var k = keys.length - 1; k >= 0; k--) {
        var color = colors[keys[k]] || '#9baad6';
        var grad = ctx.createLinearGradient(0, padTop, 0, H - padBot);
        grad.addColorStop(0, color + '55'); grad.addColorStop(1, color + '08');
        ctx.beginPath(); ctx.moveTo(toX(0), toY(0));
        for (var i = 0; i < len; i++) {
          var topVal = stacked[i].layers[k].top;
          if (i === 0) ctx.lineTo(toX(0), toY(topVal));
          else { var cx1 = (toX(i-1)+toX(i))/2; ctx.bezierCurveTo(cx1, toY(stacked[i-1].layers[k].top), cx1, toY(topVal), toX(i), toY(topVal)); }
        }
        ctx.lineTo(toX(len - 1), toY(0)); ctx.closePath(); ctx.fillStyle = grad; ctx.fill();
        ctx.beginPath();
        for (var i = 0; i < len; i++) {
          var topVal = stacked[i].layers[k].top;
          if (i === 0) ctx.moveTo(toX(0), toY(topVal));
          else { var cx1 = (toX(i-1)+toX(i))/2; ctx.bezierCurveTo(cx1, toY(stacked[i-1].layers[k].top), cx1, toY(topVal), toX(i), toY(topVal)); }
        }
        ctx.strokeStyle = color; ctx.lineWidth = 1.5; ctx.stroke();
      }
      if (opts.yAxis) {
        ctx.fillStyle = '#9baad6'; ctx.font = '9px ui-monospace, monospace'; ctx.textAlign = 'right';
        var steps = 4;
        for (var s = 0; s <= steps; s++) {
          var val = (maxTotal / steps) * s, y = toY(val);
          ctx.fillText(opts.yFormat ? opts.yFormat(val) : val.toFixed(2), padLeft - 6, y + 3);
          if (s > 0 && s < steps) { ctx.strokeStyle = 'rgba(113,113,122,0.1)'; ctx.lineWidth = 0.5; ctx.beginPath(); ctx.moveTo(padLeft, y); ctx.lineTo(W - padRight, y); ctx.stroke(); }
        }
      }
      if (showLabels) {
        ctx.fillStyle = '#9baad6'; ctx.font = '9px ui-monospace, monospace'; ctx.textAlign = 'center';
        var labelStep = Math.max(1, Math.floor(len / 6));
        for (var i = 0; i < len; i += labelStep) { var label = opts.xLabels ? opts.xLabels[i] : (i + 'h'); ctx.fillText(label, toX(i), H - 4); }
      }
    }, 0);
    return '<canvas id="' + id + '" style="width:100%;height:' + height + 'px;display:block"></canvas>';
  }

  function deltaHtml(current, previous) {
    if (previous === 0) return '<span class="cc-delta flat">&mdash;</span>';
    var pct = ((current - previous) / Math.abs(previous) * 100);
    var dir = pct > 1 ? 'up' : pct < -1 ? 'down' : 'flat';
    var arrow = dir === 'up' ? '\u2191' : dir === 'down' ? '\u2193' : '\u2192';
    return '<span class="cc-delta ' + dir + '">' + arrow + ' ' + Math.abs(pct).toFixed(1) + '%</span>';
  }
  function formatUptime(sec) {
    if (sec == null) return '\u2014';
    sec = Math.floor(sec);
    var d = Math.floor(sec / 86400), h = Math.floor((sec % 86400) / 3600), m = Math.floor((sec % 3600) / 60);
    if (d > 0) return d + 'd ' + h + 'h ' + m + 'm';
    if (h > 0) return h + 'h ' + m + 'm';
    return m + 'm';
  }
  function formatTimestampLabel(value) {
    if (!value) return '\u2014';
    var normalized = String(value).replace(' ', 'T');
    if (!/[zZ]|[+-]\d\d:\d\d$/.test(normalized)) normalized += 'Z';
    var parsed = new Date(normalized);
    if (Number.isNaN(parsed.getTime())) return String(value);
    return parsed.toLocaleString();
  }
  function seriesLast(s) { return s[s.length - 1]; }
  function seriesPrev(s) { return s[s.length - 2] || s[0]; }
  function seriesAvg(s) { return s.reduce(function(a, b) { return a + b; }, 0) / s.length; }
  function seriesMax(s) { return Math.max.apply(null, s); }

  var overviewRefreshTimer = null;
  var modelsRefreshTimer = null;
  var FLEET_HISTORY_MAX_POINTS = 40;
  var _fleetHistory = { labels: [], byAgent: {} };
  function pushFleetSnapshot(agents, activityByAgent) {
    var now = new Date();
    var hh = String(now.getHours()).padStart(2, '0');
    var mm = String(now.getMinutes()).padStart(2, '0');
    var label = hh + ':' + mm;
    _fleetHistory.labels.push(label);
    if (_fleetHistory.labels.length > FLEET_HISTORY_MAX_POINTS) _fleetHistory.labels.shift();

    var activeKeys = {};
    agents.forEach(function(a) {
      var k = a.name || a.id;
      activeKeys[k] = true;
      if (!_fleetHistory.byAgent[k]) _fleetHistory.byAgent[k] = [];
      _fleetHistory.byAgent[k].push(activityByAgent[k] || 0);
      if (_fleetHistory.byAgent[k].length > FLEET_HISTORY_MAX_POINTS) _fleetHistory.byAgent[k].shift();
    });

    Object.keys(_fleetHistory.byAgent).forEach(function(k) {
      if (!activeKeys[k]) delete _fleetHistory.byAgent[k];
    });

    var len = _fleetHistory.labels.length;
    Object.keys(_fleetHistory.byAgent).forEach(function(k) {
      while (_fleetHistory.byAgent[k].length < len) _fleetHistory.byAgent[k].unshift(0);
    });
  }
  function startOverviewRefresh(app) {
    if (overviewRefreshTimer) clearInterval(overviewRefreshTimer);
    overviewRefreshTimer = setInterval(function() {
      if (!app || app.page !== 'overview') return;
      app.refreshOverview();
    }, 7000);
  }
  function stopModelsBackgroundRefresh() {
    if (modelsRefreshTimer) {
      clearInterval(modelsRefreshTimer);
      modelsRefreshTimer = null;
    }
  }
  function startModelsBackgroundRefresh(app) {
    stopModelsBackgroundRefresh();
    // Keep model cache warm using metadata-only discovery (no inference calls).
    modelsRefreshTimer = setInterval(function() {
      if (!app || !app._loadAvailableModels) return;
      app._loadAvailableModels({
        forceRefresh: true,
        cacheTtlMs: 0,
        timeoutMs: 700,
        nonBlocking: true,
        validationLevel: 'zero'
      }).catch(function() {});
    }, 180000);
  }
  var workspaceRefreshTimer = null;
  function stopWorkspaceRefresh() {
    if (workspaceRefreshTimer) {
      clearInterval(workspaceRefreshTimer);
      workspaceRefreshTimer = null;
    }
  }
  function startWorkspaceRefresh(app) {
    stopWorkspaceRefresh();
    workspaceRefreshTimer = setInterval(function() {
      if (app.page !== 'workspace' || !workspace || !workspace.applySnapshot) return;
      api('/api/workspace/state')
        .then(function(data) {
          _cachedWorkspace = data;
          workspace.applySnapshot(data);
          updateWorkspaceStatusPanel(data);
        })
        .catch(function() {});
    }, 3000);
  }
