@variance-authority/core 0.14.0 → 0.15.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 (108) hide show
  1. package/CHANGELOG.md +454 -0
  2. package/README.md +13 -10
  3. package/dist/attribute/component-hash.d.ts +3 -2
  4. package/dist/attribute/component-hash.js +3 -2
  5. package/dist/attribute/component-hash.js.map +1 -1
  6. package/dist/attribute/composition.d.ts +16 -0
  7. package/dist/attribute/composition.js +4 -6
  8. package/dist/attribute/composition.js.map +1 -1
  9. package/dist/attribute/index.d.ts +3 -1
  10. package/dist/attribute/index.js +2 -1
  11. package/dist/attribute/index.js.map +1 -1
  12. package/dist/attribute/region.js +1 -5
  13. package/dist/attribute/region.js.map +1 -1
  14. package/dist/attribute/source.d.ts +10 -24
  15. package/dist/attribute/source.js +4 -38
  16. package/dist/attribute/source.js.map +1 -1
  17. package/dist/attribute/subject-pieces.d.ts +37 -0
  18. package/dist/attribute/subject-pieces.js +122 -0
  19. package/dist/attribute/subject-pieces.js.map +1 -0
  20. package/dist/format/capture.d.ts +25 -0
  21. package/dist/format/capture.js +6 -0
  22. package/dist/format/capture.js.map +1 -1
  23. package/dist/format/document.d.ts +2 -2
  24. package/dist/format/document.js +8 -2
  25. package/dist/format/document.js.map +1 -1
  26. package/dist/format/drive.d.ts +191 -0
  27. package/dist/format/drive.js +343 -0
  28. package/dist/format/drive.js.map +1 -0
  29. package/dist/format/environment.d.ts +20 -2
  30. package/dist/format/environment.js +7 -0
  31. package/dist/format/environment.js.map +1 -1
  32. package/dist/format/index.d.ts +6 -3
  33. package/dist/format/index.js +4 -2
  34. package/dist/format/index.js.map +1 -1
  35. package/dist/format/intervention.d.ts +79 -0
  36. package/dist/format/intervention.js.map +1 -1
  37. package/dist/format/origin.d.ts +32 -0
  38. package/dist/format/origin.js +54 -0
  39. package/dist/format/origin.js.map +1 -0
  40. package/dist/format/snapshot.d.ts +2 -0
  41. package/dist/format/snapshot.js.map +1 -1
  42. package/dist/format/stabilize.d.ts +28 -5
  43. package/dist/format/stabilize.js +48 -100
  44. package/dist/format/stabilize.js.map +1 -1
  45. package/dist/format/waits.d.ts +12 -0
  46. package/dist/format/waits.js +156 -0
  47. package/dist/format/waits.js.map +1 -0
  48. package/dist/index.d.ts +2 -1
  49. package/dist/index.js +2 -1
  50. package/dist/index.js.map +1 -1
  51. package/dist/nearest.d.ts +46 -0
  52. package/dist/nearest.js +102 -0
  53. package/dist/nearest.js.map +1 -0
  54. package/dist/relate/before.d.ts +19 -24
  55. package/dist/relate/before.js +35 -37
  56. package/dist/relate/before.js.map +1 -1
  57. package/dist/relate/beyond.d.ts +59 -0
  58. package/dist/relate/beyond.js +98 -0
  59. package/dist/relate/beyond.js.map +1 -0
  60. package/dist/relate/carried.d.ts +122 -0
  61. package/dist/relate/carried.js +279 -0
  62. package/dist/relate/carried.js.map +1 -0
  63. package/dist/relate/dominate.d.ts +19 -0
  64. package/dist/relate/dominate.js +77 -0
  65. package/dist/relate/dominate.js.map +1 -0
  66. package/dist/relate/index.d.ts +3 -0
  67. package/dist/relate/index.js +3 -0
  68. package/dist/relate/index.js.map +1 -1
  69. package/dist/relate/merkle.js +2 -11
  70. package/dist/relate/merkle.js.map +1 -1
  71. package/dist/relate/narrow.d.ts +2 -2
  72. package/dist/relate/narrow.js +26 -73
  73. package/dist/relate/narrow.js.map +1 -1
  74. package/dist/relate/reach.d.ts +1 -1
  75. package/dist/relate/reach.js +10 -53
  76. package/dist/relate/reach.js.map +1 -1
  77. package/dist/relate/records.d.ts +1 -1
  78. package/dist/relate/records.js.map +1 -1
  79. package/dist/rules/index.d.ts +3 -3
  80. package/dist/rules/index.js +3 -3
  81. package/dist/rules/index.js.map +1 -1
  82. package/dist/rules/normalize/cascade.d.ts +12 -0
  83. package/dist/rules/normalize/cascade.js +34 -16
  84. package/dist/rules/normalize/cascade.js.map +1 -1
  85. package/dist/rules/normalize/fixture.d.ts +1 -0
  86. package/dist/rules/normalize/fixture.js +1 -0
  87. package/dist/rules/normalize/fixture.js.map +1 -1
  88. package/dist/rules/normalize/index.d.ts +2 -2
  89. package/dist/rules/normalize/index.js +9 -2
  90. package/dist/rules/normalize/index.js.map +1 -1
  91. package/dist/rules/normalize/value.d.ts +13 -2
  92. package/dist/rules/normalize/value.js +42 -8
  93. package/dist/rules/normalize/value.js.map +1 -1
  94. package/dist/rules/normalize/wrapper.js +5 -0
  95. package/dist/rules/normalize/wrapper.js.map +1 -1
  96. package/dist/rules/ruleset.d.ts +24 -4
  97. package/dist/rules/ruleset.js +24 -4
  98. package/dist/rules/ruleset.js.map +1 -1
  99. package/dist/segment/index.d.ts +20 -4
  100. package/dist/segment/index.js +28 -7
  101. package/dist/segment/index.js.map +1 -1
  102. package/dist/shard/index.d.ts +11 -0
  103. package/dist/shard/index.js +11 -0
  104. package/dist/shard/index.js.map +1 -0
  105. package/dist/shard/placement.d.ts +117 -0
  106. package/dist/shard/placement.js +170 -0
  107. package/dist/shard/placement.js.map +1 -0
  108. package/package.json +8 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,459 @@
