|
| 1 | +<!-- Copyright 2026 Phillip Cloud --> |
| 2 | +<!-- Licensed under the Apache License, Version 2.0 --> |
| 3 | + |
| 4 | +Capture a screenshot or short video demonstrating a TUI feature or bugfix |
| 5 | +for PR review. |
| 6 | + |
| 7 | +## Arguments |
| 8 | + |
| 9 | +Optional: a PR number (e.g. `/capture-ui 123`). When given, the PR is |
| 10 | +checked out in an isolated worktree, micasa is built from that source, and |
| 11 | +the capture runs against the PR's code. Without a PR number, captures run |
| 12 | +against the current working tree. |
| 13 | + |
| 14 | +## When to use |
| 15 | + |
| 16 | +- After completing TUI feature or bugfix work, before or during PR creation |
| 17 | +- To visually verify an existing PR's UI changes |
| 18 | + |
| 19 | +## Procedure |
| 20 | + |
| 21 | +### 0. PR mode setup (skip if no PR number) |
| 22 | + |
| 23 | +When a PR number is provided: |
| 24 | + |
| 25 | +```sh |
| 26 | +pr_num=<N> |
| 27 | +pr_branch=$(gh pr view "$pr_num" --json headRefName --jq .headRefName) |
| 28 | +worktree_dir=".claude/worktrees/pr-$pr_num" |
| 29 | +git worktree add "$worktree_dir" "$pr_branch" |
| 30 | +``` |
| 31 | + |
| 32 | +Build micasa from the PR source: |
| 33 | +```sh |
| 34 | +CGO_ENABLED=0 go build -trimpath -o "$worktree_dir/micasa" -C "$worktree_dir" ./cmd/micasa |
| 35 | +``` |
| 36 | + |
| 37 | +All remaining steps use `$worktree_dir/micasa` instead of `micasa` on |
| 38 | +PATH. The tape preamble changes accordingly (see step 3). |
| 39 | + |
| 40 | +### 1. Understand what changed |
| 41 | + |
| 42 | +Before writing any tape, understand what the PR or recent work actually |
| 43 | +changed in the UI. Random screenshots of unrelated screens are useless. |
| 44 | + |
| 45 | +**PR mode:** |
| 46 | +```sh |
| 47 | +gh pr view <N> --json body --jq .body |
| 48 | +gh pr diff <N> --name-only |
| 49 | +``` |
| 50 | + |
| 51 | +Read the PR description and changed files. Identify: |
| 52 | +- Which screen/tab/overlay was affected |
| 53 | +- What the visual difference is (new element, changed layout, error state, etc.) |
| 54 | +- What user interaction triggers the change |
| 55 | +- Whether the change is even visually demonstrable (some changes like |
| 56 | + error handling require specific failure conditions that demo mode can't |
| 57 | + produce — in that case, tell the user and skip the capture) |
| 58 | + |
| 59 | +**Normal mode:** You already know what changed because you just wrote it. |
| 60 | +Still pause and identify the specific screen state that demonstrates it. |
| 61 | + |
| 62 | +If the change is not visually demonstrable in demo mode, say so and abort. |
| 63 | +Do not capture random unrelated screens. |
| 64 | + |
| 65 | +### 2. Decide capture mode (screenshot vs video) |
| 66 | + |
| 67 | +- **Screenshot** (default): single PNG of the final relevant state. Use for |
| 68 | + layout changes, new columns, style tweaks, form additions. |
| 69 | +- **Video** (`--video`): animated WebP of an interaction sequence. Use for |
| 70 | + filtering, sorting, navigation, overlays, animations -- anything where |
| 71 | + the change is in the *transition*, not just the end state. |
| 72 | + |
| 73 | +### 2. Write an ad-hoc VHS tape |
| 74 | + |
| 75 | +Get the current short commit hash for the filename: |
| 76 | +```sh |
| 77 | +git rev-parse --short HEAD # normal mode |
| 78 | +git -C "$worktree_dir" rev-parse --short HEAD # PR mode |
| 79 | +``` |
| 80 | + |
| 81 | +Create `.claude/captures/<short-sha>-<descriptive-name>.tape`. |
| 82 | + |
| 83 | +**Standard preamble (no PR number):** |
| 84 | + |
| 85 | +```tape |
| 86 | +Require micasa |
| 87 | +
|
| 88 | +Output .claude/captures/<short-sha>-<descriptive-name>.webm |
| 89 | +
|
| 90 | +Set Shell bash |
| 91 | +Set FontFamily "Hack Nerd Font" |
| 92 | +Set FontSize 32 |
| 93 | +Set Width 2400 |
| 94 | +Set Height 1200 |
| 95 | +Set Padding 20 |
| 96 | +Set Theme "Dracula" |
| 97 | +Set CursorBlink false |
| 98 | +Set TypingSpeed 0 |
| 99 | +
|
| 100 | +Env NO_COLOR "" |
| 101 | +Env TERM "xterm-256color" |
| 102 | +Env COLORTERM "truecolor" |
| 103 | +Env COLORFGBG "15;0" |
| 104 | +Env PS1 "" |
| 105 | +
|
| 106 | +Hide |
| 107 | +Type "exec micasa demo" |
| 108 | +Enter |
| 109 | +Sleep 5s |
| 110 | +``` |
| 111 | + |
| 112 | +**PR mode preamble** (uses the locally built binary, no `Require`): |
| 113 | + |
| 114 | +```tape |
| 115 | +Output .claude/captures/<short-sha>-<descriptive-name>.webm |
| 116 | +
|
| 117 | +Set Shell bash |
| 118 | +Set FontFamily "Hack Nerd Font" |
| 119 | +Set FontSize 32 |
| 120 | +Set Width 2400 |
| 121 | +Set Height 1200 |
| 122 | +Set Padding 20 |
| 123 | +Set Theme "Dracula" |
| 124 | +Set CursorBlink false |
| 125 | +Set TypingSpeed 0 |
| 126 | +
|
| 127 | +Env NO_COLOR "" |
| 128 | +Env TERM "xterm-256color" |
| 129 | +Env COLORTERM "truecolor" |
| 130 | +Env COLORFGBG "15;0" |
| 131 | +Env PS1 "" |
| 132 | +
|
| 133 | +Hide |
| 134 | +Type "exec <absolute-path-to-worktree>/micasa demo" |
| 135 | +Enter |
| 136 | +Sleep 5s |
| 137 | +``` |
| 138 | + |
| 139 | +After the preamble, add keystrokes to navigate to the target state. |
| 140 | +Reference existing tapes in `docs/tapes/` for navigation patterns: |
| 141 | +- `Type "D"` + `Sleep 2s` -- dismiss/toggle dashboard |
| 142 | +- `Type "f"` -- advance to next tab |
| 143 | +- `Type "b"` -- go to previous tab |
| 144 | +- `Type "j"` / `Type "k"` -- navigate rows |
| 145 | +- `Type "l"` / `Type "h"` -- navigate columns |
| 146 | +- `Type "s"` -- sort by current column |
| 147 | +- `Enter` -- drilldown / open |
| 148 | +- `Escape` -- close overlay / go back |
| 149 | +- Tab / Shift+Tab -- toggle house profile |
| 150 | + |
| 151 | +**For screenshots:** navigate to target state hidden, then: |
| 152 | + |
| 153 | +```tape |
| 154 | +Show |
| 155 | +Sleep 1.5s |
| 156 | +Hide |
| 157 | +Ctrl+Q |
| 158 | +Sleep 1s |
| 159 | +``` |
| 160 | + |
| 161 | +**For videos:** `Show` before the interaction begins, capture the full |
| 162 | +action sequence with appropriate sleeps between keystrokes (0.4-0.8s |
| 163 | +between navigation keys, 1-2s pauses at interesting states): |
| 164 | + |
| 165 | +```tape |
| 166 | +Show |
| 167 | +Sleep 1s |
| 168 | +# ... interaction keystrokes with sleeps ... |
| 169 | +Sleep 1.5s |
| 170 | +Hide |
| 171 | +Ctrl+Q |
| 172 | +Sleep 1s |
| 173 | +``` |
| 174 | + |
| 175 | +Keep tapes short -- 5-10 seconds visible for screenshots, 10-20 seconds |
| 176 | +for videos. |
| 177 | + |
| 178 | +### 3. Create the capture directory |
| 179 | + |
| 180 | +```sh |
| 181 | +mkdir -p .claude/captures |
| 182 | +``` |
| 183 | + |
| 184 | +### 4. Record |
| 185 | + |
| 186 | +Both modes use `capture-adhoc` (bundles vhs, fonts, ffmpeg). In PR mode |
| 187 | +the tape drops `Require micasa` and uses the absolute path instead, so |
| 188 | +capture-adhoc's bundled micasa is never invoked. |
| 189 | + |
| 190 | +For screenshot (PNG): |
| 191 | +```sh |
| 192 | +nix run '.#capture-adhoc' -- .claude/captures/<name>.tape |
| 193 | +``` |
| 194 | + |
| 195 | +For video (animated WebP): |
| 196 | +```sh |
| 197 | +nix run '.#capture-adhoc' -- --video .claude/captures/<name>.tape |
| 198 | +``` |
| 199 | + |
| 200 | +Output path is printed to stdout. |
| 201 | + |
| 202 | +If VHS fails, check: |
| 203 | +- Tape syntax (compare against `docs/tapes/dashboard.tape`) |
| 204 | +- That the binary exists and runs (normal: `which micasa`, PR: `<path>/micasa --help`) |
| 205 | +- That `Sleep` after app launch is long enough (5s minimum) |
| 206 | + |
| 207 | +### 5. Present to user and ask about upload |
| 208 | + |
| 209 | +Do NOT clean up the PR worktree yet — the user may want additional |
| 210 | +captures from the same PR. |
| 211 | + |
| 212 | +Show the user: |
| 213 | +- What the capture shows |
| 214 | +- The file path |
| 215 | +- Which PR it targets (if PR mode) |
| 216 | + |
| 217 | +Then ask: **"Upload this to the PR via `gh image`?"** |
| 218 | + |
| 219 | +Do NOT upload without explicit approval. |
| 220 | + |
| 221 | +### 6. Upload if approved |
| 222 | + |
| 223 | +```sh |
| 224 | +gh image .claude/captures/<name>.png # screenshot |
| 225 | +gh image .claude/captures/<name>.webp # video |
| 226 | +``` |
| 227 | + |
| 228 | +Output is a markdown image reference. Include it in: |
| 229 | +- A PR comment: `gh pr comment <N> --body "<markdown-ref>"` |
| 230 | +- The PR body (if creating a PR next) |
| 231 | + |
| 232 | +## Notes |
| 233 | + |
| 234 | +- Captures are ephemeral -- `.claude/captures/` is gitignored |
| 235 | +- To promote a capture to a permanent demo tape, copy the `.tape` to |
| 236 | + `docs/tapes/` and adjust its `Output` path |
| 237 | +- VHS recording takes ~1-2 minutes; do not record unnecessarily |
| 238 | +- Existing tapes in `docs/tapes/` are the authoritative reference for |
| 239 | + keystroke patterns |
| 240 | +- PR mode worktrees are kept alive for additional captures; clean up |
| 241 | + manually with `git worktree remove <dir>` when done |
0 commit comments