agentleFS
gloom-shgloom-sh/gloomberb/.agents/skills/tui-testing/SKILL.md

tui-testing

Skill2.7k starsChanged 42 days agoMITView on GitHub

Testing Gloomberb at every level: CLI commands for fast data/integration checks, OpenTUI's built-in test harness for component tests, and tmux for full end-to-end terminal TUI testing. Use this skill when you need to verify terminal TUI features, write OpenTUI regression tests, or smoke-test the terminal app. Do not use it for Electrobun/desktop-web-only visual or window chrome work unless tmux/OpenTUI coverage is explicitly needed.

  • Deletes or force-pushes

7 other projects use a version of this.

Trending

Steady talk.thin dataabout the whole repository

Dec 2025: 0 mentionsJan 2026: 0 mentionsFeb 2026: 0 mentionsMar 2026: 0 mentionsApr 2026: 0 mentionsMay 2026: 0 mentionsJun 2026: 0 mentionsJul 2026: 0 mentionsAug 2026: 4 mentionsSep 2026: 2 mentionsOct 2026: 1 mentionNov 2026: 1 mention
Dec 2025Jun 2026Nov 2026
Mentions a month of the gloom-sh/gloomberb repository that our weekly crawl found: only 8 in all, so this is a hint, not a trend. There is no adoption data for this file yet.

Most recently taken up by

Who's talking about gloom-sh/gloomberb

The biggest 5 of 8 mentions linking to the repository that our crawl found. Each line is their own headline.

See all 8

Talked about on GitHub and Bluesky (8 times).

On GitHub · 7

2 more on GitHub

Used with

Projects with it also use classify-changelog-pr, cross-platform-cloud-verification and 10 more.

Other files people use alongside this one.

Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
  • black12-agSkill

    classify-changelog-pr

    Reference guidance for classifying whether an unmarked PR should appear in the changelog and under which category. Used inline by the changelog-draft skill — not dispatched as a separate agent.

    Also in 4 projects that use this

  • black12-agSkill

    cross-platform-cloud-verification

    Orchestrates cost-conscious cloud verification across available operating-system and architecture runners after cheaper checks pass. Use whenever a code change, bug fix, build, packaging flow, native dependency, UI behavior, filesystem behavior, or test has material cross-platform implications, even if the user only asks to verify or test the change. Consult domain verification skills for what to test; use this skill to choose and access the smallest relevant set of platforms.

    Also in 4 projects that use this

  • +10 more from black12-ag
  • Other versions7 projects use a version of this.

    Word for word · 1

    1. vincelwt/gloomberb★ 2.7k100% same

    Edited · 0

    None found.

    Wrote their own version · 6

    1. warpdotdev/Warp★ 66ksame name
    2. warpdotdev/warp★ 66ksame name
    3. dominikmartn/harp★ 698same name
    4. masonhuemmer/dops★ 3same name
    5. rundops/dops★ 3same name
    6. black12-ag/weft★ 2same name

    How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.

  • In this repository3 other files it gives its agents.

The file

Show its rules and text

We split this skill into the rules it gives an agent