1
1
  # @variance-authority/core
2
2
 
3
+ ## 0.15.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 8dbc834: The components a module declares are read from its code. A declaration written
8
+ in a block comment, a JSDoc example or a template literal no longer names a
9
+ component, so a commented-out `function Retired()` no longer makes its file
10
+ the one that declares `Retired`. A name is declared by a statement of the
11
+ module itself: a function, class or binding inside a function body belongs to
12
+ that function and no longer counts. An `export async function Page()`, an
13
+ abstract class, a generator, a second name in `const A = 1, B = 2`, and a name
14
+ that starts with a non-ASCII capital or carries a `$` are now declared too.
15
+
16
+ `variance run`, `variance collect`, the Storybook collector and the route
17
+ collector resolve a component to its `file:line` from the same reading, through
18
+ `indexDeclarations`, which `sense` now exports. What that changes in the index
19
+ they build from `source.dirs`:
20
+
21
+ - The line is the first line of the declaring statement. A decorated class
22
+ sits at its first decorator, or at its `export` when the decorator is written
23
+ above the `export`, and a second name in a multi-line `const` at the line of
24
+ the `const`.
25
+ - `.d.ts` files, and test, spec and story files your own `exclude` lets
26
+ through, declare nothing.
27
+ - A file the parser cannot read declares nothing, where the text scan found
28
+ the components in it. That covers a Flow-annotated `.js` file, and a `.vue`,
29
+ `.svelte` or `.mdx` file you add to a collector's `extensions`, which is
30
+ parsed as TSX and fails. The index built from `source.dirs` or a collector's
31
+ `source` does not say so: the file's components are missing, and nothing
32
+ names the file. Only `sense`'s source index, for a file it records, names the
33
+ parse error in that file's record.
34
+ - Building the index needs `sense`'s native addon, which ships for macOS on
35
+ arm64, Linux on x64 and arm64 with glibc, and Windows on x64. On any other
36
+ machine, including an Intel Mac, an Alpine image and Windows on arm64, a
37
+ configured `source.dirs` fails at its first file and names why the addon did
38
+ not load, where the text scan ran anywhere. The route collector depends on
39
+ `sense` for it.
40
+
41
+ `indexSource` is removed from `@variance-authority/core/attribute`. Build a
42
+ `SourceIndex` with `indexDeclarations` from `@variance-authority/sense`, or
43
+ write one as plain data: a map from component name to `{ file, line, via }`.
44
+ `SourceRef['via']` loses `'declared'`, which nothing produced, and
45
+ `readCapture` in `@variance-authority/unit-test` refuses a capture whose source
46
+ index carries it.
47
+
48
+ The source index format moves to version 18, so an index written before this
49
+ release is rebuilt once instead of keeping the names it read from comments.
50
+ - 6755276: `variance distill --file <path>` lists each module no case entered under the
51
+ import that made the test file load it: the topmost import every path from the
52
+ test file to the module runs through, with nothing behind it that a case
53
+ entered, often in a module the test used rather than in the test file. A module
54
+ two imports reach is listed as shared, with the nearest file every path to it
55
+ runs through; one no static import reaches, such as a dynamic import's, is
56
+ listed apart. The file graph is read from the checkout only when the reading
57
+ has a module no case entered. A module the file mocked with a factory was never
58
+ evaluated, and its import is not walked.
59
+
60
+ `@variance-authority/core/relate` exports `dominatorsOf`, each node's immediate
61
+ dominator from a root. `distillFile` takes an optional `imports` lookup and
62
+ sets `cause` on each module no case entered; `@variance-authority/distill`
63
+ exports `LoadCause`.
64
+ - b065364: `::before` and `::after` are read as children of the element they hang from
65
+
66
+ Restyling or rewording a `::before` or `::after` box used to repaint the page
67
+ and move no hash: the rules written for it were matched against the element,
68
+ failed, and were dropped from both the snapshot and the render document. A
69
+ generated box now enters the capture as a child node tagged `::before` (first)
70
+ or `::after` (last), carrying the rules written for it. In a browser it carries
71
+ its computed style, projected onto the allowlist, and the text its `content`
72
+ says as a `#text` child; a box whose `content` is `none` or `normal`, or whose
73
+ `display` is `none`, is not in the tree. Strings are joined and `attr()` reads
74
+ the element's attribute; a part such as `counter()` or `open-quote` is carried
75
+ as written.
76
+
77
+ Under JSDOM, which computes no generated content, the declared rules answer,
78
+ picked by the same cascade order normalization uses: a box is in the tree when
79
+ the winning `content` gives it one and the winning `display` is not `none`, with the rules that style it and no computed style.
80
+ When that `content` is strings and `attr()`, the box says its words as a
81
+ `#text` child, as in a browser, so rewording it moves the snapshot. When it
82
+ holds a `counter()`, a quote, a `var()` or a `url()`, the box has no text and
83
+ carries `unread` on `RawNode` and `SemanticNode`, naming the parts it could not
84
+ resolve; `unread` is outside every hash. JSDOM's own parser drops a `content`
85
+ that is a single `attr()` or `counter()` and nothing else, so no box is read
86
+ for one.
87
+
88
+ This is part of `RULESET_VERSION` `r2`, the same `r2` that keeps the served
89
+ port out of a render. A subject with a `::before` or `::after` box has a new
90
+ render document, so after this upgrade its image shows the box and reads
91
+ `changed`; `variance accept --all` and Playwright's
92
+ `--update-snapshots=changed` adopt it.
93
+ - 1a0b845: A subject is read with nothing hovered, nothing focused, motion reduced and `Date` fixed
94
+
95
+ Every layout and raster baseline made before this release is compared under a
96
+ moved recipe once; a semantic baseline, read in jsdom, does not move. The
97
+ default recipes gained tricks, so the stabilization digest every reading records
98
+ has changed, and each subject names that recipe as the cause of whatever it
99
+ reports. Review the changes and adopt them with `variance accept --all`, as for
100
+ any recipe change.
101
+
102
+ The standard collection recipe now also holds:
103
+
104
+ - `no-hover`: the pointer is moved to `(-1, -1)`, off the page, once per page
105
+ before it loads. On the `variance` fixture's page it is moved again before
106
+ every observation, a frame each, and not put back: a menu a script opened on
107
+ hover is closed for your test's next step.
108
+ - `no-focus`: whatever holds focus is blurred before the subject is read,
109
+ including an element inside a shadow root. On a page your test or agent goes
110
+ on using, focus goes back to that element once the reading and any screenshot
111
+ of it are taken, so a test types where it left off. The blur still ran the
112
+ page's blur handlers, and what they did, such as a validate-on-blur error,
113
+ stays.
114
+ - `reduced-motion`: the browser reports `prefers-reduced-motion: reduce`, which
115
+ stops the script animations of a page that honours it, and the reading
116
+ declares the same preference among the conditions its `@media` rules are
117
+ resolved against. A `prefers-reduced-motion` condition you declare through
118
+ `createHarness`'s `features` wins, and the browser is asked for that value;
119
+ a declared `no-preference` is not recorded as `reduced-motion`. Rules under
120
+ `@media (prefers-reduced-motion: no-preference)` are not read by default;
121
+ leave `reduced-motion` out to read them.
122
+ - `freeze-clock`: `Date` reads one instant. A script added before the page's
123
+ own replaces `new Date()`, `Date()` and `Date.now()`, and gives the same
124
+ instant to `Intl.DateTimeFormat`'s `format()` and `formatToParts()` called
125
+ without a date. Timers, animation frames and `performance.now()` are the
126
+ browser's own, so a page that settles on a timer still settles. No Playwright
127
+ clock is installed, so `page.clock` stays yours, and a clock your test
128
+ installs replaces the fixed `Date`; the reading still records `freeze-clock`,
129
+ so leave it out of the list when your test owns the clock. `Temporal.Now` and
130
+ `Date` inside a worker are not fixed. The default instant is in the past, and
131
+ the browser's cookie store keeps real time: a cookie your page's script sets
132
+ with an expiry counted from `Date.now()` is already expired and is not
133
+ stored, so a dismissed consent banner can come back after the next
134
+ navigation. Set `varianceNow` or `now` near today, or leave `freeze-clock`
135
+ out.
136
+
137
+ The render and layout recipes gain `no-focus` alone, because a painted document
138
+ still honours a collected `autofocus`. The other three have nothing to act on
139
+ there: the date the page printed is already text in the document, and its
140
+ `@media` rules were resolved when it was read. A `<script>` inside a subject
141
+ root is carried into the render document and runs there under the real clock.
142
+ Every document is painted under the render recipe, so a reading taken without
143
+ `no-focus` is still painted blurred.
144
+
145
+ The instant defaults to `DEFAULT_NOW`, `2024-06-15T12:00:00.000Z`, exported
146
+ from `@variance-authority/core/format`. `routeCollector` and
147
+ `storybookCollector` take a `now` option to choose another. It must be an ISO
148
+ 8601 instant with its zone; anything else is refused before a browser opens.
149
+ An instant other than the default is spelled in the clock's id,
150
+ `freeze-clock@<instant>`, so it is part of the environment key, and a baseline taken at one instant is compared
151
+ with a run at another with the moved instant named as the cause.
152
+
153
+ To read a subject hovered, focused, animated or at the real time, pass your own
154
+ `stabilize` list without that trick: `DEFAULT_STABILIZE`, the ids of the
155
+ default recipe, filtered, as in
156
+ `DEFAULT_STABILIZE.filter((id) => id !== 'freeze-clock')`. It is exported from
157
+ `@variance-authority/core/format`, `@variance-authority/route-collector`,
158
+ `@variance-authority/storybook-collector` and
159
+ `@variance-authority/playwright-test`. `storybookCollector` and `observe` in
160
+ `@variance-authority/playwright-test` now take `stabilize`, as `routeCollector`
161
+ already did.
162
+
163
+ A trick needs a browser driver to apply it, and is recorded only where one did.
164
+ The collectors apply all four. So does the `variance` fixture in
165
+ `@variance-authority/playwright-test`: it sets `reduced-motion` and
166
+ `freeze-clock` on the `page` it hands your test, before the test body runs, and
167
+ takes the pointer off the page and releases focus before every observation,
168
+ giving focus back after it. Two new fixture options, `varianceStabilize` and
169
+ `varianceNow`, choose what it sets and the instant, and are set with
170
+ `test.use`; `varianceDriven` holds the ids it set. A fixed `Date` stops code
171
+ that times itself with `Date.now()`: a debounce or a throttle, such as lodash's
172
+ `debounce`, sees no time pass and never fires, so a test that types into a
173
+ debounced search box can stop getting results after this upgrade. A loop that
174
+ waits for `Date.now()` to pass a mark hangs the page, an animation timed by
175
+ `Date`, such as jQuery's `animate`, never calls its `complete`, and a client
176
+ that checks a token's `nbf` or `iat` against `Date.now()` can refuse a token
177
+ issued today. None of these failures names the clock. Leave
178
+ `freeze-clock` out for such a test, and for one that sets `page.clock` itself:
179
+
180
+ ```ts
181
+ import { DEFAULT_STABILIZE } from '@variance-authority/playwright-test';
182
+
183
+ test.use({ varianceStabilize: DEFAULT_STABILIZE.filter((id) => id !== 'freeze-clock') });
184
+ ```
185
+
186
+ On a page the fixture never handed your test,
187
+ such as one opened from your own browser context, call the new
188
+ `driveBeforeTest(page)` before `page.goto()` to set the same two; `observe` and
189
+ `createVariance` there apply `no-hover` and `no-focus`.
190
+ `@variance-authority/vitest-browser` reads from inside your page and has no
191
+ driver; `@variance-authority/presentation` reads through the Playwright `Page`
192
+ you hand it, a test's or an agent's live browser, and does not drive it. Both
193
+ apply `no-focus` only, giving focus back once the reading is taken. A trick left out is absent from the
194
+ reading's ids and digest, as a trick a tier cannot reach already is.
195
+ `stabilizeForObservation` in `@variance-authority/dom` takes `driven`, the ids
196
+ your driver applied, and returns `refocus()`, which gives focus back to the
197
+ element the reading released. `stabilizeAsRequested` takes the recipe by id, as
198
+ a page agent receives it. `createHarness` in `@variance-authority/playwright`
199
+ takes a `stabilize` recipe to drive its page before the first navigation, and
200
+ reports the ids it applied as `driven`. `createObservedHarness` opens one with
201
+ `observeNetwork` started before its first navigation, as both collectors do.
202
+
203
+ `createPlaywrightRenderer` no longer couples `no-focus` to `waitForFonts`:
204
+ `waitForFonts: false` turns off the waits alone.
205
+
206
+ `@variance-authority/core/format` exports `DEFAULT_STABILIZE`, `DEFAULT_NOW`,
207
+ `driveBeforeLoad` and `driveBeforeRead`, which apply the driver tricks to a
208
+ Playwright `Page`, `forDriver`, which drops the ones a host did not apply,
209
+ `featuresOf`, which declares the motion preference a recipe drove, and
210
+ `recipeToDrive`, which resolves a `stabilize` list and checks `now` before a
211
+ browser opens.
212
+ - d432339: A page reads its clock in UTC and formats for en-US, not for the host
213
+
214
+ The harness the route and Storybook collectors open, and the pages the renderer
215
+ paints, took their timezone and locale from the machine. A date the page
216
+ formatted read one way on a laptop in Sydney and another on a runner in
217
+ Virginia, from one build, and the two compared against one baseline as
218
+ `changed`.
219
+
220
+ Both are pinned: `timezoneId` to `UTC` and `locale` to `en-US`. Declare either
221
+ in `variance.config.json` (`"timezoneId": "Europe/Berlin"`, `"locale":
222
+ "de-DE"`) and the collectors and the renderer pin that instead; an unknown
223
+ timezone or a malformed language tag is refused by its field.
224
+ `createHarness` and `createPlaywrightRenderer` take the same two options.
225
+ `@variance-authority/core/format` exports their type, `PageRegion`, and
226
+ `@variance-authority/playwright` exports `pinnedRegion`, which fills in `UTC`
227
+ and `en-US` for a field left out.
228
+
229
+ Both are part of the environment key, and the renderer's pair is part of
230
+ `RenderIdentity.rasterization`. A capture read in another timezone or language
231
+ is another environment, and an image painted in one is compared with a
232
+ baseline painted in another, with `rasterization` named as the cause of
233
+ whatever moved.
234
+
235
+ `@variance-authority/playwright-test` paints each test in the timezone and
236
+ locale its page runs in: whatever the Playwright project or `test.use` sets.
237
+ When neither sets a `timezoneId`, `varianceFixtures` opens the page in `UTC`
238
+ rather than the host's, so a laptop and a runner in UTC read it under one key;
239
+ Playwright Test already opens it in `en-US` when nothing sets a `locale`. That
240
+ reaches every test of a suite extended with `varianceFixtures`, including tests
241
+ that never call `variance`, so a test that asserts a date formatted in your
242
+ machine's timezone reads it in `UTC` after this upgrade; set `timezoneId` in
243
+ `use` to keep your own. A page
244
+ you hand to `createVariance` is read and painted in whatever its own context
245
+ opened it in, which is the host's timezone when that context set none: open the
246
+ context with `timezoneId: 'UTC'` to match. `varianceRenderer` is now a test fixture, chosen
247
+ by the page, and `varianceRenderers` is the worker's set of renderers, one per
248
+ timezone and locale; a project that overrode `varianceRenderer` at worker scope
249
+ overrides it at test scope instead.
250
+
251
+ **Every image painted by the Playwright renderer is compared under a moved
252
+ recipe once after this upgrade**, because its recipe now carries the timezone
253
+ and locale. Each subject names `rasterization` as the cause of what it reports,
254
+ and one whose pixels moved is `changed`:
255
+
256
+ - With `variance run`: the run exits `1` for each `changed` subject. Review
257
+ them in `variance report --format html` and adopt them with
258
+ `variance accept --all`. A rerun reports `unchanged`.
259
+ - With `@variance-authority/playwright-test`: the suite fails each `changed`
260
+ test, and `--update-snapshots=changed` adopts them.
261
+ - With `@variance-authority/vitest-browser`: `vitest --update` adopts them, as
262
+ `variance accept --all` does.
263
+
264
+ `@variance-authority/vitest-browser` reads no `variance.config.json` and takes no
265
+ timezone or locale, so it paints in `UTC` and `en-US` whatever its tab ran in,
266
+ and neither it nor `@variance-authority/unit-test` writes a timezone or locale
267
+ to the environment key.
268
+ - 14aefbf: Ask what one test, or one story, checks that the narrower ones inside it do
269
+ not.
270
+
271
+ - `variance ask test-composition --file <test> --name <words>`, and
272
+ `docs_test_composition`, read one recorded test as the smaller tests whose
273
+ regions sit inside its own, the larger tests holding it, and the regions no
274
+ smaller test entered: modules only it enters, and paths through a smaller
275
+ test's module only it takes.
276
+ - `variance_composition {subject}` closes with the same reading for a story:
277
+ the smaller stories inside it, the larger ones holding it, and what no
278
+ smaller story renders — components only it mounts, and components a smaller
279
+ story renders another way. The run carries it per subject in the report's
280
+ structure section; `piecesOf` in `@variance-authority/core/attribute`
281
+ computes it.
282
+ - A component mounted by more than half the suite's subjects is structure. A
283
+ subject that mounts a component many times now counts once, where every
284
+ mount used to count: a chip story is an example of the chip again when the
285
+ pages around it mount chips many times over.
286
+ - f1c1b64: A bumped package and a moved manifest are read as the repository files they
287
+ change, once, before anything else is asked. `beyondReach` in
288
+ `@variance-authority/core/relate` walks a bump through the install to the first
289
+ files whose runtime imports load it, and stops there: a test that entered
290
+ anything further along evaluated that file on the way. A moved manifest becomes
291
+ every file beside it. `variance select`, `variance reach` and `variance run
292
+ --since` hand those files on as changed whole, so the suite's `before` and the
293
+ record answer for them as they do for an edited file.
294
+
295
+ `before` now holds files alone. A bump of a package your setup imports runs the
296
+ whole suite and names the setup file, not the package. `changedBefore` takes the
297
+ changed files only, and `BeforeReach.packages` is gone.
298
+
299
+ The `packages` option of `narrowByJourneys`, `narrowByExecution` and
300
+ `selectJourneyFile` is removed: pass the files `beyondReach` returns, each with
301
+ no line ranges.
302
+ - a4f55c1: One walk carries a change to every file that loads it
303
+
304
+ `@variance-authority/core/relate` exports `carried`, the walk against the
305
+ arrows that `dependentsOf`, its narrowed form and test selection's two importer
306
+ walks now all run. You pass seeds, each with what its change carries — the
307
+ whole file, or the names that moved — and a step that says what one importer
308
+ does with what reached it. The walk owns the worklist, enters each file once
309
+ per name, ends on any cycle, and treats a step with no answer as the whole
310
+ file. A walk you write over the graph gets the same answers on cycles,
311
+ self-imports and re-exports as the ones the selector runs, with no loop of your
312
+ own.
313
+ - bd98c3a: `--shard` in Vitest and Jest is placed by recorded time, and `variance shards` says how many to start
314
+
315
+ A Vitest or Jest config wrapped by `withTestSelection` now places the files of
316
+ `--shard k/n` by the time each took on the last recorded run, rather than
317
+ leaving the runner to divide them by count. Each shard computes the whole
318
+ placement from the same record and keeps its own part: files go longest first
319
+ to the shard with the least time so far, and a file the record has not seen is
320
+ priced at the median of the ones it has. The run says on stderr which record
321
+ placed it and what each shard was given:
322
+
323
+ ```text
324
+ variance-authority: shard 1/2 by the times recorded at 8e3b0f0e9028: 1 of 5 files, 9.0 s (shards 400 ms to 9.0 s)
325
+ ```
326
+
327
+ When the times cannot be read, the runner's own split runs instead, and the
328
+ line says why. A failed read never fails the run. A Jest config that names no
329
+ `testSequencer` finds Jest's own through Jest, so a layout that does not hoist
330
+ `@jest/test-sequencer`, as pnpm does not, runs as before. Under
331
+ `VARIANCE_AUTHORITY_SINCE`, only the files the selection keeps are placed.
332
+
333
+ `variance shards --setup <seconds>` prints how many shards a recorded suite is
334
+ worth, and with `--format json` a `matrix` of `k/n` strings for a CI job to
335
+ start. Every shard is charged its setup, so a shard is added only when it
336
+ shortens the wait by more than it spends, and the count stops at the slowest
337
+ test file, which the answer names with its slowest case. `--budget` asks for
338
+ the fewest shards that finish within it, `--workers` divides a shard's share
339
+ among its runner's workers, and `--since` leaves out what the change lets the
340
+ run skip, so a change that reaches no test answers `0 shards`. `--collected
341
+ <file>`, the runner's list of test files, counts a test file the record has not
342
+ seen, priced at the median.
343
+
344
+ It always answers, and names what it could not weigh, in `notes` under
345
+ `--format json`. Without `--setup` the count weighs no setup. A budget no
346
+ longer than the setup is out of reach at any count, and the answer is the count
347
+ that finishes soonest, with `why` set to `setup over budget`. `--since` without
348
+ `--collected` counts only recorded files and never answers fewer than one
349
+ shard, and `--at-distance` without `--since` counts the whole suite. With
350
+ nothing recorded it answers one shard until `--unrecorded <n>` names the count
351
+ to start.
352
+
353
+ `@variance-authority/core/shard` is the placement and the count, which
354
+ `variance run --shard` places stories and routes by too.
355
+ `@variance-authority/sense` exports `recordedTimes` and `fileCases`, and
356
+ `@variance-authority/cli` exports `suiteTimes`.
357
+ - af6b022: `before` moves out of `source` and is declared where the suite is. The top-level
358
+ `before` is what every suite rests on — a CI workflow, a `.nvmrc` — and a
359
+ suite's own `before`, beside its `kind`, is its runner config and its setup
360
+ files, each listed, since a config names its setup as a string and a string is
361
+ not an import. `variance select` now reads both for the suite it reads: each
362
+ entry is walked down the file graph, and a change to any file it reaches, a
363
+ `package.json` whose `exports`, `main` or `type` moved over one of those files,
364
+ or a bump of a package those files import, runs the whole suite and names what
365
+ moved. A directory in either list is walked from every file under it. `select
366
+ --execution` reads the same lists for the suite `--suite` names, the only one
367
+ declared, or every declared suite when none is named; beside a snapshot
368
+ `--execution`, `--suite` is refused, since both name the record. A manifest move
369
+ is read when the diff leaves every lockfile alone. A
370
+ config below the repository root inherits the root's `before`, as it inherits
371
+ its suites. A suite that declares nothing has nothing before its reach, and
372
+ `select` says so.
373
+ `variance run --since` reads the top-level list as it read `source.before`.
374
+
375
+ `source.before` is refused: move it to the top level.
376
+ - 754d04c: The port a build is served on is no part of a render
377
+
378
+ The route and Storybook collectors serve a build on a free port, and that port
379
+ reached three identities. An asset the page loaded was keyed by its absolute
380
+ URL, a `url()` in a computed style was the absolute URL the engine resolved,
381
+ and the document digest read both. One build served twice therefore read as two
382
+ environment keys and two documents: each run missed the render cache, painted
383
+ again, and read its document as changed against its own baseline.
384
+
385
+ An asset the page's own origin serves is keyed by its path, and the page's
386
+ origin is dropped from a `url()` before it is hashed. An asset another origin
387
+ serves keeps its absolute URL. A capture carries the page's `baseUrl` so core
388
+ knows which origin is the page's, and the renderer resolves a path-keyed asset
389
+ against the document's `baseUrl`. `documentDigest` reads the page's own base
390
+ and resources as paths, so one build served on two ports addresses one render
391
+ and the render cache hits; the document keeps them absolute for the renderer,
392
+ and a resource another origin serves stays absolute in the digest too.
393
+
394
+ A `@variance-authority/unit-test` capture keys its assets the same way, so a
395
+ `@variance-authority/vitest-browser` observation, which captures through it on
396
+ the port the runner had free, keeps the port out of its environment key.
397
+ `readCapture` reads back a capture whose asset is keyed by path, resolving the
398
+ key against the document's `baseUrl` as the renderer does; `assetUrl` in
399
+ `@variance-authority/core/format` is that one resolution.
400
+
401
+ **The document of every page served over `http` moves once after this
402
+ upgrade.** `documentDigest` now reads a document's base and resources without
403
+ the page's origin, so the digest of every document served over `http` or
404
+ `https` changes: every route, every story, every Vitest browser page, and every
405
+ Playwright page opened by URL. A moved document misses the render cache once,
406
+ and its image is still compared with the baseline: a page whose pixels did not
407
+ move reads `unchanged`, and one whose pixels did reads `changed`, which
408
+ `variance accept --all` and Playwright's `--update-snapshots=changed` adopt.
409
+ `RULESET_VERSION` moves from `r1` to `r2`. The ruleset is part of a snapshot's
410
+ environment key, not of the render recipe, so no image names it as a moved
411
+ identity; a snapshot diffed against one recorded under `r1` names the ruleset as its
412
+ environment root.
413
+ - 8187fb1: `didYouMean` and `nearest` are exported from `@variance-authority/core` and no
414
+ longer from `@variance-authority/mcp/tools`. Import them from
415
+ `@variance-authority/core`. A mistyped command or flag is refused, with the
416
+ nearest right one, without loading the MCP server's tools.
417
+ - ca7813e: `intersectionArea` is exported from `@variance-authority/core/format`
418
+
419
+ The area two `Rect`s share, zero when they only touch or do not meet. It sits
420
+ beside `Rect`, where the attribution already used it, so the review service can
421
+ ask whether two builds changed the same place.
422
+
423
+ ### Patch Changes
424
+
425
+ - 85e1a40: `wait-for-fonts` gives up after 15 seconds and names the font
426
+
427
+ A web font whose request never answered kept `document.fonts.ready` pending, and
428
+ the capture waited forever. `wait-for-fonts` now waits at most 15 seconds, the
429
+ same bound as `wait-for-images`, then fails naming every font still loading:
430
+ its family, and the `src` that requested it when the stylesheet declaring it is
431
+ same-origin. A face declared in a cross-origin stylesheet is named by family
432
+ alone, because the page cannot read that sheet's rules.
433
+ - cb8a52f: A source index another release wrote is no longer reported as damaged
434
+
435
+ After an upgrade, the first command that read the source index said it was
436
+ damaged and read only up to its first bad segment, and in CI it refused with
437
+ the same words. The segments were whole: an earlier release had written them in
438
+ another format version. Now the reader names both versions, for example that
439
+ the index was written in format version 15 and this release reads version 18.
440
+ On a workstation it rebuilds the index and says so, and `variance index` prints
441
+ `source index rebuilt over one written in format version 15`. In CI the refusal
442
+ names both versions and asks you to run `variance index` with this release
443
+ before the command that reads it. `ask orient --files` over such an index says
444
+ which version wrote it and that `variance index` rebuilds it.
445
+ - ef496ad: READMEs name what the program does
446
+
447
+ The package READMEs, and the `@variance-authority/vantage` and
448
+ `@variance-authority/playwright-test` descriptions, no longer write a test, a
449
+ record or a run as something that says, asks or knows. Each sentence names what
450
+ the program does: a command prints, a record holds a field, a test runs or
451
+ covers. Where a value is filled in from configuration or a default rather than
452
+ recorded, the README says so. Four renamed headings change their anchors:
453
+ `help`'s "Where the declaration is undocumented", `playwright`'s "Where a
454
+ component is declared, read from the engine", `storybook`'s "What a pass sends
455
+ the preview" and `sense`'s "Correct what a file's text declares it imports".
456
+
3
457
  ## 0.14.0
