On a Mac, --force-color-profile=hdr10 makes Chrome put its window in an HDR colour space, and macOS then tone-maps every ordinary page inside it. Measured off the real screen, our test page's background went from (54,54,58) to (70,71,74) and its white fell from 255 to 243. We shipped it anyway, because every check we ran read the page before that happened. We now use extended-srgb, which reports the same media queries and renders correctly.

Why we were forcing a colour profile at all

Mosaic draws a colour gamut for each profile instead of passing on the computer's own, because a value identical across every profile on one machine is a join key. For a Mac-like profile that gamut is p3. The first switch we used for it, --force-color-profile=display-p3-d65, moved color-gamut to p3 and left dynamic-range at standard.

That pair is close to nonexistent. A Mac whose panel is wide-gamut also reports high dynamic range. We read both media queries together on branded Chrome 153, one launch per row:

nothing forced      p3   high        <- what a real Mac answers
display-p3-d65      p3   standard    <- what every profile answered
hdr10               p3   high
scrgb-linear        p3   high
extended-srgb       p3   high
srgb                srgb standard
generic-rgb         srgb standard

Fixing one field had produced a different contradiction. When a switch moves a pair of related values, read both.

Every profile is drawn from one real computer, not from separate listsIn Mosaic: Machines that exist. GPU, screen, CPU and fonts from one real device. Free holds 3 profiles. Pro is $49 a month for any number.

How it works: Machines that exist

The checks we ran, and why they all agreed

hdr10 was the first row that gave the right pair, so we checked what else it moved. A canvas drawn and hashed inside the page, the same readback canvas fingerprinting relies on, came back d289dba9 under all five colour profiles. screen.colorDepth, pixelDepth, (color-gamut: p3) and the WebGL version string were unchanged. A screenshot of the page through the DevTools protocol was byte-identical to one taken before. So was our stored detector screenshot of IPHey's page, regenerated and compared against the old one from git: median luminance 54 and 99th percentile 255 in both.

Four independent checks, all clean, and we shipped it.

Within the hour the report came in from outside: "why is there a weird tint to the website when I visit IPHey". It was not IPHey. It was every page.

What the screen showed

Forcing an HDR profile tells macOS the window holds HDR content. macOS then maps the page's SDR colours into that space, which puts reference white below peak and lifts black. We captured the actual screen, same page and same window geometry, one launch per profile:

display-p3-d65   background (54,54,58)   brightest 255
hdr10            background (70,71,74)   brightest 243
scrgb-linear     background (53,53,58)   brightest 255
extended-srgb    background (53,54,59)   brightest 255

Against the correctly rendering baseline, the mean absolute pixel difference across the whole page was 18.70 for hdr10, 0.46 for scrgb-linear and 0.62 for extended-srgb. The last two are text antialiasing between separate launches, so on pixels they tie. We chose extended-srgb on the transfer function: it keeps the sRGB curve and only allows values outside 0 to 1, where scrgb-linear composites in linear light, a bigger change for nothing gained.

A canvas readback is not a rendering check

Every one of the four checks read the framebuffer before the display transform. The canvas hash, the CDP screenshot and the stored detector image all come from the page's own pixels, and the tone-map is applied after those, on the way to the panel. So all four agreed, and all four were answering a question nobody had asked. They told us the page was the same. They could not tell us it looked the same.

When a change sits downstream of the page, in a colour profile, the compositor or anything the window server does, the only measurement is a capture of the screen. On macOS that is screencapture, and nothing less counts.

The same family of mistake shows up elsewhere in this work. A check that reads a value too early or from the wrong place reports a pass, and the pass looks like evidence. The fix is always a control that can see the thing in question. For browser behaviour that control is Google's own build of the same version. For pixels it is the screen.

A Mosaic profile fingerprint with the hardware, screen and locale drawn for the profile
Gamut and dynamic range are drawn with the rest of the machine

What profiles launch with now

A profile drawn with a p3 gamut launches with --force-color-profile=extended-srgb. One drawn as srgb launches with srgb. Each reads the same color-gamut and dynamic-range pair that a real machine of its kind reports, and pages render as they should. The switch is set by the engine at launch, not by a script in the page, which is the approach behind every value Mosaic changes (why C++ and not JavaScript). How the gamut and the rest of a profile are drawn together from real hardware records is on real fingerprints.