@variance-authority/report 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 (53) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/LICENSE +21 -0
  3. package/README.md +258 -0
  4. package/dist/changelog-message.d.ts +51 -0
  5. package/dist/changelog-message.js +244 -0
  6. package/dist/changelog-message.js.map +1 -0
  7. package/dist/changelog.d.ts +231 -0
  8. package/dist/changelog.js +96 -0
  9. package/dist/changelog.js.map +1 -0
  10. package/dist/cluster.d.ts +112 -0
  11. package/dist/cluster.js +109 -0
  12. package/dist/cluster.js.map +1 -0
  13. package/dist/composition.d.ts +248 -0
  14. package/dist/composition.js +33 -0
  15. package/dist/composition.js.map +1 -0
  16. package/dist/declarations.d.ts +266 -0
  17. package/dist/declarations.js +212 -0
  18. package/dist/declarations.js.map +1 -0
  19. package/dist/file.d.ts +28 -0
  20. package/dist/file.js +151 -0
  21. package/dist/file.js.map +1 -0
  22. package/dist/finding-record.d.ts +64 -0
  23. package/dist/finding-record.js +14 -0
  24. package/dist/finding-record.js.map +1 -0
  25. package/dist/findings.d.ts +157 -0
  26. package/dist/findings.js +227 -0
  27. package/dist/findings.js.map +1 -0
  28. package/dist/format.d.ts +444 -0
  29. package/dist/format.js +2 -0
  30. package/dist/format.js.map +1 -0
  31. package/dist/history-records.d.ts +112 -0
  32. package/dist/history-records.js +16 -0
  33. package/dist/history-records.js.map +1 -0
  34. package/dist/index.d.ts +45 -0
  35. package/dist/index.js +35 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/intent.d.ts +168 -0
  38. package/dist/intent.js +214 -0
  39. package/dist/intent.js.map +1 -0
  40. package/dist/presentation-record.d.ts +66 -0
  41. package/dist/presentation-record.js +9 -0
  42. package/dist/presentation-record.js.map +1 -0
  43. package/dist/promotion.d.ts +86 -0
  44. package/dist/promotion.js +104 -0
  45. package/dist/promotion.js.map +1 -0
  46. package/dist/reach.d.ts +154 -0
  47. package/dist/reach.js +47 -0
  48. package/dist/reach.js.map +1 -0
  49. package/dist/variation.d.ts +58 -0
  50. package/dist/variation.js +2 -0
  51. package/dist/variation.js.map +1 -0
  52. package/mark.svg +30 -0
  53. package/package.json +48 -0