4
458
 
5
459
  Lockstep release — nothing in this package changed. Every `@variance-authority/*` package shares one version.
package/README.md CHANGED
@@ -158,12 +158,14 @@ specifier exports only the capture artifact.
158
158
  | `core/relate` | what rests on what: a file graph in adjacency form, the components a change reaches, and a closure digest over each one |
159
159
  | `core/segment` | columnar bytes: named columns, interned strings, and the validation a decode performs before it believes a file |
160
160
  | `core/share` | leaving those bytes where another machine finds them, as the latest of a mainline or a branch |
161
+ | `core/shard` | which shard runs which group of a suite, by recorded time, and how many shards it is worth: `shardCount` weighs each shard's `setup` against the time it saves, within a `budget` or up to a `max`, with `workers` running a shard's groups at once |
161
162
 
162
163
  Five of those are the order an answer travels through: `format`, `rules`,
163
- `compare`, `attribute`, `judge`. `plan` and `relate` are asked before anything
164
+ `compare`, `attribute`, `judge`. `plan` and `relate` run before anything
164
165
  is captured — one decides which baselines a run can use, the other which
165
166
  subjects are worth reading. `segment` and `share` are how a derived answer
166
- is written down and handed to another machine.
167
+ is written down and handed to another machine. `shard` splits one run across
168
+ several.
167
169
 
