Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/demos/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ TUI demos (Zellij, Claude UI) can't use text snapshots because VHS only captures
4. Validation runs automatically when building TUI demos with defined checkpoints

Checkpoints are defined in `docs/demos/shared/validation.py`. To add validation to a TUI demo:
1. Identify key frame numbers by examining the GIF (30fps, so frame 90 = 3 seconds)
1. Identify key frame numbers by examining the GIF (25fps, so frame 75 = 3 seconds; a negative frame counts back from the last, for the state a recording ends in)
2. Define checkpoint patterns in `validation.py` with frame numbers, expected patterns, and forbidden patterns

`wt-switch`, `wt-statusline`, and `wt-zellij-omnibus` have checkpoints. Other TUI demos are skipped until checkpoints are added.
Expand Down Expand Up @@ -208,7 +208,7 @@ Each theme starts from a freshly prepared demo environment because recording a t

Social build generates light only (social media doesn't support theme-switching media queries).

Theme definitions are in `docs/demos/shared/themes.py`, matching the `--wt-*` custom properties in `docs/src/styles/custom.css`.
Each recording's environment carries its theme. The VHS terminal, Zellij, and the starship prompt take their colors from `docs/demos/shared/themes.py`, which reads the `--wt-*` custom properties in `docs/src/styles/custom.css` when the build runs, so a site palette change reaches the GIFs on the next recording. Claude Code and delta switch to their own light or dark theme.

## Debugging a demo environment

Expand Down
37 changes: 19 additions & 18 deletions docs/demos/build
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ from __future__ import annotations

import argparse
import os
import shutil
import subprocess
import sys
import tempfile
Expand All @@ -30,7 +29,6 @@ SCRIPT_DIR = Path(__file__).parent
sys.path.insert(0, str(SCRIPT_DIR))

from shared import ( # noqa: E402
FIXTURES_DIR,
VALIDATION_RS,
DemoEnv,
DemoSize,
Expand All @@ -43,7 +41,9 @@ from shared import ( # noqa: E402
check_dependencies,
check_ffmpeg_libass,
setup_demo_output,
write_starship_config,
record_theme,
recorder_env,
SIZE_DOCS,
SIZE_DOCS_MOBILE,
SIZE_SOCIAL,
Expand Down Expand Up @@ -73,18 +73,20 @@ CLAUDE_DEMOS = {"wt-switch", "wt-statusline", "wt-zellij-omnibus"}


@contextmanager
def prepared_demo_env(output_name: str, setup_func):
def prepared_demo_env(output_name: str, theme: str, setup_func):
"""Create a prepared demo environment and remove it after use."""
with tempfile.TemporaryDirectory(
prefix="wt-demo-", ignore_cleanup_errors=True
) as demo_env_dir:
yield _prepare_demo_env(Path(demo_env_dir), output_name, setup_func)
yield _prepare_demo_env(Path(demo_env_dir), output_name, theme, setup_func)


def _prepare_demo_env(out_dir: Path, output_name: str, setup_func) -> DemoEnv:
def _prepare_demo_env(
out_dir: Path, output_name: str, theme: str, setup_func
) -> DemoEnv:
"""Prepare a demo environment at an existing directory."""
setup_demo_output(out_dir)
env = DemoEnv(name=output_name, out_dir=out_dir, repo_name="acme")
env = DemoEnv(name=output_name, out_dir=out_dir, theme=theme, repo_name="acme")
setup_demo_output(env)
setup_func(env)
return env

Expand Down Expand Up @@ -379,16 +381,17 @@ def record_demo(
if not tape_path.exists():
raise FileNotFoundError(f"{tape_file} not found")

# Snapshot and text output carry no colors, so they use the first theme.
if snapshot_mode:
with prepared_demo_env(output_name, setup_func) as env:
with prepared_demo_env(output_name, themes[0], setup_func) as env:
output_snap = SNAPSHOTS_DIR / f"{output_name}.snap"
record_snapshot(env, tape_path, output_snap, REPO_ROOT)
elapsed = time.monotonic() - t0
print(f"✓ {output_name}: Snapshot saved ({elapsed:.0f}s)")
return elapsed

if text_mode:
with prepared_demo_env(output_name, setup_func) as env:
with prepared_demo_env(output_name, themes[0], setup_func) as env:
replacements = build_tape_replacements(env, REPO_ROOT)
output_txt = mode_out_dir / f"{output_name}.txt"
record_text(env, tape_path, output_txt, replacements, REPO_ROOT)
Expand All @@ -405,11 +408,10 @@ def record_demo(
for theme, output_gif in output_gifs.items():
# Every tape mutates its repository. A fresh environment keeps one
# theme's commands from changing the starting state of the next theme.
with prepared_demo_env(output_name, setup_func) as env:
with prepared_demo_env(output_name, theme, setup_func) as env:
record_theme(
env,
tape_path,
theme,
output_gif,
REPO_ROOT,
vhs_binary=vhs_binary,
Expand Down Expand Up @@ -449,15 +451,14 @@ def record_demo(
# =============================================================================


def spawn_debug_shell(env: DemoEnv, starship_config: Path):
def spawn_debug_shell(env: DemoEnv):
"""Spawn an interactive fish shell with the demo environment set up."""
shell_env = os.environ.copy()
shell_env.pop("CLAUDE_CONFIG_DIR", None)
shell_env = recorder_env()
shell_env.update(
{
"HOME": str(env.home),
"XDG_CONFIG_HOME": str(env.home / ".config"),
"STARSHIP_CONFIG": str(starship_config),
"STARSHIP_CONFIG": str(env.starship_config),
"PATH": f"{REPO_ROOT / 'target' / 'debug'}:{env.home / '.local' / 'bin'}:{os.environ.get('PATH', '')}",
# Keep real rustup/cargo for any rust tools
"RUSTUP_HOME": str(Path.home() / ".rustup"),
Expand Down Expand Up @@ -610,7 +611,7 @@ def build_target(target: str, args, vhs_binary: str) -> list[str]:
mode_out_dir.mkdir(parents=True, exist_ok=True)

if target == "docs":
shutil.copy(FIXTURES_DIR / "starship.toml", mode_out_dir / "starship.toml")
write_starship_config(mode_out_dir / "starship.toml", "light")

target_config = TARGETS[target]
demos = target_config["demos"]
Expand Down Expand Up @@ -642,9 +643,9 @@ def build_target(target: str, args, vhs_binary: str) -> list[str]:
if args.shell:
tape_file, output_name, setup_func = demos[0]
debug_dir = Path(tempfile.mkdtemp(prefix="wt-demo-"))
env = _prepare_demo_env(debug_dir, output_name, setup_func)
env = _prepare_demo_env(debug_dir, output_name, themes[0], setup_func)
print(f"Demo repo: {env.repo}")
spawn_debug_shell(env, FIXTURES_DIR / "starship.toml")
spawn_debug_shell(env)
return []

# TUI demos run sequentially (they use Zellij which conflicts when parallel)
Expand Down
4 changes: 4 additions & 0 deletions docs/demos/shared/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@
check_dependencies,
check_ffmpeg_libass,
setup_demo_output,
write_starship_config,
record_theme,
recorder_env,
# Text output recording
record_text,
build_tape_replacements,
Expand Down Expand Up @@ -66,7 +68,9 @@
"check_dependencies",
"check_ffmpeg_libass",
"setup_demo_output",
"write_starship_config",
"record_theme",
"recorder_env",
# Text output recording
"record_text",
"build_tape_replacements",
Expand Down
21 changes: 6 additions & 15 deletions docs/demos/shared/fixtures/starship.toml
Original file line number Diff line number Diff line change
@@ -1,19 +1,10 @@
format = "$directory$character"
palette = "gh_light"

[palettes.gh_light]
fg = "#1f2328"
bg = "#ffffff"
blue = "#0969da"
yellow = "#d29922"
green = "#2ea043"
red = "#d73a49"
muted = "#57606a"
soft = "#8b7355"
gold = "#d97706"
# setup_demo_output appends [palettes.site] with the site's colors for the
# recording's theme.
palette = "site"

[directory]
style = "bold fg:soft"
style = "bold fg:craft"
truncation_length = 3
truncate_to_repo = true
home_symbol = "~"
Expand All @@ -40,9 +31,9 @@ format = " [$duration]($style)"
style = "fg:muted"

[character]
success_symbol = "[❯](fg:soft)"
success_symbol = "[❯](fg:craft)"
error_symbol = "[❯](fg:red)"
vicmd_symbol = "[❮](fg:soft)"
vicmd_symbol = "[❮](fg:craft)"

[time]
disabled = true
Loading
Loading