← Blog

The screenshot was lying about the app

Hub 2 min read

The Hub is one HTML file rendered by WKWebView on macOS and WebView2 on Windows, so screenshots are how it gets checked. Both builds take one with a --shot flag, because the obvious tools lie about this kind of window — screencapture -l<windowid> answers "could not create image from window" for a perfectly healthy WKWebView, since it composites out of process.

Then the screenshots started lying too.

What it showed#

Click "Extensions" in the sidebar. The Extensions page appears. The highlight stays on "Dashboard".

That is a real bug shape, and a familiar one: an attribute selector that the style engine did not re-evaluate.

.nav button[aria-current="true"] { background: var(--accent-soft); }

What it actually was#

Before rewriting anything, ask the page what it thinks. Dispatch a real click, wait well past the transition, and read the computed style rather than looking at pixels:

dash = false / 213,90,52,0.14
ext  = true  / 0,0,0,0

The attributes are correct. dash is false and still painted the accent colour; ext is true and still transparent. Nine hundred milliseconds after a .18s transition should have finished.

The tell is that both are sitting exactly on their starting values. Not stale, not wrong — unstarted. Removing one line settled it:

.nav button { transition: none }

With that, the same run is correct in both the computed style and the picture. Which identifies the timeline, not the selector, and not style invalidation.

Why#

The window is never put on a screen. A --shot run creates it, renders, snaps and quits, and a web engine has no reason to advance an animation for a surface nobody is looking at. The transition is queued, the first frame is committed, and time stops.

So the picture was accurate. It was an accurate picture of a moment 200ms into a 180ms transition that would never advance — and every human who looked at it read a permanent state.

Two things changed, and neither was the page#

The page was already correct and was left alone. The mistake was nearly "fixing" it, and shipping a class-based workaround plus a confident comment about a WebKit bug that does not exist. That comment would have outlived everyone who could contradict it.

The screenshot flag now turns transitions and animations off before it changes anything:

var s = document.createElement('style');
s.textContent = '*{transition:none !important;animation:none !important}';
document.head.appendChild(s);

And it grew --shot-delay, because the next thing to photograph had to sign in, download something and start a browser first, and none of that fits in the second and a half the default allows.

The general version#

A measurement can be wrong in a way that looks exactly like the thing you were measuring being wrong. The instrument had a known failure mode already — that is why the flag exists — and it acquired a second one that pointed at the subject instead of at itself.

Cheap defence, and the only one that worked here: before believing a diagnosis, change one variable that should be irrelevant. transition: none should not have been able to fix a broken selector. It did, and that was the answer.