@variance-authority/core 0.14.0 → 0.15.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 (108) hide show
  1. package/CHANGELOG.md +454 -0
  2. package/README.md +13 -10
  3. package/dist/attribute/component-hash.d.ts +3 -2
  4. package/dist/attribute/component-hash.js +3 -2
  5. package/dist/attribute/component-hash.js.map +1 -1
  6. package/dist/attribute/composition.d.ts +16 -0
  7. package/dist/attribute/composition.js +4 -6
  8. package/dist/attribute/composition.js.map +1 -1
  9. package/dist/attribute/index.d.ts +3 -1
  10. package/dist/attribute/index.js +2 -1
  11. package/dist/attribute/index.js.map +1 -1
  12. package/dist/attribute/region.js +1 -5
  13. package/dist/attribute/region.js.map +1 -1
  14. package/dist/attribute/source.d.ts +10 -24
  15. package/dist/attribute/source.js +4 -38
  16. package/dist/attribute/source.js.map +1 -1
  17. package/dist/attribute/subject-pieces.d.ts +37 -0
  18. package/dist/attribute/subject-pieces.js +122 -0
  19. package/dist/attribute/subject-pieces.js.map +1 -0
  20. package/dist/format/capture.d.ts +25 -0
  21. package/dist/format/capture.js +6 -0
  22. package/dist/format/capture.js.map +1 -1
  23. package/dist/format/document.d.ts +2 -2
  24. package/dist/format/document.js +8 -2
  25. package/dist/format/document.js.map +1 -1
  26. package/dist/format/drive.d.ts +191 -0
  27. package/dist/format/drive.js +343 -0
  28. package/dist/format/drive.js.map +1 -0
  29. package/dist/format/environment.d.ts +20 -2
  30. package/dist/format/environment.js +7 -0
  31. package/dist/format/environment.js.map +1 -1
  32. package/dist/format/index.d.ts +6 -3
  33. package/dist/format/index.js +4 -2
  34. package/dist/format/index.js.map +1 -1
  35. package/dist/format/intervention.d.ts +79 -0
  36. package/dist/format/intervention.js.map +1 -1
  37. package/dist/format/origin.d.ts +32 -0
  38. package/dist/format/origin.js +54 -0
  39. package/dist/format/origin.js.map +1 -0
  40. package/dist/format/snapshot.d.ts +2 -0
  41. package/dist/format/snapshot.js.map +1 -1
  42. package/dist/format/stabilize.d.ts +28 -5
  43. package/dist/format/stabilize.js +48 -100
  44. package/dist/format/stabilize.js.map +1 -1
  45. package/dist/format/waits.d.ts +12 -0
  46. package/dist/format/waits.js +156 -0
  47. package/dist/format/waits.js.map +1 -0
  48. package/dist/index.d.ts +2 -1
  49. package/dist/index.js +2 -1
  50. package/dist/index.js.map +1 -1
  51. package/dist/nearest.d.ts +46 -0
  52. package/dist/nearest.js +102 -0
  53. package/dist/nearest.js.map +1 -0
  54. package/dist/relate/before.d.ts +19 -24
  55. package/dist/relate/before.js +35 -37
  56. package/dist/relate/before.js.map +1 -1
  57. package/dist/relate/beyond.d.ts +59 -0
  58. package/dist/relate/beyond.js +98 -0
  59. package/dist/relate/beyond.js.map +1 -0
  60. package/dist/relate/carried.d.ts +122 -0
  61. package/dist/relate/carried.js +279 -0
  62. package/dist/relate/carried.js.map +1 -0
  63. package/dist/relate/dominate.d.ts +19 -0
  64. package/dist/relate/dominate.js +77 -0
  65. package/dist/relate/dominate.js.map +1 -0
  66. package/dist/relate/index.d.ts +3 -0
  67. package/dist/relate/index.js +3 -0
  68. package/dist/relate/index.js.map +1 -1
  69. package/dist/relate/merkle.js +2 -11
  70. package/dist/relate/merkle.js.map +1 -1
  71. package/dist/relate/narrow.d.ts +2 -2
  72. package/dist/relate/narrow.js +26 -73
  73. package/dist/relate/narrow.js.map +1 -1
  74. package/dist/relate/reach.d.ts +1 -1
  75. package/dist/relate/reach.js +10 -53
  76. package/dist/relate/reach.js.map +1 -1
  77. package/dist/relate/records.d.ts +1 -1
  78. package/dist/relate/records.js.map +1 -1
  79. package/dist/rules/index.d.ts +3 -3
  80. package/dist/rules/index.js +3 -3
  81. package/dist/rules/index.js.map +1 -1
  82. package/dist/rules/normalize/cascade.d.ts +12 -0
  83. package/dist/rules/normalize/cascade.js +34 -16
  84. package/dist/rules/normalize/cascade.js.map +1 -1
  85. package/dist/rules/normalize/fixture.d.ts +1 -0
  86. package/dist/rules/normalize/fixture.js +1 -0
  87. package/dist/rules/normalize/fixture.js.map +1 -1
  88. package/dist/rules/normalize/index.d.ts +2 -2
  89. package/dist/rules/normalize/index.js +9 -2
  90. package/dist/rules/normalize/index.js.map +1 -1
  91. package/dist/rules/normalize/value.d.ts +13 -2
  92. package/dist/rules/normalize/value.js +42 -8
  93. package/dist/rules/normalize/value.js.map +1 -1
  94. package/dist/rules/normalize/wrapper.js +5 -0
  95. package/dist/rules/normalize/wrapper.js.map +1 -1
  96. package/dist/rules/ruleset.d.ts +24 -4
  97. package/dist/rules/ruleset.js +24 -4
  98. package/dist/rules/ruleset.js.map +1 -1
  99. package/dist/segment/index.d.ts +20 -4
  100. package/dist/segment/index.js +28 -7
  101. package/dist/segment/index.js.map +1 -1
  102. package/dist/shard/index.d.ts +11 -0
  103. package/dist/shard/index.js +11 -0
  104. package/dist/shard/index.js.map +1 -0
  105. package/dist/shard/placement.d.ts +117 -0
  106. package/dist/shard/placement.js +170 -0
  107. package/dist/shard/placement.js.map +1 -0
  108. package/package.json +8 -1
