Compare commits

...
Author SHA1 Message Date
Stefan HallerandClaude Opus 5 3889653d1b Add a script for re-recording every demo a page embeds
Changing demo/settings.tape, or anything about how lazygit looks, dates
every recording at once, and re-recording them one at a time means
running the recorder fifteen times and pasting fifteen new URLs.
Attachment URLs say nothing about where they came from, so there is also
nothing to tell you which demo a video in the README is of.

Name the demo in a comment above each video. GitHub drops the comment
when it renders the page, so it costs the reader nothing, and it gives
us a way back from a page to the demo that produced it. Then walk those
comments, re-record each demo and rewrite the URL below it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-22 22:07:30 +02:00
Stefan HallerandClaude Opus 5 b4e70420cc amend! Record demos with vhs and upload them to GitHub's attachment store
Record demos with vhs and publish them to GitHub's attachment store

The demo gifs in the README start on their own, loop without telling the
reader where a run begins, and give no way to pause, seek or replay. A
video with the browser's own controls fixes all three, but GitHub plays
a video in a README only when it is served from its own attachment
store. If you commit one to the assets branch and link it the way we
link the images, GitHub drops the whole <video> element when it renders
the page.

Replace terminalizer with vhs. vhs records the demo straight to mp4
rather than going through a gif, and it takes the terminal size, font
and colours from demo/settings.tape. Then upload the result from the
endpoint that GitHub's own drag-and-drop upload posts to, and print the
tag to paste into the page.

Uploading alone is not enough. An attachment is readable only by people
who are signed in to GitHub until a posted comment in the repository
refers to it, and a README on a branch does not count. So post each
recording to a collecting issue and wait until the video can be fetched
without a token. Miss that step and the video plays for whoever recorded
it and 404s for every other reader.

Pad the bottom of the frame as well, because the browser draws its
playback controls over the video and they are tall enough to cover the
line where the demos put their captions.

Pass --no-upload while you are still working on how a demo looks. That
writes the video to demo/output and stops, so trying out a colour or a
font costs nothing but the recording itself.

The recording is sharper and smaller than the gif it replaces. It runs
at 1866x1230 and 25 fps for 306K, against 1140x828 and about 5 fps for
461K. It also costs the reader nothing until they press play, whereas
the gifs are fetched every time the README is opened.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-22 22:07:07 +02:00
Stefan Haller 7072461d8a fixup! Show the commit_and_push demo in the README as a video 2026-09-22 22:06:49 +02:00
Stefan HallerandClaude Opus 5 8c1852e074 amend! Record demos with vhs and upload them to GitHub's attachment store
Record demos with vhs and publish them to GitHub's attachment store

The demo gifs in the README start on their own, loop without telling the
reader where a run begins, and give no way to pause, seek or replay. A
video with the browser's own controls fixes all three, but GitHub plays
a video in a README only when it is served from its own attachment
store. If you commit one to the assets branch and link it the way we
link the images, GitHub drops the whole <video> element when it renders
the page.

Replace terminalizer with vhs. vhs records the demo straight to mp4
rather than going through a gif, and it takes the terminal size, font
and colours from demo/settings.tape. Then upload the result from the
endpoint that GitHub's own drag-and-drop upload posts to, and print the
tag to paste into the page.

Uploading alone is not enough. An attachment is readable only by people
who are signed in to GitHub until a posted comment in the repository
refers to it, and a README on a branch does not count. So post each
recording to a collecting issue and wait until the video can be fetched
without a token. Miss that step and the video plays for whoever recorded
it and 404s for every other reader.

Pad the bottom of the frame as well, because the browser draws its
playback controls over the video and they are tall enough to cover the
line where the demos put their captions.

The recording is sharper and smaller than the gif it replaces. It runs
at 1866x1290 and 25 fps for 304K, against 1140x828 and about 5 fps for
461K. It also costs the reader nothing until they press play, whereas
the gifs are fetched every time the README is opened.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-22 21:37:45 +02:00
Stefan Haller 84dd4b41d7 fixup! Show the commit_and_push demo in the README as a video 2026-09-22 20:48:56 +02:00
Stefan Haller 2fe4905817 fixup! Record demos with vhs and upload them to GitHub's attachment store 2026-09-22 20:48:56 +02:00
Stefan Haller bcde5e68cb fixup! Record demos with vhs and upload them to GitHub's attachment store 2026-09-22 20:35:30 +02:00
Stefan HallerandClaude Opus 5 1027c1cd92 Show the commit_and_push demo in the README as a video
Readers can pause, seek and replay it now, and it no longer starts on
its own.

