@variance-authority/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (260) hide show
  1. package/CHANGELOG.md +79 -0
  2. package/LICENSE +21 -0
  3. package/README.md +169 -0
  4. package/dist/artifact.d.ts +47 -0
  5. package/dist/artifact.js +2 -0
  6. package/dist/artifact.js.map +1 -0
  7. package/dist/attribute/because.d.ts +43 -0
  8. package/dist/attribute/because.js +64 -0
  9. package/dist/attribute/because.js.map +1 -0
  10. package/dist/attribute/boundary.d.ts +191 -0
  11. package/dist/attribute/boundary.js +328 -0
  12. package/dist/attribute/boundary.js.map +1 -0
  13. package/dist/attribute/call-site.d.ts +96 -0
  14. package/dist/attribute/call-site.js +183 -0
  15. package/dist/attribute/call-site.js.map +1 -0
  16. package/dist/attribute/component-hash.d.ts +188 -0
  17. package/dist/attribute/component-hash.js +312 -0
  18. package/dist/attribute/component-hash.js.map +1 -0
  19. package/dist/attribute/composition-fixture.d.ts +18 -0
  20. package/dist/attribute/composition-fixture.js +53 -0
  21. package/dist/attribute/composition-fixture.js.map +1 -0
  22. package/dist/attribute/composition.d.ts +182 -0
  23. package/dist/attribute/composition.js +169 -0
  24. package/dist/attribute/composition.js.map +1 -0
  25. package/dist/attribute/control.d.ts +43 -0
  26. package/dist/attribute/control.js +54 -0
  27. package/dist/attribute/control.js.map +1 -0
  28. package/dist/attribute/coverage.d.ts +99 -0
  29. package/dist/attribute/coverage.js +86 -0
  30. package/dist/attribute/coverage.js.map +1 -0
  31. package/dist/attribute/divergence.d.ts +102 -0
  32. package/dist/attribute/divergence.js +152 -0
  33. package/dist/attribute/divergence.js.map +1 -0
  34. package/dist/attribute/index.d.ts +43 -0
  35. package/dist/attribute/index.js +29 -0
  36. package/dist/attribute/index.js.map +1 -0
  37. package/dist/attribute/instability.d.ts +89 -0
  38. package/dist/attribute/instability.js +183 -0
  39. package/dist/attribute/instability.js.map +1 -0
  40. package/dist/attribute/instances.d.ts +145 -0
  41. package/dist/attribute/instances.js +140 -0
  42. package/dist/attribute/instances.js.map +1 -0
  43. package/dist/attribute/locate.d.ts +22 -0
  44. package/dist/attribute/locate.js +100 -0
  45. package/dist/attribute/locate.js.map +1 -0
  46. package/dist/attribute/mask.d.ts +156 -0
  47. package/dist/attribute/mask.js +201 -0
  48. package/dist/attribute/mask.js.map +1 -0
  49. package/dist/attribute/movement.d.ts +211 -0
  50. package/dist/attribute/movement.js +181 -0
  51. package/dist/attribute/movement.js.map +1 -0
  52. package/dist/attribute/region.d.ts +188 -0
  53. package/dist/attribute/region.js +144 -0
  54. package/dist/attribute/region.js.map +1 -0
  55. package/dist/attribute/source-map.d.ts +98 -0
  56. package/dist/attribute/source-map.js +264 -0
  57. package/dist/attribute/source-map.js.map +1 -0
  58. package/dist/attribute/source.d.ts +77 -0
  59. package/dist/attribute/source.js +95 -0
  60. package/dist/attribute/source.js.map +1 -0
  61. package/dist/attribute/stack.d.ts +80 -0
  62. package/dist/attribute/stack.js +108 -0
  63. package/dist/attribute/stack.js.map +1 -0
  64. package/dist/compare/band.d.ts +85 -0
  65. package/dist/compare/band.js +70 -0
  66. package/dist/compare/band.js.map +1 -0
  67. package/dist/compare/cascade.d.ts +45 -0
  68. package/dist/compare/cascade.js +71 -0
  69. package/dist/compare/cascade.js.map +1 -0
  70. package/dist/compare/derive.d.ts +78 -0
  71. package/dist/compare/derive.js +39 -0
  72. package/dist/compare/derive.js.map +1 -0
  73. package/dist/compare/diff/attribution.d.ts +23 -0
  74. package/dist/compare/diff/attribution.js +244 -0
  75. package/dist/compare/diff/attribution.js.map +1 -0
  76. package/dist/compare/diff/compare-nodes.d.ts +15 -0
  77. package/dist/compare/diff/compare-nodes.js +227 -0
  78. package/dist/compare/diff/compare-nodes.js.map +1 -0
  79. package/dist/compare/diff/components.d.ts +20 -0
  80. package/dist/compare/diff/components.js +76 -0
  81. package/dist/compare/diff/components.js.map +1 -0
  82. package/dist/compare/diff/delta.d.ts +165 -0
  83. package/dist/compare/diff/delta.js +12 -0
  84. package/dist/compare/diff/delta.js.map +1 -0
  85. package/dist/compare/diff/index.d.ts +69 -0
  86. package/dist/compare/diff/index.js +101 -0
  87. package/dist/compare/diff/index.js.map +1 -0
  88. package/dist/compare/diff/match.d.ts +37 -0
  89. package/dist/compare/diff/match.js +167 -0
  90. package/dist/compare/diff/match.js.map +1 -0
  91. package/dist/compare/explain.d.ts +23 -0
  92. package/dist/compare/explain.js +190 -0
  93. package/dist/compare/explain.js.map +1 -0
  94. package/dist/compare/holding-diff.d.ts +25 -0
  95. package/dist/compare/holding-diff.js +74 -0
  96. package/dist/compare/holding-diff.js.map +1 -0
  97. package/dist/compare/impact.d.ts +56 -0
  98. package/dist/compare/impact.js +124 -0
  99. package/dist/compare/impact.js.map +1 -0
  100. package/dist/compare/index.d.ts +30 -0
  101. package/dist/compare/index.js +22 -0
  102. package/dist/compare/index.js.map +1 -0
  103. package/dist/compare/instance.d.ts +30 -0
  104. package/dist/compare/instance.js +145 -0
  105. package/dist/compare/instance.js.map +1 -0
  106. package/dist/compare/observability.d.ts +49 -0
  107. package/dist/compare/observability.js +57 -0
  108. package/dist/compare/observability.js.map +1 -0
  109. package/dist/compare/parting.d.ts +179 -0
  110. package/dist/compare/parting.js +244 -0
  111. package/dist/compare/parting.js.map +1 -0
  112. package/dist/compare/slice.d.ts +74 -0
  113. package/dist/compare/slice.js +79 -0
  114. package/dist/compare/slice.js.map +1 -0
  115. package/dist/compare/value.d.ts +60 -0
  116. package/dist/compare/value.js +111 -0
  117. package/dist/compare/value.js.map +1 -0
  118. package/dist/format/accessibility.d.ts +27 -0
  119. package/dist/format/accessibility.js +17 -0
  120. package/dist/format/accessibility.js.map +1 -0
  121. package/dist/format/canonical.d.ts +38 -0
  122. package/dist/format/canonical.js +65 -0
  123. package/dist/format/canonical.js.map +1 -0
  124. package/dist/format/capture.d.ts +212 -0
  125. package/dist/format/capture.js +2 -0
  126. package/dist/format/capture.js.map +1 -0
  127. package/dist/format/document.d.ts +279 -0
  128. package/dist/format/document.js +65 -0
  129. package/dist/format/document.js.map +1 -0
  130. package/dist/format/environment.d.ts +120 -0
  131. package/dist/format/environment.js +53 -0
  132. package/dist/format/environment.js.map +1 -0
  133. package/dist/format/hash.d.ts +45 -0
  134. package/dist/format/hash.js +41 -0
  135. package/dist/format/hash.js.map +1 -0
  136. package/dist/format/holding.d.ts +134 -0
  137. package/dist/format/holding.js +2 -0
  138. package/dist/format/holding.js.map +1 -0
  139. package/dist/format/index.d.ts +39 -0
  140. package/dist/format/index.js +25 -0
  141. package/dist/format/index.js.map +1 -0
  142. package/dist/format/intervention.d.ts +87 -0
  143. package/dist/format/intervention.js +11 -0
  144. package/dist/format/intervention.js.map +1 -0
  145. package/dist/format/profile.d.ts +59 -0
  146. package/dist/format/profile.js +66 -0
  147. package/dist/format/profile.js.map +1 -0
  148. package/dist/format/provenance.d.ts +179 -0
  149. package/dist/format/provenance.js +222 -0
  150. package/dist/format/provenance.js.map +1 -0
  151. package/dist/format/sha256.d.ts +34 -0
  152. package/dist/format/sha256.js +152 -0
  153. package/dist/format/sha256.js.map +1 -0
  154. package/dist/format/snapshot.d.ts +263 -0
  155. package/dist/format/snapshot.js +2 -0
  156. package/dist/format/snapshot.js.map +1 -0
  157. package/dist/format/stabilize.d.ts +190 -0
  158. package/dist/format/stabilize.js +333 -0
  159. package/dist/format/stabilize.js.map +1 -0
  160. package/dist/format/tier.d.ts +19 -0
  161. package/dist/format/tier.js +11 -0
  162. package/dist/format/tier.js.map +1 -0
  163. package/dist/format/value.d.ts +115 -0
  164. package/dist/format/value.js +182 -0
  165. package/dist/format/value.js.map +1 -0
  166. package/dist/format/wiring.d.ts +115 -0
  167. package/dist/format/wiring.js +60 -0
  168. package/dist/format/wiring.js.map +1 -0
  169. package/dist/index.d.ts +35 -0
  170. package/dist/index.js +35 -0
  171. package/dist/index.js.map +1 -0
  172. package/dist/judge/docket.d.ts +85 -0
  173. package/dist/judge/docket.js +162 -0
  174. package/dist/judge/docket.js.map +1 -0
  175. package/dist/judge/fingerprint.d.ts +53 -0
  176. package/dist/judge/fingerprint.js +144 -0
  177. package/dist/judge/fingerprint.js.map +1 -0
  178. package/dist/judge/ignore.d.ts +206 -0
  179. package/dist/judge/ignore.js +237 -0
  180. package/dist/judge/ignore.js.map +1 -0
  181. package/dist/judge/index.d.ts +44 -0
  182. package/dist/judge/index.js +34 -0
  183. package/dist/judge/index.js.map +1 -0
  184. package/dist/judge/inspect.d.ts +148 -0
  185. package/dist/judge/inspect.js +208 -0
  186. package/dist/judge/inspect.js.map +1 -0
  187. package/dist/judge/intent.d.ts +146 -0
  188. package/dist/judge/intent.js +150 -0
  189. package/dist/judge/intent.js.map +1 -0
  190. package/dist/judge/locale.d.ts +111 -0
  191. package/dist/judge/locale.js +247 -0
  192. package/dist/judge/locale.js.map +1 -0
  193. package/dist/judge/scope.d.ts +41 -0
  194. package/dist/judge/scope.js +47 -0
  195. package/dist/judge/scope.js.map +1 -0
  196. package/dist/judge/sensitivity.d.ts +202 -0
  197. package/dist/judge/sensitivity.js +152 -0
  198. package/dist/judge/sensitivity.js.map +1 -0
  199. package/dist/judge/trail.d.ts +122 -0
  200. package/dist/judge/trail.js +96 -0
  201. package/dist/judge/trail.js.map +1 -0
  202. package/dist/judge/verdict.d.ts +39 -0
  203. package/dist/judge/verdict.js +32 -0
  204. package/dist/judge/verdict.js.map +1 -0
  205. package/dist/plan/index.d.ts +12 -0
  206. package/dist/plan/index.js +11 -0
  207. package/dist/plan/index.js.map +1 -0
  208. package/dist/plan/tool.d.ts +85 -0
  209. package/dist/plan/tool.js +87 -0
  210. package/dist/plan/tool.js.map +1 -0
  211. package/dist/relate/graph.d.ts +190 -0
  212. package/dist/relate/graph.js +234 -0
  213. package/dist/relate/graph.js.map +1 -0
  214. package/dist/relate/index.d.ts +16 -0
  215. package/dist/relate/index.js +16 -0
  216. package/dist/relate/index.js.map +1 -0
  217. package/dist/relate/merkle.d.ts +110 -0
  218. package/dist/relate/merkle.js +246 -0
  219. package/dist/relate/merkle.js.map +1 -0
  220. package/dist/relate/reach.d.ts +65 -0
  221. package/dist/relate/reach.js +111 -0
  222. package/dist/relate/reach.js.map +1 -0
  223. package/dist/relate/records.d.ts +129 -0
  224. package/dist/relate/records.js +112 -0
  225. package/dist/relate/records.js.map +1 -0
  226. package/dist/rules/index.d.ts +19 -0
  227. package/dist/rules/index.js +17 -0
  228. package/dist/rules/index.js.map +1 -0
  229. package/dist/rules/normalize/alias.d.ts +50 -0
  230. package/dist/rules/normalize/alias.js +111 -0
  231. package/dist/rules/normalize/alias.js.map +1 -0
  232. package/dist/rules/normalize/cascade.d.ts +85 -0
  233. package/dist/rules/normalize/cascade.js +324 -0
  234. package/dist/rules/normalize/cascade.js.map +1 -0
  235. package/dist/rules/normalize/color.d.ts +30 -0
  236. package/dist/rules/normalize/color.js +185 -0
  237. package/dist/rules/normalize/color.js.map +1 -0
  238. package/dist/rules/normalize/fixture.d.ts +83 -0
  239. package/dist/rules/normalize/fixture.js +74 -0
  240. package/dist/rules/normalize/fixture.js.map +1 -0
  241. package/dist/rules/normalize/index.d.ts +39 -0
  242. package/dist/rules/normalize/index.js +268 -0
  243. package/dist/rules/normalize/index.js.map +1 -0
  244. package/dist/rules/normalize/project.d.ts +42 -0
  245. package/dist/rules/normalize/project.js +83 -0
  246. package/dist/rules/normalize/project.js.map +1 -0
  247. package/dist/rules/normalize/shorthand.d.ts +38 -0
  248. package/dist/rules/normalize/shorthand.js +388 -0
  249. package/dist/rules/normalize/shorthand.js.map +1 -0
  250. package/dist/rules/normalize/value.d.ts +23 -0
  251. package/dist/rules/normalize/value.js +162 -0
  252. package/dist/rules/normalize/value.js.map +1 -0
  253. package/dist/rules/normalize/wrapper.d.ts +16 -0
  254. package/dist/rules/normalize/wrapper.js +137 -0
  255. package/dist/rules/normalize/wrapper.js.map +1 -0
  256. package/dist/rules/ruleset.d.ts +93 -0
  257. package/dist/rules/ruleset.js +175 -0
  258. package/dist/rules/ruleset.js.map +1 -0
  259. package/mark.svg +30 -0
  260. package/package.json +69 -0
