Skip to content

Screenshot pixel diff: slider/overlay view (blocked — screenshot capture no longer exists) #127

Description

@gregoryfoster

Rewritten 2026-08-19 in a backlog staleness pass. Every factual claim in the original "Current state" section is now false — the entire screenshot feature was deleted in 4e4d97a ("#229 chore: delete dead code from retired features") following the Phase 5 cutover (#156). Kept open as a far-future item because the design work is sound and the intent is on record, but nothing here is actionable until three separate layers exist, two of them in another repo. Original body preserved at the bottom.

Status: blocked on three unbuilt layers

#127 (this)
  └── a persistent previous-screenshot to diff against ......... #222
  └── a way to carry captured images across the blob boundary .. does not exist
  └── browser-based capture ................................... replicator#63

Watcher does not fetch (#241, cutover 2026-08-06), so it cannot capture. Screenshot capture needs a browser, which is Replicator's to build (replicator#63, itself carrying an unresolved selection-mechanism question). Even with capture, the fetch path's BlobAvailableEvent carries one blob per command — how a screenshot rides alongside the content blob is undesigned. And a diff needs the previous image, which is the same persistent-content-store problem as #222.

Do not start this without all three. It is recorded so the design is not lost, not because it is ready.

What was deleted

For anyone who opens this expecting the code the original describes — none of it exists:

Original claim Reality
"already captures Playwright screenshots (src/core/screenshot.py)" File gone; zero occurrences of screenshot anywhere in src/; Playwright is not a dependency
"computes a Change.visual_change_score" Gone — no Change model, no such column
"pages/change_detail.html near line 49" Template gone
"when both snapshots have screenshot_path set" No snapshots, no such field
"Pillow already pulled in" Not in pyproject.toml

Restore anchor: the commit preceding 4e4d97a. Unlike #126's residue (see #273), #229 cleaned this up properly — there are no orphaned assets.

Design worth keeping

The original sketch stands on its own merits and should be the starting point if this is ever revived:

  1. Slider mode — draggable vertical handle, CSS clip-path on the After image, ~50 lines of vanilla JS, no dependency.
  2. Overlay mode — precomputed alpha-masked diff PNG (ImageChops.difference + threshold) layered over the After image, changed pixels in a high-contrast colour.
  3. Mode toggle — a segment-group per docs/UI.md, matching the existing component vocabulary.

And its three open questions, none of which the intervening rework has answered:

  1. Where does the diff PNG live? Lazily on first view, or eagerly in the pipeline? Note this is now harder, not easier: there is no local snapshot storage to put it next to.
  2. Threshold tuning — anti-aliasing must not register; needs a perceptual threshold, not bitwise.
  3. Long pages — full-page captures can exceed 10000px tall; slider and overlay both need a scroll strategy within the visual area.

Accessibility requirements from the original acceptance list still apply and are easy to lose: keyboard arrow-key control when the slider has focus, touch support, aria-label describing the overlay, and dark-mode legibility for the handle and legend (docs/STYLE.md §8).

Standing intent

The user asked, after #53 (Snapshot screenshot preview, closed) shipped, to revisit a set of screenshot follow-ups including screenshot diffing / visual regression. That intent predates the retirement of the whole feature — so it is recorded here rather than lost, but it now implies rebuilding capture from nothing rather than extending it.

Related follow-ups from that same list, all equally unbuilt: screenshots for non-HTML content types (PDF, CSV), configurable viewport (was hardcoded 1280×800), on-demand regeneration.


Original body (2026-04-28) — factually stale, retained for the design sketch

Background

Split out from the descoped #115 Phase D. Phase A + B.1 of the diff viewer have shipped; this is the remaining "promote screenshot comparison to a first-class view" item.

Current state

  • The Watch pipeline already captures Playwright screenshots (src/core/screenshot.py) and computes a Change.visual_change_score (perceptual similarity).
  • The Change Detail page renders a side-by-side "Visual Comparison" block with the two raw screenshots (pages/change_detail.html near line 49) when both snapshots have screenshot_path set.
  • That block is informational — there's no overlay, no slider, no diff highlighting. Users see "Before" and "After" but have to eyeball the differences themselves, which is impractical for large pages.

Goal

Promote the Visual Comparison from "two thumbnails" to a real visual diff tool:

  1. Slider mode — single image area with a draggable vertical handle; left of handle shows Before, right shows After. Standard before/after slider pattern.
  2. Overlay mode — Before + After stacked, with a per-pixel difference mask highlighted in magenta or similar high-contrast color. Useful for spotting subtle changes a slider misses.
  3. Mode toggle — a segment-group like the existing diff modes: Side-by-side (current), Slider, Overlay.
  4. Optional: zoom/pan — for high-resolution screenshots, click-to-zoom.

Approach sketch

  • Pixel-diff computation: Pillow already pulled in (or via pillow extra). Use ImageChops.difference + a threshold to produce an alpha-masked overlay PNG. Compute server-side once, cache to storage alongside the snapshots; serve via a new route /watches/{id}/screenshot-diff?from={prev_id}&to={curr_id}.
  • Slider (client-side): vanilla JS + CSS clip-path on the After image, wired to a draggable handle. ~50 lines, no JS dep.
  • Overlay (client-side): just an <img> with the precomputed diff PNG layered over the After.
  • Mode switching: Alpine or plain JS toggling visibility of the three modes.

Acceptance

  • Three-segment toggle: Side-by-side / Slider / Overlay
  • Slider: smooth drag; touch + keyboard support (a11y — left/right arrow keys when slider has focus)
  • Overlay: changed pixels visibly marked; aria-label describes what's shown
  • Pixel-diff PNG is computed once per Change, cached, served via a route
  • Dark mode: slider handle and overlay legend remain readable
  • Graceful fallback: if either snapshot lacks screenshot_path, the toggle is hidden (current behavior)
  • No regression in the existing side-by-side rendering

Open design questions

  1. Where does the diff PNG live? LocalStorage under a new screenshots-diff/{change_id}.png path, computed lazily on first view? Or eagerly during the check pipeline?
  2. Threshold tuning — anti-aliasing differences shouldn't show; need a perceptual threshold, not bitwise.
  3. Long pages — Playwright captures full-page screenshots; some watches produce 10000+ px tall images. Slider/overlay UX needs to handle that (scroll within the visual area).

Dependencies

  • Reuses existing screenshot capture pipeline.
  • New dep: none (Pillow likely sufficient; if perceptual diff needs more, consider imagehash or numpy — defer until measurement says we need them).

Related

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions