Scripts/tuirec/README.md
tuirecUse this guide when an issue or PR asks for a GIF/video capture of a Terminal.Gui
app or scenario. The recording tool is gui-cs/tuirec —
a Go CLI that spawns the target app in a PTY, injects keystrokes, records terminal
output as an asciinema v2 cast, and renders an animated GIF via agg.
# Requires Go 1.22+
go install github.com/gui-cs/tuirec/cmd/tuirec@latest
tuirec --version
# agg is auto-downloaded on first use — no separate install needed.
Verify: tuirec --version. If not on PATH, add $(go env GOPATH)\bin to PATH.
# 1. Build ScenarioRunner (do this ONCE before recording)
dotnet build Examples/ScenarioRunner/ScenarioRunner.csproj -c Release
# 2. Record (cross-platform: use dotnet to run the DLL)
$dll = "./Examples/ScenarioRunner/bin/Release/net10.0/ScenarioRunner.dll"
$ks = 'wait:1200,Tab,Tab,wait:400,A,wait:1800,B,o,wait:1800,E,wait:1800,Tab,wait:400,CursorDown,CursorDown,CursorDown,wait:400,Shift+F10,wait:1500,Escape,wait:400,Escape'
tuirec record `
--binary dotnet `
--args "$dll,run,Character Map" `
--name CharacterMap `
--title "Character Map" `
--keystrokes $ks `
--startup-delay 2000 `
--drain 1500 `
--cols 120 --rows 30 `
--open --copy
Output: artifacts/CharacterMap.gif and artifacts/CharacterMap.cast.
Copy the GIF to the scenario directory:
Copy-Item artifacts/CharacterMap.gif Examples/UICatalog/Scenarios/CharacterMap/CharacterMap.gif
dotnet build Examples/ScenarioRunner/ScenarioRunner.csproj -c Release
dotnet run --project Examples/ScenarioRunner -c Release --no-build -- list
Each scenario has a GetDemoKeyStrokes() method that defines a canonical
interaction sequence for benchmarking. Use this as your starting point:
# Find the demo keystrokes for a scenario:
grep -n "GetDemoKeyStrokes" Examples/UICatalog/Scenarios/<ScenarioFile>.cs
The demo keystrokes show what keys the scenario expects and what UI flow is interesting. Translate them to tuirec syntax:
| Terminal.Gui Key | tuirec Token |
|---|---|
Key.CursorDown | CursorDown |
Key.CursorLeft | CursorLeft |
Key.Tab | Tab |
Key.Tab.WithShift | Shift+Tab |
Key.Enter | Enter |
Key.Esc | Esc |
Key.B | B (or `B` for literal) |
Principles for a great recording:
wait:1000 — let the UI render fully after startup-delay.wait: between logical steps — wait:500 to wait:1500 between
groups of actions so viewers can follow what's happening.Escape — the default Terminal.Gui quit key.$dll = "./Examples/ScenarioRunner/bin/Release/net10.0/ScenarioRunner.dll"
$ks = '<your keystroke script here>'
tuirec record `
--binary dotnet `
--args "$dll,run,<Scenario Name>" `
--name <ScenarioName> `
--title "<Scenario Name>" `
--keystrokes $ks `
--startup-delay 2000 `
--drain 2000 `
--cols 120 --rows 30 `
--verbosity high `
--open --copy
# Copy GIF to scenario directory
Copy-Item artifacts/<ScenarioName>.gif Examples/UICatalog/Scenarios/<ScenarioDir>/<ScenarioName>.gif
GIFs live alongside the .cs file they document:
| What | Where |
|---|---|
| Scenario in a subdirectory | Examples/UICatalog/Scenarios/<ScenarioDir>/<ScenarioName>.gif |
Scenario directly in Scenarios/ | Examples/UICatalog/Scenarios/<ScenarioName>.gif |
| View-derived class | docfx/images/views/<ViewName>.gif |
Use --name <ScenarioName> (PascalCase matching the class name) so the output
file is named correctly. The --name value determines the artifact filenames.
--kitty-keyboard DecisionKnown bug (gui-cs/tuirec#54):
tuirec currently encodes navigation keys (CursorUp, CursorDown, CursorLeft,
CursorRight, PageUp, PageDown, Home, End) incorrectly under
--kitty-keyboard — it sends fabricated CSI u codepoints that the Kitty spec
doesn't define. Terminal.Gui ignores or misinterprets these sequences.
Workaround until fixed:
--kitty-keyboard for demos that use navigation keys.--kitty-keyboard only when you need modifier disambiguation for
non-navigation keys (Ctrl+M vs Enter, Ctrl+I vs Tab, Ctrl+Q, etc.)
and the demo doesn't rely on arrow/page/home/end keys.Once the bug is fixed, --kitty-keyboard should be the default for all
Terminal.Gui recordings (it provides cleaner modifier handling).
--args for ScenarioRunnerThe --args flag uses comma-separated values (not space-separated):
--args "run,Character Map" # Correct: two args ["run", "Character Map"]
--args "run Character Map" # WRONG: one arg "run Character Map"
Always assign keystrokes to a single-quoted $ks variable to preserve
backtick literals:
# Correct — single quotes prevent PowerShell backtick interpolation:
$ks = 'wait:1000,`search text`,Enter,wait:500,Escape'
# WRONG — PowerShell eats the backticks:
--keystrokes "wait:1000,`search text`,Enter"
$dll = "./Examples/ScenarioRunner/bin/Release/net10.0/ScenarioRunner.dll"
# Navigate to category list, browse Arrows → Box Drawing → Emoji, then context menu
$ks = 'wait:1200,Tab,Tab,wait:400,A,wait:1800,B,o,wait:1800,E,wait:1800,Tab,wait:400,CursorDown,CursorDown,CursorDown,wait:400,Shift+F10,wait:1500,Escape,wait:400,Escape'
tuirec record `
--binary dotnet `
--args "$dll,run,Character Map" `
--name CharacterMap `
--title "Character Map" `
--keystrokes $ks `
--startup-delay 2000 `
--drain 1500 `
--cols 120 --rows 30 `
--open --copy
Script breakdown:
| Step | Tokens | What happens |
|---|---|---|
| 1 | wait:1200 | Let the CharMap UI fully render |
| 2 | Tab,Tab | Move focus to category list |
| 3 | A | CollectionNavigator jumps to "Arrows" |
| 4 | wait:1800 | Pause so viewer sees arrow characters |
| 5 | B,o | Type "Bo" — jumps to "Box Drawing" |
| 6 | wait:1800 | Pause so viewer sees box-drawing characters |
| 7 | E | Type "E" — jumps to "Emoji" |
| 8 | wait:1800 | Pause so viewer sees emoji characters |
| 9 | Tab | Return focus to charmap grid |
| 10 | CursorDown ×3 | Navigate to a glyph |
| 11 | Shift+F10 | Open context menu (Copy Glyph / Copy Code Point) |
| 12 | wait:1500,Escape | Let viewer see the menu, then dismiss |
| 13 | Escape | Quit |
Key techniques demonstrated:
Shift+F10 (the PopoverMenu.DefaultKey) shows the
right-click menu on the selected glyph(Coming soon — will use a dedicated design-mode runner that instantiates
a single View with EnableForDesign() and records its interactions.)
For apps in Examples/ that are not UICatalog scenarios:
$dll = "./Examples/<AppName>/bin/Release/net10.0/<AppName>.dll"
$ks = 'wait:1000,<keystrokes>,Escape'
tuirec record `
--binary dotnet `
--args "$dll" `
--name <app-id> `
--title "<App Name> Demo" `
--keystrokes $ks `
--startup-delay 2000 `
--drain 2000 `
--cols 120 --rows 30 `
--open --copy
After every recording, verify:
tuirec record exited with code 0 and wrote both .gif and .cast.Select-String -Path artifacts/<name>.cast -Pattern "error|unknown|not found|usage:" -CaseSensitive:$false
--open flag) and confirm:
Examples/UICatalog/Scenarios/<ScenarioDir>/<ScenarioName>.gif
| Problem | Cause | Fix |
|---|---|---|
| Wide glyphs misaligned in GIF | Emoji/CJK chars are 2-cell wide; agg renders per-cell | Avoid emoji/CJK categories; use single-width ranges (Arrows, Box Drawing, etc.) |
Nav keys ignored with --kitty-keyboard | tuirec bug #54 — sends wrong codepoints | Remove --kitty-keyboard |
| App doesn't quit | Wrong quit key or key not delivered | Use Escape (the default quit key); check --kitty-keyboard interaction |
| Blank frames at start/end | Pre/postroll not trimmed | --trim is on by default in v0.4.2+; ensure tuirec is up-to-date |
| GIF validation: 1 frame | --trim removes all frames for static views | Use --trim=false for views with no visual change during demo |
| Recording times out | App stuck / wrong keystrokes | Check with --verbosity high, fix script |
--binary permission error | Relative path on Windows | Use ./ prefix or absolute path with forward slashes |
| Backtick text missing | PowerShell interpolation | Use single-quoted $ks variable |
When asked to record a scenario GIF:
dotnet build Examples/ScenarioRunner -c Releasedotnet run --project Examples/ScenarioRunner -c Release --no-build -- listGetDemoKeyStrokes() — find it in the scenario source filetuirec record --binary ... --args "run,<Name>" --keystrokes $ks ...--kitty-keyboard and retrytuirec agent-guide (embeds the complete reference)tuirec record --helpExamples/ScenarioRunner/ — CLI that runs individual UICatalog scenarios