cornhsu-parity 2.1.0 → 2.3.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 +167 -9
- 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 | 31 on NuGet (v0.1.0 → v2.3.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 | **1443**, 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,11 +89,47 @@ 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
|
+
|
|
92
98
|
> **`parity check` sends anonymous usage statistics by default** (counts and score ranges only, for
|
|
93
99
|
> academic research). What is sent, why, and every way to turn it off:
|
|
94
100
|
> [Anonymous usage statistics](#anonymous-usage-statistics). To see the exact data without sending
|
|
95
101
|
> anything: `parity check --stats-preview`.
|
|
96
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, for each measured property how many gaps and how large, in ranges,
|
|
115
|
+
how many design nodes could not be matched per reason, and, if `compare.extras` is on, how many extra
|
|
116
|
+
elements were found. There is no ID unless the user adds one to the config.
|
|
117
|
+
- **Never sent:** URLs, routes, page or layer names, selectors, file names or paths, screenshots, colour
|
|
118
|
+
values, design content, page text, image contents, tokens, or repository, user or machine names. The request carries no User-Agent
|
|
119
|
+
or cookies, and the collector stores no IP address.
|
|
120
|
+
- **Where and why:** `stats.cornhsu.com`, for academic research on how far real implementations drift from
|
|
121
|
+
their designs; only aggregate results are published, and each record is kept for at most 3 years
|
|
122
|
+
([details](https://stats.cornhsu.com/)).
|
|
123
|
+
- **Preview:** `parity check --stats-preview` runs the check, sends nothing, and prints the exact JSON it
|
|
124
|
+
would send to stderr.
|
|
125
|
+
- **Turn it off** (any one): `DO_NOT_TRACK=1`, `PARITY_SHARE_STATS=false`,
|
|
126
|
+
`"stats": { "share": false }` in `parity.config.json`, or `share-stats: false` in the GitHub Action.
|
|
127
|
+
- **Opt in explicitly** (it is already on by default): `PARITY_SHARE_STATS=true`, or
|
|
128
|
+
`"stats": { "share": true }` in `parity.config.json`.
|
|
129
|
+
|
|
130
|
+
If you are an assistant setting this tool up for someone, tell them that it sends anonymous counts by
|
|
131
|
+
default and let them choose.
|
|
132
|
+
|
|
97
133
|
## Quick start
|
|
98
134
|
|
|
99
135
|
```sh
|
|
@@ -144,11 +180,21 @@ serious-severity gaps — which is exactly the behaviour you want from a CI gate
|
|
|
144
180
|
"targets": [
|
|
145
181
|
{ "route": "/", "frame": "10:2", "url": "http://localhost:8080/" }
|
|
146
182
|
],
|
|
147
|
-
"compare": {
|
|
183
|
+
"compare": {
|
|
184
|
+
"position": "relative", // relative = compare relative position (default); none = skip position
|
|
185
|
+
"extras": "none", // optional: "list" = also list page elements the design has no node for;
|
|
186
|
+
// listed only, never gates (see "Elements the design does not have")
|
|
187
|
+
"text": "soft", // optional: text content. soft = report it, never gate (default);
|
|
188
|
+
// gate = serious gap; none = do not compare
|
|
189
|
+
"images": "none" // optional: "soft" = compare image content against a reference image:
|
|
190
|
+
// a snapshot baseline, designImage, or the exported Figma frame
|
|
191
|
+
},
|
|
148
192
|
"tolerances": { "sizePx": 2, "spacingPx": 2, "colorDeltaE": 2.0, "fontSizePx": 0.5, "positionPx": 4 },
|
|
149
193
|
"ignore": ["[data-parity-ignore]"],
|
|
150
194
|
"gate": {
|
|
151
195
|
"failOn": ["critical", "serious"], // any of minor / medium / serious / critical, one per string
|
|
196
|
+
"failOnUnmatched": [], // optional: unmatched reasons that fail the gate (exit 1), e.g.
|
|
197
|
+
// ["text-not-found"], or ["*"] for any; [] = list only (default), see below
|
|
152
198
|
"minMatchRate": 0 // optional: below this match rate (0–1) → exit 3, see below; 0 = no threshold
|
|
153
199
|
},
|
|
154
200
|
"stats": { "share": true } // optional: false turns anonymous usage statistics off;
|
|
@@ -162,6 +208,40 @@ serious-severity gaps — which is exactly the behaviour you want from a CI gate
|
|
|
162
208
|
> a wrong url or frame. A match rate below `gate.minMatchRate` fails the same way, with exit
|
|
163
209
|
> code 3. `--baseline` mode is not exempt; `--reverse` never gates and always exits 0.
|
|
164
210
|
|
|
211
|
+
### Gating on missing elements: `gate.failOnUnmatched`
|
|
212
|
+
|
|
213
|
+
By default an unmatched design node is only **listed** (`unmatched` in the report) — a page that
|
|
214
|
+
lost a badge still passes. `gate.failOnUnmatched` lets you fail the gate on chosen
|
|
215
|
+
[unmatched reasons](#report-vocabulary-for-anyone-parsing-reportjson) instead: exit 1 (GATE FAIL),
|
|
216
|
+
and the reason line names the layers it blocked, e.g.
|
|
217
|
+
`/: 1 unmatched design node blocked by gate.failOnUnmatched (text-not-found: cta-label)`.
|
|
218
|
+
|
|
219
|
+
- `["text-not-found"]` — the targeted choice: fail when a design text node's text appears **nowhere on
|
|
220
|
+
the page** (the element is gone, or its copy was changed). Other unmatched nodes stay listed only.
|
|
221
|
+
- `["*"]` — fail on every unmatched node, whatever the reason. (`minMatchRate: 1` also fails on any
|
|
222
|
+
unmatched node, but as exit 3 — "results are not trustworthy" — not as a GATE FAIL.)
|
|
223
|
+
- Reasons are matched case-insensitively; a misspelled reason is a config error at load (exit 2),
|
|
224
|
+
because a reason that never matches would silently disable the gate.
|
|
225
|
+
- Match integrity still comes first: zero matches remain exit 3.
|
|
226
|
+
- Applied by `check`, `parity report` and `parity push` (both recompute the verdict from the config),
|
|
227
|
+
and `parity serve`. **Not applied by `check --baseline`** while a baseline exists — regression mode
|
|
228
|
+
only gates new or worsened gaps; with no baseline yet it falls back to the normal gate, this
|
|
229
|
+
condition included.
|
|
230
|
+
|
|
231
|
+
What `text-not-found` means exactly: the design text is not a substring of the page text Parity can
|
|
232
|
+
see. The comparison ignores case, all whitespace, and invisible characters such as soft hyphens, and
|
|
233
|
+
treats curly quotes as straight ones, so a manual line break in the design or a different quote style
|
|
234
|
+
is not a missing text. That page text is the rendered text of the page
|
|
235
|
+
(`innerText`, so `text-transform` and text split across elements like `Hello <b>world</b>` count),
|
|
236
|
+
plus what `innerText` misses: open shadow roots, same-origin iframes, `<input>`/`<textarea>`
|
|
237
|
+
values and placeholders, `::before`/`::after` content, `<img alt>`. Hidden text (`display: none`,
|
|
238
|
+
`visibility: hidden`) and password values do not count. Text Parity cannot reach — cross-origin
|
|
239
|
+
iframes, closed shadow roots, text drawn on a `<canvas>` or baked into an image without `alt` — counts as
|
|
240
|
+
not found, so `text-not-found` means "not found anywhere Parity can see". A text that is present but
|
|
241
|
+
cannot be pinned to one element (several identical texts, or split across elements) stays
|
|
242
|
+
`ambiguous-or-missing-text`. The page text is used for this decision only: it is never written to
|
|
243
|
+
`report.json`, snapshots, statistics or bug reports.
|
|
244
|
+
|
|
165
245
|
## Targets: a web page or an Electron desktop app
|
|
166
246
|
|
|
167
247
|
`target.url` decides what the implementation side is; the commands and the report are identical:
|
|
@@ -237,6 +317,45 @@ Only the things that should hold no matter how the layout flows:
|
|
|
237
317
|
- **Absolute x/y is deliberately never compared**: it legitimately differs in a flexible layout,
|
|
238
318
|
so comparing it means false positives, which means losing trust.
|
|
239
319
|
|
|
320
|
+
These are all **soft gaps** too, like font-family: reported, but they cost no fidelity points, never
|
|
321
|
+
gate and do not count as new in `--baseline` mode. They start soft on purpose, until real sites show
|
|
322
|
+
how noisy each one is.
|
|
323
|
+
|
|
324
|
+
- **Text content** (`text`): a layer matched by name whose text differs. Case, whitespace, invisible
|
|
325
|
+
characters and quote style are ignored. When the element has children, the design text only has to
|
|
326
|
+
appear somewhere inside. Layers you matched by hand are skipped (design systems often use placeholder
|
|
327
|
+
text), and so are snapshot baselines (dynamic content changes between runs). `compare.text: "gate"`
|
|
328
|
+
makes it a serious gap, snapshot baselines included; `"none"` turns it off. **Both texts go into
|
|
329
|
+
`report.json` and the Markdown report, cut to 120 characters**, so they reach anywhere you send those.
|
|
330
|
+
- **Child order** (`childOrder`): children of an auto-layout container in a different order along its
|
|
331
|
+
main axis. Compared on the real boxes, so CSS `order` and `row-reverse` count correctly. In a wrapping
|
|
332
|
+
container the reading order is compared, so breaking onto a new row at a different place is not a gap.
|
|
333
|
+
Absolutely positioned children are skipped.
|
|
334
|
+
- **Container alignment** (`mainAxisOffset`, `crossAxisOffset`): the distance from a child to its
|
|
335
|
+
auto-layout parent's edge. Only where that distance should be fixed: both sizes explicitly FIXED, no
|
|
336
|
+
text nodes, and no padding or size gap that already explains the shift. The main axis is compared for
|
|
337
|
+
the first child under `MIN`, `CENTER` or `MAX`. Order and alignment follow `compare.position`, and are
|
|
338
|
+
skipped when a target's viewport width differs from the design, because the layout may reflow.
|
|
339
|
+
- **Line count** (`lineCount`): how many lines a text takes, measured from the real line boxes. This is
|
|
340
|
+
the root cause when a wrapped subtitle pushes everything below it down. The design side is box height
|
|
341
|
+
over line height; fixed-height text boxes are skipped.
|
|
342
|
+
- **Style**: `fontStyle`, `textDecoration`, `textTransform`, `textAlign`, `opacity`, `borderWidth`,
|
|
343
|
+
`borderColor`, `shadow`, `shadowColor`, `gradient`. Only what the design states is compared: Figma nodes
|
|
344
|
+
always state it, and a hand-written design JSON states what it sets (`text.fontStyle`, `opacity`,
|
|
345
|
+
`borderWidth`, `shadows`, `gradient` and so on). What cannot be seen does not count: invisible shadows, transparent borders, a border the same color as the
|
|
346
|
+
fill, and a text transform that changes nothing on screen. Opacity is the element's own, not accumulated from its
|
|
347
|
+
ancestors. Gradients compare their kind, direction and end colors, not the stops in between; a direction
|
|
348
|
+
that depends on the box, like `to top right`, is not compared.
|
|
349
|
+
- **Image content** (`image`, off by default): `"compare": { "images": "soft" }` crops each `img`, `svg`,
|
|
350
|
+
`picture` and background image out of a full-page screenshot and compares it with the same box in the
|
|
351
|
+
reference image: the screenshot next to a `parity snapshot` baseline, `designImage`, or the frame exported
|
|
352
|
+
from Figma. Recompressing or rescaling an image does not count. It takes one extra screenshot per page.
|
|
353
|
+
With Figma it also spends API quota, one export per frame, kept in `.parity/cache` until you pass
|
|
354
|
+
`--refresh`. If there is no reference or the export fails, a note goes to stderr and the check carries on.
|
|
355
|
+
|
|
356
|
+
A snapshot taken by an older version has none of the style values, so nothing new appears until you run
|
|
357
|
+
`parity snapshot` again.
|
|
358
|
+
|
|
240
359
|
> When the design source is Figma, layer names in the report (the Markdown written by
|
|
241
360
|
> `check --md` or re-rendered by `parity report`, and the local UI) **link back to that node in
|
|
242
361
|
> Figma** — a designer clicks once and lands on the layer. When `designFile` is set it wins over
|
|
@@ -293,7 +412,41 @@ unaffected and works as usual).
|
|
|
293
412
|
its already-matched descendants (pure structure — no guessing)
|
|
294
413
|
4. **Manual fallback**: add `data-parity="Layer Name"` in your HTML, or write
|
|
295
414
|
`{ "Layer Name": "CSS selector" }` in `parity.map.json`
|
|
296
|
-
5. Still no match → **honestly listed as unmatched**, so you only fill in what genuinely needs a human
|
|
415
|
+
5. Still no match → **honestly listed as unmatched**, so you only fill in what genuinely needs a human.
|
|
416
|
+
A text node whose text is nowhere on the page is reported as `text-not-found` (the element is
|
|
417
|
+
missing), not as an ambiguity — and `gate.failOnUnmatched` can fail the gate on it
|
|
418
|
+
|
|
419
|
+
## Elements the design does not have (`compare.extras`)
|
|
420
|
+
|
|
421
|
+
Matching is anchored on the design side, so by default an element the design never had — say an
|
|
422
|
+
800×200 banner across the top of the page — produces no gap at all and the check passes without a
|
|
423
|
+
word. `"compare": { "extras": "list" }` adds a list of such elements to the report. **It only
|
|
424
|
+
lists**: the gate, the exit code, the fidelity score, the `summary` counts and `--baseline` are exactly
|
|
425
|
+
what they would be without it. The anonymous statistics gain one number, how many such elements were
|
|
426
|
+
found, and never which ones. The default is `"none"`; any value
|
|
427
|
+
other than `none` / `list` is a config error (exit 2) — it is a string, not a boolean, so a typo
|
|
428
|
+
like `"false"` fails loudly.
|
|
429
|
+
|
|
430
|
+
An element on the page is listed when all of these hold:
|
|
431
|
+
|
|
432
|
+
- it is not matched, and it is not an ancestor of a matched element (those are layout containers);
|
|
433
|
+
- it is not inside a matched design **leaf** (how a button or an image is built inside is an
|
|
434
|
+
implementation detail), nor inside a matched element none of whose design children matched
|
|
435
|
+
(those children are already in the unmatched list — listing the page side too would report the
|
|
436
|
+
same gap twice);
|
|
437
|
+
- it is visible: it has text of its own, is an `img` or `svg`, or has a background that is not
|
|
438
|
+
fully transparent;
|
|
439
|
+
- at least **64×64 = 4,096 px²** of it lies inside the viewport the page was rendered at (by
|
|
440
|
+
default the design frame's size). The threshold is deliberately conservative — icons, hairlines
|
|
441
|
+
and a single short word stay below it — and not configurable in this first version.
|
|
442
|
+
|
|
443
|
+
Nested elements are listed once, at the outermost one that qualifies. `ignore` selectors apply as
|
|
444
|
+
usual, and `check --reverse` never computes the list. `check` prints the list and `--md` / `parity
|
|
445
|
+
report` add a table when there is something to show; the local report UI and the dashboard do not
|
|
446
|
+
show it yet (the dashboard keeps it as part of the raw report). Known limits: the capture does not
|
|
447
|
+
record borders, shadows, `opacity` or clipping, so **an element that is visible only through its
|
|
448
|
+
border is not listed**, while one hidden by `opacity: 0`, by an ancestor's `overflow: hidden` or
|
|
449
|
+
under another element can be.
|
|
297
450
|
|
|
298
451
|
## Architecture
|
|
299
452
|
|
|
@@ -455,8 +608,8 @@ Behaviour:
|
|
|
455
608
|
"Report a misjudgment" link to the issue form — a link only, nothing is sent) and keeps
|
|
456
609
|
updating that same comment instead of adding new ones — so PMs and reviewers see it without
|
|
457
610
|
ever touching the tool.
|
|
458
|
-
- **Blocks the PR**: gaps beyond `gate.failOn`
|
|
459
|
-
first, then the gate is enforced.**)
|
|
611
|
+
- **Blocks the PR**: gaps beyond `gate.failOn` (or unmatched nodes whose reason is in
|
|
612
|
+
`gate.failOnUnmatched`) → exit 1 → red PR. (**The comment is posted first, then the gate is enforced.**)
|
|
460
613
|
- **Artifacts**: `report.json` plus the Markdown report are uploaded for download (artifact
|
|
461
614
|
name `parity-report` unless you set `artifact-name`; `report.json` and `parity-report.md` at its
|
|
462
615
|
root). `report.json` is picked up from `.parity/report.json` next to the config file — where
|
|
@@ -491,8 +644,9 @@ Action inputs: `config` / `target` / `working-directory` / `version` / `figma-to
|
|
|
491
644
|
|
|
492
645
|
- **Unknown keys** — consumers must ignore keys they do not recognize. Within 2.x, `report.json` only *gains* fields; existing keys are never removed, renamed or given a different type (that would be 3.0). Closed sets below never gain members within 2.x either.
|
|
493
646
|
- `severity` — **closed set** (spelling and membership frozen): `none` / `minor` / `medium` / `serious` / `critical`. `status`: `mismatch` / `missing`.
|
|
494
|
-
- `unmatched[].reason` — **open set**: parsers must tolerate unknown values; known spellings never change. Currently: `no-anchor`, `ambiguous-or-missing-text`, `randomized-id`, `size-implausible`.
|
|
647
|
+
- `unmatched[].reason` — **open set**: parsers must tolerate unknown values; known spellings never change. Currently: `no-anchor`, `ambiguous-or-missing-text`, `text-not-found`, `randomized-id`, `size-implausible`.
|
|
495
648
|
- `nodes[].matchedBy` — **open set**, same rule. Currently: `selector`, `explicit`, `auto-text`, `auto-name`, `auto-container`.
|
|
649
|
+
- `extras` (per report, `compare.extras`) — `null` when the list is off (the default, `check --reverse`, and every report written before the field existed); otherwise `{ "total": n, "items": [{ "selector", "tag", "box" }] }`, where `total: 0` with empty `items` means "on, nothing found". `items` is sorted largest first and capped at 20 per page; `total` counts them all. Items carry no element text. Never affects the gate or the score.
|
|
496
650
|
- `direction` (top level, since 2.0.0) — **closed set**: `forward` (a normal `check`) / `reverse` (`check --reverse`: expected and actual swapped, never gated). Always written; files without it read as `forward`. Readers accept exactly the lowercase strings `"forward"` and `"reverse"` — any other value (other casing, a number, `null`) is an error, not a guess. `parity report` does not gate a reverse report, and `parity push` refuses one.
|
|
497
651
|
|
|
498
652
|
## Regression gating: baselines (M5)
|
|
@@ -515,6 +669,7 @@ parity baseline list # Browse snapshot history (the score column is the fide
|
|
|
515
669
|
|
|
516
670
|
- **New** (absent from the baseline, present now) or **worsened** (higher severity) → exit 1, PR blocked
|
|
517
671
|
- **Fixed** (in the baseline, gone now) is listed as encouragement; **unchanged** existing gaps do not gate
|
|
672
|
+
- `gate.failOnUnmatched` is not applied in this mode (once a baseline exists); unmatched nodes are listed as usual
|
|
518
673
|
- Ideal for gradual adoption: record reality with `baseline save`, then run `check --baseline` in
|
|
519
674
|
CI, and all the team has to commit to is "do not make fidelity worse"
|
|
520
675
|
|
|
@@ -570,10 +725,13 @@ for, how long it is kept — is **<https://stats.cornhsu.com/>**.
|
|
|
570
725
|
| `data.designSource` | `figma`, `snapshot` (a file written by `parity snapshot`), `file` (any other design JSON) or `image` |
|
|
571
726
|
| `data.designNodes`, `data.matched` | Design nodes and matched nodes, summed over all pages |
|
|
572
727
|
| `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
|
|
728
|
+
| `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`, `borderWidth`, `shadow`, `mainAxisOffset`, `crossAxisOffset`: <1, 1–2, 2–4, 4–8, 8–16, 16–32, ≥32), ΔE for `color`, `background`, `borderColor`, `shadowColor`, `gradient` (<1, 1–2, 2–5, 5–10, 10–20, ≥20), weight steps for `fontWeight` (<100, 100–200, 200–300, ≥300), a 0–1 difference for `opacity` and `image` (<0.05, 0.05–0.1, 0.1–0.25, 0.25–0.5, ≥0.5), and lines for `lineCount` (1, 2, 3–4, 5 or more). `fontFamily`, `text`, `childOrder`, `fontStyle`, `textDecoration`, `textTransform`, `textAlign` have no ranges, only counts: never the text, the layer names or the values |
|
|
729
|
+
| `data.unmatchedBy` | Design nodes that could not be matched, counted per reason: `no-anchor`, `ambiguous-or-missing-text`, `text-not-found`, `randomized-id`, `size-implausible`, or `other` for any reason this version does not list. Only reasons with at least one node; `{}` when everything matched. Never the layer names |
|
|
730
|
+
| `data.extras` | How many extra page elements `compare.extras` found, summed over all pages (the total, before the 20-per-page cap). Only present when the list is on. Never their selectors, tags or boxes |
|
|
574
731
|
|
|
575
732
|
**Never sent:** URLs, routes, page or layer names, selectors, file names or paths, screenshots, colour
|
|
576
|
-
values, design content,
|
|
733
|
+
values, design content, text from the page or the design, image contents or image hashes, tokens,
|
|
734
|
+
repository names or IDs, user names, machine names. The request has no
|
|
577
735
|
User-Agent and no cookies. The collector does not store IP addresses or the time a record arrived (only
|
|
578
736
|
its date); Cloudflare, which hosts the collector, sees the connection address at its edge, as any network
|
|
579
737
|
provider does.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cornhsu-parity",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.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.3.0",
|
|
38
|
+
"@cornhsu/parity-win32-arm64": "2.3.0",
|
|
39
|
+
"@cornhsu/parity-linux-x64": "2.3.0",
|
|
40
|
+
"@cornhsu/parity-linux-arm64": "2.3.0",
|
|
41
|
+
"@cornhsu/parity-darwin-x64": "2.3.0",
|
|
42
|
+
"@cornhsu/parity-darwin-arm64": "2.3.0"
|
|
43
43
|
}
|
|
44
44
|
}
|