The other demos stay as gifs until they are re-recorded.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-22 20:32:07 +02:00
Stefan HallerandClaude Opus 5 efdf8b360c Record demos with vhs and upload them to GitHub's attachment store
The demo gifs in the README start on their own, loop without telling the
reader where a run begins, and give no way to pause, seek or replay. A
video with the browser's own controls fixes all three, but GitHub plays
a video in a README only when it is served from its own attachment
store. If you commit one to the assets branch and link it the way we
link the images, GitHub drops the whole <video> element when it renders
the page.

Replace terminalizer with vhs. vhs records the demo straight to mp4
rather than going through a gif, and it takes the terminal size, font
and colours from demo/settings.tape. Then upload the result from the
endpoint that GitHub's own drag-and-drop upload posts to, and print the
tag to paste into the page.

The recording is sharper and smaller than the gif it replaces. It runs
at 1866x1140 and 25 fps for 297K, against 1140x828 and about 5 fps for
461K. It also costs the reader nothing until they press play, whereas
the gifs are fetched every time the README is opened.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-22 20:32:07 +02:00
Stefan HallerandClaude Opus 5 2c74c8d6d3 Give the tests a visible inactive border colour
The integration test config asks for `black` inactive borders. In a
recording that comes out as a mid grey, because the recording theme
remaps the terminal's black to #7a7a7a. In an ordinary terminal it is
real black, so when you watch a test with `just e2e-cli` the inactive
frames all but disappear against the background.

