# 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](https://github.com/sinelaw/fresh/pull/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/master` → `origin/master`
→ `master`. 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
```
