@juxtapose/core 0.4.0 → 0.5.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 (131) hide show
  1. package/dist/agent/handoff.d.ts +8 -1
  2. package/dist/agent/handoff.js +75 -7
  3. package/dist/agent/handoff.js.map +1 -1
  4. package/dist/analysis/affected.d.ts +37 -0
  5. package/dist/analysis/affected.js +97 -22
  6. package/dist/analysis/affected.js.map +1 -1
  7. package/dist/analysis/cause.d.ts +32 -0
  8. package/dist/analysis/cause.js +187 -0
  9. package/dist/analysis/cause.js.map +1 -0
  10. package/dist/baseline/approve.d.ts +24 -0
  11. package/dist/baseline/approve.js +54 -0
  12. package/dist/baseline/approve.js.map +1 -0
  13. package/dist/baseline/git-store.d.ts +4 -0
  14. package/dist/baseline/git-store.js +22 -0
  15. package/dist/baseline/git-store.js.map +1 -1
  16. package/dist/browser/acquire.d.ts +11 -0
  17. package/dist/browser/acquire.js +15 -0
  18. package/dist/browser/acquire.js.map +1 -0
  19. package/dist/browser/browser.d.ts +53 -0
  20. package/dist/browser/browser.js +122 -0
  21. package/dist/browser/browser.js.map +1 -0
  22. package/dist/browser/cdp.d.ts +81 -0
  23. package/dist/browser/cdp.js +230 -0
  24. package/dist/browser/cdp.js.map +1 -0
  25. package/dist/browser/index.d.ts +9 -0
  26. package/dist/browser/index.js +10 -0
  27. package/dist/browser/index.js.map +1 -0
  28. package/dist/browser/install.d.ts +33 -0
  29. package/dist/browser/install.js +167 -0
  30. package/dist/browser/install.js.map +1 -0
  31. package/dist/browser/launch.d.ts +37 -0
  32. package/dist/browser/launch.js +265 -0
  33. package/dist/browser/launch.js.map +1 -0
  34. package/dist/browser/page.d.ts +125 -0
  35. package/dist/browser/page.js +564 -0
  36. package/dist/browser/page.js.map +1 -0
  37. package/dist/browser/pins.d.ts +12 -0
  38. package/dist/browser/pins.js +57 -0
  39. package/dist/browser/pins.js.map +1 -0
  40. package/dist/browser/resolve.d.ts +25 -0
  41. package/dist/browser/resolve.js +115 -0
  42. package/dist/browser/resolve.js.map +1 -0
  43. package/dist/browser/selectors.d.ts +10 -0
  44. package/dist/browser/selectors.js +60 -0
  45. package/dist/browser/selectors.js.map +1 -0
  46. package/dist/browser/transport.d.ts +39 -0
  47. package/dist/browser/transport.js +99 -0
  48. package/dist/browser/transport.js.map +1 -0
  49. package/dist/browser/zip.d.ts +27 -0
  50. package/dist/browser/zip.js +241 -0
  51. package/dist/browser/zip.js.map +1 -0
  52. package/dist/capture/capture.d.ts +7 -1
  53. package/dist/capture/capture.js +25 -8
  54. package/dist/capture/capture.js.map +1 -1
  55. package/dist/capture/env.d.ts +1 -1
  56. package/dist/capture/launch.d.ts +19 -1
  57. package/dist/capture/launch.js +91 -1
  58. package/dist/capture/launch.js.map +1 -1
  59. package/dist/capture/pool.d.ts +1 -1
  60. package/dist/capture/pool.js +41 -8
  61. package/dist/capture/pool.js.map +1 -1
  62. package/dist/capture/settle.d.ts +1 -1
  63. package/dist/capture/settle.js +2 -0
  64. package/dist/capture/settle.js.map +1 -1
  65. package/dist/capture/snap.d.ts +43 -0
  66. package/dist/capture/snap.js +103 -0
  67. package/dist/capture/snap.js.map +1 -0
  68. package/dist/capture/structure.d.ts +77 -0
  69. package/dist/capture/structure.js +498 -0
  70. package/dist/capture/structure.js.map +1 -0
  71. package/dist/diff/compare.d.ts +19 -0
  72. package/dist/diff/compare.js +19 -0
  73. package/dist/diff/compare.js.map +1 -0
  74. package/dist/diff/crops.d.ts +26 -0
  75. package/dist/diff/crops.js +92 -0
  76. package/dist/diff/crops.js.map +1 -0
  77. package/dist/diff/structural/classify.d.ts +29 -0
  78. package/dist/diff/structural/classify.js +113 -0
  79. package/dist/diff/structural/classify.js.map +1 -0
  80. package/dist/diff/structural/coverage.d.ts +11 -0
  81. package/dist/diff/structural/coverage.js +37 -0
  82. package/dist/diff/structural/coverage.js.map +1 -0
  83. package/dist/diff/structural/deltas.d.ts +29 -0
  84. package/dist/diff/structural/deltas.js +218 -0
  85. package/dist/diff/structural/deltas.js.map +1 -0
  86. package/dist/diff/structural/disambiguate.d.ts +3 -0
  87. package/dist/diff/structural/disambiguate.js +89 -0
  88. package/dist/diff/structural/disambiguate.js.map +1 -0
  89. package/dist/diff/structural/label.d.ts +15 -0
  90. package/dist/diff/structural/label.js +64 -0
  91. package/dist/diff/structural/label.js.map +1 -0
  92. package/dist/diff/structural/match.d.ts +13 -0
  93. package/dist/diff/structural/match.js +135 -0
  94. package/dist/diff/structural/match.js.map +1 -0
  95. package/dist/diff/structural/shift.d.ts +2 -0
  96. package/dist/diff/structural/shift.js +144 -0
  97. package/dist/diff/structural/shift.js.map +1 -0
  98. package/dist/diff/structural/summary.d.ts +31 -0
  99. package/dist/diff/structural/summary.js +420 -0
  100. package/dist/diff/structural/summary.js.map +1 -0
  101. package/dist/diff/structural.d.ts +40 -0
  102. package/dist/diff/structural.js +219 -0
  103. package/dist/diff/structural.js.map +1 -0
  104. package/dist/index.d.ts +25 -3
  105. package/dist/index.js +29 -2
  106. package/dist/index.js.map +1 -1
  107. package/dist/platform.d.ts +1 -1
  108. package/dist/platforms/urls.d.ts +3 -0
  109. package/dist/platforms/urls.js +89 -0
  110. package/dist/platforms/urls.js.map +1 -0
  111. package/dist/report/comment.js +9 -3
  112. package/dist/report/comment.js.map +1 -1
  113. package/dist/report/format.d.ts +18 -0
  114. package/dist/report/format.js +20 -0
  115. package/dist/report/format.js.map +1 -1
  116. package/dist/report/html.js +2 -2
  117. package/dist/report/html.js.map +1 -1
  118. package/dist/run/groups.d.ts +2 -0
  119. package/dist/run/groups.js +45 -0
  120. package/dist/run/groups.js.map +1 -0
  121. package/dist/run/run.d.ts +6 -2
  122. package/dist/run/run.js +74 -6
  123. package/dist/run/run.js.map +1 -1
  124. package/dist/shell-quote.d.ts +2 -0
  125. package/dist/shell-quote.js +10 -0
  126. package/dist/shell-quote.js.map +1 -0
  127. package/dist/types.d.ts +199 -1
  128. package/dist/url-platform.d.ts +2 -2
  129. package/dist/url-platform.js +2 -2
  130. package/dist/url-platform.js.map +1 -1
  131. package/package.json +5 -10
