SystemMonitor

A live top/htop pane whose meters and columns wander on seamless harmonics — a long, non-repeating cycle that reads as a running machine, not a looping GIF.

Install

$ npx shadcn@latest add wisp.pouriah.com/r/system-monitor.json
Live · DOMlive state

SystemMonitoresc or double-click to exit full screendouble-tap to exit

demo/system-monitor.tsx
"use client";

import * as React from "react";
import { cx, useScriptedSequence } from "@pouriahlabs/wisp-ui";

/**
 * SystemMonitor — a live `top`/`htop` pane that never repeats on you.
 *
 * The other demo surfaces reveal a fixed script and loop it; on a landing page
 * a short loop announces itself, and a monitor that plays the same eight seconds
 * on repeat reads as a GIF, not a running machine. So this one doesn't reveal —
 * it *runs*. Every meter and every CPU/MEM column is a sum of sine harmonics
 * whose periods each divide the cycle length, which buys two things at once: the
 * frame at the end of the cycle is bit-identical to the frame at the start, so
 * the loop seam is invisible, and with a long default cycle (~86s) a reader
 * watching the mockup never reaches it anyway. The numbers wander, the bars
 * breathe, and nothing ever snaps back.
 *
 * Structural rule shared with the rest of the tier: every row is laid out up
 * front at a fixed height and only its *values* mutate — the process list is
 * sorted by PID and never reorders, so the pane can't reflow or push the page
 * around while it plays. Bars tween via `transform: scaleX` on the compositor
 * between ticks, never `width`, so growth never triggers layout.
 *
 * The lifecycle comes free from `useScriptedSequence`, used here as a frame
 * clock rather than a line revealer: it pauses off-screen and in hidden tabs,
 * and under reduced motion it rests on one composed, fully-populated frame —
 * never an empty box.
 *
 * Colour comes from custom properties so the component carries no palette of
 * its own; see `.wisp-mon` in `wisp-ui/styles.css` for the defaults.
 *
 * Every number here is synthetic and never settles — there is no "final
 * value" the way `NumberTicker` has one, so there's nothing meaningful to
 * read out digit by digit. `role="img"` with one static `aria-label`
 * describes what the mockup shows instead of exposing dozens of numbers that
 * change every tick and say nothing.
 */

export interface MonitorProcess {
  pid: number;
  user: string;
  command: string;
  /** Baseline CPU%, 0–100. */
  cpu?: number;
  /** Baseline MEM%, 0–100. */
  mem?: number;
  /** How far CPU% swings around its baseline. */
  swing?: number;
}

export interface SystemMonitorProps extends React.HTMLAttributes<HTMLDivElement> {
  /** The process table. Defaults to a realistic dev-box deck. */
  processes?: readonly MonitorProcess[];
  /** Cores the aggregate CPU meter divides by. */
  cores?: number;
  /** Frames in one seamless cycle. Larger = longer before it repeats. */
  ticks?: number;
  /** Milliseconds per frame. */
  tickMs?: number;
  /** Pause before the cycle restarts. The cycle is seamless, so keep it small. */
  loop?: number;
  /** Fixed uptime string shown in the status line. */
  uptime?: string;
  /** `user@host` for the prompt line above the meters. `""` drops the prompt. */
  host?: string;
  /** What that prompt says it ran. */
  command?: string;
}

const DEFAULT_PROCESSES: readonly MonitorProcess[] = [
  { pid: 1287, user: "wisp", command: "next dev --turbo", cpu: 41, mem: 6.2, swing: 34 },
  { pid: 990, user: "wisp", command: "node scripts/build-registry", cpu: 27, mem: 3.4, swing: 31 },
  {
    pid: 1501,
    user: "wisp",
    command: "chromium --headless --renderer",
    cpu: 23,
    mem: 8.9,
    swing: 27,
  },
  { pid: 655, user: "wisp", command: "esbuild --watch --bundle", cpu: 17, mem: 1.9, swing: 22 },
  { pid: 1042, user: "root", command: "/usr/bin/dockerd", cpu: 11, mem: 2.3, swing: 15 },
  { pid: 812, user: "postgres", command: "postgres: writer process", cpu: 4, mem: 3.8, swing: 9 },
  { pid: 33, user: "root", command: "kworker/u16:2-events_unbound", cpu: 6, mem: 0.2, swing: 13 },
  { pid: 214, user: "root", command: "/lib/systemd/systemd-journald", cpu: 2, mem: 0.6, swing: 6 },
];