168
170
  ## The four words the output uses
169
171
 
@@ -172,9 +174,9 @@ structure and declared style; `chromium` adds layout and pixels. A profile that
172
174
  cannot measure a box cannot decide pixels either.
173
175
 
174
176
  An **identity** is the content hash that addresses a result, derived from the
175
- whole plan — ruleset version, viewport, policy, interventions. Two runs are
176
- comparable only when their identities match, so changing any part of the plan
177
- changes which baselines the run can see.
177
+ whole plan — ruleset version, viewport, policy, interventions. Two runs share
178
+ a result only when their identities match, so changing any part of the plan
179
+ changes the address its result is stored under.
178
180
 
179
181
  A **band** is the kind of change a delta is, and which band a delta lands in
180
182
  decides how loudly it is reported. Change frequency and change importance run
@@ -255,6 +257,7 @@ observation profiles differ.
255
257
  | `fingerprintOfMask` | `grid` controls the shape sample and `coverage` the minimum occupied share |
256
258
  | `compareLocales` / ignore validation | `slack` permits a declared locale distance; `sites` supplies resolved ignore locations and `now` evaluates expiry |
257
259
  | `buildDocket` / dependency reach | `sampleSize` limits review examples; `through` selects graph edge kinds, `avoid` names nodes a walk never covers, and `shadows` lists per file the modules its run never loads, so `affectedBy` leaves out a file every trail to which crosses one of its own shadows; `depends` adds the install — which package rests on which — so a dependency bump is a seed like any other and affects only the files that import it; `uses` names what each import binds and each re-export republishes, and `moved` lists per seed the exports its change changed, so `affectedBy` covers only the files that import one of them |