23 rules. 0 are common, 8 are distinctive, and 15 are very specific to this project.

  • 0

    Common

    Most of the 6 edited versions we read keep these.

  • 8

    Distinctive

    This author's own choices. Worth a look.

  • 15

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Never touch the real ~/.gloomberb: Every tmux smoke, and any CLI command that writes (plugins, config, notes, alerts, portfolios), runs …

    line 20
  2. Very specific:

    Prefer named tmux sessions and end them with tmux kill-session -t <name>.

    line 37
  3. Very specific:

    If you start a background process without tmux, capture its PID and terminate it explicitly with kill <pid> or the matching shutdown …

    line 38
  4. Distinctive:

    Do not leave test helpers running across iterations unless the workflow explicitly requires it and you clean them up before handoff.

    line 39
  5. Distinctive:

    Finish with a leak sweep: Before you report a test as done, confirm nothing you started is still alive:

    line 49
  6. Very specific:

    Use bun start <command> for one-shot checks.

    line 81
  7. Very specific:

    Verifying data layer changes: after modifying config, ticker storage, or persistence, run portfolio or ticker to confirm data loads …

    line 96
  8. Very specific:

    Checking new data fields: if you add a new field (e.g.

    line 97
  9. Distinctive:

    Fast: no real terminal, network, or app startup.

    line 116
  10. Very specific:

    Use mockInput and React's act() to simulate user input:

    line 143
  11. Very specific:

    Test files live next to source: foo.tsx → foo.test.tsx

    line 201
  12. Very specific:

    Create the renderer with createOpenTuiTestHarness so it is destroyed after every test.

    line 202
  13. Very specific:

    Always call renderOnce() before captureCharFrame()

    line 203
  14. Distinctive:

    Use consistent dimensions for snapshot stability (80×24 default)

    line 204
  15. Distinctive:

    Run the actual app in a tmux session, send keystrokes, and capture the rendered screen.

    line 210
  16. Distinctive:

    When tmux is used for React/OpenTUI changes, always treat runtime health as part of the smoke.

    line 212
  17. Very specific:

    Create the throwaway $SMOKE_HOME profile from Critical Rules first.

    line 224
  18. Very specific:

    If you need to run the app without tmux, keep the process handle so you can shut it down:

    line 243
  19. Very specific:

    Important: Always use -l for text input.

    line 280
  20. Very specific:

    Always add sleep 0.5 to sleep 1 after sending input to let the UI re-render.

    line 284
  21. Distinctive:

    When a bug mentions a specific saved layout, pane type, or focus path, launch directly into that layout or use the remote-control endpoint …

    line 356
  22. Distinctive:

    Timing: If captures look incomplete, increase sleep duration.

    line 367
  23. Very specific:

    Cleanup: Always kill the tmux session or any other background process you started when done, then rm -rf "${SMOKE_HOME:?}".

    line 369

“Common” is measured: kept by at least 6 in 10 of the 6 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Testing Gloomberb

Three testing approaches. Start with the simplest level that covers your change and escalate only when needed.

This skill is for terminal TUI and OpenTUI test workflows. For Electrobun/desktop-web-only fixes, use focused desktop builds/tests instead unless the task explicitly asks for tmux or terminal rendering coverage.

Critical Rules

Never touch the real ~/.gloomberb. Every tmux smoke, and any CLI command that writes (plugins, config, notes, alerts, portfolios), runs against a throwaway profile: point GLOOMBERB_HOME at a temp dir whose config.json sets "onboardingComplete": true.

SMOKE_HOME=$HOME/.cache/gloom-smoke/my-change   # a literal path, unique to this task, on disk
mkdir -p "$SMOKE_HOME"
printf '{ "dataDir": "%s", "onboardingComplete": true }\n' "$SMOKE_HOME" > "$SMOKE_HOME/config.json"
grep '"dataDir"' "$SMOKE_HOME/config.json"   # must name $SMOKE_HOME, never ~/.gloomberb

Keep the profile on disk, not under /tmp: on many Linux machines /tmp is RAM-backed with a fixed inode limit, and one profile is about 29,000 files once the first launch has installed the plugins, so a handful of leftover profiles can fill it and make every shell command fail.

An empty GLOOMBERB_HOME falls back to the real profile: ~/.gloomberb, or the XDG folders (~/.config/gloomberb, ~/.local/share/gloomberb) on a Linux machine without one. Shell variables do not carry over between separate shells, so set SMOKE_HOME again in every shell that launches the app, and write it as ${SMOKE_HOME:?} in launch commands so an unset value aborts instead of opening the real profile.

If you start from a copy of a real config instead, rewrite its dataDir into the temp dir and confirm with the same grep before launching: the app reads and writes wherever dataDir points, so a copied config that still names ~/.gloomberb edits the real profile. The first launch on a new profile reinstalls the plugins that moved out of this repo into $SMOKE_HOME/plugins, which needs network and takes a few seconds.

Cleanup is part of testing. If you start tmux, a dev server, a watcher, or any other background process while testing, stop it before you finish the task, even if the test fails.

  • Prefer named tmux sessions and end them with tmux kill-session -t <name>.
  • If you start a background process without tmux, capture its PID and terminate it explicitly with kill <pid> or the matching shutdown command.
  • Do not leave test helpers running across iterations unless the workflow explicitly requires it and you clean them up before handoff.

Never open the TV pane in a live app. macro-tv (shortcut TV) spawns an external media player such as mpv, plus yt-dlp/ffmpeg. Those processes outlive the tmux session that started them, keep playing audio, and burn CPU on the developer's machine until they are killed by hand. Verify TV changes by reading the code and with unit tests. The same rule applies to any pane or feature that shells out to a media player.

Never run an unbounded or streaming log scan. A tail -c 4000000 <log> | strings | grep ... | head -20 health scan once pinned a core at 100% for over five hours. Scan a bounded slice of a file that is no longer being written, and never involve strings:

head -c 200000 /tmp/gloomberb-test.log | grep -aE "<pattern>" | head -20

Finish with a leak sweep. Before you report a test as done, confirm nothing you started is still alive:

ps -Ao pid,%cpu,etime,command \
  | grep -iE "mpv|yt-dlp|ffmpeg|ffplay|tail -c|strings|bun run dev|bun src/cli/entry.ts" \
  | grep -v grep
tmux ls 2>&1

Kill anything of yours that shows up and re-run until the sweep is clean.

What are you testing?
├─ Data flow, config, business logic, or integration with external sources
│  ├─ CLI command exists for it → Run the CLI command (fastest)
│  └─ No CLI command, but it makes sense as one → Add it, then use it
├─ A component's rendering, interaction, or visual regression
│  └─ OpenTUI test harness (.test.tsx file)
├─ Pure logic with no UI or data layer
│  └─ Unit test (bun:test, no renderer needed)
└─ Full app behavior across multiple views
   └─ tmux (last resort)

1. CLI Commands (fastest feedback loop)

Gloomberb doubles as a CLI tool. CLI commands are the fastest way to verify data flow, config state, and business logic: no renderer, no harness setup, just run and check output.

Running commands

Use bun start <command> for one-shot checks. It runs src/cli/entry.ts once, like the installed gloomberb; bun run dev adds --watch and is for working on the interactive app.

bun start help                          # Every command, grouped
bun start help ticker                   # Usage, options, and examples for one command
bun start portfolio                     # Portfolios and watchlists
bun start ticker AAPL                   # Quote, fundamentals, positions
bun start ticker AAPL --json            # The same report as JSON
bun start plugins                       # Plugins installed from GitHub

bun start help is the source of truth for what exists; do not rely on a list copied into docs.

When to use CLI testing

  • Verifying data layer changes: after modifying config, ticker storage, or persistence, run portfolio or ticker to confirm data loads correctly.
  • Checking new data fields: if you add a new field (e.g. earnings date), first expose it via the ticker CLI command and verify the output, before wiring it into the TUI.
  • Smoke-testing integrations: ticker AAPL exercises the market data providers, quote formatting, and fundamentals parsing in one command.
  • Testing plugin management: install, remove, update, and plugins commands verify the plugin lifecycle. Run them under a temp GLOOMBERB_HOME (see Critical Rules).

Adding new CLI commands

If you're building a feature and need to verify data that isn't exposed via CLI yet, consider adding a CLI command first when it makes sense (e.g. listing watchlists, showing config values, checking broker sync status). This gives you a fast feedback loop before building the TUI component.

Commands are CliCommandDef objects. Core commands are listed in createCoreCliCommands() in src/cli/index.ts, most of them defined in src/cli/commands/*. A plugin contributes its own through cliCommands (see src/plugins/builtin/portfolio-list/cli/portfolio-command.ts). src/cli/registry.ts merges both into one lookup, rejects duplicate names and aliases, and drops commands from disabled plugins. Give it a help block (group, usage, examples) so bun start help files it under the right heading.


2. OpenTUI Test Harness (component & snapshot tests)

Headless, deterministic component and integration tests using Bun's test runner and OpenTUI's built-in test renderer. Use this for UI component tests and visual regressions.

Why use this

  • Deterministic: renderOnce() guarantees the frame is complete before capture. No timing guesswork.
  • Fast: no real terminal, network, or app startup.
  • Isolated: each test gets its own headless renderer, and the harness destroys it after the test.
  • Composable: render individual components with controlled props/state.
  • Snapshotable: built-in toMatchSnapshot() for visual regression testing.

Quick start

import { test, expect } from "bun:test";
import { createOpenTuiTestHarness } from "../../renderers/opentui/test-utils"; // relative to your test file

// One renderer per test, destroyed after every test even when an assertion fails.
const tui = createOpenTuiTestHarness({ width: 80, height: 24 });

test("renders my component", async () => {
  await tui.render(<MyComponent someProp="value" />);

  // Polls until the text renders and throws with the last frame if it never does.
  const frame = await tui.waitForFrameToContain("expected text");
  // or: expect(frame).toMatchSnapshot();
});

Import the harness from src/renderers/opentui/test-utils (plugins in their own repositories use gloomberb/test-support), not testRender straight from @opentui/react/test-utils: the wrapper mounts the UI host, input, toast, and dialog providers that the shared kit needs. Besides render and setup() (the current renderer), the harness has frame(), emitKeypress, renderFrames, waitForFrameToContain, waitForFrameToExclude, clickFrameText and destroy(). Prefer waitForFrameToContain over a fixed number of frames when a test waits for something to render: fixed frame counts flake on a loaded CI runner. A suite that must configure the renderer before anything mounts (kitty capabilities, pixel resolution) uses tui.createRoot() instead of render.

Interaction testing

Use mockInput and React's act() to simulate user input:

import { act } from "react";

test("arrow keys navigate the list", async () => {
  await tui.render(<MyList items={items} />);
  await tui.renderFrames(1);

  await act(async () => {
    tui.setup().mockInput.pressArrow("down");
    await tui.setup().renderOnce();
  });

  expect(tui.frame()).toContain("▸ Second Item");
});

Testing components that need app context

The harness already provides the dialog host. Components that read app state also need AppContext:

import { AppContext, createInitialState } from "../../state/app/context";
import { createDefaultConfig } from "../../types/config";

const state = createInitialState(createDefaultConfig("/tmp/gloomberb-my-test"));
await tui.render(
  <AppContext value={{ state, dispatch: () => {} }}>
    <MyComponent />
  </AppContext>,
);

src/components/layout/header.test.tsx is a short example that also records dispatched actions and clicks with mockMouse; src/components/ui/ui.test.tsx covers the shared kit (lists, tables, tabs, fields, dialogs).

tui.setup() return object

PropertyTypeDescription
rendererRendererThe headless renderer instance
renderOnce() => Promise<void>Trigger a single render cycle
captureCharFrame() => stringCapture current output as text
resize(w, h) => voidResize the virtual terminal
mockInputMockInputSimulate keyboard input (e.g. pressArrow("down"), typeText("AAPL"))
mockMouseMockMouseSimulate mouse input (e.g. click(x, y), scroll(x, y, "down"))

Running tests

bun test                              # Run all tests
bun test src/components/ui/ui.test.tsx # Run a specific file
bun test -t "command bar"             # Only tests whose name matches
bun test --update-snapshots           # Update snapshot files

Conventions

  • Test files live next to source: foo.tsx → foo.test.tsx
  • Create the renderer with createOpenTuiTestHarness so it is destroyed after every test; keep only suite-specific resets in your own afterEach
  • Always call renderOnce() before captureCharFrame()
  • Use consistent dimensions for snapshot stability (80×24 default)

3. tmux (full end-to-end TUI testing)

Run the actual app in a tmux session, send keystrokes, and capture the rendered screen. Use this as a last resort, for verifying full-app behavior that can't be tested with the harness or CLI.

When tmux is used for React/OpenTUI changes, always treat runtime health as part of the smoke. Do not stop at "it rendered"; scan for React hook/update-depth failures, listener leak warnings, and obvious memory runaway.

When to use tmux

  • Verifying layout and pane arrangement with real data
  • Testing keyboard navigation flows across multiple views
  • Smoke-testing after major refactors
  • Debugging rendering issues that only appear with the full app
  • Checking for real-app React warnings, listener leaks, update loops, or memory growth after layout/pane/chart/subscription changes

Setup

Create the throwaway $SMOKE_HOME profile from Critical Rules first.

# Kill any existing test session first
tmux kill-session -t test 2>/dev/null

# Start the app with a fixed terminal size for consistent captures
tmux new-session -d -s test -x 120 -y 40 "GLOOMBERB_HOME=${SMOKE_HOME:?} bun start 2>&1"

# Wait for the app to render (2-3 seconds for initial load)
sleep 3

For warning/error scans, pipe the pane output to a log before interacting:

tmux pipe-pane -o -t test 'cat > /tmp/gloomberb-test.log'

If you need to run the app without tmux, keep the process handle so you can shut it down:

GLOOMBERB_HOME="${SMOKE_HOME:?}" bun start > /tmp/gloomberb-test.log 2>&1 &
app_pid=$!

# ... test whatever you need ...

kill "$app_pid"
wait "$app_pid" 2>/dev/null

Capturing the screen

tmux capture-pane -t test -p

Returns the full rendered text grid including box-drawing characters. Does NOT capture colors or styling.

Sending input

# Special keys
tmux send-keys -t test C-p          # Ctrl+P: open command bar
tmux send-keys -t test Escape        # Escape: close dialogs
tmux send-keys -t test Enter         # Enter: select/confirm
tmux send-keys -t test Tab           # Tab: focus the next pane
tmux send-keys -t test Up            # Arrow keys
tmux send-keys -t test Down
tmux send-keys -t test j             # j/k: navigate lists
tmux send-keys -t test k

# Typing text (always use -l flag for literal strings)
tmux send-keys -t test -l 'AAPL'

Important: Always use -l for text input. Without it, each character is interpreted as a key name.

Typical test flow

Always add sleep 0.5 to sleep 1 after sending input to let the UI re-render.

# 1. Start the app on the throwaway profile
tmux kill-session -t test 2>/dev/null
tmux new-session -d -s test -x 120 -y 40 "GLOOMBERB_HOME=${SMOKE_HOME:?} bun start 2>&1"
sleep 3

# 2. Capture initial state
tmux capture-pane -t test -p

# 3. Open command bar and search
tmux send-keys -t test C-p
sleep 0.5
tmux send-keys -t test -l 'AAPL'
sleep 0.5
tmux capture-pane -t test -p

# 4. Select and verify
tmux send-keys -t test Enter
sleep 1
tmux capture-pane -t test -p

# 5. Always clean up
tmux kill-session -t test

Runtime health checks

For real-app smokes, especially after touching React hooks, subscriptions, chart rendering, pane settings, market-data events, layout switching, or plugin runtime code, perform these checks before finishing:

  1. Capture a log and final screen.
  2. Scan both for React/runtime warnings.
  3. Sample RSS before and after interactions if the test runs long enough to compare.
  4. Explicitly report any remaining uncertainty, such as a short smoke not proving long-horizon leak absence.

Suggested scan:

LOG=/tmp/gloomberb-test.log
CAP=/tmp/gloomberb-test.capture
tmux capture-pane -t test -p > "$CAP"

rg -n \
  "Possible EventTarget|MaxListeners|Maximum update depth|Invalid hook|Rendered more hooks|Rendered fewer hooks|React has detected|\\[ERROR\\]|Error:" \
  "$LOG" "$CAP" || true

Suggested RSS sampling:

pane_pid=$(tmux display-message -p -t test '#{pane_pid}')
child_pids=$(pgrep -P "$pane_pid" || true)
grandchild_pids=""
for pid in $child_pids; do
  grandchild_pids="$grandchild_pids $(pgrep -P "$pid" || true)"
done
app_pids=$(printf '%s\n' $child_pids $grandchild_pids | awk 'NF' | sort -u)

for pid in $app_pids; do
  ps -o pid=,rss=,command= -p "$pid" || true
done

Interpret memory samples conservatively:

  • A short smoke can catch obvious runaway growth, but it cannot prove there is no long-horizon leak.
  • Startup, first data load, chart bitmap allocation, and layout switching can legitimately increase RSS.
  • Suspicious signs are repeated warning logs, unbounded growth across repeated identical interactions, or jumps paired with update-depth/listener warnings.

Layout and pane smoke coverage

When a bug mentions a specific saved layout, pane type, or focus path, launch directly into that layout or use the remote-control endpoint to switch layouts rather than relying only on keyboard shortcuts. Keyboard encoding in tmux can be terminal-dependent.

Recommended coverage for layout/pane regressions:

  • Launch the affected layout directly from the throwaway profile, or from a copied app config whose dataDir you rewrote into it.
  • Exercise the affected pane's own interactions, such as chart period changes, refresh, tab changes, pane settings, or legend toggles.
  • Switch between the affected layout and another layout at least a few times when the bug involved layout activation or pane unmount/remount behavior.
  • Re-run the warning scan after interactions and after layout switches.

Tips

  • Timing: If captures look incomplete, increase sleep duration. Network-dependent views take longer.
  • Terminal size: -x 120 -y 40 gives consistent layout. Smaller sizes may cause wrapping.
  • Cleanup: Always kill the tmux session or any other background process you started when done, then rm -rf "${SMOKE_HOME:?}".
  • Debugging crashes: stderr is captured via 2>&1 so crash output is visible.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.