@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,179 @@
1
+ import { type Digest } from './hash.js';
2
+ /**
3
+ * Provenance: the chain that lets a diff arrive already carrying a name.
4
+ *
5
+ * Principle 5 says causality flows forward — code to semantic to raster — and the
6
+ * system never infers cause from pixels. Provenance is what makes that possible:
7
+ * every serialized node remembers which components produced it, so by the time a
8
+ * change is observed, its owner is already known rather than reconstructed.
9
+ */
10
+ export interface Provenance {
11
+ /**
12
+ * Composite components enclosing this node, innermost first:
13
+ * `['Button', 'Header', 'CheckoutPage']`. Host elements are excluded — they
14
+ * are not units anyone owns or adjudicates.
15
+ */
16
+ readonly owners: readonly OwnerFrame[];
17
+ /**
18
+ * The component whose JSX literally created this element, when the renderer
19
+ * records it. Distinct from `owners[0]`, which is the nearest *enclosing*
20
+ * component: a `<Button>` passed as a prop and rendered by `Toolbar` is owned
21
+ * by its author but enclosed by `Toolbar`. Attribution needs the author;
22
+ * "where did it end up" needs the enclosure.
23
+ */
24
+ readonly createdBy?: string;
25
+ /** Enabled per project via a compiler plugin; absent otherwise. */
26
+ readonly source?: SourceLocation;
27
+ /**
28
+ * Call-site candidates read off the fiber, awaiting a source map. **Transient.**
29
+ *
30
+ * This is what a project that installed *nothing* has: React's development
31
+ * build constructs an `Error` inside its own `jsx` and keeps it on every fiber,
32
+ * so the call site is already present in any dev server, Vitest or Jest run.
33
+ * What it is not yet is a location — a frame names the module the browser was
34
+ * served, and the file a reviewer opens is a source map away.
35
+ *
36
+ * Present only between the page read and resolution. The collector spends it,
37
+ * writing `source`; normalize drops it either way, so it never reaches a
38
+ * document, a digest or a baseline. That is deliberate rather than tidy: a
39
+ * frame holds an absolute URL with a build hash in it, and hashing one would
40
+ * make every baseline disagree with the next dev-server restart.
41
+ */
42
+ readonly stack?: readonly StackFrame[];
43
+ }
44
+ /**
45
+ * One frame of a stack an engine wrote, as positions in the served module.
46
+ *
47
+ * Not a `SourceLocation` and deliberately not shaped like one. A `SourceLocation`
48
+ * is an answer — a file and a line somebody can open. This is the question: a URL
49
+ * the browser fetched, at coordinates in the code it was actually sent.
50
+ */
51
+ export interface StackFrame {
52
+ /** The URL the engine reported, with any query string kept — it is part of the module's identity. */
53
+ readonly url: string;
54
+ /** 1-based, as the engine counts. */
55
+ readonly line: number;
56
+ /** 1-based, as the engine counts. */
57
+ readonly column: number;
58
+ /** The function the engine named, when it named one. Top-level code has none. */
59
+ readonly function?: string;
60
+ }
61
+ export interface OwnerFrame {
62
+ /** `displayName`, falling back to function name, falling back to `Anonymous`. */
63
+ readonly name: string;
64
+ /**
65
+ * Digest of this boundary's serializable props.
66
+ *
67
+ * This is what separates a *root* change from a *collateral* one (spec §6.2):
68
+ * a subtree that changed while its incoming props digest held is an internal
69
+ * change and this component is the root. If the digest moved too, the change
70
+ * arrived from outside and the root is upstream.
71
+ */
72
+ readonly propsDigest: Digest;
73
+ /**
74
+ * The component whose JSX created *this component's* element.
75
+ *
76
+ * Needed because the two useful senses of "who is responsible" separate here.
77
+ * `Provenance.createdBy` names whoever rendered the host node — for a `<Chip>`,
78
+ * that is `Chip` itself. This names whoever decided a `<Chip>` belongs at this
79
+ * point in the tree, which is a different component and the one that owns a
80
+ * structural change. When a list reorders, blaming the moved element reports
81
+ * the thing that was rearranged; this names the code that rearranged it.
82
+ */
83
+ readonly createdBy?: string;
84
+ }
85
+ export interface SourceLocation {
86
+ readonly file: string;
87
+ readonly line: number;
88
+ readonly column: number;
89
+ }
90
+ /**
91
+ * Where a JSX transform leaves the location, and where a fiber reader finds it.
92
+ *
93
+ * Every JSX transform in ordinary use already computes this. The automatic dev
94
+ * runtime passes `{fileName, lineNumber, columnNumber}` as the fifth argument to
95
+ * `jsxDEV`; the classic transform passes it as a `__source` prop. **React 19
96
+ * discards both** — `jsxDEV`'s public export takes four parameters and
97
+ * synthesizes its own `Error` for the fifth, and `createElement` skips
98
+ * `__source` by name when it copies config into props (both read in 19.2.8).
99
+ * Nothing is missing from the build; the last hop is missing from the runtime.
100
+ *
101
+ * So the runtime is the hop: `@variance-authority/jsx-source` sits in front of
102
+ * React's, writes the location here, and hands the props on unchanged. A symbol
103
+ * rather than a string key is what makes that free — `for…in` does not enumerate
104
+ * it, so `react-dom` never renders it as an attribute, `propsDigest` never
105
+ * digests it, and a component spreading `{...props}` onto a host element does not
106
+ * put it in the document.
107
+ *
108
+ * `Symbol.for` rather than a module-level symbol: the writer is in the page's
109
+ * bundle and the reader is in a page agent evaluated beside it. They are two
110
+ * module graphs and will never share an import, so the registry is the only
111
+ * place they can meet.
112
+ */
113
+ export declare const JSX_SOURCE: unique symbol;
114
+ /**
115
+ * The location a JSX runtime recorded on this props object, if one did.
116
+ *
117
+ * Total, and deliberately so: this runs once per node across a whole document,
118
+ * for props objects React built from arbitrary user code. A shape that does not
119
+ * match is absence, never a throw.
120
+ */
121
+ export declare function jsxSourceOf(props: unknown): SourceLocation | undefined;
122
+ /**
123
+ * The same location, expressed relative to a root.
124
+ *
125
+ * A transform writes the path it compiled — which for every bundler in ordinary
126
+ * use is absolute, because that is what its module graph holds. Two things go
127
+ * wrong if that reaches a report. A baseline committed from one machine names
128
+ * `/Users/somebody/...`, which is both a home directory in a public repository
129
+ * and a path that resolves nowhere in CI; and the location no longer matches
130
+ * `SourceRef.file`, which the source index has always answered with
131
+ * repository-relative paths.
132
+ *
133
+ * A path *outside* the root is returned untouched rather than turned into a
134
+ * chain of `..`. Something compiled from elsewhere — a linked package, a
135
+ * dependency shipping JSX — is genuinely not at a repository-relative path, and
136
+ * an absolute one an editor can open beats a relative one that resolves nowhere.
137
+ */
138
+ export declare function relativizeSource(location: SourceLocation, root: string): SourceLocation;
139
+ /**
140
+ * Digest a props object, tolerating values that cannot be serialized.
141
+ *
142
+ * The stability requirement cuts both ways (spec §11.2). Over-invalidation:
143
+ * inline arrow functions and object literals get a fresh identity every render,
144
+ * so identity-based digests would report every subject as changed, every build.
145
+ * Under-invalidation: collapsing all functions to one token hides a genuinely
146
+ * swapped handler.
147
+ *
148
+ * We resolve toward *shape*, deliberately accepting the under-invalidation:
149
+ * - a function digests as its name, so `onClick={handleSave}` differs from
150
+ * `onClick={handleDelete}` but re-creating the same arrow does not register;
151
+ * - an element digests as its type, so swapping `<Icon>` for `<Avatar>`
152
+ * registers but re-creating the same element does not.
153
+ *
154
+ * The accepted cost is real: renaming nothing but rebinding an anonymous closure
155
+ * to different behavior is invisible here. That is tolerable only because props
156
+ * digests are an *attribution* input, not a correctness gate — the rendered
157
+ * output still changes, and the semantic diff still catches it. The digest
158
+ * decides who gets blamed, not whether anything happened.
159
+ */
160
+ export declare function propsDigest(props: Readonly<Record<string, unknown>>): Digest;
161
+ /**
162
+ * The same projection, over one value rather than a props object.
163
+ *
164
+ * `propsDigest` is this function with a name that says what its argument is. The
165
+ * split exists because {@link Holding} digests things that are not props — a
166
+ * `useState` cell, a `useSyncExternalStore` snapshot, a context's current value,
167
+ * one prop on its own — and every one of them needs *this* shaper rather than a
168
+ * second one written to look like it.
169
+ *
170
+ * Nothing about the projection is incidental to that reuse, and two of its
171
+ * defences are load-bearing for hooks in a way they never were for props. A
172
+ * `useRef` cell holds a DOM node about as often as not, and `asHostObject` is
173
+ * what stops walking one from dragging the whole fiber graph through the digest
174
+ * and killing the renderer. An effect cell's `next` closes a ring, and the cycle
175
+ * guard is what makes that a token instead of a hang. A second implementation
176
+ * would have to rediscover both, in a page, on somebody else's site.
177
+ */
178
+ export declare function heldDigest(value: unknown): Digest;
179
+ //# sourceMappingURL=provenance.d.ts.map
@@ -0,0 +1,222 @@
1
+ import { digestValue } from './hash.js';
2
+ /**
3
+ * Where a JSX transform leaves the location, and where a fiber reader finds it.
4
+ *
5
+ * Every JSX transform in ordinary use already computes this. The automatic dev
6
+ * runtime passes `{fileName, lineNumber, columnNumber}` as the fifth argument to
7
+ * `jsxDEV`; the classic transform passes it as a `__source` prop. **React 19
8
+ * discards both** — `jsxDEV`'s public export takes four parameters and
9
+ * synthesizes its own `Error` for the fifth, and `createElement` skips
10
+ * `__source` by name when it copies config into props (both read in 19.2.8).
11
+ * Nothing is missing from the build; the last hop is missing from the runtime.
12
+ *
13
+ * So the runtime is the hop: `@variance-authority/jsx-source` sits in front of
14
+ * React's, writes the location here, and hands the props on unchanged. A symbol
15
+ * rather than a string key is what makes that free — `for…in` does not enumerate
16
+ * it, so `react-dom` never renders it as an attribute, `propsDigest` never
17
+ * digests it, and a component spreading `{...props}` onto a host element does not
18
+ * put it in the document.
19
+ *
20
+ * `Symbol.for` rather than a module-level symbol: the writer is in the page's
21
+ * bundle and the reader is in a page agent evaluated beside it. They are two
22
+ * module graphs and will never share an import, so the registry is the only
23
+ * place they can meet.
24
+ */
25
+ export const JSX_SOURCE = Symbol.for('@variance-authority/jsx-source');
26
+ /**
27
+ * The location a JSX runtime recorded on this props object, if one did.
28
+ *
29
+ * Total, and deliberately so: this runs once per node across a whole document,
30
+ * for props objects React built from arbitrary user code. A shape that does not
31
+ * match is absence, never a throw.
32
+ */
33
+ export function jsxSourceOf(props) {
34
+ if (props === null || typeof props !== 'object')
35
+ return undefined;
36
+ const recorded = props[JSX_SOURCE];
37
+ if (recorded === null || typeof recorded !== 'object')
38
+ return undefined;
39
+ const { file, line, column } = recorded;
40
+ if (typeof file !== 'string' || file === '' || typeof line !== 'number')
41
+ return undefined;
42
+ return { file, line, column: typeof column === 'number' ? column : 0 };
43
+ }
44
+ /**
45
+ * The same location, expressed relative to a root.
46
+ *
47
+ * A transform writes the path it compiled — which for every bundler in ordinary
48
+ * use is absolute, because that is what its module graph holds. Two things go
49
+ * wrong if that reaches a report. A baseline committed from one machine names
50
+ * `/Users/somebody/...`, which is both a home directory in a public repository
51
+ * and a path that resolves nowhere in CI; and the location no longer matches
52
+ * `SourceRef.file`, which the source index has always answered with
53
+ * repository-relative paths.
54
+ *
55
+ * A path *outside* the root is returned untouched rather than turned into a
56
+ * chain of `..`. Something compiled from elsewhere — a linked package, a
57
+ * dependency shipping JSX — is genuinely not at a repository-relative path, and
58
+ * an absolute one an editor can open beats a relative one that resolves nowhere.
59
+ */
60
+ export function relativizeSource(location, root) {
61
+ const path = location.file.replace(/\\/g, '/');
62
+ const base = root.replace(/\\/g, '/').replace(/\/+$/, '');
63
+ if (base === '' || !path.startsWith(`${base}/`))
64
+ return location;
65
+ return { ...location, file: path.slice(base.length + 1) };
66
+ }
67
+ /**
68
+ * Digest a props object, tolerating values that cannot be serialized.
69
+ *
70
+ * The stability requirement cuts both ways (spec §11.2). Over-invalidation:
71
+ * inline arrow functions and object literals get a fresh identity every render,
72
+ * so identity-based digests would report every subject as changed, every build.
73
+ * Under-invalidation: collapsing all functions to one token hides a genuinely
74
+ * swapped handler.
75
+ *
76
+ * We resolve toward *shape*, deliberately accepting the under-invalidation:
77
+ * - a function digests as its name, so `onClick={handleSave}` differs from
78
+ * `onClick={handleDelete}` but re-creating the same arrow does not register;
79
+ * - an element digests as its type, so swapping `<Icon>` for `<Avatar>`
80
+ * registers but re-creating the same element does not.
81
+ *
82
+ * The accepted cost is real: renaming nothing but rebinding an anonymous closure
83
+ * to different behavior is invisible here. That is tolerable only because props
84
+ * digests are an *attribution* input, not a correctness gate — the rendered
85
+ * output still changes, and the semantic diff still catches it. The digest
86
+ * decides who gets blamed, not whether anything happened.
87
+ */
88
+ export function propsDigest(props) {
89
+ return heldDigest(props);
90
+ }
91
+ /**
92
+ * The same projection, over one value rather than a props object.
93
+ *
94
+ * `propsDigest` is this function with a name that says what its argument is. The
95
+ * split exists because {@link Holding} digests things that are not props — a
96
+ * `useState` cell, a `useSyncExternalStore` snapshot, a context's current value,
97
+ * one prop on its own — and every one of them needs *this* shaper rather than a
98
+ * second one written to look like it.
99
+ *
100
+ * Nothing about the projection is incidental to that reuse, and two of its
101
+ * defences are load-bearing for hooks in a way they never were for props. A
102
+ * `useRef` cell holds a DOM node about as often as not, and `asHostObject` is
103
+ * what stops walking one from dragging the whole fiber graph through the digest
104
+ * and killing the renderer. An effect cell's `next` closes a ring, and the cycle
105
+ * guard is what makes that a token instead of a hang. A second implementation
106
+ * would have to rediscover both, in a page, on somebody else's site.
107
+ */
108
+ export function heldDigest(value) {
109
+ return digestValue(shapeOf(value, new WeakSet(), { left: MAX_VALUES }));
110
+ }
111
+ /**
112
+ * Values one digest may walk before it stops walking.
113
+ *
114
+ * The cycle guard below unwinds on exit, deliberately — two references to one
115
+ * object must digest the same wherever they appear — and the cost of that is
116
+ * that a *shared* subgraph is re-walked once per path to it. On a graph with
117
+ * enough sharing that is exponential, and exponential inside somebody else's
118
+ * page is not a slow digest: it is the renderer process dying with the capture
119
+ * in it. Far above any authored props object; only a graph nobody meant to hand
120
+ * us reaches it.
121
+ */
122
+ const MAX_VALUES = 20_000;
123
+ function shapeOf(value, seen, budget) {
124
+ if (value === null)
125
+ return null;
126
+ switch (typeof value) {
127
+ case 'undefined':
128
+ // Distinguished from absence: `{a: undefined}` and `{}` render the same
129
+ // but are different authoring intents, and the docket should say which.
130
+ return '\u0000undefined';
131
+ case 'boolean':
132
+ case 'string':
133
+ return value;
134
+ case 'number':
135
+ return Number.isFinite(value) ? value : `\u0000number:${String(value)}`;
136
+ case 'bigint':
137
+ return `\u0000bigint:${value.toString()}`;
138
+ case 'symbol':
139
+ return `\u0000symbol:${value.description ?? ''}`;
140
+ case 'function':
141
+ return `\u0000fn:${value.name || 'anonymous'}`;
142
+ }
143
+ const object = value;
144
+ // A cyclic prop graph is normal (a node holding its parent). Recursing is not.
145
+ if (seen.has(object))
146
+ return '\u0000cycle';
147
+ if ((budget.left -= 1) < 0)
148
+ return '\u0000budget';
149
+ seen.add(object);
150
+ try {
151
+ if (Array.isArray(object)) {
152
+ return object.map((item) => shapeOf(item, seen, budget));
153
+ }
154
+ const element = asReactElement(object);
155
+ if (element)
156
+ return `\u0000element:${element}`;
157
+ const host = asHostObject(object);
158
+ if (host !== null)
159
+ return host;
160
+ const shape = {};
161
+ for (const key of Object.keys(object).sort()) {
162
+ shape[key] = shapeOf(object[key], seen, budget);
163
+ }
164
+ return shape;
165
+ }
166
+ finally {
167
+ seen.delete(object);
168
+ }
169
+ }
170
+ /**
171
+ * A DOM node or a window, named rather than walked.
172
+ *
173
+ * **The reason this exists is a crash.** A prop holding an element is ordinary —
174
+ * Storybook hands every story its `canvasElement` — and `Object.keys` on an
175
+ * element returns its expandos, which on a React page are `__reactFiber$…` and
176
+ * `__reactContainer$…`. Walking one therefore walks the entire fiber graph
177
+ * through `child`, `sibling`, `return` and `alternate`, and that graph shares
178
+ * subtrees along many paths. Verified: hashing `canvasElement` on a story with
179
+ * two nested Suspense boundaries takes the renderer process down with an
180
+ * out-of-memory kill, taking the capture with it.
181
+ *
182
+ * Naming it is also the answer that was right anyway. A digest exists to say
183
+ * whether the *inputs* to a component changed, and an element's identity moves
184
+ * on every remount while its expandos move on every render — so walking one
185
+ * would report a prop change on a subject nobody touched. Detected structurally,
186
+ * because `core` runs in Node as well as in a page and may not assume `Node`
187
+ * exists (ADR-0001).
188
+ */
189
+ function asHostObject(object) {
190
+ const node = object;
191
+ if (typeof node.nodeType === 'number' && typeof node.nodeName === 'string') {
192
+ return `\u0000node:${node.nodeName}`;
193
+ }
194
+ // A window holds every global there is, including the document, and is the
195
+ // same explosion by another door.
196
+ if (node.window === object)
197
+ return '\u0000window';
198
+ return null;
199
+ }
200
+ /**
201
+ * Recognize a React element without importing React.
202
+ *
203
+ * `core` must stay framework-free (ADR-0001), and the element brand is a stable
204
+ * public contract — `Symbol.for('react.element')` for the classic runtime,
205
+ * `react.transitional.element` since React 19. Structural detection here beats a
206
+ * dependency edge from `core` to React.
207
+ */
208
+ function asReactElement(object) {
209
+ const $$typeof = object.$$typeof;
210
+ if (typeof $$typeof !== 'symbol')
211
+ return null;
212
+ const brand = $$typeof.description ?? '';
213
+ if (brand !== 'react.element' && brand !== 'react.transitional.element')
214
+ return null;
215
+ const type = object.type;
216
+ if (typeof type === 'string')
217
+ return type;
218
+ if (typeof type === 'function')
219
+ return type.name || 'Anonymous';
220
+ return 'Unknown';
221
+ }
222
+ //# sourceMappingURL=provenance.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provenance.js","sourceRoot":"","sources":["../../src/format/provenance.ts"],"names":[],"mappings":"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","sourcesContent":["import { digestValue, type Digest } from './hash.js';\n\n/**\n * Provenance: the chain that lets a diff arrive already carrying a name.\n *\n * Principle 5 says causality flows forward — code to semantic to raster — and the\n * system never infers cause from pixels. Provenance is what makes that possible:\n * every serialized node remembers which components produced it, so by the time a\n * change is observed, its owner is already known rather than reconstructed.\n */\n\nexport interface Provenance {\n /**\n * Composite components enclosing this node, innermost first:\n * `['Button', 'Header', 'CheckoutPage']`. Host elements are excluded — they\n * are not units anyone owns or adjudicates.\n */\n readonly owners: readonly OwnerFrame[];\n\n /**\n * The component whose JSX literally created this element, when the renderer\n * records it. Distinct from `owners[0]`, which is the nearest *enclosing*\n * component: a `<Button>` passed as a prop and rendered by `Toolbar` is owned\n * by its author but enclosed by `Toolbar`. Attribution needs the author;\n * \"where did it end up\" needs the enclosure.\n */\n readonly createdBy?: string;\n\n /** Enabled per project via a compiler plugin; absent otherwise. */\n readonly source?: SourceLocation;\n\n /**\n * Call-site candidates read off the fiber, awaiting a source map. **Transient.**\n *\n * This is what a project that installed *nothing* has: React's development\n * build constructs an `Error` inside its own `jsx` and keeps it on every fiber,\n * so the call site is already present in any dev server, Vitest or Jest run.\n * What it is not yet is a location — a frame names the module the browser was\n * served, and the file a reviewer opens is a source map away.\n *\n * Present only between the page read and resolution. The collector spends it,\n * writing `source`; normalize drops it either way, so it never reaches a\n * document, a digest or a baseline. That is deliberate rather than tidy: a\n * frame holds an absolute URL with a build hash in it, and hashing one would\n * make every baseline disagree with the next dev-server restart.\n */\n readonly stack?: readonly StackFrame[];\n}\n\n/**\n * One frame of a stack an engine wrote, as positions in the served module.\n *\n * Not a `SourceLocation` and deliberately not shaped like one. A `SourceLocation`\n * is an answer — a file and a line somebody can open. This is the question: a URL\n * the browser fetched, at coordinates in the code it was actually sent.\n */\nexport interface StackFrame {\n /** The URL the engine reported, with any query string kept — it is part of the module's identity. */\n readonly url: string;\n /** 1-based, as the engine counts. */\n readonly line: number;\n /** 1-based, as the engine counts. */\n readonly column: number;\n /** The function the engine named, when it named one. Top-level code has none. */\n readonly function?: string;\n}\n\nexport interface OwnerFrame {\n /** `displayName`, falling back to function name, falling back to `Anonymous`. */\n readonly name: string;\n\n /**\n * Digest of this boundary's serializable props.\n *\n * This is what separates a *root* change from a *collateral* one (spec §6.2):\n * a subtree that changed while its incoming props digest held is an internal\n * change and this component is the root. If the digest moved too, the change\n * arrived from outside and the root is upstream.\n */\n readonly propsDigest: Digest;\n\n /**\n * The component whose JSX created *this component's* element.\n *\n * Needed because the two useful senses of \"who is responsible\" separate here.\n * `Provenance.createdBy` names whoever rendered the host node — for a `<Chip>`,\n * that is `Chip` itself. This names whoever decided a `<Chip>` belongs at this\n * point in the tree, which is a different component and the one that owns a\n * structural change. When a list reorders, blaming the moved element reports\n * the thing that was rearranged; this names the code that rearranged it.\n */\n readonly createdBy?: string;\n}\n\nexport interface SourceLocation {\n readonly file: string;\n readonly line: number;\n readonly column: number;\n}\n\n/**\n * Where a JSX transform leaves the location, and where a fiber reader finds it.\n *\n * Every JSX transform in ordinary use already computes this. The automatic dev\n * runtime passes `{fileName, lineNumber, columnNumber}` as the fifth argument to\n * `jsxDEV`; the classic transform passes it as a `__source` prop. **React 19\n * discards both** — `jsxDEV`'s public export takes four parameters and\n * synthesizes its own `Error` for the fifth, and `createElement` skips\n * `__source` by name when it copies config into props (both read in 19.2.8).\n * Nothing is missing from the build; the last hop is missing from the runtime.\n *\n * So the runtime is the hop: `@variance-authority/jsx-source` sits in front of\n * React's, writes the location here, and hands the props on unchanged. A symbol\n * rather than a string key is what makes that free — `for…in` does not enumerate\n * it, so `react-dom` never renders it as an attribute, `propsDigest` never\n * digests it, and a component spreading `{...props}` onto a host element does not\n * put it in the document.\n *\n * `Symbol.for` rather than a module-level symbol: the writer is in the page's\n * bundle and the reader is in a page agent evaluated beside it. They are two\n * module graphs and will never share an import, so the registry is the only\n * place they can meet.\n */\nexport const JSX_SOURCE: unique symbol = Symbol.for(\n '@variance-authority/jsx-source',\n) as typeof JSX_SOURCE;\n\n/**\n * The location a JSX runtime recorded on this props object, if one did.\n *\n * Total, and deliberately so: this runs once per node across a whole document,\n * for props objects React built from arbitrary user code. A shape that does not\n * match is absence, never a throw.\n */\nexport function jsxSourceOf(props: unknown): SourceLocation | undefined {\n if (props === null || typeof props !== 'object') return undefined;\n\n const recorded = (props as Record<symbol, unknown>)[JSX_SOURCE];\n if (recorded === null || typeof recorded !== 'object') return undefined;\n\n const { file, line, column } = recorded as Partial<SourceLocation>;\n if (typeof file !== 'string' || file === '' || typeof line !== 'number') return undefined;\n\n return { file, line, column: typeof column === 'number' ? column : 0 };\n}\n\n/**\n * The same location, expressed relative to a root.\n *\n * A transform writes the path it compiled — which for every bundler in ordinary\n * use is absolute, because that is what its module graph holds. Two things go\n * wrong if that reaches a report. A baseline committed from one machine names\n * `/Users/somebody/...`, which is both a home directory in a public repository\n * and a path that resolves nowhere in CI; and the location no longer matches\n * `SourceRef.file`, which the source index has always answered with\n * repository-relative paths.\n *\n * A path *outside* the root is returned untouched rather than turned into a\n * chain of `..`. Something compiled from elsewhere — a linked package, a\n * dependency shipping JSX — is genuinely not at a repository-relative path, and\n * an absolute one an editor can open beats a relative one that resolves nowhere.\n */\nexport function relativizeSource(location: SourceLocation, root: string): SourceLocation {\n const path = location.file.replace(/\\\\/g, '/');\n const base = root.replace(/\\\\/g, '/').replace(/\\/+$/, '');\n if (base === '' || !path.startsWith(`${base}/`)) return location;\n\n return { ...location, file: path.slice(base.length + 1) };\n}\n\n/**\n * Digest a props object, tolerating values that cannot be serialized.\n *\n * The stability requirement cuts both ways (spec §11.2). Over-invalidation:\n * inline arrow functions and object literals get a fresh identity every render,\n * so identity-based digests would report every subject as changed, every build.\n * Under-invalidation: collapsing all functions to one token hides a genuinely\n * swapped handler.\n *\n * We resolve toward *shape*, deliberately accepting the under-invalidation:\n * - a function digests as its name, so `onClick={handleSave}` differs from\n * `onClick={handleDelete}` but re-creating the same arrow does not register;\n * - an element digests as its type, so swapping `<Icon>` for `<Avatar>`\n * registers but re-creating the same element does not.\n *\n * The accepted cost is real: renaming nothing but rebinding an anonymous closure\n * to different behavior is invisible here. That is tolerable only because props\n * digests are an *attribution* input, not a correctness gate — the rendered\n * output still changes, and the semantic diff still catches it. The digest\n * decides who gets blamed, not whether anything happened.\n */\nexport function propsDigest(props: Readonly<Record<string, unknown>>): Digest {\n return heldDigest(props);\n}\n\n/**\n * The same projection, over one value rather than a props object.\n *\n * `propsDigest` is this function with a name that says what its argument is. The\n * split exists because {@link Holding} digests things that are not props — a\n * `useState` cell, a `useSyncExternalStore` snapshot, a context's current value,\n * one prop on its own — and every one of them needs *this* shaper rather than a\n * second one written to look like it.\n *\n * Nothing about the projection is incidental to that reuse, and two of its\n * defences are load-bearing for hooks in a way they never were for props. A\n * `useRef` cell holds a DOM node about as often as not, and `asHostObject` is\n * what stops walking one from dragging the whole fiber graph through the digest\n * and killing the renderer. An effect cell's `next` closes a ring, and the cycle\n * guard is what makes that a token instead of a hang. A second implementation\n * would have to rediscover both, in a page, on somebody else's site.\n */\nexport function heldDigest(value: unknown): Digest {\n return digestValue(shapeOf(value, new WeakSet(), { left: MAX_VALUES }) as never);\n}\n\n/**\n * Values one digest may walk before it stops walking.\n *\n * The cycle guard below unwinds on exit, deliberately — two references to one\n * object must digest the same wherever they appear — and the cost of that is\n * that a *shared* subgraph is re-walked once per path to it. On a graph with\n * enough sharing that is exponential, and exponential inside somebody else's\n * page is not a slow digest: it is the renderer process dying with the capture\n * in it. Far above any authored props object; only a graph nobody meant to hand\n * us reaches it.\n */\nconst MAX_VALUES = 20_000;\n\ninterface Budget {\n left: number;\n}\n\nfunction shapeOf(value: unknown, seen: WeakSet<object>, budget: Budget): unknown {\n if (value === null) return null;\n\n switch (typeof value) {\n case 'undefined':\n // Distinguished from absence: `{a: undefined}` and `{}` render the same\n // but are different authoring intents, and the docket should say which.\n return '\\u0000undefined';\n case 'boolean':\n case 'string':\n return value;\n case 'number':\n return Number.isFinite(value) ? value : `\\u0000number:${String(value)}`;\n case 'bigint':\n return `\\u0000bigint:${value.toString()}`;\n case 'symbol':\n return `\\u0000symbol:${value.description ?? ''}`;\n case 'function':\n return `\\u0000fn:${value.name || 'anonymous'}`;\n }\n\n const object = value as object;\n\n // A cyclic prop graph is normal (a node holding its parent). Recursing is not.\n if (seen.has(object)) return '\\u0000cycle';\n if ((budget.left -= 1) < 0) return '\\u0000budget';\n seen.add(object);\n\n try {\n if (Array.isArray(object)) {\n return object.map((item) => shapeOf(item, seen, budget));\n }\n\n const element = asReactElement(object);\n if (element) return `\\u0000element:${element}`;\n\n const host = asHostObject(object);\n if (host !== null) return host;\n\n const shape: Record<string, unknown> = {};\n for (const key of Object.keys(object).sort()) {\n shape[key] = shapeOf((object as Record<string, unknown>)[key], seen, budget);\n }\n return shape;\n } finally {\n seen.delete(object);\n }\n}\n\n/**\n * A DOM node or a window, named rather than walked.\n *\n * **The reason this exists is a crash.** A prop holding an element is ordinary —\n * Storybook hands every story its `canvasElement` — and `Object.keys` on an\n * element returns its expandos, which on a React page are `__reactFiber$…` and\n * `__reactContainer$…`. Walking one therefore walks the entire fiber graph\n * through `child`, `sibling`, `return` and `alternate`, and that graph shares\n * subtrees along many paths. Verified: hashing `canvasElement` on a story with\n * two nested Suspense boundaries takes the renderer process down with an\n * out-of-memory kill, taking the capture with it.\n *\n * Naming it is also the answer that was right anyway. A digest exists to say\n * whether the *inputs* to a component changed, and an element's identity moves\n * on every remount while its expandos move on every render — so walking one\n * would report a prop change on a subject nobody touched. Detected structurally,\n * because `core` runs in Node as well as in a page and may not assume `Node`\n * exists (ADR-0001).\n */\nfunction asHostObject(object: object): string | null {\n const node = object as { nodeType?: unknown; nodeName?: unknown; window?: unknown };\n\n if (typeof node.nodeType === 'number' && typeof node.nodeName === 'string') {\n return `\\u0000node:${node.nodeName}`;\n }\n\n // A window holds every global there is, including the document, and is the\n // same explosion by another door.\n if (node.window === object) return '\\u0000window';\n\n return null;\n}\n\n/**\n * Recognize a React element without importing React.\n *\n * `core` must stay framework-free (ADR-0001), and the element brand is a stable\n * public contract — `Symbol.for('react.element')` for the classic runtime,\n * `react.transitional.element` since React 19. Structural detection here beats a\n * dependency edge from `core` to React.\n */\nfunction asReactElement(object: object): string | null {\n const $$typeof = (object as { $$typeof?: symbol }).$$typeof;\n if (typeof $$typeof !== 'symbol') return null;\n\n const brand = $$typeof.description ?? '';\n if (brand !== 'react.element' && brand !== 'react.transitional.element') return null;\n\n const type = (object as { type?: unknown }).type;\n if (typeof type === 'string') return type;\n if (typeof type === 'function') return type.name || 'Anonymous';\n return 'Unknown';\n}\n"]}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * SHA-256, in portable TypeScript.
3
+ *
4
+ * `core` originally used `node:crypto`, which was wrong — and the reason is not
5
+ * ergonomic. `propsDigest` has to run *inside the page*: a React prop can be a
6
+ * function or an element, and neither survives serialization out to a host
7
+ * process, so the digest must be taken where the value still exists. A `core`
8
+ * that imports `node:crypto` cannot run there, which quietly contradicted the
9
+ * remote-sub-renderer story in ADR-0002 and the DOM-free boundary in ADR-0001.
10
+ * (The `lib: ["ES2022"]` guard is one-directional: it stops `core` reaching into
11
+ * a browser, not `core` depending on Node.)
12
+ *
13
+ * `crypto.subtle.digest` is the obvious replacement and is not usable: it is
14
+ * async, and hashing is synchronous throughout by design — making it async would
15
+ * colour the entire normalizer for no benefit.
16
+ *
17
+ * So the algorithm lives here. It is fully specified by FIPS 180-4, so this
18
+ * produces byte-identical output to any other correct implementation on any
19
+ * runtime, which is exactly the determinism Principle 2 requires. Performance is
20
+ * not a consideration until a gate in ADR-0004 fires; none has.
21
+ */
22
+ /** Hash a UTF-8 string, returning lowercase hex. */
23
+ export declare function sha256Hex(input: string): string;
24
+ /**
25
+ * Hash raw bytes, returning lowercase hex.
26
+ *
27
+ * The string form is this with one encoding step in front of it, and not the
28
+ * other way round: an image is bytes, and routing it through a string would mean
29
+ * choosing an encoding that survives arbitrary octets — which is a second
30
+ * decision, made in the wrong place, that changes the digest if it is ever
31
+ * revisited.
32
+ */
33
+ export declare function sha256HexBytes(bytes: Uint8Array): string;
34
+ //# sourceMappingURL=sha256.d.ts.map
@@ -0,0 +1,152 @@
1
+ /**
2
+ * SHA-256, in portable TypeScript.
3
+ *
4
+ * `core` originally used `node:crypto`, which was wrong — and the reason is not
5
+ * ergonomic. `propsDigest` has to run *inside the page*: a React prop can be a
6
+ * function or an element, and neither survives serialization out to a host
7
+ * process, so the digest must be taken where the value still exists. A `core`
8
+ * that imports `node:crypto` cannot run there, which quietly contradicted the
9
+ * remote-sub-renderer story in ADR-0002 and the DOM-free boundary in ADR-0001.
10
+ * (The `lib: ["ES2022"]` guard is one-directional: it stops `core` reaching into
11
+ * a browser, not `core` depending on Node.)
12
+ *
13
+ * `crypto.subtle.digest` is the obvious replacement and is not usable: it is
14
+ * async, and hashing is synchronous throughout by design — making it async would
15
+ * colour the entire normalizer for no benefit.
16
+ *
17
+ * So the algorithm lives here. It is fully specified by FIPS 180-4, so this
18
+ * produces byte-identical output to any other correct implementation on any
19
+ * runtime, which is exactly the determinism Principle 2 requires. Performance is
20
+ * not a consideration until a gate in ADR-0004 fires; none has.
21
+ */
22
+ const K = new Uint32Array([
23
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
24
+ 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
25
+ 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
26
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
27
+ 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
28
+ 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
29
+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
30
+ 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
31
+ ]);
32
+ const INITIAL = new Uint32Array([
33
+ 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
34
+ ]);
35
+ /** Hash a UTF-8 string, returning lowercase hex. */
36
+ export function sha256Hex(input) {
37
+ return sha256HexBytes(utf8Bytes(input));
38
+ }
39
+ /**
40
+ * Hash raw bytes, returning lowercase hex.
41
+ *
42
+ * The string form is this with one encoding step in front of it, and not the
43
+ * other way round: an image is bytes, and routing it through a string would mean
44
+ * choosing an encoding that survives arbitrary octets — which is a second
45
+ * decision, made in the wrong place, that changes the digest if it is ever
46
+ * revisited.
47
+ */
48
+ export function sha256HexBytes(bytes) {
49
+ const state = new Uint32Array(INITIAL);
50
+ // Padding: a single 1 bit, zeros, then the 64-bit big-endian bit length.
51
+ const bitLength = bytes.length * 8;
52
+ // Padding is minimal by definition: the smallest multiple of 64 that fits the
53
+ // message, the 0x80 byte, and the 8-byte length. Rounding up unconditionally
54
+ // would append a spare block whenever `length + 9` is already a multiple of
55
+ // 64, which is a different message and therefore a different digest.
56
+ const paddedLength = Math.ceil((bytes.length + 9) / 64) * 64;
57
+ const padded = new Uint8Array(paddedLength);
58
+ padded.set(bytes);
59
+ padded[bytes.length] = 0x80;
60
+ // Written as two 32-bit halves rather than a BigInt: inputs never approach
61
+ // 2^32 bits (512 MiB), and the high word is always zero in practice, but
62
+ // emitting it keeps the block format exactly as specified.
63
+ const view = new DataView(padded.buffer);
64
+ view.setUint32(paddedLength - 8, Math.floor(bitLength / 0x100000000), false);
65
+ view.setUint32(paddedLength - 4, bitLength >>> 0, false);
66
+ const w = new Uint32Array(64);
67
+ for (let offset = 0; offset < paddedLength; offset += 64) {
68
+ for (let i = 0; i < 16; i += 1) {
69
+ w[i] = view.getUint32(offset + i * 4, false);
70
+ }
71
+ for (let i = 16; i < 64; i += 1) {
72
+ const s0 = rotr(w[i - 15], 7) ^ rotr(w[i - 15], 18) ^ (w[i - 15] >>> 3);
73
+ const s1 = rotr(w[i - 2], 17) ^ rotr(w[i - 2], 19) ^ (w[i - 2] >>> 10);
74
+ w[i] = (w[i - 16] + s0 + w[i - 7] + s1) >>> 0;
75
+ }
76
+ let [a, b, c, d, e, f, g, h] = state;
77
+ for (let i = 0; i < 64; i += 1) {
78
+ const S1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
79
+ const ch = (e & f) ^ (~e & g);
80
+ const temp1 = (h + S1 + ch + K[i] + w[i]) >>> 0;
81
+ const S0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22);
82
+ const maj = (a & b) ^ (a & c) ^ (b & c);
83
+ const temp2 = (S0 + maj) >>> 0;
84
+ h = g;
85
+ g = f;
86
+ f = e;
87
+ e = (d + temp1) >>> 0;
88
+ d = c;
89
+ c = b;
90
+ b = a;
91
+ a = (temp1 + temp2) >>> 0;
92
+ }
93
+ state[0] = (state[0] + a) >>> 0;
94
+ state[1] = (state[1] + b) >>> 0;
95
+ state[2] = (state[2] + c) >>> 0;
96
+ state[3] = (state[3] + d) >>> 0;
97
+ state[4] = (state[4] + e) >>> 0;
98
+ state[5] = (state[5] + f) >>> 0;
99
+ state[6] = (state[6] + g) >>> 0;
100
+ state[7] = (state[7] + h) >>> 0;
101
+ }
102
+ let hex = '';
103
+ for (const word of state) {
104
+ hex += word.toString(16).padStart(8, '0');
105
+ }
106
+ return hex;
107
+ }
108
+ function rotr(value, bits) {
109
+ return ((value >>> bits) | (value << (32 - bits))) >>> 0;
110
+ }
111
+ /**
112
+ * Encode as UTF-8 without `TextEncoder`.
113
+ *
114
+ * `TextEncoder` is available everywhere this runs today, but hand-encoding keeps
115
+ * `core` free of *every* host global, not merely the Node ones — which is the
116
+ * property that just failed to hold. Lone surrogates are encoded as U+FFFD, the
117
+ * same substitution `TextEncoder` makes, so a malformed string cannot produce
118
+ * two different digests on two runtimes.
119
+ */
120
+ function utf8Bytes(input) {
121
+ const bytes = [];
122
+ for (let i = 0; i < input.length; i += 1) {
123
+ let code = input.charCodeAt(i);
124
+ if (code >= 0xd800 && code <= 0xdbff) {
125
+ const next = input.charCodeAt(i + 1);
126
+ if (next >= 0xdc00 && next <= 0xdfff) {
127
+ code = 0x10000 + ((code - 0xd800) << 10) + (next - 0xdc00);
128
+ i += 1;
129
+ }
130
+ else {
131
+ code = 0xfffd;
132
+ }
133
+ }
134
+ else if (code >= 0xdc00 && code <= 0xdfff) {
135
+ code = 0xfffd;
136
+ }
137
+ if (code < 0x80) {
138
+ bytes.push(code);
139
+ }
140
+ else if (code < 0x800) {
141
+ bytes.push(0xc0 | (code >> 6), 0x80 | (code & 0x3f));
142
+ }
143
+ else if (code < 0x10000) {
144
+ bytes.push(0xe0 | (code >> 12), 0x80 | ((code >> 6) & 0x3f), 0x80 | (code & 0x3f));
145
+ }
146
+ else {
147
+ bytes.push(0xf0 | (code >> 18), 0x80 | ((code >> 12) & 0x3f), 0x80 | ((code >> 6) & 0x3f), 0x80 | (code & 0x3f));
148
+ }
149
+ }
150
+ return Uint8Array.from(bytes);
151
+ }
152
+ //# sourceMappingURL=sha256.js.map