Name the grey directly instead (but a little bit brighter than the #7a
we had before), so that the frames look the same either way.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-22 20:32:07 +02:00
10 changed files with 422 additions and 185 deletions
+4
View File
@@ -68,6 +68,10 @@ bump-lazycore:
record-demo:
demo/record_demo.sh $(filter-out $@,$(MAKECMDGOALS))
.PHONY: rerecord-demos
rerecord-demos:
demo/rerecord_demos.sh $(filter-out $@,$(MAKECMDGOALS))
.PHONY: vendor
vendor:
go mod tidy && go mod vendor
+2 -1
View File
@@ -47,7 +47,8 @@ A simple terminal UI for git commands
[![GitHub Releases](https://img.shields.io/github/downloads/jesseduffield/lazygit/total)](https://github.com/jesseduffield/lazygit/releases) [![Go Report Card](https://goreportcard.com/badge/github.com/jesseduffield/lazygit)](https://goreportcard.com/report/github.com/jesseduffield/lazygit) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/f46416b715d74622895657935fcada21)](https://app.codacy.com/gh/jesseduffield/lazygit/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade) [![Codacy Badge](https://app.codacy.com/project/badge/Coverage/f46416b715d74622895657935fcada21)](https://app.codacy.com/gh/jesseduffield/lazygit/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_coverage) [![golangci-lint](https://img.shields.io/badge/linted%20by-golangci--lint-brightgreen)](https://golangci-lint.run/) [![GitHub tag](https://img.shields.io/github/v/tag/jesseduffield/lazygit?color=blue)](https://github.com/jesseduffield/lazygit/releases/latest) [![homebrew](https://img.shields.io/homebrew/v/lazygit?color=blue)](https://formulae.brew.sh/formula/lazygit)
![commit_and_push](../assets/demo/commit_and_push-compressed.gif)
<!-- demo: commit_and_push -->
<video src="https://github.com/user-attachments/assets/a1f28c26-7545-4ba8-ae79-b37f848b1b8e" controls></video>
</div>
-112
View File
@@ -1,112 +0,0 @@
# Specify a command to be executed
# like `/bin/bash -l`, `ls`, or any other commands
# the default is bash for Linux
# or powershell.exe for Windows
command: echo "YOU NEED TO SPECIFY YOUR OWN COMMAND WITH THE -d ARG"
# Specify the current working directory path
# the default is the current working directory path
cwd: null
# Export additional ENV variables
env:
recording: true
# Explicitly set the number of columns
# or use `auto` to take the current
# number of columns of your shell
cols: 120 # 100
# Explicitly set the number of rows
# or use `auto` to take the current
# number of rows of your shell
rows: 35 # 30
# Amount of times to repeat GIF
# If value is -1, play once
# If value is 0, loop indefinitely
# If value is a positive number, loop n times
repeat: 0
# Quality
# 1 - 100
# Higher quality seems to make no difference, but running it through
# gifsicle ends up with a much better compressed version.
quality: 100
# Delay between frames in ms
# If the value is `auto` use the actual recording delays
frameDelay: auto
# Maximum delay between frames in ms
# Ignored if the `frameDelay` isn't set to `auto`
# Set to `auto` to prevent limiting the max idle time
maxIdleTime: 2000
# The surrounding frame box
# The `type` can be null, window, floating, or solid`
# To hide the title use the value null
# Don't forget to add a backgroundColor style with a null as type
frameBox:
type: floating
title: Lazygit
style:
border: 0px black solid
backgroundColor: "#1d1d1d"
margin: -5px
# Add a watermark image to the rendered gif
# You need to specify an absolute path for
# the image on your machine or a URL, and you can also
# add your own CSS styles
watermark:
imagePath: null
style:
position: absolute
right: 15px
bottom: 15px
width: 100px
opacity: 0.9
# Cursor style can be one of
# `block`, `underline`, or `bar`
cursorStyle: block
# Font family
# You can use any font that is installed on your machine
# in CSS-like syntax
# Download from:
# https://github.com/ryanoasis/nerd-fonts/releases/download/v3.0.2/DejaVuSansMono.zip
# Not using the mono font because it makes icons too small.
fontFamily: "DejaVuSansM Nerd Font"
# The size of the font
fontSize: 8
# The height of lines
lineHeight: 1
# The spacing between letters
letterSpacing: 0
# Theme
theme:
background: "transparent"
foreground: "#dddad6"
cursor: "#c7c7c7"
black: "#7a7a7a"
red: "#fc4384"
green: "#b3e33b"
yellow: "#ffa727"
blue: "#102895"
magenta: "#c930c7"
cyan: "#00c5c7"
white: "#c7c7c7"
brightBlack: "#676767"
brightRed: "#ff7fac"
brightGreen: "#c8ed71"
brightYellow: "#ebdf86"
brightBlue: "#6871ff"
brightMagenta: "#ff76ff"
brightCyan: "#5ffdff"
brightWhite: "#fffefe"
+172 -46
View File
@@ -2,55 +2,72 @@
set -e
TYPE=$1
TEST=$2
# The repository the demo is uploaded to. GitHub only plays videos that live in
# its own attachment store, and an attachment is tied to one repository.
REPO=jesseduffield/lazygit
# The issue that collects the demo recordings. Posting a comment there is what
# makes an uploaded video readable by people who are not signed in to GitHub.
# It can stay closed; commenting on a closed issue publishes the video just as
# well, and does not reopen it.
PUBLISH_ISSUE=6051
usage() {
echo "Usage: $0 [gif|mp4] <test path>"
echo "e.g. using full path: $0 gif pkg/integration/tests/demo/nuke_working_tree.go"
echo "Usage: $0 [--no-upload] <test path>"
echo "e.g. $0 pkg/integration/tests/demo/nuke_working_tree.go"
echo
echo "--no-upload leaves the video in demo/output and stops there, for"
echo "checking how a change to demo/settings.tape turns out."
exit 1
}
if [ "$#" -ne 2 ]
UPLOAD=true
if [ "$1" = "--no-upload" ]
then
UPLOAD=false
shift
fi
TEST=$1
if [ "$#" -ne 1 ]
then
usage
fi
if [ "$TYPE" != "gif" ] && [ "$TYPE" != "mp4" ]
TOOLS="vhs ttyd ffmpeg"
if [ "$UPLOAD" = true ]
then
usage
exit 1
TOOLS="$TOOLS gh"
fi
if [ -z "$TEST" ]
for TOOL in $TOOLS
do
if ! command -v "$TOOL" > /dev/null 2>&1
then
echo "$TOOL could not be found"
echo "Install it with: brew install $TOOL"
exit 1
fi
done
if [ "$UPLOAD" = true ]
then
usage
fi
WORKTREE_PATH=$(git worktree list | grep assets | awk '{print $1}')
WORKTREE_PATH=$(git worktree list | grep assets | awk '{print $1}')
if [ -z "$WORKTREE_PATH" ]
then
echo "Could not find assets worktree. You'll need to create a worktree for the assets branch using the following command:"
echo "git worktree add .worktrees/assets assets"
echo "The assets branch has no shared history with the main branch: it exists to store assets which are too large to store in the main branch."
exit 1
fi
if [ -z "$WORKTREE_PATH" ]
then
echo "Could not find assets worktree. You'll need to create a worktree for the assets branch using the following command:"
echo "git worktree add .worktrees/assets assets"
echo "The assets branch has no shared history with the main branch: it exists to store assets which are too large to store in the main branch."
exit 1
fi
OUTPUT_DIR="$WORKTREE_PATH/demo"
if ! command -v terminalizer &> /dev/null
then
echo "terminalizer could not be found"
echo "Install it with: npm install -g terminalizer"
exit 1
fi
if ! command -v "gifsicle" &> /dev/null
then
echo "gifsicle could not be found"
echo "Install it with: npm install -g gifsicle"
exit 1
OUTPUT_DIR="$WORKTREE_PATH/demo"
else
OUTPUT_DIR=demo/output
fi
# Get last part of the test path and set that as the output name
@@ -63,19 +80,128 @@ go generate pkg/integration/tests/tests.go
mkdir -p "$OUTPUT_DIR"
# First we record the demo into a yaml representation
terminalizer -c demo/config.yml record --skip-sharing -d "go run cmd/integration_test/main.go cli --slow $TEST" "$OUTPUT_DIR/$NAME"
# Then we render it into a gif
terminalizer render "$OUTPUT_DIR/$NAME" -o "$OUTPUT_DIR/$NAME.gif"
SCRATCH=$(mktemp -d)
trap 'rm -rf "$SCRATCH"' EXIT
# Then we convert it to either an mp4 or gif based on the command line argument
if [ "$TYPE" = "mp4" ]
TAPE="$SCRATCH/$NAME.tape"
RECORDING="$SCRATCH/$NAME.mp4"
OUTPUT="$OUTPUT_DIR/$NAME.mp4"
# The two quotes in the marker keep the literal VHSDONE out of the command line
# that stays on screen while we wait for the marker to be printed.
cat > "$TAPE" <<EOF
Output "$RECORDING"
Source demo/settings.tape
# The command is typed while the recording is hidden, so there is nothing to
# gain from animating it.
Set TypingSpeed 0ms
Hide
Type "go run cmd/integration_test/main.go cli --slow $TEST; echo VHS''DONE"
Enter
Wait+Screen@180s /Local branches/
Show
Wait+Screen@600s /VHSDONE/
Hide
EOF
vhs "$TAPE"
if [ ! -f "$RECORDING" ]
then
COMPRESSED_PATH="$OUTPUT_DIR/$NAME.mp4"
ffmpeg -y -i "$OUTPUT_DIR/$NAME.gif" -movflags faststart -pix_fmt yuv420p -vf "scale=trunc(iw/2)*2:trunc(ih/2)*2" "$COMPRESSED_PATH"
else
COMPRESSED_PATH="$OUTPUT_DIR/$NAME-compressed.gif"
gifsicle --colors 256 --use-col=web -O3 < "$OUTPUT_DIR/$NAME.gif" > "$COMPRESSED_PATH"
echo "vhs recorded the demo but wrote no video."
echo "vhs 0.12.0 does this; see https://github.com/charmbracelet/vhs/issues/787."
echo "Install a working version with: go install github.com/charmbracelet/vhs@v0.11.0"
exit 1
fi
echo "Demo recorded to $COMPRESSED_PATH"
# The browser draws its playback controls over the bottom of the video, and
# they are tall enough to hide lazygit's caption line. Pad the frame so that
# the caption sits above them. Chrome draws the tallest bar of the three, and
# at the width a README gives the video its buttons start to overlap the
# caption below about 90px, so there is not much room to trim here. Measure it
# again if demo/settings.tape changes the size of the recording.
CAPTION_CLEARANCE=90
BACKGROUND=$(sed -n 's/.*"background": *"\(#[0-9a-fA-F]*\)".*/\1/p' demo/settings.tape)
if [ -z "$BACKGROUND" ]
then
echo "Could not read the background colour from demo/settings.tape"
exit 1
fi
# Hold the last frame for a moment so that the end state stays readable, and
# move the moov atom to the front so that the video starts playing before it
# has fully downloaded.
ffmpeg -y -loglevel error -i "$RECORDING" \
-vf "tpad=stop_mode=clone:stop_duration=1.2,pad=iw:ih+$CAPTION_CLEARANCE:0:0:color=$BACKGROUND" \
-c:v libx264 -crf 23 -preset slow -pix_fmt yuv420p \
-movflags +faststart -an "$OUTPUT"
if [ "$UPLOAD" = false ]
then
echo "Demo recorded to $OUTPUT"
exit 0
fi
# GitHub's web editor posts to this endpoint when you drag a file into a
# comment box. It is undocumented, but it accepts an ordinary token, so we can
# upload from here. You need push access to $REPO for it to work. If the
# endpoint ever goes away, drag the video into a comment box on github.com
# instead and copy the URL that GitHub inserts.
REPOSITORY_ID=$(gh api "repos/$REPO" --jq .id)
RESPONSE=$(curl --silent --show-error --fail \
--request POST \
--header "Authorization: Bearer $(gh auth token)" \
--header "Accept: application/json" \
--header "Content-Type: video/mp4" \
--data-binary "@$OUTPUT" \
"https://uploads.github.com/user-attachments/assets?name=$NAME.mp4&content_type=video%2Fmp4&repository_id=$REPOSITORY_ID")
URL=$(echo "$RESPONSE" | sed -e 's/.*"url":"//' -e 's/".*//')
if [ -z "$URL" ]
then
echo "Could not read an attachment URL out of GitHub's response:"
echo "$RESPONSE"
exit 1
fi
# An attachment stays private until a posted comment somewhere in the
# repository refers to it. Until that happens the video is a 404 for anyone who
# is not signed in, and the README shows a broken player. Referring to it once
# makes it public for good, even if the comment is deleted afterwards, so we
# collect the recordings in one issue and leave the comments in place.
gh api "repos/$REPO/issues/$PUBLISH_ISSUE/comments" \
--raw-field "body=$NAME
$URL" > /dev/null
# Make sure that worked before handing over a URL, because the person recording
# the demo is signed in and will not see the failure.
ATTEMPT=0
while [ "$ATTEMPT" -lt 30 ]
do
if curl --silent --fail --output /dev/null --max-time 20 --range 0-1 "$URL"
then
break
fi
ATTEMPT=$((ATTEMPT + 1))
sleep 2
done
if [ "$ATTEMPT" -eq 30 ]
then
echo "$URL is still not readable without signing in to GitHub."
echo "Embedding it now would give logged-out readers a broken player."
exit 1
fi
echo "Demo recorded to $OUTPUT"
echo
echo "Embed it with:"
echo "<video src=\"$URL\" controls></video>"
+113
View File
@@ -0,0 +1,113 @@
#!/bin/sh
set -e
# Re-records the demos that a page embeds and points the page at the new
# videos. Use it when a change to demo/settings.tape, or to lazygit's
# appearance, leaves the existing recordings looking out of date.
#
# A page names the demo behind each video in a comment above it:
#
# <!-- demo: commit_and_push -->
# <video src="https://github.com/user-attachments/assets/..." controls></video>
#
# GitHub drops that comment when it renders the page, so it costs the reader
# nothing. This script re-records the demo the comment names and rewrites the
# URL on the line below it.
usage() {
echo "Usage: $0 [--no-upload] [page ...]"
echo "e.g. $0 README.md"
echo
echo "Re-records every demo the given pages embed. With no page given,"
echo "that means README.md."
echo
echo "--no-upload leaves the videos in demo/output and the pages untouched,"
echo "which is what you want for reviewing a change to demo/settings.tape."
exit 1
}
NO_UPLOAD=
if [ "$1" = "--no-upload" ]
then
NO_UPLOAD=--no-upload
shift
fi
if [ "$1" = "-h" ] || [ "$1" = "--help" ]
then
usage
fi
if [ ! -x demo/record_demo.sh ]
then
echo "Run this from the root of the repository."
exit 1
fi
if [ "$#" -eq 0 ]
then
set -- README.md
fi
for PAGE in "$@"
do
if [ ! -f "$PAGE" ]
then
echo "$PAGE does not exist"
exit 1
fi
NAMES=$(sed -n 's/^<!-- demo: \([A-Za-z0-9_]*\) -->$/\1/p' "$PAGE")
if [ -z "$NAMES" ]
then
echo "$PAGE embeds no demos. Each video needs a <!-- demo: <name> -->"
echo "comment on the line above it to say which demo it came from."
exit 1
fi
for NAME in $NAMES
do
TEST="pkg/integration/tests/demo/$NAME.go"
if [ ! -f "$TEST" ]
then
echo "$PAGE asks for a demo called $NAME, but $TEST does not exist"
exit 1
fi
echo
echo "=== $NAME ==="
if [ -n "$NO_UPLOAD" ]
then
demo/record_demo.sh --no-upload "$TEST"
continue
fi
# Keep the recording chatter on screen, since a full run takes a while,
# and read the new URL back out of it afterwards.
LOG=$(mktemp)
demo/record_demo.sh "$TEST" | tee "$LOG"
URL=$(sed -n 's/.*<video src="\([^"]*\)".*/\1/p' "$LOG")
rm -f "$LOG"
if [ -z "$URL" ]
then
echo "Recording $NAME produced no URL"
exit 1
fi
awk -v marker="<!-- demo: $NAME -->" -v url="$URL" '
hit { sub(/src="[^"]*"/, "src=\"" url "\""); hit = 0 }
$0 == marker { hit = 1 }
{ print }
' "$PAGE" > "$PAGE.new"
mv "$PAGE.new" "$PAGE"
echo "$PAGE now points at $URL"
done
done
+46
View File
@@ -0,0 +1,46 @@
# Terminal settings shared by all demo recordings. The tape that
# demo/record_demo.sh generates for a demo sources this file.
# Use the non-Mono Nerd Font variant; the Mono variant renders the icons too
# small.
Set FontFamily "SauceCodePro NF"
Set FontSize 24
Set LineHeight 1.0
Set Padding 20
# vhs sizes the terminal in pixels rather than in cells, so these two numbers
# are what produce a 120x35 grid at the font settings above. If you change the
# font, the font size or the padding, record a tape that runs `stty size` and
# adjust them until the grid is 120x35 again.
Set Width 1866
Set Height 1140
# There is no frame rate setting here because vhs ignores `Set Framerate` when
# it writes a video; it captures at 25 fps either way.
# The frame around the focused view is bold green, and xterm.js draws bold text
# in the bright variant of a colour, so brightGreen is the one those frames end
# up using.
Set Theme {
"background": "#1d1d1d",
"foreground": "#dddad6",
"cursor": "#c7c7c7",
"selection": "#44475a",
"black": "#7a7a7a",
"red": "#fc4384",
"green": "#3fb950",
"yellow": "#ffa727",
"blue": "#102895",
"magenta": "#c930c7",
"cyan": "#00c5c7",
"white": "#c7c7c7",
"brightBlack": "#676767",
"brightRed": "#ff7fac",
"brightGreen": "#56d364",
"brightYellow": "#ebdf86",
"brightBlue": "#6871ff",
"brightMagenta": "#ff76ff",
"brightCyan": "#5ffdff",
"brightWhite": "#fffefe"
}
+74 -25
View File
@@ -8,17 +8,18 @@ You'll want to familiarise yourself with how integration tests are written: see
Ideally we'd run this whole thing through docker but we haven't got that working. So you will need:
```
# for recording
npm i -g terminalizer
# for gif compression
npm i -g gifsicle
# for mp4 conversion
brew install ffmpeg
# for recording; vhs drives ttyd and ffmpeg under the hood
brew install ttyd ffmpeg
# vhs 0.12.0 runs the tape, reports success and writes no video at all
# (https://github.com/charmbracelet/vhs/issues/787), so pin the release
# before it
go install github.com/charmbracelet/vhs@v0.11.0
# font with icons
wget https://github.com/ryanoasis/nerd-fonts/releases/download/v3.0.2/DejaVuSansMono.tar.xz && \
tar -xf DejaVuSansMono.tar.xz -C /usr/local/share/fonts && \
rm DejaVuSansMono.tar.xz
wget https://github.com/ryanoasis/nerd-fonts/releases/download/v3.0.2/SourceCodePro.tar.xz && \
tar -xf SourceCodePro.tar.xz -C ~/Library/Fonts && \
rm SourceCodePro.tar.xz
```
## Creating a demo
@@ -49,34 +50,82 @@ The scripts and demo definitions live in the code branches but the output lives
git worktree add .worktrees/assets assets
```
Outputs will be stored in `.worktrees/assets/demos/`. We'll store three separate things:
* the yaml of the recording
* the original gif
* either the compressed gif or the mp4 depending on the output you chose (see below)
The mp4 of the recording will be stored in `.worktrees/assets/demo/`.
### Recording the demo
Once you're happy with your demo you can record it using:
```sh
scripts/record_demo.sh [gif|mp4] <path>
scripts/record_demo.sh <path>
# e.g.
scripts/record_demo.sh gif pkg/integration/tests/demo/interactive_rebase.go
scripts/record_demo.sh pkg/integration/tests/demo/interactive_rebase.go
```
~~The gif format is for use in the first video of the readme (it has a larger size but has auto-play and looping)~~
~~The mp4 format is for everything else (no looping, requires clicking, but smaller size).~~
The terminal size, font and colours live in `demo/settings.tape`, which the
script sources into the tape it generates for the demo.
Turns out that you can't store mp4s in a repo and link them from a README so we're gonna just use gifs across the board for now.
While you are still working on how a demo looks, pass `--no-upload`. That
leaves the video in `demo/output` (which is git-ignored) and stops there, so
you can watch it without uploading anything or touching the assets worktree:
```sh
scripts/record_demo.sh --no-upload pkg/integration/tests/demo/interactive_rebase.go
```
### Including demos in README/docs
If you've followed the above steps you'll end up with your output in your assets worktree.
Recording a demo does three things with the mp4: it writes it to your assets
worktree, it uploads a copy to GitHub's attachment store, and it posts that
copy as a comment on the issue named by `PUBLISH_ISSUE` in the script. Then it
prints the tag to embed:
Within that worktree, stage all three output files and raise a PR against the assets branch.
Then back in the code branch, in the doc, you can embed the recording like so:
```md
![Nuke working tree](../assets/demo/interactive_rebase-compressed.gif)
```html
<video src="https://github.com/user-attachments/assets/<uuid>" controls></video>
```
This means we can update assets without needing to update the docs that embed them.
GitHub plays a video in a README only when it is served from its own attachment
store. If you commit a video to the assets branch and link it the way we link
the images, GitHub drops the whole `<video>` element when it renders the page.
So the README reads the uploaded copy rather than the one in the assets
worktree. Keep that one anyway, so that we still have the file if we ever need
to upload it again. Stage it and raise a PR against the assets branch as you
would for any other asset.
Attachment URLs are opaque and have no path we can predict, so a new recording
of an existing demo means a new URL and an edit to the page that embeds it.
That comment on `PUBLISH_ISSUE` is not bookkeeping; it is what makes the video
watchable. An uploaded attachment is readable only by people signed in to
GitHub until some posted comment in the repository refers to it, and a README
on a branch does not count. Skip that step and the video plays for you and
404s for everyone else, which is easy to miss because you are signed in. The
script waits until the video can be fetched without a token before it prints
the tag. Referring to an attachment once is enough and cannot be undone, so
the comments could be deleted later, but leaving them gives us a dated list of
every recording.
Uploading needs push access to the lazygit repository. If you don't have it,
record the demo, then ask a maintainer to upload the mp4 for you.
### Re-recording every demo on a page
Write the name of the demo above each video, so that we can find our way from
a page back to the demo that produced it:
```html
<!-- demo: commit_and_push -->
<video src="https://github.com/user-attachments/assets/<uuid>" controls></video>
```
GitHub drops the comment when it renders the page. With it in place, a change
to `demo/settings.tape` or to lazygit's own appearance can be rolled out across
every recording at once:
```sh
scripts/rerecord_demos.sh
# or, to look before you upload anything:
scripts/rerecord_demos.sh --no-upload
```
That re-records every demo the README embeds and rewrites each URL in place.
Name other pages as arguments to do the same for them.
+4
View File
@@ -74,5 +74,9 @@ bump-gocui:
demo *args:
demo/record_demo.sh {{ args }}
# Re-record every demo the README embeds
rerecord-demos *args:
demo/rerecord_demos.sh {{ args }}
vendor:
go mod tidy && go mod vendor
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh
demo/rerecord_demos.sh "$@"
+4 -1
View File
@@ -8,8 +8,11 @@ gui:
activeBorderColor:
- green
- bold
# Not the "black" of the terminal palette: that is a real black in an
# ordinary terminal, which makes the frames all but invisible when you run
# a test with `just e2e-cli`.
inactiveBorderColor:
- black
- '#999999'
# Not important in tests but it creates clutter in demos
showRandomTip: false
animateExplosion: false # takes too long