@juxtapose/core 0.2.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.
- package/dist/agent/handoff.d.ts +8 -1
- package/dist/agent/handoff.js +75 -7
- package/dist/agent/handoff.js.map +1 -1
- package/dist/analysis/affected.d.ts +58 -0
- package/dist/analysis/affected.js +134 -22
- package/dist/analysis/affected.js.map +1 -1
- package/dist/analysis/cause.d.ts +32 -0
- package/dist/analysis/cause.js +187 -0
- package/dist/analysis/cause.js.map +1 -0
- package/dist/baseline/approve.d.ts +24 -0
- package/dist/baseline/approve.js +54 -0
- package/dist/baseline/approve.js.map +1 -0
- package/dist/baseline/git-store.d.ts +4 -0
- package/dist/baseline/git-store.js +22 -0
- package/dist/baseline/git-store.js.map +1 -1
- package/dist/browser/acquire.d.ts +11 -0
- package/dist/browser/acquire.js +15 -0
- package/dist/browser/acquire.js.map +1 -0
- package/dist/browser/browser.d.ts +53 -0
- package/dist/browser/browser.js +122 -0
- package/dist/browser/browser.js.map +1 -0
- package/dist/browser/cdp.d.ts +81 -0
- package/dist/browser/cdp.js +230 -0
- package/dist/browser/cdp.js.map +1 -0
- package/dist/browser/index.d.ts +9 -0
- package/dist/browser/index.js +10 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/browser/install.d.ts +33 -0
- package/dist/browser/install.js +167 -0
- package/dist/browser/install.js.map +1 -0
- package/dist/browser/launch.d.ts +37 -0
- package/dist/browser/launch.js +265 -0
- package/dist/browser/launch.js.map +1 -0
- package/dist/browser/page.d.ts +125 -0
- package/dist/browser/page.js +564 -0
- package/dist/browser/page.js.map +1 -0
- package/dist/browser/pins.d.ts +12 -0
- package/dist/browser/pins.js +57 -0
- package/dist/browser/pins.js.map +1 -0
- package/dist/browser/resolve.d.ts +25 -0
- package/dist/browser/resolve.js +115 -0
- package/dist/browser/resolve.js.map +1 -0
- package/dist/browser/selectors.d.ts +10 -0
- package/dist/browser/selectors.js +60 -0
- package/dist/browser/selectors.js.map +1 -0
- package/dist/browser/transport.d.ts +39 -0
- package/dist/browser/transport.js +99 -0
- package/dist/browser/transport.js.map +1 -0
- package/dist/browser/zip.d.ts +27 -0
- package/dist/browser/zip.js +241 -0
- package/dist/browser/zip.js.map +1 -0
- package/dist/capture/capture.d.ts +7 -1
- package/dist/capture/capture.js +27 -3
- package/dist/capture/capture.js.map +1 -1
- package/dist/capture/env.d.ts +8 -0
- package/dist/capture/env.js +38 -0
- package/dist/capture/env.js.map +1 -0
- package/dist/capture/launch.d.ts +19 -1
- package/dist/capture/launch.js +91 -1
- package/dist/capture/launch.js.map +1 -1
- package/dist/capture/pool.d.ts +1 -1
- package/dist/capture/pool.js +41 -8
- package/dist/capture/pool.js.map +1 -1
- package/dist/capture/settle.d.ts +1 -1
- package/dist/capture/settle.js +2 -0
- package/dist/capture/settle.js.map +1 -1
- package/dist/capture/snap.d.ts +43 -0
- package/dist/capture/snap.js +103 -0
- package/dist/capture/snap.js.map +1 -0
- package/dist/capture/structure.d.ts +77 -0
- package/dist/capture/structure.js +498 -0
- package/dist/capture/structure.js.map +1 -0
- package/dist/diff/compare.d.ts +19 -0
- package/dist/diff/compare.js +19 -0
- package/dist/diff/compare.js.map +1 -0
- package/dist/diff/crops.d.ts +26 -0
- package/dist/diff/crops.js +92 -0
- package/dist/diff/crops.js.map +1 -0
- package/dist/diff/structural/classify.d.ts +29 -0
- package/dist/diff/structural/classify.js +113 -0
- package/dist/diff/structural/classify.js.map +1 -0
- package/dist/diff/structural/coverage.d.ts +11 -0
- package/dist/diff/structural/coverage.js +37 -0
- package/dist/diff/structural/coverage.js.map +1 -0
- package/dist/diff/structural/deltas.d.ts +29 -0
- package/dist/diff/structural/deltas.js +218 -0
- package/dist/diff/structural/deltas.js.map +1 -0
- package/dist/diff/structural/disambiguate.d.ts +3 -0
- package/dist/diff/structural/disambiguate.js +89 -0
- package/dist/diff/structural/disambiguate.js.map +1 -0
- package/dist/diff/structural/label.d.ts +15 -0
- package/dist/diff/structural/label.js +64 -0
- package/dist/diff/structural/label.js.map +1 -0
- package/dist/diff/structural/match.d.ts +13 -0
- package/dist/diff/structural/match.js +135 -0
- package/dist/diff/structural/match.js.map +1 -0
- package/dist/diff/structural/shift.d.ts +2 -0
- package/dist/diff/structural/shift.js +144 -0
- package/dist/diff/structural/shift.js.map +1 -0
- package/dist/diff/structural/summary.d.ts +31 -0
- package/dist/diff/structural/summary.js +420 -0
- package/dist/diff/structural/summary.js.map +1 -0
- package/dist/diff/structural.d.ts +40 -0
- package/dist/diff/structural.js +219 -0
- package/dist/diff/structural.js.map +1 -0
- package/dist/index.d.ts +34 -12
- package/dist/index.js +55 -11
- package/dist/index.js.map +1 -1
- package/dist/platform.d.ts +1 -1
- package/dist/platforms/urls.d.ts +3 -0
- package/dist/platforms/urls.js +89 -0
- package/dist/platforms/urls.js.map +1 -0
- package/dist/report/comment.d.ts +2 -1
- package/dist/report/comment.js +305 -57
- package/dist/report/comment.js.map +1 -1
- package/dist/report/format.d.ts +20 -0
- package/dist/report/format.js +32 -0
- package/dist/report/format.js.map +1 -0
- package/dist/report/github.d.ts +12 -0
- package/dist/report/github.js +21 -0
- package/dist/report/github.js.map +1 -1
- package/dist/report/html.d.ts +12 -0
- package/dist/report/html.js +202 -0
- package/dist/report/html.js.map +1 -0
- package/dist/run/groups.d.ts +2 -0
- package/dist/run/groups.js +45 -0
- package/dist/run/groups.js.map +1 -0
- package/dist/run/phases.d.ts +19 -0
- package/dist/run/phases.js +50 -0
- package/dist/run/phases.js.map +1 -0
- package/dist/run/run.d.ts +10 -2
- package/dist/run/run.js +97 -9
- package/dist/run/run.js.map +1 -1
- package/dist/shell-quote.d.ts +2 -0
- package/dist/shell-quote.js +10 -0
- package/dist/shell-quote.js.map +1 -0
- package/dist/types.d.ts +234 -3
- package/dist/url-platform.d.ts +2 -2
- package/dist/url-platform.js +2 -2
- package/dist/url-platform.js.map +1 -1
- package/package.json +14 -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":"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"}
|
|
@@ -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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"}
|