@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,182 @@
1
+ import { canonicalize } from './canonical.js';
2
+ import { digestCombine, digestString } from './hash.js';
3
+ /**
4
+ * A value that was never rendered, as something a run can compare (spec 0031).
5
+ *
6
+ * The third material. It carries **text**, not a parse tree, for the reason the
7
+ * raster arm carries bytes: every reader that understands a dialect is somebody
8
+ * else's dependency, `core` carries none, and a stored parse is a parse that
9
+ * reader's next version disagrees with. The text is what the digest was taken
10
+ * over, what a baseline holds, and what a person reads in a pull request.
11
+ *
12
+ * The canonical form is `canonicalize`, unchanged and unwrapped — sorted keys,
13
+ * portable number formatting, `undefined` omitted rather than nulled, and a
14
+ * refusal on a non-finite number. What this module adds is the *shaping* that
15
+ * has to happen before it, because two of the three rules below decide whether
16
+ * the comparison downstream is readable or useless.
17
+ */
18
+ /**
19
+ * The version of the shaping rules, and part of every value digest.
20
+ *
21
+ * Bumping it invalidates stored value baselines deliberately: a text produced
22
+ * under different rules is not the same reading of the same subject, and a
23
+ * digest that could not tell the difference would report a rule change as a
24
+ * subject that did not move.
25
+ */
26
+ export const VALUE_RECIPE = 'value/1';
27
+ /** What a dropped value becomes. Present in the text, never compared. */
28
+ export const DROPPED = '[dropped]';
29
+ /**
30
+ * Shape a value, canonicalize it, and address it by content.
31
+ *
32
+ * Throws on anything that cannot be a value rather than encoding it: a function,
33
+ * a `Date`, a `bigint`, a non-finite number. Each refusal names the JSON Pointer
34
+ * where it was found, because the alternative — `JSON.stringify`'s — is to drop
35
+ * a function silently, which reads downstream as a key that was removed.
36
+ */
37
+ export function shapeValue(value, options = {}) {
38
+ const shaped = shapeNode(value, '', options);
39
+ const text = canonicalize(shaped);
40
+ const keyed = Object.keys(options.arrayKey ?? {})
41
+ .map((pointer) => pointerShape(pointer))
42
+ .sort();
43
+ return {
44
+ dialect: options.dialect ?? 'json',
45
+ text,
46
+ digest: digestCombine('value/v1', [digestString(text), digestString(VALUE_RECIPE)]),
47
+ recipe: VALUE_RECIPE,
48
+ ...(keyed.length === 0 ? {} : { keyed }),
49
+ ...(options.generator === undefined ? {} : { generator: options.generator }),
50
+ };
51
+ }
52
+ /** RFC 6901: `~` becomes `~0` and `/` becomes `~1`, in that order. */
53
+ export function pointerToken(token) {
54
+ return token.replace(/~/g, '~0').replace(/\//g, '~1');
55
+ }
56
+ /**
57
+ * The pointer with every row identity replaced by `-`.
58
+ *
59
+ * Two things: it is what a wildcard rule is matched against, and it is what a
60
+ * change's fingerprint is taken over — so the same kind of edit at row 4 and at
61
+ * row 900 is one shape happening twice rather than two findings.
62
+ *
63
+ * An index is a row identity by spelling. A **keyed** array's members are named
64
+ * instead, and no spelling can distinguish those from an ordinary object's keys —
65
+ * so `keyed` is passed in from the capture that declared it, and only the segment
66
+ * immediately below a keyed array is wildcarded.
67
+ */
68
+ export function pointerShape(pointer, keyed = []) {
69
+ const shaped = pointer.replace(/\/\d+(?=\/|$)/g, '/-');
70
+ return keyed.reduce((current, array) => {
71
+ if (!current.startsWith(`${array}/`))
72
+ return current;
73
+ const rest = current.slice(array.length + 1);
74
+ const cut = rest.indexOf('/');
75
+ return cut === -1 ? `${array}/-` : `${array}/-${rest.slice(cut)}`;
76
+ }, shaped);
77
+ }
78
+ function matches(rules, pointer) {
79
+ return rules.includes(pointer) || rules.includes(pointerShape(pointer));
80
+ }
81
+ function ruleFor(rules, pointer) {
82
+ if (rules === undefined)
83
+ return undefined;
84
+ return rules[pointer] ?? rules[pointerShape(pointer)];
85
+ }
86
+ function shapeNode(value, pointer, options) {
87
+ if (matches(options.drop ?? [], pointer))
88
+ return DROPPED;
89
+ const replaced = ruleFor(options.replace, pointer);
90
+ if (replaced !== undefined)
91
+ return replaced;
92
+ if (value === null)
93
+ return null;
94
+ switch (typeof value) {
95
+ case 'string':
96
+ case 'boolean':
97
+ return value;
98
+ case 'number':
99
+ if (!Number.isFinite(value)) {
100
+ throw new RangeError(`${describe(pointer)} is ${String(value)}, which no digest may encode: a non-finite ` +
101
+ 'number means the thing that produced it failed, and hashing it would give a broken ' +
102
+ 'reading a stable identity');
103
+ }
104
+ return value;
105
+ case 'object':
106
+ break;
107
+ default:
108
+ throw new TypeError(`${describe(pointer)} is a ${typeof value}, which is not a value. ` +
109
+ 'Serializing it would drop it silently, and a key that vanished from the text is ' +
110
+ 'read downstream as a key that was removed.');
111
+ }
112
+ if (Array.isArray(value))
113
+ return shapeArray(value, pointer, options);
114
+ if (opaque(value)) {
115
+ throw new TypeError(`${describe(pointer)} is a ${nameOf(value)}, which is not a value. Its state is not in ` +
116
+ 'its own enumerable keys, so it would serialize to `{}` and two different ones would ' +
117
+ 'compare as unchanged. Convert it where it is produced — an ISO string, a number, a ' +
118
+ 'plain object — so the text says what was compared.');
119
+ }
120
+ const shaped = {};
121
+ for (const [key, member] of Object.entries(value)) {
122
+ if (member === undefined)
123
+ continue;
124
+ shaped[key] = shapeNode(member, `${pointer}/${pointerToken(key)}`, options);
125
+ }
126
+ return shaped;
127
+ }
128
+ /**
129
+ * Whether an object keeps its state somewhere `Object.entries` cannot reach.
130
+ *
131
+ * A `Date`, a `Map`, a `Set`, a `URL`, a `RegExp` — each has no own enumerable
132
+ * key, so the object branch below produces `{}` for every one of them. That is
133
+ * the exact harm the function and bigint refusals exist to prevent, arriving
134
+ * through the one `typeof` that does not name it: a timestamp that moved a year
135
+ * and a timestamp that did not both address to the digest of `{}`, and the run
136
+ * that compares them reports unchanged.
137
+ *
138
+ * Emptiness alone is not the test — `{}` is a value, and an adopter may legitimately
139
+ * snapshot one. The test is emptiness in something that is not a plain object,
140
+ * which is what says the state went somewhere else. An instance carrying its own
141
+ * fields serializes those fields and is left alone, exactly as `JSON.stringify`
142
+ * would leave it.
143
+ */
144
+ function opaque(value) {
145
+ if (Object.keys(value).length > 0)
146
+ return false;
147
+ const prototype = Object.getPrototypeOf(value);
148
+ return prototype !== Object.prototype && prototype !== null;
149
+ }
150
+ /** What to call the thing in the refusal, so the message names the type at hand. */
151
+ function nameOf(value) {
152
+ const named = value.constructor?.name;
153
+ return typeof named === 'string' && named.length > 0 ? named : 'object';
154
+ }
155
+ function shapeArray(items, pointer, options) {
156
+ const key = ruleFor(options.arrayKey, pointer);
157
+ if (key === undefined) {
158
+ return items.map((item, index) => shapeNode(item, `${pointer}/${index}`, options));
159
+ }
160
+ const keyed = {};
161
+ items.forEach((item, index) => {
162
+ const at = `${pointer}/${index}`;
163
+ if (typeof item !== 'object' || item === null || Array.isArray(item)) {
164
+ throw new TypeError(`${describe(at)} is keyed by \`${key}\` and is not a record`);
165
+ }
166
+ const member = item[key];
167
+ if (typeof member !== 'string' && typeof member !== 'number') {
168
+ throw new TypeError(`${describe(at)} has no \`${key}\` to be identified by`);
169
+ }
170
+ const identity = String(member);
171
+ if (identity in keyed) {
172
+ throw new Error(`${describe(pointer)} has two rows with \`${key}\` of ${identity}. A key that does not ` +
173
+ 'identify a row would silently take the later one, making the earlier row invisible.');
174
+ }
175
+ keyed[identity] = shapeNode(item, `${pointer}/${pointerToken(identity)}`, options);
176
+ });
177
+ return keyed;
178
+ }
179
+ function describe(pointer) {
180
+ return pointer === '' ? 'the value' : `the value at ${pointer}`;
181
+ }
182
+ //# sourceMappingURL=value.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"value.js","sourceRoot":"","sources":["../../src/format/value.ts"],"names":[],"mappings":"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","sourcesContent":["import { canonicalize, type CanonicalValue } from './canonical.js';\nimport { digestCombine, digestString, type Digest } from './hash.js';\n\n/**\n * A value that was never rendered, as something a run can compare (spec 0031).\n *\n * The third material. It carries **text**, not a parse tree, for the reason the\n * raster arm carries bytes: every reader that understands a dialect is somebody\n * else's dependency, `core` carries none, and a stored parse is a parse that\n * reader's next version disagrees with. The text is what the digest was taken\n * over, what a baseline holds, and what a person reads in a pull request.\n *\n * The canonical form is `canonicalize`, unchanged and unwrapped — sorted keys,\n * portable number formatting, `undefined` omitted rather than nulled, and a\n * refusal on a non-finite number. What this module adds is the *shaping* that\n * has to happen before it, because two of the three rules below decide whether\n * the comparison downstream is readable or useless.\n */\n\n/**\n * The version of the shaping rules, and part of every value digest.\n *\n * Bumping it invalidates stored value baselines deliberately: a text produced\n * under different rules is not the same reading of the same subject, and a\n * digest that could not tell the difference would report a rule change as a\n * subject that did not move.\n */\nexport const VALUE_RECIPE = 'value/1';\n\n/** What a dropped value becomes. Present in the text, never compared. */\nexport const DROPPED = '[dropped]';\n\n/** What a value capture looks like once it has been shaped. */\nexport interface CapturedValue {\n /** How to read the text: `json`, `openapi`, `graphql`, `route-table`. */\n readonly dialect: string;\n\n /** The canonical serialization — byte for byte what the digest was taken over. */\n readonly text: string;\n\n readonly digest: Digest;\n\n /** The shaping rules this text was produced under. {@link VALUE_RECIPE}. */\n readonly recipe: string;\n\n /**\n * The arrays this text was keyed by, as wildcarded pointers.\n *\n * A keyed array is an object whose members are named by a row's identity, so\n * `/rows/checkout` is a *row* and `/rows/total` might not be. Carried because\n * nothing downstream can tell those apart by looking, and a comparison that\n * guessed would either group two unrelated members or fail to group the same\n * edit made to two rows.\n */\n readonly keyed?: readonly string[];\n\n /** What emitted it. The environment key for this material, when there is one. */\n readonly generator?: { readonly name: string; readonly version: string };\n}\n\n/**\n * What to do to a value before it is canonicalized.\n *\n * Every key is a JSON Pointer (RFC 6901) into the value. A `-` token matches any\n * array index, so `/items/-/updatedAt` names that member of every item — the\n * concrete pointer and the wildcarded one are both offered to the rules, so a\n * literal `/items/0/updatedAt` still names exactly one.\n */\nexport interface ValueShaping {\n /**\n * Paths whose values are volatile.\n *\n * Recorded as {@link DROPPED} rather than removed, because *this key is here\n * and I chose not to look at it* and *this key is gone* are different facts and\n * a comparison must not collapse them.\n */\n readonly drop?: readonly string[];\n\n /** Paths whose values become a stable token of the adopter's choosing. */\n readonly replace?: Readonly<Record<string, string>>;\n\n /**\n * For an array of records, the member that identifies a row.\n *\n * Load-bearing, and not a convenience. An array compared by index reports a row\n * inserted at the top of a two-thousand-row list as two thousand rows having\n * changed — the same failure as forty red screenshots for one edit, in a medium\n * where nobody can see it at a glance. Keyed, the array becomes an object, so\n * order stops being a fact about it and an insertion is one addition.\n */\n readonly arrayKey?: Readonly<Record<string, string>>;\n}\n\nexport interface ShapeValueOptions extends ValueShaping {\n /** Defaults to `json`. */\n readonly dialect?: string;\n readonly generator?: { readonly name: string; readonly version: string };\n}\n\n/**\n * Shape a value, canonicalize it, and address it by content.\n *\n * Throws on anything that cannot be a value rather than encoding it: a function,\n * a `Date`, a `bigint`, a non-finite number. Each refusal names the JSON Pointer\n * where it was found, because the alternative — `JSON.stringify`'s — is to drop\n * a function silently, which reads downstream as a key that was removed.\n */\nexport function shapeValue(value: unknown, options: ShapeValueOptions = {}): CapturedValue {\n const shaped = shapeNode(value, '', options);\n const text = canonicalize(shaped);\n\n const keyed = Object.keys(options.arrayKey ?? {})\n .map((pointer) => pointerShape(pointer))\n .sort();\n\n return {\n dialect: options.dialect ?? 'json',\n text,\n digest: digestCombine('value/v1', [digestString(text), digestString(VALUE_RECIPE)]),\n recipe: VALUE_RECIPE,\n ...(keyed.length === 0 ? {} : { keyed }),\n ...(options.generator === undefined ? {} : { generator: options.generator }),\n };\n}\n\n/** RFC 6901: `~` becomes `~0` and `/` becomes `~1`, in that order. */\nexport function pointerToken(token: string): string {\n return token.replace(/~/g, '~0').replace(/\\//g, '~1');\n}\n\n/**\n * The pointer with every row identity replaced by `-`.\n *\n * Two things: it is what a wildcard rule is matched against, and it is what a\n * change's fingerprint is taken over — so the same kind of edit at row 4 and at\n * row 900 is one shape happening twice rather than two findings.\n *\n * An index is a row identity by spelling. A **keyed** array's members are named\n * instead, and no spelling can distinguish those from an ordinary object's keys —\n * so `keyed` is passed in from the capture that declared it, and only the segment\n * immediately below a keyed array is wildcarded.\n */\nexport function pointerShape(pointer: string, keyed: readonly string[] = []): string {\n const shaped = pointer.replace(/\\/\\d+(?=\\/|$)/g, '/-');\n\n return keyed.reduce((current, array) => {\n if (!current.startsWith(`${array}/`)) return current;\n const rest = current.slice(array.length + 1);\n const cut = rest.indexOf('/');\n return cut === -1 ? `${array}/-` : `${array}/-${rest.slice(cut)}`;\n }, shaped);\n}\n\nfunction matches(rules: readonly string[], pointer: string): boolean {\n return rules.includes(pointer) || rules.includes(pointerShape(pointer));\n}\n\nfunction ruleFor<T>(rules: Readonly<Record<string, T>> | undefined, pointer: string): T | undefined {\n if (rules === undefined) return undefined;\n return rules[pointer] ?? rules[pointerShape(pointer)];\n}\n\nfunction shapeNode(value: unknown, pointer: string, options: ValueShaping): CanonicalValue {\n if (matches(options.drop ?? [], pointer)) return DROPPED;\n\n const replaced = ruleFor(options.replace, pointer);\n if (replaced !== undefined) return replaced;\n\n if (value === null) return null;\n\n switch (typeof value) {\n case 'string':\n case 'boolean':\n return value;\n case 'number':\n if (!Number.isFinite(value)) {\n throw new RangeError(\n `${describe(pointer)} is ${String(value)}, which no digest may encode: a non-finite ` +\n 'number means the thing that produced it failed, and hashing it would give a broken ' +\n 'reading a stable identity',\n );\n }\n return value;\n case 'object':\n break;\n default:\n throw new TypeError(\n `${describe(pointer)} is a ${typeof value}, which is not a value. ` +\n 'Serializing it would drop it silently, and a key that vanished from the text is ' +\n 'read downstream as a key that was removed.',\n );\n }\n\n if (Array.isArray(value)) return shapeArray(value, pointer, options);\n\n if (opaque(value)) {\n throw new TypeError(\n `${describe(pointer)} is a ${nameOf(value)}, which is not a value. Its state is not in ` +\n 'its own enumerable keys, so it would serialize to `{}` and two different ones would ' +\n 'compare as unchanged. Convert it where it is produced — an ISO string, a number, a ' +\n 'plain object — so the text says what was compared.',\n );\n }\n\n const shaped: Record<string, CanonicalValue> = {};\n for (const [key, member] of Object.entries(value as Record<string, unknown>)) {\n if (member === undefined) continue;\n shaped[key] = shapeNode(member, `${pointer}/${pointerToken(key)}`, options);\n }\n return shaped;\n}\n\n/**\n * Whether an object keeps its state somewhere `Object.entries` cannot reach.\n *\n * A `Date`, a `Map`, a `Set`, a `URL`, a `RegExp` — each has no own enumerable\n * key, so the object branch below produces `{}` for every one of them. That is\n * the exact harm the function and bigint refusals exist to prevent, arriving\n * through the one `typeof` that does not name it: a timestamp that moved a year\n * and a timestamp that did not both address to the digest of `{}`, and the run\n * that compares them reports unchanged.\n *\n * Emptiness alone is not the test — `{}` is a value, and an adopter may legitimately\n * snapshot one. The test is emptiness in something that is not a plain object,\n * which is what says the state went somewhere else. An instance carrying its own\n * fields serializes those fields and is left alone, exactly as `JSON.stringify`\n * would leave it.\n */\nfunction opaque(value: object): boolean {\n if (Object.keys(value).length > 0) return false;\n const prototype = Object.getPrototypeOf(value);\n return prototype !== Object.prototype && prototype !== null;\n}\n\n/** What to call the thing in the refusal, so the message names the type at hand. */\nfunction nameOf(value: object): string {\n const named = value.constructor?.name;\n return typeof named === 'string' && named.length > 0 ? named : 'object';\n}\n\nfunction shapeArray(\n items: readonly unknown[],\n pointer: string,\n options: ValueShaping,\n): CanonicalValue {\n const key = ruleFor(options.arrayKey, pointer);\n if (key === undefined) {\n return items.map((item, index) => shapeNode(item, `${pointer}/${index}`, options));\n }\n\n const keyed: Record<string, CanonicalValue> = {};\n items.forEach((item, index) => {\n const at = `${pointer}/${index}`;\n if (typeof item !== 'object' || item === null || Array.isArray(item)) {\n throw new TypeError(`${describe(at)} is keyed by \\`${key}\\` and is not a record`);\n }\n const member = (item as Record<string, unknown>)[key];\n if (typeof member !== 'string' && typeof member !== 'number') {\n throw new TypeError(`${describe(at)} has no \\`${key}\\` to be identified by`);\n }\n const identity = String(member);\n if (identity in keyed) {\n throw new Error(\n `${describe(pointer)} has two rows with \\`${key}\\` of ${identity}. A key that does not ` +\n 'identify a row would silently take the later one, making the earlier row invisible.',\n );\n }\n keyed[identity] = shapeNode(item, `${pointer}/${pointerToken(identity)}`, options);\n });\n return keyed;\n}\n\nfunction describe(pointer: string): string {\n return pointer === '' ? 'the value' : `the value at ${pointer}`;\n}\n"]}
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Wiring: how a component is attached to its framework, as a dimension of its own.
3
+ *
4
+ * The other bands read the artefact. `structure` reads the tree the renderer
5
+ * produced, `style` reads the declarations that matched it, `geometry` reads the
6
+ * boxes those declarations made. All three are downstream of a decision that has
7
+ * already been taken, and none of them can reach back past the render to say what
8
+ * the component *is*.
9
+ *
10
+ * Wiring is that reach. Two components can produce byte-identical structure,
11
+ * semantics, text, style and geometry while one of them subscribes to a theme
12
+ * context and the other does not; while one is memoised and the other re-renders
13
+ * on every parent tick; while one keys a list by identity and the other by index,
14
+ * so that the first survives a reorder and the second corrupts. Those are
15
+ * differences in the component, they are the ones that decide how it behaves
16
+ * under change, and no amount of HTML and CSS contains them. They are in the
17
+ * fiber, so the fiber is where they are read from.
18
+ *
19
+ * ## Why this is a wire type in `core` and a reader in `react`
20
+ *
21
+ * Exactly like {@link Provenance}: the collector produces it, it travels with the
22
+ * document over every wire this project has, and a differ on the other end reads
23
+ * it without ever having seen a fiber. A type the snapshot format needs cannot
24
+ * live in the framework adapter, and the adapter is the only thing that should
25
+ * know what a `_debugHookTypes` is.
26
+ *
27
+ * ## What is deliberately not here
28
+ *
29
+ * **State values.** `useState(0)` records `useState`, never `0`. A hook's *value*
30
+ * is the thing that legitimately differs between two readings of one page — it is
31
+ * the ticker, the timestamp, the animation frame — so a band carrying it would be
32
+ * a flake generator wearing a band's name. Wiring carries the shape of the
33
+ * component's attachment and nothing that attachment happened to be holding.
34
+ *
35
+ * **Anything temporal.** Whether an instance *remounted* is the single most useful
36
+ * thing the fiber knows, and it is not in here, because it is not a property of a
37
+ * revision — it is a property of a reading, and by construction it differs between
38
+ * two readings of one unchanged page. It lives in `@variance-authority/react`'s
39
+ * `remounted()` as a finding, next to `pendingSuspense`. The rule that separates
40
+ * them is stated once, in `wiring.test.ts`, and it is checkable: read the same
41
+ * page twice without changing anything, and if the value moved it is not a band.
42
+ */
43
+ /** How the framework holds a component, at one boundary. */
44
+ export interface Wiring {
45
+ /**
46
+ * Hook names in call order: `['useState', 'useRef', 'useEffect']`.
47
+ *
48
+ * Exact rather than inferred. React records the list itself during a
49
+ * development render, so this is the compiler's own account of the component
50
+ * and not a reconstruction from the hook chain's shape — which cannot in
51
+ * principle separate `useMemo` from `useCallback`, or `useEffect` from
52
+ * `useLayoutEffect`, because those pairs build identical memo cells.
53
+ *
54
+ * Absent when the adapter could not see them, and that covers more than a
55
+ * production build: React only populates its record once a hook actually runs,
56
+ * so a component that declares none is indistinguishable from one nobody could
57
+ * read. Absent for both, never `[]` — "declares no hooks" is a positive claim
58
+ * and this observation does not support it (ADR-0002).
59
+ */
60
+ readonly hooks?: readonly string[];
61
+ /**
62
+ * The wrappers the author put around the component, outermost first.
63
+ *
64
+ * `memo` is a claim about when the component may be skipped, and `forwardRef`
65
+ * is a claim about who owns its host node. Both are load-bearing, both are
66
+ * invisible in the output, and both are ordinary things to lose in a refactor:
67
+ * dropping a `memo` changes no pixel and no digest in any other band, and turns
68
+ * a component that rendered once into one that renders on every parent tick.
69
+ *
70
+ * A list rather than one value because `memo(forwardRef(f))` is both, and
71
+ * reporting either alone would say a component lost a wrapper it still has.
72
+ */
73
+ readonly wrappers?: readonly ('memo' | 'forwardRef')[];
74
+ /**
75
+ * Contexts this boundary subscribes to, by display name, sorted.
76
+ *
77
+ * The dependency edge that decides re-renders and that nothing in the document
78
+ * shows. A component under a theme switch that changed without appearing here
79
+ * got its change through the CSS cascade rather than through React, and those
80
+ * are different explanations for the same visual difference.
81
+ *
82
+ * Anonymous contexts are named `(anonymous)` rather than dropped — a
83
+ * subscription nobody named is still a subscription, and dropping it would let
84
+ * a component that gained one compare equal to one that has none.
85
+ */
86
+ readonly contexts?: readonly string[];
87
+ /**
88
+ * The reconciliation key React holds this boundary under, when it has one.
89
+ *
90
+ * Two lists that serialize to the same `<li>a</li><li>b</li>` reconcile
91
+ * differently depending on whether their keys are `'a', 'b'` or `'0', '1'`, and
92
+ * the difference only becomes visible when the list reorders — at which point
93
+ * the index-keyed one has moved every child's state onto the wrong row. The
94
+ * fiber holds the answer before the reorder, so this is a defect readable in a
95
+ * single still reading of a page that currently looks perfect.
96
+ *
97
+ * Absent when React assigned none, which is the ordinary case for a component
98
+ * that is not one of several siblings.
99
+ */
100
+ readonly key?: string;
101
+ }
102
+ /**
103
+ * Whether a key looks positional.
104
+ *
105
+ * A heuristic, and named as one. `key: '0'` on the first child, `'1'` on the
106
+ * second, is what `items.map((item, index) => ...)` produces and is the defect;
107
+ * `key: '0'` on a list of numeric ids is the same string arrived at honestly. The
108
+ * two are indistinguishable at one node and separable across siblings, which is
109
+ * why this takes the whole run of keys rather than one of them.
110
+ *
111
+ * Returns `false` for anything shorter than two siblings: a one-element list
112
+ * keyed `'0'` reorders into nothing and there is no defect to report.
113
+ */
114
+ export declare function keyedByPosition(keys: readonly (string | undefined)[]): boolean;
115
+ //# sourceMappingURL=wiring.d.ts.map
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Wiring: how a component is attached to its framework, as a dimension of its own.
3
+ *
4
+ * The other bands read the artefact. `structure` reads the tree the renderer
5
+ * produced, `style` reads the declarations that matched it, `geometry` reads the
6
+ * boxes those declarations made. All three are downstream of a decision that has
7
+ * already been taken, and none of them can reach back past the render to say what
8
+ * the component *is*.
9
+ *
10
+ * Wiring is that reach. Two components can produce byte-identical structure,
11
+ * semantics, text, style and geometry while one of them subscribes to a theme
12
+ * context and the other does not; while one is memoised and the other re-renders
13
+ * on every parent tick; while one keys a list by identity and the other by index,
14
+ * so that the first survives a reorder and the second corrupts. Those are
15
+ * differences in the component, they are the ones that decide how it behaves
16
+ * under change, and no amount of HTML and CSS contains them. They are in the
17
+ * fiber, so the fiber is where they are read from.
18
+ *
19
+ * ## Why this is a wire type in `core` and a reader in `react`
20
+ *
21
+ * Exactly like {@link Provenance}: the collector produces it, it travels with the
22
+ * document over every wire this project has, and a differ on the other end reads
23
+ * it without ever having seen a fiber. A type the snapshot format needs cannot
24
+ * live in the framework adapter, and the adapter is the only thing that should
25
+ * know what a `_debugHookTypes` is.
26
+ *
27
+ * ## What is deliberately not here
28
+ *
29
+ * **State values.** `useState(0)` records `useState`, never `0`. A hook's *value*
30
+ * is the thing that legitimately differs between two readings of one page — it is
31
+ * the ticker, the timestamp, the animation frame — so a band carrying it would be
32
+ * a flake generator wearing a band's name. Wiring carries the shape of the
33
+ * component's attachment and nothing that attachment happened to be holding.
34
+ *
35
+ * **Anything temporal.** Whether an instance *remounted* is the single most useful
36
+ * thing the fiber knows, and it is not in here, because it is not a property of a
37
+ * revision — it is a property of a reading, and by construction it differs between
38
+ * two readings of one unchanged page. It lives in `@variance-authority/react`'s
39
+ * `remounted()` as a finding, next to `pendingSuspense`. The rule that separates
40
+ * them is stated once, in `wiring.test.ts`, and it is checkable: read the same
41
+ * page twice without changing anything, and if the value moved it is not a band.
42
+ */
43
+ /**
44
+ * Whether a key looks positional.
45
+ *
46
+ * A heuristic, and named as one. `key: '0'` on the first child, `'1'` on the
47
+ * second, is what `items.map((item, index) => ...)` produces and is the defect;
48
+ * `key: '0'` on a list of numeric ids is the same string arrived at honestly. The
49
+ * two are indistinguishable at one node and separable across siblings, which is
50
+ * why this takes the whole run of keys rather than one of them.
51
+ *
52
+ * Returns `false` for anything shorter than two siblings: a one-element list
53
+ * keyed `'0'` reorders into nothing and there is no defect to report.
54
+ */
55
+ export function keyedByPosition(keys) {
56
+ if (keys.length < 2)
57
+ return false;
58
+ return keys.every((key, index) => key === String(index));
59
+ }
60
+ //# sourceMappingURL=wiring.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wiring.js","sourceRoot":"","sources":["../../src/format/wiring.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAiEH;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,eAAe,CAAC,IAAqC;IACnE,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IAClC,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC,GAAG,KAAK,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;AAC3D,CAAC","sourcesContent":["/**\n * Wiring: how a component is attached to its framework, as a dimension of its own.\n *\n * The other bands read the artefact. `structure` reads the tree the renderer\n * produced, `style` reads the declarations that matched it, `geometry` reads the\n * boxes those declarations made. All three are downstream of a decision that has\n * already been taken, and none of them can reach back past the render to say what\n * the component *is*.\n *\n * Wiring is that reach. Two components can produce byte-identical structure,\n * semantics, text, style and geometry while one of them subscribes to a theme\n * context and the other does not; while one is memoised and the other re-renders\n * on every parent tick; while one keys a list by identity and the other by index,\n * so that the first survives a reorder and the second corrupts. Those are\n * differences in the component, they are the ones that decide how it behaves\n * under change, and no amount of HTML and CSS contains them. They are in the\n * fiber, so the fiber is where they are read from.\n *\n * ## Why this is a wire type in `core` and a reader in `react`\n *\n * Exactly like {@link Provenance}: the collector produces it, it travels with the\n * document over every wire this project has, and a differ on the other end reads\n * it without ever having seen a fiber. A type the snapshot format needs cannot\n * live in the framework adapter, and the adapter is the only thing that should\n * know what a `_debugHookTypes` is.\n *\n * ## What is deliberately not here\n *\n * **State values.** `useState(0)` records `useState`, never `0`. A hook's *value*\n * is the thing that legitimately differs between two readings of one page — it is\n * the ticker, the timestamp, the animation frame — so a band carrying it would be\n * a flake generator wearing a band's name. Wiring carries the shape of the\n * component's attachment and nothing that attachment happened to be holding.\n *\n * **Anything temporal.** Whether an instance *remounted* is the single most useful\n * thing the fiber knows, and it is not in here, because it is not a property of a\n * revision — it is a property of a reading, and by construction it differs between\n * two readings of one unchanged page. It lives in `@variance-authority/react`'s\n * `remounted()` as a finding, next to `pendingSuspense`. The rule that separates\n * them is stated once, in `wiring.test.ts`, and it is checkable: read the same\n * page twice without changing anything, and if the value moved it is not a band.\n */\n\n/** How the framework holds a component, at one boundary. */\nexport interface Wiring {\n /**\n * Hook names in call order: `['useState', 'useRef', 'useEffect']`.\n *\n * Exact rather than inferred. React records the list itself during a\n * development render, so this is the compiler's own account of the component\n * and not a reconstruction from the hook chain's shape — which cannot in\n * principle separate `useMemo` from `useCallback`, or `useEffect` from\n * `useLayoutEffect`, because those pairs build identical memo cells.\n *\n * Absent when the adapter could not see them, and that covers more than a\n * production build: React only populates its record once a hook actually runs,\n * so a component that declares none is indistinguishable from one nobody could\n * read. Absent for both, never `[]` — \"declares no hooks\" is a positive claim\n * and this observation does not support it (ADR-0002).\n */\n readonly hooks?: readonly string[];\n\n /**\n * The wrappers the author put around the component, outermost first.\n *\n * `memo` is a claim about when the component may be skipped, and `forwardRef`\n * is a claim about who owns its host node. Both are load-bearing, both are\n * invisible in the output, and both are ordinary things to lose in a refactor:\n * dropping a `memo` changes no pixel and no digest in any other band, and turns\n * a component that rendered once into one that renders on every parent tick.\n *\n * A list rather than one value because `memo(forwardRef(f))` is both, and\n * reporting either alone would say a component lost a wrapper it still has.\n */\n readonly wrappers?: readonly ('memo' | 'forwardRef')[];\n\n /**\n * Contexts this boundary subscribes to, by display name, sorted.\n *\n * The dependency edge that decides re-renders and that nothing in the document\n * shows. A component under a theme switch that changed without appearing here\n * got its change through the CSS cascade rather than through React, and those\n * are different explanations for the same visual difference.\n *\n * Anonymous contexts are named `(anonymous)` rather than dropped — a\n * subscription nobody named is still a subscription, and dropping it would let\n * a component that gained one compare equal to one that has none.\n */\n readonly contexts?: readonly string[];\n\n /**\n * The reconciliation key React holds this boundary under, when it has one.\n *\n * Two lists that serialize to the same `<li>a</li><li>b</li>` reconcile\n * differently depending on whether their keys are `'a', 'b'` or `'0', '1'`, and\n * the difference only becomes visible when the list reorders — at which point\n * the index-keyed one has moved every child's state onto the wrong row. The\n * fiber holds the answer before the reorder, so this is a defect readable in a\n * single still reading of a page that currently looks perfect.\n *\n * Absent when React assigned none, which is the ordinary case for a component\n * that is not one of several siblings.\n */\n readonly key?: string;\n}\n\n/**\n * Whether a key looks positional.\n *\n * A heuristic, and named as one. `key: '0'` on the first child, `'1'` on the\n * second, is what `items.map((item, index) => ...)` produces and is the defect;\n * `key: '0'` on a list of numeric ids is the same string arrived at honestly. The\n * two are indistinguishable at one node and separable across siblings, which is\n * why this takes the whole run of keys rather than one of them.\n *\n * Returns `false` for anything shorter than two siblings: a one-element list\n * keyed `'0'` reorders into nothing and there is no defect to report.\n */\nexport function keyedByPosition(keys: readonly (string | undefined)[]): boolean {\n if (keys.length < 2) return false;\n return keys.every((key, index) => key === String(index));\n}\n"]}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * `@variance-authority/core` — the format, the rules, and the verdict model.
3
+ *
4
+ * This package is pure data in, pure data out: no DOM, no React, no I/O, no
5
+ * async. That is enforced by its `tsconfig` (`lib: ES2022` only), so "the core
6
+ * cannot peek at a live document" is a compile error rather than a convention
7
+ * (ADR-0001). Collectors extract; core normalizes and adjudicates.
8
+ *
9
+ * Seven groups, each its own entrypoint, in the order an answer travels through
10
+ * them:
11
+ *
12
+ * | entrypoint | what it holds |
13
+ * |---|---|
14
+ * | `core/format` | what a subject *is*: capture, snapshot, document, identity |
15
+ * | `core/rules` | the versioned opinions: allowlist, cascade, canonicalization |
16
+ * | `core/compare` | two snapshots become deltas — and no verdict |
17
+ * | `core/attribute` | a position becomes a component becomes a file |
18
+ * | `core/relate` | what reaches what: the graph a change is traversed over |
19
+ * | `core/judge` | policy: verdicts, intent, the docket a reader is handed |
20
+ * | `core/plan` | a composition as a value, and the identity derived from it |
21
+ *
22
+ * This entrypoint is all seven, and is what most callers want. The groups exist for
23
+ * the callers who genuinely want one: somebody implementing the format for a
24
+ * renderer this project has never met needs `core/format` and would be misled by
25
+ * everything else.
26
+ */
27
+ export * from './format/index.js';
28
+ export * from './rules/index.js';
29
+ export * from './compare/index.js';
30
+ export * from './attribute/index.js';
31
+ export * from './relate/index.js';
32
+ export * from './judge/index.js';
33
+ export * from './plan/index.js';
34
+ export * from './artifact.js';
35
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,35 @@
1
+ /**
2
+ * `@variance-authority/core` — the format, the rules, and the verdict model.
3
+ *
4
+ * This package is pure data in, pure data out: no DOM, no React, no I/O, no
5
+ * async. That is enforced by its `tsconfig` (`lib: ES2022` only), so "the core
6
+ * cannot peek at a live document" is a compile error rather than a convention
7
+ * (ADR-0001). Collectors extract; core normalizes and adjudicates.
8
+ *
9
+ * Seven groups, each its own entrypoint, in the order an answer travels through
10
+ * them:
11
+ *
12
+ * | entrypoint | what it holds |
13
+ * |---|---|
14
+ * | `core/format` | what a subject *is*: capture, snapshot, document, identity |
15
+ * | `core/rules` | the versioned opinions: allowlist, cascade, canonicalization |
16
+ * | `core/compare` | two snapshots become deltas — and no verdict |
17
+ * | `core/attribute` | a position becomes a component becomes a file |
18
+ * | `core/relate` | what reaches what: the graph a change is traversed over |
19
+ * | `core/judge` | policy: verdicts, intent, the docket a reader is handed |
20
+ * | `core/plan` | a composition as a value, and the identity derived from it |
21
+ *
22
+ * This entrypoint is all seven, and is what most callers want. The groups exist for
23
+ * the callers who genuinely want one: somebody implementing the format for a
24
+ * renderer this project has never met needs `core/format` and would be misled by
25
+ * everything else.
26
+ */
27
+ export * from './format/index.js';
28
+ export * from './rules/index.js';
29
+ export * from './compare/index.js';
30
+ export * from './attribute/index.js';
31
+ export * from './relate/index.js';
32
+ export * from './judge/index.js';
33
+ export * from './plan/index.js';
34
+ export * from './artifact.js';
35
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,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 * This entrypoint is all seven, and is what most callers want. The groups exist for\n * the callers who genuinely want one: somebody implementing the format for a\n * renderer this project has never met needs `core/format` and would be misled by\n * everything else.\n */\n\nexport * from './format/index.js';\nexport * from './rules/index.js';\nexport * from './compare/index.js';\nexport * from './attribute/index.js';\nexport * from './relate/index.js';\nexport * from './judge/index.js';\nexport * from './plan/index.js';\nexport * from './artifact.js';\n"]}
@@ -0,0 +1,85 @@
1
+ import { type Band } from '../compare/band.js';
2
+ import type { ChangedComponent, RootKind, SemanticDiff } from '../compare/diff/index.js';
3
+ import { type AggregateImpact } from '../compare/impact.js';
4
+ /**
5
+ * The docket: one entry per root cause, across every subject in a change set.
6
+ *
7
+ * A `SemanticDiff` answers "what changed in this subject". That is the wrong unit
8
+ * for review, and reviewing at that unit is what makes visual regression
9
+ * unbearable — one design-token edit arrives as three hundred separate subjects
10
+ * to click through, and the three-hundred-and-first gets approved without being
11
+ * read.
12
+ *
13
+ * The docket inverts it. One token edit is **one entry**, carrying the count of
14
+ * what it reached. Spec §6.2 requires that approving it be a single action, which
15
+ * is only possible if the thing being approved is the cause rather than each of
16
+ * its effects.
17
+ *
18
+ * Aggregation is by root **id**, which is why root ids are constructed to be
19
+ * stable across subjects: `token:--color-primary` is the same root wherever it
20
+ * lands, so the grouping needs no similarity heuristic.
21
+ */
22
+ export interface DocketEntry {
23
+ readonly rootId: string;
24
+ readonly kind: RootKind;
25
+ readonly label: string;
26
+ readonly band: Band;
27
+ readonly impact: AggregateImpact;
28
+ /** Subjects this root reached, in first-seen order. */
29
+ readonly subjects: readonly string[];
30
+ readonly subjectCount: number;
31
+ /** Individual deltas across all subjects — the collateral count. */
32
+ readonly deltaCount: number;
33
+ readonly components: readonly ChangedComponent[];
34
+ /**
35
+ * A few subjects to actually look at.
36
+ *
37
+ * Mass re-baselining is only survivable if approval is *informed*, and nobody
38
+ * informs themselves by reviewing three hundred identical diffs. Spec §7.3 asks
39
+ * for a sampled spot-check list, and this is it.
40
+ */
41
+ readonly sample: readonly string[];
42
+ /**
43
+ * Distinct places this root's changes were observed, most common first.
44
+ *
45
+ * Carried on the entry rather than derived from deltas at report time, because
46
+ * an entry spans subjects and the deltas behind it are not kept. Three is
47
+ * enough to orient without turning a collapsed root back into a list — the
48
+ * thing collapsing it was for.
49
+ */
50
+ readonly places: readonly string[];
51
+ /**
52
+ * `true` when nothing under this root added, removed, moved, or renamed a node.
53
+ *
54
+ * The other half of the sentence the product promises. "One token change, 300
55
+ * collateral" is only reassuring alongside "structure intact" — that is what
56
+ * makes it a one-action approval rather than three hundred things to check.
57
+ */
58
+ readonly structureIntact: boolean;
59
+ }
60
+ export interface Docket {
61
+ readonly entries: readonly DocketEntry[];
62
+ readonly subjectsCompared: number;
63
+ readonly subjectsUnchanged: number;
64
+ readonly subjectsChanged: number;
65
+ /**
66
+ * `true` when every change in the set has one explanation.
67
+ *
68
+ * The condition under which the whole review collapses to a single action.
69
+ */
70
+ readonly singleRoot: boolean;
71
+ }
72
+ export interface DocketOptions {
73
+ /** Subjects listed per entry for spot-checking. Policy, not a constant. */
74
+ readonly sampleSize?: number;
75
+ }
76
+ export declare function buildDocket(diffs: readonly SemanticDiff[], options?: DocketOptions): Docket;
77
+ /**
78
+ * The docket as one sentence per entry.
79
+ *
80
+ * Deliberately terse. The audience is a reviewer deciding whether to look
81
+ * further and an agent deciding whether to act, and both are worse served by a
82
+ * paragraph than by a line they can scan.
83
+ */
84
+ export declare function summarize(docket: Docket): string;
85
+ //# sourceMappingURL=docket.d.ts.map