Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,10 @@ jobs:
# would find nothing and silently fall back to the dev-box path.
chromium=$(nix build --no-link --print-out-paths \
-f tools/toolchain/chromium-e2e-env.nix chromium)
[ -n "$chromium" ] || {
echo "::error::chromium-e2e-env.nix chromium produced no store path"
exit 1
}
echo "PLAYWRIGHT_CHROMIUM_PATH=$chromium/bin/chromium" >>"$GITHUB_ENV"
# secretspec: the CLI the Go secrets WRITE path spawns BY NAME
# (go/internal/secrets/resolver.go's `defaultCLI = "secretspec"`,
Expand All @@ -441,6 +445,27 @@ jobs:
-f tools/toolchain/secretspec-env.nix secretspec)
echo "$secretspec/bin" >>"$GITHUB_PATH"

# The visual-regression gate's fonts, from the same pinned helper.
# Without this the runner rasterizes with whatever faces its image
# ships, making the committed baselines a function of the runner
# image rather than of the repo — a base-image bump would then red
# every apps/ui PR at once, and no agent could reproduce it locally.
# The config deliberately sees ONLY the pinned faces, so a box with
# extra fonts installed renders identically to one without.
fonts=$(nix build --no-link --print-out-paths \
-f tools/toolchain/chromium-e2e-env.nix fontconfig)
# Fail closed: the two malformed values fail in opposite directions
# and BOTH stay green. An EMPTY FONTCONFIG_FILE is not "use the
# system config" — fontconfig reads it as a config with no font dirs
# at all, and every string then measures zero width. A path that does
# not exist falls back to the full host set, which is the undeclared
# input this whole step exists to remove.
[ -n "$fonts" ] || {
echo "::error::chromium-e2e-env.nix fontconfig produced no store path"
exit 1
}
echo "FONTCONFIG_FILE=$fonts" >>"$GITHUB_ENV"

- name: Toolchain parity
if: matrix.run == 'true'
# Runs BEFORE the battery, though `:ci` schedules it again (it takes under
Expand Down Expand Up @@ -2700,7 +2725,27 @@ jobs:
run: |
chromium=$(nix build --no-link --print-out-paths \
-f tools/toolchain/chromium-e2e-env.nix chromium)
[ -n "$chromium" ] || {
echo "::error::chromium-e2e-env.nix chromium produced no store path"
exit 1
}
echo "PLAYWRIGHT_CHROMIUM_PATH=$chromium/bin/chromium" >>"$GITHUB_ENV"
# The same pinned fontconfig the moon job's gate replays with. The
# capture lane MUST rasterize with it too: the gate compares these
# bytes against a run that has it set, so a capture without it bakes
# the runner image's own faces into the baselines and reds every
# text-bearing shot permanently.
fonts=$(nix build --no-link --print-out-paths \
-f tools/toolchain/chromium-e2e-env.nix fontconfig)
# Fail closed HERE above all: an empty or bogus value in the capture
# lane writes zero-width or host-font pixels into committed baselines,
# and the bot PR then presents them for image review as a legitimate
# capture. A red gate is recoverable; a poisoned baseline is not.
[ -n "$fonts" ] || {
echo "::error::chromium-e2e-env.nix fontconfig produced no store path"
exit 1
}
echo "FONTCONFIG_FILE=$fonts" >>"$GITHUB_ENV"

- name: Install the workspace JS dependencies
# Unlike the forge regen lane — whose payload is `go test` and needs no
Expand Down
15 changes: 15 additions & 0 deletions devenv.nix
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,7 @@ in
# toolchain. Platform-independent, so it lives in the base set, not the
# Linux-only merge below.
MOON_TOOLCHAIN_FORCE_GLOBALS = "true";

}
# The Compass native app (Wails v3, go/cmd/compass-app) links the Linux
# GTK4/WebKitGTK stack through cgo. pkg-config (in `packages` above) finds each
Expand Down Expand Up @@ -298,6 +299,20 @@ in
(lib.makeSearchPathOutput "dev" "lib/pkgconfig" pcClosure)
(lib.makeSearchPathOutput "dev" "share/pkgconfig" pcClosure)
];

# The visual-regression gate (apps/ui:visual-gate) rasterizes text with
# these faces, pinned to the same devenv.lock nixpkgs CI resolves, and the
# config also pins hinting/antialiasing, so a local run and a CI run
# rasterize identically and a red gate is reproducible off-CI. The config
# sees ONLY the pinned faces, so the host's own font set — which previously
# decided which substitute the CSS stack fell through to, with no declared
# cause — cannot affect the output.
#
# Linux-only despite the fonts themselves building everywhere: chromium is
# Linux-only in nixpkgs, so a darwin shell cannot run this gate at all, and
# FONTCONFIG_FILE is process-tree-wide — it would replace the font universe
# for everything launched from the shell in exchange for nothing.
FONTCONFIG_FILE = (import tools/toolchain/chromium-e2e-env.nix).fontconfig;
};

