wsb-daemon 0.275.7622

wsb's daemon: the workbench, the GitHub mirror's schedule and peer continuity, for every workspace on the machine
<script lang="ts">
  /**
   * The host, at a glance.
   *
   * Five measures in the title bar, each one sparkline and one number. It is
   * not a dashboard and must never become one: what it answers is "is this
   * machine all right", which is a question somebody answers by looking, not
   * by reading. So each measure gets a line eight pixels tall and a number
   * short enough to take in without focusing — free space for the disk,
   * a percentage for everything else.
   *
   * A measure this host cannot answer is LEFT OUT rather than shown empty. A
   * flat line at the bottom reads as idle, which is a claim; a machine with no
   * discrete GPU is not a machine whose GPU is idle.
   *
   * The graphs are drawn as one SVG path each — no chart library, no canvas,
   * no per-frame work. They redraw when a reading arrives, which is every two
   * seconds.
   */
  import { onMount } from "svelte";
  import { host, human, percent, ratio, HISTORY } from "../../stores/host.svelte.js";

  onMount(() => {
    host.connect();
    return () => host.disconnect();
  });

  /** One sparkline's path, over a fixed 0..1 range. */
  function path(series: (number | null)[], width: number, height: number): string {
    const points = series.length;
    if (points === 0) return "";
    // Fixed range, so the height of the line means the same thing in every
    // graph and at every moment. An auto-scaled sparkline makes an idle
    // machine look busy, which is the opposite of what this is for.
    const step = points > 1 ? width / (HISTORY - 1) : width;
    // A line only where there are readings; a gap where there are not.
    let out = "";
    let pen = false;
    series.forEach((value, index) => {
      if (value === null) {
        pen = false;
        return;
      }
      const x = index * step;
      const y = height - Math.max(0, Math.min(1, value)) * height;
      out += `${pen ? "L" : "M"}${x.toFixed(1)},${y.toFixed(1)}`;
      pen = true;
    });
    return out;
  }

  const W = 34;
  const H = 12;

  /// The measures, in the order they are read. Absent ones fall out here, so
  /// the markup never has to ask whether a thing exists.
  const measures = $derived.by(() => {
    const sample = host.latest;
    if (!sample) return [];
    const rows: {
      id: string;
      label: string;
      series: (number | null)[];
      value: string;
      title: string;
      warn: boolean;
    }[] = [];

    if (sample.cpu !== null) {
      rows.push({
        id: "cpu",
        label: "CPU",
        series: host.cpu,
        value: percent(sample.cpu),
        title: `Processor ${percent(sample.cpu)} busy${
          sample.cpu_count ? ` · ${sample.cpu_count} logical cores` : ""
        }`,
        warn: sample.cpu > 0.9,
      });
    }
    if (sample.memory) {
      const used = ratio(sample.memory) ?? 0;
      rows.push({
        id: "ram",
        label: "RAM",
        series: host.memory,
        // The number is what is LEFT, because that is the number somebody is
        // deciding on: whether to start something else.
        value: human(sample.memory.total - sample.memory.used),
        title: `Memory: ${human(sample.memory.total - sample.memory.used)} free of ${human(
          sample.memory.total,
        )} (${percent(used)} in use)`,
        warn: used > 0.9,
      });
    }
    if (sample.vram) {
      const used = ratio(sample.vram) ?? 0;
      rows.push({
        id: "vram",
        label: "VRAM",
        series: host.vram,
        value: human(sample.vram.total - sample.vram.used),
        title: `${sample.gpu_name ?? "Graphics"} memory: ${human(
          sample.vram.total - sample.vram.used,
        )} free of ${human(sample.vram.total)} (${percent(used)} in use)`,
        warn: used > 0.9,
      });
    }
    if (sample.gpu !== null) {
      rows.push({
        id: "gpu",
        label: "GPU",
        series: host.gpu,
        value: percent(sample.gpu),
        title: `${sample.gpu_name ?? "Graphics"} ${percent(sample.gpu)} busy`,
        warn: sample.gpu > 0.9,
      });
    }
    if (sample.disk) {
      const used = ratio(sample.disk) ?? 0;
      rows.push({
        id: "disk",
        label: "Disk",
        series: host.disk,
        value: human(sample.disk.total - sample.disk.used),
        title: `Worktree volume: ${human(sample.disk.total - sample.disk.used)} free of ${human(
          sample.disk.total,
        )} (${percent(used)} in use)`,
        warn: used > 0.9,
      });
    }
    return rows;
  });
</script>

{#if measures.length > 0}
  <div class="host-strip" role="group" aria-label="Host system">
    {#each measures as measure (measure.id)}
      <div class="measure" class:warn={measure.warn} title={measure.title}>
        <svg class="spark" width={W} height={H} viewBox="0 0 {W} {H}" aria-hidden="true">
          <!-- The full range, so a line near the top always means the same
               thing. Drawn faintly rather than labelled: an axis in a graph
               this size is more ink than signal. -->
          <path class="spark-line" d={path(measure.series, W, H)} />
        </svg>
        <span class="value">{measure.value}</span>
        <span class="sr-only">{measure.label}: {measure.value}</span>
      </div>
    {/each}
  </div>
{/if}

<style>
  .host-strip {
    display: flex;
    align-items: center;
    gap: var(--wb-spacing-10);
    padding: 0 var(--wb-spacing-4);
    /* Never the reason the bar grows or the layout jumps: the strip is the
       least important thing in the title bar and yields first. */
    min-width: 0;
    overflow: hidden;
  }

  .measure {
    display: flex;
    align-items: center;
    gap: var(--wb-spacing-4);
    color: var(--vscode-descriptionForeground, #999);
    font-size: var(--wb-font-size-10);
    font-variant-numeric: tabular-nums;
    line-height: 1;
    flex-shrink: 0;
  }

  /* One state worth colouring, and it is the one somebody should act on. */
  .measure.warn {
    color: var(--vscode-editorWarning-foreground, #cca700);
  }

  .spark {
    display: block;
    overflow: visible;
  }

  .spark-line {
    fill: none;
    stroke: currentColor;
    stroke-width: 1;
    stroke-linejoin: round;
    stroke-linecap: round;
    opacity: 0.75;
  }

  .value {
    min-width: var(--wb-control-size-34);
    text-align: right;
  }

  /* Named for a screen reader, which cannot read a sparkline. */
  .sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: var(--wb-spacing-neg-1);
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
  }

  @media (max-width: 900px) {
    /* The two that matter least when there is no room. */
    .measure:nth-child(3),
    .measure:nth-child(4) {
      display: none;
    }
  }
</style>