@variance-authority/core 0.14.0 → 0.17.1

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 (147) hide show
  1. package/CHANGELOG.md +878 -0
  2. package/README.md +38 -17
  3. package/dist/attribute/component-hash.d.ts +14 -5
  4. package/dist/attribute/component-hash.js +14 -5
  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/movement.d.ts +1 -1
  13. package/dist/attribute/movement.js.map +1 -1
  14. package/dist/attribute/region.d.ts +47 -4
  15. package/dist/attribute/region.js +91 -22
  16. package/dist/attribute/region.js.map +1 -1
  17. package/dist/attribute/source.d.ts +10 -24
  18. package/dist/attribute/source.js +4 -38
  19. package/dist/attribute/source.js.map +1 -1
  20. package/dist/attribute/subject-pieces.d.ts +37 -0
  21. package/dist/attribute/subject-pieces.js +122 -0
  22. package/dist/attribute/subject-pieces.js.map +1 -0
  23. package/dist/compare/band.d.ts +8 -2
  24. package/dist/compare/band.js +7 -2
  25. package/dist/compare/band.js.map +1 -1
  26. package/dist/compare/diff/attribution.js +6 -4
  27. package/dist/compare/diff/attribution.js.map +1 -1
  28. package/dist/compare/diff/compare-nodes.js +9 -1
  29. package/dist/compare/diff/compare-nodes.js.map +1 -1
  30. package/dist/compare/diff/match.d.ts +2 -1
  31. package/dist/compare/diff/match.js +52 -0
  32. package/dist/compare/diff/match.js.map +1 -1
  33. package/dist/compare/parting.js +6 -4
  34. package/dist/compare/parting.js.map +1 -1
  35. package/dist/format/capture.d.ts +25 -0
  36. package/dist/format/capture.js +6 -0
  37. package/dist/format/capture.js.map +1 -1
  38. package/dist/format/document.d.ts +28 -5
  39. package/dist/format/document.js +8 -2
  40. package/dist/format/document.js.map +1 -1
  41. package/dist/format/drive.d.ts +191 -0
  42. package/dist/format/drive.js +343 -0
  43. package/dist/format/drive.js.map +1 -0
  44. package/dist/format/environment.d.ts +20 -2
  45. package/dist/format/environment.js +7 -0
  46. package/dist/format/environment.js.map +1 -1
  47. package/dist/format/index.d.ts +13 -3
  48. package/dist/format/index.js +8 -2
  49. package/dist/format/index.js.map +1 -1
  50. package/dist/format/intervention.d.ts +79 -0
  51. package/dist/format/intervention.js.map +1 -1
  52. package/dist/format/origin.d.ts +32 -0
  53. package/dist/format/origin.js +54 -0
  54. package/dist/format/origin.js.map +1 -0
  55. package/dist/format/packed.d.ts +32 -0
  56. package/dist/format/packed.js +23 -0
  57. package/dist/format/packed.js.map +1 -0
  58. package/dist/format/shape.d.ts +31 -0
  59. package/dist/format/shape.js +269 -0
  60. package/dist/format/shape.js.map +1 -0
  61. package/dist/format/snapshot.d.ts +2 -0
  62. package/dist/format/snapshot.js.map +1 -1
  63. package/dist/format/stabilize.d.ts +35 -6
  64. package/dist/format/stabilize.js +58 -103
  65. package/dist/format/stabilize.js.map +1 -1
  66. package/dist/format/steps.d.ts +44 -0
  67. package/dist/format/steps.js +67 -0
  68. package/dist/format/steps.js.map +1 -0
  69. package/dist/format/waits.d.ts +12 -0
  70. package/dist/format/waits.js +156 -0
  71. package/dist/format/waits.js.map +1 -0
  72. package/dist/format/width.d.ts +35 -0
  73. package/dist/format/width.js +38 -0
  74. package/dist/format/width.js.map +1 -0
  75. package/dist/index.d.ts +2 -1
  76. package/dist/index.js +2 -1
  77. package/dist/index.js.map +1 -1
  78. package/dist/judge/docket.js +1 -0
  79. package/dist/judge/docket.js.map +1 -1
  80. package/dist/judge/index.d.ts +1 -0
  81. package/dist/judge/index.js +1 -0
  82. package/dist/judge/index.js.map +1 -1
  83. package/dist/judge/restamp.d.ts +20 -0
  84. package/dist/judge/restamp.js +20 -0
  85. package/dist/judge/restamp.js.map +1 -0
  86. package/dist/nearest.d.ts +46 -0
  87. package/dist/nearest.js +102 -0
  88. package/dist/nearest.js.map +1 -0
  89. package/dist/relate/before.d.ts +19 -24
  90. package/dist/relate/before.js +37 -39
  91. package/dist/relate/before.js.map +1 -1
  92. package/dist/relate/beyond.d.ts +59 -0
  93. package/dist/relate/beyond.js +98 -0
  94. package/dist/relate/beyond.js.map +1 -0
  95. package/dist/relate/carried.d.ts +122 -0
  96. package/dist/relate/carried.js +279 -0
  97. package/dist/relate/carried.js.map +1 -0
  98. package/dist/relate/graph.d.ts +14 -12
  99. package/dist/relate/graph.js +12 -11
  100. package/dist/relate/graph.js.map +1 -1
  101. package/dist/relate/index.d.ts +3 -1
  102. package/dist/relate/index.js +3 -1
  103. package/dist/relate/index.js.map +1 -1
  104. package/dist/relate/merkle.js +2 -11
  105. package/dist/relate/merkle.js.map +1 -1
  106. package/dist/relate/narrow.d.ts +2 -2
  107. package/dist/relate/narrow.js +26 -73
  108. package/dist/relate/narrow.js.map +1 -1
  109. package/dist/relate/reach.d.ts +6 -6
  110. package/dist/relate/reach.js +10 -53
  111. package/dist/relate/reach.js.map +1 -1
  112. package/dist/relate/records.d.ts +6 -5
  113. package/dist/relate/records.js +3 -2
  114. package/dist/relate/records.js.map +1 -1
  115. package/dist/rules/index.d.ts +3 -3
  116. package/dist/rules/index.js +3 -3
  117. package/dist/rules/index.js.map +1 -1
  118. package/dist/rules/normalize/cascade.d.ts +12 -0
  119. package/dist/rules/normalize/cascade.js +34 -16
  120. package/dist/rules/normalize/cascade.js.map +1 -1
  121. package/dist/rules/normalize/fixture.d.ts +3 -0
  122. package/dist/rules/normalize/fixture.js +3 -1
  123. package/dist/rules/normalize/fixture.js.map +1 -1
  124. package/dist/rules/normalize/index.d.ts +2 -2
  125. package/dist/rules/normalize/index.js +9 -2
  126. package/dist/rules/normalize/index.js.map +1 -1
  127. package/dist/rules/normalize/value.d.ts +13 -2
  128. package/dist/rules/normalize/value.js +42 -8
  129. package/dist/rules/normalize/value.js.map +1 -1
  130. package/dist/rules/normalize/wrapper.js +5 -0
  131. package/dist/rules/normalize/wrapper.js.map +1 -1
  132. package/dist/rules/ruleset.d.ts +24 -4
  133. package/dist/rules/ruleset.js +24 -4
  134. package/dist/rules/ruleset.js.map +1 -1
  135. package/dist/segment/index.d.ts +20 -4
  136. package/dist/segment/index.js +28 -7
  137. package/dist/segment/index.js.map +1 -1
  138. package/dist/shard/index.d.ts +11 -0
  139. package/dist/shard/index.js +11 -0
  140. package/dist/shard/index.js.map +1 -0
  141. package/dist/shard/placement.d.ts +119 -0
  142. package/dist/shard/placement.js +156 -0
  143. package/dist/shard/placement.js.map +1 -0
  144. package/dist/share/index.d.ts +1 -1
  145. package/dist/share/index.js +1 -1
  146. package/dist/share/index.js.map +1 -1
  147. package/package.json +8 -1