@@ -0,0 +1,108 @@
1
+ /**
2
+ * V8: ` at App (http://host/src/probe.jsx:23:26)` — and the same without a
3
+ * name for top-level code, which is why the parenthesised half is optional.
4
+ *
5
+ * `async`, `new` and `Object.<anonymous>` all land in the name, which is fine:
6
+ * nothing here reads the name, and a name that is slightly wrong costs nothing
7
+ * where a *position* that is slightly wrong costs a reviewer an open file.
8
+ */
9
+ const V8_FRAME = /^\s*at\s+(?:(.+?)\s+\()?(.+?):(\d+):(\d+)\)?$/;
10
+ /** SpiderMonkey and JavaScriptCore: `App@http://host/src/probe.jsx:23:26`. */
11
+ const AT_FRAME = /^\s*(?:(.*?)@)(.+?):(\d+):(\d+)$/;
12
+ /**
13
+ * The frames of a stack, in order, outermost call last.
14
+ *
15
+ * The message line is dropped, and so is any line neither engine format
16
+ * recognises — a stack is diagnostic output rather than a data format, and a
17
+ * frame that cannot be read is one fewer candidate rather than a failure.
18
+ *
19
+ * `eval` frames are deliberately not unwrapped. V8 writes them as
20
+ * `at eval (eval at fn (http://host/a.js:1:1), <anonymous>:2:3)`, where two
21
+ * positions are present and the inner one is meaningless outside the eval.
22
+ * Taking the outer one would name the line that *called* `eval` as the line that
23
+ * wrote the element, which is a confident wrong answer.
24
+ */
25
+ export function parseStackFrames(stack) {
26
+ const frames = [];
27
+ for (const raw of stack.split('\n')) {
28
+ const line = raw.trimEnd();
29
+ if (line === '' || line.includes('(eval at '))
30
+ continue;
31
+ const match = V8_FRAME.exec(line) ?? AT_FRAME.exec(line);
32
+ if (match === null)
33
+ continue;
34
+ const [, name, url = '', lineDigits = '', columnDigits = ''] = match;
35
+ if (url === '' || url.startsWith('data:'))
36
+ continue;
37
+ const at = Number(lineDigits);
38
+ const column = Number(columnDigits);
39
+ if (!Number.isFinite(at) || !Number.isFinite(column) || at < 1)
40
+ continue;
41
+ frames.push({
42
+ url,
43
+ line: at,
44
+ column,
45
+ ...(name !== undefined && name !== '' ? { function: name } : {}),
46
+ });
47
+ }
48
+ return frames;
49
+ }
50
+ /**
51
+ * Whether a path belongs to a dependency rather than to the project.
52
+ *
53
+ * Three spellings of the same fact, because three build tools spell it
54
+ * differently: the directory itself, Vite's prebundled dependency cache, and the
55
+ * `webpack-internal:` scheme wrapping the same directory. The test runs against
56
+ * both the served URL — which saves fetching a map for a megabyte of vendor
57
+ * code — and against the path the map resolves to, which is what actually
58
+ * decides it.
59
+ *
60
+ * A dependency that *is* the subject cannot be attributed by this, and that is
61
+ * the intended reading: a project reviewing its own components wants the line in
62
+ * its own repository, and the line inside `react-dom` that rendered it is never
63
+ * the answer to "what changed".
64
+ */
65
+ export function isVendorPath(path) {
66
+ return (path.includes('node_modules') ||
67
+ path.includes('/.vite/deps/') ||
68
+ path.includes('/.yarn/') ||
69
+ path.startsWith('webpack-internal:'));
70
+ }
71
+ /**
72
+ * The location that wrote this element, chosen from its stack.
73
+ *
74
+ * The rule is one sentence: **the first frame that resolves to a file the
75
+ * project wrote.** Frame zero is always React's own runtime, because React is
76
+ * what constructed the error. Frame one is the author in an ordinary build and
77
+ * is a custom JSX runtime in a build that has one — and this does not need to
78
+ * know which, because Emotion resolves into `node_modules` and the component
79
+ * does not.
80
+ *
81
+ * That is the same answer the recorded-symbol path gives, arrived at from the
82
+ * other side. Where `jsx-source` is installed it wins, because it is exact
83
+ * without a map; this is what a project that installed nothing still gets.
84
+ *
85
+ * `originalFor` is supplied rather than performed because resolving a frame
86
+ * means fetching the module the browser was served, and this package may not
87
+ * assume a network (ADR-0013). The caller owns the fetch, the cache, and the
88
+ * decision about how long to wait; this owns which frame to ask about and when
89
+ * to stop asking.
90
+ */
91
+ export function writerLocationOf(frames, originalFor) {
92
+ for (const frame of frames) {
93
+ if (isVendorPath(frame.url))
94
+ continue;
95
+ const original = originalFor(frame);
96
+ // No map is not the same as a vendor frame. An unmapped application module
97
+ // is one a build served as written — which a dev server does for plain `.js`
98
+ // — and its own coordinates are already the answer.
99
+ if (original === null) {
100
+ return { file: frame.url, line: frame.line, column: frame.column };
101
+ }
102
+ if (isVendorPath(original.source))
103
+ continue;
104
+ return { file: original.source, line: original.line, column: original.column };
105
+ }
106
+ return null;
107
+ }
108
+ //# sourceMappingURL=stack.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stack.js","sourceRoot":"","sources":["../../src/attribute/stack.ts"],"names":[],"mappings":"AA+BA;;;;;;;GAOG;AACH,MAAM,QAAQ,GAAG,+CAA+C,CAAC;AAEjE,8EAA8E;AAC9E,MAAM,QAAQ,GAAG,kCAAkC,CAAC;AAEpD;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAa;IAC5C,MAAM,MAAM,GAAiB,EAAE,CAAC;IAEhC,KAAK,MAAM,GAAG,IAAI,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACpC,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,EAAE,CAAC;QAC3B,IAAI,IAAI,KAAK,EAAE,IAAI,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC;YAAE,SAAS;QAExD,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACzD,IAAI,KAAK,KAAK,IAAI;YAAE,SAAS;QAE7B,MAAM,CAAC,EAAE,IAAI,EAAE,GAAG,GAAG,EAAE,EAAE,UAAU,GAAG,EAAE,EAAE,YAAY,GAAG,EAAE,CAAC,GAAG,KAAK,CAAC;QACrE,IAAI,GAAG,KAAK,EAAE,IAAI,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC;YAAE,SAAS;QAEpD,MAAM,EAAE,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC;QAC9B,MAAM,MAAM,GAAG,MAAM,CAAC,YAAY,CAAC,CAAC;QACpC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC;YAAE,SAAS;QAEzE,MAAM,CAAC,IAAI,CAAC;YACV,GAAG;YACH,IAAI,EAAE,EAAE;YACR,MAAM;YACN,GAAG,CAAC,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACjE,CAAC,CAAC;IACL,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,OAAO,CACL,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC;QAC7B,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC;QAC7B,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC;QACxB,IAAI,CAAC,UAAU,CAAC,mBAAmB,CAAC,CACrC,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,gBAAgB,CAC9B,MAA6B,EAC7B,WAA2D;IAE3D,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,YAAY,CAAC,KAAK,CAAC,GAAG,CAAC;YAAE,SAAS;QAEtC,MAAM,QAAQ,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;QAEpC,2EAA2E;QAC3E,6EAA6E;QAC7E,oDAAoD;QACpD,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;YACtB,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC;QACrE,CAAC;QAED,IAAI,YAAY,CAAC,QAAQ,CAAC,MAAM,CAAC;YAAE,SAAS;QAE5C,OAAO,EAAE,IAAI,EAAE,QAAQ,CAAC,MAAM,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC;IACjF,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC","sourcesContent":["import type { SourceLocation, StackFrame } from '../format/provenance.js';\nimport type { OriginalPosition } from './source-map.js';\n\n/**\n * Reading a call site out of a stack, and deciding which frame is the author.\n *\n * React 19 constructs an `Error` inside its own `jsx`/`jsxDEV` and keeps it on\n * every fiber. That error is a complete answer to \"which line wrote this\n * element\" that no build has to opt into: the runtime that captured it is frame\n * zero, and whoever called it is frame one. Nothing is configured, nothing is\n * installed, and the location is present in any build where React is the\n * development build — which is every dev server, every Vitest run and every Jest\n * run, because all three are what \"not production\" means.\n *\n * Two jobs live here and they are separable on purpose. Parsing a stack is a\n * fact about a JavaScript engine. Choosing *which* frame is the author is a\n * policy, and it is the part with a defensible rule rather than a heuristic:\n * the author is the first frame that maps back to code the project wrote. A\n * custom JSX runtime — Emotion, theme-ui — sits between React and the author and\n * is skipped not because it is on a list but because it resolves into\n * `node_modules`.\n */\n\n/**\n * A frame is a wire value, not a local one: the page reads it, the collector\n * resolves it, and it travels between them on `Provenance`. It is declared where\n * the rest of provenance is declared, and re-exported here because this is the\n * group that does something with it.\n */\nexport type { StackFrame };\n\n/**\n * V8: ` at App (http://host/src/probe.jsx:23:26)` — and the same without a\n * name for top-level code, which is why the parenthesised half is optional.\n *\n * `async`, `new` and `Object.<anonymous>` all land in the name, which is fine:\n * nothing here reads the name, and a name that is slightly wrong costs nothing\n * where a *position* that is slightly wrong costs a reviewer an open file.\n */\nconst V8_FRAME = /^\\s*at\\s+(?:(.+?)\\s+\\()?(.+?):(\\d+):(\\d+)\\)?$/;\n\n/** SpiderMonkey and JavaScriptCore: `App@http://host/src/probe.jsx:23:26`. */\nconst AT_FRAME = /^\\s*(?:(.*?)@)(.+?):(\\d+):(\\d+)$/;\n\n/**\n * The frames of a stack, in order, outermost call last.\n *\n * The message line is dropped, and so is any line neither engine format\n * recognises — a stack is diagnostic output rather than a data format, and a\n * frame that cannot be read is one fewer candidate rather than a failure.\n *\n * `eval` frames are deliberately not unwrapped. V8 writes them as\n * `at eval (eval at fn (http://host/a.js:1:1), <anonymous>:2:3)`, where two\n * positions are present and the inner one is meaningless outside the eval.\n * Taking the outer one would name the line that *called* `eval` as the line that\n * wrote the element, which is a confident wrong answer.\n */\nexport function parseStackFrames(stack: string): StackFrame[] {\n const frames: StackFrame[] = [];\n\n for (const raw of stack.split('\\n')) {\n const line = raw.trimEnd();\n if (line === '' || line.includes('(eval at ')) continue;\n\n const match = V8_FRAME.exec(line) ?? AT_FRAME.exec(line);\n if (match === null) continue;\n\n const [, name, url = '', lineDigits = '', columnDigits = ''] = match;\n if (url === '' || url.startsWith('data:')) continue;\n\n const at = Number(lineDigits);\n const column = Number(columnDigits);\n if (!Number.isFinite(at) || !Number.isFinite(column) || at < 1) continue;\n\n frames.push({\n url,\n line: at,\n column,\n ...(name !== undefined && name !== '' ? { function: name } : {}),\n });\n }\n\n return frames;\n}\n\n/**\n * Whether a path belongs to a dependency rather than to the project.\n *\n * Three spellings of the same fact, because three build tools spell it\n * differently: the directory itself, Vite's prebundled dependency cache, and the\n * `webpack-internal:` scheme wrapping the same directory. The test runs against\n * both the served URL — which saves fetching a map for a megabyte of vendor\n * code — and against the path the map resolves to, which is what actually\n * decides it.\n *\n * A dependency that *is* the subject cannot be attributed by this, and that is\n * the intended reading: a project reviewing its own components wants the line in\n * its own repository, and the line inside `react-dom` that rendered it is never\n * the answer to \"what changed\".\n */\nexport function isVendorPath(path: string): boolean {\n return (\n path.includes('node_modules') ||\n path.includes('/.vite/deps/') ||\n path.includes('/.yarn/') ||\n path.startsWith('webpack-internal:')\n );\n}\n\n/**\n * The location that wrote this element, chosen from its stack.\n *\n * The rule is one sentence: **the first frame that resolves to a file the\n * project wrote.** Frame zero is always React's own runtime, because React is\n * what constructed the error. Frame one is the author in an ordinary build and\n * is a custom JSX runtime in a build that has one — and this does not need to\n * know which, because Emotion resolves into `node_modules` and the component\n * does not.\n *\n * That is the same answer the recorded-symbol path gives, arrived at from the\n * other side. Where `jsx-source` is installed it wins, because it is exact\n * without a map; this is what a project that installed nothing still gets.\n *\n * `originalFor` is supplied rather than performed because resolving a frame\n * means fetching the module the browser was served, and this package may not\n * assume a network (ADR-0013). The caller owns the fetch, the cache, and the\n * decision about how long to wait; this owns which frame to ask about and when\n * to stop asking.\n */\nexport function writerLocationOf(\n frames: readonly StackFrame[],\n originalFor: (frame: StackFrame) => OriginalPosition | null,\n): SourceLocation | null {\n for (const frame of frames) {\n if (isVendorPath(frame.url)) continue;\n\n const original = originalFor(frame);\n\n // No map is not the same as a vendor frame. An unmapped application module\n // is one a build served as written — which a dev server does for plain `.js`\n // — and its own coordinates are already the answer.\n if (original === null) {\n return { file: frame.url, line: frame.line, column: frame.column };\n }\n\n if (isVendorPath(original.source)) continue;\n\n return { file: original.source, line: original.line, column: original.column };\n }\n\n return null;\n}\n"]}
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Frequency bands (spec §5).
3
+ *
4
+ * Every semantic delta lands in exactly one band, and the band decides how loudly
5
+ * it is reported. The insight the bands encode: change frequency and change
6
+ * importance are inversely correlated. An accessible name almost never moves and
7
+ * is a defect when it does; anti-aliasing moves constantly and never matters.
8
+ *
9
+ * The list is ordered, loudest first, and `loudestBand` is the only thing allowed
10
+ * to collapse a set of them. Three call sites used to hard-code their own
11
+ * `if (bands.includes('geometry'))` ladder, which meant adding a band silently
12
+ * demoted it below everything in every one of them.
13
+ */
14
+ export type Band =
15
+ /**
16
+ * Rarest, and the one a picture cannot hold. A role, an accessible name, or an
17
+ * ARIA state changed — what a screen reader announces moved, whether or not
18
+ * anything was repainted.
19
+ *
20
+ * Split out of `geometry` deliberately. Both are low-frequency, but a box that
21
+ * moved and a control that lost its name are different findings for different
22
+ * readers, and folding the second into the first means the evidence this
23
+ * project collects and no image-comparison tool has is reported under a label
24
+ * that sounds like layout. A project can now write `blocking: ['a11y']` and
25
+ * have an accessibility regression stop a merge on its own terms.
26
+ */
27
+ 'a11y'
28
+ /** Low frequency. Boxes appear, vanish, move, or resize; content reflows. */
29
+ | 'geometry'
30
+ /** Mid frequency. Style *values* moved while structure held. */
31
+ | 'token'
32
+ /**
33
+ * Text moved and nothing else did.
34
+ *
35
+ * The highest-frequency band that is still a real change: copy edits, counts,
36
+ * dates, translations. Quieter than `geometry` because a reviewer who is told
37
+ * a string changed does not also need to be told a box moved — the reflow is
38
+ * the consequence, and `geometry` outranks it exactly when the tree moved for
39
+ * some other reason too.
40
+ *
41
+ * It is separated from `geometry` because it is the band a message catalogue
42
+ * joins against. A locale run compares the same subject's `content` across two
43
+ * languages, which is a question no baseline image can be asked.
44
+ */
45
+ | 'content'
46
+ /**
47
+ * High frequency. Sub-semantic rendering variance — anti-aliasing, sub-pixel
48
+ * shifts, compression artifacts. Auto-passes; counted, never reviewed.
49
+ */
50
+ | 'texture';
51
+ /** Loudest first. `loudestBand` reads this order; nothing else may assume one. */
52
+ export declare const BANDS: readonly Band[];
53
+ /**
54
+ * The loudest band present, or `null` for an empty set.
55
+ *
56
+ * `null` rather than a default, because "nothing changed" and "something changed
57
+ * at the quietest band" are different states and a caller that wants to conflate
58
+ * them should have to say so.
59
+ */
60
+ export declare function loudestBand(bands: Iterable<Band>): Band | null;
61
+ /** What kind of thing changed, before policy has an opinion about it. */
62
+ export type DeltaKind = 'node-added' | 'node-removed' | 'node-moved' | 'role-changed' | 'name-changed' | 'description-changed' | 'state-changed' | 'text-changed' | 'attribute-changed' | 'rect-changed' | 'style-changed' | 'token-changed' | 'raster-residue';
63
+ /**
64
+ * The band a delta falls in, before policy.
65
+ *
66
+ * `attribute-changed` is `geometry` rather than `a11y` for a reason that is
67
+ * easy to get backwards: **no `aria-*` attribute ever reaches it.**
68
+ * `ATTRIBUTE_ALLOWLIST` drops them all, because each one resolves into a field
69
+ * of its own — `role`, `name`, `description`, `state` — and those are the four
70
+ * kinds banded `a11y`. An `aria-*` delta arriving as `attribute-changed` would
71
+ * mean normalization had stopped resolving it, which is a defect rather than a
72
+ * banding question.
73
+ *
74
+ * Note that `rect-changed` is `geometry` while `style-changed` is `token`, even
75
+ * though a padding change produces both. That is intended: the two are reported
76
+ * at different bands from different evidence, and the attributor folds the rect
77
+ * movement into the style change as collateral. Under a profile without layout
78
+ * there is no rect evidence at all, and the style change stands alone — which is
79
+ * why JSDOM can decide the token band but only part of geometry.
80
+ *
81
+ * Policy may promote or demote from here per project, subject, region, or
82
+ * component (spec §5). This function is the default, not the answer.
83
+ */
84
+ export declare function bandOf(kind: DeltaKind): Band;
85
+ //# sourceMappingURL=band.d.ts.map
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Frequency bands (spec §5).
3
+ *
4
+ * Every semantic delta lands in exactly one band, and the band decides how loudly
5
+ * it is reported. The insight the bands encode: change frequency and change
6
+ * importance are inversely correlated. An accessible name almost never moves and
7
+ * is a defect when it does; anti-aliasing moves constantly and never matters.
8
+ *
9
+ * The list is ordered, loudest first, and `loudestBand` is the only thing allowed
10
+ * to collapse a set of them. Three call sites used to hard-code their own
11
+ * `if (bands.includes('geometry'))` ladder, which meant adding a band silently
12
+ * demoted it below everything in every one of them.
13
+ */
14
+ /** Loudest first. `loudestBand` reads this order; nothing else may assume one. */
15
+ export const BANDS = ['a11y', 'geometry', 'token', 'content', 'texture'];
16
+ /**
17
+ * The loudest band present, or `null` for an empty set.
18
+ *
19
+ * `null` rather than a default, because "nothing changed" and "something changed
20
+ * at the quietest band" are different states and a caller that wants to conflate
21
+ * them should have to say so.
22
+ */
23
+ export function loudestBand(bands) {
24
+ const present = new Set(bands);
25
+ return BANDS.find((band) => present.has(band)) ?? null;
26
+ }
27
+ /**
28
+ * The band a delta falls in, before policy.
29
+ *
30
+ * `attribute-changed` is `geometry` rather than `a11y` for a reason that is
31
+ * easy to get backwards: **no `aria-*` attribute ever reaches it.**
32
+ * `ATTRIBUTE_ALLOWLIST` drops them all, because each one resolves into a field
33
+ * of its own — `role`, `name`, `description`, `state` — and those are the four
34
+ * kinds banded `a11y`. An `aria-*` delta arriving as `attribute-changed` would
35
+ * mean normalization had stopped resolving it, which is a defect rather than a
36
+ * banding question.
37
+ *
38
+ * Note that `rect-changed` is `geometry` while `style-changed` is `token`, even
39
+ * though a padding change produces both. That is intended: the two are reported
40
+ * at different bands from different evidence, and the attributor folds the rect
41
+ * movement into the style change as collateral. Under a profile without layout
42
+ * there is no rect evidence at all, and the style change stands alone — which is
43
+ * why JSDOM can decide the token band but only part of geometry.
44
+ *
45
+ * Policy may promote or demote from here per project, subject, region, or
46
+ * component (spec §5). This function is the default, not the answer.
47
+ */
48
+ export function bandOf(kind) {
49
+ switch (kind) {
50
+ case 'role-changed':
51
+ case 'name-changed':
52
+ case 'description-changed':
53
+ case 'state-changed':
54
+ return 'a11y';
55
+ case 'text-changed':
56
+ return 'content';
57
+ case 'node-added':
58
+ case 'node-removed':
59
+ case 'node-moved':
60
+ case 'attribute-changed':
61
+ case 'rect-changed':
62
+ return 'geometry';
63
+ case 'style-changed':
64
+ case 'token-changed':
65
+ return 'token';
66
+ case 'raster-residue':
67
+ return 'texture';
68
+ }
69
+ }
70
+ //# sourceMappingURL=band.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"band.js","sourceRoot":"","sources":["../../src/compare/band.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAwCH,kFAAkF;AAClF,MAAM,CAAC,MAAM,KAAK,GAAoB,CAAC,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC;AAE1F;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAC,KAAqB;IAC/C,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;IAC/B,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC;AACzD,CAAC;AAkBD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,MAAM,CAAC,IAAe;IACpC,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,cAAc,CAAC;QACpB,KAAK,cAAc,CAAC;QACpB,KAAK,qBAAqB,CAAC;QAC3B,KAAK,eAAe;YAClB,OAAO,MAAM,CAAC;QAChB,KAAK,cAAc;YACjB,OAAO,SAAS,CAAC;QACnB,KAAK,YAAY,CAAC;QAClB,KAAK,cAAc,CAAC;QACpB,KAAK,YAAY,CAAC;QAClB,KAAK,mBAAmB,CAAC;QACzB,KAAK,cAAc;YACjB,OAAO,UAAU,CAAC;QACpB,KAAK,eAAe,CAAC;QACrB,KAAK,eAAe;YAClB,OAAO,OAAO,CAAC;QACjB,KAAK,gBAAgB;YACnB,OAAO,SAAS,CAAC;IACrB,CAAC;AACH,CAAC","sourcesContent":["/**\n * Frequency bands (spec §5).\n *\n * Every semantic delta lands in exactly one band, and the band decides how loudly\n * it is reported. The insight the bands encode: change frequency and change\n * importance are inversely correlated. An accessible name almost never moves and\n * is a defect when it does; anti-aliasing moves constantly and never matters.\n *\n * The list is ordered, loudest first, and `loudestBand` is the only thing allowed\n * to collapse a set of them. Three call sites used to hard-code their own\n * `if (bands.includes('geometry'))` ladder, which meant adding a band silently\n * demoted it below everything in every one of them.\n */\n\nexport type Band =\n /**\n * Rarest, and the one a picture cannot hold. A role, an accessible name, or an\n * ARIA state changed — what a screen reader announces moved, whether or not\n * anything was repainted.\n *\n * Split out of `geometry` deliberately. Both are low-frequency, but a box that\n * moved and a control that lost its name are different findings for different\n * readers, and folding the second into the first means the evidence this\n * project collects and no image-comparison tool has is reported under a label\n * that sounds like layout. A project can now write `blocking: ['a11y']` and\n * have an accessibility regression stop a merge on its own terms.\n */\n | 'a11y'\n /** Low frequency. Boxes appear, vanish, move, or resize; content reflows. */\n | 'geometry'\n /** Mid frequency. Style *values* moved while structure held. */\n | 'token'\n /**\n * Text moved and nothing else did.\n *\n * The highest-frequency band that is still a real change: copy edits, counts,\n * dates, translations. Quieter than `geometry` because a reviewer who is told\n * a string changed does not also need to be told a box moved — the reflow is\n * the consequence, and `geometry` outranks it exactly when the tree moved for\n * some other reason too.\n *\n * It is separated from `geometry` because it is the band a message catalogue\n * joins against. A locale run compares the same subject's `content` across two\n * languages, which is a question no baseline image can be asked.\n */\n | 'content'\n /**\n * High frequency. Sub-semantic rendering variance — anti-aliasing, sub-pixel\n * shifts, compression artifacts. Auto-passes; counted, never reviewed.\n */\n | 'texture';\n\n/** Loudest first. `loudestBand` reads this order; nothing else may assume one. */\nexport const BANDS: readonly Band[] = ['a11y', 'geometry', 'token', 'content', 'texture'];\n\n/**\n * The loudest band present, or `null` for an empty set.\n *\n * `null` rather than a default, because \"nothing changed\" and \"something changed\n * at the quietest band\" are different states and a caller that wants to conflate\n * them should have to say so.\n */\nexport function loudestBand(bands: Iterable<Band>): Band | null {\n const present = new Set(bands);\n return BANDS.find((band) => present.has(band)) ?? null;\n}\n\n/** What kind of thing changed, before policy has an opinion about it. */\nexport type DeltaKind =\n | 'node-added'\n | 'node-removed'\n | 'node-moved'\n | 'role-changed'\n | 'name-changed'\n | 'description-changed'\n | 'state-changed'\n | 'text-changed'\n | 'attribute-changed'\n | 'rect-changed'\n | 'style-changed'\n | 'token-changed'\n | 'raster-residue';\n\n/**\n * The band a delta falls in, before policy.\n *\n * `attribute-changed` is `geometry` rather than `a11y` for a reason that is\n * easy to get backwards: **no `aria-*` attribute ever reaches it.**\n * `ATTRIBUTE_ALLOWLIST` drops them all, because each one resolves into a field\n * of its own — `role`, `name`, `description`, `state` — and those are the four\n * kinds banded `a11y`. An `aria-*` delta arriving as `attribute-changed` would\n * mean normalization had stopped resolving it, which is a defect rather than a\n * banding question.\n *\n * Note that `rect-changed` is `geometry` while `style-changed` is `token`, even\n * though a padding change produces both. That is intended: the two are reported\n * at different bands from different evidence, and the attributor folds the rect\n * movement into the style change as collateral. Under a profile without layout\n * there is no rect evidence at all, and the style change stands alone — which is\n * why JSDOM can decide the token band but only part of geometry.\n *\n * Policy may promote or demote from here per project, subject, region, or\n * component (spec §5). This function is the default, not the answer.\n */\nexport function bandOf(kind: DeltaKind): Band {\n switch (kind) {\n case 'role-changed':\n case 'name-changed':\n case 'description-changed':\n case 'state-changed':\n return 'a11y';\n case 'text-changed':\n return 'content';\n case 'node-added':\n case 'node-removed':\n case 'node-moved':\n case 'attribute-changed':\n case 'rect-changed':\n return 'geometry';\n case 'style-changed':\n case 'token-changed':\n return 'token';\n case 'raster-residue':\n return 'texture';\n }\n}\n"]}
@@ -0,0 +1,45 @@
1
+ import type { SemanticNode, SemanticSnapshot } from '../format/snapshot.js';
2
+ import type { MovedInput } from './parting.js';
3
+ /**
4
+ * The input nobody passes.
5
+ *
6
+ * Every rung in `parting.ts` above this one reads something a boundary
7
+ * *received* — a prop, a context, a hook cell — and all three arrive through a
8
+ * framework adapter. This one reads what the boundary did not receive and did
9
+ * not declare, out of the snapshot itself, which is why it is the only rung a
10
+ * run with no adapter attached can reach.
11
+ */
12
+ /**
13
+ * Every property some declaration set *at* a node, keyed by node and name.
14
+ *
15
+ * `styleProvenance` records where each winning declaration came from, so the
16
+ * properties it does not mention at a node are exactly the ones that arrived
17
+ * from somewhere else — which is the whole reading below.
18
+ */
19
+ export declare function declaredIn(snapshot: SemanticSnapshot): ReadonlySet<string>;
20
+ /**
21
+ * What an ancestor decided, and the rung that stops this being called a flake.
22
+ *
23
+ * A component that sets its own font and no colour paints white on a dark panel
24
+ * and near-black on a light card from one set of props. Every input the holding
25
+ * carries — props, contexts, hook cells — is byte-identical across the two, so
26
+ * before this existed the boundary settled at `undetermined` and `explainParting`
27
+ * called a perfectly deterministic component *nondeterministic*. That is the
28
+ * accusation ADR-0002 forbids making about an input nobody read, and the input
29
+ * was there to be read: the resolved value is on the node and `styleProvenance`
30
+ * says no declaration here produced it.
31
+ *
32
+ * So the reading is the difference between those two facts — **in the style,
33
+ * not in the provenance** — and a property that differs on that test came from
34
+ * an ancestor's cascade. It ranks with `handed` and `provided` because it says
35
+ * the same thing they do: the cause is above this boundary, not in it.
36
+ *
37
+ * Values are digested rather than carried, so that every `MovedInput` means one
38
+ * thing regardless of kind. The property name is the part a reader acts on, and
39
+ * the deltas already name what it turned into on the page.
40
+ *
41
+ * Unpaired boundaries contribute nothing: a component present on one side only
42
+ * has not inherited differently, it has not been compared.
43
+ */
44
+ export declare function cascadeInputs(left: SemanticNode | undefined, right: SemanticNode, there: ReadonlySet<string>, here: ReadonlySet<string>): readonly MovedInput[];
45
+ //# sourceMappingURL=cascade.d.ts.map
@@ -0,0 +1,71 @@
1
+ import { digestValue } from '../format/hash.js';
2
+ /**
3
+ * The input nobody passes.
4
+ *
5
+ * Every rung in `parting.ts` above this one reads something a boundary
6
+ * *received* — a prop, a context, a hook cell — and all three arrive through a
7
+ * framework adapter. This one reads what the boundary did not receive and did
8
+ * not declare, out of the snapshot itself, which is why it is the only rung a
9
+ * run with no adapter attached can reach.
10
+ */
11
+ /**
12
+ * Every property some declaration set *at* a node, keyed by node and name.
13
+ *
14
+ * `styleProvenance` records where each winning declaration came from, so the
15
+ * properties it does not mention at a node are exactly the ones that arrived
16
+ * from somewhere else — which is the whole reading below.
17
+ */
18
+ export function declaredIn(snapshot) {
19
+ return new Set(snapshot.styleProvenance.map(keyOf));
20
+ }
21
+ function keyOf(entry) {
22
+ return `${entry.path}\u0000${entry.property}`;
23
+ }
24
+ /**
25
+ * What an ancestor decided, and the rung that stops this being called a flake.
26
+ *
27
+ * A component that sets its own font and no colour paints white on a dark panel
28
+ * and near-black on a light card from one set of props. Every input the holding
29
+ * carries — props, contexts, hook cells — is byte-identical across the two, so
30
+ * before this existed the boundary settled at `undetermined` and `explainParting`
31
+ * called a perfectly deterministic component *nondeterministic*. That is the
32
+ * accusation ADR-0002 forbids making about an input nobody read, and the input
33
+ * was there to be read: the resolved value is on the node and `styleProvenance`
34
+ * says no declaration here produced it.
35
+ *
36
+ * So the reading is the difference between those two facts — **in the style,
37
+ * not in the provenance** — and a property that differs on that test came from
38
+ * an ancestor's cascade. It ranks with `handed` and `provided` because it says
39
+ * the same thing they do: the cause is above this boundary, not in it.
40
+ *
41
+ * Values are digested rather than carried, so that every `MovedInput` means one
42
+ * thing regardless of kind. The property name is the part a reader acts on, and
43
+ * the deltas already name what it turned into on the page.
44
+ *
45
+ * Unpaired boundaries contribute nothing: a component present on one side only
46
+ * has not inherited differently, it has not been compared.
47
+ */
48
+ export function cascadeInputs(left, right, there, here) {
49
+ if (left === undefined)
50
+ return [];
51
+ const before = inheritedAt(left, there);
52
+ const after = inheritedAt(right, here);
53
+ const moved = [];
54
+ for (const name of [...new Set([...before.keys(), ...after.keys()])].sort()) {
55
+ const from = before.get(name);
56
+ const to = after.get(name);
57
+ if (from !== to)
58
+ moved.push({ kind: 'inherited', name, from, to });
59
+ }
60
+ return moved;
61
+ }
62
+ function inheritedAt(node, declared) {
63
+ const found = new Map();
64
+ for (const [property, value] of Object.entries(node.style)) {
65
+ if (declared.has(`${node.path}\u0000${property}`))
66
+ continue;
67
+ found.set(property, digestValue(value));
68
+ }
69
+ return found;
70
+ }
71
+ //# sourceMappingURL=cascade.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cascade.js","sourceRoot":"","sources":["../../src/compare/cascade.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAe,MAAM,mBAAmB,CAAC;AAI7D;;;;;;;;GAQG;AAEH;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CAAC,QAA0B;IACnD,OAAO,IAAI,GAAG,CAAC,QAAQ,CAAC,eAAe,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;AACtD,CAAC;AAED,SAAS,KAAK,CAAC,KAA2B;IACxC,OAAO,GAAG,KAAK,CAAC,IAAI,SAAS,KAAK,CAAC,QAAQ,EAAE,CAAC;AAChD,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,UAAU,aAAa,CAC3B,IAA8B,EAC9B,KAAmB,EACnB,KAA0B,EAC1B,IAAyB;IAEzB,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,EAAE,CAAC;IAElC,MAAM,MAAM,GAAG,WAAW,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IACxC,MAAM,KAAK,GAAG,WAAW,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IACvC,MAAM,KAAK,GAAiB,EAAE,CAAC;IAE/B,KAAK,MAAM,IAAI,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,IAAI,EAAE,EAAE,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;QAC5E,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC9B,MAAM,EAAE,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC3B,IAAI,IAAI,KAAK,EAAE;YAAE,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;IACrE,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,WAAW,CAAC,IAAkB,EAAE,QAA6B;IACpE,MAAM,KAAK,GAAG,IAAI,GAAG,EAAkB,CAAC;IACxC,KAAK,MAAM,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAC3D,IAAI,QAAQ,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,IAAI,SAAS,QAAQ,EAAE,CAAC;YAAE,SAAS;QAC5D,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC;IAC1C,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC","sourcesContent":["import { digestValue, type Digest } from '../format/hash.js';\nimport type { SemanticNode, SemanticSnapshot, StyleProvenanceEntry } from '../format/snapshot.js';\nimport type { MovedInput } from './parting.js';\n\n/**\n * The input nobody passes.\n *\n * Every rung in `parting.ts` above this one reads something a boundary\n * *received* — a prop, a context, a hook cell — and all three arrive through a\n * framework adapter. This one reads what the boundary did not receive and did\n * not declare, out of the snapshot itself, which is why it is the only rung a\n * run with no adapter attached can reach.\n */\n\n/**\n * Every property some declaration set *at* a node, keyed by node and name.\n *\n * `styleProvenance` records where each winning declaration came from, so the\n * properties it does not mention at a node are exactly the ones that arrived\n * from somewhere else — which is the whole reading below.\n */\nexport function declaredIn(snapshot: SemanticSnapshot): ReadonlySet<string> {\n return new Set(snapshot.styleProvenance.map(keyOf));\n}\n\nfunction keyOf(entry: StyleProvenanceEntry): string {\n return `${entry.path}\\u0000${entry.property}`;\n}\n\n/**\n * What an ancestor decided, and the rung that stops this being called a flake.\n *\n * A component that sets its own font and no colour paints white on a dark panel\n * and near-black on a light card from one set of props. Every input the holding\n * carries — props, contexts, hook cells — is byte-identical across the two, so\n * before this existed the boundary settled at `undetermined` and `explainParting`\n * called a perfectly deterministic component *nondeterministic*. That is the\n * accusation ADR-0002 forbids making about an input nobody read, and the input\n * was there to be read: the resolved value is on the node and `styleProvenance`\n * says no declaration here produced it.\n *\n * So the reading is the difference between those two facts — **in the style,\n * not in the provenance** — and a property that differs on that test came from\n * an ancestor's cascade. It ranks with `handed` and `provided` because it says\n * the same thing they do: the cause is above this boundary, not in it.\n *\n * Values are digested rather than carried, so that every `MovedInput` means one\n * thing regardless of kind. The property name is the part a reader acts on, and\n * the deltas already name what it turned into on the page.\n *\n * Unpaired boundaries contribute nothing: a component present on one side only\n * has not inherited differently, it has not been compared.\n */\nexport function cascadeInputs(\n left: SemanticNode | undefined,\n right: SemanticNode,\n there: ReadonlySet<string>,\n here: ReadonlySet<string>,\n): readonly MovedInput[] {\n if (left === undefined) return [];\n\n const before = inheritedAt(left, there);\n const after = inheritedAt(right, here);\n const moved: MovedInput[] = [];\n\n for (const name of [...new Set([...before.keys(), ...after.keys()])].sort()) {\n const from = before.get(name);\n const to = after.get(name);\n if (from !== to) moved.push({ kind: 'inherited', name, from, to });\n }\n\n return moved;\n}\n\nfunction inheritedAt(node: SemanticNode, declared: ReadonlySet<string>): Map<string, Digest> {\n const found = new Map<string, Digest>();\n for (const [property, value] of Object.entries(node.style)) {\n if (declared.has(`${node.path}\\u0000${property}`)) continue;\n found.set(property, digestValue(value));\n }\n return found;\n}\n"]}
@@ -0,0 +1,78 @@
1
+ import { type Digest } from '../format/hash.js';
2
+ import type { SemanticSnapshot } from '../format/snapshot.js';
3
+ import type { EnvironmentDelta } from '../format/environment.js';
4
+ import { type Band } from './band.js';
5
+ import type { AggregateImpact } from './impact.js';
6
+ import { type ChangedComponent, type Delta, type Root } from './diff/index.js';
7
+ /**
8
+ * One subject read as a variation of another.
9
+ *
10
+ * The same arithmetic a verdict is made of, pointed at a pair of subjects
11
+ * somebody linked on purpose: a dark story against its light parent, a mobile
12
+ * viewport against the desktop one, a story behind a feature flag against the
13
+ * story it was copied from. Nothing here is compared against a baseline and
14
+ * nothing here is a verdict — a variation *is* a difference, so reporting it as a
15
+ * regression would be reporting the subject for existing.
16
+ *
17
+ * What it answers is the question a second case cannot: **a new story under a
18
+ * flag is a new baseline and an empty diff, and everybody has to open two
19
+ * pictures to see what the flag does.** Linked, the difference is a value with an
20
+ * identity, and the identity is the useful part — see {@link Variation.digest}.
21
+ */
22
+ export interface Variation {
23
+ /** The variation's own subject id. */
24
+ readonly subject: string;
25
+ /** The subject it declares itself a variation of. */
26
+ readonly parent: string;
27
+ /** `true` when the two renders hash identically — the flag changed nothing. */
28
+ readonly identical: boolean;
29
+ readonly deltas: readonly Delta[];
30
+ readonly roots: readonly Root[];
31
+ readonly components: readonly ChangedComponent[];
32
+ readonly impact: AggregateImpact;
33
+ /** Bands the difference falls in, in band order. Empty when identical. */
34
+ readonly bands: readonly Band[];
35
+ /**
36
+ * Bands **either** side's profile could not decide.
37
+ *
38
+ * Union rather than intersection, and never dropped: a jsdom side has no
39
+ * geometry, and a variation that reported no geometry difference because
40
+ * nothing could measure one would be the false `unchanged` this project exists
41
+ * to refuse (ADR-0002).
42
+ */
43
+ readonly unobserved: readonly Band[];
44
+ /**
45
+ * Render inputs that differ between the two sides.
46
+ *
47
+ * Reported, never a refusal. A variation is frequently a *deliberate*
48
+ * environment difference — a viewport, a colour scheme — and the field that
49
+ * would make `diffSnapshots` refuse the pair is here the thing being examined.
50
+ */
51
+ readonly environmentDeltas: readonly EnvironmentDelta[];
52
+ /**
53
+ * The identity of the difference itself: `variation/v1` over the deltas.
54
+ *
55
+ * The field that makes a linked pair worth more than two pictures. A parent and
56
+ * its variation both move by the same edit — a token changed, both re-render,
57
+ * both go red — and this digest does not move, because what the variation *adds*
58
+ * to its parent is unchanged. So the reviewer's question stops being "did these
59
+ * two subjects change" and becomes "did what this flag does change", which is
60
+ * the question they were trying to ask.
61
+ *
62
+ * Positions and values are in it and owner chains are not. An owner frame
63
+ * carries a props digest per frame, so hashing it would make the identity move
64
+ * whenever any component above the change was handed a different object —
65
+ * which is a fact about the render, not about the difference.
66
+ */
67
+ readonly digest: Digest;
68
+ }
69
+ /**
70
+ * Compare a variation against the subject it derives from.
71
+ *
72
+ * Refuses nothing. Two subject ids are the input this function exists for, and
73
+ * two profiles or two environments are reported as part of the answer — see
74
+ * `compareTrees` for why the refusals live with the caller that owes them rather
75
+ * than with the arithmetic.
76
+ */
77
+ export declare function deriveVariation(parent: SemanticSnapshot, variant: SemanticSnapshot): Variation;
78
+ //# sourceMappingURL=derive.d.ts.map
@@ -0,0 +1,39 @@
1
+ import { digestCombine, digestValue } from '../format/hash.js';
2
+ import { BANDS } from './band.js';
3
+ import { compareTrees } from './diff/index.js';
4
+ /**
5
+ * Compare a variation against the subject it derives from.
6
+ *
7
+ * Refuses nothing. Two subject ids are the input this function exists for, and
8
+ * two profiles or two environments are reported as part of the answer — see
9
+ * `compareTrees` for why the refusals live with the caller that owes them rather
10
+ * than with the arithmetic.
11
+ */
12
+ export function deriveVariation(parent, variant) {
13
+ const compared = compareTrees(parent, variant);
14
+ const moved = new Set(compared.deltas.map((delta) => delta.band));
15
+ return {
16
+ subject: variant.subject.id,
17
+ parent: parent.subject.id,
18
+ ...compared,
19
+ bands: BANDS.filter((band) => moved.has(band)),
20
+ digest: digestOfDifference(compared.deltas),
21
+ };
22
+ }
23
+ /**
24
+ * `variation/v1` over what each delta *is*, in the order the comparison produced.
25
+ *
26
+ * Order is the comparison's, which is a function of the two trees and of nothing
27
+ * else — sorting here would cost a walk to buy stability the input already has.
28
+ */
29
+ function digestOfDifference(deltas) {
30
+ return digestCombine('variation/v1', deltas.map((delta) => digestValue({
31
+ kind: delta.kind,
32
+ band: delta.band,
33
+ path: delta.path,
34
+ property: delta.property,
35
+ from: delta.from,
36
+ to: delta.to,
37
+ })));
38
+ }
39
+ //# sourceMappingURL=derive.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"derive.js","sourceRoot":"","sources":["../../src/compare/derive.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,WAAW,EAAe,MAAM,mBAAmB,CAAC;AAG5E,OAAO,EAAE,KAAK,EAAa,MAAM,WAAW,CAAC;AAE7C,OAAO,EAAE,YAAY,EAAgD,MAAM,iBAAiB,CAAC;AAuE7F;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAAC,MAAwB,EAAE,OAAyB;IACjF,MAAM,QAAQ,GAAG,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/C,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;IAElE,OAAO;QACL,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,EAAE;QAC3B,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC,EAAE;QACzB,GAAG,QAAQ;QACX,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC9C,MAAM,EAAE,kBAAkB,CAAC,QAAQ,CAAC,MAAM,CAAC;KAC5C,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,SAAS,kBAAkB,CAAC,MAAwB;IAClD,OAAO,aAAa,CAClB,cAAc,EACd,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CACnB,WAAW,CAAC;QACV,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,EAAE,EAAE,KAAK,CAAC,EAAE;KACb,CAAC,CACH,CACF,CAAC;AACJ,CAAC","sourcesContent":["import { digestCombine, digestValue, type Digest } from '../format/hash.js';\nimport type { SemanticSnapshot } from '../format/snapshot.js';\nimport type { EnvironmentDelta } from '../format/environment.js';\nimport { BANDS, type Band } from './band.js';\nimport type { AggregateImpact } from './impact.js';\nimport { compareTrees, type ChangedComponent, type Delta, type Root } from './diff/index.js';\n\n/**\n * One subject read as a variation of another.\n *\n * The same arithmetic a verdict is made of, pointed at a pair of subjects\n * somebody linked on purpose: a dark story against its light parent, a mobile\n * viewport against the desktop one, a story behind a feature flag against the\n * story it was copied from. Nothing here is compared against a baseline and\n * nothing here is a verdict — a variation *is* a difference, so reporting it as a\n * regression would be reporting the subject for existing.\n *\n * What it answers is the question a second case cannot: **a new story under a\n * flag is a new baseline and an empty diff, and everybody has to open two\n * pictures to see what the flag does.** Linked, the difference is a value with an\n * identity, and the identity is the useful part — see {@link Variation.digest}.\n */\nexport interface Variation {\n /** The variation's own subject id. */\n readonly subject: string;\n /** The subject it declares itself a variation of. */\n readonly parent: string;\n\n /** `true` when the two renders hash identically — the flag changed nothing. */\n readonly identical: boolean;\n\n readonly deltas: readonly Delta[];\n readonly roots: readonly Root[];\n readonly components: readonly ChangedComponent[];\n readonly impact: AggregateImpact;\n\n /** Bands the difference falls in, in band order. Empty when identical. */\n readonly bands: readonly Band[];\n\n /**\n * Bands **either** side's profile could not decide.\n *\n * Union rather than intersection, and never dropped: a jsdom side has no\n * geometry, and a variation that reported no geometry difference because\n * nothing could measure one would be the false `unchanged` this project exists\n * to refuse (ADR-0002).\n */\n readonly unobserved: readonly Band[];\n\n /**\n * Render inputs that differ between the two sides.\n *\n * Reported, never a refusal. A variation is frequently a *deliberate*\n * environment difference — a viewport, a colour scheme — and the field that\n * would make `diffSnapshots` refuse the pair is here the thing being examined.\n */\n readonly environmentDeltas: readonly EnvironmentDelta[];\n\n /**\n * The identity of the difference itself: `variation/v1` over the deltas.\n *\n * The field that makes a linked pair worth more than two pictures. A parent and\n * its variation both move by the same edit — a token changed, both re-render,\n * both go red — and this digest does not move, because what the variation *adds*\n * to its parent is unchanged. So the reviewer's question stops being \"did these\n * two subjects change\" and becomes \"did what this flag does change\", which is\n * the question they were trying to ask.\n *\n * Positions and values are in it and owner chains are not. An owner frame\n * carries a props digest per frame, so hashing it would make the identity move\n * whenever any component above the change was handed a different object —\n * which is a fact about the render, not about the difference.\n */\n readonly digest: Digest;\n}\n\n/**\n * Compare a variation against the subject it derives from.\n *\n * Refuses nothing. Two subject ids are the input this function exists for, and\n * two profiles or two environments are reported as part of the answer — see\n * `compareTrees` for why the refusals live with the caller that owes them rather\n * than with the arithmetic.\n */\nexport function deriveVariation(parent: SemanticSnapshot, variant: SemanticSnapshot): Variation {\n const compared = compareTrees(parent, variant);\n const moved = new Set(compared.deltas.map((delta) => delta.band));\n\n return {\n subject: variant.subject.id,\n parent: parent.subject.id,\n ...compared,\n bands: BANDS.filter((band) => moved.has(band)),\n digest: digestOfDifference(compared.deltas),\n };\n}\n\n/**\n * `variation/v1` over what each delta *is*, in the order the comparison produced.\n *\n * Order is the comparison's, which is a function of the two trees and of nothing\n * else — sorting here would cost a walk to buy stability the input already has.\n */\nfunction digestOfDifference(deltas: readonly Delta[]): Digest {\n return digestCombine(\n 'variation/v1',\n deltas.map((delta) =>\n digestValue({\n kind: delta.kind,\n band: delta.band,\n path: delta.path,\n property: delta.property,\n from: delta.from,\n to: delta.to,\n }),\n ),\n );\n}\n"]}
@@ -0,0 +1,23 @@
1
+ import type { EnvironmentDelta } from '../../format/environment.js';
2
+ import { type Delta, type Root } from './delta.js';
3
+ import type { Matching } from './match.js';
4
+ /**
5
+ * Grouping deltas into explanations — the docket's entire value proposition.
6
+ *
7
+ * Separated from `index.ts` because it is the half of diffing that has nothing
8
+ * to do with observation: it takes deltas that already exist and decides which
9
+ * *one* sentence covers them. The precedence rules, the metric-delta folding and
10
+ * the boundary walk are all in service of the same claim — report the largest
11
+ * explanation once rather than the smallest one many times — and they only make
12
+ * sense read together.
13
+ */
14
+ /**
15
+ * Group deltas into explanations (spec §6.2).
16
+ *
17
+ * Precedence is fixed and deliberate: environment, then token, then props, then
18
+ * component. Each level explains more subjects than the one below it, and the
19
+ * docket's value is entirely in reporting the *largest* explanation once rather
20
+ * than the smallest one many times.
21
+ */
22
+ export declare function attribute(deltas: readonly Delta[], matching: Matching, environmentDeltas: readonly EnvironmentDelta[]): readonly Root[];
23
+ //# sourceMappingURL=attribution.d.ts.map