From 883ff3931c57d3f6befc969b61aaf0eee2069b3c Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 19 Oct 2026 15:28:02 +0000 Subject: [PATCH 01/12] chore(desktop): add web vs macOS parity screenshot pipeline Co-authored-by: Tyler Dane --- .../scripts/compose-desktop-parity-shots.py | 90 +++++++++++++++++ .../scripts/record-macos-parity-captures.sh | 29 ++++++ .../desktop-web-parity-screenshots.yml | 99 +++++++++++++++++++ .../NativeImageSnapshotSupport.swift | 14 +++ .../NativeParityCaptureTests.swift | 86 ++++++++++++++++ docs/README.md | 2 +- .../desktop-visual-parity-screenshots.md | 41 ++++++++ e2e/parity/desktop-web-parity.spec.ts | 81 +++++++++++++++ e2e/parity/seed-demo-fixture.util.ts | 89 +++++++++++++++++ package.json | 1 + 10 files changed, 531 insertions(+), 1 deletion(-) create mode 100644 .github/scripts/compose-desktop-parity-shots.py create mode 100755 .github/scripts/record-macos-parity-captures.sh create mode 100644 .github/workflows/desktop-web-parity-screenshots.yml create mode 100644 apps/calendar-macos/CompassUITests/NativeParityCaptureTests.swift create mode 100644 docs/development/desktop-visual-parity-screenshots.md create mode 100644 e2e/parity/desktop-web-parity.spec.ts create mode 100644 e2e/parity/seed-demo-fixture.util.ts diff --git a/.github/scripts/compose-desktop-parity-shots.py b/.github/scripts/compose-desktop-parity-shots.py new file mode 100644 index 0000000000..e52adb5023 --- /dev/null +++ b/.github/scripts/compose-desktop-parity-shots.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 +"""Side-by-side macOS (left) and web (right) parity PNGs.""" +from __future__ import annotations + +import argparse +import sys +from pathlib import Path + +try: + from PIL import Image, ImageDraw, ImageFont +except ImportError: + print("Pillow is required: pip install Pillow", file=sys.stderr) + raise + +SURFACES = ( + "week-grid", + "mini-calendar", + "event-form", + "command-palette", + "settings", + "onboarding-welcome", +) + +LABEL_HEIGHT = 36 +GUTTER = 8 +TARGET_WIDTH = 1280 + + +def load_rgb(path: Path) -> Image.Image: + image = Image.open(path).convert("RGB") + if image.width != TARGET_WIDTH: + ratio = TARGET_WIDTH / image.width + image = image.resize( + (TARGET_WIDTH, max(1, int(image.height * ratio))), + Image.Resampling.LANCZOS, + ) + return image + + +def stack_pair(macos: Path, web: Path, title: str, out: Path) -> None: + left = load_rgb(macos) + right = load_rgb(web) + height = max(left.height, right.height) + + def pad(image: Image.Image) -> Image.Image: + if image.height >= height: + return image + canvas = Image.new("RGB", (image.width, height), (243, 238, 226)) + canvas.paste(image, (0, 0)) + return canvas + + left = pad(left) + right = pad(right) + total_w = left.width + GUTTER + right.width + canvas = Image.new("RGB", (total_w, height + LABEL_HEIGHT), (255, 255, 255)) + draw = ImageDraw.Draw(canvas) + draw.text((8, 8), f"{title} — macOS (left) · web (right)", fill=(64, 58, 47)) + canvas.paste(left, (0, LABEL_HEIGHT)) + canvas.paste(right, (left.width + GUTTER, LABEL_HEIGHT)) + out.parent.mkdir(parents=True, exist_ok=True) + canvas.save(out, format="PNG", optimize=True) + print(out) + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--macos-dir", type=Path, required=True) + parser.add_argument("--web-dir", type=Path, required=True) + parser.add_argument("--out-dir", type=Path, required=True) + args = parser.parse_args() + + for surface in SURFACES: + macos_path = args.macos_dir / f"{surface}.png" + web_path = args.web_dir / f"{surface}.png" + if not macos_path.is_file(): + print(f"Missing macOS capture: {macos_path}", file=sys.stderr) + continue + if not web_path.is_file(): + print(f"Missing web capture: {web_path}", file=sys.stderr) + continue + stack_pair( + macos_path, + web_path, + surface, + args.out_dir / f"{surface}-compare.png", + ) + + +if __name__ == "__main__": + main() diff --git a/.github/scripts/record-macos-parity-captures.sh b/.github/scripts/record-macos-parity-captures.sh new file mode 100755 index 0000000000..34a6e232e8 --- /dev/null +++ b/.github/scripts/record-macos-parity-captures.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# Export parity PNGs from NativeParityCaptureTests (PARITY_CAPTURE=1). +set -euo pipefail + +ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd) +OUT="${PARITY_OUTPUT_DIR:-$ROOT/build/desktop-parity/macos}" +mkdir -p "$OUT" +cd "$ROOT/apps/calendar-macos" + +if ! command -v xcodegen >/dev/null 2>&1; then + echo "xcodegen is required" >&2 + exit 1 +fi + +bash "$ROOT/.github/scripts/render-macos-icon.sh" +xcodegen generate + +export PARITY_CAPTURE=1 +export PARITY_OUTPUT_DIR="$OUT" +xcodebuild test \ + -project Compass.xcodeproj \ + -scheme Compass \ + -destination platform=macOS \ + -derivedDataPath build/DerivedData \ + -clonedSourcePackagesDirPath build/SourcePackages \ + -resultBundlePath build/ParityCapture.xcresult \ + -only-testing:CompassUITests/NativeParityCaptureTests/testExportParitySurfaces + +echo "Parity PNGs written to ${PARITY_OUTPUT_DIR}" diff --git a/.github/workflows/desktop-web-parity-screenshots.yml b/.github/workflows/desktop-web-parity-screenshots.yml new file mode 100644 index 0000000000..6ffcdd5b84 --- /dev/null +++ b/.github/workflows/desktop-web-parity-screenshots.yml @@ -0,0 +1,99 @@ +name: Desktop web parity screenshots + +# Captures the same Compass surfaces on macOS (XCUITest) and web (Playwright), +# then composes side-by-side PNGs for parity review. Re-run after each desktop +# UI PR via workflow_dispatch. +on: + workflow_dispatch: + pull_request: + paths: + - "apps/calendar-macos/CompassUI/**" + - "apps/calendar-macos/CompassUITests/**" + - "apps/calendar-web/**" + - "e2e/parity/**" + - ".github/workflows/desktop-web-parity-screenshots.yml" + - ".github/scripts/record-macos-parity-captures.sh" + - ".github/scripts/compose-desktop-parity-shots.py" + +permissions: + contents: read + +jobs: + web-capture: + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@v7 + + - uses: oven-sh/setup-bun@v2 + + - name: Install dependencies + run: bun install + + - name: Install Playwright Chromium + run: bunx playwright install chromium + + - name: Capture web parity surfaces + env: + PARITY_WEB_OUT: ${{ github.workspace }}/build/desktop-parity/web + run: bunx playwright test e2e/parity/desktop-web-parity.spec.ts --project chromium-desktop + + - uses: actions/upload-artifact@v7 + with: + name: desktop-parity-web + path: build/desktop-parity/web + if-no-files-found: error + retention-days: 30 + + macos-capture: + runs-on: macos-latest + timeout-minutes: 45 + steps: + - uses: actions/checkout@v7 + + - name: Install XcodeGen + run: brew install xcodegen + + - name: Capture macOS parity surfaces + run: bash .github/scripts/record-macos-parity-captures.sh + + - uses: actions/upload-artifact@v7 + with: + name: desktop-parity-macos + path: build/desktop-parity/macos + if-no-files-found: error + retention-days: 30 + + compose: + needs: [web-capture, macos-capture] + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v7 + + - uses: actions/download-artifact@v7 + with: + name: desktop-parity-web + path: build/desktop-parity/web + + - uses: actions/download-artifact@v7 + with: + name: desktop-parity-macos + path: build/desktop-parity/macos + + - name: Install Pillow + run: pip install Pillow + + - name: Compose side-by-side PNGs + run: | + python3 .github/scripts/compose-desktop-parity-shots.py \ + --macos-dir build/desktop-parity/macos \ + --web-dir build/desktop-parity/web \ + --out-dir build/desktop-parity/compare + + - uses: actions/upload-artifact@v7 + with: + name: desktop-parity-compare + path: build/desktop-parity/compare + if-no-files-found: error + retention-days: 90 diff --git a/apps/calendar-macos/CompassUITests/NativeImageSnapshotSupport.swift b/apps/calendar-macos/CompassUITests/NativeImageSnapshotSupport.swift index f0b8f127e4..66cedcf023 100644 --- a/apps/calendar-macos/CompassUITests/NativeImageSnapshotSupport.swift +++ b/apps/calendar-macos/CompassUITests/NativeImageSnapshotSupport.swift @@ -2,6 +2,8 @@ import XCTest enum NativeImageSnapshotSupport { static let recordSnapshots = ProcessInfo.processInfo.environment["RECORD_SNAPSHOTS"] == "1" + /// Export PNGs for web parity comparison (no ReferenceImages assert). + static let parityCapture = ProcessInfo.processInfo.environment["PARITY_CAPTURE"] == "1" @MainActor static func captureWindow(_ window: XCUIElement, named name: String, file: StaticString = #filePath, line: UInt = #line) { @@ -34,6 +36,18 @@ enum NativeImageSnapshotSupport { return } + if parityCapture, let outputDir = ProcessInfo.processInfo.environment["PARITY_OUTPUT_DIR"] { + let url = URL(fileURLWithPath: outputDir, isDirectory: true) + .appendingPathComponent("\(name).png") + do { + try FileManager.default.createDirectory(at: url.deletingLastPathComponent(), withIntermediateDirectories: true) + try screenshot.pngRepresentation.write(to: url) + } catch { + XCTFail("Failed to write parity capture \(name): \(error)", file: file, line: line) + } + return + } + guard FileManager.default.fileExists(atPath: referenceURL.path) else { return } diff --git a/apps/calendar-macos/CompassUITests/NativeParityCaptureTests.swift b/apps/calendar-macos/CompassUITests/NativeParityCaptureTests.swift new file mode 100644 index 0000000000..9e81b078a5 --- /dev/null +++ b/apps/calendar-macos/CompassUITests/NativeParityCaptureTests.swift @@ -0,0 +1,86 @@ +import XCTest + +/// Exports window PNGs for desktop ↔ web visual parity (see +/// `.github/workflows/desktop-web-parity-screenshots.yml`). Run with +/// `PARITY_CAPTURE=1` and `PARITY_OUTPUT_DIR` set; skips ReferenceImages diff. +final class NativeParityCaptureTests: XCTestCase { + private static let demoLaunchArgs = ["-COMPASS_FIXTURE", "demo", "-compass.theme", "light-beach"] + + @MainActor + func testExportParitySurfaces() { + guard NativeImageSnapshotSupport.parityCapture else { + throw XCTSkip("Set PARITY_CAPTURE=1 to export parity PNGs") + } + + captureWeekGrid() + captureMiniCalendar() + captureEventForm() + captureCommandPalette() + captureSettings() + captureWelcomeOnboarding() + } + + @MainActor + private func captureWeekGrid() { + let window = launchDemoWindow() + NativeImageSnapshotSupport.captureWindow(window, named: "week-grid") + } + + @MainActor + private func captureMiniCalendar() { + let window = launchDemoWindow() + XCTAssertTrue( + window.descendants(matching: .any)["compass-native-month-picker"].waitForExistence(timeout: 10)) + window.typeKey("i", modifierFlags: []) + NativeImageSnapshotSupport.captureWindow(window, named: "mini-calendar") + } + + @MainActor + private func captureEventForm() { + let window = launchDemoWindow() + window.typeKey("c", modifierFlags: []) + XCTAssertTrue( + window.descendants(matching: .any)["compass-event-form"].waitForExistence(timeout: 8)) + NativeImageSnapshotSupport.captureWindow(window, named: "event-form") + } + + @MainActor + private func captureCommandPalette() { + let window = launchDemoWindow() + window.typeKey("k", modifierFlags: [.command]) + XCTAssertTrue( + window.descendants(matching: .any)["compass-native-command-palette"].waitForExistence(timeout: 8)) + NativeImageSnapshotSupport.captureWindow(window, named: "command-palette") + } + + @MainActor + private func captureSettings() { + let window = launchDemoWindow() + window.typeKey(",", modifierFlags: [.command]) + XCTAssertTrue( + window.descendants(matching: .any)["compass-native-settings"].waitForExistence(timeout: 8)) + NativeImageSnapshotSupport.captureWindow(window, named: "settings") + } + + @MainActor + private func captureWelcomeOnboarding() { + let app = XCUIApplication.compassUITest() + app.launchArguments += ["-\(OnboardingStorageKeys.hasSeenWelcome)", "false", "-compass.theme", "light-beach"] + app.launch() + let window = app.windows["Compass"] + XCTAssertTrue(window.waitForExistence(timeout: 15)) + XCTAssertTrue( + window.descendants(matching: .any)["compass-native-welcome-modal"].waitForExistence(timeout: 10)) + NativeImageSnapshotSupport.captureWindow(window, named: "onboarding-welcome") + } + + @MainActor + private func launchDemoWindow() -> XCUIElement { + let app = XCUIApplication.compassUITest() + app.launchArguments += Self.demoLaunchArgs + app.launch() + let window = app.windows["Compass"] + XCTAssertTrue(window.waitForExistence(timeout: 15)) + return window + } +} diff --git a/docs/README.md b/docs/README.md index 076c404150..6e3753f465 100644 --- a/docs/README.md +++ b/docs/README.md @@ -24,7 +24,7 @@ Internal documentation for engineers and agents working in the Compass repo. - Trial, pricing, or Stripe: [Billing And Trial](./features/billing.md) - Public booking pages or availability rules: [Compass Calendar Booking (v1)](./features/booking.md) - Hiding or showing an event on the grid: [Hidden Events](./features/hidden-events.md), [Feature File Map](./development/feature-file-map.md#hidden-events) -- Native Mac app, deep links, or native menus: [Compass Desktop (macOS)](./features/desktop-client.md), [Desktop web parity checklist](./features/desktop-web-parity-checklist.md), [Desktop acceptance](./acceptance/desktop.md) +- Native Mac app, deep links, or native menus: [Compass Desktop (macOS)](./features/desktop-client.md), [Desktop web parity checklist](./features/desktop-web-parity-checklist.md), [Desktop visual parity screenshots](./development/desktop-visual-parity-screenshots.md), [Desktop acceptance](./acceptance/desktop.md) - A new calendar integration or Google-sync behavior: [Calendar providers](./features/calendar-providers.md), [Google Sync And SSE Flow](./features/google-sync-and-sse-flow.md), the `packages/sync` domain code directly ## Features diff --git a/docs/development/desktop-visual-parity-screenshots.md b/docs/development/desktop-visual-parity-screenshots.md new file mode 100644 index 0000000000..1fb70c0420 --- /dev/null +++ b/docs/development/desktop-visual-parity-screenshots.md @@ -0,0 +1,41 @@ +# Desktop visual parity screenshots + +Side-by-side PNGs compare **native macOS** (1280×820 content window) with **web** +(Playwright viewport 1280×820, Light Beach, demo-seed fixture) for dogfood review. + +## Run locally + +Web captures (Linux/macOS/Windows): + +```bash +bun run parity:screenshots:web +# output: build/desktop-parity/web/*.png +``` + +macOS captures (Mac with XcodeGen): + +```bash +PARITY_OUTPUT_DIR=build/desktop-parity/macos bash .github/scripts/record-macos-parity-captures.sh +``` + +Compose (requires Pillow: `pip install Pillow`): + +```bash +python3 .github/scripts/compose-desktop-parity-shots.py \ + --macos-dir build/desktop-parity/macos \ + --web-dir build/desktop-parity/web \ + --out-dir build/desktop-parity/compare +``` + +## CI + +Workflow: [`.github/workflows/desktop-web-parity-screenshots.yml`](../../.github/workflows/desktop-web-parity-screenshots.yml) + +- **workflow_dispatch** or PRs touching desktop UI / `e2e/parity/` +- Artifacts: `desktop-parity-web`, `desktop-parity-macos`, `desktop-parity-compare` + +Surfaces: `week-grid`, `mini-calendar`, `event-form`, `command-palette`, `settings`, `onboarding-welcome`. + +Web seed uses `CompassKit/Resources/Fixtures/demo-seed.json` and pins `compass.localCalendarId` to the export calendar id so events render like native `-COMPASS_FIXTURE demo`. + +Tracking: [#4331](https://github.com/KeepSoftwareSimple/compass-calendar/issues/4331). diff --git a/e2e/parity/desktop-web-parity.spec.ts b/e2e/parity/desktop-web-parity.spec.ts new file mode 100644 index 0000000000..fd1b3dba05 --- /dev/null +++ b/e2e/parity/desktop-web-parity.spec.ts @@ -0,0 +1,81 @@ +import { expect, test } from "@playwright/test"; +import { + installParityWebEnvironment, + PARITY_VIEWPORT, + prepareParityDemoWeek, +} from "./seed-demo-fixture.util"; +import { mkdirSync } from "node:fs"; +import { join } from "node:path"; + +const outputDir = + process.env.PARITY_WEB_OUT ?? + join(process.cwd(), "build", "desktop-parity", "web"); + +test.use({ + viewport: PARITY_VIEWPORT, + storageState: { cookies: [], origins: [] }, + timezoneId: "UTC", + colorScheme: "light", +}); + +test.describe("Desktop web parity captures", () => { + test.beforeAll(() => { + mkdirSync(outputDir, { recursive: true }); + }); + + test.beforeEach(async ({ page }, testInfo) => { + if (testInfo.title === "onboarding-welcome") return; + await installParityWebEnvironment(page); + }); + + test("week-grid", async ({ page }) => { + await prepareParityDemoWeek(page); + await page.screenshot({ path: join(outputDir, "week-grid.png") }); + }); + + test("mini-calendar", async ({ page }) => { + await prepareParityDemoWeek(page); + await page.keyboard.press("i"); + await expect(page.getByTestId("Month picker")).toBeVisible({ + timeout: 10_000, + }); + await page.screenshot({ path: join(outputDir, "mini-calendar.png") }); + }); + + test("event-form", async ({ page }) => { + await prepareParityDemoWeek(page); + await page.locator("#mainGrid").focus(); + await page.keyboard.press("c"); + await expect(page.getByLabel(/^title$/i)).toBeVisible({ + timeout: 10_000, + }); + await page.screenshot({ path: join(outputDir, "event-form.png") }); + }); + + test("command-palette", async ({ page }) => { + await prepareParityDemoWeek(page); + await page.keyboard.press("Control+K"); + await expect(page.getByLabel("Command palette search")).toBeVisible({ + timeout: 10_000, + }); + await page.screenshot({ path: join(outputDir, "command-palette.png") }); + }); + + test("settings", async ({ page }) => { + await prepareParityDemoWeek(page); + await page.keyboard.press("Control+,"); + await expect(page.getByRole("button", { name: "Accounts" })).toBeVisible({ + timeout: 10_000, + }); + await page.screenshot({ path: join(outputDir, "settings.png") }); + }); + + test("onboarding-welcome", async ({ page }) => { + await installParityWebEnvironment(page, { dismissOnboarding: false }); + await page.goto("/week", { waitUntil: "domcontentloaded" }); + await expect( + page.getByRole("dialog", { name: "Welcome to Compass Calendar" }), + ).toBeVisible({ timeout: 15_000 }); + await page.screenshot({ path: join(outputDir, "onboarding-welcome.png") }); + }); +}); diff --git a/e2e/parity/seed-demo-fixture.util.ts b/e2e/parity/seed-demo-fixture.util.ts new file mode 100644 index 0000000000..82d7b8611c --- /dev/null +++ b/e2e/parity/seed-demo-fixture.util.ts @@ -0,0 +1,89 @@ +import { type Page } from "@playwright/test"; +import { readFileSync } from "node:fs"; +import { join } from "node:path"; + +/** Matches `AppDelegate` default content size on macOS. */ +export const PARITY_VIEWPORT = { width: 1280, height: 820 } as const; + +const DEMO_SEED_PATH = join( + process.cwd(), + "apps/calendar-macos/CompassKit/Sources/CompassKit/Resources/Fixtures/demo-seed.json", +); + +type DemoSeedFixture = { + referenceNow: string; + calendarId: string; + events: unknown[]; +}; + +const demoFixture = JSON.parse( + readFileSync(DEMO_SEED_PATH, "utf8"), +) as DemoSeedFixture; + +type ParityWebOptions = { + /** When false, leave welcome modal eligible (onboarding capture). */ + dismissOnboarding?: boolean; +}; + +export async function installParityWebEnvironment( + page: Page, + options: ParityWebOptions = {}, +) { + const dismissOnboarding = options.dismissOnboarding ?? true; + const localStorageSeed: Record = { + "compass.theme": "light-beach", + "compass.migration.demo-data-seed-v2": "done", + "compass.localCalendarId": demoFixture.calendarId, + }; + if (dismissOnboarding) { + localStorageSeed["compass.onboarding.has-seen-welcome"] = "true"; + localStorageSeed["compass.onboarding.first-event-done"] = "dismissed"; + } + await page.clock.install({ time: new Date(demoFixture.referenceNow) }); + await page.addInitScript((flags) => { + for (const [key, value] of Object.entries(flags)) { + localStorage.setItem(key, value); + } + }, localStorageSeed); +} + +/** Load demo-seed.json into IndexedDB after the app has opened compass-local. */ +export async function prepareParityDemoWeek(page: Page) { + await page.goto("/week", { waitUntil: "domcontentloaded" }); + await page.locator("#timedColumns").waitFor({ timeout: 20_000 }); + await seedParityDemoEvents(page); + await page.reload({ waitUntil: "domcontentloaded" }); + await expectDemoGrid(page); +} + +export async function expectDemoGrid(page: Page) { + await page.locator("#timedColumns").waitFor({ timeout: 20_000 }); + await page + .getByRole("button", { name: /Try Compass|Morning standup/i }) + .first() + .waitFor({ timeout: 20_000 }); +} + +async function seedParityDemoEvents(page: Page) { + await page.evaluate(async (records) => { + const db = await new Promise((resolve, reject) => { + const request = indexedDB.open("compass-local"); + request.onerror = () => reject(request.error); + request.onsuccess = () => resolve(request.result); + }); + try { + await new Promise((resolve, reject) => { + const tx = db.transaction("events", "readwrite"); + const store = tx.objectStore("events"); + store.clear(); + for (const record of records) { + store.put(record); + } + tx.oncomplete = () => resolve(); + tx.onerror = () => reject(tx.error); + }); + } finally { + db.close(); + } + }, demoFixture.events); +} diff --git a/package.json b/package.json index 0cf7f78ba9..f0a0fda3de 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "test": "bun test:core && bun test:sync && bun test:web && bun test:backend && bun test:scripts && bun test:self-host", "test:a11y": "bunx playwright test e2e/accessibility", "test:e2e": "bunx playwright test", + "parity:screenshots:web": "PARITY_WEB_OUT=build/desktop-parity/web bunx playwright test e2e/parity/desktop-web-parity.spec.ts --project chromium-desktop --workers=1", "test:backend": "bun packages/scripts/src/testing/test-mongo-env.ts backend --", "test:backend:fast": "bun packages/scripts/src/testing/test-parallel.ts backend-fast -- --path-ignore-patterns '**/*.db.test.ts' --path-ignore-patterns '**/header-session.test.ts'", "test:backend:db": "bun packages/scripts/src/testing/test-mongo-env.ts backend -- './packages/backend/src/**/*.db.test.ts'", From 7fd759e8d5ced840dbe4e60a0640a7ee22d518f3 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 19 Oct 2026 15:30:23 +0000 Subject: [PATCH 02/12] ci(desktop): trigger parity screenshots on pipeline branch push Co-authored-by: Tyler Dane --- .github/workflows/desktop-web-parity-screenshots.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/desktop-web-parity-screenshots.yml b/.github/workflows/desktop-web-parity-screenshots.yml index 6ffcdd5b84..228e37f280 100644 --- a/.github/workflows/desktop-web-parity-screenshots.yml +++ b/.github/workflows/desktop-web-parity-screenshots.yml @@ -5,6 +5,9 @@ name: Desktop web parity screenshots # UI PR via workflow_dispatch. on: workflow_dispatch: + push: + branches: + - "cursor/desktop-parity-screenshots-2ef3" pull_request: paths: - "apps/calendar-macos/CompassUI/**" From 860c071a5c2acd8bdbe54bb1f465ce25916c0b8a Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 19 Oct 2026 16:12:34 +0000 Subject: [PATCH 03/12] fix(desktop): compile NativeParityCaptureTests for macOS CI export Co-authored-by: Tyler Dane --- .../CompassUITests/NativeParityCaptureTests.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/calendar-macos/CompassUITests/NativeParityCaptureTests.swift b/apps/calendar-macos/CompassUITests/NativeParityCaptureTests.swift index 9e81b078a5..ece6823c3c 100644 --- a/apps/calendar-macos/CompassUITests/NativeParityCaptureTests.swift +++ b/apps/calendar-macos/CompassUITests/NativeParityCaptureTests.swift @@ -1,3 +1,4 @@ +import CompassKit import XCTest /// Exports window PNGs for desktop ↔ web visual parity (see @@ -7,7 +8,7 @@ final class NativeParityCaptureTests: XCTestCase { private static let demoLaunchArgs = ["-COMPASS_FIXTURE", "demo", "-compass.theme", "light-beach"] @MainActor - func testExportParitySurfaces() { + func testExportParitySurfaces() throws { guard NativeImageSnapshotSupport.parityCapture else { throw XCTSkip("Set PARITY_CAPTURE=1 to export parity PNGs") } From 66aed2a9a4a99f47cecd947ad43d9a419830b7bb Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 19 Oct 2026 16:19:54 +0000 Subject: [PATCH 04/12] ci(desktop): cancel stale desktop parity workflow runs Co-authored-by: Tyler Dane --- .github/workflows/desktop-web-parity-screenshots.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/desktop-web-parity-screenshots.yml b/.github/workflows/desktop-web-parity-screenshots.yml index 228e37f280..0f229752a4 100644 --- a/.github/workflows/desktop-web-parity-screenshots.yml +++ b/.github/workflows/desktop-web-parity-screenshots.yml @@ -21,6 +21,10 @@ on: permissions: contents: read +concurrency: + group: desktop-parity-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: web-capture: runs-on: ubuntu-latest From 9710a1512ccc0193ba6286c8bfc14eb369ee67c8 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 19 Oct 2026 17:16:14 +0000 Subject: [PATCH 05/12] fix(desktop): pass PARITY_CAPTURE env to macOS UI test runner Co-authored-by: Tyler Dane --- .../scripts/record-macos-parity-captures.sh | 19 ++++++++++++++++--- .../NativeImageSnapshotSupport.swift | 18 ++++++++++++++++-- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/.github/scripts/record-macos-parity-captures.sh b/.github/scripts/record-macos-parity-captures.sh index 34a6e232e8..04536a21b3 100755 --- a/.github/scripts/record-macos-parity-captures.sh +++ b/.github/scripts/record-macos-parity-captures.sh @@ -15,8 +15,15 @@ fi bash "$ROOT/.github/scripts/render-macos-icon.sh" xcodegen generate -export PARITY_CAPTURE=1 -export PARITY_OUTPUT_DIR="$OUT" +# UI test runners do not inherit shell exports; launchctl setenv reaches XCTest. +launchctl setenv PARITY_CAPTURE 1 +launchctl setenv PARITY_OUTPUT_DIR "$OUT" +cleanup_env() { + launchctl unsetenv PARITY_CAPTURE 2>/dev/null || true + launchctl unsetenv PARITY_OUTPUT_DIR 2>/dev/null || true +} +trap cleanup_env EXIT + xcodebuild test \ -project Compass.xcodeproj \ -scheme Compass \ @@ -26,4 +33,10 @@ xcodebuild test \ -resultBundlePath build/ParityCapture.xcresult \ -only-testing:CompassUITests/NativeParityCaptureTests/testExportParitySurfaces -echo "Parity PNGs written to ${PARITY_OUTPUT_DIR}" +png_count=$(find "$OUT" -maxdepth 1 -name '*.png' 2>/dev/null | wc -l | tr -d ' ') +if [ "${png_count:-0}" -lt 1 ]; then + echo "No parity PNGs in ${OUT} (did the capture test skip?)" >&2 + exit 1 +fi + +echo "Parity PNGs written to ${OUT} (${png_count} files)" diff --git a/apps/calendar-macos/CompassUITests/NativeImageSnapshotSupport.swift b/apps/calendar-macos/CompassUITests/NativeImageSnapshotSupport.swift index 66cedcf023..342ef7aab5 100644 --- a/apps/calendar-macos/CompassUITests/NativeImageSnapshotSupport.swift +++ b/apps/calendar-macos/CompassUITests/NativeImageSnapshotSupport.swift @@ -3,7 +3,21 @@ import XCTest enum NativeImageSnapshotSupport { static let recordSnapshots = ProcessInfo.processInfo.environment["RECORD_SNAPSHOTS"] == "1" /// Export PNGs for web parity comparison (no ReferenceImages assert). - static let parityCapture = ProcessInfo.processInfo.environment["PARITY_CAPTURE"] == "1" + static var parityCapture: Bool { + if ProcessInfo.processInfo.environment["PARITY_CAPTURE"] == "1" { return true } + return ProcessInfo.processInfo.arguments.contains("-PARITY_CAPTURE") + } + + static var parityOutputDirectory: String? { + if let dir = ProcessInfo.processInfo.environment["PARITY_OUTPUT_DIR"], !dir.isEmpty { + return dir + } + let args = ProcessInfo.processInfo.arguments + if let index = args.firstIndex(of: "-PARITY_OUTPUT_DIR"), index + 1 < args.count { + return args[index + 1] + } + return nil + } @MainActor static func captureWindow(_ window: XCUIElement, named name: String, file: StaticString = #filePath, line: UInt = #line) { @@ -36,7 +50,7 @@ enum NativeImageSnapshotSupport { return } - if parityCapture, let outputDir = ProcessInfo.processInfo.environment["PARITY_OUTPUT_DIR"] { + if parityCapture, let outputDir = parityOutputDirectory { let url = URL(fileURLWithPath: outputDir, isDirectory: true) .appendingPathComponent("\(name).png") do { From ebf39db5c153a6707084526bc96d9606736903fc Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 19 Oct 2026 17:23:17 +0000 Subject: [PATCH 06/12] fix(desktop): stage parity PNGs where XCTest can write Co-authored-by: Tyler Dane --- .github/scripts/record-macos-parity-captures.sh | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/scripts/record-macos-parity-captures.sh b/.github/scripts/record-macos-parity-captures.sh index 04536a21b3..e576f22c5f 100755 --- a/.github/scripts/record-macos-parity-captures.sh +++ b/.github/scripts/record-macos-parity-captures.sh @@ -4,7 +4,9 @@ set -euo pipefail ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd) OUT="${PARITY_OUTPUT_DIR:-$ROOT/build/desktop-parity/macos}" -mkdir -p "$OUT" +# XCTest runs sandboxed and cannot write under the repo-root build/ tree; stage next to the Xcode project. +STAGING="$ROOT/apps/calendar-macos/build/parity-captures" +mkdir -p "$STAGING" "$OUT" cd "$ROOT/apps/calendar-macos" if ! command -v xcodegen >/dev/null 2>&1; then @@ -17,7 +19,7 @@ xcodegen generate # UI test runners do not inherit shell exports; launchctl setenv reaches XCTest. launchctl setenv PARITY_CAPTURE 1 -launchctl setenv PARITY_OUTPUT_DIR "$OUT" +launchctl setenv PARITY_OUTPUT_DIR "$STAGING" cleanup_env() { launchctl unsetenv PARITY_CAPTURE 2>/dev/null || true launchctl unsetenv PARITY_OUTPUT_DIR 2>/dev/null || true @@ -33,10 +35,11 @@ xcodebuild test \ -resultBundlePath build/ParityCapture.xcresult \ -only-testing:CompassUITests/NativeParityCaptureTests/testExportParitySurfaces -png_count=$(find "$OUT" -maxdepth 1 -name '*.png' 2>/dev/null | wc -l | tr -d ' ') +png_count=$(find "$STAGING" -maxdepth 1 -name '*.png' 2>/dev/null | wc -l | tr -d ' ') if [ "${png_count:-0}" -lt 1 ]; then - echo "No parity PNGs in ${OUT} (did the capture test skip?)" >&2 + echo "No parity PNGs in ${STAGING} (did the capture test skip?)" >&2 exit 1 fi -echo "Parity PNGs written to ${OUT} (${png_count} files)" +cp "$STAGING"/*.png "$OUT/" +echo "Parity PNGs written to ${OUT} (${png_count} files, staged in ${STAGING})" From 63bdeb361985333f1f69fbabad94e446ffcfbad1 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 19 Oct 2026 17:36:20 +0000 Subject: [PATCH 07/12] fix(desktop): write parity PNGs under /tmp for XCTest sandbox Co-authored-by: Tyler Dane --- .github/scripts/record-macos-parity-captures.sh | 4 ++-- .../CompassUITests/NativeImageSnapshotSupport.swift | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/scripts/record-macos-parity-captures.sh b/.github/scripts/record-macos-parity-captures.sh index e576f22c5f..a4101e6cc1 100755 --- a/.github/scripts/record-macos-parity-captures.sh +++ b/.github/scripts/record-macos-parity-captures.sh @@ -4,8 +4,8 @@ set -euo pipefail ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd) OUT="${PARITY_OUTPUT_DIR:-$ROOT/build/desktop-parity/macos}" -# XCTest runs sandboxed and cannot write under the repo-root build/ tree; stage next to the Xcode project. -STAGING="$ROOT/apps/calendar-macos/build/parity-captures" +# XCTest runs sandboxed; /tmp is writable from the UI test runner (repo build/ is not). +STAGING="${PARITY_STAGING_DIR:-/tmp/compass-parity-captures}" mkdir -p "$STAGING" "$OUT" cd "$ROOT/apps/calendar-macos" diff --git a/apps/calendar-macos/CompassUITests/NativeImageSnapshotSupport.swift b/apps/calendar-macos/CompassUITests/NativeImageSnapshotSupport.swift index 342ef7aab5..e00711d65c 100644 --- a/apps/calendar-macos/CompassUITests/NativeImageSnapshotSupport.swift +++ b/apps/calendar-macos/CompassUITests/NativeImageSnapshotSupport.swift @@ -50,7 +50,11 @@ enum NativeImageSnapshotSupport { return } - if parityCapture, let outputDir = parityOutputDirectory { + if parityCapture { + let outputDir = + parityOutputDirectory + ?? FileManager.default.temporaryDirectory + .appendingPathComponent("compass-parity-captures", isDirectory: true).path let url = URL(fileURLWithPath: outputDir, isDirectory: true) .appendingPathComponent("\(name).png") do { From 3e9a8baa2f8be8978041e3a5bc1e3a57dcb1e49d Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 19 Oct 2026 17:49:19 +0000 Subject: [PATCH 08/12] fix(desktop): export parity PNGs via ReferenceImages bundle path Co-authored-by: Tyler Dane --- .../scripts/record-macos-parity-captures.sh | 31 +++++++++++++------ .../NativeImageSnapshotSupport.swift | 14 ++++----- 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/.github/scripts/record-macos-parity-captures.sh b/.github/scripts/record-macos-parity-captures.sh index a4101e6cc1..99cdc1bf57 100755 --- a/.github/scripts/record-macos-parity-captures.sh +++ b/.github/scripts/record-macos-parity-captures.sh @@ -4,9 +4,8 @@ set -euo pipefail ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd) OUT="${PARITY_OUTPUT_DIR:-$ROOT/build/desktop-parity/macos}" -# XCTest runs sandboxed; /tmp is writable from the UI test runner (repo build/ is not). -STAGING="${PARITY_STAGING_DIR:-/tmp/compass-parity-captures}" -mkdir -p "$STAGING" "$OUT" +SURFACES=(week-grid mini-calendar event-form command-palette settings onboarding-welcome) +mkdir -p "$OUT" cd "$ROOT/apps/calendar-macos" if ! command -v xcodegen >/dev/null 2>&1; then @@ -19,10 +18,8 @@ xcodegen generate # UI test runners do not inherit shell exports; launchctl setenv reaches XCTest. launchctl setenv PARITY_CAPTURE 1 -launchctl setenv PARITY_OUTPUT_DIR "$STAGING" cleanup_env() { launchctl unsetenv PARITY_CAPTURE 2>/dev/null || true - launchctl unsetenv PARITY_OUTPUT_DIR 2>/dev/null || true } trap cleanup_env EXIT @@ -35,11 +32,25 @@ xcodebuild test \ -resultBundlePath build/ParityCapture.xcresult \ -only-testing:CompassUITests/NativeParityCaptureTests/testExportParitySurfaces -png_count=$(find "$STAGING" -maxdepth 1 -name '*.png' 2>/dev/null | wc -l | tr -d ' ') -if [ "${png_count:-0}" -lt 1 ]; then - echo "No parity PNGs in ${STAGING} (did the capture test skip?)" >&2 +REF_DIR=$(find "$ROOT/apps/calendar-macos/build/DerivedData/Build/Products" -type d -path '*CompassUITests.xctest/Contents/Resources/ReferenceImages' 2>/dev/null | head -1) +if [ -z "${REF_DIR:-}" ] || [ ! -d "$REF_DIR" ]; then + echo "Could not find CompassUITests ReferenceImages output under DerivedData" >&2 exit 1 fi -cp "$STAGING"/*.png "$OUT/" -echo "Parity PNGs written to ${OUT} (${png_count} files, staged in ${STAGING})" +copied=0 +for name in "${SURFACES[@]}"; do + if [ -f "$REF_DIR/$name.png" ]; then + cp "$REF_DIR/$name.png" "$OUT/" + copied=$((copied + 1)) + else + echo "Missing parity PNG: $REF_DIR/$name.png" >&2 + fi +done + +if [ "$copied" -lt "${#SURFACES[@]}" ]; then + echo "Expected ${#SURFACES[@]} parity PNGs, copied $copied" >&2 + exit 1 +fi + +echo "Parity PNGs written to ${OUT} (${copied} files, from ${REF_DIR})" diff --git a/apps/calendar-macos/CompassUITests/NativeImageSnapshotSupport.swift b/apps/calendar-macos/CompassUITests/NativeImageSnapshotSupport.swift index e00711d65c..77619e96d9 100644 --- a/apps/calendar-macos/CompassUITests/NativeImageSnapshotSupport.swift +++ b/apps/calendar-macos/CompassUITests/NativeImageSnapshotSupport.swift @@ -51,14 +51,14 @@ enum NativeImageSnapshotSupport { } if parityCapture { - let outputDir = - parityOutputDirectory - ?? FileManager.default.temporaryDirectory - .appendingPathComponent("compass-parity-captures", isDirectory: true).path - let url = URL(fileURLWithPath: outputDir, isDirectory: true) - .appendingPathComponent("\(name).png") + guard let url = referenceImageURL(named: name) else { + XCTFail("Could not resolve ReferenceImages bundle path for parity \(name)", file: file, line: line) + return + } do { - try FileManager.default.createDirectory(at: url.deletingLastPathComponent(), withIntermediateDirectories: true) + try FileManager.default.createDirectory( + at: url.deletingLastPathComponent(), + withIntermediateDirectories: true) try screenshot.pngRepresentation.write(to: url) } catch { XCTFail("Failed to write parity capture \(name): \(error)", file: file, line: line) From da1a12c415da0dc35e53ea95a4196043d1c48486 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 19 Oct 2026 18:00:18 +0000 Subject: [PATCH 09/12] fix(desktop): export macOS parity PNGs from xcresult attachments Co-authored-by: Tyler Dane --- .../scripts/record-macos-parity-captures.sh | 65 ++++++++++++------- .../NativeImageSnapshotSupport.swift | 13 +--- 2 files changed, 43 insertions(+), 35 deletions(-) diff --git a/.github/scripts/record-macos-parity-captures.sh b/.github/scripts/record-macos-parity-captures.sh index 99cdc1bf57..e93542dde2 100755 --- a/.github/scripts/record-macos-parity-captures.sh +++ b/.github/scripts/record-macos-parity-captures.sh @@ -4,6 +4,7 @@ set -euo pipefail ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd) OUT="${PARITY_OUTPUT_DIR:-$ROOT/build/desktop-parity/macos}" +RESULT="$ROOT/apps/calendar-macos/build/ParityCapture.xcresult" SURFACES=(week-grid mini-calendar event-form command-palette settings onboarding-welcome) mkdir -p "$OUT" cd "$ROOT/apps/calendar-macos" @@ -16,12 +17,7 @@ fi bash "$ROOT/.github/scripts/render-macos-icon.sh" xcodegen generate -# UI test runners do not inherit shell exports; launchctl setenv reaches XCTest. launchctl setenv PARITY_CAPTURE 1 -cleanup_env() { - launchctl unsetenv PARITY_CAPTURE 2>/dev/null || true -} -trap cleanup_env EXIT xcodebuild test \ -project Compass.xcodeproj \ @@ -32,25 +28,48 @@ xcodebuild test \ -resultBundlePath build/ParityCapture.xcresult \ -only-testing:CompassUITests/NativeParityCaptureTests/testExportParitySurfaces -REF_DIR=$(find "$ROOT/apps/calendar-macos/build/DerivedData/Build/Products" -type d -path '*CompassUITests.xctest/Contents/Resources/ReferenceImages' 2>/dev/null | head -1) -if [ -z "${REF_DIR:-}" ] || [ ! -d "$REF_DIR" ]; then - echo "Could not find CompassUITests ReferenceImages output under DerivedData" >&2 +if [ ! -d "$RESULT" ]; then + echo "Missing result bundle at ${RESULT}" >&2 exit 1 fi -copied=0 -for name in "${SURFACES[@]}"; do - if [ -f "$REF_DIR/$name.png" ]; then - cp "$REF_DIR/$name.png" "$OUT/" - copied=$((copied + 1)) - else - echo "Missing parity PNG: $REF_DIR/$name.png" >&2 - fi -done - -if [ "$copied" -lt "${#SURFACES[@]}" ]; then - echo "Expected ${#SURFACES[@]} parity PNGs, copied $copied" >&2 - exit 1 -fi +RAW="$(mktemp -d)" +trap 'rm -rf "$RAW"; cleanup_env' EXIT +xcrun xcresulttool export attachments --path "$RESULT" --output-path "$RAW" >/dev/null + +python3 - "$RAW" "$OUT" "${SURFACES[@]}" <<'PY' +import json +import os +import re +import shutil +import sys + +raw, out, *surfaces = sys.argv[1], sys.argv[2], *sys.argv[3:] +wanted = set(surfaces) + +with open(os.path.join(raw, "manifest.json")) as handle: + manifest = json.load(handle) + +found: dict[str, str] = {} +for test in manifest: + for attachment in test.get("attachments", []): + if attachment.get("isAssociatedWithFailure"): + continue + exported = attachment["exportedFileName"] + if not exported.lower().endswith(".png"): + continue + name = attachment.get("suggestedHumanReadableName", exported) + name = re.sub(r"_\d+_[0-9A-F-]{36}(\.\w+)?$", "", name) + base = os.path.splitext(name)[0] + if base in wanted and base not in found: + found[base] = os.path.join(raw, exported) + +missing = [s for s in surfaces if s not in found] +if missing: + raise SystemExit(f"Missing parity attachments: {', '.join(missing)}") + +for surface, src in found.items(): + shutil.copyfile(src, os.path.join(out, f"{surface}.png")) -echo "Parity PNGs written to ${OUT} (${copied} files, from ${REF_DIR})" +print(f"Parity PNGs written to {out} ({len(found)} files)") +PY diff --git a/apps/calendar-macos/CompassUITests/NativeImageSnapshotSupport.swift b/apps/calendar-macos/CompassUITests/NativeImageSnapshotSupport.swift index 77619e96d9..f8286e0827 100644 --- a/apps/calendar-macos/CompassUITests/NativeImageSnapshotSupport.swift +++ b/apps/calendar-macos/CompassUITests/NativeImageSnapshotSupport.swift @@ -51,18 +51,7 @@ enum NativeImageSnapshotSupport { } if parityCapture { - guard let url = referenceImageURL(named: name) else { - XCTFail("Could not resolve ReferenceImages bundle path for parity \(name)", file: file, line: line) - return - } - do { - try FileManager.default.createDirectory( - at: url.deletingLastPathComponent(), - withIntermediateDirectories: true) - try screenshot.pngRepresentation.write(to: url) - } catch { - XCTFail("Failed to write parity capture \(name): \(error)", file: file, line: line) - } + // CI exports PNGs from the .xcresult attachments (the test bundle cannot write to disk). return } From bffc13414a7d58cac271a7841e6ba9c21eefc15b Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 19 Oct 2026 18:07:13 +0000 Subject: [PATCH 10/12] fix(desktop): unset PARITY_CAPTURE after parity export Co-authored-by: Tyler Dane --- .github/scripts/record-macos-parity-captures.sh | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/scripts/record-macos-parity-captures.sh b/.github/scripts/record-macos-parity-captures.sh index e93542dde2..18656ec07a 100755 --- a/.github/scripts/record-macos-parity-captures.sh +++ b/.github/scripts/record-macos-parity-captures.sh @@ -34,7 +34,11 @@ if [ ! -d "$RESULT" ]; then fi RAW="$(mktemp -d)" -trap 'rm -rf "$RAW"; cleanup_env' EXIT +cleanup() { + rm -rf "$RAW" + launchctl unsetenv PARITY_CAPTURE 2>/dev/null || true +} +trap cleanup EXIT xcrun xcresulttool export attachments --path "$RESULT" --output-path "$RAW" >/dev/null python3 - "$RAW" "$OUT" "${SURFACES[@]}" <<'PY' @@ -73,3 +77,5 @@ for surface, src in found.items(): print(f"Parity PNGs written to {out} ({len(found)} files)") PY + +launchctl unsetenv PARITY_CAPTURE 2>/dev/null || true From 2bebf2b42cfe3e1372a43beed6c22f26d4c5d27c Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 19 Oct 2026 18:16:23 +0000 Subject: [PATCH 11/12] docs(desktop): record parity compare run 39299882448 in checklist Co-authored-by: Tyler Dane --- docs/features/desktop-web-parity-checklist.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/docs/features/desktop-web-parity-checklist.md b/docs/features/desktop-web-parity-checklist.md index 0a113c127f..5f072a0a29 100644 --- a/docs/features/desktop-web-parity-checklist.md +++ b/docs/features/desktop-web-parity-checklist.md @@ -317,6 +317,7 @@ Each PR should link a child issue, include before/after screenshots (web Chromiu | --- | --- | | 2026-10-19 | Initial audit checklist for #4331 dogfood gate | | 2026-10-19 | §7 visual evidence table (web CI captures; macOS/compare pending runner) | +| 2026-10-19 | §7 updated from workflow run 39299882448 (macOS + compare artifacts) | --- @@ -324,15 +325,15 @@ Each PR should link a child issue, include before/after screenshots (web Chromiu Automated captures: workflow `desktop-web-parity-screenshots.yml` (PR #4596). Compare artifact `desktop-parity-compare` pairs `web/` and `macos/` PNGs per surface. -| Surface | Web CI | macOS CI | Match | Diff notes (from code + web reference until compare lands) | +| Surface | Web CI | macOS CI | Match | Diff notes (compare run 39299882448, pre–v2 merge on macOS) | | --- | --- | --- | --- | --- | -| Week grid | ✅ run 39278514788 | ⏳ queue | 🟡 | #4604 now line gradient/rim; header focus ring still AppKit | -| Mini-calendar | ✅ | ⏳ queue | 🟡 | Custom grid styling vs react-datepicker theme | -| Event form | ✅ | ⏳ queue | 🟡 | #4603 FormCard grouping, MM.DD.YY, save bar, toolbar keycaps | -| Command palette | ✅ | ⏳ queue | 🟡 | #4602 fuzzy filter, label highlight, shadow, keycaps | -| Settings | ✅ | ⏳ queue | 🟡 | Not re-audited this pass | -| Onboarding welcome | ✅ | ⏳ queue | 🟡 | Not re-audited this pass | +| Week grid | ✅ | ✅ | 🟡 | macOS day view vs web week columns; web shows events + now column; #4604 now line on main | +| Mini-calendar | ✅ | ✅ | 🟡 | Selected day ring matches; month chrome and spacing differ | +| Event form | ✅ | ✅ | 🟡 | macOS slash dates + steppers vs web MM.DD.YY chips; #4603 targets form parity | +| Command palette | ✅ | ✅ | 🟡 | macOS flat list vs web icons, keycaps, selection ring; #4602 in queue | +| Settings | ✅ | ✅ | 🟡 | Layout close; verify row density after theme PR | +| Onboarding welcome | ✅ | ✅ | 🟡 | Copy aligned; button/footer spacing differs slightly | **Keyboard / tips (non-screenshot):** #4605 Shift+weekday column jump + sidebar tip strip; partial vs web full `useShiftHoldEventHints` (weekend `s`→u/a, digit jump, quick-time slots still ❌). -When `macos-capture` completes, replace 🟡 rows with ✅/❌ from side-by-side PNGs and open focused fix PRs for any new visual deltas. +Re-run compare after #4602–#4605 merge; promote rows to ✅ when side-by-side PNGs match within tolerance. From 507eac65a7545719dfe0108ec350d1aa2034fae3 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 19 Oct 2026 18:48:09 +0000 Subject: [PATCH 12/12] ci(e2e): assign parity specs to shard 3 Co-authored-by: Tyler Dane --- .github/workflows/test-e2e.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml index a7fa0fb65f..45b3590f14 100644 --- a/.github/workflows/test-e2e.yml +++ b/.github/workflows/test-e2e.yml @@ -90,7 +90,7 @@ jobs: - shard: 2 specs: e2e/booking e2e/oauth e2e/auth e2e/billing - shard: 3 - specs: e2e/timed e2e/event-form + specs: e2e/timed e2e/event-form e2e/parity - shard: 4 specs: e2e/onboarding e2e/calendars e2e/life e2e/navigation e2e/attendees container: