Skip to content
Back to skills

Terminal Perf Test

BSecurity

Verify terminal rendering performance and correctness (WebGL renderer, bot-switch terminal cache, forced-repaint nudge, replay bottom-pin) by running the real desktop app in Chrome against a local gravityd with the double runtime, seeding heavy ANSI output, and stress-testing switching/scrolling/streaming. Use when changing TerminalPane, terminalCache, the daemon's attach/replay/resize path, or investigating terminal CPU, lag, black screens, or rendering artifacts.

  • 21 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 19, 2026
developmentjavascriptgojavashellbashreactnodetestinggitapi

Works with

  • terminal
  • cli
  • api

Security analysis

B88/100
  • criticalSends environment variables or credentials to an external URL

Pro scans all 2 files and shows the line behind each finding

Scanned September 19, 2026

npx -y skills add ahilles107/gravity --skill terminal-perf-test --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Terminal Perf Test?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Terminal Perf Test
[![Security: B — Skills Directory](https://www.skillsdirectory.com/api/skills/ahilles107-terminal-perf-test/badge)](https://www.skillsdirectory.com/skills/ahilles107-terminal-perf-test)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: terminal-perf-test
description: Verify terminal rendering performance and correctness (WebGL renderer, bot-switch terminal cache, forced-repaint nudge, replay bottom-pin) by running the real desktop app in Chrome against a local gravityd with the double runtime, seeding heavy ANSI output, and stress-testing switching/scrolling/streaming. Use when changing TerminalPane, terminalCache, the daemon's attach/replay/resize path, or investigating terminal CPU, lag, black screens, or rendering artifacts.
---

# Terminal rendering performance test

Runs the real stack locally — `gravityd` with the deterministic `double` runtime, the
Vite-served desktop frontend in Chrome — then seeds terminals with more output
than the daemon's 1 MiB scrollback ring and stress-tests the paths that have
historically broken: bulk replay after eviction, rapid bot switching, live
streaming, scrolling under load.

Why the double runtime: it echoes every `input` byte back as terminal output
(so a driver script can pump arbitrary ANSI at any rate), and it prints a
`[resize CxR]` marker for every pty resize — making the forced-repaint nudge
(shrink to rows-1, restore ~50 ms later; see `REPAINT_NUDGE_DELAY` in
`crates/gravityd/src/supervisor.rs`) visible on screen.

## Setup

Use Node 22+ for the driver and the prerequisites in `CONTRIBUTING.md`.
Run from the repository root in a dedicated terminal. Verify test ports 49555
and 1420 are free first; choose unused ports consistently if needed.

```bash
umask 077
PERF_DIR=$(mktemp -d "${TMPDIR:-/tmp}/gravity-perf.XXXXXX")
PERF_DIR=$(cd "$PERF_DIR" && pwd -P)
export GRAVITY_HOME="$PERF_DIR"
export GRAVITY_WS=ws://127.0.0.1:49555/ws
cargo build -p gravityd
cat > "$GRAVITY_HOME/gravityd.toml" <<EOF
home = "$GRAVITY_HOME"
bind = ["127.0.0.1"]
port = 49555
negotiate_port = false
runtime = "double"
supervision_interval_ms = 500
EOF
./target/debug/gravityd --config "$GRAVITY_HOME/gravityd.toml" > "$PERF_DIR/gravityd.log" 2>&1 &
PERF_DAEMON_PID=$!
```

Wait for `$GRAVITY_HOME/gravityd.port`, confirm it is 49555 and the recorded
PID is still running, then check `curl --fail http://127.0.0.1:49555/health`.
Stop on startup failure. Do not connect the driver to an installed daemon or
use it with `pty`: its generated input belongs only in the echo runtime.

Start the frontend in this shell, with shell tracing off. The dev token stays
in the local process environment; do not print it or paste it into browser
commands or chat.

```bash
export VITE_GRAVITY_DEV_PORT=49555
export VITE_GRAVITY_DEV_TOKEN="$(cat "$GRAVITY_HOME/secrets/client.token")"
(cd apps/desktop && env -u CONDUCTOR_PORT pnpm dev)
```

Open `http://localhost:1420/` with available browser automation in a fresh test
profile. Stored connection settings override these defaults: confirm the footer
shows `127.0.0.1:49555 · connected` before interacting. Never expose or deploy
the dev frontend containing this token.

In another terminal, set `GRAVITY_HOME` to this run's exact directory and
`GRAVITY_WS` to the matching loopback URL, then seed:

```bash
node .claude/skills/terminal-perf-test/scripts/drive.mjs setup
```

This creates project "perf" and turbo1–turbo5. Turbo1 exceeds the 1 MiB ring;
the others receive smaller histories. Keep the printed bot IDs for stream mode.

## Verification checklist

Run each check; all must hold. Read browser console errors after every phase using the available tools;
zero exceptions are expected throughout.

1. **WebGL renderer active** — in the page:
   ```js
   const host = document.querySelector('.terminal-host');
   ({ canvases: host.querySelectorAll('canvas').length,      // > 0
      domRows: host.querySelector('.xterm-rows') === null }) // true: no DOM renderer
   ```

2. **Bulk replay paints and lands pinned** — select turbo1 (first visit =
   full replay of the ring). The transcript must be visible without scrolling
   and end at the newest line:
   ```js
   const vp = document.querySelector('.xterm-viewport');
   vp.scrollHeight - vp.clientHeight - vp.scrollTop   // 0 once replay settles
   ```
   A screenshot mid-replay legitimately shows the tail still streaming;
   re-check after a few seconds before calling it a failure.

3. **Cache reuse on switch-back** — tag the terminal's element, switch to
   another bot and back; the same node must return and no `term.reset` path
   should run (screen identical, no repaint flash, no new `[resize ...]`
   shrink marker):
   ```js
   document.querySelector('.terminal-host').firstElementChild.dataset.perfTag = 'x';
   // switch away and back, then:
   document.querySelector('.terminal-host').firstElementChild.dataset.perfTag  // 'x'
   ```

4. **Eviction survives** — visit 4+ other bots (cache holds 3), then return
   to turbo1. Expect: full replay again (fresh node, tag gone), content
   pinned at the bottom, and a nudge marker pair like
   `[resize 163x45][resize 163x46]` appended — shrink and restore observed by
   the runtime as two separate sizes. No console errors: eviction disposes a
   parked WebGL terminal, which is the path that once crashed the whole app.

5. **Live-stream stress with instrumentation** — install the meter, then
   `node .../drive.mjs stream <turbo1-id> 20`, and during it scroll up
   (view must hold position while streaming), switch away and back:
   ```js
   window.__perf = { frames: 0, longTasks: 0, worstFrame: 0, start: performance.now(), last: performance.now() };
   const tick = () => { const now = performance.now(); const d = now - window.__perf.last;
     if (d > window.__perf.worstFrame) window.__perf.worstFrame = d;
     window.__perf.last = now; window.__perf.frames++; requestAnimationFrame(tick); };
   requestAnimationFrame(tick);
   new PerformanceObserver(l => { for (const e of l.getEntries()) window.__perf.longTasks++; })
     .observe({ entryTypes: ['longtask'] });
   ```
   Read afterwards: avg fps ≈ display refresh rate, `longTasks` 0, worst
   frame well under 100 ms. Record the display refresh rate, hardware and elapsed time; compare with
   an unchanged build on the same machine. Reload afterwards to stop the meter.

6. **Rapid-switch storm** — 25 programmatic clicks across all bots at 120 ms
   intervals (dispatch mousedown/mouseup/click on the sidebar rows from the
   javascript tool). Expect a fully painted, pinned terminal and zero
   console errors at the end.

## Pitfalls learned the hard way

- Vite/esbuild strips comments — never verify which code the page runs by
  fetching source and searching for a comment; search for a code token.
- After a React tree crash, HMR cannot revive it and a stale module graph may
  linger: always `location.reload()` before re-testing a fix.
- Chrome/Blink hides the DOM-renderer cost at this terminal size; the CPU win
  matters on WKWebView (prod). For renderer A/B evidence, comment out the
  `loadWebgl(term)` call in `TerminalPane.tsx` temporarily — and put it back.
- The final prod check is still a release build on macOS: WKWebView is a
  different engine than the Chrome used here.

## Cleanup

Stop the foreground Vite session with Ctrl-C. Stop only the daemon started in
this live shell (`kill "$PERF_DAEMON_PID"; wait "$PERF_DAEMON_PID"`). If the
session was lost, verify process ownership again before signalling. Do not use
broad process-name matching. Unset the temporary Vite token environment.

Close the test browser profile. Retain the run directory for review and ask
before deleting its exact path. Restore only your temporary A/B edits, preserving
any pre-existing changes.

Files in this skill

  • SKILL.md7.4 KB
  • scripts/drive.mjs4.6 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.