function clamp(v: number, lo: number, hi: number): number {
  return v < lo ? lo : v > hi ? hi : v;
}

/** Deterministic 0–1 from an integer seed — used for per-row phases. */
function hash(n: number): number {
  const x = Math.sin(n * 12.9898) * 43758.5453;
  return x - Math.floor(x);
}

/**
 * A seamless wander in [-1, 1]. Every harmonic completes a whole number of
 * cycles over `ticks`, so value(t + ticks) === value(t): no loop seam.
 */
function wander(
  t: number,
  ticks: number,
  harmonics: ReadonlyArray<[k: number, amp: number, phase: number]>,
): number {
  let v = 0;
  for (const [k, amp, phase] of harmonics) {
    v += amp * Math.sin((2 * Math.PI * k * t) / ticks + phase);
  }
  return v;
}

function cpuOf(proc: MonitorProcess, seed: number, t: number, ticks: number): number {
  const base = proc.cpu ?? 8;
  const swing = proc.swing ?? 18;
  const p1 = hash(seed) * Math.PI * 2;
  const p2 = hash(seed + 7) * Math.PI * 2;
  const k1 = 1 + Math.floor(hash(seed + 1) * 3); // 1–3 whole cycles
  const k2 = 4 + Math.floor(hash(seed + 2) * 4); // 4–7 whole cycles
  const w =
    0.62 * Math.sin((2 * Math.PI * k1 * t) / ticks + p1) +
    0.38 * Math.sin((2 * Math.PI * k2 * t) / ticks + p2);
  return clamp(base + swing * w, 0, 99.9);
}

function memOf(proc: MonitorProcess, seed: number, t: number, ticks: number): number {
  // Resident memory drifts far less than CPU — a small, slow breath.
  const base = proc.mem ?? 1;
  const w = wander(t, ticks, [[1, 0.7, hash(seed + 3) * Math.PI * 2]]);
  return clamp(base + base * 0.14 * w, 0, 99.9);
}

// Module scope, not nested in SystemMonitor: a component literal declared
// inside a render function is a new type every render, so React would
// unmount/remount this node on every tick instead of updating it — killing
// the fill's transition before it ever gets to play.
const Meter = ({ label, pct }: { label: string; pct: number }) => (
  <div className="wisp-mon-meter">
    <span className="wisp-mon-key">{label}</span>
    <span className={cx("wisp-mon-bar", pct >= 82 ? "is-hot" : pct >= 58 && "is-warm")}>
      <span className="wisp-mon-bar-fill" style={{ transform: `scaleX(${pct / 100})` }} />
    </span>
    <span className="wisp-mon-pct">{pct.toFixed(1)}%</span>
  </div>
);