@@ -1 +1 @@
1
- {"version":3,"file":"component-hash.js","sourceRoot":"","sources":["../../src/attribute/component-hash.ts"],"names":[],"mappings":"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","sourcesContent":["import { BANDS, type Band } from '../compare/band.js';\nimport { declaredIn } from '../compare/cascade.js';\nimport type { CanonicalValue } from '../format/canonical.js';\nimport type { Rect } from '../format/capture.js';\nimport type { Digest } from '../format/hash.js';\nimport { digestValue } from '../format/hash.js';\nimport type { ComponentHash, SemanticSnapshot } from '../format/snapshot.js';\nimport { boundaries, shapeOf, UNATTRIBUTED } from './boundary.js';\n\nexport type { ComponentHash } from '../format/snapshot.js';\nexport { UNATTRIBUTED } from './boundary.js';\n\n/**\n * The digests a comparison of two component states actually reads.\n *\n * `movedBands` used to take two `ComponentHash`es, which carry a name and an\n * instance count neither side of the comparison consults. Narrowing it to the\n * digests is what lets a per-instance record (`ComponentInstance`, which has no\n * `instances` count because it *is* one) be compared by the same function —\n * rather than by a second copy of the band mapping, which is the one thing in\n * this file that must not exist twice: a subject relaxed to `layout` has to\n * absorb the same bands whichever shape the caller happened to be holding.\n */\nexport interface BandDigests {\n readonly structure: Digest;\n readonly semantics: Digest;\n readonly text: Digest;\n readonly style: Digest;\n readonly geometry?: Digest;\n}\n\n/**\n * Per-component content hashes, one per band.\n *\n * `SemanticSnapshot` hashes a whole subject. That answers \"did anything change\"\n * and nothing else: it moves whenever anything inside it moves, so it cannot say\n * *which area*, and a record built on it says \"the page changed\" on every commit.\n *\n * These hashes are the unit a history is kept in. Text, and small — a component\n * that did not change contributes the same digest it did last time, so the only\n * thing worth recording is the difference.\n */\n\n/**\n * Hash every component boundary in a subject.\n *\n * Ordered by component name so an unchanged subject produces byte-identical\n * output across runs.\n *\n * The walk and the per-boundary shape live in {@link ./boundary.js}, which\n * {@link ./instances.js} shares — one definition of what a component's own\n * content is, read by the aggregate and by the per-instance form.\n */\nexport function hashComponents(snapshot: SemanticSnapshot): readonly ComponentHash[] {\n const layout = snapshot.profile.layout;\n const declared = declaredIn(snapshot);\n\n const accumulated = new Map<\n string,\n {\n structure: CanonicalValue[];\n semantics: CanonicalValue[];\n text: CanonicalValue[];\n style: CanonicalValue[];\n geometry: CanonicalValue[];\n boxes: (Rect | null)[];\n values: Map<string, Set<string>>;\n }\n >();\n\n for (const boundary of boundaries(snapshot.root)) {\n const shape = shapeOf(boundary, layout, undefined, declared);\n\n const entry = accumulated.get(boundary.component) ?? {\n structure: [],\n semantics: [],\n text: [],\n style: [],\n geometry: [],\n boxes: [],\n values: new Map<string, Set<string>>(),\n };\n entry.structure.push(shape.structure);\n entry.semantics.push(shape.semantics);\n entry.text.push(shape.text);\n entry.style.push(shape.style);\n entry.geometry.push(shape.geometry);\n entry.boxes.push(shape.box);\n for (const [property, values] of Object.entries(shape.values ?? {})) {\n const seen = entry.values.get(property) ?? new Set<string>();\n for (const value of values) seen.add(value);\n entry.values.set(property, seen);\n }\n accumulated.set(boundary.component, entry);\n }\n\n return [...accumulated.entries()]\n .map(([component, entry]) => ({\n component,\n instances: entry.structure.length,\n structure: digestValue(entry.structure),\n semantics: digestValue(entry.semantics),\n text: digestValue(entry.text),\n style: digestValue(entry.style),\n ...(layout ? { geometry: digestValue(entry.geometry) } : {}),\n // Under a profile with no layout every entry is `null`, and a list of\n // nulls is a measurement nobody took written as one they did. Absent\n // instead, which is the same rule `geometry` above obeys.\n ...(layout ? { boxes: entry.boxes } : {}),\n values: sortedValues(entry.values),\n }))\n // Code-unit order, not `localeCompare`. The doc above promises byte-identical\n // output for an unchanged subject, and a locale-aware comparison makes that a\n // promise about the machine's `LANG` — which was tolerable while these were\n // internal and is not now that they are written into a sidecar, committed\n // beside a baseline, and read back on someone else's runner.\n .sort((a, b) => byCodeUnit(a.component, b.component));\n}\n\n/** Properties and their values in code-unit order, so the sidecar is byte-stable. */\nfunction sortedValues(values: ReadonlyMap<string, ReadonlySet<string>>): Record<string, string[]> {\n const sorted: Record<string, string[]> = {};\n for (const property of [...values.keys()].sort(byCodeUnit)) {\n sorted[property] = [...values.get(property)!].sort(byCodeUnit);\n }\n return sorted;\n}\n\nfunction byCodeUnit(a: string, b: string): number {\n return a < b ? -1 : a > b ? 1 : 0;\n}\n\n/**\n * Which components *caused* a change, given both revisions' hashes.\n *\n * The arithmetic behind cause-first ranking, and the reason a baseline carries\n * its component hashes at all. `rankRegions` takes a list of cause names and,\n * given none, falls back to area — which\n * [journal 0013](../../../../docs/context/journal/0013-observability.md)\n * measured as backwards by 6×, because area measures displacement rather than\n * cause. This is where that list comes from on a path with no second document.\n *\n * The distinction is the split ADR-0018 built the bands for:\n *\n * - **`structure` or `style` moved** — the component's own content is different.\n * It is a cause.\n * - **only `geometry` moved** — the component is byte-identical and its box is\n * somewhere else. Something *else* moved it, so it is collateral, and naming\n * it would send a reviewer to a file nobody edited.\n * - **appeared or disappeared** — a component present on one side only is a\n * cause. Something decided to render it or to stop.\n *\n * `instances` is deliberately not consulted. A component rendered five times\n * instead of four has a different `structure` digest for the subject, and the\n * count moving on its own — the same component, the same content, one more of\n * them — is a change in whatever decided how many, not in this component.\n *\n * A profile with no layout supplies no `geometry`, so on that tier every\n * difference is `structure` or `style` and every changed component is a cause.\n * That is correct rather than degraded: with no boxes, nothing was displaced.\n */\nexport function causesBetween(\n before: readonly ComponentHash[],\n after: readonly ComponentHash[],\n): readonly string[] {\n const previous = new Map(before.map((entry) => [entry.component, entry]));\n const causes: string[] = [];\n\n for (const entry of after) {\n // `(unattributed)` is not a component and can never be a cause. It is the\n // bucket for nodes whose provenance chain broke, so it collects unrelated\n // parts of a page under one name — and nothing downstream could act on it\n // anyway: a region with no owner reports no component, so it would never\n // match. A broken chain is a defect in this tool and is reported as\n // `unattributed` where that means something, not smuggled in here as a\n // culprit.\n if (entry.component === UNATTRIBUTED) continue;\n\n const was = previous.get(entry.component);\n if (was === undefined) {\n causes.push(entry.component);\n continue;\n }\n if (ownContentMoved(was, entry)) causes.push(entry.component);\n }\n\n // Removals too, and they are the case a candidate-only walk cannot see: a\n // component that stopped rendering leaves regions behind it, and the component\n // that used to be there is exactly the name a reviewer needs.\n const present = new Set(after.map((entry) => entry.component));\n for (const entry of before) {\n if (entry.component === UNATTRIBUTED) continue;\n if (!present.has(entry.component)) causes.push(entry.component);\n }\n\n return causes.sort();\n}\n\n/**\n * Whether a component's *own content* differs, ignoring where its box ended up.\n *\n * Deliberately not phrased in bands, because it is not a band question. Both\n * `structure` and `geometry` map to the `geometry` band, and this has to keep\n * them apart: a component whose tree changed edited itself, and a component\n * whose rect moved was pushed. That distinction is the entire cause/collateral\n * result, and asking it through the band mapping would need the digests back\n * again to answer it.\n *\n * The list is every digest a component owns except `geometry`. It grew by two on\n * 2026-08-06 without changing meaning: `semantics` and `text` used to be inside\n * `structure`.\n */\nfunction ownContentMoved(before: BandDigests, after: BandDigests): boolean {\n return (\n before.structure !== after.structure ||\n before.semantics !== after.semantics ||\n before.text !== after.text ||\n before.style !== after.style\n );\n}\n\n/**\n * Which frequency bands moved between one component's two hashes.\n *\n * The reason the digests were split. A baseline carries hashes and not\n * documents, so \"what changed here\" used to be answerable only as a boolean —\n * and a boolean cannot serve a route-level test, whose entire request is *tell\n * me when the page stops assembling and never when it is repainted*.\n *\n * The mapping is exact and it is the same one `bandOf` applies to a delta, which\n * is the property that matters: a subject relaxed to `layout` must absorb the\n * same things whether the run held two documents or two sidecars. Two mappings\n * would be one drift away from a config key meaning different things on the two\n * paths, discovered as a regression somebody let through.\n *\n * | digest | band | what it covers |\n * |---|---|---|\n * | `semantics` | `a11y` | role, accessible name, ARIA state |\n * | `text` | `content` | text runs |\n * | `structure` | `geometry` | tags, aliases, attributes, child boundaries |\n * | `geometry` | `geometry` | rects and computed layout output |\n * | `style` | `token` | declared values and custom properties |\n *\n * `texture` never appears. It is raster residue by definition, and a component\n * hash is built from a document — so the band a comparison of hashes cannot\n * decide is *absent* from the answer rather than reported as unmoved, which is\n * ADR-0002's rule applied to a narrower question.\n *\n * A missing `geometry` on either side is the profile saying it has no layout\n * engine, and is not a difference. Treating absent as a change would report\n * every component as having moved the moment a jsdom baseline met a Chromium\n * run — which the environment key already refuses as `incomparable`, so this\n * would be a second, wronger answer to a question already settled.\n */\nexport function movedBands(before: BandDigests, after: BandDigests): readonly Band[] {\n const moved = new Set<Band>();\n\n if (before.semantics !== after.semantics) moved.add('a11y');\n if (before.text !== after.text) moved.add('content');\n if (before.structure !== after.structure) moved.add('geometry');\n if (before.style !== after.style) moved.add('token');\n if (\n before.geometry !== undefined &&\n after.geometry !== undefined &&\n before.geometry !== after.geometry\n ) {\n moved.add('geometry');\n }\n\n return BANDS.filter((band) => moved.has(band));\n}\n\n/**\n * Every band that moved anywhere in the subject, given both revisions' hashes.\n *\n * A component present on one side only contributes `geometry`: something was\n * added or removed, which is the structural half of that band however the rest\n * of it compares. It deliberately does not contribute `a11y` or `content` as\n * well — a component that is simply not there did not *rename* anything, and\n * inflating the answer would make a level that absorbs nothing look like the\n * only safe choice.\n */\nexport function bandsBetween(\n before: readonly ComponentHash[],\n after: readonly ComponentHash[],\n): readonly Band[] {\n const previous = new Map(before.map((entry) => [entry.component, entry]));\n const present = new Set(after.map((entry) => entry.component));\n const moved = new Set<Band>();\n\n for (const entry of after) {\n const was = previous.get(entry.component);\n if (was === undefined) moved.add('geometry');\n else for (const band of movedBands(was, entry)) moved.add(band);\n }\n\n for (const entry of before) {\n if (!present.has(entry.component)) moved.add('geometry');\n }\n\n return BANDS.filter((band) => moved.has(band));\n}\n\n/**\n * Which bands moved, kept per component instead of folded into one list.\n *\n * {@link bandsBetween} answers *what kind of change is in this subject*, which is\n * the question a sensitivity level asks. It cannot answer the one a reviewer\n * asks — *what changed, and where* — because the fold is lossy in exactly the\n * place attribution lives: a subject reporting `content, geometry, token` has\n * told you a colour and a string and a size all moved somewhere in it, and left\n * you to guess which of the forty components on the page owns which.\n *\n * Unfolded, the same two sidecars say `Button — geometry, token` and\n * `CardFooter — a11y, content`, and that is a sentence a page can print beside a\n * picture. It is also the record that survives when the raster tier loses the\n * name: a difference that reflowed its neighbours merges into one blob whose box\n * fits no component, so the region resolves to the document root and the edit\n * arrives unattributed — while the hashes, which never looked at a pixel, still\n * hold the component that moved and the sense in which it moved.\n *\n * `cause` repeats the {@link causesBetween} predicate rather than being derived\n * from `bands`, and the repetition is the point: `structure` and `geometry` both\n * map to the `geometry` band, so a component that edited its own tree and one\n * that was merely pushed by a neighbour are indistinguishable *after* the band\n * mapping. Losing that here would make every reflowed container a culprit.\n */\nexport interface ComponentBands {\n readonly component: string;\n /** Non-empty: a component whose digests all matched has no entry at all. */\n readonly bands: readonly Band[];\n /** Its own content moved, as opposed to only its rect. `causesBetween`'s test. */\n readonly cause: boolean;\n /** Set only when the component is on one side alone, which `bands` cannot say. */\n readonly presence?: 'added' | 'removed';\n\n /**\n * How much bigger its own box got, when every instance agrees on the answer.\n *\n * The band a reviewer actually wanted. `geometry` says a rect under here is\n * not the rect it was; this says the control is eight pixels taller and\n * thirty-six wider, which is the padding somebody edited, arriving as a\n * measurement rather than as a guess about which property produced it.\n *\n * Absent for four different reasons and they are one reason: nobody can say.\n * No boxes on one side, a different number of instances, instances that\n * disagree about the delta, or a box that did not change size. Present is\n * always non-zero on at least one axis.\n */\n readonly grew?: { readonly width: number; readonly height: number };\n\n /**\n * Which declared values changed, by property, when both sides recorded them.\n *\n * `token` says a declaration under here moved; this names it. `from` holds the\n * values only the baseline had and `to` the values only this run has, so a\n * component rendered as primary and secondary that changed one background\n * reports that background and not the other. An empty side is a property no\n * node declared on that side, which was measured and is not the same as absent.\n *\n * Absent when either sidecar predates the record, and when the style digest\n * moved but no property's set of values did — a value that moved from one\n * node to another is a real change this cannot describe, and an empty list\n * would say there was none. Present is never empty.\n */\n readonly changed?: readonly ValueChange[];\n}\n\n/** One property whose set of declared values differs between two revisions. */\nexport interface ValueChange {\n readonly property: string;\n readonly from: readonly string[];\n readonly to: readonly string[];\n}\n\n/**\n * Every component whose hashes differ, with the bands it differs in.\n *\n * One-sided components contribute `geometry` and nothing else, which is\n * {@link bandsBetween}'s rule and must stay identical to it: a component that is\n * simply not there did not rename anything, and inflating the answer would put\n * an `a11y` claim on a page for a component nobody can look at. `presence` is\n * what carries the rest of that meaning, so no reader has to infer *appeared*\n * from a lone `geometry`.\n *\n * `(unattributed)` is excluded for the reason {@link causesBetween} excludes it:\n * it is a bucket for nodes whose provenance chain broke, so it collects\n * unrelated parts of a page under one name and nothing downstream could act on\n * it. The broken chain is reported where it means something, not here as a\n * component that moved.\n */\nexport function movedBandsBetween(\n before: readonly ComponentHash[],\n after: readonly ComponentHash[],\n): readonly ComponentBands[] {\n const previous = new Map(before.map((entry) => [entry.component, entry]));\n const present = new Set(after.map((entry) => entry.component));\n const moved: ComponentBands[] = [];\n\n for (const entry of after) {\n if (entry.component === UNATTRIBUTED) continue;\n const was = previous.get(entry.component);\n if (was === undefined) {\n moved.push({ component: entry.component, bands: ['geometry'], cause: true, presence: 'added' });\n continue;\n }\n const bands = movedBands(was, entry);\n if (bands.length === 0) continue;\n const grew = grewBetween(was.boxes, entry.boxes);\n const changed = was.style === entry.style ? undefined : changedBetween(was.values, entry.values);\n moved.push({\n component: entry.component,\n bands,\n cause: ownContentMoved(was, entry),\n ...(grew === undefined ? {} : { grew }),\n ...(changed === undefined ? {} : { changed }),\n });\n }\n\n for (const entry of before) {\n if (entry.component === UNATTRIBUTED) continue;\n if (present.has(entry.component)) continue;\n moved.push({ component: entry.component, bands: ['geometry'], cause: true, presence: 'removed' });\n }\n\n return moved.sort((left, right) => byCodeUnit(left.component, right.component));\n}\n\n/**\n * Every property whose values differ, or nothing when that cannot be said.\n *\n * Read only when the `style` digest moved, which is the digest these values\n * were declared into: a component whose declarations hashed the same has\n * nothing here to name, whatever else about it changed.\n */\nfunction changedBetween(\n before: ComponentHash['values'],\n after: ComponentHash['values'],\n): readonly ValueChange[] | undefined {\n if (before === undefined || after === undefined) return undefined;\n\n const changed: ValueChange[] = [];\n const properties = new Set([...Object.keys(before), ...Object.keys(after)]);\n for (const property of [...properties].sort(byCodeUnit)) {\n const was = before[property] ?? [];\n const now = after[property] ?? [];\n const from = was.filter((value) => !now.includes(value));\n const to = now.filter((value) => !was.includes(value));\n if (from.length > 0 || to.length > 0) changed.push({ property, from, to });\n }\n return changed.length === 0 ? undefined : changed;\n}\n\n/**\n * One size delta both sides agree on, or nothing.\n *\n * Instances are paired by document order, which is the only order either side\n * has. That pairing is sound exactly while the counts match: a component that\n * gained an instance shifted every index after the insertion, and the deltas\n * that fell out would be measurements of one instance against a different one.\n * So a changed count answers nothing rather than answering wrongly — the count\n * itself already moved `structure`, and the reviewer is told that instead.\n *\n * Instances that disagree also answer nothing. Three buttons where one grew and\n * two did not is a real finding and it is not *this* one, and printing the first\n * or the largest would be the page picking a representative and not saying so.\n *\n * Position is deliberately not read. Everything below a control that got taller\n * moved down, and a delta drawn from `x`/`y` would name every one of them.\n */\nfunction grewBetween(\n before: readonly (Rect | null)[] | undefined,\n after: readonly (Rect | null)[] | undefined,\n): { readonly width: number; readonly height: number } | undefined {\n if (before === undefined || after === undefined) return undefined;\n if (before.length === 0 || before.length !== after.length) return undefined;\n\n let agreed: { width: number; height: number } | undefined;\n for (const [index, was] of before.entries()) {\n const now = after[index];\n if (was === null || was === undefined || now === null || now === undefined) return undefined;\n\n const width = now.width - was.width;\n const height = now.height - was.height;\n if (agreed === undefined) agreed = { width, height };\n else if (agreed.width !== width || agreed.height !== height) return undefined;\n }\n\n return agreed === undefined || (agreed.width === 0 && agreed.height === 0) ? undefined : agreed;\n}\n"]}
1
+ {"version":3,"file":"component-hash.js","sourceRoot":"","sources":["../../src/attribute/component-hash.ts"],"names":[],"mappings":"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","sourcesContent":["import { BANDS, type Band } from '../compare/band.js';\nimport { declaredIn } from '../compare/cascade.js';\nimport type { CanonicalValue } from '../format/canonical.js';\nimport type { Rect } from '../format/capture.js';\nimport type { Digest } from '../format/hash.js';\nimport { digestValue } from '../format/hash.js';\nimport type { ComponentHash, SemanticSnapshot } from '../format/snapshot.js';\nimport { boundaries, shapeOf, UNATTRIBUTED } from './boundary.js';\n\nexport type { ComponentHash } from '../format/snapshot.js';\nexport { UNATTRIBUTED } from './boundary.js';\n\n/**\n * The digests a comparison of two component states actually reads.\n *\n * `movedBands` used to take two `ComponentHash`es, which carry a name and an\n * instance count neither side of the comparison consults. Narrowing it to the\n * digests is what lets a per-instance record (`ComponentInstance`, which has no\n * `instances` count because it *is* one) be compared by the same function —\n * rather than by a second copy of the band mapping, which is the one thing in\n * this file that must not exist twice: a subject relaxed to `layout` has to\n * absorb the same bands whichever shape the caller happened to be holding.\n */\nexport interface BandDigests {\n readonly structure: Digest;\n readonly semantics: Digest;\n readonly text: Digest;\n readonly style: Digest;\n readonly geometry?: Digest;\n}\n\n/**\n * Per-component content hashes, one per band.\n *\n * `SemanticSnapshot` hashes a whole subject. That answers \"did anything change\"\n * and nothing else: it moves whenever anything inside it moves, so it cannot say\n * *which area*, and a record built on it says \"the page changed\" on every commit.\n *\n * These hashes are the unit a history is kept in. Text, and small — a component\n * that did not change contributes the same digest it did last time, so the only\n * thing worth recording is the difference.\n */\n\n/**\n * Hash every component boundary in a subject.\n *\n * Ordered by component name so an unchanged subject produces byte-identical\n * output across runs.\n *\n * The walk and the per-boundary shape live in {@link ./boundary.js}, which\n * {@link ./instances.js} shares — one definition of what a component's own\n * content is, read by the aggregate and by the per-instance form.\n */\nexport function hashComponents(snapshot: SemanticSnapshot): readonly ComponentHash[] {\n const layout = snapshot.profile.layout;\n const declared = declaredIn(snapshot);\n\n const accumulated = new Map<\n string,\n {\n structure: CanonicalValue[];\n semantics: CanonicalValue[];\n text: CanonicalValue[];\n style: CanonicalValue[];\n geometry: CanonicalValue[];\n boxes: (Rect | null)[];\n values: Map<string, Set<string>>;\n }\n >();\n\n for (const boundary of boundaries(snapshot.root)) {\n const shape = shapeOf(boundary, layout, undefined, declared);\n\n const entry = accumulated.get(boundary.component) ?? {\n structure: [],\n semantics: [],\n text: [],\n style: [],\n geometry: [],\n boxes: [],\n values: new Map<string, Set<string>>(),\n };\n entry.structure.push(shape.structure);\n entry.semantics.push(shape.semantics);\n entry.text.push(shape.text);\n entry.style.push(shape.style);\n entry.geometry.push(shape.geometry);\n entry.boxes.push(shape.box);\n for (const [property, values] of Object.entries(shape.values ?? {})) {\n const seen = entry.values.get(property) ?? new Set<string>();\n for (const value of values) seen.add(value);\n entry.values.set(property, seen);\n }\n accumulated.set(boundary.component, entry);\n }\n\n return [...accumulated.entries()]\n .map(([component, entry]) => ({\n component,\n instances: entry.structure.length,\n structure: digestValue(entry.structure),\n semantics: digestValue(entry.semantics),\n text: digestValue(entry.text),\n style: digestValue(entry.style),\n ...(layout ? { geometry: digestValue(entry.geometry) } : {}),\n // Under a profile with no layout every entry is `null`, and a list of\n // nulls is a measurement nobody took written as one they did. Absent\n // instead, which is the same rule `geometry` above obeys.\n ...(layout ? { boxes: entry.boxes } : {}),\n values: sortedValues(entry.values),\n }))\n // Code-unit order, not `localeCompare`. The doc above promises byte-identical\n // output for an unchanged subject, and a locale-aware comparison makes that a\n // promise about the machine's `LANG` — which was tolerable while these were\n // internal and is not now that they are written into a sidecar, committed\n // beside a baseline, and read back on someone else's runner.\n .sort((a, b) => byCodeUnit(a.component, b.component));\n}\n\n/** Properties and their values in code-unit order, so the sidecar is byte-stable. */\nfunction sortedValues(values: ReadonlyMap<string, ReadonlySet<string>>): Record<string, string[]> {\n const sorted: Record<string, string[]> = {};\n for (const property of [...values.keys()].sort(byCodeUnit)) {\n sorted[property] = [...values.get(property)!].sort(byCodeUnit);\n }\n return sorted;\n}\n\nfunction byCodeUnit(a: string, b: string): number {\n return a < b ? -1 : a > b ? 1 : 0;\n}\n\n/**\n * Which components *caused* a change, given both revisions' hashes.\n *\n * The arithmetic behind cause-first ranking, and the reason a baseline carries\n * its component hashes at all. `rankRegions` takes a list of cause names and,\n * given none, falls back to area — which\n * [journal 0013](../../../../docs/context/journal/0013-observability.md)\n * measured as backwards by 6×, because area measures displacement rather than\n * cause. This is where that list comes from on a path with no second document.\n *\n * The distinction is the split ADR-0018 built the bands for:\n *\n * - **`structure` or `style` moved** — the component's own content is different.\n * It is a cause.\n * - **only `geometry` moved** — the component is byte-identical and its box is\n * somewhere else. Something *else* moved it, so it is collateral, and naming\n * it would send a reviewer to a file nobody edited.\n * - **appeared or disappeared** — a component present on one side only is a\n * cause. Something decided to render it or to stop.\n *\n * `instances` is deliberately not consulted. A component rendered five times\n * instead of four has a different `structure` digest for the subject, and the\n * count moving on its own — the same component, the same content, one more of\n * them — is a change in whatever decided how many, not in this component.\n *\n * A profile with no layout supplies no `geometry`, so on that tier every\n * difference is `structure` or `style` and every changed component is a cause.\n * That is correct rather than degraded: with no boxes, nothing was displaced.\n */\nexport function causesBetween(\n before: readonly ComponentHash[],\n after: readonly ComponentHash[],\n): readonly string[] {\n const previous = new Map(before.map((entry) => [entry.component, entry]));\n const causes: string[] = [];\n\n for (const entry of after) {\n // `(unattributed)` is not a component and can never be a cause. It is the\n // bucket for nodes whose provenance chain broke, so it collects unrelated\n // parts of a page under one name — and nothing downstream could act on it\n // anyway: a region with no owner reports no component, so it would never\n // match. A broken chain is a defect in this tool and is reported as\n // `unattributed` where that means something, not smuggled in here as a\n // culprit.\n if (entry.component === UNATTRIBUTED) continue;\n\n const was = previous.get(entry.component);\n if (was === undefined) {\n causes.push(entry.component);\n continue;\n }\n if (ownContentMoved(was, entry)) causes.push(entry.component);\n }\n\n // Removals too, and they are the case a candidate-only walk cannot see: a\n // component that stopped rendering leaves regions behind it, and the component\n // that used to be there is exactly the name a reviewer needs.\n const present = new Set(after.map((entry) => entry.component));\n for (const entry of before) {\n if (entry.component === UNATTRIBUTED) continue;\n if (!present.has(entry.component)) causes.push(entry.component);\n }\n\n return causes.sort();\n}\n\n/**\n * Whether a component's *own content* differs, ignoring where its box ended up.\n *\n * Deliberately not phrased in bands, because it is not a band question. Both\n * `structure` and `geometry` map to the `geometry` band, and this has to keep\n * them apart: a component whose tree changed edited itself, and a component\n * whose rect moved was pushed. That distinction is the entire cause/collateral\n * result, and asking it through the band mapping would need the digests back\n * again to answer it.\n *\n * The list is every digest a component owns except `geometry`. It grew by two on\n * 2026-08-06 without changing meaning: `semantics` and `text` used to be inside\n * `structure`.\n */\nfunction ownContentMoved(before: BandDigests, after: BandDigests): boolean {\n return (\n before.structure !== after.structure ||\n before.semantics !== after.semantics ||\n before.text !== after.text ||\n before.style !== after.style\n );\n}\n\n/**\n * Which frequency bands moved between one component's two hashes.\n *\n * The reason the digests were split. A baseline carries hashes and not\n * documents, so \"what changed here\" used to be answerable only as a boolean —\n * and a boolean cannot serve a route-level test, whose entire request is *tell\n * me when the page stops assembling and never when it is repainted*.\n *\n * The mapping is exact and it is the same one `bandOf` applies to a delta, which\n * is the property that matters: a subject relaxed to `layout` must absorb the\n * same things whether the run held two documents or two sidecars. Two mappings\n * would be one drift away from a config key meaning different things on the two\n * paths, discovered as a regression somebody let through.\n *\n * | digest | band | what it covers |\n * |---|---|---|\n * | `semantics` | `a11y` | role, accessible name, ARIA state |\n * | `text` | `content` | text runs |\n * | `structure` | `geometry` | tags, aliases, attributes, child boundaries |\n * | `geometry` | `geometry` | rects and computed layout output |\n * | `style` | `token` | declared values and custom properties |\n *\n * `texture` never appears. It is raster residue by definition, and a component\n * hash is built from a document — so the band a comparison of hashes cannot\n * decide is *absent* from the answer rather than reported as unmoved, which is\n * ADR-0002's rule applied to a narrower question.\n *\n * A missing `geometry` on either side is the profile saying it has no layout\n * engine, and is not a difference. Treating absent as a change would report\n * every component as having moved the moment a jsdom baseline met a Chromium\n * run — which the environment key already names as the cause of that\n * comparison, so this would be a second, wronger answer to a question already\n * settled.\n */\nexport function movedBands(before: BandDigests, after: BandDigests): readonly Band[] {\n const moved = new Set<Band>();\n\n if (before.semantics !== after.semantics) moved.add('a11y');\n if (before.text !== after.text) moved.add('content');\n if (before.structure !== after.structure) moved.add('geometry');\n if (before.style !== after.style) moved.add('token');\n if (\n before.geometry !== undefined &&\n after.geometry !== undefined &&\n before.geometry !== after.geometry\n ) {\n moved.add('geometry');\n }\n\n return BANDS.filter((band) => moved.has(band));\n}\n\n/**\n * Every band that moved anywhere in the subject, given both revisions' hashes.\n *\n * A component present on one side only contributes `geometry`: something was\n * added or removed, which is the structural half of that band however the rest\n * of it compares. It deliberately does not contribute `a11y` or `content` as\n * well — a component that is simply not there did not *rename* anything, and\n * inflating the answer would make a level that absorbs nothing look like the\n * only safe choice.\n */\nexport function bandsBetween(\n before: readonly ComponentHash[],\n after: readonly ComponentHash[],\n): readonly Band[] {\n const previous = new Map(before.map((entry) => [entry.component, entry]));\n const present = new Set(after.map((entry) => entry.component));\n const moved = new Set<Band>();\n\n for (const entry of after) {\n const was = previous.get(entry.component);\n if (was === undefined) moved.add('geometry');\n else for (const band of movedBands(was, entry)) moved.add(band);\n }\n\n for (const entry of before) {\n if (!present.has(entry.component)) moved.add('geometry');\n }\n\n return BANDS.filter((band) => moved.has(band));\n}\n\n/**\n * Which bands moved, kept per component instead of folded into one list.\n *\n * {@link bandsBetween} answers *what kind of change is in this subject*, which is\n * the question a sensitivity level asks. It cannot answer the one a reviewer\n * asks — *what changed, and where* — because the fold is lossy in exactly the\n * place attribution lives: a subject reporting `content, geometry, token` has\n * told you a colour and a string and a size all moved somewhere in it, and left\n * you to guess which of the forty components on the page owns which.\n *\n * Unfolded, the same two sidecars say `Button — geometry, token` and\n * `CardFooter — a11y, content`, and that is a sentence a page can print beside a\n * picture. It is also the record that survives when the raster tier loses the\n * name: a difference that reflowed its neighbours merges into one blob whose box\n * fits no component, so the region resolves to the document root and the edit\n * arrives unattributed — while the hashes, which never looked at a pixel, still\n * hold the component that moved and the sense in which it moved.\n *\n * `cause` repeats the {@link causesBetween} predicate rather than being derived\n * from `bands`, and the repetition is the point: `structure` and `geometry` both\n * map to the `geometry` band, so a component that edited its own tree and one\n * that was merely pushed by a neighbour are indistinguishable *after* the band\n * mapping. Losing that here would make every reflowed container a culprit.\n */\nexport interface ComponentBands {\n readonly component: string;\n /** Non-empty: a component whose digests all matched has no entry at all. */\n readonly bands: readonly Band[];\n /** Its own content moved, as opposed to only its rect. `causesBetween`'s test. */\n readonly cause: boolean;\n /** Set only when the component is on one side alone, which `bands` cannot say. */\n readonly presence?: 'added' | 'removed';\n\n /**\n * How much bigger its own box got, when every instance agrees on the answer.\n *\n * The band a reviewer actually wanted. `geometry` says a rect under here is\n * not the rect it was; this says the control is eight pixels taller and\n * thirty-six wider, which is the padding somebody edited, arriving as a\n * measurement rather than as a guess about which property produced it.\n *\n * Absent for four different reasons and they are one reason: nobody can say.\n * No boxes on one side, a different number of instances, instances that\n * disagree about the delta, or a box that did not change size. Present is\n * always non-zero on at least one axis.\n */\n readonly grew?: { readonly width: number; readonly height: number };\n\n /**\n * Which declared values changed, by property, when both sides recorded them.\n *\n * `token` says a declaration under here moved; this names it. `from` holds the\n * values only the baseline had and `to` the values only this run has, so a\n * component rendered as primary and secondary that changed one background\n * reports that background and not the other. An empty side is a property no\n * node declared on that side, which was measured and is not the same as absent.\n *\n * Absent when either sidecar predates the record, and when the style digest\n * moved but no property's set of values did — a value that moved from one\n * node to another is a real change this cannot describe, and an empty list\n * would say there was none. Present is never empty.\n */\n readonly changed?: readonly ValueChange[];\n}\n\n/** One property whose set of declared values differs between two revisions. */\nexport interface ValueChange {\n readonly property: string;\n readonly from: readonly string[];\n readonly to: readonly string[];\n}\n\n/**\n * Every component whose hashes differ, with the bands it differs in.\n *\n * One-sided components contribute `geometry` and nothing else, which is\n * {@link bandsBetween}'s rule and must stay identical to it: a component that is\n * simply not there did not rename anything, and inflating the answer would put\n * an `a11y` claim on a page for a component nobody can look at. `presence` is\n * what carries the rest of that meaning, so no reader has to infer *appeared*\n * from a lone `geometry`.\n *\n * `(unattributed)` is excluded for the reason {@link causesBetween} excludes it:\n * it is a bucket for nodes whose provenance chain broke, so it collects\n * unrelated parts of a page under one name and nothing downstream could act on\n * it. The broken chain is reported where it means something, not here as a\n * component that moved.\n */\nexport function movedBandsBetween(\n before: readonly ComponentHash[],\n after: readonly ComponentHash[],\n): readonly ComponentBands[] {\n const previous = new Map(before.map((entry) => [entry.component, entry]));\n const present = new Set(after.map((entry) => entry.component));\n const moved: ComponentBands[] = [];\n\n for (const entry of after) {\n if (entry.component === UNATTRIBUTED) continue;\n const was = previous.get(entry.component);\n if (was === undefined) {\n moved.push({ component: entry.component, bands: ['geometry'], cause: true, presence: 'added' });\n continue;\n }\n const bands = movedBands(was, entry);\n if (bands.length === 0) continue;\n const grew = grewBetween(was.boxes, entry.boxes);\n const changed = was.style === entry.style ? undefined : changedBetween(was.values, entry.values);\n moved.push({\n component: entry.component,\n bands,\n cause: ownContentMoved(was, entry),\n ...(grew === undefined ? {} : { grew }),\n ...(changed === undefined ? {} : { changed }),\n });\n }\n\n for (const entry of before) {\n if (entry.component === UNATTRIBUTED) continue;\n if (present.has(entry.component)) continue;\n moved.push({ component: entry.component, bands: ['geometry'], cause: true, presence: 'removed' });\n }\n\n return moved.sort((left, right) => byCodeUnit(left.component, right.component));\n}\n\n/**\n * Every property whose values differ, or nothing when that cannot be said.\n *\n * Read only when the `style` digest moved, which is the digest these values\n * were declared into: a component whose declarations hashed the same has\n * nothing here to name, whatever else about it changed.\n */\nfunction changedBetween(\n before: ComponentHash['values'],\n after: ComponentHash['values'],\n): readonly ValueChange[] | undefined {\n if (before === undefined || after === undefined) return undefined;\n\n const changed: ValueChange[] = [];\n const properties = new Set([...Object.keys(before), ...Object.keys(after)]);\n for (const property of [...properties].sort(byCodeUnit)) {\n const was = before[property] ?? [];\n const now = after[property] ?? [];\n const from = was.filter((value) => !now.includes(value));\n const to = now.filter((value) => !was.includes(value));\n if (from.length > 0 || to.length > 0) changed.push({ property, from, to });\n }\n return changed.length === 0 ? undefined : changed;\n}\n\n/**\n * One size delta both sides agree on, or nothing.\n *\n * Instances are paired by document order, which is the only order either side\n * has. That pairing is sound exactly while the counts match: a component that\n * gained an instance shifted every index after the insertion, and the deltas\n * that fell out would be measurements of one instance against a different one.\n * So a changed count answers nothing rather than answering wrongly — the count\n * itself already moved `structure`, and the reviewer is told that instead.\n *\n * Instances that disagree also answer nothing. Three buttons where one grew and\n * two did not is a real finding and it is not *this* one, and printing the first\n * or the largest would be the page picking a representative and not saying so.\n *\n * Position is deliberately not read. Everything below a control that got taller\n * moved down, and a delta drawn from `x`/`y` would name every one of them.\n */\nfunction grewBetween(\n before: readonly (Rect | null)[] | undefined,\n after: readonly (Rect | null)[] | undefined,\n): { readonly width: number; readonly height: number } | undefined {\n if (before === undefined || after === undefined) return undefined;\n if (before.length === 0 || before.length !== after.length) return undefined;\n\n let agreed: { width: number; height: number } | undefined;\n for (const [index, was] of before.entries()) {\n const now = after[index];\n if (was === null || was === undefined || now === null || now === undefined) return undefined;\n\n const width = now.width - was.width;\n const height = now.height - was.height;\n if (agreed === undefined) agreed = { width, height };\n else if (agreed.width !== width || agreed.height !== height) return undefined;\n }\n\n return agreed === undefined || (agreed.width === 0 && agreed.height === 0) ? undefined : agreed;\n}\n"]}
@@ -181,4 +181,20 @@ export interface Composition {
181
181
  * would produce a different artefact from the same suite on a slower machine.
182
182
  */
183
183
  export declare function composeSubjects(subjects: readonly SubjectComposition[]): Composition;
184
+ /**
185
+ * Components more than half the suite mounts.
186
+ *
187
+ * Structure, not subject matter. A harness wrapper, a theme or store provider,
188
+ * a portal root and an HOC that every screen is wrapped in are all mounted by
189
+ * nearly every subject, and none of them is what any subject is about. The test
190
+ * is a count over the suite rather than a list of names, which is the only form
191
+ * that survives contact with a real application: nothing here knows that
192
+ * `withStyles(x)` is an HOC, that a minified `aL` is a decorator, or that a
193
+ * class component is a context consumer, and nothing here needs to.
194
+ *
195
+ * More than half, because that is where the claim becomes checkable. A
196
+ * component two thirds of a suite mounts cannot be what distinguishes a subject
197
+ * from its neighbours, whatever it is called.
198
+ */
199
+ export declare function structuralComponents(subjects: readonly SubjectComposition[]): ReadonlySet<string>;
184
200
  //# sourceMappingURL=composition.d.ts.map
@@ -127,14 +127,12 @@ function echoesOf(components) {
127
127
  * component two thirds of a suite mounts cannot be what distinguishes a subject
128
128
  * from its neighbours, whatever it is called.
129
129
  */
130
- function structuralComponents(subjects) {
130
+ export function structuralComponents(subjects) {
131
131
  const mounts = new Map();
132
132
  for (const subject of subjects) {
133
- for (const instance of subject.instances) {
134
- if (!attributed(instance))
135
- continue;
136
- mounts.set(instance.component, (mounts.get(instance.component) ?? 0) + 1);
137
- }
133
+ const mounted = new Set(subject.instances.filter(attributed).map((instance) => instance.component));
134
+ for (const component of mounted)
135
+ mounts.set(component, (mounts.get(component) ?? 0) + 1);
138
136
  }
139
137
  const structural = new Set();
140
138
  for (const [component, held] of mounts) {
@@ -1 +1 @@
1
- {"version":3,"file":"composition.js","sourceRoot":"","sources":["../../src/attribute/composition.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Digest } from '../format/hash.js';\nimport type { NodePath, SemanticSnapshot } from '../format/snapshot.js';\nimport { divergencesOf, type Divergence } from './divergence.js';\nimport { attributed, type ComponentInstance } from './instances.js';\n\n/**\n * The suite read as one graph, at one commit.\n *\n * Every other comparison in this system is between two revisions of one subject.\n * This is the other axis: **many subjects, one revision**, joined on the\n * components they share. It answers the question a per-subject tool cannot even\n * phrase — *this component, here, is the same component you already have an\n * example of over there* — and it answers it from digests a run already\n * computed, with no second render, no image and no store.\n *\n * ## What the join is worth\n *\n * A suite of examples is a suite of *compositions*. The narrow example is a\n * component at a boundary; the page examples contain that same boundary again,\n * sometimes with the same props and sometimes not. Once the instances are\n * addressable, three facts fall out that nobody had:\n *\n * - **An echo.** One rendering, several subjects. The narrow example and the\n * page are watching literally the same bytes, so a change in one is a change\n * in all of them, and a reviewer looking at eleven diffs is looking at one.\n * - **A divergence.** Same component, same props, *different* rendering, at one\n * commit. Nothing about the component's own inputs can explain that. Either\n * something outside it decides its output — an ancestor's cascade, a token, its\n * own state — or the reading is not repeatable. Both are findings and neither\n * is a regression.\n * - **A control group.** For any instance that moved, the same rendering\n * somewhere else that did *not* is a stable state to refer to. That is the\n * referent flake attribution has always needed and never had, and the suite\n * supplies it for free: one commit already holds N renderings of every shared\n * component.\n *\n * ## What it deliberately does not do\n *\n * It never decides anything. Two subjects sharing a rendering is not a reason to\n * delete either — that is the argument `coverage.ts` makes at the level of names\n * and it holds harder here, because a component can be correct in one context\n * and broken in the next, which is why the contexts are separate subjects.\n */\n\n/** One instance, located. */\nexport interface Site {\n readonly subject: string;\n readonly path: NodePath;\n readonly depth: number;\n readonly within?: string;\n\n /**\n * The component whose JSX wrote this element, here.\n *\n * On the entry beside it, `createdBy` is a set folded over the whole suite —\n * every component that ever wrote one of these, anywhere. That is the right\n * shape for *who mounts this* and the wrong one for *who mounted this here*,\n * and the ladder asks the second: an edit to a component that writes a `Chip`\n * on the footer explains nothing about a `Chip` the sidebar wrote. Kept per\n * site so the question can be asked where it was answered.\n */\n readonly createdBy?: string;\n}\n\n/** One rendering of one component, and everywhere it occurred. */\nexport interface Rendering {\n readonly rendering: Digest;\n readonly structure: Digest;\n readonly semantics: Digest;\n readonly text: Digest;\n readonly style: Digest;\n\n /**\n * Child components this rendering mounted, in document order.\n *\n * Carried on the rendering rather than only on the entry because it is the\n * only evidence available about what was passed *in*: `propsDigest` excludes\n * `children` by design, so two renderings that mount different children are\n * two different inputs wearing one props digest. `divergencesOf` refuses to\n * call that a contradiction, and this is the field it reads.\n */\n readonly renders: readonly string[];\n\n /** In subject order, then document order. */\n readonly sites: readonly Site[];\n}\n\n/** One props digest of one component, and every way it rendered under it. */\nexport interface PropsClass {\n /** Absent when the collector supplied no provenance — unknown, not \"none\". */\n readonly props?: Digest;\n readonly renderings: readonly Rendering[];\n}\n\nexport interface ComponentEntry {\n readonly component: string;\n /** Subjects containing at least one boundary of it, in the order supplied. */\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. Defined by depth rather than by a naming\n * convention because a naming convention is a different tool's `story:` prefix,\n * and this has to hold for a route suite too. Ties resolve to nothing: a\n * subject whose two shallowest boundaries are siblings has no single subject.\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. The graph, upwards, again —\n * and usually the more useful of the two.\n *\n * `within` is where the boundary sits in the DOM and this is who wrote the\n * element. They are the same thing only for a component that authors a node of\n * its own, and a real application is full of components that do not: measured\n * on `examples/todomvc`, every `Chip` reports `within: ['Stack']` and\n * `createdBy: ['TodoFooter']`: the `Stack` that encloses a chip is a layout\n * primitive that knows nothing about it, and the footer that wrote the element\n * stands one boundary further out. A component that renders nothing but other\n * components owns no node, is a boundary nowhere, and appears in this graph\n * *only* here — while being the file a reviewer has to open.\n *\n * Empty on a production build, where `_debugOwner` is absent (ADR-0007). Empty\n * is *not* \"nothing mounted it\": a component with no caller is the subject root\n * and every other case is missing data, which is why the `upstream` rung in\n * `movement.ts` reads this and never concludes from its silence.\n */\n readonly createdBy: readonly string[];\n\n /** Components it encloses somewhere, sorted. The graph, downwards. */\n readonly renders: readonly string[];\n\n /**\n * Custom properties its own nodes resolve through, anywhere in the suite.\n *\n * The set a token movement is intersected against, which is how \"`--va-space-3`\n * moved\" becomes \"`--va-space-3` moved and these four components read it\".\n */\n readonly tokens: readonly string[];\n\n /** Sorted by props digest; the class with unknown props sorts last. */\n readonly classes: readonly PropsClass[];\n}\n\n/**\n * The same component, the same props, the same bytes, in more than one subject.\n *\n * The connected dots. `example` names the narrow subject among the sites when\n * there is one, because that is the artefact a reviewer already has an opinion\n * about — \"this is the Chip from the Chip story\" is a sentence, and \"these four\n * paths share a digest\" is not.\n */\nexport interface Echo {\n readonly component: string;\n readonly props?: Digest;\n readonly rendering: Digest;\n /** At least two, spanning at least two subjects. */\n readonly sites: readonly Site[];\n readonly example?: string;\n}\n\n\nexport interface SubjectComposition {\n readonly subject: string;\n readonly instances: readonly ComponentInstance[];\n\n /**\n * The subject as read, kept so a divergence can say *which input* moved.\n *\n * Optional because `composeSubjects` is a fold over instance lists and stays\n * one — a caller holding only a report's sidecars still gets the graph, the\n * echoes and the divergences, and gets them without a `partings` field it\n * would have to explain away as empty.\n */\n readonly snapshot?: SemanticSnapshot;\n}\n\nexport interface Composition {\n /** In the order supplied, which the run guarantees is plan order. */\n readonly subjects: readonly string[];\n /** Sorted by name, code-unit order. */\n readonly components: readonly ComponentEntry[];\n /** Sorted by how many sites each has, widest first, then by component. */\n readonly echoes: readonly Echo[];\n /** Sorted by component name. */\n readonly divergences: readonly Divergence[];\n}\n\n/**\n * Fold a run's per-subject instance lists into the graph.\n *\n * Pure, ordered, and a function of its input alone — the run's report has to be\n * a function of the plan, and a phase that accumulated as a worker pool finished\n * would produce a different artefact from the same suite on a slower machine.\n */\nexport function composeSubjects(subjects: readonly SubjectComposition[]): Composition {\n const entries = new Map<string, Accumulator>();\n const order = subjects.map((subject) => subject.subject);\n const structural = structuralComponents(subjects);\n\n for (const subject of subjects) {\n const example = exampleOf(subject.instances, structural);\n\n for (const instance of subject.instances) {\n if (!attributed(instance)) continue;\n\n const entry = accumulate(entries, instance.component);\n if (entry.subjects.at(-1) !== subject.subject) entry.subjects.push(subject.subject);\n entry.instances += 1;\n if (example === instance.component && entry.examples.at(-1) !== subject.subject) {\n entry.examples.push(subject.subject);\n }\n if (instance.within !== undefined) entry.within.add(instance.within);\n if (instance.createdBy !== undefined) entry.createdBy.add(instance.createdBy);\n for (const child of instance.renders) entry.renders.add(child);\n for (const token of instance.tokens) entry.tokens.add(token);\n\n const key = instance.props ?? UNKNOWN_PROPS;\n const byProps = entry.classes.get(key) ?? new Map<Digest, MutableRendering>();\n const rendering = byProps.get(instance.rendering) ?? {\n rendering: instance.rendering,\n structure: instance.structure,\n semantics: instance.semantics,\n text: instance.text,\n style: instance.style,\n renders: instance.renders,\n sites: [],\n };\n rendering.sites.push({\n subject: subject.subject,\n path: instance.path,\n depth: instance.depth,\n ...(instance.within === undefined ? {} : { within: instance.within }),\n ...(instance.createdBy === undefined ? {} : { createdBy: instance.createdBy }),\n });\n byProps.set(instance.rendering, rendering);\n entry.classes.set(key, byProps);\n }\n }\n\n const components = [...entries.entries()]\n .map(([component, entry]) => ({\n component,\n subjects: entry.subjects,\n instances: entry.instances,\n examples: entry.examples,\n within: [...entry.within].sort(byCodeUnit),\n createdBy: [...entry.createdBy].sort(byCodeUnit),\n renders: [...entry.renders].sort(byCodeUnit),\n tokens: [...entry.tokens].sort(byCodeUnit),\n classes: [...entry.classes.entries()]\n .sort(([a], [b]) => byCodeUnit(a, b))\n .map(([props, renderings]) => ({\n ...(props === UNKNOWN_PROPS ? {} : { props }),\n renderings: [...renderings.values()].sort(widestFirst),\n })),\n }))\n .sort((a, b) => byCodeUnit(a.component, b.component));\n\n const snapshots = new Map<string, SemanticSnapshot>();\n for (const subject of subjects) {\n if (subject.snapshot !== undefined) snapshots.set(subject.subject, subject.snapshot);\n }\n\n return {\n subjects: order,\n components,\n echoes: echoesOf(components),\n divergences: divergencesOf(components, snapshots),\n };\n}\n\n/**\n * Every rendering shared by two or more subjects.\n *\n * Two instances of one component inside a *single* subject sharing a rendering\n * is ordinary and says nothing — a list of three identical chips is three\n * identical chips. The finding is a rendering that survives being mounted\n * somewhere else, because that is the one a second subject is watching.\n */\nfunction echoesOf(components: readonly ComponentEntry[]): readonly Echo[] {\n const echoes: Echo[] = [];\n\n for (const entry of components) {\n for (const group of entry.classes) {\n for (const rendering of group.renderings) {\n const spread = new Set(rendering.sites.map((site) => site.subject));\n if (spread.size < 2) continue;\n\n const example = entry.examples.find((subject) => spread.has(subject));\n echoes.push({\n component: entry.component,\n ...(group.props === undefined ? {} : { props: group.props }),\n rendering: rendering.rendering,\n sites: rendering.sites,\n ...(example === undefined ? {} : { example }),\n });\n }\n }\n }\n\n return echoes.sort(\n (a, b) => b.sites.length - a.sites.length || byCodeUnit(a.component, b.component),\n );\n}\n\n\n/**\n * Components more than half the suite mounts.\n *\n * Structure, not subject matter. A harness wrapper, a theme or store provider,\n * a portal root and an HOC that every screen is wrapped in are all mounted by\n * nearly every subject, and none of them is what any subject is about. The test\n * is a count over the suite rather than a list of names, which is the only form\n * that survives contact with a real application: nothing here knows that\n * `withStyles(x)` is an HOC, that a minified `aL` is a decorator, or that a\n * class component is a context consumer, and nothing here needs to.\n *\n * More than half, because that is where the claim becomes checkable. A\n * component two thirds of a suite mounts cannot be what distinguishes a subject\n * from its neighbours, whatever it is called.\n */\nfunction structuralComponents(subjects: readonly SubjectComposition[]): ReadonlySet<string> {\n const mounts = new Map<string, number>();\n for (const subject of subjects) {\n for (const instance of subject.instances) {\n if (!attributed(instance)) continue;\n mounts.set(instance.component, (mounts.get(instance.component) ?? 0) + 1);\n }\n }\n\n const structural = new Set<string>();\n for (const [component, held] of mounts) {\n if (held * 2 > subjects.length) structural.add(component);\n }\n return structural;\n}\n\n/**\n * The component a subject exists to show, when one component does.\n *\n * The shallowest attributed boundary that is not structure, and only when it is\n * alone at its depth. A story usually mounts one thing; a page mounts a layout\n * that mounts several, and calling the first of them the subject's component\n * would be picking a winner out of document order.\n *\n * The descent is the whole of the difference between this and the obvious rule.\n * Taking the shallowest boundary outright answers with whatever the harness\n * mounted first, which on a real suite is one value for every subject in it —\n * `Wrapper` across a component library, a minified decorator root across a\n * built Storybook — and a field with one value is a field that says nothing. A\n * depth every one of whose names is structure is passed through however many\n * names it has, because a harness that mounts a provider beside a portal root\n * has still not said what the subject is about.\n *\n * Where the descent finds nothing it answers as the shallowest rule would, so\n * this can name more subjects than that rule and never fewer.\n */\nfunction exampleOf(\n instances: readonly ComponentInstance[],\n structural: ReadonlySet<string>,\n): string | undefined {\n const held = instances.filter(attributed);\n const depths = [...new Set(held.map((instance) => instance.depth))].sort((a, b) => a - b);\n\n for (const depth of depths) {\n const names = new Set(held.filter((instance) => instance.depth === depth).map((i) => i.component));\n const matter = [...names].filter((name) => !structural.has(name));\n if (matter.length === 0) continue;\n return matter.length === 1 ? matter[0] : shallowestOf(held, depths[0]);\n }\n\n return shallowestOf(held, depths[0]);\n}\n\n/** The shallowest boundary when it is alone at its depth: the rule before the descent. */\nfunction shallowestOf(held: readonly ComponentInstance[], shallowest: number | undefined): string | undefined {\n if (shallowest === undefined) return undefined;\n const leads = held.filter((instance) => instance.depth === shallowest);\n const names = new Set(leads.map((instance) => instance.component));\n return names.size === 1 ? leads[0]?.component : undefined;\n}\n\n/**\n * The key an instance with no props digest is filed under.\n *\n * A sentinel rather than `undefined`, because a `Map` keyed on `Digest |\n * undefined` reads as though unknown props were a props class like any other,\n * and every consumer would then have to remember that it is not. It sorts last\n * by construction — no digest starts with a space.\n */\nconst UNKNOWN_PROPS = ' unknown';\n\ninterface Accumulator {\n readonly subjects: string[];\n instances: number;\n readonly examples: string[];\n readonly within: Set<string>;\n readonly createdBy: Set<string>;\n readonly renders: Set<string>;\n readonly tokens: Set<string>;\n readonly classes: Map<string, Map<Digest, MutableRendering>>;\n}\n\n/** A `Rendering` while its sites are still being collected. */\ninterface MutableRendering extends Omit<Rendering, 'sites'> {\n readonly sites: Site[];\n}\n\nfunction accumulate(entries: Map<string, Accumulator>, component: string): Accumulator {\n const existing = entries.get(component);\n if (existing !== undefined) return existing;\n\n const created: Accumulator = {\n subjects: [],\n instances: 0,\n examples: [],\n within: new Set(),\n createdBy: new Set(),\n renders: new Set(),\n tokens: new Set(),\n classes: new Map(),\n };\n entries.set(component, created);\n return created;\n}\n\nfunction widestFirst(a: Rendering, b: Rendering): number {\n return b.sites.length - a.sites.length || byCodeUnit(a.rendering, b.rendering);\n}\n\n/**\n * Code-unit order, never `localeCompare`.\n *\n * The same rule `hashComponents` sorts by, for the same reason: this output\n * reaches a report that is committed, diffed and read back on another runner,\n * and a locale-aware comparison makes the byte order a promise about `LANG`.\n */\nfunction byCodeUnit(a: string, b: string): number {\n return a < b ? -1 : a > b ? 1 : 0;\n}\n"]}
1
+ {"version":3,"file":"composition.js","sourceRoot":"","sources":["../../src/attribute/composition.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Digest } from '../format/hash.js';\nimport type { NodePath, SemanticSnapshot } from '../format/snapshot.js';\nimport { divergencesOf, type Divergence } from './divergence.js';\nimport { attributed, type ComponentInstance } from './instances.js';\n\n/**\n * The suite read as one graph, at one commit.\n *\n * Every other comparison in this system is between two revisions of one subject.\n * This is the other axis: **many subjects, one revision**, joined on the\n * components they share. It answers the question a per-subject tool cannot even\n * phrase — *this component, here, is the same component you already have an\n * example of over there* — and it answers it from digests a run already\n * computed, with no second render, no image and no store.\n *\n * ## What the join is worth\n *\n * A suite of examples is a suite of *compositions*. The narrow example is a\n * component at a boundary; the page examples contain that same boundary again,\n * sometimes with the same props and sometimes not. Once the instances are\n * addressable, three facts fall out that nobody had:\n *\n * - **An echo.** One rendering, several subjects. The narrow example and the\n * page are watching literally the same bytes, so a change in one is a change\n * in all of them, and a reviewer looking at eleven diffs is looking at one.\n * - **A divergence.** Same component, same props, *different* rendering, at one\n * commit. Nothing about the component's own inputs can explain that. Either\n * something outside it decides its output — an ancestor's cascade, a token, its\n * own state — or the reading is not repeatable. Both are findings and neither\n * is a regression.\n * - **A control group.** For any instance that moved, the same rendering\n * somewhere else that did *not* is a stable state to refer to. That is the\n * referent flake attribution has always needed and never had, and the suite\n * supplies it for free: one commit already holds N renderings of every shared\n * component.\n *\n * ## What it deliberately does not do\n *\n * It never decides anything. Two subjects sharing a rendering is not a reason to\n * delete either — that is the argument `coverage.ts` makes at the level of names\n * and it holds harder here, because a component can be correct in one context\n * and broken in the next, which is why the contexts are separate subjects.\n */\n\n/** One instance, located. */\nexport interface Site {\n readonly subject: string;\n readonly path: NodePath;\n readonly depth: number;\n readonly within?: string;\n\n /**\n * The component whose JSX wrote this element, here.\n *\n * On the entry beside it, `createdBy` is a set folded over the whole suite —\n * every component that ever wrote one of these, anywhere. That is the right\n * shape for *who mounts this* and the wrong one for *who mounted this here*,\n * and the ladder asks the second: an edit to a component that writes a `Chip`\n * on the footer explains nothing about a `Chip` the sidebar wrote. Kept per\n * site so the question can be asked where it was answered.\n */\n readonly createdBy?: string;\n}\n\n/** One rendering of one component, and everywhere it occurred. */\nexport interface Rendering {\n readonly rendering: Digest;\n readonly structure: Digest;\n readonly semantics: Digest;\n readonly text: Digest;\n readonly style: Digest;\n\n /**\n * Child components this rendering mounted, in document order.\n *\n * Carried on the rendering rather than only on the entry because it is the\n * only evidence available about what was passed *in*: `propsDigest` excludes\n * `children` by design, so two renderings that mount different children are\n * two different inputs wearing one props digest. `divergencesOf` refuses to\n * call that a contradiction, and this is the field it reads.\n */\n readonly renders: readonly string[];\n\n /** In subject order, then document order. */\n readonly sites: readonly Site[];\n}\n\n/** One props digest of one component, and every way it rendered under it. */\nexport interface PropsClass {\n /** Absent when the collector supplied no provenance — unknown, not \"none\". */\n readonly props?: Digest;\n readonly renderings: readonly Rendering[];\n}\n\nexport interface ComponentEntry {\n readonly component: string;\n /** Subjects containing at least one boundary of it, in the order supplied. */\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. Defined by depth rather than by a naming\n * convention because a naming convention is a different tool's `story:` prefix,\n * and this has to hold for a route suite too. Ties resolve to nothing: a\n * subject whose two shallowest boundaries are siblings has no single subject.\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. The graph, upwards, again —\n * and usually the more useful of the two.\n *\n * `within` is where the boundary sits in the DOM and this is who wrote the\n * element. They are the same thing only for a component that authors a node of\n * its own, and a real application is full of components that do not: measured\n * on `examples/todomvc`, every `Chip` reports `within: ['Stack']` and\n * `createdBy: ['TodoFooter']`: the `Stack` that encloses a chip is a layout\n * primitive that knows nothing about it, and the footer that wrote the element\n * stands one boundary further out. A component that renders nothing but other\n * components owns no node, is a boundary nowhere, and appears in this graph\n * *only* here — while being the file a reviewer has to open.\n *\n * Empty on a production build, where `_debugOwner` is absent (ADR-0007). Empty\n * is *not* \"nothing mounted it\": a component with no caller is the subject root\n * and every other case is missing data, which is why the `upstream` rung in\n * `movement.ts` reads this and never concludes from its silence.\n */\n readonly createdBy: readonly string[];\n\n /** Components it encloses somewhere, sorted. The graph, downwards. */\n readonly renders: readonly string[];\n\n /**\n * Custom properties its own nodes resolve through, anywhere in the suite.\n *\n * The set a token movement is intersected against, which is how \"`--va-space-3`\n * moved\" becomes \"`--va-space-3` moved and these four components read it\".\n */\n readonly tokens: readonly string[];\n\n /** Sorted by props digest; the class with unknown props sorts last. */\n readonly classes: readonly PropsClass[];\n}\n\n/**\n * The same component, the same props, the same bytes, in more than one subject.\n *\n * The connected dots. `example` names the narrow subject among the sites when\n * there is one, because that is the artefact a reviewer already has an opinion\n * about — \"this is the Chip from the Chip story\" is a sentence, and \"these four\n * paths share a digest\" is not.\n */\nexport interface Echo {\n readonly component: string;\n readonly props?: Digest;\n readonly rendering: Digest;\n /** At least two, spanning at least two subjects. */\n readonly sites: readonly Site[];\n readonly example?: string;\n}\n\n\nexport interface SubjectComposition {\n readonly subject: string;\n readonly instances: readonly ComponentInstance[];\n\n /**\n * The subject as read, kept so a divergence can say *which input* moved.\n *\n * Optional because `composeSubjects` is a fold over instance lists and stays\n * one — a caller holding only a report's sidecars still gets the graph, the\n * echoes and the divergences, and gets them without a `partings` field it\n * would have to explain away as empty.\n */\n readonly snapshot?: SemanticSnapshot;\n}\n\nexport interface Composition {\n /** In the order supplied, which the run guarantees is plan order. */\n readonly subjects: readonly string[];\n /** Sorted by name, code-unit order. */\n readonly components: readonly ComponentEntry[];\n /** Sorted by how many sites each has, widest first, then by component. */\n readonly echoes: readonly Echo[];\n /** Sorted by component name. */\n readonly divergences: readonly Divergence[];\n}\n\n/**\n * Fold a run's per-subject instance lists into the graph.\n *\n * Pure, ordered, and a function of its input alone — the run's report has to be\n * a function of the plan, and a phase that accumulated as a worker pool finished\n * would produce a different artefact from the same suite on a slower machine.\n */\nexport function composeSubjects(subjects: readonly SubjectComposition[]): Composition {\n const entries = new Map<string, Accumulator>();\n const order = subjects.map((subject) => subject.subject);\n const structural = structuralComponents(subjects);\n\n for (const subject of subjects) {\n const example = exampleOf(subject.instances, structural);\n\n for (const instance of subject.instances) {\n if (!attributed(instance)) continue;\n\n const entry = accumulate(entries, instance.component);\n if (entry.subjects.at(-1) !== subject.subject) entry.subjects.push(subject.subject);\n entry.instances += 1;\n if (example === instance.component && entry.examples.at(-1) !== subject.subject) {\n entry.examples.push(subject.subject);\n }\n if (instance.within !== undefined) entry.within.add(instance.within);\n if (instance.createdBy !== undefined) entry.createdBy.add(instance.createdBy);\n for (const child of instance.renders) entry.renders.add(child);\n for (const token of instance.tokens) entry.tokens.add(token);\n\n const key = instance.props ?? UNKNOWN_PROPS;\n const byProps = entry.classes.get(key) ?? new Map<Digest, MutableRendering>();\n const rendering = byProps.get(instance.rendering) ?? {\n rendering: instance.rendering,\n structure: instance.structure,\n semantics: instance.semantics,\n text: instance.text,\n style: instance.style,\n renders: instance.renders,\n sites: [],\n };\n rendering.sites.push({\n subject: subject.subject,\n path: instance.path,\n depth: instance.depth,\n ...(instance.within === undefined ? {} : { within: instance.within }),\n ...(instance.createdBy === undefined ? {} : { createdBy: instance.createdBy }),\n });\n byProps.set(instance.rendering, rendering);\n entry.classes.set(key, byProps);\n }\n }\n\n const components = [...entries.entries()]\n .map(([component, entry]) => ({\n component,\n subjects: entry.subjects,\n instances: entry.instances,\n examples: entry.examples,\n within: [...entry.within].sort(byCodeUnit),\n createdBy: [...entry.createdBy].sort(byCodeUnit),\n renders: [...entry.renders].sort(byCodeUnit),\n tokens: [...entry.tokens].sort(byCodeUnit),\n classes: [...entry.classes.entries()]\n .sort(([a], [b]) => byCodeUnit(a, b))\n .map(([props, renderings]) => ({\n ...(props === UNKNOWN_PROPS ? {} : { props }),\n renderings: [...renderings.values()].sort(widestFirst),\n })),\n }))\n .sort((a, b) => byCodeUnit(a.component, b.component));\n\n const snapshots = new Map<string, SemanticSnapshot>();\n for (const subject of subjects) {\n if (subject.snapshot !== undefined) snapshots.set(subject.subject, subject.snapshot);\n }\n\n return {\n subjects: order,\n components,\n echoes: echoesOf(components),\n divergences: divergencesOf(components, snapshots),\n };\n}\n\n/**\n * Every rendering shared by two or more subjects.\n *\n * Two instances of one component inside a *single* subject sharing a rendering\n * is ordinary and says nothing — a list of three identical chips is three\n * identical chips. The finding is a rendering that survives being mounted\n * somewhere else, because that is the one a second subject is watching.\n */\nfunction echoesOf(components: readonly ComponentEntry[]): readonly Echo[] {\n const echoes: Echo[] = [];\n\n for (const entry of components) {\n for (const group of entry.classes) {\n for (const rendering of group.renderings) {\n const spread = new Set(rendering.sites.map((site) => site.subject));\n if (spread.size < 2) continue;\n\n const example = entry.examples.find((subject) => spread.has(subject));\n echoes.push({\n component: entry.component,\n ...(group.props === undefined ? {} : { props: group.props }),\n rendering: rendering.rendering,\n sites: rendering.sites,\n ...(example === undefined ? {} : { example }),\n });\n }\n }\n }\n\n return echoes.sort(\n (a, b) => b.sites.length - a.sites.length || byCodeUnit(a.component, b.component),\n );\n}\n\n\n/**\n * Components more than half the suite mounts.\n *\n * Structure, not subject matter. A harness wrapper, a theme or store provider,\n * a portal root and an HOC that every screen is wrapped in are all mounted by\n * nearly every subject, and none of them is what any subject is about. The test\n * is a count over the suite rather than a list of names, which is the only form\n * that survives contact with a real application: nothing here knows that\n * `withStyles(x)` is an HOC, that a minified `aL` is a decorator, or that a\n * class component is a context consumer, and nothing here needs to.\n *\n * More than half, because that is where the claim becomes checkable. A\n * component two thirds of a suite mounts cannot be what distinguishes a subject\n * from its neighbours, whatever it is called.\n */\nexport function structuralComponents(subjects: readonly SubjectComposition[]): ReadonlySet<string> {\n const mounts = new Map<string, number>();\n for (const subject of subjects) {\n const mounted = new Set(subject.instances.filter(attributed).map((instance) => instance.component));\n for (const component of mounted) mounts.set(component, (mounts.get(component) ?? 0) + 1);\n }\n\n const structural = new Set<string>();\n for (const [component, held] of mounts) {\n if (held * 2 > subjects.length) structural.add(component);\n }\n return structural;\n}\n\n/**\n * The component a subject exists to show, when one component does.\n *\n * The shallowest attributed boundary that is not structure, and only when it is\n * alone at its depth. A story usually mounts one thing; a page mounts a layout\n * that mounts several, and calling the first of them the subject's component\n * would be picking a winner out of document order.\n *\n * The descent is the whole of the difference between this and the obvious rule.\n * Taking the shallowest boundary outright answers with whatever the harness\n * mounted first, which on a real suite is one value for every subject in it —\n * `Wrapper` across a component library, a minified decorator root across a\n * built Storybook — and a field with one value is a field that says nothing. A\n * depth every one of whose names is structure is passed through however many\n * names it has, because a harness that mounts a provider beside a portal root\n * has still not said what the subject is about.\n *\n * Where the descent finds nothing it answers as the shallowest rule would, so\n * this can name more subjects than that rule and never fewer.\n */\nfunction exampleOf(\n instances: readonly ComponentInstance[],\n structural: ReadonlySet<string>,\n): string | undefined {\n const held = instances.filter(attributed);\n const depths = [...new Set(held.map((instance) => instance.depth))].sort((a, b) => a - b);\n\n for (const depth of depths) {\n const names = new Set(held.filter((instance) => instance.depth === depth).map((i) => i.component));\n const matter = [...names].filter((name) => !structural.has(name));\n if (matter.length === 0) continue;\n return matter.length === 1 ? matter[0] : shallowestOf(held, depths[0]);\n }\n\n return shallowestOf(held, depths[0]);\n}\n\n/** The shallowest boundary when it is alone at its depth: the rule before the descent. */\nfunction shallowestOf(held: readonly ComponentInstance[], shallowest: number | undefined): string | undefined {\n if (shallowest === undefined) return undefined;\n const leads = held.filter((instance) => instance.depth === shallowest);\n const names = new Set(leads.map((instance) => instance.component));\n return names.size === 1 ? leads[0]?.component : undefined;\n}\n\n/**\n * The key an instance with no props digest is filed under.\n *\n * A sentinel rather than `undefined`, because a `Map` keyed on `Digest |\n * undefined` reads as though unknown props were a props class like any other,\n * and every consumer would then have to remember that it is not. It sorts last\n * by construction — no digest starts with a space.\n */\nconst UNKNOWN_PROPS = ' unknown';\n\ninterface Accumulator {\n readonly subjects: string[];\n instances: number;\n readonly examples: string[];\n readonly within: Set<string>;\n readonly createdBy: Set<string>;\n readonly renders: Set<string>;\n readonly tokens: Set<string>;\n readonly classes: Map<string, Map<Digest, MutableRendering>>;\n}\n\n/** A `Rendering` while its sites are still being collected. */\ninterface MutableRendering extends Omit<Rendering, 'sites'> {\n readonly sites: Site[];\n}\n\nfunction accumulate(entries: Map<string, Accumulator>, component: string): Accumulator {\n const existing = entries.get(component);\n if (existing !== undefined) return existing;\n\n const created: Accumulator = {\n subjects: [],\n instances: 0,\n examples: [],\n within: new Set(),\n createdBy: new Set(),\n renders: new Set(),\n tokens: new Set(),\n classes: new Map(),\n };\n entries.set(component, created);\n return created;\n}\n\nfunction widestFirst(a: Rendering, b: Rendering): number {\n return b.sites.length - a.sites.length || byCodeUnit(a.rendering, b.rendering);\n}\n\n/**\n * Code-unit order, never `localeCompare`.\n *\n * The same rule `hashComponents` sorts by, for the same reason: this output\n * reaches a report that is committed, diffed and read back on another runner,\n * and a locale-aware comparison makes the byte order a promise about `LANG`.\n */\nfunction byCodeUnit(a: string, b: string): number {\n return a < b ? -1 : a > b ? 1 : 0;\n}\n"]}
@@ -19,7 +19,7 @@ export { isolateRegions, subtractRegions, excludedBoxes } from './mask.js';
19
19
  export type { ChangeMask, DiffRegion, ExcludedBox, Isolation, IsolationOptions, Subtraction, } from './mask.js';
20
20
  export { attributeRegions, rankRegions } from './region.js';
21
21
  export type { AttributedRegion, AttributionOptions, RankedRegion } from './region.js';
22
- export { resolveSource, formatSource, indexSource, mergeSourceIndexes, overlaySourceIndex, } from './source.js';
22
+ export { resolveSource, formatSource, mergeSourceIndexes, overlaySourceIndex, } from './source.js';
23
23
  export type { SourceIndex, SourceRef, Resolution } from './source.js';
24
24
  export { parseStackFrames, isVendorPath, writerLocationOf } from './stack.js';
25
25
  export type { StackFrame } from './stack.js';
@@ -36,6 +36,8 @@ export type { ClosureSite, NodeClosure, SharedClosure, SharedClosureOptions, Sub
36
36
  export { composeSubjects } from './composition.js';
37
37
  export type { Composition, ComponentEntry, Echo, PropsClass, Rendering, Site, SubjectComposition, } from './composition.js';
38
38
  export type { Divergence, DivergenceParting } from './divergence.js';
39
+ export { piecesOf } from './subject-pieces.js';
40
+ export type { Residue, SubjectPieces, SubjectShare } from './subject-pieces.js';
39
41
  export { lexiconOf, lexiconOfValues, lexiconValuesOf, structureOf, withExamples, LEXICON_CAP } from './lexicon.js';
40
42
  export { LANDMARK_CAP } from './landmark.js';
41
43
  export type { Landmark } from './landmark.js';
@@ -16,7 +16,7 @@
16
16
  export { locate } from './locate.js';
17
17
  export { isolateRegions, subtractRegions, excludedBoxes } from './mask.js';
18
18
  export { attributeRegions, rankRegions } from './region.js';
19
- export { resolveSource, formatSource, indexSource, mergeSourceIndexes, overlaySourceIndex, } from './source.js';
19
+ export { resolveSource, formatSource, mergeSourceIndexes, overlaySourceIndex, } from './source.js';
20
20
  export { parseStackFrames, isVendorPath, writerLocationOf } from './stack.js';
21
21
  export { parseSourceMap, originalPositionFor, sourceMappingUrlOf, inlineSourceMapOf, } from './source-map.js';
22
22
  export { createCallSiteResolver, locateProvenance, locateSites } from './call-site.js';
@@ -24,6 +24,7 @@ export { hashComponents, causesBetween, movedBands, movedBandsBetween, bandsBetw
24
24
  export { componentInstances, attributed } from './instances.js';
25
25
  export { nodeClosures, sharedClosures } from './closure.js';
26
26
  export { composeSubjects } from './composition.js';
27
+ export { piecesOf } from './subject-pieces.js';
27
28
  export { lexiconOf, lexiconOfValues, lexiconValuesOf, structureOf, withExamples, LEXICON_CAP } from './lexicon.js';
28
29
  export { LANDMARK_CAP } from './landmark.js';
29
30
  export { attributeMovement } from './movement.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/attribute/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAGrC,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAU3E,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG5D,OAAO,EACL,aAAa,EACb,YAAY,EACZ,WAAW,EACX,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,aAAa,CAAC;AAGrB,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAG9E,OAAO,EACL,cAAc,EACd,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAGvF,OAAO,EACL,cAAc,EACd,aAAa,EACb,UAAU,EACV,iBAAiB,EACjB,YAAY,EACZ,YAAY,GACb,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAGhE,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAG5D,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAanD,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,eAAe,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AACnH,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAI7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAGlD,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAG5E,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC","sourcesContent":["/**\n * `@variance-authority/core/attribute` — from a position to a name to a file.\n *\n * The group that makes a difference actionable. Pixels cluster into regions,\n * regions join the box tree, the tree knows which component produced each node,\n * and the component resolves to a source location. The same machinery answers\n * \"which component is not holding still\" and \"which subjects cover this\n * component\".\n *\n * This is the answer to *\"a pixel differ reports 1530 pixels changed\"* — and the\n * reason it is a group rather than a function is that every step of the journey\n * is separately useful, and separately capable of refusing. `locateInstability`\n * will not name a component for a difference that is beneath the semantic tier;\n * `coverage` will not recommend deleting anything.\n */\n\nexport { locate } from './locate.js';\nexport type { Location, LocationStep } from './locate.js';\n\nexport { isolateRegions, subtractRegions, excludedBoxes } from './mask.js';\nexport type {\n ChangeMask,\n DiffRegion,\n ExcludedBox,\n Isolation,\n IsolationOptions,\n Subtraction,\n} from './mask.js';\n\nexport { attributeRegions, rankRegions } from './region.js';\nexport type { AttributedRegion, AttributionOptions, RankedRegion } from './region.js';\n\nexport {\n resolveSource,\n formatSource,\n indexSource,\n mergeSourceIndexes,\n overlaySourceIndex,\n} from './source.js';\nexport type { SourceIndex, SourceRef, Resolution } from './source.js';\n\nexport { parseStackFrames, isVendorPath, writerLocationOf } from './stack.js';\nexport type { StackFrame } from './stack.js';\n\nexport {\n parseSourceMap,\n originalPositionFor,\n sourceMappingUrlOf,\n inlineSourceMapOf,\n} from './source-map.js';\nexport type { SourceMap, OriginalPosition } from './source-map.js';\n\nexport { createCallSiteResolver, locateProvenance, locateSites } from './call-site.js';\nexport type { CallSiteResolver, CallSiteStats, FetchModule, LocatableSite } from './call-site.js';\n\nexport {\n hashComponents,\n causesBetween,\n movedBands,\n movedBandsBetween,\n bandsBetween,\n UNATTRIBUTED,\n} from './component-hash.js';\nexport type { BandDigests, ComponentBands, ComponentHash, ValueChange } from './component-hash.js';\n\nexport { componentInstances, attributed } from './instances.js';\nexport type { ComponentInstance } from './instances.js';\n\nexport { nodeClosures, sharedClosures } from './closure.js';\nexport type { ClosureSite, NodeClosure, SharedClosure, SharedClosureOptions, SubjectTree } from './closure.js';\n\nexport { composeSubjects } from './composition.js';\nexport type {\n Composition,\n ComponentEntry,\n Echo,\n PropsClass,\n Rendering,\n Site,\n SubjectComposition,\n} from './composition.js';\n\nexport type { Divergence, DivergenceParting } from './divergence.js';\n\nexport { lexiconOf, lexiconOfValues, lexiconValuesOf, structureOf, withExamples, LEXICON_CAP } from './lexicon.js';\nexport { LANDMARK_CAP } from './landmark.js';\nexport type { Landmark } from './landmark.js';\nexport type { BoundaryRow, LexiconField, LexiconOptions, LexiconValues, SubjectLexicon } from './lexicon.js';\n\nexport { attributeMovement } from './movement.js';\nexport type { Attribution, Cause, Evidence, Moved, Movement } from './movement.js';\n\nexport { coverageOf, alsoCovering, summarizeCoverage } from './coverage.js';\nexport type { Coverage, ComponentCoverage, SubjectCoverage, SubjectValue } from './coverage.js';\n\nexport { locateInstability, summarizeInstability } from './instability.js';\nexport type {\n Instability,\n InstabilityBand,\n UnstableLocation,\n UnstableProperty,\n} from './instability.js';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/attribute/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAGrC,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAU3E,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG5D,OAAO,EACL,aAAa,EACb,YAAY,EACZ,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,aAAa,CAAC;AAGrB,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAG9E,OAAO,EACL,cAAc,EACd,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAGvF,OAAO,EACL,cAAc,EACd,aAAa,EACb,UAAU,EACV,iBAAiB,EACjB,YAAY,EACZ,YAAY,GACb,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAGhE,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAG5D,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAanD,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAG/C,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,eAAe,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AACnH,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAI7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAGlD,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAG5E,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC","sourcesContent":["/**\n * `@variance-authority/core/attribute` — from a position to a name to a file.\n *\n * The group that makes a difference actionable. Pixels cluster into regions,\n * regions join the box tree, the tree knows which component produced each node,\n * and the component resolves to a source location. The same machinery answers\n * \"which component is not holding still\" and \"which subjects cover this\n * component\".\n *\n * This is the answer to *\"a pixel differ reports 1530 pixels changed\"* — and the\n * reason it is a group rather than a function is that every step of the journey\n * is separately useful, and separately capable of refusing. `locateInstability`\n * will not name a component for a difference that is beneath the semantic tier;\n * `coverage` will not recommend deleting anything.\n */\n\nexport { locate } from './locate.js';\nexport type { Location, LocationStep } from './locate.js';\n\nexport { isolateRegions, subtractRegions, excludedBoxes } from './mask.js';\nexport type {\n ChangeMask,\n DiffRegion,\n ExcludedBox,\n Isolation,\n IsolationOptions,\n Subtraction,\n} from './mask.js';\n\nexport { attributeRegions, rankRegions } from './region.js';\nexport type { AttributedRegion, AttributionOptions, RankedRegion } from './region.js';\n\nexport {\n resolveSource,\n formatSource,\n mergeSourceIndexes,\n overlaySourceIndex,\n} from './source.js';\nexport type { SourceIndex, SourceRef, Resolution } from './source.js';\n\nexport { parseStackFrames, isVendorPath, writerLocationOf } from './stack.js';\nexport type { StackFrame } from './stack.js';\n\nexport {\n parseSourceMap,\n originalPositionFor,\n sourceMappingUrlOf,\n inlineSourceMapOf,\n} from './source-map.js';\nexport type { SourceMap, OriginalPosition } from './source-map.js';\n\nexport { createCallSiteResolver, locateProvenance, locateSites } from './call-site.js';\nexport type { CallSiteResolver, CallSiteStats, FetchModule, LocatableSite } from './call-site.js';\n\nexport {\n hashComponents,\n causesBetween,\n movedBands,\n movedBandsBetween,\n bandsBetween,\n UNATTRIBUTED,\n} from './component-hash.js';\nexport type { BandDigests, ComponentBands, ComponentHash, ValueChange } from './component-hash.js';\n\nexport { componentInstances, attributed } from './instances.js';\nexport type { ComponentInstance } from './instances.js';\n\nexport { nodeClosures, sharedClosures } from './closure.js';\nexport type { ClosureSite, NodeClosure, SharedClosure, SharedClosureOptions, SubjectTree } from './closure.js';\n\nexport { composeSubjects } from './composition.js';\nexport type {\n Composition,\n ComponentEntry,\n Echo,\n PropsClass,\n Rendering,\n Site,\n SubjectComposition,\n} from './composition.js';\n\nexport type { Divergence, DivergenceParting } from './divergence.js';\n\nexport { piecesOf } from './subject-pieces.js';\nexport type { Residue, SubjectPieces, SubjectShare } from './subject-pieces.js';\n\nexport { lexiconOf, lexiconOfValues, lexiconValuesOf, structureOf, withExamples, LEXICON_CAP } from './lexicon.js';\nexport { LANDMARK_CAP } from './landmark.js';\nexport type { Landmark } from './landmark.js';\nexport type { BoundaryRow, LexiconField, LexiconOptions, LexiconValues, SubjectLexicon } from './lexicon.js';\n\nexport { attributeMovement } from './movement.js';\nexport type { Attribution, Cause, Evidence, Moved, Movement } from './movement.js';\n\nexport { coverageOf, alsoCovering, summarizeCoverage } from './coverage.js';\nexport type { Coverage, ComponentCoverage, SubjectCoverage, SubjectValue } from './coverage.js';\n\nexport { locateInstability, summarizeInstability } from './instability.js';\nexport type {\n Instability,\n InstabilityBand,\n UnstableLocation,\n UnstableProperty,\n} from './instability.js';\n"]}
@@ -1,3 +1,4 @@
1
+ import { intersectionArea } from '../format/capture.js';
1
2
  import { locate } from './locate.js';
2
3
  const DEFAULT_CONTAINMENT = 0.9;
3
4
  /**
@@ -136,9 +137,4 @@ function collectBoxes(root) {
136
137
  walk(root);
137
138
  return out;
138
139
  }
139
- function intersectionArea(a, b) {
140
- const width = Math.min(a.x + a.width, b.x + b.width) - Math.max(a.x, b.x);
141
- const height = Math.min(a.y + a.height, b.y + b.height) - Math.max(a.y, b.y);
142
- return width > 0 && height > 0 ? width * height : 0;
143
- }
144
140
  //# sourceMappingURL=region.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"region.js","sourceRoot":"","sources":["../../src/attribute/region.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Rect } from '../format/capture.js';\nimport { locate } from './locate.js';\nimport type { DiffRegion } from './mask.js';\nimport type { NodePath, SemanticNode, SemanticSnapshot } from '../format/snapshot.js';\nimport type { SourceLocation } from '../format/provenance.js';\n\n/**\n * Isolating a pixel difference, and connecting it to the tree.\n *\n * A pixel differ answers one question — *how many pixels moved* — and that number\n * is the reason \"looks right, merge\" exists. 5482 is not a finding. It cannot be\n * read, it cannot be assigned, and the only available response to it is to open\n * the image and look, which is the expensive act the tool was supposed to replace.\n *\n * Two phases turn it into something else, and they are separate because they fail\n * differently. `mask.ts` is arithmetic on a bitmask — no DOM, no snapshot, no\n * notion of what a component is — and answers *where on the canvas*.\n * {@link attributeRegions} joins those coordinates to the box tree and answers\n * *what is there*. The first can be tested with a hand-written mask; the second\n * with a hand-written snapshot; neither needs a browser, which is what makes the\n * pipeline testable at all.\n *\n * The join is geometric and therefore the weakest link in the chain, so it is\n * explicit about failing: a region no box contains is reported `unattributed`.\n * Painting escapes its box routinely — shadows, outlines, overflowing glyphs — and\n * a system that resolved those by picking the nearest node would produce confident\n * attributions of exactly the kind an agent then acts on.\n */\n\nexport interface AttributedRegion {\n readonly region: DiffRegion;\n\n /**\n * Tightest node whose border box contains the region, when one does.\n *\n * Tightest rather than topmost: a change inside a button is also inside the\n * card and inside `<main>`, and only the innermost answer is actionable.\n */\n readonly path?: NodePath;\n readonly component?: string;\n /** Landmark phrase from {@link locate}, so a region has a name, not an address. */\n readonly where?: string;\n\n /**\n * The line of JSX that produced this node, when the build recorded one.\n *\n * The last hop, and a different answer from the one a component name gets.\n * Resolving `Button` against a source index names where `Button` is\n * *declared*; this names where the element that changed is *written*, which\n * for anything rendered more than once is the only one of the two that\n * distinguishes the instances. Present only under\n * `@variance-authority/jsx-source` or React ≤18 — the name scan is what a\n * repository that has configured nothing still gets.\n */\n readonly source?: SourceLocation;\n\n /**\n * `true` when no box contains the region at all.\n *\n * Rarer than it first looks, and that is worth being precise about. A screenshot\n * clipped to the subject means the root's own box contains every pixel in it, so\n * a region always lands *somewhere* — paint that escapes a button still falls\n * inside the card, and attributing it to the card is correct rather than a\n * consolation prize.\n *\n * What this flag actually catches is a region that fell outside the tree\n * entirely, and the overwhelmingly likely cause is that `scale` or `origin` was\n * wrong. That failure is worth a loud signal precisely because it does not look\n * like one: at the wrong scale every region lands in the top-left quadrant and\n * the report comes out complete, plausible, and about the wrong components.\n */\n readonly unattributed: boolean;\n\n /**\n * Tightest node the region *overlaps*, present only when unattributed.\n *\n * Offered as orientation, never as attribution — hence the separate field. It\n * answers \"what is this near\", which is a different claim from \"this is what\n * changed\", and keeping them in one field is how the second gets asserted with\n * the confidence of the first.\n */\n readonly nearest?: { readonly path: NodePath; readonly component?: string; readonly where?: string };\n\n /**\n * Nearest enclosing component, when it is not the one that authored the node.\n *\n * Two namespaces meet here and neither is wrong. `component` is `createdBy` —\n * who wrote the JSX (ADR-0007) — and a component *hash* is named for the\n * enclosure (ADR-0018). They diverge exactly where an element is passed as a\n * prop: `<Card title={<h3>Invoice</h3>} />` gives the `<h3>` `createdBy: Page`\n * and `owners[0]: Card`.\n *\n * Carried so that ranking can match a cause list without guessing which of the\n * two it is written in. Matching one namespace against the other does not\n * fail loudly — it silently finds nothing, and the ordering falls back to area,\n * which is the thing the cause list exists to prevent.\n */\n readonly owner?: string;\n\n /**\n * The shape of this difference, with its position and values removed.\n *\n * Filled in by whoever held the mask — `attributeRegions` never sets it,\n * because a fingerprint is computed from pixels and this function is given\n * boxes. It exists on this type rather than beside it so that the digest\n * travels with the region a reader is looking at: writing an ignore means\n * copying the fingerprint of the thing that annoyed you, and a digest printed\n * in a different section is a digest nobody matches up.\n *\n * See `fingerprintOfMask` in `core/judge`.\n */\n readonly fingerprint?: string;\n}\n\nexport interface AttributionOptions {\n /**\n * Device pixels per CSS pixel in the raster.\n *\n * Required, with no default. A 2x screenshot silently attributed at 1x lands\n * every region in the top-left quadrant of the page and attributes each of them\n * to the wrong node — a failure that produces a full, plausible, entirely wrong\n * report. Making the caller state it turns that into a decision someone made.\n */\n readonly scale: number;\n\n /**\n * Page-space CSS origin of the raster's top-left corner.\n *\n * Defaults to the subject root's own rect, which is right when the screenshot\n * was clipped to the subject — the normal case. A full-page shot passes `{x: 0,\n * y: 0}`.\n */\n readonly origin?: { readonly x: number; readonly y: number };\n\n /**\n * Fraction of a region's area that must fall inside a box to count as inside.\n *\n * Below 1 by default because a region's bounding box is inflated by\n * antialiasing at its edges, so exact containment would reject the correct node\n * on a text change. This is the one genuinely fuzzy number in the join and it\n * is named rather than buried.\n */\n readonly containment?: number;\n}\n\nconst DEFAULT_CONTAINMENT = 0.9;\n\n/**\n * Join isolated regions to the nodes that occupy them.\n *\n * Requires a snapshot carrying `rect`, i.e. a profile with layout (ADR-0002).\n * Under a profile without one every region comes back unattributed, which is the\n * correct answer — a rect that was never observed must never be inferred.\n */\nexport function attributeRegions(\n regions: readonly DiffRegion[],\n snapshot: SemanticSnapshot,\n options: AttributionOptions,\n): readonly AttributedRegion[] {\n const boxed = collectBoxes(snapshot.root);\n const origin = options.origin ?? snapshot.root.rect ?? { x: 0, y: 0 };\n const containment = options.containment ?? DEFAULT_CONTAINMENT;\n\n return regions.map((region) => {\n const css: Rect = {\n x: region.x / options.scale + origin.x,\n y: region.y / options.scale + origin.y,\n width: region.width / options.scale,\n height: region.height / options.scale,\n };\n const area = css.width * css.height;\n\n let inside: SemanticNode | undefined;\n let overlapping: SemanticNode | undefined;\n\n for (const node of boxed) {\n const rect = node.rect!;\n const shared = intersectionArea(css, rect);\n if (shared <= 0) continue;\n\n const nodeArea = rect.width * rect.height;\n\n // `<=`, not `<`, and the difference is one component name in every report\n // over a design system. A wrapper that shrink-wraps its only child carries\n // a *byte-identical* rect — measured on `cases/storybook-case`, where\n // `Tokens` and `Button` are both `454.34,359 115.33×50` — so neither is\n // tighter than the other and the walk decides. `collectBoxes` is pre-order,\n // so a strict `<` keeps whichever came first, which is always the outer\n // one: the run then names the wrapper nothing edited and sends a reviewer\n // to its file. Ties go to the last box seen, which is the innermost, and is\n // also the one the browser painted on top.\n if (area > 0 && shared / area >= containment) {\n if (inside === undefined || nodeArea <= inside.rect!.width * inside.rect!.height) inside = node;\n }\n // The same rule for `nearest`. It is orientation rather than attribution,\n // and a hint should point at the same node the join would have picked.\n if (overlapping === undefined || nodeArea <= overlapping.rect!.width * overlapping.rect!.height) {\n overlapping = node;\n }\n }\n\n if (inside !== undefined) {\n return {\n region,\n path: inside.path,\n ...describe(snapshot, inside),\n unattributed: false,\n };\n }\n\n return {\n region,\n unattributed: true,\n ...(overlapping !== undefined\n ? { nearest: { path: overlapping.path, ...describe(snapshot, overlapping) } }\n : {}),\n };\n });\n}\n\nexport interface RankedRegion extends AttributedRegion {\n /**\n * `true` when the semantic tier named this region's component as a *root* of\n * the change, rather than as something the change happened to move.\n */\n readonly cause: boolean;\n}\n\n/**\n * Rank attributed regions by cause, falling back to area.\n *\n * This function exists because of a measurement, and the measurement is the most\n * useful thing the raster tier has produced. Replacing a checkbox with a styled\n * div on the todomvc corpus changes 1530 pixels in 5 regions, which attribute\n * geometrically and rank by area as:\n *\n * ```\n * 933px Text <- changed, and reflowed\n * 511px Stack <- only reflowed\n * 86px Toggle <- the edit\n * ```\n *\n * Every one of those attributions is correct. The pixels really are inside those\n * nodes. The *ordering* is still wrong, because **area measures displacement,\n * not cause** — an edit that reflows its surroundings moves far more of them than\n * of itself, so `Stack`, which nothing edited, outranks `Toggle`, which is the\n * edit. That is the same defect the differ had when a list reorder blamed the\n * element that moved rather than the code that moved it.\n *\n * Geometry cannot fix this; it has no access to why. The semantic tier does — it\n * has provenance and props digests — so the raster tier stops claiming to rank\n * and takes the ordering from the tier that can. On the case above that promotes\n * `Toggle` above `Stack` while leaving both attributions untouched.\n *\n * Two limits, stated rather than smoothed over. The semantic tier may name more\n * than one cause and here it names two, `Toggle` and `Text`, both real — this\n * does not collapse them, because picking one would be inventing a fact. And\n * with no causes supplied the order falls back to area, which is honest and is\n * not good: pixels alone rank the displaced above the displacer, and nothing\n * inside this file can change that.\n */\nexport function rankRegions(\n regions: readonly AttributedRegion[],\n causes: readonly string[] = [],\n): readonly RankedRegion[] {\n const named = new Set(causes);\n\n return regions\n .map((region) => ({\n ...region,\n // Either namespace. A cause list is named for enclosures and a region is\n // named for its author, and where those differ a one-sided test finds\n // nothing and silently reverts the ordering to area.\n cause:\n (region.component !== undefined && named.has(region.component)) ||\n (region.owner !== undefined && named.has(region.owner)),\n }))\n .sort((a, b) => Number(b.cause) - Number(a.cause) || b.region.pixels - a.region.pixels);\n}\n\nfunction describe(\n snapshot: SemanticSnapshot,\n node: SemanticNode,\n): { component?: string; owner?: string; where?: string; source?: SourceLocation } {\n // `createdBy` before `owners[0]`: the component whose JSX produced this element\n // owns its appearance, while the nearest enclosing component merely contains it.\n const owner = node.provenance?.owners[0]?.name;\n const component = node.provenance?.createdBy ?? owner;\n const where = locate(snapshot.root, node.path).where;\n\n return {\n ...(component !== undefined ? { component } : {}),\n // Only when it says something `component` does not. An `owner` that repeats\n // the author is noise in every report that prints it.\n ...(owner !== undefined && owner !== component ? { owner } : {}),\n ...(where !== '' ? { where } : {}),\n ...(node.provenance?.source ? { source: node.provenance.source } : {}),\n };\n}\n\n/** Every node carrying a rect, in document order. Zero-area boxes are skipped. */\nfunction collectBoxes(root: SemanticNode): readonly SemanticNode[] {\n const out: SemanticNode[] = [];\n\n const walk = (node: SemanticNode): void => {\n if (node.rect !== undefined && node.rect.width > 0 && node.rect.height > 0) out.push(node);\n for (const child of node.children) walk(child);\n };\n walk(root);\n\n return out;\n}\n\nfunction intersectionArea(a: Rect, b: Rect): number {\n const width = Math.min(a.x + a.width, b.x + b.width) - Math.max(a.x, b.x);\n const height = Math.min(a.y + a.height, b.y + b.height) - Math.max(a.y, b.y);\n return width > 0 && height > 0 ? width * height : 0;\n}\n"]}
1
+ {"version":3,"file":"region.js","sourceRoot":"","sources":["../../src/attribute/region.ts"],"names":[],"mappings":"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","sourcesContent":["import { intersectionArea, type Rect } from '../format/capture.js';\nimport { locate } from './locate.js';\nimport type { DiffRegion } from './mask.js';\nimport type { NodePath, SemanticNode, SemanticSnapshot } from '../format/snapshot.js';\nimport type { SourceLocation } from '../format/provenance.js';\n\n/**\n * Isolating a pixel difference, and connecting it to the tree.\n *\n * A pixel differ answers one question — *how many pixels moved* — and that number\n * is the reason \"looks right, merge\" exists. 5482 is not a finding. It cannot be\n * read, it cannot be assigned, and the only available response to it is to open\n * the image and look, which is the expensive act the tool was supposed to replace.\n *\n * Two phases turn it into something else, and they are separate because they fail\n * differently. `mask.ts` is arithmetic on a bitmask — no DOM, no snapshot, no\n * notion of what a component is — and answers *where on the canvas*.\n * {@link attributeRegions} joins those coordinates to the box tree and answers\n * *what is there*. The first can be tested with a hand-written mask; the second\n * with a hand-written snapshot; neither needs a browser, which is what makes the\n * pipeline testable at all.\n *\n * The join is geometric and therefore the weakest link in the chain, so it is\n * explicit about failing: a region no box contains is reported `unattributed`.\n * Painting escapes its box routinely — shadows, outlines, overflowing glyphs — and\n * a system that resolved those by picking the nearest node would produce confident\n * attributions of exactly the kind an agent then acts on.\n */\n\nexport interface AttributedRegion {\n readonly region: DiffRegion;\n\n /**\n * Tightest node whose border box contains the region, when one does.\n *\n * Tightest rather than topmost: a change inside a button is also inside the\n * card and inside `<main>`, and only the innermost answer is actionable.\n */\n readonly path?: NodePath;\n readonly component?: string;\n /** Landmark phrase from {@link locate}, so a region has a name, not an address. */\n readonly where?: string;\n\n /**\n * The line of JSX that produced this node, when the build recorded one.\n *\n * The last hop, and a different answer from the one a component name gets.\n * Resolving `Button` against a source index names where `Button` is\n * *declared*; this names where the element that changed is *written*, which\n * for anything rendered more than once is the only one of the two that\n * distinguishes the instances. Present only under\n * `@variance-authority/jsx-source` or React ≤18 — the name scan is what a\n * repository that has configured nothing still gets.\n */\n readonly source?: SourceLocation;\n\n /**\n * `true` when no box contains the region at all.\n *\n * Rarer than it first looks, and that is worth being precise about. A screenshot\n * clipped to the subject means the root's own box contains every pixel in it, so\n * a region always lands *somewhere* — paint that escapes a button still falls\n * inside the card, and attributing it to the card is correct rather than a\n * consolation prize.\n *\n * What this flag actually catches is a region that fell outside the tree\n * entirely, and the overwhelmingly likely cause is that `scale` or `origin` was\n * wrong. That failure is worth a loud signal precisely because it does not look\n * like one: at the wrong scale every region lands in the top-left quadrant and\n * the report comes out complete, plausible, and about the wrong components.\n */\n readonly unattributed: boolean;\n\n /**\n * Tightest node the region *overlaps*, present only when unattributed.\n *\n * Offered as orientation, never as attribution — hence the separate field. It\n * answers \"what is this near\", which is a different claim from \"this is what\n * changed\", and keeping them in one field is how the second gets asserted with\n * the confidence of the first.\n */\n readonly nearest?: { readonly path: NodePath; readonly component?: string; readonly where?: string };\n\n /**\n * Nearest enclosing component, when it is not the one that authored the node.\n *\n * Two namespaces meet here and neither is wrong. `component` is `createdBy` —\n * who wrote the JSX (ADR-0007) — and a component *hash* is named for the\n * enclosure (ADR-0018). They diverge exactly where an element is passed as a\n * prop: `<Card title={<h3>Invoice</h3>} />` gives the `<h3>` `createdBy: Page`\n * and `owners[0]: Card`.\n *\n * Carried so that ranking can match a cause list without guessing which of the\n * two it is written in. Matching one namespace against the other does not\n * fail loudly — it silently finds nothing, and the ordering falls back to area,\n * which is the thing the cause list exists to prevent.\n */\n readonly owner?: string;\n\n /**\n * The shape of this difference, with its position and values removed.\n *\n * Filled in by whoever held the mask — `attributeRegions` never sets it,\n * because a fingerprint is computed from pixels and this function is given\n * boxes. It exists on this type rather than beside it so that the digest\n * travels with the region a reader is looking at: writing an ignore means\n * copying the fingerprint of the thing that annoyed you, and a digest printed\n * in a different section is a digest nobody matches up.\n *\n * See `fingerprintOfMask` in `core/judge`.\n */\n readonly fingerprint?: string;\n}\n\nexport interface AttributionOptions {\n /**\n * Device pixels per CSS pixel in the raster.\n *\n * Required, with no default. A 2x screenshot silently attributed at 1x lands\n * every region in the top-left quadrant of the page and attributes each of them\n * to the wrong node — a failure that produces a full, plausible, entirely wrong\n * report. Making the caller state it turns that into a decision someone made.\n */\n readonly scale: number;\n\n /**\n * Page-space CSS origin of the raster's top-left corner.\n *\n * Defaults to the subject root's own rect, which is right when the screenshot\n * was clipped to the subject — the normal case. A full-page shot passes `{x: 0,\n * y: 0}`.\n */\n readonly origin?: { readonly x: number; readonly y: number };\n\n /**\n * Fraction of a region's area that must fall inside a box to count as inside.\n *\n * Below 1 by default because a region's bounding box is inflated by\n * antialiasing at its edges, so exact containment would reject the correct node\n * on a text change. This is the one genuinely fuzzy number in the join and it\n * is named rather than buried.\n */\n readonly containment?: number;\n}\n\nconst DEFAULT_CONTAINMENT = 0.9;\n\n/**\n * Join isolated regions to the nodes that occupy them.\n *\n * Requires a snapshot carrying `rect`, i.e. a profile with layout (ADR-0002).\n * Under a profile without one every region comes back unattributed, which is the\n * correct answer — a rect that was never observed must never be inferred.\n */\nexport function attributeRegions(\n regions: readonly DiffRegion[],\n snapshot: SemanticSnapshot,\n options: AttributionOptions,\n): readonly AttributedRegion[] {\n const boxed = collectBoxes(snapshot.root);\n const origin = options.origin ?? snapshot.root.rect ?? { x: 0, y: 0 };\n const containment = options.containment ?? DEFAULT_CONTAINMENT;\n\n return regions.map((region) => {\n const css: Rect = {\n x: region.x / options.scale + origin.x,\n y: region.y / options.scale + origin.y,\n width: region.width / options.scale,\n height: region.height / options.scale,\n };\n const area = css.width * css.height;\n\n let inside: SemanticNode | undefined;\n let overlapping: SemanticNode | undefined;\n\n for (const node of boxed) {\n const rect = node.rect!;\n const shared = intersectionArea(css, rect);\n if (shared <= 0) continue;\n\n const nodeArea = rect.width * rect.height;\n\n // `<=`, not `<`, and the difference is one component name in every report\n // over a design system. A wrapper that shrink-wraps its only child carries\n // a *byte-identical* rect — measured on `cases/storybook-case`, where\n // `Tokens` and `Button` are both `454.34,359 115.33×50` — so neither is\n // tighter than the other and the walk decides. `collectBoxes` is pre-order,\n // so a strict `<` keeps whichever came first, which is always the outer\n // one: the run then names the wrapper nothing edited and sends a reviewer\n // to its file. Ties go to the last box seen, which is the innermost, and is\n // also the one the browser painted on top.\n if (area > 0 && shared / area >= containment) {\n if (inside === undefined || nodeArea <= inside.rect!.width * inside.rect!.height) inside = node;\n }\n // The same rule for `nearest`. It is orientation rather than attribution,\n // and a hint should point at the same node the join would have picked.\n if (overlapping === undefined || nodeArea <= overlapping.rect!.width * overlapping.rect!.height) {\n overlapping = node;\n }\n }\n\n if (inside !== undefined) {\n return {\n region,\n path: inside.path,\n ...describe(snapshot, inside),\n unattributed: false,\n };\n }\n\n return {\n region,\n unattributed: true,\n ...(overlapping !== undefined\n ? { nearest: { path: overlapping.path, ...describe(snapshot, overlapping) } }\n : {}),\n };\n });\n}\n\nexport interface RankedRegion extends AttributedRegion {\n /**\n * `true` when the semantic tier named this region's component as a *root* of\n * the change, rather than as something the change happened to move.\n */\n readonly cause: boolean;\n}\n\n/**\n * Rank attributed regions by cause, falling back to area.\n *\n * This function exists because of a measurement, and the measurement is the most\n * useful thing the raster tier has produced. Replacing a checkbox with a styled\n * div on the todomvc corpus changes 1530 pixels in 5 regions, which attribute\n * geometrically and rank by area as:\n *\n * ```\n * 933px Text <- changed, and reflowed\n * 511px Stack <- only reflowed\n * 86px Toggle <- the edit\n * ```\n *\n * Every one of those attributions is correct. The pixels really are inside those\n * nodes. The *ordering* is still wrong, because **area measures displacement,\n * not cause** — an edit that reflows its surroundings moves far more of them than\n * of itself, so `Stack`, which nothing edited, outranks `Toggle`, which is the\n * edit. That is the same defect the differ had when a list reorder blamed the\n * element that moved rather than the code that moved it.\n *\n * Geometry cannot fix this; it has no access to why. The semantic tier does — it\n * has provenance and props digests — so the raster tier stops claiming to rank\n * and takes the ordering from the tier that can. On the case above that promotes\n * `Toggle` above `Stack` while leaving both attributions untouched.\n *\n * Two limits, stated rather than smoothed over. The semantic tier may name more\n * than one cause and here it names two, `Toggle` and `Text`, both real — this\n * does not collapse them, because picking one would be inventing a fact. And\n * with no causes supplied the order falls back to area, which is honest and is\n * not good: pixels alone rank the displaced above the displacer, and nothing\n * inside this file can change that.\n */\nexport function rankRegions(\n regions: readonly AttributedRegion[],\n causes: readonly string[] = [],\n): readonly RankedRegion[] {\n const named = new Set(causes);\n\n return regions\n .map((region) => ({\n ...region,\n // Either namespace. A cause list is named for enclosures and a region is\n // named for its author, and where those differ a one-sided test finds\n // nothing and silently reverts the ordering to area.\n cause:\n (region.component !== undefined && named.has(region.component)) ||\n (region.owner !== undefined && named.has(region.owner)),\n }))\n .sort((a, b) => Number(b.cause) - Number(a.cause) || b.region.pixels - a.region.pixels);\n}\n\nfunction describe(\n snapshot: SemanticSnapshot,\n node: SemanticNode,\n): { component?: string; owner?: string; where?: string; source?: SourceLocation } {\n // `createdBy` before `owners[0]`: the component whose JSX produced this element\n // owns its appearance, while the nearest enclosing component merely contains it.\n const owner = node.provenance?.owners[0]?.name;\n const component = node.provenance?.createdBy ?? owner;\n const where = locate(snapshot.root, node.path).where;\n\n return {\n ...(component !== undefined ? { component } : {}),\n // Only when it says something `component` does not. An `owner` that repeats\n // the author is noise in every report that prints it.\n ...(owner !== undefined && owner !== component ? { owner } : {}),\n ...(where !== '' ? { where } : {}),\n ...(node.provenance?.source ? { source: node.provenance.source } : {}),\n };\n}\n\n/** Every node carrying a rect, in document order. Zero-area boxes are skipped. */\nfunction collectBoxes(root: SemanticNode): readonly SemanticNode[] {\n const out: SemanticNode[] = [];\n\n const walk = (node: SemanticNode): void => {\n if (node.rect !== undefined && node.rect.width > 0 && node.rect.height > 0) out.push(node);\n for (const child of node.children) walk(child);\n };\n walk(root);\n\n return out;\n}\n"]}
@@ -21,8 +21,10 @@
21
21
  * configuring a build, and it answers with where a component is *declared* —
22
22
  * coarser than a call site, and enough to open the right file.
23
23
  *
24
- * The index is plain data. `core` performs no I/O (ADR-0006), so building one is
25
- * a caller's job; resolving against one is here.
24
+ * The index is plain data. `core` performs no I/O (ADR-0006) and has no parser,
25
+ * so building one is a caller's job — `indexDeclarations` in
26
+ * [`@variance-authority/sense`](../../../sense/src/declarations.ts) reads it off
27
+ * a module's parse — and resolving against one is here.
26
28
  */
27
29
  export interface SourceRef {
28
30
  /** Repository-relative, so a report is portable between machines and CI. */
@@ -31,12 +33,13 @@ export interface SourceRef {
31
33
  /**
32
34
  * How the declaration was recognised. Carried so a bad match is debuggable.
33
35
  *
34
- * The first four are shapes a scan of the source text matched by name.
35
- * `engine` is the one that is not a match at all: the browser was asked where
36
- * the function it rendered was compiled from, and that position was mapped
37
- * back to the file. It names the component that ran, whatever it was called.
36
+ * The first three say which kind of statement declared the name; a `let`
37
+ * binding is shown as `const`. `engine` is the one that is not read from
38
+ * source at all: the browser was asked where the function it rendered was
39
+ * compiled from, and that position was mapped back to the file. It names the
40
+ * component that ran, whatever it was called.
38
41
  */
39
- readonly via: 'function' | 'const' | 'class' | 'declared' | 'engine';
42
+ readonly via: 'function' | 'const' | 'class' | 'engine';
40
43
  }
41
44
  /**
42
45
  * Component name → where it is declared.
@@ -62,23 +65,6 @@ export declare function resolveSource(name: string, index: SourceIndex): Resolut
62
65
  * every agent harness already know how to open.
63
66
  */
64
67
  export declare function formatSource(resolution: Resolution): string;
65
- /**
66
- * Extract component declarations from one file's source.
67
- *
68
- * A regex scan, and the limits are worth stating rather than discovering. It
69
- * finds exported and local declarations in the three shapes React components are
70
- * written in, and it will miss a component produced by a factory, assigned
71
- * dynamically, or re-exported under another name. It can also match a function
72
- * that merely looks like a component — capitalised, declared at top level — and
73
- * name a non-component in a report.
74
- *
75
- * Both failures are survivable in a way a wrong file path would not be: a miss
76
- * degrades the report to the component name, which is what it said before, and a
77
- * false positive can only appear if attribution already named that identifier.
78
- * Parsing properly is the right answer eventually; it is not worth a parser
79
- * dependency to find out whether the link is useful.
80
- */
81
- export declare function indexSource(file: string, contents: string): SourceIndex;
82
68
  /** Merge per-file indexes. A name declared in several files keeps every ref. */
83
69
  export declare function mergeSourceIndexes(indexes: readonly SourceIndex[]): SourceIndex;
84
70
  /**