Site Comparison Review Workflow

Generate comparison artifacts from a YAML site spec, serve them in the interactive review site, and inspect screenshots, CSS differences, notes, and export output.

Sections

Terminology & Glossary
📖 Documentation
Navigation
8 sectionsv0.1
📄 Site Comparison Review Workflow — glaze help site-comparison-workflow
site-comparison-workflow

Site Comparison Review Workflow

Generate comparison artifacts from a YAML site spec, serve them in the interactive review site, and inspect screenshots, CSS differences, notes, and export output.

Appreviewsite-comparisonvisual-regressionjavascript-verbsscreenshotsverbsserverepositoryspecFileoutDirdata-dir+2

A site comparison run has two responsibilities. First it captures evidence: browser screenshots, cropped regions, pixel diffs, computed CSS, bounds, attributes, and a root summary.json. Then it serves that evidence in the interactive review site so a human can compare screenshots, inspect CSS differences, leave notes, drop pins, and export a handoff document.

The important design choice is that comparison and review are separate steps. The JavaScript verb in examples/verbs/review-sweep.js opens pages and writes files. The built-in css-visual-diff serve command reads those files and displays them. This keeps browser automation, artifact generation, and human review independent. You can regenerate evidence without changing the review site, and you can reopen a completed run without re-running Chromium.

What the Workflow Produces

A complete run is a directory with one manifest and one artifact directory per page section. The review site does not infer this structure; it expects it. The review-sweep example verb writes the structure for you.

/tmp/cssvd-review-site-smoke/
├── summary.json
└── smoke/
    └── artifacts/
        ├── app/
        │   ├── compare.json
        │   ├── compare.md
        │   ├── left_region.png
        │   ├── right_region.png
        │   ├── diff_only.png
        │   ├── diff_comparison.png
        │   ├── url1_full.png
        │   └── url2_full.png
        ├── hero/
        ├── card/
        └── cta/

The summary.json file drives the card list. Each row says which page and section it represents, how many pixels changed, how the result was classified, and where the screenshots live. Each compare.json file contains the detailed evidence for one section: source URLs, selectors, bounds, pixel statistics, computed CSS differences, and attribute differences. The PNG files are the visual evidence used by the view modes.

The key files are:

FileRequiredPurpose
summary.jsonYesThe root manifest loaded by /api/manifest; it lists every review card.
<page>/artifacts/<section>/compare.jsonYesDetailed comparison data loaded when a card expands.
left_region.pngYesCropped screenshot from the left/prototype side.
right_region.pngYesCropped screenshot from the right/implementation side.
diff_only.pngYesImage containing only changed pixels.
diff_comparison.pngOptionalTriptych/combined comparison image; useful for manual artifact browsing.
compare.mdOptionalMarkdown summary for humans outside the review site.
url1_full.png, url2_full.pngOptionalFull-page screenshots for debugging capture context.

The Site Spec

The site spec is a YAML file consumed by the example JavaScript verb. It describes what to compare, not how the review site should render. The spec names pages, declares left and right URLs, defines section selectors, chooses viewport dimensions, and lists CSS properties and attributes to collect.

A minimal spec looks like this:

name: cssvd-review-site-smoke
variant: desktop

viewport:
  width: 1000
  height: 760

defaults:
  waitMs: 250
  threshold: 30

policy:
  bands:
    - name: accepted
      maxChangedPercent: 0.5
    - name: review
      maxChangedPercent: 10
    - name: tune-required
      maxChangedPercent: 30
    - name: major-mismatch
      maxChangedPercent: 100

computed:
  - font-size
  - font-weight
  - line-height
  - color
  - background-color
  - border-radius
  - padding-top
  - padding-right
  - padding-bottom
  - padding-left

attributes:
  - id
  - class
  - aria-label

pages:
  smoke:
    leftUrl: http://127.0.0.1:18767/examples/pages/review-site-smoke-left.html
    rightUrl: http://127.0.0.1:18767/examples/pages/review-site-smoke-right.html
    sections:
      app:
        selector: "#app"
      hero:
        selector: ".hero"
      cta:
        selector: ".cta"

Each section normally uses the same selector on both sides. If your prototype and implementation use different DOM shapes, use leftSelector and rightSelector:

pages:
  pricing:
    leftUrl: http://localhost:7070/pricing.html
    rightUrl: http://localhost:6006/iframe.html?id=pricing--desktop
    sections:
      cards:
        leftSelector: "#pricing-cards"
        rightSelector: "[data-component='PricingCards']"

The spec fields have these responsibilities:

