Files
claude-cloak/.claude/skills/tui-verify/SKILL.md
2026-07-09 08:49:53 +02:00

4.6 KiB
Raw Blame History

name, description
name description
tui-verify Drive and visually verify this TUI (claude-cloak, or any terminal app / embedded claude pane) by running it inside a headless tmux session, sending real keystrokes, and capturing the rendered screen as text. Use whenever you need to confirm a UI/pane/rendering change actually works end-to-end — not just that tests pass — e.g. "verify the pane expands", "does @ show the menu", "check the layout at a small size". Claude Code's own TUI cannot run in a non-tty, so tmux is the way to give it a real terminal.

Verifying a TUI with tmux

The app renders to a real terminal and cannot run in a plain pipe. tmux gives it a genuine tty in the background, lets you type into it, and lets you read back exactly what's on screen as plain text — so you can see a rendering change instead of guessing from tests.

The safety rule (read first)

Never use pkill, killall, kill -9 <pattern>, or any broad process-matching kill. The user runs their own real claude-cloak instances (including the one hosting your session) on the same machine. pkill -f claude-cloak will kill all of them. Only ever tear down tmux sessions you created, by their exact name:

tmux kill-session -t <your-session-name>   # only your own named session

Pick a distinctive session name (e.g. ccverify) so you never touch anything else. Before starting, tmux ls to see what's already there and avoid name clashes — never kill a session you didn't create.

Core loop

  1. Build the thing you're testing (cargo build --release).

  2. Launch it in a detached tmux session, sized to the scenario you care about. Small sizes (e.g. -x 100 -y 20) are what expose layout/overflow bugs — a big terminal hides them.

    tmux new-session -d -s ccverify -x 100 -y 20 "./target/release/claude-cloak"
    sleep 2   # give it time to draw the first frame
    
  3. Capture the rendered screen as text:

    tmux capture-pane -t ccverify -p
    

    Add -e to include ANSI escapes (then | cat -A) when you need to inspect colors/attributes — e.g. which menu row is highlighted:

    tmux capture-pane -t ccverify -p -e | cat -A
    
  4. Send keystrokes, then sleep briefly (the app redraws on its own ~30fps tick — give it 12s) and capture again:

    tmux send-keys -t ccverify "a"          # a literal key
    tmux send-keys -t ccverify "Down" "Enter"   # named keys
    tmux send-keys -t ccverify "C-u"        # ctrl-u (clear line)
    tmux send-keys -t ccverify "@src"       # a literal string
    
  5. Tear down your session by name when done (see safety rule).

send-keys gotchas

  • Each space-separated token is interpreted as a key name (Down, Enter, Space, C-f, Escape). So send-keys "Down Down Down" sends three Down keys — but if the app has meanwhile changed state (e.g. a menu collapsed to a single match and closed), the same tokens can land as literal text. If you see literal Down Down Down in an input box, that's this — resend as needed.
  • To send a literal string that contains spaces or key-like words, send it as one quoted argument and/or use -l (literal): tmux send-keys -l -t s "Down".
  • Control/modifier chords: C-u, C-f, C-q, S-Tab (shift-tab). This app avoids Alt on purpose — don't test Alt chords.
  • After any keystroke, sleep before capturing. Captures taken mid-repaint show a transient frame (a half-drawn box), which can look like a bug that isn't there. 11.5s is usually enough.

Reading the claude-cloak screen

  • The bottom footer line shows the bound proxy port and the active keybinding hints — a quick sanity check that the app is alive and in the expected mode.
  • The embedded pane is the lower bordered box titled claude · <id>. Its compact framing (src/term.rs) shows one context row, the input box (bracketed by ──── … ── rules), and either the statusLine (idle) or an @// menu.
  • To spawn an embedded claude pane for pane tests: press a (model picker), navigate with Down, Enter to spawn. It resumes-then-clears to a fresh chat. ctrl-f toggles fullscreen (shows the child's raw screen verbatim — useful to see what Ink actually drew vs. what we crop).

Why this beats tests alone

Unit tests over compact_frame / compact_view_range pin the framing math, but they can't catch a PTY-sizing feedback loop where Ink never draws the rows we'd measure — that only shows up when a real claude child renders into a real PTY. Run the tmux loop for any change to pane sizing, cropping, cursor, or layout.