export function SystemMonitor({
  processes = DEFAULT_PROCESSES,
  cores = 8,
  ticks = 240,
  tickMs = 360,
  loop = 90,
  uptime = "3 days, 04:12",
  host = "wisp@forge",
  command = "htop --sort-key PERCENT_CPU",
  className,
  ...props
}: SystemMonitorProps) {
  const ref = React.useRef<HTMLDivElement>(null);

  const timings = React.useMemo(
    () => Array.from({ length: Math.max(1, ticks) }, () => ({ hold: tickMs })),
    [ticks, tickMs],
  );

  const shown = useScriptedSequence(ref, { steps: timings, loop });
  // `shown` is our frame clock: it climbs 0…ticks and wraps. Because the state
  // is periodic with period `ticks`, the settle frame (`shown === ticks`) folds
  // onto frame 0 — a normal, fully-populated frame, never a blank one.
  const frame = shown % ticks;

  // ---- meters ----------------------------------------------------------
  const cpuLoad = (t: number) =>
    clamp(
      processes.reduce((sum, p, i) => sum + cpuOf(p, p.pid + i, t, ticks), 0) / Math.max(1, cores),
      2,
      99,
    );

  const cpuPct = cpuLoad(frame);
  const memPct = clamp(
    52 +
      22 *
        wander(frame, ticks, [
          [1, 0.66, 0.4],
          [3, 0.34, 2.1],
        ]),
    8,
    96,
  );
  const swpPct = clamp(
    7 +
      6 *
        wander(frame, ticks, [
          [2, 0.7, 1.2],
          [5, 0.3, 4.0],
        ]),
    0,
    60,
  );

  // Load average: three lagged reads of the CPU meter, so the trio moves like
  // the real thing instead of three unrelated dials.
  const at = (lag: number) => (frame - lag + ticks) % ticks;
  const la1 = ((cpuLoad(at(2)) / 100) * cores).toFixed(2);
  const la5 = ((cpuLoad(at(18)) / 100) * cores).toFixed(2);
  const la15 = ((cpuLoad(at(48)) / 100) * cores).toFixed(2);

  // ---- process rows ----------------------------------------------------
  const rows = processes.map((p, i) => {
    const cpu = cpuOf(p, p.pid + i, frame, ticks);
    const mem = memOf(p, p.pid + i, frame, ticks);
    const running = cpu > 55;
    return { p, cpu, mem, running };
  });
  const runningCount = rows.filter((r) => r.running).length;

  // A wall clock that belongs to the cycle rather than to the reader's machine:
  // it has to be identical on the server and on the first client frame, and it
  // has to return to where it started when the cycle wraps.
  const second = Math.floor((frame / ticks) * 3600);
  const clock = [11, Math.floor(second / 60) % 60, second % 60]
    .map((n) => String(n).padStart(2, "0"))
    .join(":");

  return (
    <div
      ref={ref}
      role="img"
      aria-label="Simulated system monitor showing CPU, memory and process activity wandering over time"
      className={cx("wisp-mon", className)}
      {...props}
    >
      {host ? (
        <div className="wisp-mon-prompt">
          <span className="wisp-mon-host">{host}</span>
          <span className="wisp-mon-path">:~$</span>
          <span className="wisp-mon-cmd-run">{command}</span>
          <span className="wisp-mon-clock">{clock}</span>
        </div>
      ) : null}

      <div className="wisp-mon-gauges">
        <Meter label="CPU" pct={cpuPct} />
        <Meter label="Mem" pct={memPct} />
        <Meter label="Swp" pct={swpPct} />
      </div>

      <div className="wisp-mon-status">
        Tasks <b>{processes.length}</b>, <b>{runningCount}</b> running · Load{" "}
        <b>
          {la1} {la5} {la15}
        </b>{" "}
        · Up <b>{uptime}</b>
      </div>

      <div className="wisp-mon-table">
        <div className="wisp-mon-head">
          <span className="wisp-mon-num">PID</span>
          <span>USER</span>
          <span className="wisp-mon-num">CPU%</span>
          <span className="wisp-mon-num">MEM%</span>
          <span>COMMAND</span>
        </div>
        {rows.map(({ p, cpu, mem, running }) => (
          <div key={p.pid} className={cx("wisp-mon-row", running && "is-active")}>
            <span className="wisp-mon-num">{p.pid}</span>
            <span className="wisp-mon-user">{p.user}</span>
            <span className={cx("wisp-mon-num", cpu >= 55 && "is-hot")}>{cpu.toFixed(1)}</span>
            <span className="wisp-mon-num">{mem.toFixed(1)}</span>
            <span className="wisp-mon-cmd">{p.command}</span>
          </div>
        ))}
      </div>

      {/* The line the reader would type next. It is what separates a pane that
          is running from a screenshot of one — the table can be still for a
          second, and this never is. */}
      <div className="wisp-mon-tail">
        <span className="wisp-mon-host">{host || "wisp@forge"}</span>
        <span className="wisp-mon-path">:~$</span>
        <span className="wisp-caret wisp-mon-caret" aria-hidden />
      </div>
    </div>
  );
}

Certified against the contract