@variance-authority/core 0.1.1 → 0.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.
Files changed (96) hide show
  1. package/CHANGELOG.md +245 -10
  2. package/README.md +279 -110
  3. package/dist/attribute/call-site.d.ts +20 -2
  4. package/dist/attribute/call-site.js +17 -20
  5. package/dist/attribute/call-site.js.map +1 -1
  6. package/dist/attribute/closure.d.ts +105 -0
  7. package/dist/attribute/closure.js +180 -0
  8. package/dist/attribute/closure.js.map +1 -0
  9. package/dist/attribute/composition.d.ts +5 -3
  10. package/dist/attribute/composition.js +67 -11
  11. package/dist/attribute/composition.js.map +1 -1
  12. package/dist/attribute/divergence.js +7 -1
  13. package/dist/attribute/divergence.js.map +1 -1
  14. package/dist/attribute/index.d.ts +7 -1
  15. package/dist/attribute/index.js +4 -1
  16. package/dist/attribute/index.js.map +1 -1
  17. package/dist/attribute/landmark.d.ts +97 -0
  18. package/dist/attribute/landmark.js +144 -0
  19. package/dist/attribute/landmark.js.map +1 -0
  20. package/dist/attribute/lexicon.d.ts +112 -0
  21. package/dist/attribute/lexicon.js +210 -0
  22. package/dist/attribute/lexicon.js.map +1 -0
  23. package/dist/attribute/source.d.ts +19 -2
  24. package/dist/attribute/source.js +12 -0
  25. package/dist/attribute/source.js.map +1 -1
  26. package/dist/attribute/stack.d.ts +62 -1
  27. package/dist/attribute/stack.js +116 -5
  28. package/dist/attribute/stack.js.map +1 -1
  29. package/dist/compare/derive.d.ts +17 -0
  30. package/dist/compare/derive.js.map +1 -1
  31. package/dist/compare/diff/index.d.ts +25 -1
  32. package/dist/compare/diff/index.js +8 -5
  33. package/dist/compare/diff/index.js.map +1 -1
  34. package/dist/compare/explain.d.ts +1 -1
  35. package/dist/compare/explain.js +15 -8
  36. package/dist/compare/explain.js.map +1 -1
  37. package/dist/compare/index.d.ts +2 -2
  38. package/dist/compare/index.js +1 -1
  39. package/dist/compare/index.js.map +1 -1
  40. package/dist/compare/observability.d.ts +25 -1
  41. package/dist/compare/observability.js +54 -0
  42. package/dist/compare/observability.js.map +1 -1
  43. package/dist/compare/parting.d.ts +24 -2
  44. package/dist/compare/parting.js +14 -9
  45. package/dist/compare/parting.js.map +1 -1
  46. package/dist/compare/slice.d.ts +15 -2
  47. package/dist/compare/slice.js +6 -2
  48. package/dist/compare/slice.js.map +1 -1
  49. package/dist/format/capture.d.ts +15 -0
  50. package/dist/format/capture.js +30 -1
  51. package/dist/format/capture.js.map +1 -1
  52. package/dist/format/document.d.ts +65 -5
  53. package/dist/format/document.js +34 -0
  54. package/dist/format/document.js.map +1 -1
  55. package/dist/format/hash.d.ts +26 -0
  56. package/dist/format/hash.js +41 -0
  57. package/dist/format/hash.js.map +1 -1
  58. package/dist/format/index.d.ts +4 -3
  59. package/dist/format/index.js +3 -2
  60. package/dist/format/index.js.map +1 -1
  61. package/dist/format/sha256.d.ts +10 -2
  62. package/dist/format/sha256.js +42 -9
  63. package/dist/format/sha256.js.map +1 -1
  64. package/dist/format/stabilize.d.ts +15 -0
  65. package/dist/format/stabilize.js +22 -1
  66. package/dist/format/stabilize.js.map +1 -1
  67. package/dist/index.d.ts +3 -11
  68. package/dist/index.js +3 -11
  69. package/dist/index.js.map +1 -1
  70. package/dist/judge/inspect.js +57 -4
  71. package/dist/judge/inspect.js.map +1 -1
  72. package/dist/relate/before.d.ts +137 -0
  73. package/dist/relate/before.js +155 -0
  74. package/dist/relate/before.js.map +1 -0
  75. package/dist/relate/graph.d.ts +53 -16
  76. package/dist/relate/graph.js +33 -15
  77. package/dist/relate/graph.js.map +1 -1
  78. package/dist/relate/index.d.ts +3 -2
  79. package/dist/relate/index.js +2 -1
  80. package/dist/relate/index.js.map +1 -1
  81. package/dist/relate/merkle.d.ts +14 -5
  82. package/dist/relate/merkle.js +14 -6
  83. package/dist/relate/merkle.js.map +1 -1
  84. package/dist/relate/reach.d.ts +19 -5
  85. package/dist/relate/reach.js +10 -8
  86. package/dist/relate/reach.js.map +1 -1
  87. package/dist/relate/records.d.ts +95 -4
  88. package/dist/relate/records.js +93 -13
  89. package/dist/relate/records.js.map +1 -1
  90. package/dist/segment/index.d.ts +123 -0
  91. package/dist/segment/index.js +265 -0
  92. package/dist/segment/index.js.map +1 -0
  93. package/dist/share/index.d.ts +148 -0
  94. package/dist/share/index.js +190 -0
  95. package/dist/share/index.js.map +1 -0
  96. package/package.json +10 -2