260
+ | `carried` | the walk under every reach question, with your own step per importer: `through` and `avoid` as above; `mask` and `via` take arrays you already hold, so a caller that walks again writes into the same marks and trail; `seedsHold` set to `false` lets a seed take back what a cycle carries to it, for a file that reads its own names through an importer |
258
261
  | `beforeReach` | `sensed` names the directories the scan already answers for, and the descent from a declared entry point stops at the first file under one of them rather than dragging the repository's own source in behind the harness |
259
262
  | `sharedClosures` | `floor` drops a shared subtree below a node count |
260
263
  | `lexiconOf` | `examples`, `declaredIn` and `regions` supply what the instances cannot: which components a subject is the example of, the files declaring each component, and the regions its journey covered |
@@ -263,8 +266,8 @@ observation profiles differ.
263
266
 
264
267
  ## Which input changed
265
268
 
266
- `compare` says what changed. `judge` says whether anyone should mind. Between
267
- them, `partingOf` says which input changed.
269
+ `compare` finds what changed. `judge` decides whether anyone should mind.
270
+ Between them, `partingOf` finds which input changed.
268
271
 
269
272
  Given two snapshots with holdings — the inputs a collector recorded at each
270
273
  component boundary — it walks the boundaries for the shallowest one whose inputs
@@ -293,7 +296,7 @@ run that recorded no component provenance gets `unread` instead.
293
296
  What a suite is made of is a fact about a commit rather than about a run.
