.agents/skills/opencode-qa/references/tui-tmux.md
capture-pane reads the rendered frame; send-keys delivers keystrokes to the composer. This is proven and good for SMOKE checks: did it boot, does it render, does it accept input.opencode run (Case A, references/cli-commands.md), the server API + SSE (Case B, references/server-api.md + events-hooks.md), or the TUI control HTTP API (below). The TUI talks to the same server, so API-level QA is equivalent to driving the screen.Launching the real TUI would create sessions in the real ~/.local/share/opencode DB. Run it under an isolated XDG sandbox. The bundled scripts/tui-smoke.sh does exactly this and verifies the real session count is unchanged before/after.
scripts/tui-smoke.sh --self-test launches the TUI under tmux in an isolated sandbox, polls capture-pane for a render marker (version string / "Ask anything" / footer), sends a sentinel keystroke, then kills the tmux session and confirms the real DB is untouched.For PR evidence or any TUI visual QA claim, keep the tmux transcript and add a browser-rendered screenshot using the repository helper:
node script/qa/web-terminal-visual-qa.mjs --title "OpenCode TUI QA" \
--from-file .omo/evidence/<slug>/opencode-tui-pane.txt \
--evidence-dir .omo/evidence/<slug>/opencode-web-terminal
The helper replays the terminal frame into terminal.html, captures
terminal.png with Chrome when available, writes metadata.json, and records
the cleanup receipt. This is the required TUI visual evidence pattern when the
review needs to see the screen, while scripts/tui-smoke.sh remains the
isolation/smoke authority.
SESS=oqa_tui_demo
DIR=$(mktemp -d)
tmux new-session -d -s "$SESS" -x 200 -y 50
# isolate XDG so no real session is written
tmux send-keys -t "$SESS" "XDG_DATA_HOME=$DIR/data XDG_CONFIG_HOME=$DIR/cfg XDG_STATE_HOME=$DIR/state XDG_CACHE_HOME=$DIR/cache OPENCODE_DISABLE_AUTOUPDATE=1 OPENCODE_DISABLE_MODELS_FETCH=1 opencode $DIR" Enter
sleep 7
tmux capture-pane -t "$SESS" -p | sed -n '1,30p' # inspect the rendered frame
tmux send-keys -t "$SESS" "hello" # type into the composer
sleep 1
tmux capture-pane -t "$SESS" -p | sed -n '1,30p'
tmux kill-session -t "$SESS" # teardown (kills the TUI)
rm -rf "$DIR"
Explain: capture-pane -p prints the visible frame; send-keys injects input; kill-session tears down the process tree. Always teardown and remove the temp dir.
A running TUI is a client of the local server, so you can drive it over HTTP without scraping the screen:
Use these (with auth + ?directory=) to deterministically drive a TUI you launched, then assert via the event stream (references/events-hooks.md).
opencode unit-tests TUI components headlessly with @opentui/core/testing createTestRenderer (see packages/opencode/test/cli/tui/, e.g. app-lifecycle.test.ts). This is the route for asserting TUI component behavior in the source repo; see references/testing-harness.md.
Bottom line: tmux for smoke, server/SSE or /tui/* control for assertions, createTestRenderer for source unit tests.