package/CHANGELOG.md CHANGED
@@ -1,13 +1,248 @@
1
1
  # @variance-authority/core
2
2
 
3
+ ## 0.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 05d6683: Changes before and beyond reach
8
+
9
+ A run reads left to right: the harness starts it, the tests enter your code,
10
+ your code goes out into the install and never comes back. Selection lives in the
11
+ middle, and both ends were invisible for opposite reasons.
12
+
13
+ The far right already arrived — a package is a node, a bump is a seed, the same
14
+ backwards walk answers it. The far left is this. Nothing imports a
15
+ `vitest.config.ts`, a setup module, a CI workflow or a `.nvmrc`, so no walk
16
+ reaches one and the honest structural answer about a change to one is *no
17
+ component moved*: a skipped suite over the file that decides how every test in
18
+ it runs. A diff that was *wholly* outside the graph already widened. The hole
19
+ was a config edited beside an ordinary source file, where the walk had a seed
20
+ and answered confidently about a change it never looked at.
21
+
22
+ `source.before` names those files, repository-root-relative, and a directory
23
+ claims everything under it. What a declaration buys beyond its own name is
24
+ everything below it: `beforeReach` in `@variance-authority/core/relate` walks
25
+ *along* the arrows from each entry — the one question whose subject has no
26
+ dependents — and collects the setup module, the fixture only that setup
27
+ imports, and the packages the environment rests on. The descent stops at the
28
+ first file `source.dirs` already covers, because that file has dependents and a
29
+ change to it is answered exactly by walking them; everything below it is
30
+ reached through it and does not arrive either.
31
+
32
+ The two ends meet there. A `jsdom` bump is named by the install comparison,
33
+ reaches `jest-environment-jsdom`, and reaches a config no file in the
34
+ repository imports — a change beyond reach arriving before it.
35
+
36
+ `scanRelations` takes `before` to seed those paths by name, since a harness
37
+ lives above every directory a component scan is pointed at. A named path that
38
+ is absent or has no reader is dropped rather than recorded unreadable: an
39
+ unknown file seeds every walk forever, so a typo would otherwise widen every
40
+ run in the repository. A declared entry the graph does not hold contributes
41
+ only its own name, which is the whole answer for a `.nvmrc` and a symptom for a
42
+ harness config, so the run reports it as a note rather than guessing.
43
+
44
+ `source.before` requires `source.relations: true`.
45
+
46
+ ## 0.2.0
47
+
48
+ ### Minor Changes
49
+
50
+ - e546e21: A collector's complaints travel into the observation
51
+
52
+ A finding produced at collection reached the CLI's record and nobody else. A
53
+ suite driving this from its own runner asks the observation what is wrong with a
54
+ subject, and the one diagnostic that explained the moved pixels — the host, not
55
+ the stylesheet, chose the typeface — was produced, carried, and dropped one call
56
+ short of the person reading the failure.
57
+
58
+ `mergeDiagnostics` dedupes on every field, so the CLI, which now meets the same
59
+ list twice, says a shared complaint once.
60
+ - f4b328b: Mainline's evaluation can travel, so the next machine does not derive it again
61
+
62
+ A suite index is bytes addressed by the commit they were written at, which is
63
+ exactly the shape a cache already wants. What was missing was anywhere to put
64
+ them: every checkout that wanted to know what `main` looked like had to run
65
+ `main`, and in CI that is the whole suite, twice, on every pull request.
66
+
67
+ `@variance-authority/core/share` is the transport, and it has two backends
68
+ because there are two. A **directory** is what `actions/cache`, `aws s3 sync`, a
69
+ network mount and a laptop all are once the bytes are on disk; an **HTTP
70
+ endpoint** is what a bucket, a presigned URL and a tribunal deployment all are.
71
+ `createDirectoryShare` in `@variance-authority/store/share` is the filesystem
72
+ half, kept out of `core` because `core` takes no platform.
73
+
74
+ A share never fails a run. `get` answers `null` and `put` resolves, whatever the
75
+ network did — a share that is down, misconfigured or empty is indistinguishable
76
+ from a cold one, and the run derives its own index and continues. The cost is
77
+ deliberate: a typo in an endpoint is a suite that quietly got slower rather than
78
+ a build that went red, and the `behind` count `variance share` prints is the
79
+ signal that publishing has stopped.
80
+
81
+ The CLI gained a `share` section in its config, a `variance share` command that
82
+ says what the share holds for mainline or publishes what this run derived, and a
83
+ line on `variance run` naming where the index went. A lookup walks the lineage —
84
+ `merge-base` with the configured `mainline`, then first-parent — and asks this
85
+ machine's own cache for every commit before it asks the share, because the run
86
+ that just published is usually the one asking.
87
+
88
+ `docs/sharing.md` is the arrangement end to end, GitHub Actions first.
89
+ - e546e21: A subject is allowed to reference something that is not there
90
+
91
+ A fixture that points an `<img>` at a path nobody serves is testing the fallback,
92
+ and the broken state is the subject. Capture had no way to say so: the resolver
93
+ returned bytes or the capture was refused, so Material UI's CardMedia,
94
+ ImageListItem and Avatar suites — which all reference a deliberately absent
95
+ `/fake.png` — could not be captured at all.
96
+
97
+ `resolveResource` may answer `{ absent: true }`. The resource is recorded with
98
+ its status, and the renderer answers that status instead of counting the request
99
+ as one the document failed to carry.
100
+ - e546e21: A subject with no pixels is a baseline without an image, not a subject that got away
101
+
102
+ A wrapper whose only child went to a portal, or a conformance mount with no
103
+ children, occupies nothing. Refusing to photograph it is right; refusing the
104
+ subject was not. On Material UI's unit tier that was 1109 of 4371 subjects
105
+ reported as unobserved while the capture held their markup, their rules, their
106
+ component hashes and their accessibility tree — none of which was in doubt.
107
+
108
+ `Raster` makes the image optional: `bytes`, `width` and `height` are absent
109
+ together or present together, and `pictured` is the one place that narrows all
110
+ three. Absent means *this subject has no pixels*, which is a measurement — it
111
+ never means the image was lost. `occupiesPixels` asks the same question of a
112
+ record read without bytes, which is the only form a sidecar takes. `observe`
113
+ gets a second tier in `unpictured.ts`, where the comparison such a subject can
114
+ still take — document digest, component hashes, accessibility — is the whole
115
+ verdict. `promotionOf` promotes the sidecar alone when there is no `after`,
116
+ because the subject reached a verdict and the only missing half is the one a
117
+ camera would have produced.
118
+
119
+ The file-backed store carries the same nullable pair, and with it the split of a
120
+ baseline's two halves into two path prefixes. `identities` scans the record root,
121
+ because a subject with no pixels has no image directory to be found in and the
122
+ sibling scan would otherwise call another machine's baseline new.
123
+
124
+ Capture stops handing this to Playwright to fail on. Both screenshot paths used
125
+ to refuse a zero-area subject in terms of their own arguments — the clip path
126
+ with `Expected options.clip.height to be greater than 0`, the element path by
127
+ spending the full actionability timeout and then complaining about visibility —
128
+ so a reader had a component that rendered nothing and a sentence about a
129
+ rectangle. `captureSubject` decides it now, and the renderer names the subject.
130
+
131
+ **Operators:** this is schema **18**. `baselines` and `render_cache` drop
132
+ `NOT NULL` from `width` and `height`, shipped as `0016_pixel-less-baselines.sql`.
133
+ Apply it before pointing a CLI of this version at the deployment; `GET /version`
134
+ reports the schema a build expects.
135
+ - f4b328b: A run leaves behind what its suite looked like, in bytes another checkout can read
136
+
137
+ A run report answers about *this run*. Nothing answered about the suite from
138
+ somewhere else: which components exist, which subjects hold them, which of them
139
+ has an example of its own, and every name the run indexed. A branch asking any
140
+ of that against `main` had nothing to ask, because `main`'s answers were computed
141
+ on a machine that has since gone away — which is why *this component is not
142
+ covered anywhere* was a question with no reader, and why a second run could only
143
+ re-derive what the first already knew.
144
+
145
+ `@variance-authority/report/suite-index` is that artifact. `suiteIndexOf` takes
146
+ the baseline-bearing half of a report — the census, the subject denominator it is
147
+ counted against, and the lexicon — and `encodeSuiteIndex` writes it as one
148
+ segment, with `readSuiteIndex` and `writeSuiteIndex` beside the report's own file
149
+ functions. The other half of a composition does not travel: `movements` is what
150
+ moved since a comparison nobody else made, and `divergences` and `echoes` are
151
+ readings of one commit's snapshots. None of the three is a fact about the suite.
152
+
153
+ It carries the commit it was written at and nothing else about where it is, which
154
+ is the rule the selection index already settled — a commit answers *what changed
155
+ since this was written* exactly, and a timestamp says when a machine was rather
156
+ than where a tree was. An index that cannot name itself is the honest record of a
157
+ run that could not, and a reader holding one has nothing to diff.
158
+
159
+ It is not JSON. A lexicon is the same few thousand strings written once per
160
+ subject that holds them, and in JSON the repetition *is* the payload. Interned
161
+ once and referenced by number it stops being one, and equal facts encode to equal
162
+ bytes — a sorted dictionary, so a cache that keys on content actually hits.
163
+
164
+ `@variance-authority/core/segment` is the arithmetic underneath: named columns,
165
+ alignment, interned strings, and the validation a decode performs before it
166
+ believes a file. A column's width comes from the array's own type, so a mismatch
167
+ is a compile error rather than a decode against the wrong stride, and a malformed
168
+ reference rejects the whole file — a segment is a cache or a baseline, both of
169
+ which may be rebuilt, and half of either is worse than neither. The source index
170
+ now reads and writes through it and keeps its own bytes; what it still owns is the
171
+ part that is about source.
172
+ - e546e21: Two readings of two places are not a flake
173
+
174
+ `flake` is an accusation — *this reading is not repeatable* — and it was being
175
+ made about a comparison that never claimed repeatability. Lifting two instances
176
+ out of two subjects at one commit and parting them produces a case where every
177
+ input agrees, the tree holds, and the output moved; the old table had exactly one
178
+ row for that.
179
+
180
+ Where a component sits is decided by the boxes around it, and no component
181
+ receives its own position as a prop. Two instances that agreed on everything they
182
+ were handed and landed at different coordinates have contradicted nothing.
183
+
184
+ `partingOf` takes a `PartingPlace`: `same` for one subject read twice, across
185
+ revisions or across two moments of one scenario, and `elsewhere` for two
186
+ instances at one commit. It decides only between `flake` and the new `placed`
187
+ rung and slice, and it defaults to `same`, which is what every comparison across
188
+ revisions is. `divergencesOf` is the one caller that says `elsewhere`. The
189
+ sentence points outwards, at what put the component there, rather than at the
190
+ component.
191
+
192
+ ### Patch Changes
193
+
194
+ - e546e21: Four ways a large unit tier lost subjects, none of which said so
195
+
196
+ **The styling was gone before the capture looked.** A capture taken from a setup
197
+ file runs in the outermost `afterEach` a run has; every hook registered inside a
198
+ `describe` has already finished, and `onTestFinished` runs later still. CSS-in-JS
199
+ teardown lives in exactly those inner hooks — emotion's test renderer removes each
200
+ `<style>` tag it inserted — so the capture read the page with the styling taken
201
+ back off it. The class names were all still in the markup, they matched nothing,
202
+ and every subject captured, compared and passed against a photograph of unstyled
203
+ DOM. Measured on Material UI's unit tier: 399 of 400 captures carried no CSS at
204
+ all, and 1201 subjects laid out to zero height because nothing was sizing them.
205
+ `retainStyles` records style elements as they are inserted and puts the removed
206
+ ones back, in insertion order, for the length of one capture. The next test still
207
+ gets a clean page.
208
+
209
+ **A subject id can be longer than a filename.** A suite that names subjects after
210
+ the test that produced them — a file path and a full test name — passes 255 bytes
211
+ on ordinary tests, and the point of that convention is that the id says where the
212
+ subject came from. `fileNameFor` in core is now the one rule: a readable prefix,
213
+ plus a digest of the whole id when the id does not fit, because truncation alone
214
+ merges two tests into one file. The baseline store, the capture archive, the
215
+ CLI's image directory, the Playwright evidence directory and the Eyes journal all
216
+ use it; before this, each was one long subject id away from a raw `ENAMETOOLONG`
217
+ that never mentions a subject. `@variance-authority/eyes` declares
218
+ `@variance-authority/core` directly rather than reaching it through `react` — the
219
+ host stacks it keeps optional are Playwright and Testing Library, and a filename
220
+ rule is not one of them.
221
+
222
+ **An inline `url()` is spelled in entities.** `style="background-image:url(&quot;/a.png&quot;)"`
223
+ reaches the scan through HTML serialization, and reading it literally asked the
224
+ caller for bytes at a URL that exists nowhere but in the escaping. The HTML half
225
+ is scanned with the attribute's entities undone; the stylesheet half, which was
226
+ never escaped, is scanned as before.
227
+
228
+ **A stubbed `getComputedStyle` is not a broken asset scan.** Replacing
229
+ `window.getComputedStyle` with a map of the two properties a component reads is
230
+ the only way to drive some layouts in jsdom. The scan called `getPropertyValue`
231
+ on the plain object it got back, and every test in the file died on a `TypeError`
232
+ raised four frames below anything you wrote. There is nothing to scan and nothing
233
+ to complain about — whatever those styles would have named, the stub already
234
+ removed from the page. The attributes on the element are still read.
235
+
3
236
  ## 0.1.1
