Each release makes the demo video again, with the screenshots

A new website-media skill makes the screenshots and the website's demo video, says what to look at, and commits them: the screenshots in MeshTerm, the video in the site checkout. The release skill runs it after the bump and pushes the site before the tag, so the website job builds the page with the new video. The release preflight also reports the site checkout.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jean-Pierre MartineauandClaude Opus 5.5 committed 2026-10-17 10:06:27 -04:00
1 parent 507e3bd999
commit daf16505d7
4 files changed
+222 -30

No files matched your search

+24 -22
View File
@@ -135,30 +135,26 @@ alias to the gather step instead.
What *does* still carry the number: `docs/cli/README.md` has a sample document with a `"version"`
field, which is the live `{version}` placeholder and must show the new one.
### The screenshots — made again, after the bump
### The screenshots and the video — made again, after the bump
Every release refreshes the screenshots (JP, 2026-10-16: "make it so the images are
automatically updated when we release"). They show the version in their header, so this
runs **after** `__version__` changes and before the commit:
automatically updated when we release") and the website's demo video (JP, 2026-10-17).
Both show the version in their header, so they run **after** `__version__` changes and
before the commit: follow the **`website-media` skill in release mode**. It runs
`scripts/screenshots.py` (~1 min) then `scripts/video.py` (~4 min), and says what to look
at before anything is committed.
```
python scripts/screenshots.py
git status --short docs/screenshots
```
The two land in different repositories:
It drives `--demo` headless and rewrites `docs/screenshots/{terminal,cardputer-zero,picocalc-lyra}/`
(the README's four captures, and the website's three sets) and `docs/screenshots/splash/`
(the website's splash), about a minute in all. The demo is deterministic, so the only
changes should be the version in each header plus whatever the release changed on those
screens. Open one image of each set and look at it before committing: a screen that
changed in a way the changelog doesn't explain is a bug to understand, not a picture to
ship. The images ride in the release commit, so the tag carries them and the `website`
job builds meshterm.net from them.
- The screenshots (`docs/screenshots/`) ride in the release commit, so the tag carries
them and the `website` job builds meshterm.net from them.
- The video, its poster, and its play card go to the site checkout beside this one
(`../meshterm.net/src/`) and get a commit there ("The demo video of MeshTerm X.Y.Z"). The
`website` job builds from the site's `main`, so that commit is **pushed before the tag**
(step 5).
It needs the `dev` extras (Pillow, fontTools, time-machine), the emulator's Terminus font
(`meshterm emulate --fetch-fonts`, once), Windows' Cascadia Mono and Segoe UI fonts, and
the network for the map's tiles the first time. If it fails, stop and fix it, or ask JP
whether to release with last release's images — don't commit a half-written set.
If either fails, stop and fix it, or ask JP whether to release with last release's
images and video — don't commit a half-written set.
## 5. Commit, tag, push
@@ -178,12 +174,17 @@ hardcoded one. Seven release commits (0.3.1 through 0.10.0) went out without it
this line used to omit it.
Commit on `main` — never a topic branch. Then **confirm with JP before pushing**, because
the push is the act that builds and publishes the release:
the push is the act that builds and publishes the release. The site goes first, so the
`website` job finds the new video on its `main`:
```
git -C ../meshterm.net push origin main
git push origin main && git push origin vX.Y.Z
```
The one confirmation covers both pushes: say that the site push carries the new video
(and any other site commits that are ahead of origin, by subject) when you ask.
This is the one workflow where pushing is expected. It is still asked for, once, in that
moment — the standing rule against unasked pushes is not suspended by the skill, it is
satisfied by the answer.
@@ -196,8 +197,9 @@ gh release view vX.Y.Z
```
Five platform builds, the release job, and `website` — which rebuilds meshterm.net from the
tag so the site shows the new version and the screenshots of step 4 — so it takes a few
minutes. `website` runs the site repo's own `build.py`, so a change to the site's sources
tag so the site shows the new version, the screenshots, and the video of step 4 — so it
takes a few minutes. Once `website` has pushed its "MeshTerm X.Y.Z" commit, bring the
local site up to date: `git -C ../meshterm.net pull --ff-only`. `website` runs the site repo's own `build.py`, so a change to the site's sources
must be pushed to `meshterm.net` before the tag, or the job builds with the old ones. If only `website` fails,
the release itself is fine (it runs after publishing); the usual cause is the
`SITE_DEPLOY_KEY` secret, and once that is fixed `gh run rerun <run-id> --failed` finishes
+26 -6
View File
@@ -1,10 +1,11 @@
#!/usr/bin/env python3
"""Preflight for a MeshTerm release, and the answer to "what's in the next one?".
Reports the six things worth knowing before a version number is written down, and says
Reports the seven things worth knowing before a version number is written down, and says
nothing else: the current version and the tag it belongs to, the commits since, whether
there is an `[Unreleased]` section to rename, which changelog link refs are missing, and
whether the working tree is clean and level with `origin/main`.
there is an `[Unreleased]` section to rename, which changelog link refs are missing,
whether the working tree is clean and level with `origin/main`, and the same for the site
checkout beside it, where the release's demo video goes (the `website-media` skill).
It changes nothing. Every finding is a line a person acts on, not a gate that blocks.
"""
@@ -19,13 +20,14 @@ from pathlib import Path
ROOT = Path(__file__).resolve().parents[3]
INIT = ROOT / "meshterm" / "__init__.py"
CHANGELOG = ROOT / "CHANGELOG.md"
SITE = ROOT.parent / "meshterm.net"
OK, WARN, INFO = "ok ", "warn", " "
def git(*args: str) -> str:
"""Run a git command in the repo, returning stripped stdout ('' on failure)."""
done = subprocess.run(["git", "-C", str(ROOT), *args], capture_output=True, text=True)
def git(*args: str, repo: Path = ROOT) -> str:
"""Run a git command in ``repo``, returning stripped stdout ('' on failure)."""
done = subprocess.run(["git", "-C", str(repo), *args], capture_output=True, text=True)
return done.stdout.strip() if done.returncode == 0 else ""
@@ -115,6 +117,24 @@ def main() -> int:
behind, ahead = counts.split()
say(OK if behind == "0" else WARN, f"{ahead} ahead / {behind} behind origin/main")
print(f"\nSite ({SITE.name}, where the demo video goes)")
if not (SITE / ".git").exists():
say(WARN, f"no checkout at {SITE}: the video cannot be made")
else:
dirty = git("status", "--porcelain", "--", "src", repo=SITE)
count = len(dirty.splitlines())
say(OK if not dirty else WARN, "src/ clean" if not dirty else f"src/: {count} change(s)")
git("fetch", "--quiet", "origin", repo=SITE)
counts = git("rev-list", "--left-right", "--count", "origin/main...HEAD", repo=SITE)
if counts:
behind, ahead = counts.split()
say(OK if behind == "0" else WARN, f"{ahead} ahead / {behind} behind origin/main")
if behind != "0":
say(INFO, "pull --ff-only before making the video")
# These go out with the release's site push, before the tag.
for subject in git("log", "--format=%s", "origin/main..HEAD", repo=SITE).splitlines():
say(INFO, f"ahead: {subject}")
print("\nRun the gates before tagging:")
print(" python -m pytest -q && ruff check . && ruff format --check .\n")
return 0
+168
View File
@@ -0,0 +1,168 @@
---
name: website-media
description: Make the website's demo video and screenshots again from the demo mesh — the terminal video on meshterm.net (scripts/video.py) and the twelve screenshots of the README and the website (scripts/screenshots.py) — check them, and commit them in both repositories. Use when JP asks to make, redo, refresh, or update the video, the demo recording, the screenshots, the website's images, or the website after a visible change; the release skill also runs it after each version bump.
---
# Making the website's video and screenshots
The two always go together (JP, 2026-10-17: "include it with the screenshot updates when we
update the website, manually or automatically when we release"). Both come from the demo
mesh (`meshterm --demo`), headless, and both show the version in their header, so a set
made before a bump is already stale.
| What | Script | Where it goes | Time |
|---|---|---|---|
| 12 screenshots + 2 splash wordmarks | `scripts/screenshots.py` | MeshTerm `docs/screenshots/` | ~1 min |
| The demo video, its poster, its play card | `scripts/video.py` | meshterm.net `src/demo.mp4`, `src/demo-poster.png`, `src/demo-play.png` | ~4 min |
**The video is terminal-only, on purpose** (JP, 2026-10-17: "it's marketing after all").
Don't offer handheld videos again.
**Why the video lives in the site repository and not in the tag:** it is ~4 MB, and it
changes at every release (the version is on screen). In MeshTerm, that would grow every
clone of the code by 4 MB a release. The site repository already keeps the copy it serves.
The cost is one more push at release time (see *Release mode*).
## Before you start
- The site checkout must sit beside this one, at `../meshterm.net`. The video script
writes there by default and stops if the folder is missing.
- The site must be in sync and clean where the video goes:
```
git -C ../meshterm.net fetch -q
git -C ../meshterm.net status -sb
```
If it is behind (the release workflow's `website` job pushes a "MeshTerm X.Y.Z" commit
each release), `git -C ../meshterm.net pull --ff-only`. If `src/demo*` has uncommitted
changes you didn't make, stop and ask.
- Needs: the `dev` extras (Pillow, fontTools, time-machine), the emulator's Terminus font
(`meshterm emulate --fetch-fonts`, once), Windows' Cascadia Mono and Segoe UI fonts, the
network for the map's tiles the first time, and ffmpeg with libx264 on the `PATH`.
## 1. Run both, one after the other, in the background
```
.venv/Scripts/python scripts/screenshots.py
.venv/Scripts/python scripts/video.py
```
One background command for both, in this order. **Never in parallel**, and don't run the
test suite beside them: the video's tour runs on the real clock and its painter uses every
core. Both are driven by waits on screen text, so a slow machine only makes them slower.
The video prints its length and size at the end (`recorded 118.9 s, 189 screens`, then
`demo.mp4 (4001 KB)`).
## 2. Look at what you made
A green run is not a correct picture. Check both:
- **Screenshots:** `git status --short docs/screenshots`, then open one image of each set.
Only the version in the header should change, plus whatever this change set out to
change on those screens. Anything else is a bug to understand, not a picture to ship.
- **Video length:** under **2:00**. The site's caption says "Under two minutes". If it
went over, shorten holds in `_tour` (`p.hold(...)`), never the scenes JP asked for.
- **Video content:** make a contact sheet and look at it:
```
ffmpeg -v error -y -i ../meshterm.net/src/demo.mp4 -vf "fps=1/4,scale=400:-1,tile=6x5" -frames:v 1 <scratchpad>/sheet.png
```
The tour, in order (JP chose each part; keep them all):
1. the main menu;
2. Chat → Public → the paths of Gabriel's message from Beloeil, selected 1, 2, 1, 2;
3. Chloé's message arrives live; we type "Hey, I wonder if @[Dave-Lachine] is coming
Saturday?" — the mention turns into a chip when the `]` is typed — and Dave answers
with a mention of us, so his message carries our white ★;
4. Trace target → Brossard, 2 bytes per hop (our node uses 3; a trace collapses it to
2), the "5 new records" dialog, then the per-hop table;
5. the map: pan, ^L to our node, zoom 10 → 16 until street names and buildings show;
6. Repeater admin on Mont-Royal: Read settings (the video jumps over most of it), then
Send advert;
7. the live feed, one packet opened, and back to a clean main menu.
Also open `src/demo-poster.png`: it must show the paths dialog with the **second** path
selected.
## 3. When it fails
`video.py` prints `the recording failed (1)` after a traceback. Read the traceback's last
line: a `TimeoutError` carries the whole screen as it was.
- **A wait never matched** (`'…' did not appear`): a UX string the tour waits for changed,
or a dialog the tour doesn't expect is in the way. The video's own history: the trace
once got a records dialog when the path width changed, and a menu filter "live" matched
Courier first. Fix the tour in `scripts/video.py`, not the app — unless the screen shows
an app bug, which then gets fixed and reported to JP.
- **The trace says "no reply":** the demo's route to that target includes a link the
world doesn't have. Brossard is the target because its route always works; see the
`_tour` comment before changing it.
- **To iterate on painting or encoding** without recording again, pass
`--recording <scratchpad>/rec.pickle`: the first run keeps the recording there, a later
run with the same path only paints and encodes. Never keep a recording across a change
to the app.
If you cannot make it work, stop and ask JP whether to keep last time's video. Never ship
half a set.
## 4. Commit
MeshTerm, by name (other sessions share the tree):
```
git add docs/screenshots
```
The site, the three source files only:
```
git -C ../meshterm.net add src/demo.mp4 src/demo-poster.png src/demo-play.png
git -C ../meshterm.net commit -m "The demo video of MeshTerm X.Y.Z" -m "Co-Authored-By: <the model running this> <noreply@anthropic.com>"
```
Then one of the two modes.
### Manual mode — JP asked for the website now
Commit the screenshots on their own in MeshTerm ("The screenshots show …", what changed).
Then rebuild the site so its `docs/` serves the new files:
```
cd ../meshterm.net && ../MeshTerm/.venv/Scripts/python build.py
```
`build.py` writes seven derived images again with new bytes even when nothing changed.
Unless the splash art changed (`docs/screenshots/splash/` in MeshTerm), restore them:
```
git -C ../meshterm.net checkout -- docs/assets/apple-touch-icon.png docs/assets/logo-96.png docs/assets/og.png docs/assets/social-preview.png docs/assets/splash-narrow.png docs/assets/splash.png docs/favicon.ico
```
Commit the built `docs/` with the source files (one commit). **Don't push**: tell JP the
site is committed and ahead of origin. The live site changes only when he pushes it. Note
that the screenshots the site shows come from the MeshTerm checkout, so they are the
working tree's, not a release's.
### Release mode — called from the release skill, after the bump
Don't build the site locally: the release workflow's `website` job builds it from the tag
and from the site's `main`. Commit the site's three source files as above, and the
screenshots ride in the release commit (the release skill stages `docs/screenshots`).
**The site's commit must be pushed before the tag**, or the job builds with last
release's video. The release skill's push step does it, in the same confirmed push. After
the job has run, `git -C ../meshterm.net pull --ff-only` picks up its "MeshTerm X.Y.Z"
commit.
## The one step no script can do
MeshTerm's README plays its own copy of the video: a `user-attachments` upload, because
GitHub plays nothing else in a README. GitHub has no API for that upload. After a new
video, say in one line that the README still plays the previous upload; never try to
upload it yourself. If JP drops `../meshterm.net/src/demo.mp4` into a GitHub comment box
and gives you the new URL, swap it in `README.md` (the bare `user-attachments` line near
the top). Its caption on the next line must then say the video comes from the demo mesh:
the one written for the first upload says "made on a real mesh", which is true only of
that upload.
+4 -2
View File
@@ -103,5 +103,7 @@ something slow: the tiles of the map, and most of the read of the repeater's set
Thus the video jumps over the wait.
Each run gives the same video to the eye, but not the same file, because the camera copies
the screen on the real clock. After a change to the screens, run the script, then rebuild
the website (refer to the README of the `meshterm.net` repository).
the screen on the real clock. Each release makes the video again, after the screenshots,
and pushes it to the website repository before the tag. After a change to the screens
between two releases, run the two scripts, then rebuild the website (refer to the README of
the `meshterm.net` repository).