294
297
  `core/segment` writes such a thing as columns — equal facts encode to equal
295
298
  bytes, which is what lets a transport skip an upload — and `core/share` keeps
296
- the latest of those bytes on a line. A checkout asks *what is mainline now*,
299
+ the latest of those bytes on a line. A checkout looks up *what is mainline now*,
297
300
  and a line answers that: one mainline or one branch, holding the latest entry
298
301
  of each kind — the run report, one record per suite — and the images those
299
302
  entries name, by digest.
@@ -329,7 +332,7 @@ appended to. `headers` is sent on every request, which is where a bucket's
329
332
  `Authorization` or a deployment's token goes. `method` is the verb a write uses
330
333
  — `PUT` for a bucket, `POST` for a deployment that routes on it. `timeoutMs`
331
334
  is how long one request may take, reading its body included, before the store
332
- is `unreachable` with a message that says it timed out; it is 60 000 when you
335
+ is `unreachable` with a message naming the timeout; it is 60 000 when you
333
336
  leave it out. A presigned base needs only the endpoint. `memoryLineCell()`
334
337
  holds a line in this process, for tests and for a run that shares with itself.
335
338
 
@@ -343,7 +346,7 @@ A publish replaces only the entries it carries, so suites from different jobs
343
346
  land on one line. On a mainline, a held entry whose commit descends from yours