4
237
 
238
+ Lockstep release — nothing in this package changed. Every `@variance-authority/*` package shares one version.
239
+
5
240
  ## 0.1.0
6
241
 
7
242
  ### Minor Changes
8
243
 
9
244
  - 5c34e6d: Say which input moved, not just which tag did
10
-
245
+
11
246
  `partingOf(baseline, candidate)` climbs from a set of deltas to the component
12
247
  boundary that owns them and names the input that carried the decision:
13
248
  `Cart chose differently — useState #0 moved`, with `Summary` reported as having
@@ -16,7 +251,7 @@
16
251
  as lines. Two runs of one page that differ in a `<p>` where the other has a
17
252
  `<span>` previously produced a structural delta and nothing to say about it;
18
253
  the tag is now the symptom and the hook cell is the report.
19
-
254
+
20
255
  `holdingOf` from `@variance-authority/react` supplies the evidence — props,
21
256
  context values and hook cells as digests, per boundary, including
22
257
  `useSyncExternalStore` snapshots so a store that moved outside React is
@@ -28,28 +263,28 @@
28
263
  caller-supplied reader, opt in separately from `wiringOf`, and `capture` in
29
264
  `@variance-authority/unit-test` now accepts `provenanceOf`, `wiringOf` and
30
265
  `holdingOf` so a unit test can read one.
