diff --git a/.github/workflows/acceptance.yml b/.github/workflows/acceptance.yml
index 8b2c0a4..a13efb5 100644
--- a/.github/workflows/acceptance.yml
+++ b/.github/workflows/acceptance.yml
@@ -36,6 +36,9 @@ jobs:
run: |
raco pkg install --auto --no-docs "https://github.com/turinglambdaai/keel.git#$env:KEEL_PIN"
raco pkg install --auto --no-docs "https://github.com/turinglambdaai/rivet.git#$env:RIVET_PIN"
+ - name: Verify Windows installer artwork
+ shell: pwsh
+ run: python scripts/check-windows-installer-bitmaps.py
- name: Run executable acceptance contract
shell: pwsh
run: |
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 341d442..7835a05 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -25,6 +25,8 @@ jobs:
run: bash scripts/check-release-version.sh
- name: Verify dependency pins stay aligned
run: bash scripts/check-dependency-pins.sh
+ - name: Verify Windows installer bitmap and checkbox surface
+ run: python3 scripts/check-windows-installer-bitmaps.py
core:
name: Racket core (contract tests)
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 3fba709..d550bc7 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -102,6 +102,9 @@ jobs:
- name: Version preflight (VERSION == rivet.rktd == updater == tag)
shell: bash
run: bash scripts/check-release-version.sh "${{ github.ref_name }}"
+ - name: Verify Windows installer checkbox surface
+ shell: pwsh
+ run: python scripts/check-windows-installer-bitmaps.py
- name: Release (MSI + portable zip)
shell: pwsh
run: raco rivet release --without-updates --development
diff --git a/assets/wizard-dialog.bmp b/assets/wizard-dialog.bmp
index 3bcab09..e775df5 100644
Binary files a/assets/wizard-dialog.bmp and b/assets/wizard-dialog.bmp differ
diff --git a/assets/wizard-dialog.png b/assets/wizard-dialog.png
index 82c5a47..9604ce0 100644
Binary files a/assets/wizard-dialog.png and b/assets/wizard-dialog.png differ
diff --git a/assets/wizard-dialog.svg b/assets/wizard-dialog.svg
index de30f9a..cda33c1 100644
--- a/assets/wizard-dialog.svg
+++ b/assets/wizard-dialog.svg
@@ -11,4 +11,10 @@
taskly
+
+
+
diff --git a/scripts/check-windows-installer-bitmaps.py b/scripts/check-windows-installer-bitmaps.py
new file mode 100644
index 0000000..a5c325a
--- /dev/null
+++ b/scripts/check-windows-installer-bitmaps.py
@@ -0,0 +1,72 @@
+#!/usr/bin/env python3
+"""Verify WiX 5 completion dialog bitmap and native checkbox background."""
+from __future__ import annotations
+
+import struct
+import zlib
+from pathlib import Path
+
+ASSETS = Path(__file__).resolve().parents[1] / "assets"
+WIDTH, HEIGHT = 493, 312
+
+
+def bmp_pixels() -> bytes:
+ source = (ASSETS / "wizard-dialog.bmp").read_bytes()
+ assert source[:2] == b"BM", "invalid BMP"
+ offset = struct.unpack_from(" bytes:
+ source = (ASSETS / "wizard-dialog.png").read_bytes()
+ assert source[:8] == b"\x89PNG\r\n\x1a\n"
+ pos, chunks = 8, []
+ while pos < len(source):
+ size = struct.unpack_from(">I", source, pos)[0]
+ tag = source[pos + 4 : pos + 8]
+ payload = source[pos + 8 : pos + 8 + size]
+ if tag == b"IHDR":
+ assert struct.unpack_from(">IIBBBBB", payload) == (
+ WIDTH, HEIGHT, 8, 2, 0, 0, 0
+ )
+ if tag == b"IDAT":
+ chunks.append(payload)
+ pos += size + 12
+ raw = zlib.decompress(b"".join(chunks))
+ stride = WIDTH * 3
+ pixels = bytearray()
+ for y in range(HEIGHT):
+ offset = y * (stride + 1)
+ assert raw[offset] == 0, "PNG preview must use filter None"
+ pixels.extend(raw[offset + 1 : offset + 1 + stride])
+ return bytes(pixels)
+
+
+def main() -> None:
+ pixels = bmp_pixels()
+ assert png_pixels() == pixels, "preview PNG differs from shipping BMP"
+ row_width = WIDTH * 3
+ for y in range(248, HEIGHT):
+ row = pixels[y * row_width : (y + 1) * row_width]
+ assert row == bytes((240, 240, 240)) * WIDTH, (
+ f"Windows checkbox backing is not flat #F0F0F0 at y={y}"
+ )
+ divider = pixels[247 * row_width : 248 * row_width]
+ assert divider == bytes((214, 210, 206)) * WIDTH
+ print("Windows MSI artwork OK: 493x312, flat native checkbox footer")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/shared/spec/DESIGN-TOKENS.md b/shared/spec/DESIGN-TOKENS.md
index 853904b..e4051a0 100644
--- a/shared/spec/DESIGN-TOKENS.md
+++ b/shared/spec/DESIGN-TOKENS.md
@@ -109,3 +109,18 @@ accent-filled white-text rounded button. No chrome field borders anywhere.
White rounded square (macOS-squircle radius ≈ 22.5%) with a quiet checklist
mark: one system-blue rounded bar over two neutral (`#CECED3`) bars, staggered
widths. Monochrome, no gradients — must read on light and dark docks alike.
+
+## Windows MSI installer artwork
+
+WiX 5's `WixUI_InstallDir` shares `assets/wizard-dialog.bmp` (493×312)
+between welcome and completion pages. Its `ExitDialog` optional checkbox is
+**opaque** at x=135…355 and y=190…230 installer units, mapping to approximately
+x=180…473 and y=253…307 on the bitmap. Gradients under this native control
+produce a conspicuous rectangular patch.
+
+The image's **entire bottom footer** (y=248…311) is flat classic Windows
+button-face `#F0F0F0`, with a subtle divider at y=247. The interactive
+checkbox remains native and accessible, while its backing now blends into the
+same surface. Keep SVG, PNG preview, and shipping BMP synchronized.
+`scripts/check-windows-installer-bitmaps.py` verifies pixel identity and
+native checkbox surface in CI, packaged acceptance and Windows release.