ev_lib 0.16.2

EV-invest shared Rust libraries, one per feature
Documentation
# Visual regression

One PNG per uikit primitive, in `__screenshots__/`, compared **byte for byte**.

```sh
nix run .#visual              # capture and compare — exit 0 means identical
nix run .#visual -- --update  # accept the current render as the baseline
```

Adding a primitive is still one line in `../support/gallery.rs`; the page list
flows from there through `dist/manifest.json` into the spec.

## Why it is shaped this way

```
  what moves the render?

  re-run, same machine ........... 0 px          deterministic
  x86_64-linux vs aarch64-linux .. 0 px          CPU arch is irrelevant
  host fonts vs pinned fonts ..... 0.03–0.21 %   real
  host fonts vs no Inter ......... 0.12–0.38 %   real
  linux vs darwin ................ > 1 %         cannot be fixed in-process
```

The OS matters and the CPU does not, so the capture is always a `*-linux`
derivation (`packages.visual-snapshots`) no matter who runs it. A mac routes it
to a **native** `aarch64-linux` builder VM and still matches these x86_64
baselines — no Docker, no Rosetta, no emulation.

Everything the render reads is pinned in the `visual` block of `flake.nix`:
runner and browsers from one nixpkgs (`playwright-test` carries its own browser
revision, so they cannot drift apart), the font set, and the Tailwind browser
engine — served out of `dist/` rather than a CDN, because the derivation builds
with no network.

There is no diff-ratio threshold. The render is deterministic once the inputs
are pinned, so a threshold is not slack that absorbs noise — it is a window the
baselines can drift through. The last one was 1 %, and the host font set moved
inside it unnoticed.

`dist/` is generated on the host, outside the derivation, so nothing here needs
cargo vendoring. If that ever made the HTML host-dependent, it would show up as
a snapshot diff — which is what the suite is for.

## macOS

A mac has two ways to refresh the baselines. Both end in the same place — the
capture is the `*-linux` derivation either way.

**Through CI (no local builder needed).** `.github/workflows/visual-baselines.yml`
renders the suite on the same ubuntu image the gate uses and publishes the PNGs
as the `visual-snapshots` artifact. It runs on every pull request that touches
`rust/`, the token/motion CSS, the flake or the cargo manifests/toolchain, and on `workflow_dispatch`:

```sh
gh run list --workflow "Visual baselines" --branch "$(git branch --show-current)"
rm -f rust/tests/visual/__screenshots__/*.png   # a board removed from GALLERY must not leave its PNG behind
gh run download <run-id> -n visual-snapshots -D rust/tests/visual/__screenshots__
git add rust/tests/visual/__screenshots__ && git commit -m "test(uikit): refresh visual baselines (run <run-id>)"
```

Put the run id in the commit message: it is the provenance of the bytes. The
gate job (`nix run .#visual` in `errors.yml`) then confirms them on the next
push. This workflow is hand-written, not produced by the `github` block in
`flake.nix` — the generator only overwrites its own fixed file names
(`errors.yml`, `warnings.yml`, `claude*.yml`, `release-*.yml`, …), so it stays.

**With a linux builder.** In nix-darwin:

```nix
nix.linux-builder.enable = true;   # defaults to the host's arch → aarch64-linux
nix.settings.trusted-users = [ "@admin" ];
```

Without nix-darwin, run `nix run nixpkgs#darwin.linux-builder` and register it in
`nix.buildMachines`. Then `nix run .#visual -- --update` works as on linux.

The guest has its **own** store (`nix-builder-vm.nix` sets
`useNixStoreImage = true`), so it fetches the browser closure once from
substituters rather than reusing the host's.

## Not covered

Nothing here stops a stale baseline being committed — it only makes a stale one
detectable. Catching it at merge is the `nix run .#visual` job in
`.github/workflows/errors.yml` (the `flake-app` entry in the `github` block of
`flake.nix`): an ubuntu runner is byte-compatible with these baselines for free.