31
-
266
+
32
267
  A boundary whose own input could not be read is reported as `unread` and never
33
268
  as nondeterminism: `undetermined` is reserved for a component whose every input
34
269
  was read and agreed. A wrapper that roots a component boundary no longer
35
270
  collapses, because collapsing it discarded the holding — the cost is that a run
36
271
  reading holdings keeps wrappers a run without them removes, which is why both
37
272
  sides of a comparison must be read the same way.
38
-
273
+
39
274
  A component that ran no hooks is read as having run none, rather than as
40
275
  unreadable. React writes `_debugHookTypes = null` on every fiber and fills it on
41
276
  the first hook call, so the property's absence is the only silence — and
42
277
  collapsing the two reported the one shape most worth calling nondeterministic, a
43
278
  component with no props and no hooks that renders differently twice, as
44
279
  something nothing could be said about.
45
-
280
+
46
281
  `PartedBoundary.moved` names the properties the owned deltas named — `color`,
47
282
  `padding-top`, `width` — and `explainParting` spends them on the delta line.
48
283
  That is the last joint of the chain the rungs climb: a hook cell moved, a prop
49
284
  carried it down, and this is what the prop turned into on the page. A count and
50
285
  a band stop one link short of what somebody chasing a visual regression is
51
286
  trying to name.