FieldMeaning
nameHuman name for the run. The example verb does not require it for paths, but it makes specs easier to identify.
variantLabel copied into summary rows, often desktop, tablet, or mobile.
viewport.width, viewport.heightBrowser viewport used for every comparison unless the verb is extended.
defaults.waitMsPost-navigation wait before capturing. Increase this for pages with client-side rendering, animations, or slow fonts.
defaults.thresholdPer-channel pixel threshold passed to diff.compareRegion. Higher values ignore small pixel differences.
policy.bandsClassification thresholds applied to changedPercent. These labels drive the colored review cards.
computedCSS properties extracted into compare.json for the CSS diff panel.
attributesHTML attributes extracted for attribute comparisons.
pages.<page>.leftUrlURL for the baseline/prototype side.
pages.<page>.rightUrlURL for the candidate/implementation side.
sections.<section>.selectorCSS selector used on both sides.
sections.<section>.leftSelectorOptional left-side selector override.
sections.<section>.rightSelectorOptional right-side selector override.
sections.<section>.leftWaitMs, rightWaitMsOptional wait overrides for one side of one section.

Selectors should target stable visual regions. A section that is too broad produces noisy screenshots and large pixel percentages. A section that is too narrow can miss the visual problem. For a first run, choose a small set of regions such as header, main, .hero, .card, and the primary CTA. Add more sections after the review site confirms the pipeline is working.

How the Example JavaScript Verb Works

The example generator lives at examples/verbs/review-sweep.js. It is a repository-scanned JavaScript verb, not a built-in Go command. You make it available by passing --repository examples/verbs to css-visual-diff verbs.

The verb exposes two commands:

css-visual-diff verbs --repository examples/verbs examples review-sweep from-spec \
  --specFile examples/specs/review-site-smoke.yaml \
  --outDir /tmp/cssvd-review-site-smoke

css-visual-diff verbs --repository examples/verbs examples review-sweep summary \
  --specFile examples/specs/review-site-smoke.yaml \
  --outDir /tmp/cssvd-review-site-smoke

from-spec reads the YAML spec, runs comparisons, writes all artifacts, and creates summary.json. summary walks an existing output directory and rebuilds summary.json from compare.json files. The second command is useful when you changed policy bands, copied artifacts, or lost the root manifest.

At the center of from-spec is a loop over pages and sections. For each section it calls the native diff module:

const diff = require("diff")

const result = diff.compareRegion({
  left: {
    url: pageSpec.leftUrl,
    selector: leftSelector,
    waitMs: leftWait,
  },
  right: {
    url: pageSpec.rightUrl,
    selector: rightSelector,
    waitMs: rightWait,
  },
  viewport: {
    width: vpWidth,
    height: vpHeight,
  },
  output: {
    outDir: artifactDir,
    threshold: threshold,
    writeJson: true,
    writeMarkdown: writeMd,
    writePngs: true,
  },
  computed: computedProps,
  attributes: attrProps,
})

The native comparison writes section artifacts into <outDir>/<page>/artifacts/<section>/. The JS verb then converts the raw result into a row for summary.json: it copies pixel counts, computes the policy classification, records screenshot paths, extracts style changes, extracts attribute changes, and computes bounds deltas.

This split is useful when you adapt the workflow for a project. Keep the low-level comparison call stable, and customize the spec reader, policy bands, naming rules, or output rows in JavaScript. Project-specific concepts belong in the JS verb; the Go core stays focused on browser automation and artifact primitives.

Running the Smoke Setup

The repository includes a deterministic setup for testing the review website with visible differences. Start the fixture server from the repository root:

python3 -m http.server 18767

In a second terminal, build a local binary and generate the review data:

GOWORK=off go build -o /tmp/css-visual-diff-review-smoke ./cmd/css-visual-diff

rm -rf /tmp/cssvd-review-site-smoke
/tmp/css-visual-diff-review-smoke verbs \
  --repository examples/verbs \
  examples review-sweep from-spec \
  --specFile examples/specs/review-site-smoke.yaml \
  --outDir /tmp/cssvd-review-site-smoke \
  --output json

Serve the review site:

/tmp/css-visual-diff-review-smoke serve \
  --data-dir /tmp/cssvd-review-site-smoke \
  --port 18098 \
  --open

Open http://127.0.0.1:18098. The page should show four cards: smoke/app, smoke/hero, smoke/card, and smoke/cta. The fixture intentionally changes spacing, color, typography, border radius, card layout, and button text, so the classifications should include tune-required and major-mismatch.

Reading Screenshots in the Review Site

Screenshots are the main evidence. The review site provides four view modes because each mode answers a different question.

