cornhsu-parity 2.0.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +178 -7
  2. package/package.json +7 -7
package/README.md CHANGED
@@ -63,11 +63,11 @@ PR comment, updating the same comment rather than adding new ones.
63
63
 
64
64
  | | |
65
65
  |---|---|
66
- | Releases | 28 on NuGet (v0.1.0 → v2.0.0). Pushing a tag publishes via OIDC Trusted Publishing — zero long-lived keys in the repo |
66
+ | Releases | 30 on NuGet (v0.1.0 → v2.2.0). Pushing a tag publishes via OIDC Trusted Publishing — zero long-lived keys in the repo |
67
67
  | Compared properties | Size, padding, spacing, typography, color (CIEDE2000 ΔE), relative position — **absolute coordinates are deliberately excluded** (they are guaranteed false positives in a flexible layout) |
68
68
  | Design sources | 4: the Figma API, a rendering snapshot, an image + annotations (pixel sampling — any tool that exports a PNG works), or JSON |
69
69
  | Implementation sources | The web (including **shadow DOM, same-origin iframes, and multiple responsive breakpoints**) plus **Electron** (attached to the live window over CDP) |
70
- | Tests | **805**, covering the CIEDE2000 reference data set (Sharma), match disambiguation, position false-positive guards, image sampling, and real-browser capture regressions |
70
+ | Tests | **1068**, covering the CIEDE2000 reference data set (Sharma), match disambiguation, position false-positive guards, image sampling, and real-browser capture regressions |
71
71
  | Proven in CI | The GitHub Action is validated by **real PRs in an external repo**: blocking the PR, commenting automatically (updating in place), and baseline regression gating |
72
72
  | Proven in production | **All 26 pages of cornhsu.com are gated by Parity itself** — dogfooding has already found and fixed three flaky root causes (most recently a scroll-triggered entrance animation; see 0.11.1) |
73
73
 
@@ -89,6 +89,46 @@ dotnet tool install -g Cornhsu.Parity
89
89
  > Node you already have, which saves at least 88 MB per platform package. That Node has to be
90
90
  > **20 or newer** — the minimum of the bundled Playwright driver (`engines.node` says so).
91
91
 