52
-
287
+
53
288
  `Parting.slice` answers the question asked before which input moved: is this
54
289
  worth opening. Three facts are read independently — did the component tree
55
290
  move, did any input move, did the output move — and the combinations collapse
@@ -58,24 +293,24 @@
58
293
  since a pixel differ can say the screenshots match and nothing about what was
59
294
  rewritten underneath. `flake` is `settled`'s opposite number and is refused on
60
295
  silence — a run that read no boundary reports `unread`, never `flake`.
61
-
296
+
62
297
  `explainParting` leads with that line, and stops enumerating manifestations
63
298
  past three: one input at a fork can put a boundary on every component beneath
64
299
  it, and nine lines carrying one decision bury the one line worth reading.
65
-
300
+
66
301
  The whole layer is documented in `docs/parting.md`: the six slices, the seven
67
302
  rungs, what a holding carries and what it deliberately does not.
68
303
 
69
304
  ### Patch Changes
70
305
 
71
306
  - e8fee66: Refuse a value whose state is not in its own enumerable keys.
72
-
307
+
73
308
  `shapeValue` documented a `Date` as refused and encoded it instead. `typeof`
74
309
  answers `object`, `Object.entries` answers empty, and the canonical text came
75
310
  out `{}` — so a snapshot holding a timestamp addressed to the digest of an empty
76
311
  object, and a later run comparing a different timestamp reported unchanged. The
77
312
  same hole swallowed `Map`, `Set`, `URL`, and `RegExp`.
78
-
313
+
79
314
  An object with no own enumerable keys and a prototype other than `Object`'s is
80
315
  now refused by name at its pointer. A genuinely empty object is still a value,
81
316
  and an instance carrying its own fields still serializes them.