View modeUse it when you want to answer
Side-by-sideWhat changed in layout, content, color, or component shape?
OverlayDo two regions align? Are text baselines, edges, or card positions shifted?
SliderWhere does the visual change begin and end across the region?
Diff onlyWhich pixels changed, independent of the original visual content?

Start with Diff only to locate the area of change. Move to Side-by-side to understand the visual meaning of that change. Use Overlay or Slider when the difference is spatial: shifted text, different padding, altered height, or different alignment.

The screenshot links at the bottom of each expanded card open the raw artifacts. Use these links when you need to inspect the exact PNG file outside the review UI or attach it to an issue.

Reading CSS Differences

The CSS diff panel depends on the computed list in the spec. If a property is not listed, the example verb cannot show it as a style difference. Choose properties that explain the visual questions you expect to ask: font size, line height, margins, padding, color, background, radius, shadow, display, position, width, and height are good first choices.

CSS differences and pixel differences do not always have a one-to-one relationship. A changed font-size may produce many pixel differences. A changed box-shadow may produce a small pixel percentage but still matter visually. Use the CSS panel to explain likely causes; use the screenshots to decide visual importance.

Serving Existing Runs

Once a data directory exists, serving it is independent of the original pages. The original URLs do not need to be live for review unless you want to re-run the comparison. The server only needs summary.json, compare.json, and artifact files on disk.

css-visual-diff serve \
  --data-dir /tmp/cssvd-review-site-smoke \
  --port 18098

The server provides:

EndpointPurpose
/api/manifestReturns the root summary.json.
/api/compare?page=<page>&section=<section>Returns one section's compare.json.
/artifacts/<page>/<section>/<file>Serves one PNG or JSON artifact from <data-dir>/<page>/artifacts/<section>/<file>.

The React app converts absolute artifact paths from summary.json into /artifacts/... URLs before loading images. The Go server validates path segments so a browser request cannot escape the selected data directory.

Adapting the Example to a Real Site

For a real site, copy the smoke spec or examples/specs/review-sweep.example.yaml and replace URLs and selectors:

pages:
  homepage:
    leftUrl: https://production.example.com/
    rightUrl: http://localhost:5173/
    sections:
      header:
        selector: "header"
      hero:
        selector: ".hero"
      primary-action:
        selector: "[data-testid='primary-action']"

Run a small comparison first. One page and two or three sections are enough to validate URLs, selectors, waits, and artifact loading. Add more pages after the first data directory serves correctly.

If a selector fails, run a narrow inspection command or open the page in the browser and verify the DOM. A missing selector is usually caused by one of these conditions: the page has not finished rendering, the selector differs between prototype and implementation, the content is inside an iframe, or the target requires authentication.

Troubleshooting

ProblemCauseSolution
unknown flag: --spec-fileThe example verb uses camelCase field names from its JS metadata.Use --specFile and --outDir.
The review site opens but shows no cards.The server cannot read summary.json, or the data directory is wrong.Check css-visual-diff serve --data-dir ... and verify <data-dir>/summary.json exists.
Cards show but images are broken.Artifact paths do not match <data-dir>/<page>/artifacts/<section>/<file>, or the frontend bundle is stale.Verify files exist, then rebuild the embedded review site with make build-embed; use make build-web-local if Docker/Dagger is unavailable.
compare.json not found when expanding a card.A summary row references a page/section without a matching artifact directory.Re-run from-spec or rebuild the summary with examples review-sweep summary.
A section records an error row.The selector was missing, invalid, or not visible/capturable at comparison time.Increase waitMs, set side-specific selectors, or inspect the page manually.
CSS diff is empty even though the screenshots differ.The changed property is not listed in computed, or the difference is caused by layout/content rather than CSS properties in the list.Add relevant properties to computed and re-run the comparison.
The same site compared to itself still has large diffs.The page has animations, timestamps, randomized content, ads, lazy loading, or non-deterministic fonts.Disable animations, wait longer, target smaller stable regions, or add project-specific prepare logic.

See Also

  • css-visual-diff help review-site explains the interactive review UI, keyboard shortcuts, local storage, and export modal.
  • css-visual-diff help review-site-data-spec defines the summary.json, compare.json, and artifact directory contracts in detail.
  • css-visual-diff help js-verb-review-sweep explains the example verb that generates review-site data.
  • css-visual-diff help javascript-verbs explains repository-scanned JavaScript verbs.
  • css-visual-diff help pixel-accuracy-scripting-guide explains lower-level browser, locator, screenshot, and CSS inspection primitives.