cornhsu-parity 2.0.0 → 2.1.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.
- package/README.md +143 -7
- 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 |
|
|
66
|
+
| Releases | 29 on NuGet (v0.1.0 → v2.1.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 | **
|
|
70
|
+
| Tests | **1047**, 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,11 @@ 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
|
+
> **`parity check` sends anonymous usage statistics by default** (counts and score ranges only, for
|
|
93
|
+
> academic research). What is sent, why, and every way to turn it off:
|
|
94
|
+
> [Anonymous usage statistics](#anonymous-usage-statistics). To see the exact data without sending
|
|
95
|
+
> anything: `parity check --stats-preview`.
|
|
96
|
+
|
|
92
97
|
## Quick start
|
|
93
98
|
|
|
94
99
|
```sh
|
|
@@ -97,6 +102,7 @@ parity install-browser # Download Playwright Chromium: on first run, and aga
|
|
|
97
102
|
# upgrade if check says Chromium is not installed
|
|
98
103
|
export FIGMA_TOKEN=... # Only the file_content:read scope is needed
|
|
99
104
|
parity check # Compare; writes a report and sets the exit code
|
|
105
|
+
# (--stats-preview: print the anonymous statistics it would send, send nothing)
|
|
100
106
|
parity report # Re-render the Markdown report from an existing report.json
|
|
101
107
|
# (no re-scan; --md writes a file, otherwise it prints to stdout)
|
|
102
108
|
parity snapshot # Freeze the currently rendered UI as the design baseline —
|
|
@@ -108,7 +114,7 @@ parity lint # Design lint: are the design file's values inside th
|
|
|
108
114
|
parity check --reverse # Reverse view, for a designer redrawing an existing page:
|
|
109
115
|
# how does my draft differ from what ships? (never gates)
|
|
110
116
|
parity bug-report # (experimental) Collect version / OS / Node / Chromium info and print
|
|
111
|
-
# it with a pre-filled GitHub issue link.
|
|
117
|
+
# it with a pre-filled GitHub issue link. bug-report itself connects to nothing;
|
|
112
118
|
# opening the link sends the masked report to github.com (it rides in
|
|
113
119
|
# the URL). No issue until you submit the form. --config adds a
|
|
114
120
|
# structural summary without URLs, routes, keys, tokens or paths
|
|
@@ -144,7 +150,9 @@ serious-severity gaps — which is exactly the behaviour you want from a CI gate
|
|
|
144
150
|
"gate": {
|
|
145
151
|
"failOn": ["critical", "serious"], // any of minor / medium / serious / critical, one per string
|
|
146
152
|
"minMatchRate": 0 // optional: below this match rate (0–1) → exit 3, see below; 0 = no threshold
|
|
147
|
-
}
|
|
153
|
+
},
|
|
154
|
+
"stats": { "share": true } // optional: false turns anonymous usage statistics off;
|
|
155
|
+
// see "Anonymous usage statistics" below (also projectId)
|
|
148
156
|
}
|
|
149
157
|
```
|
|
150
158
|
|
|
@@ -337,6 +345,51 @@ There are three shells, and **the engine does not know that any of them exist**:
|
|
|
337
345
|
> setting, and the integration tests inject one that a developer's `PARITY_SERVER_DB` must not
|
|
338
346
|
> redirect.
|
|
339
347
|
|
|
348
|
+
### One dashboard, two tools
|
|
349
|
+
|
|
350
|
+
The same server also takes reports from **[XamlContrast](https://github.com/HSU-YU-MING/cornhsu-xamlcontrast)**
|
|
351
|
+
(the static WCAG contrast audit for XAML apps): `xamlcontrast --push <report.json>` sends the file
|
|
352
|
+
its `--json` option wrote, through the same `POST /api/runs`, marked with an `X-Parity-Tool: xamlcontrast`
|
|
353
|
+
header (`parity push` sends no such header, which the server reads as `parity`). A project can hold runs
|
|
354
|
+
from both tools. XamlContrast runs get their own overview card per project (pairs below AA, fail · warn,
|
|
355
|
+
coverage), a fail/warn trend, and an issue list (file and line, element, foreground and background as
|
|
356
|
+
written in the XAML, the ratio in each theme, the threshold). Each XamlContrast run also shows what changed
|
|
357
|
+
since the previous XamlContrast run on the same branch — new, worse, fixed and improved pairs — compared the
|
|
358
|
+
way XamlContrast's own `--baseline` does (a pair is file, element, foreground and background, without the line
|
|
359
|
+
number; more places or a lower worst ratio is worse). Like `parity push`, the XamlContrast side is
|
|
360
|
+
**experimental** until the dashboard is publicly deployed.
|
|
361
|
+
|
|
362
|
+
### Hosting it yourself
|
|
363
|
+
|
|
364
|
+
The repo root has a `Dockerfile`. The image contains the server, its data layer and the report types,
|
|
365
|
+
and nothing else: no browser, no engine (CI checks this).
|
|
366
|
+
|
|
367
|
+
```sh
|
|
368
|
+
docker build -t parity-server .
|
|
369
|
+
docker run -d --name parity -p 8080:8080 -v parity-data:/data \
|
|
370
|
+
-e Parity__PublicUrl=https://parity.example.com \
|
|
371
|
+
-e ASPNETCORE_FORWARDEDHEADERS_ENABLED=true \
|
|
372
|
+
parity-server
|
|
373
|
+
docker exec parity dotnet Parity.Server.dll create-project my-site # prints the CI token once
|
|
374
|
+
docker exec parity dotnet Parity.Server.dll create-invite my-site you@example.com owner
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
| Setting | What it does |
|
|
378
|
+
|---|---|
|
|
379
|
+
| `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. |
|
|
380
|
+
| `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. |
|
|
381
|
+
| `PARITY_SERVER_DB` | The SQLite database. Preset to `/data/parity-server.db` in the image. |
|
|
382
|
+
| `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. |
|
|
383
|
+
|
|
384
|
+
- **Everything lives in `/data`**: mount a volume there. TLS is the job of the proxy or platform in front.
|
|
385
|
+
- **Run one instance.** The database is SQLite and login attempts are serialized inside one process;
|
|
386
|
+
more than one instance needs a different database first.
|
|
387
|
+
- **Backups**: `docker exec parity dotnet Parity.Server.dll backup /data/backup-2026-10-26.db` writes a
|
|
388
|
+
consistent copy while the server keeps running (SQLite `VACUUM INTO`; it never overwrites a file and
|
|
389
|
+
never creates a missing database). Copy it off the machine. To restore: stop the server, put the copy
|
|
390
|
+
where the database was, start it again.
|
|
391
|
+
- **Uptime**: `GET /healthz` is anonymous and answers `ok` only when the database answers, `503` otherwise.
|
|
392
|
+
|
|
340
393
|
### The iron rule: the cloud never runs a browser
|
|
341
394
|
|
|
342
395
|
`Parity.Server`'s only endpoint that takes in scan results is `POST /api/runs` (its other
|
|
@@ -413,10 +466,13 @@ Behaviour:
|
|
|
413
466
|
own artifact and keeps its own PR comment; two calls with the same name collide on the artifact upload.
|
|
414
467
|
Two calls in **one job** work only while the first one passes: when its gate fails, the job stops at
|
|
415
468
|
that call's last step and the second call never runs (no report, no comment, no artifact for it).
|
|
469
|
+
- **Anonymous usage statistics**: on by default ([what and why](#anonymous-usage-statistics));
|
|
470
|
+
`share-stats: false` turns them off. The job summary gets one line saying whether they were sent, or why not.
|
|
416
471
|
|
|
417
472
|
Action inputs: `config` / `target` / `working-directory` / `version` / `figma-token` /
|
|
418
473
|
`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)
|
|
474
|
+
`artifact-name` (default `parity-report`; letters, digits, `.`, `_`, `-`, at most 100 characters, not starting with `-` and not only dots) /
|
|
475
|
+
`share-stats` (default `true`; exactly `true` or `false` — anything else stops the action before it installs Parity).
|
|
420
476
|
|
|
421
477
|
> The action installs the tool with `dotnet tool install -g Cornhsu.Parity` (published on
|
|
422
478
|
> nuget.org) — the latest 2.x when `version` is omitted, so `@v2` never jumps to a future 3.x,
|
|
@@ -480,15 +536,95 @@ file is released immediately), and the engine's `BaselineComparer` is a pure, un
|
|
|
480
536
|
yet, so the command and its server protocol may change in a 2.x minor release and join the interface
|
|
481
537
|
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
538
|
measured coordinates (no screenshot upload), an overview and score trend per project and
|
|
483
|
-
route, and accounts / project roles / invite links.
|
|
484
|
-
|
|
539
|
+
route, and accounts / project roles / invite links. It also takes XamlContrast reports
|
|
540
|
+
([one dashboard, two tools](#one-dashboard-two-tools)). Its own internal M1–M4.6 are done; of its
|
|
541
|
+
M5, the deployment groundwork is done (a Docker image, the public URL, persisted sign-in keys,
|
|
542
|
+
online backups), the actual hosting (a host, a domain, TLS) is not. **It receives
|
|
485
543
|
reports, it never scans** — see the iron rule above.
|
|
486
544
|
|
|
487
545
|
> Open work, known blind spots and the next priorities are in [ROADMAP.md](ROADMAP.md);
|
|
488
546
|
> version history is in [CHANGELOG.md](CHANGELOG.md).
|
|
489
547
|
|
|
548
|
+
## Anonymous usage statistics
|
|
549
|
+
|
|
550
|
+
From 2.1.0, `parity check` sends **anonymous usage statistics**, once per run — **on by default, in CI
|
|
551
|
+
too**. They are counts and score ranges used for **academic research** on how far real
|
|
552
|
+
implementations drift from their designs; only aggregate results are published. The public data page — what is collected, what it is used
|
|
553
|
+
for, how long it is kept — is **<https://stats.cornhsu.com/>**.
|
|
554
|
+
|
|
555
|
+
**When.** Only `parity check`, and only when it produced a report (exit 0, 1 or 3). `--reverse`,
|
|
556
|
+
`--stats-preview`, every other command and any run that ends in exit 2 send nothing.
|
|
557
|
+
|
|
558
|
+
**Everything that is sent:**
|
|
559
|
+
|
|
560
|
+
| Field | Meaning |
|
|
561
|
+
|---|---|
|
|
562
|
+
| `schema`, `tool` | Always `1` and `"parity"` |
|
|
563
|
+
| `toolVersion` | The Parity version, e.g. `2.1.0` (no commit hash) |
|
|
564
|
+
| `os` | `windows`, `linux`, `macos` or `other` |
|
|
565
|
+
| `ci` | Whether `CI` or `GITHUB_ACTIONS` is `true` |
|
|
566
|
+
| `projectId` | `null`, unless you set `stats.projectId` yourself (see below) |
|
|
567
|
+
| `data.score` | The fidelity score `check` printed (0–100) |
|
|
568
|
+
| `data.gateFailed` | Whether `check`'s exit code was not 0 (so `--baseline` and exit 3 count as they did in your CI) |
|
|
569
|
+
| `data.pages` | How many targets the report covers |
|
|
570
|
+
| `data.designSource` | `figma`, `snapshot` (a file written by `parity snapshot`), `file` (any other design JSON) or `image` |
|
|
571
|
+
| `data.designNodes`, `data.matched` | Design nodes and matched nodes, summed over all pages |
|
|
572
|
+
| `data.severity` | Gap counts per severity: `critical`, `serious`, `medium`, `minor` |
|
|
573
|
+
| `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 |
|
|
574
|
+
|
|
575
|
+
**Never sent:** URLs, routes, page or layer names, selectors, file names or paths, screenshots, colour
|
|
576
|
+
values, design content, tokens, repository names or IDs, user names, machine names. The request has no
|
|
577
|
+
User-Agent and no cookies. The collector does not store IP addresses or the time a record arrived (only
|
|
578
|
+
its date); Cloudflare, which hosts the collector, sees the connection address at its edge, as any network
|
|
579
|
+
provider does.
|
|
580
|
+
|
|
581
|
+
**No ID unless you add one.** Without `stats.projectId`, nothing links one run to another or to you —
|
|
582
|
+
which also means a record cannot be picked out and deleted afterwards. If you want your runs counted as
|
|
583
|
+
one project (and deletable on request), make up a random ID — for example `openssl rand -hex 12`, or
|
|
584
|
+
`[guid]::NewGuid().ToString('N')` in PowerShell. Parity never derives one from your repository, paths or
|
|
585
|
+
machine, and an ID that is not 16–64 characters of `A–Z a–z 0–9 _ -` is a config error (exit 2):
|
|
586
|
+
|
|
587
|
+
```jsonc
|
|
588
|
+
"stats": {
|
|
589
|
+
"share": true, // false turns statistics off
|
|
590
|
+
"projectId": "REPLACE-with-your-own-random-id" // optional; never copy someone else's
|
|
591
|
+
}
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
**Preview.** `parity check --stats-preview` runs the check as usual, sends nothing, and prints the exact
|
|
595
|
+
JSON to stderr, followed by whether it would be sent — or why not. The exit code, stdout and the report
|
|
596
|
+
files are unchanged.
|
|
597
|
+
|
|
598
|
+
**Turning it off.** Any one of these is enough; they are checked in this order, and the first that says
|
|
599
|
+
no wins:
|
|
600
|
+
|
|
601
|
+
1. `DO_NOT_TRACK` set to anything except `0` or `false` — always wins.
|
|
602
|
+
2. `CORNHSU_SHARE_STATS` (shared with [XamlContrast](https://github.com/HSU-YU-MING/cornhsu-xamlcontrast))
|
|
603
|
+
or `PARITY_SHARE_STATS` set to `false` — in fact any value other than `1`, `true`, `yes` or `on`, so a
|
|
604
|
+
typo errs on the side of *not* sending.
|
|
605
|
+
3. `"stats": { "share": false }` in `parity.config.json`.
|
|
606
|
+
4. The GitHub Action's `share-stats: false` (it sets `PARITY_SHARE_STATS=false`).
|
|
607
|
+
5. Builds from source and other non-release builds (any version starting with `0.0.0-`, such as `0.0.0-dev`) never send.
|
|
608
|
+
|
|
609
|
+
Like every other key in the config, a misspelt one is ignored: `"stats": { "shrae": false }` turns
|
|
610
|
+
nothing off. `--stats-preview` tells you whether a switch took effect.
|
|
611
|
+
|
|
612
|
+
**Notice.** The first time `check` sends on a machine, it prints a short notice to stderr and leaves a
|
|
613
|
+
marker file, `cornhsu/parity-stats-notice` in your user settings directory (`%APPDATA%` on Windows,
|
|
614
|
+
`~/.config` elsewhere), so the notice is not repeated — delete the file to see it again. In CI it prints
|
|
615
|
+
one line every time statistics were sent. Nothing is printed when nothing is sent, and nothing ever goes
|
|
616
|
+
to stdout. The GitHub Action also writes one line to the job summary: sent, or why not.
|
|
617
|
+
|
|
618
|
+
**Cost.** The request waits at most 1.5 s to connect and 3 s in total, is never retried and never follows
|
|
619
|
+
a redirect. When it fails — offline, a firewall, the collector is down — nothing is printed and nothing
|
|
620
|
+
else changes: the exit code, stdout, the report files and the PR comment are exactly what they would have
|
|
621
|
+
been.
|
|
622
|
+
|
|
490
623
|
## Security
|
|
491
624
|
|
|
625
|
+
- **What Parity connects to**: the Figma API (only with a Figma design source), the dashboard you name
|
|
626
|
+
for `parity push`, and `stats.cornhsu.com` for the [anonymous usage statistics](#anonymous-usage-statistics)
|
|
627
|
+
of `parity check` (unless turned off). The page under test is opened by the local browser. Nothing else.
|
|
492
628
|
- The Figma token comes from an environment variable (`env:FIGMA_TOKEN`); it never reaches a log
|
|
493
629
|
and never goes in a URL (it is sent as the `X-Figma-Token` header).
|
|
494
630
|
- 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.
|
|
3
|
+
"version": "2.1.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.
|
|
38
|
-
"@cornhsu/parity-win32-arm64": "2.
|
|
39
|
-
"@cornhsu/parity-linux-x64": "2.
|
|
40
|
-
"@cornhsu/parity-linux-arm64": "2.
|
|
41
|
-
"@cornhsu/parity-darwin-x64": "2.
|
|
42
|
-
"@cornhsu/parity-darwin-arm64": "2.
|
|
37
|
+
"@cornhsu/parity-win32-x64": "2.1.0",
|
|
38
|
+
"@cornhsu/parity-win32-arm64": "2.1.0",
|
|
39
|
+
"@cornhsu/parity-linux-x64": "2.1.0",
|
|
40
|
+
"@cornhsu/parity-linux-arm64": "2.1.0",
|
|
41
|
+
"@cornhsu/parity-darwin-x64": "2.1.0",
|
|
42
|
+
"@cornhsu/parity-darwin-arm64": "2.1.0"
|
|
43
43
|
}
|
|
44
44
|
}
|