344
347
  stays: that is an older run finishing last. A held entry in a newer format stays
345
348
  on any line. A line reports why it missed — `absent`, `newer`, `refused`,
346
- `unreachable` or `unreadable` — because each asks you for a different action.
349
+ `unreachable` or `unreadable` — because each needs a different action from you.
347
350
  Everything a line holds can be derived again, so a miss costs you the
348
351
  derivation. [Sharing an evaluation](https://variance-authority.dev/docs/sharing)
349
352
  is the operator's side of it.
@@ -103,8 +103,9 @@ export declare function causesBetween(before: readonly ComponentHash[], after: r
103
103
  * A missing `geometry` on either side is the profile saying it has no layout
104
104
  * engine, and is not a difference. Treating absent as a change would report
105
105
  * every component as having moved the moment a jsdom baseline met a Chromium
106
- * run — which the environment key already refuses as `incomparable`, so this
107
- * would be a second, wronger answer to a question already settled.
106
+ * run — which the environment key already names as the cause of that
107
+ * comparison, so this would be a second, wronger answer to a question already
108
+ * settled.
108
109
  */
109
110
  export declare function movedBands(before: BandDigests, after: BandDigests): readonly Band[];
110
111
  /**
@@ -198,8 +198,9 @@ function ownContentMoved(before, after) {
198
198
  * A missing `geometry` on either side is the profile saying it has no layout
199
199
  * engine, and is not a difference. Treating absent as a change would report
200
200
  * every component as having moved the moment a jsdom baseline met a Chromium
201
- * run — which the environment key already refuses as `incomparable`, so this
202
- * would be a second, wronger answer to a question already settled.
201
+ * run — which the environment key already names as the cause of that
202
+ * comparison, so this would be a second, wronger answer to a question already
203
+ * settled.
203
204
  */
204
205
  export function movedBands(before, after) {
205
206
  const moved = new Set();