enterShell = ''
Expand Down
61 changes: 58 additions & 3 deletions tools/toolchain/chromium-e2e-env.nix
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@
# Pins nixpkgs to the SAME devenv.lock revision the dev shell and gate-tools.nix
# resolve, so CI drives byte-for-byte the Chromium a Linux dev box does.
#
# One output the ci.yml step consumes, realized with `nix build` (never
# Two outputs the ci.yml steps consume, realized with `nix build` (never
# `nix eval`, which strips the store context that would build the derivation):
#
# chromium the nix-wrapped Chromium derivation; the step reads its
# bin/chromium out-path into PLAYWRIGHT_CHROMIUM_PATH.
# chromium the nix-wrapped Chromium derivation; the step reads its
# bin/chromium out-path into PLAYWRIGHT_CHROMIUM_PATH.
# fontconfig a self-contained fontconfig read into FONTCONFIG_FILE by both
# pixel-touching lanes and by the Linux dev shell (devenv.nix).
let
lock = builtins.fromJSON (builtins.readFile ../../devenv.lock);
node = lock.nodes.nixpkgs.locked;
Expand All @@ -32,11 +34,64 @@ let
sha256 = node.narHash;
};
pkgs = import nixpkgsSrc { };

# The two branded faces the design tokens name (`--rigel-mono`,
# `--rigel-display` in apps/ui/src/design/tokens.css), then a coverage
# fallback: the branded pair leaves 21 of the UI's 49 non-ASCII glyphs
# uncovered, and an uncovered glyph bakes tofu into a baseline.
#
# Unifont, not a stock system font. DejaVu pulled DejaVu Math TeX Gyre in
# behind it, putting a proportional math face in a monospace grid; Unifont is
# 1-bit 16x16, so it reads as pixel-grid instead of as a foreign sans. It is
# dual-width though (fontconfig spacing=90): 17 fallback glyphs sit at 0.5em
# and 4 — including the gear — at 1.0em, against Space Mono's 0.612em cell,
# so these glyphs do not land on the grid. RIG-3603 retires them to
# BadgeGlyph-style dot-matrix SVG, which is what actually fixes that.
fontDirs = [
"${pkgs.google-fonts.override { fonts = [ "SpaceMono" ]; }}/share/fonts/truetype"
"${pkgs.departure-mono}/share/fonts/otf"
"${pkgs.unifont}/share/fonts/opentype"
"${pkgs.unifont_upper}/share/fonts/opentype"
];
in
{
# Referenced by its own store path, never merged into a buildEnv: the
# nix-wrapped chromium resolves its sandbox helper + unwrapped browser through
# its own bin/ wrapper scripts and store-relative references that a
# symlink-merge would break. ci.yml reads `bin/chromium` off this out-path.
chromium = pkgs.chromium;

# A self-contained fontconfig: the pinned faces, and the rasterization
# parameters. Both halves are load-bearing.
#
# The <dir> list makes the host's fonts invisible, so a box with 800+
# families installed resolves the same faces as a bare runner. Format
# subdirectories rather than each package's share/fonts, because the whole
# tree also ships woff/woff2 duplicates and Unifont's bdf/otb/pcf bitmap
# strikes — and a strike outsorts the scalable face at some pixel sizes, so
# exposing them makes glyph choice depend on font size.
#
# The <match> block pins hinting and antialiasing, which the font set alone
# does not. Left unset these come from fontconfig's compiled-in defaults, so
# they are a property of the linked library rather than of this repo: under
# the built-in hintfull, Space Mono's 612/1000em advance renders 0.26px/glyph
# wider than under hintslight, in the same browser with the same font file.
# A devenv.lock bump that moved fontconfig would then shift every
# text-bearing baseline with no declared cause. rgba=none because subpixel
# order changes per-pixel colour, and a diffed screenshot wants grayscale AA.
fontconfig = pkgs.writeText "compass-visual-gate-fonts.conf" ''
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
${builtins.concatStringsSep "\n" (map (d: " <dir>${d}</dir>") fontDirs)}
<match target="font">
<edit name="antialias" mode="assign"><bool>true</bool></edit>
<edit name="hinting" mode="assign"><bool>true</bool></edit>
<edit name="hintstyle" mode="assign"><const>hintslight</const></edit>
<edit name="rgba" mode="assign"><const>none</const></edit>
<edit name="lcdfilter" mode="assign"><const>lcddefault</const></edit>
</match>
<cachedir prefix="xdg">fontconfig</cachedir>
</fontconfig>
'';
}
Loading