@@ -0,0 +1,248 @@
1
+ /**
2
+ * The suite as one graph, in the artifact.
3
+ *
4
+ * Every other part of a run report is *one subject against its baseline* — two
5
+ * revisions, one thing. This is the other axis, and it is the one a suite of
6
+ * examples has always implied and never written down:
7
+ *
8
+ * > A visual-regression example is a component built from components. The example
9
+ * > *is* a component, at a boundary; the same component appears again, with the
10
+ * > same or different props, inside larger examples.
11
+ *
12
+ * Once the boundaries are addressable, the run can say which of its examples are
13
+ * watching literally the same bytes, which of them disagree at one commit, and —
14
+ * for anything that moved — what in the run explains it. None of that needs a
15
+ * second render, a second image, or a store: it is a fold over digests the
16
+ * collection already produced.
17
+ *
18
+ * ## Why the shapes here are smaller than the ones that produced them
19
+ *
20
+ * `@variance-authority/core`'s `Composition` carries every site of every
21
+ * rendering, which is one entry per component boundary per subject — tens of
22
+ * thousands of objects on a real suite, and a report is a file people open. What
23
+ * survives into the artifact is what a *sentence* needs: the names, the counts,
24
+ * and the subject lists that let a reader go and look. A consumer that wants the
25
+ * full graph recomputes it from the snapshots, which is the same bargain the
26
+ * report already makes about masks and PNGs.
27
+ *
28
+ * The one thing kept in full is the movement list, because it is short by
29
+ * construction — only components that moved — and because it is the part somebody
30
+ * acts on.
31
+ */
32
+ /** A component's census entry: where it is, what it is inside, what it reads. */
33
+ export interface ComponentRecord {
34
+ readonly component: string;
35
+ /** Subjects holding at least one boundary of it, in plan order. */
36
+ readonly subjects: readonly string[];
37
+ /** Boundaries summed across every subject. Distinct from `subjects.length`. */
38
+ readonly instances: number;
39
+ /**
40
+ * Subjects whose shallowest attributed boundary is this component.
41
+ *
42
+ * The narrow example — the subject that exists to *show* this thing rather than
43
+ * a page that happens to contain it. Empty is a real answer and a common one: a
44
+ * component that appears only inside pages has no example, which is the gap a
45
+ * reviewer is usually looking for.
46
+ */
47
+ readonly examples: readonly string[];
48
+ /** Components that enclose it somewhere, sorted. The graph, upwards. */
49
+ readonly within: readonly string[];
50
+ /**
51
+ * Components that **mounted** it somewhere, sorted. Usually the useful edge.
52
+ *
53
+ * `within` is where the boundary sits and this is who wrote the element, and in
54
+ * a real application they are mostly different: measured on
55
+ * `examples/todomvc`, every `Chip` is `within: ["Stack"]` and
56
+ * `createdBy: ["TodoFooter"]`. `TodoFooter` renders nothing but other
57
+ * components, so it owns no DOM node, is a boundary nowhere, and would be
58
+ * absent from this graph entirely if only `within` were recorded — while being
59
+ * the file a reviewer has to open.
60
+ *
61
+ * Empty on a production build, where React's `_debugOwner` is gone. Empty is
62
+ * *not* "nothing mounted it".
63
+ */
64
+ readonly createdBy: readonly string[];
65
+ /** Components it encloses somewhere, sorted. The graph, downwards. */
66
+ readonly renders: readonly string[];
67
+ /** Custom properties its own nodes resolve through, anywhere in the suite. */
68
+ readonly tokens: readonly string[];
69
+ /**
70
+ * Distinct props digests it was rendered with, and distinct renderings it
71
+ * produced.
72
+ *
73
+ * Two numbers rather than the classes themselves, because the classes are
74
+ * digests and a digest tells a human nothing. What the pair says is worth a
75
+ * line: `variants: 4, renderings: 4` is a component whose output is a function
76
+ * of its input, and `variants: 1, renderings: 3` is one whose output is not —
77
+ * which is what `divergences` is about.
78
+ *
79
+ * `variants` counts the unknown-props class as one when it occurred, because
80
+ * instances with no provenance are a group this report can describe and not one
81
+ * it may join on.
82
+ */
83
+ readonly variants: number;
84
+ readonly renderings: number;
85
+ }
86
+ /**
87
+ * One rendering, several subjects. The dots, connected.
88
+ *
89
+ * The finding is a rendering that survives being mounted somewhere else — three
90
+ * identical chips in one list say nothing, and the same chip in a chip story and
91
+ * in a page footer says that a reviewer looking at two diffs is looking at one.
92
+ */
93
+ export interface EchoRecord {
94
+ readonly component: string;
95
+ /** The joining digest: the four content digests of the boundary, together. */
96
+ readonly rendering: string;
97
+ /** At least two, in plan order. */
98
+ readonly subjects: readonly string[];
99
+ /** Boundaries, which is at least `subjects.length`. */
100
+ readonly sites: number;
101
+ /** The narrow example among them, when the suite has one. */
102
+ readonly example?: string;
103
+ }
104
+ /**
105
+ * The same component, the same **inputs**, more than one rendering. At one commit.
106
+ *
107
+ * Not a regression — there is no baseline anywhere in it. It says the component's
108
+ * own inputs do not determine its output, which is either a fact about the design
109
+ * (a token, a theme, an ancestor's cascade) or a reading that is not repeatable.
110
+ * `bands` says which kind, in the vocabulary a sensitivity absorbs, so a
111
+ * divergence entirely inside a relaxed band can be dismissed without opening it.
112
+ *
113
+ * *Inputs*, not *props*: a props digest excludes `children`, and `divergencesOf`
114
+ * in `core` refuses every pair the exclusion could explain. An empty list is a
115
+ * real and common answer — `examples/todomvc` produces exactly zero — and it
116
+ * means nothing in the suite rendered two ways from one input.
117
+ */
118
+ export interface DivergenceRecord {
119
+ readonly component: string;
120
+ readonly bands: readonly string[];
121
+ /**
122
+ * The subjects behind each distinct rendering, widest group first. At least two.
123
+ *
124
+ * Grouped rather than counted, and that is the whole record. A divergence says
125
+ * one input produced more than one output, and the only sentence a reader can
126
+ * act on is *which* subjects sit on each side of it — `price` and `receipt`
127
+ * render one way, `promo` renders another, so `promo` is the one to open. A
128
+ * count with a flat subject list beside it, which is what this was, names every
129
+ * subject involved and withholds the only thing distinguishing them: the reader
130
+ * has three names, knows two of them agree, and cannot tell which two.
131
+ *
132
+ * Deduplicated per group, because a rendering reached twice in one subject is
133
+ * one subject to go and look at. `renderings.length` is the count this replaced.
134
+ */
135
+ readonly renderings: readonly (readonly string[])[];
136
+ /**
137
+ * Why each rendering after the first parted from it.
138
+ *
139
+ * The sentence, under the split. `renderings` says *promo is the odd one out*;
140
+ * this says *`Price` inherited a different `color` — an ancestor declared it*,
141
+ * which is the difference between a reader knowing where to look and knowing
142
+ * what they will find when they get there.
143
+ *
144
+ * Absent when the run kept no documents to read. Empty is never written.
145
+ */
146
+ readonly partings?: readonly PartingRecord[];
147
+ }
148
+ /** One rendering of a divergence, spoken. */
149
+ export interface PartingRecord {
150
+ /** Index into {@link DivergenceRecord.renderings}, always at least 1. */
151
+ readonly rendering: number;
152
+ /** Already phrased for a reader; the first line is the triage slice. */
153
+ readonly lines: readonly string[];
154
+ }
155
+ /**
156
+ * Why one component moved in one subject, and what the suite held against it.
157
+ *
158
+ * The ladder is `edited` → `token` → `upstream` → `contradicted` → `unexplained`,
159
+ * and the last one is the finding. See
160
+ * [`flakiness.md`](../../../docs/flakiness.md) for what it is and is not allowed
161
+ * to conclude.
162
+ */
163
+ export interface MovementRecord {
164
+ readonly subject: string;
165
+ readonly component: string;
166
+ /** Empty means *not known* — a name-only comparison — never *no band*. */
167
+ readonly bands: readonly string[];
168
+ readonly cause: 'edited' | 'token' | 'upstream' | 'contradicted' | 'unexplained';
169
+ /** One sentence, naming the evidence rather than the category. */
170
+ readonly because: string;
171
+ readonly file?: string;
172
+ readonly tokens?: readonly string[];
173
+ readonly upstream?: string;
174
+ /**
175
+ * Components between `upstream` and this one, outermost first.
176
+ *
177
+ * Present only on the `upstream` rung, and absent when the edited component
178
+ * draws this one directly. A reviewer sent to `ProductCard` because a
179
+ * `CardFooter` moved has exactly one question next — *how does that reach
180
+ * this* — and the answer is a couple of names the run already walked.
181
+ */
182
+ readonly through?: readonly string[];
183
+ /** Other subjects this same component moved in. The "one cause, N subjects" fold. */
184
+ readonly alsoIn: readonly string[];
185
+ /**
186
+ * Subjects where the same component, with the same props, did **not** move.
187
+ *
188
+ * The control group — the *stable states to refer to*. Empty means the suite
189
+ * offered no control, which weakens an unexplained movement rather than
190
+ * strengthening it, and is why this is a list and not a flag.
191
+ */
192
+ readonly held: readonly string[];
193
+ /**
194
+ * Renders of this component with the same props, other than this one, that the
195
+ * run read.
196
+ *
197
+ * The denominator `held` is a numerator of. Without it an empty control group
198
+ * is two opposite findings wearing one shape — nothing to compare against, or a
199
+ * comparison made in every render and answering the same way — and a reader
200
+ * that guesses picks the wrong one on any suite where the change reached
201
+ * everything.
202
+ *
203
+ * Optional because a report written before this was carried has no answer, and
204
+ * `0` is an answer.
205
+ */
206
+ readonly compared?: number;
207
+ /**
208
+ * For an unexplained movement only: whether the subject was already proven
209
+ * unstable in this run.
210
+ *
211
+ * `flake` means the subject failed to read the same way twice *and* nothing in
212
+ * the run explains what moved — the two halves of the sentence, together, for
213
+ * the first time. `suspect` means nobody has read it twice yet, so it is a
214
+ * shortlist entry and not a verdict.
215
+ *
216
+ * Absent on every explained movement, where the question does not arise.
217
+ */
218
+ readonly standing?: 'flake' | 'suspect';
219
+ }
220
+ /**
221
+ * What one run learned by comparing its subjects to each other.
222
+ *
223
+ * Absent from a report whose collection produced no semantic snapshots — a
224
+ * raster-only tier has no boundaries to join, and an empty graph would read as
225
+ * "this suite shares nothing", which is a different and false claim.
226
+ */
227
+ export interface CompositionReport {
228
+ /** Subjects that contributed a snapshot, in plan order. The denominator. */
229
+ readonly subjects: readonly string[];
230
+ /** Sorted by name, code-unit order. */
231
+ readonly components: readonly ComponentRecord[];
232
+ /** Widest first. Capped — see `truncated`. */
233
+ readonly echoes: readonly EchoRecord[];
234
+ /** Sorted by component name. */
235
+ readonly divergences: readonly DivergenceRecord[];
236
+ /** Every component the run found moved, in the order the observations came. */
237
+ readonly movements: readonly MovementRecord[];
238
+ /**
239
+ * What was left out of the lists above, when anything was.
240
+ *
241
+ * A cap that says nothing is a cap that reads as coverage. Present only when a
242
+ * list was shortened, and counts what did not make it rather than what did.
243
+ */
244
+ readonly truncated?: {
245
+ readonly echoes: number;
246
+ };
247
+ }
248
+ //# sourceMappingURL=composition.d.ts.map
@@ -0,0 +1,33 @@
1
+ /**
2
+ * The suite as one graph, in the artifact.
3
+ *
4
+ * Every other part of a run report is *one subject against its baseline* — two
5
+ * revisions, one thing. This is the other axis, and it is the one a suite of
6
+ * examples has always implied and never written down:
7
+ *
8
+ * > A visual-regression example is a component built from components. The example
9
+ * > *is* a component, at a boundary; the same component appears again, with the
10
+ * > same or different props, inside larger examples.
11
+ *
12
+ * Once the boundaries are addressable, the run can say which of its examples are
13
+ * watching literally the same bytes, which of them disagree at one commit, and —
14
+ * for anything that moved — what in the run explains it. None of that needs a
15
+ * second render, a second image, or a store: it is a fold over digests the
16
+ * collection already produced.
17
+ *
18
+ * ## Why the shapes here are smaller than the ones that produced them
19
+ *
20
+ * `@variance-authority/core`'s `Composition` carries every site of every
21
+ * rendering, which is one entry per component boundary per subject — tens of
22
+ * thousands of objects on a real suite, and a report is a file people open. What
23
+ * survives into the artifact is what a *sentence* needs: the names, the counts,
24
+ * and the subject lists that let a reader go and look. A consumer that wants the
25
+ * full graph recomputes it from the snapshots, which is the same bargain the
26
+ * report already makes about masks and PNGs.
27
+ *
28
+ * The one thing kept in full is the movement list, because it is short by
29
+ * construction — only components that moved — and because it is the part somebody
30
+ * acts on.
31
+ */
32
+ export {};
33
+ //# sourceMappingURL=composition.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"composition.js","sourceRoot":"","sources":["../src/composition.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG","sourcesContent":["/**\n * The suite as one graph, in the artifact.\n *\n * Every other part of a run report is *one subject against its baseline* — two\n * revisions, one thing. This is the other axis, and it is the one a suite of\n * examples has always implied and never written down:\n *\n * > A visual-regression example is a component built from components. The example\n * > *is* a component, at a boundary; the same component appears again, with the\n * > same or different props, inside larger examples.\n *\n * Once the boundaries are addressable, the run can say which of its examples are\n * watching literally the same bytes, which of them disagree at one commit, and —\n * for anything that moved — what in the run explains it. None of that needs a\n * second render, a second image, or a store: it is a fold over digests the\n * collection already produced.\n *\n * ## Why the shapes here are smaller than the ones that produced them\n *\n * `@variance-authority/core`'s `Composition` carries every site of every\n * rendering, which is one entry per component boundary per subject — tens of\n * thousands of objects on a real suite, and a report is a file people open. What\n * survives into the artifact is what a *sentence* needs: the names, the counts,\n * and the subject lists that let a reader go and look. A consumer that wants the\n * full graph recomputes it from the snapshots, which is the same bargain the\n * report already makes about masks and PNGs.\n *\n * The one thing kept in full is the movement list, because it is short by\n * construction — only components that moved — and because it is the part somebody\n * acts on.\n */\n\n/** A component's census entry: where it is, what it is inside, what it reads. */\nexport interface ComponentRecord {\n readonly component: string;\n /** Subjects holding at least one boundary of it, in plan order. */\n readonly subjects: readonly string[];\n /** Boundaries summed across every subject. Distinct from `subjects.length`. */\n readonly instances: number;\n\n /**\n * Subjects whose shallowest attributed boundary is this component.\n *\n * The narrow example — the subject that exists to *show* this thing rather than\n * a page that happens to contain it. Empty is a real answer and a common one: a\n * component that appears only inside pages has no example, which is the gap a\n * reviewer is usually looking for.\n */\n readonly examples: readonly string[];\n\n /** Components that enclose it somewhere, sorted. The graph, upwards. */\n readonly within: readonly string[];\n\n /**\n * Components that **mounted** it somewhere, sorted. Usually the useful edge.\n *\n * `within` is where the boundary sits and this is who wrote the element, and in\n * a real application they are mostly different: measured on\n * `examples/todomvc`, every `Chip` is `within: [\"Stack\"]` and\n * `createdBy: [\"TodoFooter\"]`. `TodoFooter` renders nothing but other\n * components, so it owns no DOM node, is a boundary nowhere, and would be\n * absent from this graph entirely if only `within` were recorded — while being\n * the file a reviewer has to open.\n *\n * Empty on a production build, where React's `_debugOwner` is gone. Empty is\n * *not* \"nothing mounted it\".\n */\n readonly createdBy: readonly string[];\n\n /** Components it encloses somewhere, sorted. The graph, downwards. */\n readonly renders: readonly string[];\n /** Custom properties its own nodes resolve through, anywhere in the suite. */\n readonly tokens: readonly string[];\n\n /**\n * Distinct props digests it was rendered with, and distinct renderings it\n * produced.\n *\n * Two numbers rather than the classes themselves, because the classes are\n * digests and a digest tells a human nothing. What the pair says is worth a\n * line: `variants: 4, renderings: 4` is a component whose output is a function\n * of its input, and `variants: 1, renderings: 3` is one whose output is not —\n * which is what `divergences` is about.\n *\n * `variants` counts the unknown-props class as one when it occurred, because\n * instances with no provenance are a group this report can describe and not one\n * it may join on.\n */\n readonly variants: number;\n readonly renderings: number;\n}\n\n/**\n * One rendering, several subjects. The dots, connected.\n *\n * The finding is a rendering that survives being mounted somewhere else — three\n * identical chips in one list say nothing, and the same chip in a chip story and\n * in a page footer says that a reviewer looking at two diffs is looking at one.\n */\nexport interface EchoRecord {\n readonly component: string;\n /** The joining digest: the four content digests of the boundary, together. */\n readonly rendering: string;\n /** At least two, in plan order. */\n readonly subjects: readonly string[];\n /** Boundaries, which is at least `subjects.length`. */\n readonly sites: number;\n /** The narrow example among them, when the suite has one. */\n readonly example?: string;\n}\n\n/**\n * The same component, the same **inputs**, more than one rendering. At one commit.\n *\n * Not a regression — there is no baseline anywhere in it. It says the component's\n * own inputs do not determine its output, which is either a fact about the design\n * (a token, a theme, an ancestor's cascade) or a reading that is not repeatable.\n * `bands` says which kind, in the vocabulary a sensitivity absorbs, so a\n * divergence entirely inside a relaxed band can be dismissed without opening it.\n *\n * *Inputs*, not *props*: a props digest excludes `children`, and `divergencesOf`\n * in `core` refuses every pair the exclusion could explain. An empty list is a\n * real and common answer — `examples/todomvc` produces exactly zero — and it\n * means nothing in the suite rendered two ways from one input.\n */\nexport interface DivergenceRecord {\n readonly component: string;\n readonly bands: readonly string[];\n\n /**\n * The subjects behind each distinct rendering, widest group first. At least two.\n *\n * Grouped rather than counted, and that is the whole record. A divergence says\n * one input produced more than one output, and the only sentence a reader can\n * act on is *which* subjects sit on each side of it — `price` and `receipt`\n * render one way, `promo` renders another, so `promo` is the one to open. A\n * count with a flat subject list beside it, which is what this was, names every\n * subject involved and withholds the only thing distinguishing them: the reader\n * has three names, knows two of them agree, and cannot tell which two.\n *\n * Deduplicated per group, because a rendering reached twice in one subject is\n * one subject to go and look at. `renderings.length` is the count this replaced.\n */\n readonly renderings: readonly (readonly string[])[];\n\n /**\n * Why each rendering after the first parted from it.\n *\n * The sentence, under the split. `renderings` says *promo is the odd one out*;\n * this says *`Price` inherited a different `color` — an ancestor declared it*,\n * which is the difference between a reader knowing where to look and knowing\n * what they will find when they get there.\n *\n * Absent when the run kept no documents to read. Empty is never written.\n */\n readonly partings?: readonly PartingRecord[];\n}\n\n/** One rendering of a divergence, spoken. */\nexport interface PartingRecord {\n /** Index into {@link DivergenceRecord.renderings}, always at least 1. */\n readonly rendering: number;\n /** Already phrased for a reader; the first line is the triage slice. */\n readonly lines: readonly string[];\n}\n\n/**\n * Why one component moved in one subject, and what the suite held against it.\n *\n * The ladder is `edited` → `token` → `upstream` → `contradicted` → `unexplained`,\n * and the last one is the finding. See\n * [`flakiness.md`](../../../docs/flakiness.md) for what it is and is not allowed\n * to conclude.\n */\nexport interface MovementRecord {\n readonly subject: string;\n readonly component: string;\n /** Empty means *not known* — a name-only comparison — never *no band*. */\n readonly bands: readonly string[];\n readonly cause: 'edited' | 'token' | 'upstream' | 'contradicted' | 'unexplained';\n /** One sentence, naming the evidence rather than the category. */\n readonly because: string;\n\n readonly file?: string;\n readonly tokens?: readonly string[];\n readonly upstream?: string;\n\n /**\n * Components between `upstream` and this one, outermost first.\n *\n * Present only on the `upstream` rung, and absent when the edited component\n * draws this one directly. A reviewer sent to `ProductCard` because a\n * `CardFooter` moved has exactly one question next — *how does that reach\n * this* — and the answer is a couple of names the run already walked.\n */\n readonly through?: readonly string[];\n\n /** Other subjects this same component moved in. The \"one cause, N subjects\" fold. */\n readonly alsoIn: readonly string[];\n\n /**\n * Subjects where the same component, with the same props, did **not** move.\n *\n * The control group — the *stable states to refer to*. Empty means the suite\n * offered no control, which weakens an unexplained movement rather than\n * strengthening it, and is why this is a list and not a flag.\n */\n readonly held: readonly string[];\n\n /**\n * Renders of this component with the same props, other than this one, that the\n * run read.\n *\n * The denominator `held` is a numerator of. Without it an empty control group\n * is two opposite findings wearing one shape — nothing to compare against, or a\n * comparison made in every render and answering the same way — and a reader\n * that guesses picks the wrong one on any suite where the change reached\n * everything.\n *\n * Optional because a report written before this was carried has no answer, and\n * `0` is an answer.\n */\n readonly compared?: number;\n\n /**\n * For an unexplained movement only: whether the subject was already proven\n * unstable in this run.\n *\n * `flake` means the subject failed to read the same way twice *and* nothing in\n * the run explains what moved — the two halves of the sentence, together, for\n * the first time. `suspect` means nobody has read it twice yet, so it is a\n * shortlist entry and not a verdict.\n *\n * Absent on every explained movement, where the question does not arise.\n */\n readonly standing?: 'flake' | 'suspect';\n}\n\n/**\n * What one run learned by comparing its subjects to each other.\n *\n * Absent from a report whose collection produced no semantic snapshots — a\n * raster-only tier has no boundaries to join, and an empty graph would read as\n * \"this suite shares nothing\", which is a different and false claim.\n */\nexport interface CompositionReport {\n /** Subjects that contributed a snapshot, in plan order. The denominator. */\n readonly subjects: readonly string[];\n /** Sorted by name, code-unit order. */\n readonly components: readonly ComponentRecord[];\n /** Widest first. Capped — see `truncated`. */\n readonly echoes: readonly EchoRecord[];\n /** Sorted by component name. */\n readonly divergences: readonly DivergenceRecord[];\n /** Every component the run found moved, in the order the observations came. */\n readonly movements: readonly MovementRecord[];\n\n /**\n * What was left out of the lists above, when anything was.\n *\n * A cap that says nothing is a cap that reads as coverage. Present only when a\n * list was shortened, and counts what did not make it rather than what did.\n */\n readonly truncated?: {\n readonly echoes: number;\n };\n}\n"]}
@@ -0,0 +1,266 @@
1
+ /**
2
+ * What the config *declared*, and what each declaration did in this run.
3
+ *
4
+ * The two ledgers a run keeps about its own settings. An ignore says *part of
5
+ * this page is not my subject*; a sensitivity says *assert on this much of it*.
6
+ * Both make a run less observant on purpose, and both are only safe because
7
+ * every run counts what they absorbed — a declaration that stopped being needed
8
+ * is invisible without a count of zero, and a mask that outlives its cause grows
9
+ * quietly over a real regression.
10
+ *
11
+ * They live here rather than with the command that computes them for the reason
12
+ * the rest of this package exists: they have several readers. The CLI folds them,
13
+ * a terminal prints them, an HTML page tabulates them, and a review service
14
+ * stores them so the audit survives the machine the run happened on. A shape
15
+ * owned by the first of those bends towards a terminal.
16
+ *
17
+ * ## One decision, taken once
18
+ *
19
+ * A rule's *state* — spent, mistyped, expired, or simply working — is a reading
20
+ * of the counts, not another count. It is taken by {@link ignoreState} and
21
+ * {@link sensitivityState} and nowhere else, because a second reader that
22
+ * re-derived it would eventually disagree: the first HTML report to try shipped
23
+ * `pixels === 0` as the whole test for *dead*, which calls every rule in a fresh
24
+ * checkout dead, on the run that proves least about any of them.
25
+ */
26
+ /** What one ignore rule did this run. */
27
+ export interface IgnoreUsage {
28
+ readonly rule: string;
29
+ readonly reason: string;
30
+ /** Changed pixels this rule absorbed across the run. */
31
+ readonly pixels: number;
32
+ /** Subjects where it excluded something, whether or not it absorbed anything. */
33
+ readonly subjects: number;
34
+ /**
35
+ * Subjects where it excluded something **and a comparison happened**.
36
+ *
37
+ * The denominator that stops "absorbed nothing" from being an accusation. A
38
+ * subject that is `new`, `incomparable`, or settled from a digest compared no
39
+ * pixels, so an ignore over it had nothing to absorb — which says nothing at
40
+ * all about whether the rule is still needed. Without this a fresh checkout
41
+ * reported every ignore in the config as dead.
42
+ */
43
+ readonly comparedIn: number;
44
+ /** Subjects where it excluded something and absorbed nothing there. */
45
+ readonly inertIn: number;
46
+ /** `true` when it never resolved to a place in any subject. */
47
+ readonly unresolved: boolean;
48
+ /**
49
+ * Tags the rule names that no subject in this run wears.
50
+ *
51
+ * The only defence a tag has. A misspelled *key* is refused by name because
52
+ * the config's objects are closed; a misspelled *tag* is a legal word that
53
+ * simply matches nothing, and the rule then silently applies nowhere while the
54
+ * operator reads their config and believes it applies somewhere. So the words
55
+ * nothing answered to are named, with the near-misses that were present, and
56
+ * an empty list is the ordinary case rather than the interesting one.
57
+ */
58
+ readonly unwornTags: readonly string[];
59
+ /** `true` when it is past `until` and no longer absorbing. */
60
+ readonly expired: boolean;
61
+ }
62
+ /** Every ignore rule the config named, and what the run as a whole absorbed. */
63
+ export interface IgnoreLedger {
64
+ readonly rules: readonly IgnoreUsage[];
65
+ /**
66
+ * Rules that absorbed nothing anywhere this run.
67
+ *
68
+ * The list an operator is meant to act on. Named separately rather than left
69
+ * to be derived, because a derivation nobody writes is a report nobody reads.
70
+ */
71
+ readonly dead: readonly string[];
72
+ /** Subjects whose only differences were absorbed. Green, and not `unchanged`. */
73
+ readonly fullyIgnored: readonly string[];
74
+ readonly totalPixels: number;
75
+ /** Every tag worn by a subject this run planned, for the near-miss hint. */
76
+ readonly vocabulary: readonly string[];
77
+ }
78
+ /** What one sensitivity rule did this run. */
79
+ export interface SensitivityUsage {
80
+ readonly rule: string;
81
+ readonly reason: string;
82
+ readonly level: string;
83
+ /** Subjects this rule was in scope for, whether or not it absorbed them. */
84
+ readonly scoped: number;
85
+ /** Subjects whose verdict it decided. */
86
+ readonly absorbed: readonly string[];
87
+ /** Bands it absorbed, across every subject it decided. */
88
+ readonly bands: readonly string[];
89
+ /**
90
+ * `true` when the rule matched no subject this run planned.
91
+ *
92
+ * A different failure from absorbing nothing, and worth its own word: a rule
93
+ * naming `route/*` in a project whose subjects are all `story:*` is a typo,
94
+ * not a policy that has outlived its cause.
95
+ */
96
+ readonly unscoped: boolean;
97
+ }
98
+ /** Every sensitivity rule the config named, and how much they relaxed. */
99
+ export interface SensitivityLedger {
100
+ readonly rules: readonly SensitivityUsage[];
101
+ readonly totalAbsorbed: number;
102
+ }
103
+ /**
104
+ * What an ignore rule turned out to be, read from what it did.
105
+ *
106
+ * - `expired` — past its date. The differences it used to absorb are back.
107
+ * - `unworn` — it names a tag no subject in this run wears, so it applied nowhere.
108
+ * - `unresolved` — its selector matched no element in any subject.
109
+ * - `untested` — it excluded a subtree, and nothing it covered was compared.
110
+ * - `dead` — it excluded a subtree in subjects that *were* compared, and took nothing.
111
+ * - `live` — it absorbed something.
112
+ *
113
+ * The order is the order the checks run in, and it matters: an expired rule that
114
+ * also names a mistyped tag is expired, because that is the fact that explains
115
+ * the other. `untested` and `dead` are the pair worth keeping apart — the first
116
+ * is an absence of evidence and the second is evidence of absence, and a report
117
+ * that spells both *dead* tells an operator to delete a rule on the run that
118
+ * proves least about it.
119
+ */
120
+ export type IgnoreState = 'expired' | 'unworn' | 'unresolved' | 'untested' | 'dead' | 'live';
121
+ /**
122
+ * What a sensitivity rule turned out to be.
123
+ *
124
+ * - `unscoped` — it matched no subject this run planned.
125
+ * - `dead` — it was in scope and decided nothing; nothing here needed relaxing.
126
+ * - `live` — it decided at least one verdict.
127
+ */
128
+ export type SensitivityState = 'unscoped' | 'dead' | 'live';
129
+ /** The single reading of an ignore's counts. Every surface asks this one. */
130
+ export declare function ignoreState(entry: IgnoreUsage): IgnoreState;
131
+ /** The single reading of a sensitivity's counts. */
132
+ export declare function sensitivityState(entry: SensitivityUsage): SensitivityState;
133
+ /**
134
+ * Whether a state names something to do about the config.
135
+ *
136
+ * `untested` is deliberately not one. It is the absence of evidence, and a
137
+ * report that flagged it would ask an operator to act on a run that measured
138
+ * nothing — which is how audits stop being read.
139
+ */
140
+ export declare function isActionable(state: IgnoreState | SensitivityState): boolean;
141
+ /**
142
+ * The sentence behind an ignore's state.
143
+ *
144
+ * Here rather than in a renderer for the reason the state itself is here: an
145
+ * HTML table, a terminal and a review page all have to answer *why is this rule
146
+ * marked* and there is one answer. Two of them writing their own is how a report
147
+ * and a service come to disagree about the same run in front of the same person.
148
+ *
149
+ * The near-miss on `unworn` is the part worth carrying: the likeliest cause of a
150
+ * tag nothing wears is a typo, and naming the tags that *are* worn turns a
151
+ * report into a fix.
152
+ */
153
+ export declare function ignoreSays(entry: IgnoreUsage, ledger: IgnoreLedger): string;
154
+ /**
155
+ * The run-level line under the ignore table, vocabulary included.
156
+ *
157
+ * The last clause is the one that has to be there. With no vocabulary the
158
+ * unworn-tag check did not run at all, and a footer that said nothing about it
159
+ * would read as every tag having been checked and every tag having been found.
160
+ *
161
+ * The first clause counts the rules that *absorbed*, over the rules that were
162
+ * declared, and the two are only the same number on a run where every rule
163
+ * worked. Counting declarations there put the whole run's pixels behind the size
164
+ * of the config: two rules, one of them matching nothing, read as *647 pixels
165
+ * absorbed by 2 rules* directly above a table saying one of them resolved
166
+ * nowhere. The table is the audit; the footer must not contradict it.
167
+ */
168
+ export declare function ignoreTotals(ledger: IgnoreLedger): string;
169
+ /** The sentence behind a sensitivity's state. */
170
+ export declare function sensitivitySays(entry: SensitivityUsage): string;
171
+ /**
172
+ * The run-level line under the sensitivity table.
173
+ *
174
+ * Rules that decided something, over rules declared — the same reading as
175
+ * {@link ignoreTotals}, and for the same reason. *0 subjects not asserted on in
176
+ * full, by 1 rule* is a sentence with a rule in it that did nothing.
177
+ */
178
+ export declare function sensitivityTotals(ledger: SensitivityLedger): string;
179
+ /**
180
+ * How many of the declared ignores absorbed anything, over how many were written.
181
+ *
182
+ * A phrase rather than a number because there are four footers printing it — an
183
+ * HTML report, a review page and two terminal summaries — and the arithmetic is
184
+ * the part that has to be identical between them. Whether a rule counts here is
185
+ * the same reading {@link ignoreState} takes, and a surface that re-derived it
186
+ * would eventually take a different one.
187
+ */
188
+ export declare function ignoreShare(ledger: IgnoreLedger): string;
189
+ /** The same reading for sensitivities: rules that decided a verdict, over rules written. */
190
+ export declare function sensitivityShare(ledger: SensitivityLedger): string;
191
+ /**
192
+ * The bands a rule absorbed, or the fact that they were not kept.
193
+ *
194
+ * `undefined` is the third answer, and it is the whole reason this is a function
195
+ * rather than a field read. A rule that decided verdicts with no band recorded is
196
+ * not a rule that absorbed nothing — it is a report written before the bands were
197
+ * kept, or by a writer that dropped them, and an empty list says the first.
198
+ */
199
+ export declare function absorbedBands(entry: SensitivityUsage): readonly string[] | undefined;
200
+ /**
201
+ * The part of an observation that says why it came back green.
202
+ *
203
+ * Structural rather than the whole `ObservationRecord`, because the second
204
+ * caller is a review service whose subjects arrive from a database and carry
205
+ * only what was stored. Writing the relation over the fields it actually reads
206
+ * is what lets both of them ask the same question, and stops the answer from
207
+ * being re-derived on the far side of the wire.
208
+ */
209
+ export interface GreenSubject {
210
+ readonly verdict: 'unchanged' | 'changed' | 'new' | 'incomparable' | 'ignored';
211
+ readonly ignored?: {
212
+ readonly pixels: number;
213
+ readonly boxes: number;
214
+ readonly byRule: Readonly<Record<string, number>>;
215
+ } | undefined;
216
+ readonly relaxed?: {
217
+ readonly rule: string;
218
+ readonly level: string;
219
+ readonly bands: readonly string[];
220
+ } | undefined;
221
+ }
222
+ /**
223
+ * Why one subject is green.
224
+ *
225
+ * - `measured` — nothing differed, and no declaration stood over it.
226
+ * - `masked` — nothing differed, and a rule was watching anyway. Its boxes
227
+ * caught nothing here, which is how a mask starts outliving its cause.
228
+ * - `relaxed` — it differed, and every band that moved is one this subject is
229
+ * not asserted on.
230
+ * - `absorbed` — it differed, and every differing pixel fell inside an excluded
231
+ * subtree. The rules that took them are named.
232
+ * - `unsaid` — it is `ignored` and the record does not say what did it.
233
+ */
234
+ export type Green = {
235
+ readonly kind: 'measured';
236
+ } | {
237
+ readonly kind: 'masked';
238
+ readonly boxes: number;
239
+ } | {
240
+ readonly kind: 'relaxed';
241
+ readonly rule: string;
242
+ readonly level: string;
243
+ readonly bands: readonly string[];
244
+ } | {
245
+ readonly kind: 'absorbed';
246
+ readonly rules: readonly string[];
247
+ readonly pixels: number;
248
+ } | {
249
+ readonly kind: 'unsaid';
250
+ };
251
+ /**
252
+ * What decided a green subject, in the words of whatever decided it.
253
+ *
254
+ * `unsaid` is the case this exists for. A subject reported `ignored` whose
255
+ * record carries neither block says *a declaration absorbed this* and does not
256
+ * say which — and both obvious renderings of that are false. Printing nothing
257
+ * reads as *nothing was absorbed*; printing `0 px` reads as *a rule absorbed
258
+ * nothing*. So the absence is a state, and every surface has to render it.
259
+ *
260
+ * It is reachable two ways and they are worth telling apart when reading a bug:
261
+ * an older report written before the blocks were kept, and a store that dropped
262
+ * them on the way in. The second is the one that turns a report and a service
263
+ * into two different accounts of one run.
264
+ */
265
+ export declare function greenBecause(entry: GreenSubject): Green;
266
+ //# sourceMappingURL=declarations.d.ts.map