@@ -0,0 +1,26 @@
1
+ import type { ChangedRegion } from '../types.js';
2
+ export declare const MAX_CROPS = 8;
3
+ export declare const MAX_CROP_SIDE = 512;
4
+ export interface Crop {
5
+ /** The changed region this strip shows, page px. */
6
+ region: ChangedRegion;
7
+ /** What was cropped: the region doubled about its centre, clamped to both frames. */
8
+ box: ChangedRegion;
9
+ /** Output px per page px: an integer above 1 when enlarged, under 1 when reduced. */
10
+ scale: number;
11
+ /** The whole strip: left side, gutter, right side. */
12
+ width: number;
13
+ height: number;
14
+ png: Buffer;
15
+ }
16
+ export declare function renderCrops(a: Buffer, b: Buffer, regions: ChangedRegion[]): Promise<Crop[]>;
17
+ /** The region doubled about its centre, at least MIN_BOX a side, clamped to
18
+ * the frame, integer px. A zero-size box means the region lies outside the
19
+ * frame. */
20
+ export declare function cropBox(r: ChangedRegion, frame: {
21
+ width: number;
22
+ height: number;
23
+ }): ChangedRegion;
24
+ /** An integer enlargement that keeps the longer side within MAX_CROP_SIDE,
25
+ * or a fractional reduction once the box itself is past it. */
26
+ export declare function scaleFor(box: ChangedRegion): number;
@@ -0,0 +1,92 @@
1
+ // Tier-3: the pixels, but only where they changed and only as much as an
2
+ // agent needs. One PNG per changed region — baseline | current, side by
3
+ // side — with the region's bounding box doubled about its centre for
4
+ // context and scaled so the longer side is at most 512px. Nearest-neighbour
5
+ // when scaling up, so a 1px border reads as a hard 6px step and not a blur.
6
+ // No browser; sharp only, which `diffImages` already loads.
7
+ import sharp from 'sharp';
8
+ export const MAX_CROPS = 8;
9
+ export const MAX_CROP_SIDE = 512;
10
+ const GUTTER = 4;
11
+ /** A 1×1 region still needs enough around it to recognise. */
12
+ const MIN_BOX = 16;
13
+ /** Pinned so the same input is the same bytes: sharp strips metadata by
14
+ * default; the level and filter fix the zlib stream. */
15
+ const PNG = { compressionLevel: 9, adaptiveFiltering: false };
16
+ /** The gutter colour: magenta is in no UI palette, so it never reads as content. */
17
+ const GUTTER_RGBA = { r: 255, g: 0, b: 255, alpha: 1 };
18
+ export async function renderCrops(a, b, regions) {
19
+ const [ma, mb] = await Promise.all([sharp(a).metadata(), sharp(b).metadata()]);
20
+ // Only the overlap exists on both sides; after a size change that is the
21
+ // smaller frame's extent.
22
+ const frame = {
23
+ width: Math.min(ma.width ?? 0, mb.width ?? 0),
24
+ height: Math.min(ma.height ?? 0, mb.height ?? 0),
25
+ };
26
+ // Nothing marked (sub-threshold, or a size change): the whole frame is
27
+ // the one thing worth looking at.
28
+ const wanted = regions.length > 0 ? regions : [{ x: 0, y: 0, ...frame }];
29
+ const out = [];
30
+ for (const region of wanted) {
31
+ // The cap counts rendered crops, not candidates: off-frame regions at the
32
+ // front must not starve a valid later one.
33
+ if (out.length >= MAX_CROPS)
34
+ break;
35
+ const box = cropBox(region, frame);
36
+ if (box.width === 0 || box.height === 0)
37
+ continue;
38
+ const scale = scaleFor(box);
39
+ const sw = Math.max(1, Math.round(box.width * scale));
40
+ const sh = Math.max(1, Math.round(box.height * scale));
41
+ const side = (img) => sharp(img)
42
+ .extract({ left: box.x, top: box.y, width: box.width, height: box.height })
43
+ .resize(sw, sh, { kernel: scale >= 1 ? 'nearest' : 'lanczos3', fit: 'fill' })
44
+ .ensureAlpha()
45
+ .png(PNG)
46
+ .toBuffer();
47
+ const [left, right] = await Promise.all([side(a), side(b)]);
48
+ const width = sw * 2 + GUTTER;
49
+ const png = await sharp({
50
+ create: { width, height: sh, channels: 4, background: GUTTER_RGBA },
51
+ })
52
+ .composite([
53
+ { input: left, left: 0, top: 0 },
54
+ { input: right, left: sw + GUTTER, top: 0 },
55
+ ])
56
+ .png(PNG)
57
+ .toBuffer();
58
+ out.push({ region, box, scale, width, height: sh, png });
59
+ }
60
+ return out;
61
+ }
62
+ /** The region doubled about its centre, at least MIN_BOX a side, clamped to
63
+ * the frame, integer px. A zero-size box means the region lies outside the
64
+ * frame. */
65
+ export function cropBox(r, frame) {
66
+ // Clamping alone would drag a region left of / above the frame back to the
67
+ // origin and crop something unrelated, so outside means no box. The edges
68
+ // are exclusive: a region starting at x == width covers no pixel. A
69
+ // zero-area region counts as one px, so (0, 0, 0, 0) is still inside.
70
+ if (r.x + Math.max(1, r.width) <= 0 ||
71
+ r.y + Math.max(1, r.height) <= 0 ||
72
+ r.x >= frame.width ||
73
+ r.y >= frame.height) {
74
+ return { x: 0, y: 0, width: 0, height: 0 };
75
+ }
76
+ const w = Math.max(MIN_BOX, r.width * 2);
77
+ const h = Math.max(MIN_BOX, r.height * 2);
78
+ const x0 = Math.min(frame.width, Math.max(0, Math.floor(r.x + r.width / 2 - w / 2)));
79
+ const y0 = Math.min(frame.height, Math.max(0, Math.floor(r.y + r.height / 2 - h / 2)));
80
+ const x1 = Math.min(frame.width, Math.ceil(x0 + w));
81
+ const y1 = Math.min(frame.height, Math.ceil(y0 + h));
82
+ return { x: x0, y: y0, width: Math.max(0, x1 - x0), height: Math.max(0, y1 - y0) };
83
+ }
84
+ /** An integer enlargement that keeps the longer side within MAX_CROP_SIDE,
85
+ * or a fractional reduction once the box itself is past it. */
86
+ export function scaleFor(box) {
87
+ const longer = Math.max(box.width, box.height);
88
+ return longer <= MAX_CROP_SIDE
89
+ ? Math.max(1, Math.floor(MAX_CROP_SIDE / longer))
90
+ : MAX_CROP_SIDE / longer;
91
+ }
92
+ //# sourceMappingURL=crops.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crops.js","sourceRoot":"","sources":["../../src/diff/crops.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ import type { ChangedRegion, Classification, Flag, StructureElement, StructureSnapshot, StyleTag } from '../../types.js';
2
+ import type { Fact } from './deltas.js';
3
+ export interface ClassifyContext {
4
+ baseline?: StructureSnapshot;
5
+ current?: StructureSnapshot;
6
+ facts: Fact[];
7
+ explained: 'full' | 'partial' | 'none';
8
+ unexplainedRegions: ChangedRegion[];
9
+ pixels: {
10
+ /** Byte-identical images: the only pixel evidence of "no change". */
11
+ imagesIdentical: boolean;
12
+ dimensionMismatch: boolean;
13
+ /** The pixel diff marked at least one region with area. */
14
+ hasRegions: boolean;
15
+ /** The images differ but the pixel diff marked no region. */
16
+ subThreshold: boolean;
17
+ };
18
+ }
19
+ export declare function styleTagOf(prop: string): StyleTag;
20
+ export declare function classify(ctx: ClassifyContext): {
21
+ classification: Classification;
22
+ styleTags: StyleTag[];
23
+ };
24
+ /** The first self-painting element (canvas, video, img…) on the current side
25
+ * under an unexplained region: its content changes without the DOM or styles
26
+ * changing, so the pixels there are content, not drift. */
27
+ export declare function selfPaintingAt(current: StructureSnapshot, unexplained: ChangedRegion[]): StructureElement | undefined;
28
+ /** In a fixed order, so the flag list serializes identically every run. */
29
+ export declare function computeFlags(ctx: ClassifyContext): Flag[];
@@ -0,0 +1,113 @@
1
+ // Classification and risk flags. First match wins, in an order that puts
2
+ // causes before consequences: a padding change that reflows the page is a
3
+ // spacing change with a shift in its deltas, not a "reflow" — the spec's own
4
+ // summary example says so.
5
+ import { SELF_PAINTING } from './label.js';
6
+ import { intersects } from './coverage.js';
7
+ const TAG_ORDER = ['color', 'typography', 'spacing', 'border', 'effects'];
8
+ export function styleTagOf(prop) {
9
+ if (/^(padding|margin)-|^gap$/.test(prop))
10
+ return 'spacing';
11
+ if (/^border-.*-(width|radius)$/.test(prop) || /^border-.*-color$/.test(prop))
12
+ return 'border';
13
+ if (/^(font-|line-height|letter-spacing|text-|content$)/.test(prop))
14
+ return 'typography';
15
+ if (/^(opacity|transform|box-shadow|z-index|stroke-width)$/.test(prop))
16
+ return 'effects';
17
+ return 'color'; // color, background-*, fill, stroke
18
+ }
19
+ export function classify(ctx) {
20
+ const none = [];
21
+ const { pixels, baseline, current, facts } = ctx;
22
+ // Never "0 mismatch pixels": pixelmatch's tolerance hides real changes
23
+ // (the corpus `background` case changes the bytes and marks 0 pixels).
24
+ if (pixels.imagesIdentical &&
25
+ !pixels.dimensionMismatch &&
26
+ !pixels.hasRegions &&
27
+ facts.length === 0)
28
+ return { classification: 'identical', styleTags: none };
29
+ // Before the structure checks: a size change is a size change whether or
30
+ // not either side captured structure.
31
+ if (pixels.dimensionMismatch)
32
+ return { classification: 'dimension-change', styleTags: none };
33
+ if (!baseline || !current)
34
+ return { classification: 'unknown', styleTags: none };
35
+ if (baseline.viewport.width !== current.viewport.width ||
36
+ baseline.viewport.height !== current.viewport.height) {
37
+ return { classification: 'dimension-change', styleTags: none };
38
+ }
39
+ // Equal hashes and no facts: the DOM and styles did not change, so the
40
+ // pixels are drift or self-painted content. The facts come from the
41
+ // elements themselves, so they outrank a hash that disagrees with them.
42
+ if (facts.length === 0 && baseline.structureHash === current.structureHash) {
43
+ const selfPainted = selfPaintingAt(current, ctx.unexplainedRegions) !== undefined;
44
+ return { classification: selfPainted ? 'unknown' : 'render-drift', styleTags: none };
45
+ }
46
+ const kinds = new Set(facts.map((f) => f.delta.kind));
47
+ if (kinds.has('removed'))
48
+ return { classification: 'element-removed', styleTags: none };
49
+ if (kinds.has('added'))
50
+ return { classification: 'element-added', styleTags: none };
51
+ const primary = ['text', 'style', 'attr'].filter((k) => kinds.has(k));
52
+ const styleTags = kinds.has('style')
53
+ ? TAG_ORDER.filter((t) => facts.some((f) => f.delta.kind === 'style' && styleTagOf(f.delta.prop) === t))
54
+ : none;
55
+ if (primary.length > 1)
56
+ return { classification: 'mixed', styleTags };
57
+ if (primary[0] === 'text')
58
+ return { classification: 'text-only', styleTags: none };
59
+ if (primary[0] === 'style')
60
+ return { classification: 'style-only', styleTags };
61
+ if (primary[0] === 'attr')
62
+ return { classification: 'attr-only', styleTags: none };
63
+ if (kinds.has('shift'))
64
+ return { classification: 'reflow', styleTags: none };
65
+ if (kinds.has('moved') || kinds.has('resized'))
66
+ return { classification: 'geometry', styleTags: none };
67
+ // Bytes differ, nothing marked, nothing structural: antialiasing-scale drift.
68
+ if (pixels.subThreshold)
69
+ return { classification: 'render-drift', styleTags: none };
70
+ return { classification: 'unknown', styleTags: none };
71
+ }
72
+ /** The first self-painting element (canvas, video, img…) on the current side
73
+ * under an unexplained region: its content changes without the DOM or styles
74
+ * changing, so the pixels there are content, not drift. */
75
+ export function selfPaintingAt(current, unexplained) {
76
+ for (const r of unexplained) {
77
+ const e = current.elements.find((e) => SELF_PAINTING.has(e.tag) && intersects(r, e.rect));
78
+ if (e)
79
+ return e;
80
+ }
81
+ return undefined;
82
+ }
83
+ const LARGE_SHIFT_COUNT = 10;
84
+ const LARGE_SHIFT_PX = 50;
85
+ /** In a fixed order, so the flag list serializes identically every run. */
86
+ export function computeFlags(ctx) {
87
+ const flags = [];
88
+ const { baseline, current, facts } = ctx;
89
+ const has = (k) => facts.some((f) => f.delta.kind === k);
90
+ if (has('removed'))
91
+ flags.push('element-removed');
92
+ if (has('text'))
93
+ flags.push('text-changed');
94
+ if (facts.some((f) => f.delta.kind === 'shift' &&
95
+ (f.delta.count >= LARGE_SHIFT_COUNT || Math.abs(f.delta.dy) >= LARGE_SHIFT_PX))) {
96
+ flags.push('large-reflow');
97
+ }
98
+ if (ctx.explained !== 'full')
99
+ flags.push('unexplained-pixels');
100
+ if (ctx.pixels.subThreshold)
101
+ flags.push('sub-threshold');
102
+ if (baseline?.truncated || current?.truncated)
103
+ flags.push('structure-truncated');
104
+ if (!baseline)
105
+ flags.push('no-baseline-structure');
106
+ if (!current)
107
+ flags.push('no-current-structure');
108
+ const belowFold = (s) => s !== undefined && s.contentHeight > s.viewport.height;
109
+ if (belowFold(baseline) || belowFold(current))
110
+ flags.push('below-fold');
111
+ return flags;
112
+ }
113
+ //# sourceMappingURL=classify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"classify.js","sourceRoot":"","sources":["../../../src/diff/structural/classify.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,0EAA0E;AAC1E,6EAA6E;AAC7E,2BAA2B;AAY3B,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAmB3C,MAAM,SAAS,GAAe,CAAC,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC;AAEtF,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,IAAI,0BAA0B,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,SAAS,CAAC;IAC5D,IAAI,4BAA4B,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC;QAC3E,OAAO,QAAQ,CAAC;IAClB,IAAI,oDAAoD,CAAC,IAAI,CAAC,IAAI,CAAC;QACjE,OAAO,YAAY,CAAC;IACtB,IAAI,uDAAuD,CAAC,IAAI,CAAC,IAAI,CAAC;QACpE,OAAO,SAAS,CAAC;IACnB,OAAO,OAAO,CAAC,CAAC,oCAAoC;AACtD,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,GAAoB;IAI3C,MAAM,IAAI,GAAe,EAAE,CAAC;IAC5B,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,GAAG,CAAC;IACjD,uEAAuE;IACvE,uEAAuE;IACvE,IACE,MAAM,CAAC,eAAe;QACtB,CAAC,MAAM,CAAC,iBAAiB;QACzB,CAAC,MAAM,CAAC,UAAU;QAClB,KAAK,CAAC,MAAM,KAAK,CAAC;QAElB,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;IAC1D,yEAAyE;IACzE,sCAAsC;IACtC,IAAI,MAAM,CAAC,iBAAiB;QAC1B,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;IACjE,IAAI,CAAC,QAAQ,IAAI,CAAC,OAAO;QAAE,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;IACjF,IACE,QAAQ,CAAC,QAAQ,CAAC,KAAK,KAAK,OAAO,CAAC,QAAQ,CAAC,KAAK;QAClD,QAAQ,CAAC,QAAQ,CAAC,MAAM,KAAK,OAAO,CAAC,QAAQ,CAAC,MAAM,EACpD,CAAC;QACD,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;IACjE,CAAC;IACD,uEAAuE;IACvE,oEAAoE;IACpE,wEAAwE;IACxE,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,QAAQ,CAAC,aAAa,KAAK,OAAO,CAAC,aAAa,EAAE,CAAC;QAC3E,MAAM,WAAW,GAAG,cAAc,CAAC,OAAO,EAAE,GAAG,CAAC,kBAAkB,CAAC,KAAK,SAAS,CAAC;QAClF,OAAO,EAAE,cAAc,EAAE,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,cAAc,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;IACvF,CAAC;IAED,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;IACtD,IAAI,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC;QAAE,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;IACxF,IAAI,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC;QAAE,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;IAEpF,MAAM,OAAO,GAAI,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,CAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACjF,MAAM,SAAS,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC;QAClC,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CACrB,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,KAAK,OAAO,IAAI,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAC9E;QACH,CAAC,CAAC,IAAI,CAAC;IACT,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC;IACtE,IAAI,OAAO,CAAC,CAAC,CAAC,KAAK,MAAM;QAAE,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;IACnF,IAAI,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO;QAAE,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,SAAS,EAAE,CAAC;IAC/E,IAAI,OAAO,CAAC,CAAC,CAAC,KAAK,MAAM;QAAE,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;IACnF,IAAI,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC;QAAE,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;IAC7E,IAAI,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC;QAC5C,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;IACzD,8EAA8E;IAC9E,IAAI,MAAM,CAAC,YAAY;QAAE,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;IACpF,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;AACxD,CAAC;AAED;;4DAE4D;AAC5D,MAAM,UAAU,cAAc,CAC5B,OAA0B,EAC1B,WAA4B;IAE5B,KAAK,MAAM,CAAC,IAAI,WAAW,EAAE,CAAC;QAC5B,MAAM,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC,IAAI,CAC7B,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,CACzD,CAAC;QACF,IAAI,CAAC;YAAE,OAAO,CAAC,CAAC;IAClB,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAC7B,MAAM,cAAc,GAAG,EAAE,CAAC;AAE1B,2EAA2E;AAC3E,MAAM,UAAU,YAAY,CAAC,GAAoB;IAC/C,MAAM,KAAK,GAAW,EAAE,CAAC;IACzB,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,GAAG,CAAC;IACzC,MAAM,GAAG,GAAG,CAAC,CAAgB,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC;IACxE,IAAI,GAAG,CAAC,SAAS,CAAC;QAAE,KAAK,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;IAClD,IAAI,GAAG,CAAC,MAAM,CAAC;QAAE,KAAK,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IAC5C,IACE,KAAK,CAAC,IAAI,CACR,CAAC,CAAC,EAAE,EAAE,CACJ,CAAC,CAAC,KAAK,CAAC,IAAI,KAAK,OAAO;QACxB,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,IAAI,iBAAiB,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,cAAc,CAAC,CACjF,EACD,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IAC7B,CAAC;IACD,IAAI,GAAG,CAAC,SAAS,KAAK,MAAM;QAAE,KAAK,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;IAC/D,IAAI,GAAG,CAAC,MAAM,CAAC,YAAY;QAAE,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;IACzD,IAAI,QAAQ,EAAE,SAAS,IAAI,OAAO,EAAE,SAAS;QAAE,KAAK,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;IACjF,IAAI,CAAC,QAAQ;QAAE,KAAK,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;IACnD,IAAI,CAAC,OAAO;QAAE,KAAK,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC;IACjD,MAAM,SAAS,GAAG,CAAC,CAAgC,EAAE,EAAE,CACrD,CAAC,KAAK,SAAS,IAAI,CAAC,CAAC,aAAa,GAAG,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC;IACzD,IAAI,SAAS,CAAC,QAAQ,CAAC,IAAI,SAAS,CAAC,OAAO,CAAC;QAAE,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IACxE,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -0,0 +1,11 @@
1
+ import type { ChangedRegion, StructureRect } from '../../types.js';
2
+ import type { Fact } from './deltas.js';
3
+ export interface Coverage {
4
+ /** Changed regions with area; zero-area boxes carry no pixels. */
5
+ regionCount: number;
6
+ coverage: number;
7
+ explained: 'full' | 'partial' | 'none';
8
+ unexplainedRegions: ChangedRegion[];
9
+ }
10
+ export declare function intersects(r: ChangedRegion, rect: StructureRect): boolean;
11
+ export declare function computeCoverage(regions: ChangedRegion[], facts: Fact[]): Coverage;
@@ -0,0 +1,37 @@
1
+ // Does the structure explain the pixels? A changed region is explained when
2
+ // it intersects the old or new rect of some delta's element. Coverage is by
3
+ // region area: the diff carries bounding boxes, not per-pixel membership, and
4
+ // boxes are what an agent reads back.
5
+ export function intersects(r, rect) {
6
+ return (r.x < rect.x + rect.w &&
7
+ r.x + r.width > rect.x &&
8
+ r.y < rect.y + rect.h &&
9
+ r.y + r.height > rect.y);
10
+ }
11
+ export function computeCoverage(regions, facts) {
12
+ // No marked regions: nothing is left unexplained. That includes a change
13
+ // below the pixel diff's tolerance, where the deltas are the whole story.
14
+ const real = regions.filter((r) => r.width > 0 && r.height > 0);
15
+ if (real.length === 0)
16
+ return { regionCount: 0, coverage: 1, explained: 'full', unexplainedRegions: [] };
17
+ const rects = facts.flatMap((f) => f.rects);
18
+ let total = 0;
19
+ let explained = 0;
20
+ const unexplainedRegions = [];
21
+ for (const r of real) {
22
+ const area = r.width * r.height;
23
+ total += area;
24
+ if (rects.some((rect) => intersects(r, rect)))
25
+ explained += area;
26
+ else
27
+ unexplainedRegions.push(r);
28
+ }
29
+ const coverage = total === 0 ? 1 : Math.round((explained / total) * 1000) / 1000;
30
+ return {
31
+ regionCount: real.length,
32
+ coverage,
33
+ explained: coverage >= 0.98 ? 'full' : coverage >= 0.5 ? 'partial' : 'none',
34
+ unexplainedRegions,
35
+ };
36
+ }
37
+ //# sourceMappingURL=coverage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coverage.js","sourceRoot":"","sources":["../../../src/diff/structural/coverage.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,4EAA4E;AAC5E,8EAA8E;AAC9E,sCAAsC;AAatC,MAAM,UAAU,UAAU,CAAC,CAAgB,EAAE,IAAmB;IAC9D,OAAO,CACL,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;QACrB,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;QACtB,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;QACrB,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC,CACxB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,OAAwB,EAAE,KAAa;IACrE,yEAAyE;IACzE,0EAA0E;IAC1E,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAChE,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QACnB,OAAO,EAAE,WAAW,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,kBAAkB,EAAE,EAAE,EAAE,CAAC;IACpF,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IAC5C,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,MAAM,kBAAkB,GAAoB,EAAE,CAAC;IAC/C,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,MAAM,CAAC;QAChC,KAAK,IAAI,IAAI,CAAC;QACd,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;YAAE,SAAS,IAAI,IAAI,CAAC;;YAC5D,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClC,CAAC;IACD,MAAM,QAAQ,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,SAAS,GAAG,KAAK,CAAC,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;IACjF,OAAO;QACL,WAAW,EAAE,IAAI,CAAC,MAAM;QACxB,QAAQ;QACR,SAAS,EAAE,QAAQ,IAAI,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,IAAI,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM;QAC3E,kBAAkB;KACnB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,29 @@
1
+ import type { Delta, StructureRect, StructureSnapshot } from '../../types.js';
2
+ import type { ElementMatch } from './match.js';
3
+ export interface Fact {
4
+ delta: Delta;
5
+ /** Old and new rects of the element(s) this fact is about, for coverage. */
6
+ rects: StructureRect[];
7
+ /** `role|tag` of the element, for the signature. */
8
+ abstract: string;
9
+ /** Position in the current document; for `removed`, that of the preceding
10
+ * matched element. Keeps output order stable across runs. */
11
+ order: number;
12
+ /** The element this fact is about, as an index into one side's snapshot:
13
+ * the baseline for matched and removed elements (their label comes from
14
+ * it), the current side for added ones; a shift carries its anchor's.
15
+ * Labels are not identities — two `div.card`s are two elements — so
16
+ * anything that groups facts by element keys on this. */
17
+ source: Source;
18
+ }
19
+ export interface Source {
20
+ side: 'baseline' | 'current';
21
+ index: number;
22
+ }
23
+ /** Within one position, which fact reads first. */
24
+ export declare const KIND_RANK: Record<Delta['kind'], number>;
25
+ export declare function computeFacts(a: StructureSnapshot, b: StructureSnapshot, match: ElementMatch): Fact[];
26
+ export declare function sortFacts(facts: Fact[]): Fact[];
27
+ /** Byte-identical output across machines needs a locale-free order;
28
+ * `localeCompare` is not one. */
29
+ export declare const compareCodeUnits: (a: string, b: string) => number;
@@ -0,0 +1,218 @@
1
+ // One typed fact per changed thing between two matched snapshots.
2
+ //
3
+ // Style deltas are reported where they originate. Compact storage (see
4
+ // capture/structure.ts) stores an inherited property only where it differs
5
+ // from the parent, but a change on `main` still changes every descendant's
6
+ // effective colour — reporting it 40 times would bury the one fact that
7
+ // matters. So an inherited delta is emitted only where the element's own
8
+ // stored value changed, or where the parents' effective values already agree.
9
+ import { INHERITED_PROPS, STRUCTURE_PROPS, effectiveStyle, round, } from '../../capture/structure.js';
10
+ import { abstractLabel, clip, label } from './label.js';
11
+ /** Within one position, which fact reads first. */
12
+ export const KIND_RANK = {
13
+ removed: 0,
14
+ added: 1,
15
+ text: 2,
16
+ attr: 3,
17
+ style: 4,
18
+ resized: 5,
19
+ moved: 6,
20
+ shift: 7,
21
+ };
22
+ const ATTRS = ['src', 'role', 'name'];
23
+ /** Longest `from`/`to` in a delta. A paragraph of copy or a data-URI src
24
+ * would otherwise make one delta larger than the whole tier-0 budget; 200
25
+ * keeps any realistic value (a font stack, a shadow, a URL) whole. With
26
+ * labels capped at MAX_LABEL, every delta has a fixed worst-case size. */
27
+ const MAX_VALUE = 200;
28
+ const BORDER_COLOR = /^border-(top|right|bottom|left)-color$/;
29
+ export function computeFacts(a, b, match) {
30
+ const facts = [];
31
+ const aToB = new Map(match.pairs);
32
+ /** Baseline index → its facts; and children whose only change was
33
+ * following, so their rects can ride on an ancestor's fact. */
34
+ const byElement = new Map();
35
+ const followers = [];
36
+ for (const [i, j] of match.pairs) {
37
+ const ea = a.elements[i];
38
+ const eb = b.elements[j];
39
+ const fact = (delta) => {
40
+ const f = {
41
+ delta,
42
+ rects: [ea.rect, eb.rect],
43
+ abstract: abstractLabel(eb),
44
+ order: j,
45
+ source: { side: 'baseline', index: i },
46
+ };
47
+ byElement.set(i, [...(byElement.get(i) ?? []), f]);
48
+ return f;
49
+ };
50
+ // Every fact about a matched element carries ONE label, the baseline
51
+ // side's. A button's label is its name, and its name is usually its text,
52
+ // so the current side would read `button[name="Save changes"] text
53
+ // "Save"→"Save changes"`, and a text delta labelled from one side beside
54
+ // a resize labelled from the other reads as two buttons.
55
+ const el = label(ea);
56
+ // Masked stand-ins carry no content by construction; anything that leaks
57
+ // onto one must not surface as a delta either.
58
+ if (ea.tag !== 'masked' && eb.tag !== 'masked') {
59
+ const ta = ea.text ?? '';
60
+ const tb = eb.text ?? '';
61
+ if (ta !== tb)
62
+ facts.push(fact({ kind: 'text', el, from: clip(ta, MAX_VALUE), to: clip(tb, MAX_VALUE) }));
63
+ for (const attr of ATTRS) {
64
+ const from = ea[attr] ?? '';
65
+ const to = eb[attr] ?? '';
66
+ if (from === to)
67
+ continue;
68
+ // A name that is just the text restates the text delta.
69
+ if (attr === 'name' && from === ta && to === tb)
70
+ continue;
71
+ facts.push(fact({
72
+ kind: 'attr',
73
+ el,
74
+ attr,
75
+ from: clip(from, MAX_VALUE),
76
+ to: clip(to, MAX_VALUE),
77
+ }));
78
+ }
79
+ for (const prop of STRUCTURE_PROPS) {
80
+ // An unstored border colour resolves to currentColor, so a plain
81
+ // `color` change would also report four phantom border-colour deltas
82
+ // on every borderless element. No border, no border-colour fact.
83
+ if (BORDER_COLOR.test(prop) && borderless(a, i, prop) && borderless(b, j, prop))
84
+ continue;
85
+ const from = effectiveStyle(a, i, prop);
86
+ const to = effectiveStyle(b, j, prop);
87
+ if (from === to)
88
+ continue;
89
+ if (INHERITED_PROPS.has(prop) && !originatesHere(a, b, i, j, aToB, prop))
90
+ continue;
91
+ facts.push(fact({
92
+ kind: 'style',
93
+ el,
94
+ prop,
95
+ from: clip(from, MAX_VALUE),
96
+ to: clip(to, MAX_VALUE),
97
+ }));
98
+ }
99
+ }
100
+ const dw = round(eb.rect.w - ea.rect.w);
101
+ const dh = round(eb.rect.h - ea.rect.h);
102
+ const dx = round(eb.rect.x - ea.rect.x);
103
+ const dy = round(eb.rect.y - ea.rect.y);
104
+ // A child whose box changed exactly as its parent's did only followed it.
105
+ // On the 12-item reorder probe the item's `span.meta moved down 222px`
106
+ // read as a second change beside the item's own move, and in the corpus
107
+ // `nested-reorder` case the five spans doubled the shift count (6 for 3).
108
+ // The ancestor that actually moved keeps its fact. Its rect usually
109
+ // contains the child's, but an overflowing child's does not, so the
110
+ // child's rects are handed to that fact below and coverage loses nothing.
111
+ const p = parentChange(a, b, i, j, aToB);
112
+ const resized = dw !== 0 || dh !== 0;
113
+ const moved = dx !== 0 || dy !== 0;
114
+ const followedSize = resized && p !== undefined && p.dw === dw && p.dh === dh;
115
+ const followedPos = moved && p !== undefined && p.dx === dx && p.dy === dy;
116
+ if (resized && !followedSize)
117
+ facts.push(fact({ kind: 'resized', el, dw, dh }));
118
+ if (moved && !followedPos)
119
+ facts.push(fact({ kind: 'moved', el, dx, dy }));
120
+ if ((followedSize || followedPos) && !byElement.has(i))
121
+ followers.push({ i, rects: [ea.rect, eb.rect] });
122
+ }
123
+ // Nearest baseline ancestor with a geometry fact: the move or resize the
124
+ // child followed. Walked on the baseline tree, the side `source` indexes.
125
+ for (const { i, rects } of followers) {
126
+ for (let k = a.elements[i].parent; k !== null; k = a.elements[k].parent) {
127
+ const own = byElement
128
+ .get(k)
129
+ ?.find((f) => f.delta.kind === 'moved' || f.delta.kind === 'resized');
130
+ if (own) {
131
+ own.rects.push(...rects);
132
+ break;
133
+ }
134
+ }
135
+ }
136
+ for (const j of match.added) {
137
+ const e = b.elements[j];
138
+ facts.push({
139
+ delta: { kind: 'added', el: label(e), rect: e.rect },
140
+ rects: [e.rect],
141
+ abstract: abstractLabel(e),
142
+ order: j,
143
+ source: { side: 'current', index: j },
144
+ });
145
+ }
146
+ // A removed element has no current position; order it after the last
147
+ // matched element that precedes it on the baseline side.
148
+ const lastMatchedBefore = (i) => {
149
+ for (let k = i - 1; k >= 0; k--) {
150
+ const j = aToB.get(k);
151
+ if (j !== undefined)
152
+ return j + 0.5;
153
+ }
154
+ return -0.5;
155
+ };
156
+ for (const i of match.removed) {
157
+ const e = a.elements[i];
158
+ facts.push({
159
+ delta: { kind: 'removed', el: label(e), rect: e.rect },
160
+ rects: [e.rect],
161
+ abstract: abstractLabel(e),
162
+ order: lastMatchedBefore(i),
163
+ source: { side: 'baseline', index: i },
164
+ });
165
+ }
166
+ return sortFacts(facts);
167
+ }
168
+ /** That side of `border-<side>-color` has zero width on this element. */
169
+ function borderless(s, i, colorProp) {
170
+ return effectiveStyle(s, i, colorProp.replace(/-color$/, '-width')) === '0px';
171
+ }
172
+ /** An inherited property changed on this element, not merely on an ancestor:
173
+ * either it stores its own value on a side, or its parents' effective values
174
+ * agree (so the difference starts here). */
175
+ function originatesHere(a, b, i, j, aToB, prop) {
176
+ const ea = a.elements[i];
177
+ const eb = b.elements[j];
178
+ if (ea.styles[prop] !== undefined || eb.styles[prop] !== undefined)
179
+ return true;
180
+ const pa = ea.parent;
181
+ const pb = eb.parent;
182
+ if (pa === null || pb === null)
183
+ return true;
184
+ // Parents unmatched: nothing above to blame, so report here.
185
+ if (aToB.get(pa) !== pb)
186
+ return true;
187
+ return effectiveStyle(a, pa, prop) === effectiveStyle(b, pb, prop);
188
+ }
189
+ /** How the matched parent's box changed; undefined when the parent is the
190
+ * root or unmatched. Read off the rects, not off the facts, so a grandchild
191
+ * still reads as following when its parent's own move was itself
192
+ * suppressed. */
193
+ function parentChange(a, b, i, j, aToB) {
194
+ const pa = a.elements[i].parent;
195
+ const pb = b.elements[j].parent;
196
+ if (pa === null || pb === null || aToB.get(pa) !== pb)
197
+ return undefined;
198
+ const ra = a.elements[pa].rect;
199
+ const rb = b.elements[pb].rect;
200
+ return {
201
+ dx: round(rb.x - ra.x),
202
+ dy: round(rb.y - ra.y),
203
+ dw: round(rb.w - ra.w),
204
+ dh: round(rb.h - ra.h),
205
+ };
206
+ }
207
+ export function sortFacts(facts) {
208
+ return facts.sort((x, y) => x.order - y.order ||
209
+ KIND_RANK[x.delta.kind] - KIND_RANK[y.delta.kind] ||
210
+ compareCodeUnits(factKey(x.delta), factKey(y.delta)));
211
+ }
212
+ function factKey(d) {
213
+ return d.kind === 'style' ? d.prop : d.kind === 'attr' ? d.attr : '';
214
+ }
215
+ /** Byte-identical output across machines needs a locale-free order;
216
+ * `localeCompare` is not one. */
217
+ export const compareCodeUnits = (a, b) => a < b ? -1 : a > b ? 1 : 0;
218
+ //# sourceMappingURL=deltas.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deltas.js","sourceRoot":"","sources":["../../../src/diff/structural/deltas.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { StructureSnapshot } from '../../types.js';
2
+ import type { Fact } from './deltas.js';
3
+ export declare function disambiguate(facts: Fact[], baseline: StructureSnapshot, current: StructureSnapshot): Fact[];