@@ -0,0 +1,156 @@
1
+ /**
2
+ * The two tricks that block until something outside the page has landed.
3
+ *
4
+ * Apart from [`stabilize.ts`](./stabilize.ts) because they are the tricks with
5
+ * the most page-side code: each ships a closure into the page, bounds its wait at
6
+ * 15000ms and names what never answered. The rest of the vocabulary is a line of
7
+ * CSS or a screenshot option.
8
+ */
9
+ export const waitForFonts = {
10
+ id: 'wait-for-fonts',
11
+ trick: 'wait',
12
+ needs: 'layout',
13
+ governs: 'fonts',
14
+ because: 'waited for web fonts, whose advances change every metric on the page',
15
+ // Written without a module-scope helper on purpose: a settle closure is
16
+ // shipped to a page as source text, so anything it names by identifier is a
17
+ // `ReferenceError` on the far side. Types are erased and therefore free.
18
+ settle: async (target) => {
19
+ await target.evaluate(async () => {
20
+ const view = globalThis;
21
+ const fonts = view.document.fonts;
22
+ if (fonts === undefined)
23
+ return;
24
+ // Bounded as `waitForImages` is: a font request that never answers neither
25
+ // loads nor fails, so `ready` never settles. Refused at the same 15000ms,
26
+ // naming each face still loading by family and the source its rule asked for.
27
+ let timer;
28
+ const expired = new Promise((_, reject) => {
29
+ timer = view.setTimeout(() => {
30
+ const unquoted = (family) => family.trim().replace(/^(["'])(.*)\1$/, '$2');
31
+ const sources = new Map();
32
+ const visit = (rules) => {
33
+ for (const rule of Array.from(rules)) {
34
+ if (rule.cssRules !== undefined)
35
+ visit(rule.cssRules);
36
+ // An `@import` carries its sheet; a cross-origin one hides its rules.
37
+ try {
38
+ if (rule.styleSheet != null)
39
+ visit(rule.styleSheet.cssRules);
40
+ }
41
+ catch { }
42
+ // Only a `@font-face` rule carries `src`; a style rule reads empty.
43
+ const source = rule.style?.getPropertyValue('src') ?? '';
44
+ if (source === '')
45
+ continue;
46
+ const family = unquoted(rule.style.getPropertyValue('font-family'));
47
+ sources.set(family, [...(sources.get(family) ?? []), source]);
48
+ }
49
+ };
50
+ for (const sheet of Array.from(view.document.styleSheets)) {
51
+ try {
52
+ visit(sheet.cssRules);
53
+ }
54
+ catch {
55
+ // A cross-origin sheet hides its rules; its faces are named by family alone.
56
+ }
57
+ }
58
+ const stuck = Array.from(fonts).filter((face) => face.status === 'loading');
59
+ const named = stuck.map((face) => {
60
+ const asked = sources.get(unquoted(face.family));
61
+ return asked === undefined ? face.family : `${face.family} from ${asked.join(' or ')}`;
62
+ });
63
+ reject(new Error(`${stuck.length} web font(s) had not loaded after 15000ms, and neither answered ` +
64
+ `nor failed: ${named.join('; ')}`));
65
+ }, 15000);
66
+ });
67
+ try {
68
+ await Promise.race([fonts.ready, expired]);
69
+ }
70
+ finally {
71
+ view.clearTimeout(timer);
72
+ }
73
+ });
74
+ },
75
+ };
76
+ export const waitForImages = {
77
+ id: 'wait-for-images',
78
+ trick: 'wait',
79
+ needs: 'layout',
80
+ governs: 'images',
81
+ because: 'waited for images to decode, since their intrinsic size participates in layout, and asked ' +
82
+ 'for the ones the browser had deferred',
83
+ settle: async (target) => {
84
+ await target.evaluate(async () => {
85
+ const view = globalThis;
86
+ const pending = Array.from(view.document.images).filter((image) => !image.complete);
87
+ const arrived = Promise.all(pending.map((image) => new Promise((resolve) => {
88
+ image.addEventListener('load', () => resolve(), { once: true });
89
+ image.addEventListener('error', () => resolve(), { once: true });
90
+ })));
91
+ // Asked for, after the listeners are attached and before anything is
92
+ // awaited. A `loading="lazy"` image outside the viewport has not been
93
+ // requested and will not be until something scrolls, so waiting on it is
94
+ // waiting for a decision the browser has already taken the other way.
95
+ //
96
+ // This is the same act as pinning an animation, not a different kind of
97
+ // thing: what the page shows stops depending on where the viewport
98
+ // happens to be. It matters most for the readings that need it most — a
99
+ // full-page capture of a long page is exactly the case where most of the
100
+ // images are deferred, and photographing it without them yields a picture
101
+ // full of empty boxes whose contents change with the browser's loading
102
+ // heuristics rather than with the product.
103
+ const asServed = pending.map((image) => image.loading);
104
+ for (const image of pending)
105
+ image.loading = 'eager';
106
+ // Bounded, and it throws when the bound is reached.
107
+ //
108
+ // A response can stall: a CDN that never answers, an image endpoint that
109
+ // deadlocks under its own concurrency, an image the browser deferred and
110
+ // will not request from where the page is scrolled. None of those fire
111
+ // `load` and none fire `error`, so an unbounded wait turns one stuck
112
+ // request into a run that never ends and never says why — not a wrong
113
+ // answer, no answer.
114
+ //
115
+ // Giving up quietly would be worse than the hang in a subtler way: it
116
+ // photographs a page with holes in it and reports the holes as a change,
117
+ // inventing a regression out of the network this trick exists to hold
118
+ // still. Refused instead, naming what it was still waiting for, because
119
+ // "these two never answered" is a finding about the application.
120
+ //
121
+ // The bound is written twice — once as the delay, once in the sentence —
122
+ // because a settle closure is shipped to the page as source text and a
123
+ // constant it named would be a `ReferenceError` on the far side.
124
+ // Held so the loser can be cancelled. A timer left to fire after the
125
+ // images arrived rejects a promise nothing is waiting on any more, which
126
+ // the page reports as an unhandled rejection — this trick's own noise,
127
+ // arriving in the console of every subject it succeeded on.
128
+ let timer;
129
+ const expired = new Promise((_, reject) => {
130
+ timer = view.setTimeout(() => {
131
+ const stuck = pending.filter((image) => !image.complete);
132
+ reject(new Error(`${stuck.length} image(s) had not loaded after 15000ms, and neither answered ` +
133
+ `nor failed: ${stuck.map((image) => image.currentSrc || image.src).join(', ')}`));
134
+ }, 15000);
135
+ });
136
+ try {
137
+ await Promise.race([arrived, expired]);
138
+ }
139
+ finally {
140
+ view.clearTimeout(timer);
141
+ // Put back, because the trick has to be invisible to the tier that
142
+ // reads the page after it. `loading` is a reflected attribute, so a
143
+ // document whose deferred images were switched to `eager` and left that
144
+ // way is a document that no longer matches the one the product served —
145
+ // and which images were still undecoded when this ran is a question
146
+ // about the network, so the edit lands in some readings and not others.
147
+ // Left in, it makes the same page at two widths disagree about its own
148
+ // markup: this trick filed as a finding against the application.
149
+ for (let index = 0; index < pending.length; index += 1) {
150
+ pending[index].loading = asServed[index];
151
+ }
152
+ }
153
+ });
154
+ },
155
+ };
156
+ //# sourceMappingURL=waits.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"waits.js","sourceRoot":"","sources":["../../src/format/waits.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Intervention, PageGlobals } from './intervention.js';\n\n/**\n * The two tricks that block until something outside the page has landed.\n *\n * Apart from [`stabilize.ts`](./stabilize.ts) because they are the tricks with\n * the most page-side code: each ships a closure into the page, bounds its wait at\n * 15000ms and names what never answered. The rest of the vocabulary is a line of\n * CSS or a screenshot option.\n */\n\nexport const waitForFonts: Intervention = {\n id: 'wait-for-fonts',\n trick: 'wait',\n needs: 'layout',\n governs: 'fonts',\n because: 'waited for web fonts, whose advances change every metric on the page',\n // Written without a module-scope helper on purpose: a settle closure is\n // shipped to a page as source text, so anything it names by identifier is a\n // `ReferenceError` on the far side. Types are erased and therefore free.\n settle: async (target) => {\n await target.evaluate(async () => {\n const view = globalThis as unknown as PageGlobals;\n const fonts = view.document.fonts;\n if (fonts === undefined) return;\n // Bounded as `waitForImages` is: a font request that never answers neither\n // loads nor fails, so `ready` never settles. Refused at the same 15000ms,\n // naming each face still loading by family and the source its rule asked for.\n let timer: unknown;\n const expired = new Promise<never>((_, reject) => {\n timer = view.setTimeout(() => {\n const unquoted = (family: string): string => family.trim().replace(/^([\"'])(.*)\\1$/, '$2');\n const sources = new Map<string, string[]>();\n type Rules = PageGlobals['document']['styleSheets'][number]['cssRules'];\n const visit = (rules: Rules): void => {\n for (const rule of Array.from(rules)) {\n if (rule.cssRules !== undefined) visit(rule.cssRules);\n // An `@import` carries its sheet; a cross-origin one hides its rules.\n try {\n if (rule.styleSheet != null) visit(rule.styleSheet.cssRules);\n } catch {}\n // Only a `@font-face` rule carries `src`; a style rule reads empty.\n const source = rule.style?.getPropertyValue('src') ?? '';\n if (source === '') continue;\n const family = unquoted(rule.style!.getPropertyValue('font-family'));\n sources.set(family, [...(sources.get(family) ?? []), source]);\n }\n };\n for (const sheet of Array.from(view.document.styleSheets)) {\n try {\n visit(sheet.cssRules);\n } catch {\n // A cross-origin sheet hides its rules; its faces are named by family alone.\n }\n }\n const stuck = Array.from(fonts).filter((face) => face.status === 'loading');\n const named = stuck.map((face) => {\n const asked = sources.get(unquoted(face.family));\n return asked === undefined ? face.family : `${face.family} from ${asked.join(' or ')}`;\n });\n reject(\n new Error(\n `${stuck.length} web font(s) had not loaded after 15000ms, and neither answered ` +\n `nor failed: ${named.join('; ')}`,\n ),\n );\n }, 15000);\n });\n try {\n await Promise.race([fonts.ready, expired]);\n } finally {\n view.clearTimeout(timer);\n }\n });\n },\n};\n\nexport const waitForImages: Intervention = {\n id: 'wait-for-images',\n trick: 'wait',\n needs: 'layout',\n governs: 'images',\n because:\n 'waited for images to decode, since their intrinsic size participates in layout, and asked ' +\n 'for the ones the browser had deferred',\n settle: async (target) => {\n await target.evaluate(async () => {\n const view = globalThis as unknown as PageGlobals;\n const pending = Array.from(view.document.images).filter((image) => !image.complete);\n\n const arrived = Promise.all(\n pending.map(\n (image) =>\n new Promise<void>((resolve) => {\n image.addEventListener('load', () => resolve(), { once: true });\n image.addEventListener('error', () => resolve(), { once: true });\n }),\n ),\n );\n\n // Asked for, after the listeners are attached and before anything is\n // awaited. A `loading=\"lazy\"` image outside the viewport has not been\n // requested and will not be until something scrolls, so waiting on it is\n // waiting for a decision the browser has already taken the other way.\n //\n // This is the same act as pinning an animation, not a different kind of\n // thing: what the page shows stops depending on where the viewport\n // happens to be. It matters most for the readings that need it most — a\n // full-page capture of a long page is exactly the case where most of the\n // images are deferred, and photographing it without them yields a picture\n // full of empty boxes whose contents change with the browser's loading\n // heuristics rather than with the product.\n const asServed = pending.map((image) => image.loading);\n for (const image of pending) image.loading = 'eager';\n\n // Bounded, and it throws when the bound is reached.\n //\n // A response can stall: a CDN that never answers, an image endpoint that\n // deadlocks under its own concurrency, an image the browser deferred and\n // will not request from where the page is scrolled. None of those fire\n // `load` and none fire `error`, so an unbounded wait turns one stuck\n // request into a run that never ends and never says why — not a wrong\n // answer, no answer.\n //\n // Giving up quietly would be worse than the hang in a subtler way: it\n // photographs a page with holes in it and reports the holes as a change,\n // inventing a regression out of the network this trick exists to hold\n // still. Refused instead, naming what it was still waiting for, because\n // \"these two never answered\" is a finding about the application.\n //\n // The bound is written twice — once as the delay, once in the sentence —\n // because a settle closure is shipped to the page as source text and a\n // constant it named would be a `ReferenceError` on the far side.\n // Held so the loser can be cancelled. A timer left to fire after the\n // images arrived rejects a promise nothing is waiting on any more, which\n // the page reports as an unhandled rejection — this trick's own noise,\n // arriving in the console of every subject it succeeded on.\n let timer: unknown;\n\n const expired = new Promise<never>((_, reject) => {\n timer = view.setTimeout(() => {\n const stuck = pending.filter((image) => !image.complete);\n reject(\n new Error(\n `${stuck.length} image(s) had not loaded after 15000ms, and neither answered ` +\n `nor failed: ${stuck.map((image) => image.currentSrc || image.src).join(', ')}`,\n ),\n );\n }, 15000);\n });\n\n try {\n await Promise.race([arrived, expired]);\n } finally {\n view.clearTimeout(timer);\n\n // Put back, because the trick has to be invisible to the tier that\n // reads the page after it. `loading` is a reflected attribute, so a\n // document whose deferred images were switched to `eager` and left that\n // way is a document that no longer matches the one the product served —\n // and which images were still undecoded when this ran is a question\n // about the network, so the edit lands in some readings and not others.\n // Left in, it makes the same page at two widths disagree about its own\n // markup: this trick filed as a finding against the application.\n for (let index = 0; index < pending.length; index += 1) {\n pending[index]!.loading = asServed[index]!;\n }\n }\n });\n },\n};\n"]}
@@ -0,0 +1,35 @@
1
+ /** The part of a subject its width is spelled into. */
2
+ export interface Widenable {
3
+ readonly subject: {
4
+ readonly id: string;
5
+ };
6
+ /** What a baseline is filed by under its title; each width carries its own. */
7
+ readonly name?: string;
8
+ }
9
+ /**
10
+ * The subject read at one width: its id and its name, each with the width.
11
+ *
12
+ * A width is a subject of its own — its own baseline, its own verdict — so the
13
+ * width goes into everything a baseline is found by. The id carries it as
14
+ * `<id>@<width>`, and so must the name, or a baseline path filed by title and
15
+ * name would hold every width in one file.
16
+ *
17
+ * One spelling for every party that names a width: the collector that plans or
18
+ * visits one, and the run that asks a store which baselines a story it did not
19
+ * visit may have left at widths it never read.
20
+ */
21
+ export declare function atWidth<S extends Widenable>(subject: S, width: number): S;
22
+ /**
23
+ * The id a width id was read from: `story:card@375` is `story:card` at 375.
24
+ * Absent for an id that carries no width, so that a party asking whether a
25
+ * realized id belongs to a planned one never takes a story whose id merely
26
+ * begins with the planned one for one of its widths.
27
+ */
28
+ export declare function widenedFrom(id: string): string | undefined;
29
+ /**
30
+ * Whether a visit to `planned` may realize `id`: the planned subject itself, or
31
+ * it at a width, `<planned>@<digits>`. Nothing else, so a story whose id merely
32
+ * begins with the planned one is never filed as one of its readings.
33
+ */
34
+ export declare function realizes(planned: string, id: string): boolean;
35
+ //# sourceMappingURL=width.d.ts.map
@@ -0,0 +1,38 @@
1
+ // compass: variance-authority.normalization
2
+ /**
3
+ * The subject read at one width: its id and its name, each with the width.
4
+ *
5
+ * A width is a subject of its own — its own baseline, its own verdict — so the
6
+ * width goes into everything a baseline is found by. The id carries it as
7
+ * `<id>@<width>`, and so must the name, or a baseline path filed by title and
8
+ * name would hold every width in one file.
9
+ *
10
+ * One spelling for every party that names a width: the collector that plans or
11
+ * visits one, and the run that asks a store which baselines a story it did not
12
+ * visit may have left at widths it never read.
13
+ */
14
+ export function atWidth(subject, width) {
15
+ return {
16
+ ...subject,
17
+ subject: { ...subject.subject, id: `${subject.subject.id}@${width}` },
18
+ ...(subject.name === undefined ? {} : { name: `${subject.name} @${width}` }),
19
+ };
20
+ }
21
+ /**
22
+ * The id a width id was read from: `story:card@375` is `story:card` at 375.
23
+ * Absent for an id that carries no width, so that a party asking whether a
24
+ * realized id belongs to a planned one never takes a story whose id merely
25
+ * begins with the planned one for one of its widths.
26
+ */
27
+ export function widenedFrom(id) {
28
+ return /^(.+)@\d+$/.exec(id)?.[1];
29
+ }
30
+ /**
31
+ * Whether a visit to `planned` may realize `id`: the planned subject itself, or
32
+ * it at a width, `<planned>@<digits>`. Nothing else, so a story whose id merely
33
+ * begins with the planned one is never filed as one of its readings.
34
+ */
35
+ export function realizes(planned, id) {
36
+ return id === planned || widenedFrom(id) === planned;
37
+ }
38
+ //# sourceMappingURL=width.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"width.js","sourceRoot":"","sources":["../../src/format/width.ts"],"names":[],"mappings":"AAAA,4CAA4C;AAS5C;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,OAAO,CAAsB,OAAU,EAAE,KAAa;IACpE,OAAO;QACL,GAAG,OAAO;QACV,OAAO,EAAE,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,EAAE,IAAI,KAAK,EAAE,EAAE;QACrE,GAAG,CAAC,OAAO,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,IAAI,KAAK,KAAK,EAAE,EAAE,CAAC;KAC7E,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,EAAU;IACpC,OAAO,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;AACpC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,QAAQ,CAAC,OAAe,EAAE,EAAU;IAClD,OAAO,EAAE,KAAK,OAAO,IAAI,WAAW,CAAC,EAAE,CAAC,KAAK,OAAO,CAAC;AACvD,CAAC","sourcesContent":["// compass: variance-authority.normalization\n\n/** The part of a subject its width is spelled into. */\nexport interface Widenable {\n readonly subject: { readonly id: string };\n /** What a baseline is filed by under its title; each width carries its own. */\n readonly name?: string;\n}\n\n/**\n * The subject read at one width: its id and its name, each with the width.\n *\n * A width is a subject of its own — its own baseline, its own verdict — so the\n * width goes into everything a baseline is found by. The id carries it as\n * `<id>@<width>`, and so must the name, or a baseline path filed by title and\n * name would hold every width in one file.\n *\n * One spelling for every party that names a width: the collector that plans or\n * visits one, and the run that asks a store which baselines a story it did not\n * visit may have left at widths it never read.\n */\nexport function atWidth<S extends Widenable>(subject: S, width: number): S {\n return {\n ...subject,\n subject: { ...subject.subject, id: `${subject.subject.id}@${width}` },\n ...(subject.name === undefined ? {} : { name: `${subject.name} @${width}` }),\n };\n}\n\n/**\n * The id a width id was read from: `story:card@375` is `story:card` at 375.\n * Absent for an id that carries no width, so that a party asking whether a\n * realized id belongs to a planned one never takes a story whose id merely\n * begins with the planned one for one of its widths.\n */\nexport function widenedFrom(id: string): string | undefined {\n return /^(.+)@\\d+$/.exec(id)?.[1];\n}\n\n/**\n * Whether a visit to `planned` may realize `id`: the planned subject itself, or\n * it at a width, `<planned>@<digits>`. Nothing else, so a story whose id merely\n * begins with the planned one is never filed as one of its readings.\n */\nexport function realizes(planned: string, id: string): boolean {\n return id === planned || widenedFrom(id) === planned;\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -21,7 +21,8 @@
21
21
  *
22
22
  * Every name lives in exactly one of them; nothing is reachable from two paths.
23
23
  * This entrypoint holds only what belongs to no group: the artifact a capture is
24
- * written to.
24
+ * written to, and the name a refusal offers when one was mistyped.
25
25
  */
26
26
  export * from './artifact.js';
27
+ export { didYouMean, nearest } from './nearest.js';
27
28
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -21,7 +21,8 @@
21
21
  *
22
22
  * Every name lives in exactly one of them; nothing is reachable from two paths.
23
23
  * This entrypoint holds only what belongs to no group: the artifact a capture is
24
- * written to.
24
+ * written to, and the name a refusal offers when one was mistyped.
25
25
  */
26
26
  export * from './artifact.js';
27
+ export { didYouMean, nearest } from './nearest.js';
27
28
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,cAAc,eAAe,CAAC","sourcesContent":["/**\n * `@variance-authority/core` — the format, the rules, and the verdict model.\n *\n * This package is pure data in, pure data out: no DOM, no React, no I/O, no\n * async. That is enforced by its `tsconfig` (`lib: ES2022` only), so \"the core\n * cannot peek at a live document\" is a compile error rather than a convention\n * (ADR-0001). Collectors extract; core normalizes and adjudicates.\n *\n * Seven groups, each its own entrypoint, in the order an answer travels through\n * them:\n *\n * | entrypoint | what it holds |\n * |---|---|\n * | `core/format` | what a subject *is*: capture, snapshot, document, identity |\n * | `core/rules` | the versioned opinions: allowlist, cascade, canonicalization |\n * | `core/compare` | two snapshots become deltas — and no verdict |\n * | `core/attribute` | a position becomes a component becomes a file |\n * | `core/relate` | what reaches what: the graph a change is traversed over |\n * | `core/judge` | policy: verdicts, intent, the docket a reader is handed |\n * | `core/plan` | a composition as a value, and the identity derived from it |\n *\n * Every name lives in exactly one of them; nothing is reachable from two paths.\n * This entrypoint holds only what belongs to no group: the artifact a capture is\n * written to.\n */\n\nexport * from './artifact.js';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,cAAc,eAAe,CAAC;AAC9B,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC","sourcesContent":["/**\n * `@variance-authority/core` — the format, the rules, and the verdict model.\n *\n * This package is pure data in, pure data out: no DOM, no React, no I/O, no\n * async. That is enforced by its `tsconfig` (`lib: ES2022` only), so \"the core\n * cannot peek at a live document\" is a compile error rather than a convention\n * (ADR-0001). Collectors extract; core normalizes and adjudicates.\n *\n * Seven groups, each its own entrypoint, in the order an answer travels through\n * them:\n *\n * | entrypoint | what it holds |\n * |---|---|\n * | `core/format` | what a subject *is*: capture, snapshot, document, identity |\n * | `core/rules` | the versioned opinions: allowlist, cascade, canonicalization |\n * | `core/compare` | two snapshots become deltas — and no verdict |\n * | `core/attribute` | a position becomes a component becomes a file |\n * | `core/relate` | what reaches what: the graph a change is traversed over |\n * | `core/judge` | policy: verdicts, intent, the docket a reader is handed |\n * | `core/plan` | a composition as a value, and the identity derived from it |\n *\n * Every name lives in exactly one of them; nothing is reachable from two paths.\n * This entrypoint holds only what belongs to no group: the artifact a capture is\n * written to, and the name a refusal offers when one was mistyped.\n */\n\nexport * from './artifact.js';\nexport { didYouMean, nearest } from './nearest.js';\n"]}
@@ -117,6 +117,7 @@ function isStructural(delta) {
117
117
  case 'node-added':
118
118
  case 'node-removed':
119
119
  case 'node-moved':
120
+ case 'tag-changed':
120
121
  case 'role-changed':
121
122
  case 'name-changed':
122
123
  case 'text-changed':
@@ -1 +1 @@
1
- {"version":3,"file":"docket.js","sourceRoot":"","sources":["../../src/judge/docket.ts"],"names":[],"mappings":"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","sourcesContent":["import { loudestBand, type Band } from '../compare/band.js';\nimport type { ChangedComponent, Delta, Root, RootKind, SemanticDiff } from '../compare/diff/index.js';\nimport { aggregateImpact, type AggregateImpact, type PropertyImpact } from '../compare/impact.js';\n\n/**\n * The docket: one entry per root cause, across every subject in a change set.\n *\n * A `SemanticDiff` answers \"what changed in this subject\". That is the wrong unit\n * for review, and reviewing at that unit is what makes visual regression\n * unbearable — one design-token edit arrives as three hundred separate subjects\n * to click through, and the three-hundred-and-first gets approved without being\n * read.\n *\n * The docket inverts it. One token edit is **one entry**, carrying the count of\n * what it reached. Spec §6.2 requires that approving it be a single action, which\n * is only possible if the thing being approved is the cause rather than each of\n * its effects.\n *\n * Aggregation is by root **id**, which is why root ids are constructed to be\n * stable across subjects: `token:--color-primary` is the same root wherever it\n * lands, so the grouping needs no similarity heuristic.\n */\n\nexport interface DocketEntry {\n readonly rootId: string;\n readonly kind: RootKind;\n readonly label: string;\n readonly band: Band;\n readonly impact: AggregateImpact;\n\n /** Subjects this root reached, in first-seen order. */\n readonly subjects: readonly string[];\n readonly subjectCount: number;\n /** Individual deltas across all subjects — the collateral count. */\n readonly deltaCount: number;\n\n readonly components: readonly ChangedComponent[];\n\n /**\n * A few subjects to actually look at.\n *\n * Mass re-baselining is only survivable if approval is *informed*, and nobody\n * informs themselves by reviewing three hundred identical diffs. Spec §7.3 asks\n * for a sampled spot-check list, and this is it.\n */\n readonly sample: readonly string[];\n\n /**\n * Distinct places this root's changes were observed, most common first.\n *\n * Carried on the entry rather than derived from deltas at report time, because\n * an entry spans subjects and the deltas behind it are not kept. Three is\n * enough to orient without turning a collapsed root back into a list — the\n * thing collapsing it was for.\n */\n readonly places: readonly string[];\n\n /**\n * `true` when nothing under this root added, removed, moved, or renamed a node.\n *\n * The other half of the sentence the product promises. \"One token change, 300\n * collateral\" is only reassuring alongside \"structure intact\" — that is what\n * makes it a one-action approval rather than three hundred things to check.\n */\n readonly structureIntact: boolean;\n}\n\nexport interface Docket {\n readonly entries: readonly DocketEntry[];\n readonly subjectsCompared: number;\n readonly subjectsUnchanged: number;\n readonly subjectsChanged: number;\n\n /**\n * `true` when every change in the set has one explanation.\n *\n * The condition under which the whole review collapses to a single action.\n */\n readonly singleRoot: boolean;\n}\n\nexport interface DocketOptions {\n /** Subjects listed per entry for spot-checking. Policy, not a constant. */\n readonly sampleSize?: number;\n}\n\nexport function buildDocket(\n diffs: readonly SemanticDiff[],\n options: DocketOptions = {},\n): Docket {\n const sampleSize = options.sampleSize ?? 3;\n const groups = new Map<string, Accumulator>();\n\n for (const diff of diffs) {\n if (diff.identical) continue;\n\n for (const root of diff.roots) {\n const entry = groups.get(root.id) ?? newAccumulator(root);\n groups.set(root.id, entry);\n\n entry.subjects.add(diff.subjectId);\n entry.deltas.push(...root.deltas);\n for (const component of componentsUnder(root, diff.components)) {\n mergeComponent(entry.components, component);\n }\n }\n }\n\n const entries = [...groups.values()]\n .map((entry) => finalize(entry, sampleSize))\n // Widest blast radius first: a root touching forty subjects is the review,\n // and one touching a single subject is a detail underneath it.\n .sort((a, b) => b.subjectCount - a.subjectCount || b.deltaCount - a.deltaCount);\n\n const changed = diffs.filter((diff) => !diff.identical).length;\n\n return {\n entries,\n subjectsCompared: diffs.length,\n subjectsUnchanged: diffs.length - changed,\n subjectsChanged: changed,\n singleRoot: entries.length === 1,\n };\n}\n\n/**\n * The docket as one sentence per entry.\n *\n * Deliberately terse. The audience is a reviewer deciding whether to look\n * further and an agent deciding whether to act, and both are worse served by a\n * paragraph than by a line they can scan.\n */\nexport function summarize(docket: Docket): string {\n if (docket.entries.length === 0) {\n return `${docket.subjectsCompared} subjects compared, nothing changed.`;\n }\n\n const header =\n `${docket.subjectsChanged} of ${docket.subjectsCompared} subjects changed, ` +\n `explained by ${docket.entries.length} root${docket.entries.length === 1 ? '' : 's'}.`;\n\n const lines = docket.entries.map((entry) => {\n const reach =\n entry.subjectCount === 1\n ? `${entry.deltaCount} change${entry.deltaCount === 1 ? '' : 's'}`\n : `${entry.subjectCount} subjects, ${entry.deltaCount} collateral`;\n\n const structure = entry.structureIntact ? 'structure intact' : 'structure changed';\n const components =\n entry.components.length > 0\n ? ` — ${entry.components\n .slice(0, 4)\n .map((component) => component.name)\n .join(', ')}`\n : '';\n\n return (\n ` ${entry.kind}: ${entry.label} — ${reach}, ${entry.band}/${entry.impact}, ` +\n `${structure}${components}`\n );\n });\n\n return [header, ...lines].join('\\n');\n}\n\ninterface Accumulator {\n readonly root: Root;\n readonly subjects: Set<string>;\n readonly deltas: Delta[];\n readonly components: Map<string, MutableComponent>;\n}\n\ninterface MutableComponent {\n name: string;\n role: 'root' | 'collateral';\n deltaCount: number;\n bands: Set<Band>;\n impacts: (PropertyImpact | 'structural')[];\n renderedIn: Set<string>;\n}\n\nfunction newAccumulator(root: Root): Accumulator {\n return { root, subjects: new Set(), deltas: [], components: new Map() };\n}\n\nfunction finalize(entry: Accumulator, sampleSize: number): DocketEntry {\n const subjects = [...entry.subjects];\n\n return {\n rootId: entry.root.id,\n kind: entry.root.kind,\n label: entry.root.label,\n // The root's own band is a floor, not the answer. A token edit whose\n // collateral includes a dropped accessible name is reported at `a11y`: the\n // entry is one review action, so it must be labelled by the loudest thing a\n // reviewer would be signing off on, not by the thing that caused it.\n band: loudestBand([entry.root.band, ...entry.deltas.map((delta) => delta.band)]) ?? entry.root.band,\n impact: aggregateImpact(entry.deltas.map((delta) => delta.impact ?? 'structural')),\n subjects,\n subjectCount: subjects.length,\n deltaCount: entry.deltas.length,\n components: [...entry.components.values()]\n .map((component) => ({\n name: component.name,\n role: component.role,\n deltaCount: component.deltaCount,\n bands: [...component.bands],\n impact: aggregateImpact(component.impacts),\n renderedIn: [...component.renderedIn],\n }))\n .sort(\n (a, b) =>\n b.deltaCount - a.deltaCount || (a.name < b.name ? -1 : a.name > b.name ? 1 : 0),\n ),\n sample: subjects.slice(0, sampleSize),\n places: rankedPlaces(entry.deltas),\n structureIntact: !entry.deltas.some(isStructural),\n };\n}\n\n/** Distinct `where` phrases, most frequent first, capped. */\nfunction rankedPlaces(deltas: readonly Delta[]): readonly string[] {\n const counts = new Map<string, number>();\n\n for (const delta of deltas) {\n if (delta.where === undefined || delta.where === '') continue;\n counts.set(delta.where, (counts.get(delta.where) ?? 0) + 1);\n }\n\n return [...counts.entries()]\n .sort((a, b) => b[1] - a[1])\n .slice(0, 3)\n .map(([where]) => where);\n}\n\n/**\n * Whether a delta changed the shape of the tree rather than a value.\n *\n * A `rect-changed` that was *derived* from a style change does not count: the\n * box moved because a value moved, and calling that a structural change would\n * make every spacing token look like it rearranged the page.\n */\nfunction isStructural(delta: Delta): boolean {\n switch (delta.kind) {\n case 'node-added':\n case 'node-removed':\n case 'node-moved':\n case 'role-changed':\n case 'name-changed':\n case 'text-changed':\n return true;\n case 'rect-changed':\n return delta.derivedFrom === undefined;\n default:\n return false;\n }\n}\n\n/** Components that appear in this root's deltas, from the subject's own view. */\nfunction componentsUnder(\n root: Root,\n components: readonly ChangedComponent[],\n): readonly ChangedComponent[] {\n const names = new Set<string>();\n for (const delta of root.deltas) {\n for (const owner of delta.owners ?? []) names.add(owner.name);\n }\n return components.filter((component) => names.has(component.name));\n}\n\nfunction mergeComponent(\n into: Map<string, MutableComponent>,\n component: ChangedComponent,\n): void {\n const existing = into.get(component.name);\n\n if (!existing) {\n into.set(component.name, {\n name: component.name,\n role: component.role,\n deltaCount: component.deltaCount,\n bands: new Set(component.bands),\n impacts: [],\n renderedIn: new Set(component.renderedIn),\n });\n return;\n }\n\n existing.deltaCount += component.deltaCount;\n for (const band of component.bands) existing.bands.add(band);\n for (const parent of component.renderedIn) existing.renderedIn.add(parent);\n // Root in any subject means root: a component that changed internally\n // somewhere is a cause, even if elsewhere it was only along for the ride.\n if (component.role === 'root') existing.role = 'root';\n}\n"]}
1
+ {"version":3,"file":"docket.js","sourceRoot":"","sources":["../../src/judge/docket.ts"],"names":[],"mappings":"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","sourcesContent":["import { loudestBand, type Band } from '../compare/band.js';\nimport type { ChangedComponent, Delta, Root, RootKind, SemanticDiff } from '../compare/diff/index.js';\nimport { aggregateImpact, type AggregateImpact, type PropertyImpact } from '../compare/impact.js';\n\n/**\n * The docket: one entry per root cause, across every subject in a change set.\n *\n * A `SemanticDiff` answers \"what changed in this subject\". That is the wrong unit\n * for review, and reviewing at that unit is what makes visual regression\n * unbearable — one design-token edit arrives as three hundred separate subjects\n * to click through, and the three-hundred-and-first gets approved without being\n * read.\n *\n * The docket inverts it. One token edit is **one entry**, carrying the count of\n * what it reached. Spec §6.2 requires that approving it be a single action, which\n * is only possible if the thing being approved is the cause rather than each of\n * its effects.\n *\n * Aggregation is by root **id**, which is why root ids are constructed to be\n * stable across subjects: `token:--color-primary` is the same root wherever it\n * lands, so the grouping needs no similarity heuristic.\n */\n\nexport interface DocketEntry {\n readonly rootId: string;\n readonly kind: RootKind;\n readonly label: string;\n readonly band: Band;\n readonly impact: AggregateImpact;\n\n /** Subjects this root reached, in first-seen order. */\n readonly subjects: readonly string[];\n readonly subjectCount: number;\n /** Individual deltas across all subjects — the collateral count. */\n readonly deltaCount: number;\n\n readonly components: readonly ChangedComponent[];\n\n /**\n * A few subjects to actually look at.\n *\n * Mass re-baselining is only survivable if approval is *informed*, and nobody\n * informs themselves by reviewing three hundred identical diffs. Spec §7.3 asks\n * for a sampled spot-check list, and this is it.\n */\n readonly sample: readonly string[];\n\n /**\n * Distinct places this root's changes were observed, most common first.\n *\n * Carried on the entry rather than derived from deltas at report time, because\n * an entry spans subjects and the deltas behind it are not kept. Three is\n * enough to orient without turning a collapsed root back into a list — the\n * thing collapsing it was for.\n */\n readonly places: readonly string[];\n\n /**\n * `true` when nothing under this root added, removed, moved, or renamed a node.\n *\n * The other half of the sentence the product promises. \"One token change, 300\n * collateral\" is only reassuring alongside \"structure intact\" — that is what\n * makes it a one-action approval rather than three hundred things to check.\n */\n readonly structureIntact: boolean;\n}\n\nexport interface Docket {\n readonly entries: readonly DocketEntry[];\n readonly subjectsCompared: number;\n readonly subjectsUnchanged: number;\n readonly subjectsChanged: number;\n\n /**\n * `true` when every change in the set has one explanation.\n *\n * The condition under which the whole review collapses to a single action.\n */\n readonly singleRoot: boolean;\n}\n\nexport interface DocketOptions {\n /** Subjects listed per entry for spot-checking. Policy, not a constant. */\n readonly sampleSize?: number;\n}\n\nexport function buildDocket(\n diffs: readonly SemanticDiff[],\n options: DocketOptions = {},\n): Docket {\n const sampleSize = options.sampleSize ?? 3;\n const groups = new Map<string, Accumulator>();\n\n for (const diff of diffs) {\n if (diff.identical) continue;\n\n for (const root of diff.roots) {\n const entry = groups.get(root.id) ?? newAccumulator(root);\n groups.set(root.id, entry);\n\n entry.subjects.add(diff.subjectId);\n entry.deltas.push(...root.deltas);\n for (const component of componentsUnder(root, diff.components)) {\n mergeComponent(entry.components, component);\n }\n }\n }\n\n const entries = [...groups.values()]\n .map((entry) => finalize(entry, sampleSize))\n // Widest blast radius first: a root touching forty subjects is the review,\n // and one touching a single subject is a detail underneath it.\n .sort((a, b) => b.subjectCount - a.subjectCount || b.deltaCount - a.deltaCount);\n\n const changed = diffs.filter((diff) => !diff.identical).length;\n\n return {\n entries,\n subjectsCompared: diffs.length,\n subjectsUnchanged: diffs.length - changed,\n subjectsChanged: changed,\n singleRoot: entries.length === 1,\n };\n}\n\n/**\n * The docket as one sentence per entry.\n *\n * Deliberately terse. The audience is a reviewer deciding whether to look\n * further and an agent deciding whether to act, and both are worse served by a\n * paragraph than by a line they can scan.\n */\nexport function summarize(docket: Docket): string {\n if (docket.entries.length === 0) {\n return `${docket.subjectsCompared} subjects compared, nothing changed.`;\n }\n\n const header =\n `${docket.subjectsChanged} of ${docket.subjectsCompared} subjects changed, ` +\n `explained by ${docket.entries.length} root${docket.entries.length === 1 ? '' : 's'}.`;\n\n const lines = docket.entries.map((entry) => {\n const reach =\n entry.subjectCount === 1\n ? `${entry.deltaCount} change${entry.deltaCount === 1 ? '' : 's'}`\n : `${entry.subjectCount} subjects, ${entry.deltaCount} collateral`;\n\n const structure = entry.structureIntact ? 'structure intact' : 'structure changed';\n const components =\n entry.components.length > 0\n ? ` — ${entry.components\n .slice(0, 4)\n .map((component) => component.name)\n .join(', ')}`\n : '';\n\n return (\n ` ${entry.kind}: ${entry.label} — ${reach}, ${entry.band}/${entry.impact}, ` +\n `${structure}${components}`\n );\n });\n\n return [header, ...lines].join('\\n');\n}\n\ninterface Accumulator {\n readonly root: Root;\n readonly subjects: Set<string>;\n readonly deltas: Delta[];\n readonly components: Map<string, MutableComponent>;\n}\n\ninterface MutableComponent {\n name: string;\n role: 'root' | 'collateral';\n deltaCount: number;\n bands: Set<Band>;\n impacts: (PropertyImpact | 'structural')[];\n renderedIn: Set<string>;\n}\n\nfunction newAccumulator(root: Root): Accumulator {\n return { root, subjects: new Set(), deltas: [], components: new Map() };\n}\n\nfunction finalize(entry: Accumulator, sampleSize: number): DocketEntry {\n const subjects = [...entry.subjects];\n\n return {\n rootId: entry.root.id,\n kind: entry.root.kind,\n label: entry.root.label,\n // The root's own band is a floor, not the answer. A token edit whose\n // collateral includes a dropped accessible name is reported at `a11y`: the\n // entry is one review action, so it must be labelled by the loudest thing a\n // reviewer would be signing off on, not by the thing that caused it.\n band: loudestBand([entry.root.band, ...entry.deltas.map((delta) => delta.band)]) ?? entry.root.band,\n impact: aggregateImpact(entry.deltas.map((delta) => delta.impact ?? 'structural')),\n subjects,\n subjectCount: subjects.length,\n deltaCount: entry.deltas.length,\n components: [...entry.components.values()]\n .map((component) => ({\n name: component.name,\n role: component.role,\n deltaCount: component.deltaCount,\n bands: [...component.bands],\n impact: aggregateImpact(component.impacts),\n renderedIn: [...component.renderedIn],\n }))\n .sort(\n (a, b) =>\n b.deltaCount - a.deltaCount || (a.name < b.name ? -1 : a.name > b.name ? 1 : 0),\n ),\n sample: subjects.slice(0, sampleSize),\n places: rankedPlaces(entry.deltas),\n structureIntact: !entry.deltas.some(isStructural),\n };\n}\n\n/** Distinct `where` phrases, most frequent first, capped. */\nfunction rankedPlaces(deltas: readonly Delta[]): readonly string[] {\n const counts = new Map<string, number>();\n\n for (const delta of deltas) {\n if (delta.where === undefined || delta.where === '') continue;\n counts.set(delta.where, (counts.get(delta.where) ?? 0) + 1);\n }\n\n return [...counts.entries()]\n .sort((a, b) => b[1] - a[1])\n .slice(0, 3)\n .map(([where]) => where);\n}\n\n/**\n * Whether a delta changed the shape of the tree rather than a value.\n *\n * A `rect-changed` that was *derived* from a style change does not count: the\n * box moved because a value moved, and calling that a structural change would\n * make every spacing token look like it rearranged the page.\n */\nfunction isStructural(delta: Delta): boolean {\n switch (delta.kind) {\n case 'node-added':\n case 'node-removed':\n case 'node-moved':\n case 'tag-changed':\n case 'role-changed':\n case 'name-changed':\n case 'text-changed':\n return true;\n case 'rect-changed':\n return delta.derivedFrom === undefined;\n default:\n return false;\n }\n}\n\n/** Components that appear in this root's deltas, from the subject's own view. */\nfunction componentsUnder(\n root: Root,\n components: readonly ChangedComponent[],\n): readonly ChangedComponent[] {\n const names = new Set<string>();\n for (const delta of root.deltas) {\n for (const owner of delta.owners ?? []) names.add(owner.name);\n }\n return components.filter((component) => names.has(component.name));\n}\n\nfunction mergeComponent(\n into: Map<string, MutableComponent>,\n component: ChangedComponent,\n): void {\n const existing = into.get(component.name);\n\n if (!existing) {\n into.set(component.name, {\n name: component.name,\n role: component.role,\n deltaCount: component.deltaCount,\n bands: new Set(component.bands),\n impacts: [],\n renderedIn: new Set(component.renderedIn),\n });\n return;\n }\n\n existing.deltaCount += component.deltaCount;\n for (const band of component.bands) existing.bands.add(band);\n for (const parent of component.renderedIn) existing.renderedIn.add(parent);\n // Root in any subject means root: a component that changed internally\n // somewhere is a cause, even if elsewhere it was only along for the ride.\n if (component.role === 'root') existing.role = 'root';\n}\n"]}
@@ -33,6 +33,7 @@ export { applyIgnores, summarizeIgnores, validateIgnoreRule } from './ignore.js'
33
33
  export { isUnder, matchesGlob, appliesToSubject, isExpired } from './scope.js';
34
34
  export type { Scoped } from './scope.js';
35
35
  export { fingerprintOfRoot, fingerprintOfMask, shapeOfDelta } from './fingerprint.js';
36
+ export { restamps } from './restamp.js';
36
37
  export type { MaskFingerprintOptions } from './fingerprint.js';
37
38
  export type { IgnoreRule, IgnoreOptions, IgnoreOutcome, IgnoreRegister, AbsorbedByRule, } from './ignore.js';
38
39
  export { applySensitivity, absorbsEntirely, asIgnore, bandsOf, relaxes, summarizeSensitivity, } from './sensitivity.js';
@@ -28,6 +28,7 @@ export { findingMark, inspect, summarizeFindings } from './inspect.js';
28
28
  export { applyIgnores, summarizeIgnores, validateIgnoreRule } from './ignore.js';
29
29
  export { isUnder, matchesGlob, appliesToSubject, isExpired } from './scope.js';
30
30
  export { fingerprintOfRoot, fingerprintOfMask, shapeOfDelta } from './fingerprint.js';
31
+ export { restamps } from './restamp.js';
31
32
  export { applySensitivity, absorbsEntirely, asIgnore, bandsOf, relaxes, summarizeSensitivity, } from './sensitivity.js';
32
33
  export { startTrail, record, progress, summarizeTrail } from './trail.js';
33
34
  export { compareLocales } from './locale.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/judge/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAGH,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAE9D,OAAO,EAAE,UAAU,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAGhE,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAGrD,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAGvE,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjF,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE/E,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAUtF,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,QAAQ,EACR,OAAO,EACP,OAAO,EACP,oBAAoB,GACrB,MAAM,kBAAkB,CAAC;AAS1B,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAG1E,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC","sourcesContent":["/**\n * `@variance-authority/core/judge` — from what moved to whether anyone should mind.\n *\n * Verdicts and their ordering, intent claims and their adjudication, and the\n * docket a reader is handed. This is where policy lives, and it is deliberately\n * the last group and the smallest: everything above it produces facts, and a\n * team that disagrees with the policy replaces this and keeps the rest.\n *\n * `UNOBSERVED` is the group's whole argument in one export. A band a profile\n * cannot see is not a band that passed, and the type system will not let the two\n * be spelled the same way.\n *\n * `inspect` is the one member that needs no baseline. Everything else here\n * decides between two snapshots; it decides about one, which is the only way a\n * defect present on the *first* run is ever reported rather than approved into\n * the baseline.\n *\n * `ignore` is the group's second refusal to conflate two things. A difference the\n * operator declined to look at is not a difference that did not happen, so an\n * absorbed delta is counted, attributed to the rule that absorbed it, and the\n * rule is reported when it absorbs nothing — the same treatment `UNOBSERVED`\n * gives a band nobody could see.\n */\n\nexport type { Verdict, BandOutcome } from './verdict.js';\nexport { UNOBSERVED, severityOf, blocks } from './verdict.js';\n\nexport { adjudicate, summarizeAdjudication } from './intent.js';\nexport type { Intent, IntentClaim, Policy, Adjudication, Adjudicated } from './intent.js';\n\nexport { buildDocket, summarize } from './docket.js';\nexport type { Docket, DocketEntry, DocketOptions } from './docket.js';\n\nexport { findingMark, inspect, summarizeFindings } from './inspect.js';\nexport type { Finding, FindingRule, InspectionReportOptions } from './inspect.js';\n\nexport { applyIgnores, summarizeIgnores, validateIgnoreRule } from './ignore.js';\nexport { isUnder, matchesGlob, appliesToSubject, isExpired } from './scope.js';\nexport type { Scoped } from './scope.js';\nexport { fingerprintOfRoot, fingerprintOfMask, shapeOfDelta } from './fingerprint.js';\nexport type { MaskFingerprintOptions } from './fingerprint.js';\nexport type {\n IgnoreRule,\n IgnoreOptions,\n IgnoreOutcome,\n IgnoreRegister,\n AbsorbedByRule,\n} from './ignore.js';\n\nexport {\n applySensitivity,\n absorbsEntirely,\n asIgnore,\n bandsOf,\n relaxes,\n summarizeSensitivity,\n} from './sensitivity.js';\nexport type {\n Level,\n SensitivityRule,\n SensitivityOutcome,\n SensitivityRegister,\n Relaxation,\n} from './sensitivity.js';\n\nexport { startTrail, record, progress, summarizeTrail } from './trail.js';\nexport type { Trail, TrailStep, Progress } from './trail.js';\n\nexport { compareLocales } from './locale.js';\nexport type { LocaleComparison, LocaleOptions, Uncompared } from './locale.js';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/judge/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAGH,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAE9D,OAAO,EAAE,UAAU,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAGhE,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAGrD,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAGvE,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjF,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE/E,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACtF,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAUxC,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,QAAQ,EACR,OAAO,EACP,OAAO,EACP,oBAAoB,GACrB,MAAM,kBAAkB,CAAC;AAS1B,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAG1E,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC","sourcesContent":["/**\n * `@variance-authority/core/judge` — from what moved to whether anyone should mind.\n *\n * Verdicts and their ordering, intent claims and their adjudication, and the\n * docket a reader is handed. This is where policy lives, and it is deliberately\n * the last group and the smallest: everything above it produces facts, and a\n * team that disagrees with the policy replaces this and keeps the rest.\n *\n * `UNOBSERVED` is the group's whole argument in one export. A band a profile\n * cannot see is not a band that passed, and the type system will not let the two\n * be spelled the same way.\n *\n * `inspect` is the one member that needs no baseline. Everything else here\n * decides between two snapshots; it decides about one, which is the only way a\n * defect present on the *first* run is ever reported rather than approved into\n * the baseline.\n *\n * `ignore` is the group's second refusal to conflate two things. A difference the\n * operator declined to look at is not a difference that did not happen, so an\n * absorbed delta is counted, attributed to the rule that absorbed it, and the\n * rule is reported when it absorbs nothing — the same treatment `UNOBSERVED`\n * gives a band nobody could see.\n */\n\nexport type { Verdict, BandOutcome } from './verdict.js';\nexport { UNOBSERVED, severityOf, blocks } from './verdict.js';\n\nexport { adjudicate, summarizeAdjudication } from './intent.js';\nexport type { Intent, IntentClaim, Policy, Adjudication, Adjudicated } from './intent.js';\n\nexport { buildDocket, summarize } from './docket.js';\nexport type { Docket, DocketEntry, DocketOptions } from './docket.js';\n\nexport { findingMark, inspect, summarizeFindings } from './inspect.js';\nexport type { Finding, FindingRule, InspectionReportOptions } from './inspect.js';\n\nexport { applyIgnores, summarizeIgnores, validateIgnoreRule } from './ignore.js';\nexport { isUnder, matchesGlob, appliesToSubject, isExpired } from './scope.js';\nexport type { Scoped } from './scope.js';\nexport { fingerprintOfRoot, fingerprintOfMask, shapeOfDelta } from './fingerprint.js';\nexport { restamps } from './restamp.js';\nexport type { MaskFingerprintOptions } from './fingerprint.js';\nexport type {\n IgnoreRule,\n IgnoreOptions,\n IgnoreOutcome,\n IgnoreRegister,\n AbsorbedByRule,\n} from './ignore.js';\n\nexport {\n applySensitivity,\n absorbsEntirely,\n asIgnore,\n bandsOf,\n relaxes,\n summarizeSensitivity,\n} from './sensitivity.js';\nexport type {\n Level,\n SensitivityRule,\n SensitivityOutcome,\n SensitivityRegister,\n Relaxation,\n} from './sensitivity.js';\n\nexport { startTrail, record, progress, summarizeTrail } from './trail.js';\nexport type { Trail, TrailStep, Progress } from './trail.js';\n\nexport { compareLocales } from './locale.js';\nexport type { LocaleComparison, LocaleOptions, Uncompared } from './locale.js';\n"]}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Whether an accepting run rewrites a baseline the run found unchanged.
3
+ *
4
+ * Nothing changed, so there is nothing to accept, but the baseline is left
5
+ * worse than a fresh one in two ways: painted under another identity, it names
6
+ * the same move on every later run (ADR-0090); keeping no snapshot of its
7
+ * nodes, it joins the first change after it against one render (ADR-0027);
8
+ * read from an identity directory, it stays at a path no reviewer
9
+ * can read, and rewriting it is the move.
10
+ *
11
+ * One rule, asked of the signals an observation and its record share, so that
12
+ * Playwright's `--update-snapshots`, vitest's `--update` and `variance accept`
13
+ * cannot come to disagree about which unchanged baselines they rewrite.
14
+ */
15
+ export declare function restamps(signals: {
16
+ readonly identity?: unknown;
17
+ readonly snapshot?: 'unrecorded';
18
+ readonly relocated?: unknown;
19
+ } | undefined): boolean;
20
+ //# sourceMappingURL=restamp.d.ts.map
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Whether an accepting run rewrites a baseline the run found unchanged.
3
+ *
4
+ * Nothing changed, so there is nothing to accept, but the baseline is left
5
+ * worse than a fresh one in two ways: painted under another identity, it names
6
+ * the same move on every later run (ADR-0090); keeping no snapshot of its
7
+ * nodes, it joins the first change after it against one render (ADR-0027);
8
+ * read from an identity directory, it stays at a path no reviewer
9
+ * can read, and rewriting it is the move.
10
+ *
11
+ * One rule, asked of the signals an observation and its record share, so that
12
+ * Playwright's `--update-snapshots`, vitest's `--update` and `variance accept`
13
+ * cannot come to disagree about which unchanged baselines they rewrite.
14
+ */
15
+ export function restamps(signals) {
16
+ return (signals?.identity !== undefined ||
17
+ signals?.snapshot === 'unrecorded' ||
18
+ signals?.relocated !== undefined);
19
+ }
20
+ //# sourceMappingURL=restamp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"restamp.js","sourceRoot":"","sources":["../../src/judge/restamp.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,QAAQ,CACtB,OAMa;IAEb,OAAO,CACL,OAAO,EAAE,QAAQ,KAAK,SAAS;QAC/B,OAAO,EAAE,QAAQ,KAAK,YAAY;QAClC,OAAO,EAAE,SAAS,KAAK,SAAS,CACjC,CAAC;AACJ,CAAC","sourcesContent":["/**\n * Whether an accepting run rewrites a baseline the run found unchanged.\n *\n * Nothing changed, so there is nothing to accept, but the baseline is left\n * worse than a fresh one in two ways: painted under another identity, it names\n * the same move on every later run (ADR-0090); keeping no snapshot of its\n * nodes, it joins the first change after it against one render (ADR-0027);\n * read from an identity directory, it stays at a path no reviewer\n * can read, and rewriting it is the move.\n *\n * One rule, asked of the signals an observation and its record share, so that\n * Playwright's `--update-snapshots`, vitest's `--update` and `variance accept`\n * cannot come to disagree about which unchanged baselines they rewrite.\n */\nexport function restamps(\n signals:\n | {\n readonly identity?: unknown;\n readonly snapshot?: 'unrecorded';\n readonly relocated?: unknown;\n }\n | undefined,\n): boolean {\n return (\n signals?.identity !== undefined ||\n signals?.snapshot === 'unrecorded' ||\n signals?.relocated !== undefined\n );\n}\n"]}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * The name somebody meant, when exactly one name is close enough.
3
+ *
4
+ * A refusal names what was typed and then the command's synopsis, which lists
5
+ * what it accepts. The reader is usually an agent, and an agent that mistyped
6
+ * `--quer` should correct it from the same message instead of spending a second
7
+ * turn on it. So one sentence goes in front of the synopsis. Naming a candidate
8
+ * is cheap and reversible: nothing is run on the guess, and a wrong guess costs
9
+ * one line.
10
+ *
11
+ * ## Why one, and why not always
12
+ *
13
+ * Two names equally close is not a suggestion, it is a second decision handed
14
+ * back — so a tie suggests nothing and the set alone answers. And the distance
15
+ * is bounded by the length of what was typed: without that, `--x` is one edit
16
+ * from nothing and three from half the table, and a suggestion that fires on
17
+ * anything is one nobody can read as a signal.
18
+ *
19
+ * Damerau-Levenshtein, iterative, over three rows. Transposed neighbours count
20
+ * as one edit rather than two, which is not a refinement: `aks` for `ask` and
21
+ * `--fromat` for `--format` are the typo, and under plain Levenshtein both sit
22
+ * at distance two and are missed by the bound below. No dependency, for the
23
+ * reason `parse.ts` gives — a tool whose claim is that its output can be trusted
24
+ * does not acquire a transitive graph to spell-check a flag.
25
+ */
26
+ /**
27
+ * The single closest candidate, or nothing.
28
+ *
29
+ * Case-insensitive, because a name typed in the wrong case is the same mistake
30
+ * and the answer is the same name. Comparison only — what comes back is the
31
+ * candidate as it is actually spelled, since that is what has to be typed next.
32
+ */
33
+ export declare function nearest(typed: string, candidates: readonly string[]): string | undefined;
34
+ /**
35
+ * The line a refusal adds beside its list, or nothing.
36
+ *
37
+ * Its own function so every refusal says it the same way. An agent reading three
38
+ * different phrasings for one idea has three things to recognise.
39
+ *
40
+ * A line of its own, and it carries the newline itself so a refusal with nothing
41
+ * to suggest does not print a blank one. Beside the accepted set rather than
42
+ * inside that sentence: the set is the part a reader parses — a name split out
43
+ * of a list should not have half a second sentence attached to it.
44
+ */
45
+ export declare function didYouMean(typed: string, candidates: readonly string[]): string;
46
+ //# sourceMappingURL=nearest.d.ts.map