Back to Fresh

Theme screenshot diff — before/after gallery for theme PRs

docs/theme-screenshot-diff.md

0.4.16.4 KB
Original Source

Theme screenshot diff — before/after gallery for theme PRs

A test + CI job that renders before vs. after screenshots whenever a pull request changes a built-in theme, so reviewers can see exactly what a color tweak does instead of eyeballing RGB triples in a JSON diff (e.g. #2372, an APCA-contrast audit that touched seven themes).

Objectives

  1. Make theme diffs reviewable visually. For every theme JSON a PR changes, produce side-by-side screenshots of the editor rendered with the base (pre-change) colors and the PR (post-change) colors, across the UI surfaces a theme actually controls (syntax, selection, search, palette, explorer, splits, diagnostics, menus, help, settings, diffs, scrollbar, whitespace).
  2. Run unattended in CI on GitHub. The gallery is generated by a normal cargo test and surfaced as a downloadable build artifact on the PR's checks — no manual steps, no repo writes, no comment spam.
  3. Self-contained. A single test run produces the full before/after comparison; it reads the baseline straight from git, so there is no two-checkout dance in the workflow.
  4. Reuse the showcase machinery. Build on the existing theme_screenshot_gallery scene suite and BlogShowcase SVG renderer rather than inventing a second capture path.
  5. Zero cost on the normal suite. The diff test is #[ignore]d so it never runs (or slows) the default cargo nextest job; it only runs in its dedicated, path-filtered workflow.

How "before" and "after" are obtained

Themes ship as JSON under crates/fresh-editor/themes/*.json and are embedded at compile time, but the editor also loads themes at runtime from a themes_dir. The test exploits this:

  • After = the theme JSON in the working tree (what the PR proposes).
  • Before = the same path at the base ref, read via git show <base-ref>:crates/fresh-editor/themes/<name>.json.

Each version is written into a throw-away themes_dir under a unique name and selected through config.theme, so one process can render both versions with no recompile and no global state.

Base ref resolution (first that works): FRESH_THEME_BASE_REF env → git merge-base HEAD origin/masterorigin/mastermaster. If none resolve (e.g. shallow clone with no base), the test prints a notice and renders after only.

Test behavior / acceptance criteria

The test is theme_diff_gallery (crates/fresh-editor/tests/e2e/theme_screenshots.rs, #[ignore]).

  • Scope. By default it processes only themes whose JSON differs from the base ref. FRESH_THEME_DIFF_THEMES=dark,nord restricts to a list; FRESH_THEME_DIFF_ALL=1 forces all built-in themes.
  • Added themes (present in the PR, absent at base) render after only, with the before column labelled "new theme — no baseline".
  • Unchanged themes are skipped (unless FRESH_THEME_DIFF_ALL=1).
  • Output lands in docs/blog/theme-diff/ (git-ignored):
    • docs/blog/theme-diff/index.html — top-level index linking every processed theme and how many color keys changed.
    • docs/blog/theme-diff/<theme>/index.html — a table of the changed color keys (old → new) followed by every captured frame as a before | after pair.
    • docs/blog/theme-diff/<theme>/{before,after}/ — the raw SVG frames + showcase.json metadata for each side.
  • UI coverage. Both sides render a suite of real editor surfaces — not synthetic swatches — so reviewers see colors in context. The scenes: syntax-highlighted code (with a vertical ruler), selection, multi-cursor, find, command palette, file explorer, split view, inline diagnostics (theme-keyed overlays + inline text), open menu dropdown, help overlay, settings UI, diff highlights (theme-keyed), scrollbar, whitespace, find & replace toolbar, go-to-line prompt, keybinding editor, completion popup, reference/semantic highlight, and the integrated terminal (PTY-guarded). Two further scenes spin up their own harness with the same theme: a git file-status explorer (real git repo + git_explorer plugin → file_status_* colors) and an LSP status indicator (fake LSP server → status_lsp_on_*). Best-effort scenes (git/LSP/terminal) skip or snap-whatever-rendered on both sides identically, so frames always stay aligned. Diagnostics/diff overlays use theme-keyed OverlayFace::Style, so those colors come from the theme, not fixed RGB.
  • Completeness backstop. The per-theme changed-keys table lists every key the PR changed (with swatches), so a key that no scene happens to show in context is still surfaced explicitly.
  • Cost. A theme renders in ~10-12s (before + after). Every wait uses wait_until, which returns the instant its condition holds (git status, LSP "on" pill, and terminal pane each settle in tens of ms) — no fixed sleeps or polling budgets. The bulk is the ~4s/side of real keystroke + render work across the scene suite. The run scales linearly with the number of changed themes; the CI job and render step are time-boxed so a stuck wait_until fails fast instead of hanging.
  • Determinism. Both sides run the identical scene sequence at the same terminal size, so frame indices line up one-to-one for pairing.
  • Robustness. Missing git, a missing base file, or an unparseable baseline degrade to a printed notice + partial output; the test never panics on environmental gaps. It does fail loudly if a theme version it wrote cannot be re-selected (guards against silent fallback to the default theme).
  • Isolation. No reliance on the user's ~/.config; every theme is loaded from a per-run temp dir.

CI integration

.github/workflows/theme-screenshots.yml:

  • Triggers on pull_request events whose changes touch crates/fresh-editor/themes/**.
  • Fetches the PR base, exports FRESH_THEME_BASE_REF, then runs the test with --run-ignored ignored-only.
  • Uploads docs/blog/theme-diff/ as an artifact and writes a short summary (themes changed, link to the artifact) to the job summary.

Running locally

sh
# Only the themes you changed vs. origin/master:
scripts/generate-theme-diff.sh

# A specific theme, against an explicit base:
FRESH_THEME_BASE_REF=origin/master FRESH_THEME_DIFF_THEMES=dark \
  scripts/generate-theme-diff.sh

# then open docs/blog/theme-diff/index.html