buildnotes Example 2 of 3 · uploads.sh

Before and after screenshots, side by side

Tag two screenshots before and after, and the pull request comment shows them in one row. Open either one, and a divider slides across both.

Most screenshots on a pull request come in pairs: the page before the change, and the page after it. uploads.sh collects a branch's screenshots into one comment on its pull request. That comment used to show a pair the way it shows anything else, one image under the other in the order they arrived. The reviewer had to match them up in their head, then open each one in its own tab to compare them. Now a pair finds itself:

01Pairs that find each other

A screenshot already carries two small labels when you upload it: the page it shows (its path) and its state, such as before or after. The comment uses those to pair:

The rule is strict on purpose. A pair forms only when there is exactly one before and exactly one after. Two befores for the same page could belong to different changes. A group like that stays as it was rather than being matched by a guess. Anything outside a pair shows exactly as it did before: a video, a loading state, a shot with no label.

The two sides of a pair are narrower than a single image, so the row fits the comment column on a phone without scrolling sideways.

A pull request comment from uploads.sh on GitHub: an unpaired screenshot of an API errors page labelled /docs/api/errors · after, and below it one row with two screenshots of a REST API docs page, labelled Before and After, each captioned /docs/api/rest.
Fig. 1. A real comment on a releases.sh pull request. The errors page had no before, so it shows on its own; the REST API page's two shots share a row.

02A divider instead of thumbnails

Each screenshot also has its own page on uploads.sh, the one its link in the comment opens. The first version of pairing there was modest: two small panels, before and after, in a strip above the image. It didn't work. At 220 pixels wide, the panels were too small to show what had changed in a screenshot. They weren't links, and they repeated the image right below them.

So the panels went, and the comparison moved into the main view. A switch above the image reads This image or Compare. The page opens on the image its link names. Compare lays the two shots on top of each other, before on the left of a divider you drag, at full size.

Round oneThe file page with two small before and after thumbnails in a strip above a full-size copy of the same image.
As it shipsThe file page in Compare mode: one full-size stage with a vertical divider, Before on the left and After on the right, and a Comparison section in the side rail.
Fig. 2. A file page with a pair, in the first version and as it ships. Both captures were taken while it was being built.

The divider is a small open-source component of about 3.6 kB with no dependencies of its own. The page runs no framework, so a slider built for React would have added React just for this. The component only loads on a page that has a pair. A public page also never shows a private counterpart. If the other half of a pair can't be seen on its own, the page acts as if it isn't there.

03Two shots that line up

Two screenshots of the same page are rarely the same size. Add a paragraph and the after is a few pixels taller. Laid on top of each other carelessly, the two drift apart, and every line looks changed.

So the after image sets the size of the frame, and the before is fitted into it from the top-left corner. That is how two shots of one page differ: the top stays put and the change pushes content down. Content that didn't move lines up across the divider, and what did change stands out. When the sizes differ, the before gets an empty margin. It is never cropped or stretched.

A file page in Compare mode showing a REST API docs page. A purple divider runs down the middle of the image; the left side is the before shot, the right is the after, with labels Before and After in the bottom corners. The side rail lists the path, state and viewport.
Fig. 3. The REST API pair from Fig. 1 in Compare: before to the left of the divider, after to the right, both anchored at the top-left corner.

One case still looks odd: a before several times taller than its after. It shows narrower than the frame, with space beside it. Fixing that would mean giving up the rule that the after sets the frame, so it stays, and it fails safe.

04The pair that overwrote itself

The most common way to make a pair is to capture the same page twice: once before the change, once after. It turned out to be the one way that didn't work. It surfaced during everyday use, while capturing a docs page before and after a change.

The screenshot command named the file after the page's address. The state was a label on the file, not part of its name. So the after landed on the before's name and replaced it. The command reported success, and the pull request showed the after image twice, as before and as after. It also meant the first pairing rule could never match a pair made this way.

Now the state is part of the name. The same page captured before and after makes two files, …-before and …-after, and the pair forms. Capturing the same page in the same state again still replaces the earlier shot, and the command now says so.

Before the fix · same page, twice
before → …/localhost-docs-mcp.webp
after  → …/localhost-docs-mcp.webp
          (the after replaced the before)
As it ships
before → …/localhost-docs-mcp-before.webp
after  → …/localhost-docs-mcp-after.webp
Fig. 4. Two captures of one page, before and after the fix. The names are the ones in the report and the fix.

To make a pair, tag each side with its state when you upload it:

uploads put ./hero-before.webp --state before
uploads put ./hero-after.webp --state after

Or capture the page twice with the screenshot command, once with each state. When a pull request has several pairs, give both sides of each pair the same page label (--meta path=).

This post is a buildnotes example

The same story in 47 seconds

buildnotes wrote this post from the pull requests behind one uploads.sh feature, then cut the video from the post: every word, number and screenshot in it comes from the page above.

Walkthrough videosSunny · post and 52 seconds video Weekly digestsRelease Notes Index · post and one minute video