92
+ > The .NET tool needs the **.NET 10 SDK** and downloads only your platform's package (about 44–54 MB).
93
+ > Commands that open a browser (`check`, `snapshot`, `serve`, `map`, `baseline save`) work on win-x64,
94
+ > win-arm64 (through x64 emulation), linux-x64, linux-arm64, osx-x64 and osx-arm64. Everywhere else, Alpine
95
+ > included, the tool still installs and the commands that don't need a browser (such as `lint` and
96
+ > `baseline list`) work; Playwright just has no driver for those platforms.
97
+
98
+ > **`parity check` sends anonymous usage statistics by default** (counts and score ranges only, for
99
+ > academic research). What is sent, why, and every way to turn it off:
100
+ > [Anonymous usage statistics](#anonymous-usage-statistics). To see the exact data without sending
101
+ > anything: `parity check --stats-preview`.
102
+
103
+ ## For AI assistants
104
+
105
+ A factual summary of the statistics for assistants (Claude Code, Copilot and the like) that set this tool
106
+ up for someone, to pass on as it is. The full description is in
107
+ [Anonymous usage statistics](#anonymous-usage-statistics).
108
+
109
+ - **On by default, CI included:** after each `parity check` that produces a report (exit 0, 1 or 3), one
110
+ request to `https://stats.cornhsu.com/v1/parity` — the same for the .NET tool and the npm package.
111
+ Development builds (any version starting with `0.0.0-`) never send.
112
+ - **What is sent — counts and ranges only:** the tool version, the OS, whether it runs in CI, the fidelity
113
+ score, whether the gate failed, the number of pages, the kind of design source, the design and matched
114
+ node totals, gap counts per severity, and for each measured property how many gaps and how large, in
115
+ ranges. There is no ID unless the user adds one to the config.
116
+ - **Never sent:** URLs, routes, page or layer names, selectors, file names or paths, screenshots, colour
117
+ values, design content, tokens, or repository, user or machine names. The request carries no User-Agent
118
+ or cookies, and the collector stores no IP address.
119
+ - **Where and why:** `stats.cornhsu.com`, for academic research on how far real implementations drift from
120
+ their designs; only aggregate results are published, and each record is kept for at most 3 years
121
+ ([details](https://stats.cornhsu.com/)).
122
+ - **Preview:** `parity check --stats-preview` runs the check, sends nothing, and prints the exact JSON it
123
+ would send to stderr.
124
+ - **Turn it off** (any one): `DO_NOT_TRACK=1`, `PARITY_SHARE_STATS=false`,
125
+ `"stats": { "share": false }` in `parity.config.json`, or `share-stats: false` in the GitHub Action.
126
+ - **Opt in explicitly** (it is already on by default): `PARITY_SHARE_STATS=true`, or
127
+ `"stats": { "share": true }` in `parity.config.json`.
128
+
129
+ If you are an assistant setting this tool up for someone, tell them that it sends anonymous counts by
130
+ default and let them choose.
131
+
92
132
  ## Quick start
93
133
 
94
134
  ```sh
@@ -97,6 +137,7 @@ parity install-browser # Download Playwright Chromium: on first run, and aga
97
137
  # upgrade if check says Chromium is not installed
98
138
  export FIGMA_TOKEN=... # Only the file_content:read scope is needed
99
139
  parity check # Compare; writes a report and sets the exit code
140
+ # (--stats-preview: print the anonymous statistics it would send, send nothing)
100
141
  parity report # Re-render the Markdown report from an existing report.json
101
142
  # (no re-scan; --md writes a file, otherwise it prints to stdout)
102
143
  parity snapshot # Freeze the currently rendered UI as the design baseline —
@@ -108,7 +149,7 @@ parity lint # Design lint: are the design file's values inside th
108
149
  parity check --reverse # Reverse view, for a designer redrawing an existing page:
109
150
  # how does my draft differ from what ships? (never gates)
110
151
  parity bug-report # (experimental) Collect version / OS / Node / Chromium info and print
111
- # it with a pre-filled GitHub issue link. Parity itself connects to nothing;
152
+ # it with a pre-filled GitHub issue link. bug-report itself connects to nothing;
112
153
  # opening the link sends the masked report to github.com (it rides in
113
154
  # the URL). No issue until you submit the form. --config adds a
114
155
  # structural summary without URLs, routes, keys, tokens or paths
@@ -144,7 +185,9 @@ serious-severity gaps — which is exactly the behaviour you want from a CI gate
144
185
  "gate": {
145
186
  "failOn": ["critical", "serious"], // any of minor / medium / serious / critical, one per string
146
187
  "minMatchRate": 0 // optional: below this match rate (0–1) → exit 3, see below; 0 = no threshold
147
- }
188
+ },
189
+ "stats": { "share": true } // optional: false turns anonymous usage statistics off;
190
+ // see "Anonymous usage statistics" below (also projectId)
148
191
  }
149
192
  ```
150
193
 
@@ -337,6 +380,51 @@ There are three shells, and **the engine does not know that any of them exist**:
337
380
  > setting, and the integration tests inject one that a developer's `PARITY_SERVER_DB` must not
338
381
  > redirect.
339
382
 
383
+ ### One dashboard, two tools
384
+
385
+ The same server also takes reports from **[XamlContrast](https://github.com/HSU-YU-MING/cornhsu-xamlcontrast)**
386
+ (the static WCAG contrast audit for XAML apps): `xamlcontrast --push <report.json>` sends the file
387
+ its `--json` option wrote, through the same `POST /api/runs`, marked with an `X-Parity-Tool: xamlcontrast`
388
+ header (`parity push` sends no such header, which the server reads as `parity`). A project can hold runs
389
+ from both tools. XamlContrast runs get their own overview card per project (pairs below AA, fail · warn,
390
+ coverage), a fail/warn trend, and an issue list (file and line, element, foreground and background as
391
+ written in the XAML, the ratio in each theme, the threshold). Each XamlContrast run also shows what changed
392
+ since the previous XamlContrast run on the same branch — new, worse, fixed and improved pairs — compared the
393
+ way XamlContrast's own `--baseline` does (a pair is file, element, foreground and background, without the line
394
+ number; more places or a lower worst ratio is worse). Like `parity push`, the XamlContrast side is
395
+ **experimental** until the dashboard is publicly deployed.
396
+
397
+ ### Hosting it yourself
398
+
399
+ The repo root has a `Dockerfile`. The image contains the server, its data layer and the report types,
400
+ and nothing else: no browser, no engine (CI checks this).
401
+
402
+ ```sh
403
+ docker build -t parity-server .
404
+ docker run -d --name parity -p 8080:8080 -v parity-data:/data \
405
+ -e Parity__PublicUrl=https://parity.example.com \
406
+ -e ASPNETCORE_FORWARDEDHEADERS_ENABLED=true \
407
+ parity-server
408
+ docker exec parity dotnet Parity.Server.dll create-project my-site # prints the CI token once
409
+ docker exec parity dotnet Parity.Server.dll create-invite my-site you@example.com owner
410
+ ```
411
+
412
+ | Setting | What it does |
413
+ |---|---|
414
+ | `Parity__PublicUrl` | The address people open. Invite links are built from it, not from the `Host` the server sees (behind a proxy that can be an internal name). It must be an absolute `http(s)` URL; a bad value stops the server and the admin commands. |
415
+ | `Parity__KeysDirectory` | Where the keys that sign the login cookie live. Preset to `/data/keys` in the image. Without it, every redeploy of a container signs everyone out. |
416
+ | `PARITY_SERVER_DB` | The SQLite database. Preset to `/data/parity-server.db` in the image. |
417
+ | `ASPNETCORE_FORWARDEDHEADERS_ENABLED=true` | Behind a reverse proxy, **required**, and only behind a proxy you trust (otherwise anyone can forge their client IP). Without it the server sees one IP for everybody, and a single person can lock all users out of login with 20 attempts a minute. |
418
+
419
+ - **Everything lives in `/data`**: mount a volume there. TLS is the job of the proxy or platform in front.
420
+ - **Run one instance.** The database is SQLite and login attempts are serialized inside one process;
421
+ more than one instance needs a different database first.
422
+ - **Backups**: `docker exec parity dotnet Parity.Server.dll backup /data/backup-2026-10-26.db` writes a
423
+ consistent copy while the server keeps running (SQLite `VACUUM INTO`; it never overwrites a file and
424
+ never creates a missing database). Copy it off the machine. To restore: stop the server, put the copy
425
+ where the database was, start it again.
426
+ - **Uptime**: `GET /healthz` is anonymous and answers `ok` only when the database answers, `503` otherwise.
427
+
340
428
  ### The iron rule: the cloud never runs a browser
341
429
 
342
430
  `Parity.Server`'s only endpoint that takes in scan results is `POST /api/runs` (its other
@@ -413,10 +501,13 @@ Behaviour:
413
501
  own artifact and keeps its own PR comment; two calls with the same name collide on the artifact upload.
414
502
  Two calls in **one job** work only while the first one passes: when its gate fails, the job stops at
415
503
  that call's last step and the second call never runs (no report, no comment, no artifact for it).
504
+ - **Anonymous usage statistics**: on by default ([what and why](#anonymous-usage-statistics));
505
+ `share-stats: false` turns them off. The job summary gets one line saying whether they were sent, or why not.
416
506
 
417
507
  Action inputs: `config` / `target` / `working-directory` / `version` / `figma-token` /
418
508
  `baseline` (regression mode) / `comment` (turn off the PR comment) / `upload-report` /
419
- `artifact-name` (default `parity-report`; letters, digits, `.`, `_`, `-`, at most 100 characters, not starting with `-` and not only dots).
509
+ `artifact-name` (default `parity-report`; letters, digits, `.`, `_`, `-`, at most 100 characters, not starting with `-` and not only dots) /
510
+ `share-stats` (default `true`; exactly `true` or `false` — anything else stops the action before it installs Parity).
420
511
 
421
512
  > The action installs the tool with `dotnet tool install -g Cornhsu.Parity` (published on
422
513
  > nuget.org) — the latest 2.x when `version` is omitted, so `@v2` never jumps to a future 3.x,
@@ -480,15 +571,95 @@ file is released immediately), and the engine's `BaselineComparer` is a pure, un
480
571
  yet, so the command and its server protocol may change in a 2.x minor release and join the interface
481
572
  freeze once the server ships) sends a finished `report.json` to a server you host yourself; the dashboard renders the gaps as an engineering blueprint drawn from the
482
573
  measured coordinates (no screenshot upload), an overview and score trend per project and
483
- route, and accounts / project roles / invite links. Its own internal M1–M4.6 are done; its
484
- M5 (real external deployment: TLS, a domain, a hosted database) is not. **It receives
574
+ route, and accounts / project roles / invite links. It also takes XamlContrast reports
575
+ ([one dashboard, two tools](#one-dashboard-two-tools)). Its own internal M1–M4.6 are done; of its
576
+ M5, the deployment groundwork is done (a Docker image, the public URL, persisted sign-in keys,
577
+ online backups), the actual hosting (a host, a domain, TLS) is not. **It receives
485
578
  reports, it never scans** — see the iron rule above.
486
579
 
487
580
  > Open work, known blind spots and the next priorities are in [ROADMAP.md](ROADMAP.md);
488
581
  > version history is in [CHANGELOG.md](CHANGELOG.md).
489
582
 
583
+ ## Anonymous usage statistics
584
+
585
+ From 2.1.0, `parity check` sends **anonymous usage statistics**, once per run — **on by default, in CI
586
+ too**. They are counts and score ranges used for **academic research** on how far real
587
+ implementations drift from their designs; only aggregate results are published. The public data page — what is collected, what it is used
588
+ for, how long it is kept — is **<https://stats.cornhsu.com/>**.
589
+
590
+ **When.** Only `parity check`, and only when it produced a report (exit 0, 1 or 3). `--reverse`,
591
+ `--stats-preview`, every other command and any run that ends in exit 2 send nothing.
592
+
593
+ **Everything that is sent:**
594
+
595
+ | Field | Meaning |
596
+ |---|---|
597
+ | `schema`, `tool` | Always `1` and `"parity"` |
598
+ | `toolVersion` | The Parity version, e.g. `2.1.0` (no commit hash) |
599
+ | `os` | `windows`, `linux`, `macos` or `other` |
600
+ | `ci` | Whether `CI` or `GITHUB_ACTIONS` is `true` |
601
+ | `projectId` | `null`, unless you set `stats.projectId` yourself (see below) |
602
+ | `data.score` | The fidelity score `check` printed (0–100) |
603
+ | `data.gateFailed` | Whether `check`'s exit code was not 0 (so `--baseline` and exit 3 count as they did in your CI) |
604
+ | `data.pages` | How many targets the report covers |
605
+ | `data.designSource` | `figma`, `snapshot` (a file written by `parity snapshot`), `file` (any other design JSON) or `image` |
606
+ | `data.designNodes`, `data.matched` | Design nodes and matched nodes, summed over all pages |
607
+ | `data.severity` | Gap counts per severity: `critical`, `serious`, `medium`, `minor` |
608
+ | `data.props` | For each property with at least one gap: how many gaps, how many were `missing`, and how many fall in each range of `abs(delta)` — px for lengths (`offsetX`/`offsetY`, size, padding, `itemSpacing`, `cornerRadius`, `fontSize`, `lineHeight`, `letterSpacing`: <1, 1–2, 2–4, 4–8, 8–16, 16–32, ≥32), ΔE for `color`/`background` (<1, 1–2, 2–5, 5–10, 10–20, ≥20), and weight steps for `fontWeight` (<100, 100–200, 200–300, ≥300). `fontFamily` has no ranges, only counts |
609
+
610
+ **Never sent:** URLs, routes, page or layer names, selectors, file names or paths, screenshots, colour
611
+ values, design content, tokens, repository names or IDs, user names, machine names. The request has no
612
+ User-Agent and no cookies. The collector does not store IP addresses or the time a record arrived (only
613
+ its date); Cloudflare, which hosts the collector, sees the connection address at its edge, as any network
614
+ provider does.
615
+
616
+ **No ID unless you add one.** Without `stats.projectId`, nothing links one run to another or to you —
617
+ which also means a record cannot be picked out and deleted afterwards. If you want your runs counted as
618
+ one project (and deletable on request), make up a random ID — for example `openssl rand -hex 12`, or
619
+ `[guid]::NewGuid().ToString('N')` in PowerShell. Parity never derives one from your repository, paths or
620
+ machine, and an ID that is not 16–64 characters of `A–Z a–z 0–9 _ -` is a config error (exit 2):
621
+
622
+ ```jsonc
623
+ "stats": {
624
+ "share": true, // false turns statistics off
625
+ "projectId": "REPLACE-with-your-own-random-id" // optional; never copy someone else's
626
+ }
627
+ ```
628
+
629
+ **Preview.** `parity check --stats-preview` runs the check as usual, sends nothing, and prints the exact
630
+ JSON to stderr, followed by whether it would be sent — or why not. The exit code, stdout and the report
631
+ files are unchanged.
632
+
633
+ **Turning it off.** Any one of these is enough; they are checked in this order, and the first that says
634
+ no wins:
635
+
636
+ 1. `DO_NOT_TRACK` set to anything except `0` or `false` — always wins.
637
+ 2. `CORNHSU_SHARE_STATS` (shared with [XamlContrast](https://github.com/HSU-YU-MING/cornhsu-xamlcontrast))
638
+ or `PARITY_SHARE_STATS` set to `false` — in fact any value other than `1`, `true`, `yes` or `on`, so a
639
+ typo errs on the side of *not* sending.
640
+ 3. `"stats": { "share": false }` in `parity.config.json`.
641
+ 4. The GitHub Action's `share-stats: false` (it sets `PARITY_SHARE_STATS=false`).
642
+ 5. Builds from source and other non-release builds (any version starting with `0.0.0-`, such as `0.0.0-dev`) never send.
643
+
644
+ Like every other key in the config, a misspelt one is ignored: `"stats": { "shrae": false }` turns
645
+ nothing off. `--stats-preview` tells you whether a switch took effect.
646
+
647
+ **Notice.** The first time `check` sends on a machine, it prints a short notice to stderr and leaves a
648
+ marker file, `cornhsu/parity-stats-notice` in your user settings directory (`%APPDATA%` on Windows,
649
+ `~/.config` elsewhere), so the notice is not repeated — delete the file to see it again. In CI it prints
650
+ one line every time statistics were sent. Nothing is printed when nothing is sent, and nothing ever goes
651
+ to stdout. The GitHub Action also writes one line to the job summary: sent, or why not.
652
+
653
+ **Cost.** The request waits at most 1.5 s to connect and 3 s in total, is never retried and never follows
654
+ a redirect. When it fails — offline, a firewall, the collector is down — nothing is printed and nothing
655
+ else changes: the exit code, stdout, the report files and the PR comment are exactly what they would have
656
+ been.
657
+
490
658
  ## Security
491
659
 
660
+ - **What Parity connects to**: the Figma API (only with a Figma design source), the dashboard you name
661
+ for `parity push`, and `stats.cornhsu.com` for the [anonymous usage statistics](#anonymous-usage-statistics)
662
+ of `parity check` (unless turned off). The page under test is opened by the local browser. Nothing else.
492
663
  - The Figma token comes from an environment variable (`env:FIGMA_TOKEN`); it never reaches a log
493
664
  and never goes in a URL (it is sent as the `X-Figma-Token` header).
494
665
  - Fetched frames are cached in `.parity/cache` (add `.parity/` to your `.gitignore`), so re-runs do
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cornhsu-parity",
3
- "version": "2.0.0",
3
+ "version": "2.2.0",
4
4
  "description": "Property-Level Design QA that can gate your CI. Compares real numeric values from your Figma file against the rendered UI and fails the PR when the implementation drifts from the design.",
5
5
  "keywords": [
6
6
  "figma",
@@ -34,11 +34,11 @@
34
34
  "node": ">=20"
35
35
  },
36
36
  "optionalDependencies": {
37
- "@cornhsu/parity-win32-x64": "2.0.0",
38
- "@cornhsu/parity-win32-arm64": "2.0.0",
39
- "@cornhsu/parity-linux-x64": "2.0.0",
40
- "@cornhsu/parity-linux-arm64": "2.0.0",
41
- "@cornhsu/parity-darwin-x64": "2.0.0",
42
- "@cornhsu/parity-darwin-arm64": "2.0.0"
37
+ "@cornhsu/parity-win32-x64": "2.2.0",
38
+ "@cornhsu/parity-win32-arm64": "2.2.0",
39
+ "@cornhsu/parity-linux-x64": "2.2.0",
40
+ "@cornhsu/parity-linux-arm64": "2.2.0",
41
+ "@cornhsu/parity-darwin-x64": "2.2.0",
42
+ "@cornhsu/parity-darwin-arm64": "2.2.0"
43
43
  }
44
44
  }