@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,89 @@
|
|
|
1
|
+
// A label is a description, not an identity: a page with two cards has two
|
|
2
|
+
// `div.card`s, and when both change, every reader of the verdict — the JSON,
|
|
3
|
+
// the summary, `describeDeltas` — sees one element. Measured on a dark-mode
|
|
4
|
+
// probe: the two cards' facts merged into one clause that stated
|
|
5
|
+
// `background-color` twice; on a 12-item reorder, seven `span.meta` moves
|
|
6
|
+
// read as one line and six deltas vanished from the human output.
|
|
7
|
+
//
|
|
8
|
+
// So within one verdict, distinct elements get distinct labels. Only labels
|
|
9
|
+
// that actually collide change, and they take the shortest stable
|
|
10
|
+
// distinguisher: the nearest ancestor with a name, id or test id
|
|
11
|
+
// (`span.meta in listitem[name="Item 4"]`), or, when that does not separate
|
|
12
|
+
// them, the element's ordinal among same-labelled elements on its side of the
|
|
13
|
+
// diff (`div.card (2nd)`). Both are properties of the page, not of the diff,
|
|
14
|
+
// so the same change labels the same way on every run.
|
|
15
|
+
import { MAX_LABEL, clip, label } from './label.js';
|
|
16
|
+
export function disambiguate(facts, baseline, current) {
|
|
17
|
+
const snap = (s) => (s.side === 'baseline' ? baseline : current);
|
|
18
|
+
const nth = {
|
|
19
|
+
baseline: undefined,
|
|
20
|
+
current: undefined,
|
|
21
|
+
};
|
|
22
|
+
const nthOf = (s) => (nth[s.side] ??= ordinals(snap(s)))[s.index];
|
|
23
|
+
const key = (s) => `${s.side}:${s.index}`;
|
|
24
|
+
const labelOf = (f) => ('el' in f.delta ? f.delta.el : f.delta.after);
|
|
25
|
+
// label → the distinct elements wearing it, in first-seen (fact) order
|
|
26
|
+
const byLabel = new Map();
|
|
27
|
+
for (const f of facts) {
|
|
28
|
+
const l = labelOf(f);
|
|
29
|
+
let ids = byLabel.get(l);
|
|
30
|
+
if (!ids)
|
|
31
|
+
byLabel.set(l, (ids = new Map()));
|
|
32
|
+
ids.set(key(f.source), f.source);
|
|
33
|
+
}
|
|
34
|
+
const rename = new Map();
|
|
35
|
+
for (const [l, ids] of byLabel) {
|
|
36
|
+
if (ids.size < 2)
|
|
37
|
+
continue;
|
|
38
|
+
const sources = [...ids.values()];
|
|
39
|
+
const scoped = sources.map((s) => {
|
|
40
|
+
const a = labelledAncestor(snap(s), s.index);
|
|
41
|
+
return a === undefined ? undefined : clip(`${l} in ${a}`, MAX_LABEL);
|
|
42
|
+
});
|
|
43
|
+
const distinct = scoped.every((x) => x !== undefined) && new Set(scoped).size === scoped.length;
|
|
44
|
+
for (const [k, s] of sources.entries()) {
|
|
45
|
+
const suffix = ` (${ordinal(nthOf(s))})`;
|
|
46
|
+
rename.set(key(s), distinct ? scoped[k] : clip(l, MAX_LABEL - suffix.length) + suffix);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (rename.size === 0)
|
|
50
|
+
return facts;
|
|
51
|
+
return facts.map((f) => {
|
|
52
|
+
const to = rename.get(key(f.source));
|
|
53
|
+
if (to === undefined)
|
|
54
|
+
return f;
|
|
55
|
+
const d = f.delta;
|
|
56
|
+
return { ...f, delta: 'el' in d ? { ...d, el: to } : { ...d, after: to } };
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
/** Label of the nearest ancestor that names itself (role+name, id, test id);
|
|
60
|
+
* a class or a path does not tell two siblings' parents apart. */
|
|
61
|
+
function labelledAncestor(s, index) {
|
|
62
|
+
for (let p = s.elements[index].parent; p !== null; p = s.elements[p].parent) {
|
|
63
|
+
const e = s.elements[p];
|
|
64
|
+
if (e.tag === 'masked')
|
|
65
|
+
continue;
|
|
66
|
+
if ((e.role && e.name) || e.id !== undefined || e.testId !== undefined)
|
|
67
|
+
return label(e);
|
|
68
|
+
}
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
/** Each element's 1-based position among its side's elements that carry the
|
|
72
|
+
* same label, in document order. One pass per side, not one per element: a
|
|
73
|
+
* list of 5,000 identical rows that all changed would otherwise be 25M
|
|
74
|
+
* label calls. */
|
|
75
|
+
function ordinals(s) {
|
|
76
|
+
const seen = new Map();
|
|
77
|
+
return s.elements.map((e) => {
|
|
78
|
+
const l = label(e);
|
|
79
|
+
const n = (seen.get(l) ?? 0) + 1;
|
|
80
|
+
seen.set(l, n);
|
|
81
|
+
return n;
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
function ordinal(n) {
|
|
85
|
+
const teen = n % 100 >= 11 && n % 100 <= 13;
|
|
86
|
+
const suffix = teen ? 'th' : (['th', 'st', 'nd', 'rd'][n % 10] ?? 'th');
|
|
87
|
+
return `${n}${suffix}`;
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=disambiguate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"disambiguate.js","sourceRoot":"","sources":["../../../src/diff/structural/disambiguate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { StructureElement } from '../../types.js';
|
|
2
|
+
/** Elements that paint their own pixels: a change inside them leaves the DOM
|
|
3
|
+
* and computed styles untouched, so unexplained pixels on one of these mean
|
|
4
|
+
* "content", not "drift". */
|
|
5
|
+
export declare const SELF_PAINTING: ReadonlySet<string>;
|
|
6
|
+
/** Longest label in a delta or summary. A deep path or a generated id or
|
|
7
|
+
* class must not be able to spend the verdict's 1,500-char budget. */
|
|
8
|
+
export declare const MAX_LABEL = 60;
|
|
9
|
+
export declare function label(el: StructureElement): string;
|
|
10
|
+
/** At most `max` UTF-16 units, ending in `…` when cut, never splitting a
|
|
11
|
+
* surrogate pair (half an emoji is invalid UTF-16 in the JSON). */
|
|
12
|
+
export declare function clip(s: string, max: number): string;
|
|
13
|
+
/** Identity with the page-specific part removed, so the same change on two
|
|
14
|
+
* pages hashes to the same signature. */
|
|
15
|
+
export declare function abstractLabel(el: StructureElement): string;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// Short human labels for elements in deltas and summaries. The budget is
|
|
2
|
+
// ~1,500 chars for a whole verdict, so a label is the shortest thing that
|
|
3
|
+
// still tells an agent which element to look at.
|
|
4
|
+
/** Elements that paint their own pixels: a change inside them leaves the DOM
|
|
5
|
+
* and computed styles untouched, so unexplained pixels on one of these mean
|
|
6
|
+
* "content", not "drift". */
|
|
7
|
+
export const SELF_PAINTING = new Set([
|
|
8
|
+
'canvas',
|
|
9
|
+
'video',
|
|
10
|
+
'img',
|
|
11
|
+
'iframe',
|
|
12
|
+
'object',
|
|
13
|
+
'embed',
|
|
14
|
+
]);
|
|
15
|
+
/** Longest label in a delta or summary. A deep path or a generated id or
|
|
16
|
+
* class must not be able to spend the verdict's 1,500-char budget. */
|
|
17
|
+
export const MAX_LABEL = 60;
|
|
18
|
+
export function label(el) {
|
|
19
|
+
if (el.tag === 'masked')
|
|
20
|
+
return 'masked';
|
|
21
|
+
if (el.role && el.name)
|
|
22
|
+
return clip(`${el.role}[name="${clip(el.name, 40)}"]`, MAX_LABEL);
|
|
23
|
+
if (el.id !== undefined)
|
|
24
|
+
return clip(`${el.tag}#${el.id}`, MAX_LABEL);
|
|
25
|
+
if (el.testId !== undefined)
|
|
26
|
+
return clip(`${el.tag}[data-testid="${el.testId}"]`, MAX_LABEL);
|
|
27
|
+
if (el.classes?.length)
|
|
28
|
+
return clip(`${el.tag}.${el.classes[0]}`, MAX_LABEL);
|
|
29
|
+
return clipPath(el.path
|
|
30
|
+
.replace(/^html:nth-of-type\(1\)>body:nth-of-type\(1\)>/, '')
|
|
31
|
+
.replace(/:nth-of-type\(1\)/g, ''));
|
|
32
|
+
}
|
|
33
|
+
/** A path keeps its tail — the element itself and its nearest ancestors —
|
|
34
|
+
* cut at a `>` boundary, so what remains is still a valid selector suffix. */
|
|
35
|
+
function clipPath(path) {
|
|
36
|
+
if (path.length <= MAX_LABEL)
|
|
37
|
+
return path;
|
|
38
|
+
const parts = path.split('>');
|
|
39
|
+
let out = parts[parts.length - 1];
|
|
40
|
+
for (let k = parts.length - 2; k >= 0; k--) {
|
|
41
|
+
const next = `${parts[k]}>${out}`;
|
|
42
|
+
if (next.length + 2 > MAX_LABEL)
|
|
43
|
+
break;
|
|
44
|
+
out = next;
|
|
45
|
+
}
|
|
46
|
+
return `…>${clip(out, MAX_LABEL - 2)}`;
|
|
47
|
+
}
|
|
48
|
+
/** At most `max` UTF-16 units, ending in `…` when cut, never splitting a
|
|
49
|
+
* surrogate pair (half an emoji is invalid UTF-16 in the JSON). */
|
|
50
|
+
export function clip(s, max) {
|
|
51
|
+
if (s.length <= max)
|
|
52
|
+
return s;
|
|
53
|
+
let end = max - 1;
|
|
54
|
+
const c = s.charCodeAt(end - 1);
|
|
55
|
+
if (c >= 0xd800 && c <= 0xdbff)
|
|
56
|
+
end--;
|
|
57
|
+
return s.slice(0, end) + '…';
|
|
58
|
+
}
|
|
59
|
+
/** Identity with the page-specific part removed, so the same change on two
|
|
60
|
+
* pages hashes to the same signature. */
|
|
61
|
+
export function abstractLabel(el) {
|
|
62
|
+
return el.role ?? el.tag;
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=label.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"label.js","sourceRoot":"","sources":["../../../src/diff/structural/label.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,0EAA0E;AAC1E,iDAAiD;AAIjD;;8BAE8B;AAC9B,MAAM,CAAC,MAAM,aAAa,GAAwB,IAAI,GAAG,CAAC;IACxD,QAAQ;IACR,OAAO;IACP,KAAK;IACL,QAAQ;IACR,QAAQ;IACR,OAAO;CACR,CAAC,CAAC;AAEH;uEACuE;AACvE,MAAM,CAAC,MAAM,SAAS,GAAG,EAAE,CAAC;AAE5B,MAAM,UAAU,KAAK,CAAC,EAAoB;IACxC,IAAI,EAAE,CAAC,GAAG,KAAK,QAAQ;QAAE,OAAO,QAAQ,CAAC;IACzC,IAAI,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,IAAI;QACpB,OAAO,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,UAAU,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;IACpE,IAAI,EAAE,CAAC,EAAE,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC,GAAG,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,SAAS,CAAC,CAAC;IACtE,IAAI,EAAE,CAAC,MAAM,KAAK,SAAS;QACzB,OAAO,IAAI,CAAC,GAAG,EAAE,CAAC,GAAG,iBAAiB,EAAE,CAAC,MAAM,IAAI,EAAE,SAAS,CAAC,CAAC;IAClE,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM;QAAE,OAAO,IAAI,CAAC,GAAG,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC;IAC7E,OAAO,QAAQ,CACb,EAAE,CAAC,IAAI;SACJ,OAAO,CAAC,+CAA+C,EAAE,EAAE,CAAC;SAC5D,OAAO,CAAC,oBAAoB,EAAE,EAAE,CAAC,CACrC,CAAC;AACJ,CAAC;AAED;+EAC+E;AAC/E,SAAS,QAAQ,CAAC,IAAY;IAC5B,IAAI,IAAI,CAAC,MAAM,IAAI,SAAS;QAAE,OAAO,IAAI,CAAC;IAC1C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC9B,IAAI,GAAG,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAClC,KAAK,IAAI,CAAC,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC3C,MAAM,IAAI,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,GAAG,EAAE,CAAC;QAClC,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,GAAG,SAAS;YAAE,MAAM;QACvC,GAAG,GAAG,IAAI,CAAC;IACb,CAAC;IACD,OAAO,KAAK,IAAI,CAAC,GAAG,EAAE,SAAS,GAAG,CAAC,CAAC,EAAE,CAAC;AACzC,CAAC;AAED;oEACoE;AACpE,MAAM,UAAU,IAAI,CAAC,CAAS,EAAE,GAAW;IACzC,IAAI,CAAC,CAAC,MAAM,IAAI,GAAG;QAAE,OAAO,CAAC,CAAC;IAC9B,IAAI,GAAG,GAAG,GAAG,GAAG,CAAC,CAAC;IAClB,MAAM,CAAC,GAAG,CAAC,CAAC,UAAU,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;IAChC,IAAI,CAAC,IAAI,MAAM,IAAI,CAAC,IAAI,MAAM;QAAE,GAAG,EAAE,CAAC;IACtC,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,GAAG,CAAC;AAC/B,CAAC;AAED;0CAC0C;AAC1C,MAAM,UAAU,aAAa,CAAC,EAAoB;IAChD,OAAO,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,GAAG,CAAC;AAC3B,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { StructureElement, StructureSnapshot } from '../../types.js';
|
|
2
|
+
export interface ElementMatch {
|
|
3
|
+
/** [baseline index, current index], sorted by current index. */
|
|
4
|
+
pairs: Array<[number, number]>;
|
|
5
|
+
/** Current indices of unmatched subtree roots, ascending. */
|
|
6
|
+
added: number[];
|
|
7
|
+
/** Baseline indices of unmatched subtree roots, ascending. */
|
|
8
|
+
removed: number[];
|
|
9
|
+
}
|
|
10
|
+
/** What makes an element "the same one" across captures: tag plus the most
|
|
11
|
+
* stable identity it carries. Masked stand-ins have only their tag. */
|
|
12
|
+
export declare function identityKey(el: StructureElement): string;
|
|
13
|
+
export declare function matchElements(a: StructureSnapshot, b: StructureSnapshot): ElementMatch;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
// Which element on the baseline side is which element on the current side.
|
|
2
|
+
//
|
|
3
|
+
// Positional paths alone would report every sibling after an insertion as
|
|
4
|
+
// changed. Instead children are aligned per parent with an LCS over an
|
|
5
|
+
// identity key, recursing into matches, so an insertion is one `added` and the
|
|
6
|
+
// rest stay matched. LCS is per parent, so cost is Σ(children²) — small for
|
|
7
|
+
// real DOMs; a pathological flat parent falls back to in-order key pairing.
|
|
8
|
+
/** Above this many DP cells for one parent, pair greedily instead. */
|
|
9
|
+
const LCS_CELL_CAP = 250_000;
|
|
10
|
+
/** What makes an element "the same one" across captures: tag plus the most
|
|
11
|
+
* stable identity it carries. Masked stand-ins have only their tag. */
|
|
12
|
+
export function identityKey(el) {
|
|
13
|
+
if (el.id !== undefined)
|
|
14
|
+
return `${el.tag}#${el.id}`;
|
|
15
|
+
if (el.testId !== undefined)
|
|
16
|
+
return `${el.tag}[${el.testId}]`;
|
|
17
|
+
if (el.role && el.name)
|
|
18
|
+
return `${el.tag}|${el.role}=${el.name}`;
|
|
19
|
+
if (el.classes?.length)
|
|
20
|
+
return `${el.tag}.${el.classes.join('.')}`;
|
|
21
|
+
return el.tag;
|
|
22
|
+
}
|
|
23
|
+
export function matchElements(a, b) {
|
|
24
|
+
const childrenA = childLists(a.elements);
|
|
25
|
+
const childrenB = childLists(b.elements);
|
|
26
|
+
const keyA = a.elements.map(identityKey);
|
|
27
|
+
const keyB = b.elements.map(identityKey);
|
|
28
|
+
const pairs = [];
|
|
29
|
+
const added = [];
|
|
30
|
+
const removed = [];
|
|
31
|
+
const walk = (pa, pb) => {
|
|
32
|
+
const ca = childrenA.get(pa) ?? [];
|
|
33
|
+
const cb = childrenB.get(pb) ?? [];
|
|
34
|
+
const first = align(ca, cb, (i, j) => keyA[i] === keyB[j]);
|
|
35
|
+
// A text edit changes a name-derived key, a toggled class changes a
|
|
36
|
+
// class-derived one. Pair what is left by tag, in order, so those read as
|
|
37
|
+
// `text`/`attr` deltas rather than removed + added.
|
|
38
|
+
// Never across conflicting explicit identities: `div#row-1` removed and
|
|
39
|
+
// `div#banner` added are two different elements, not one edited one.
|
|
40
|
+
const second = align(first.leftA, first.leftB, (i, j) => sameKind(a.elements[i], b.elements[j]));
|
|
41
|
+
for (const [i, j] of [...first.matched, ...second.matched]) {
|
|
42
|
+
pairs.push([i, j]);
|
|
43
|
+
walk(i, j);
|
|
44
|
+
}
|
|
45
|
+
removed.push(...second.leftA);
|
|
46
|
+
added.push(...second.leftB);
|
|
47
|
+
};
|
|
48
|
+
walk(-1, -1);
|
|
49
|
+
pairs.sort((p, q) => p[1] - q[1]);
|
|
50
|
+
added.sort((x, y) => x - y);
|
|
51
|
+
removed.sort((x, y) => x - y);
|
|
52
|
+
return { pairs, added, removed };
|
|
53
|
+
}
|
|
54
|
+
/** Tag equal, and no explicit id or testId present on both sides and unequal. */
|
|
55
|
+
function sameKind(x, y) {
|
|
56
|
+
if (x.tag !== y.tag)
|
|
57
|
+
return false;
|
|
58
|
+
if (x.id !== undefined && y.id !== undefined && x.id !== y.id)
|
|
59
|
+
return false;
|
|
60
|
+
if (x.testId !== undefined && y.testId !== undefined && x.testId !== y.testId)
|
|
61
|
+
return false;
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
/** Children in document order, keyed by parent index; -1 is the root. */
|
|
65
|
+
function childLists(elements) {
|
|
66
|
+
const m = new Map();
|
|
67
|
+
elements.forEach((e, i) => {
|
|
68
|
+
const p = e.parent ?? -1;
|
|
69
|
+
const list = m.get(p);
|
|
70
|
+
if (list)
|
|
71
|
+
list.push(i);
|
|
72
|
+
else
|
|
73
|
+
m.set(p, [i]);
|
|
74
|
+
});
|
|
75
|
+
return m;
|
|
76
|
+
}
|
|
77
|
+
/** Order-preserving alignment of two sibling lists under `same`. */
|
|
78
|
+
function align(ca, cb, same) {
|
|
79
|
+
const matched = ca.length * cb.length > LCS_CELL_CAP ? greedy(ca, cb, same) : lcs(ca, cb, same);
|
|
80
|
+
const usedA = new Set(matched.map(([i]) => i));
|
|
81
|
+
const usedB = new Set(matched.map(([, j]) => j));
|
|
82
|
+
return {
|
|
83
|
+
matched,
|
|
84
|
+
leftA: ca.filter((i) => !usedA.has(i)),
|
|
85
|
+
leftB: cb.filter((j) => !usedB.has(j)),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function lcs(ca, cb, same) {
|
|
89
|
+
const n = ca.length;
|
|
90
|
+
const m = cb.length;
|
|
91
|
+
// L[i][j] = LCS length of ca[i..] and cb[j..]
|
|
92
|
+
const L = Array.from({ length: n + 1 }, () => new Uint32Array(m + 1));
|
|
93
|
+
for (let i = n - 1; i >= 0; i--) {
|
|
94
|
+
for (let j = m - 1; j >= 0; j--) {
|
|
95
|
+
L[i][j] = same(ca[i], cb[j])
|
|
96
|
+
? L[i + 1][j + 1] + 1
|
|
97
|
+
: Math.max(L[i + 1][j], L[i][j + 1]);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const out = [];
|
|
101
|
+
let i = 0;
|
|
102
|
+
let j = 0;
|
|
103
|
+
while (i < n && j < m) {
|
|
104
|
+
if (same(ca[i], cb[j])) {
|
|
105
|
+
out.push([ca[i], cb[j]]);
|
|
106
|
+
i++;
|
|
107
|
+
j++;
|
|
108
|
+
}
|
|
109
|
+
else if (L[i + 1][j] >= L[i][j + 1])
|
|
110
|
+
i++;
|
|
111
|
+
else
|
|
112
|
+
j++;
|
|
113
|
+
}
|
|
114
|
+
return out;
|
|
115
|
+
}
|
|
116
|
+
/** In-order pairing: each a takes the first unused b after the last pairing
|
|
117
|
+
* that is `same`. Linear-ish, order-preserving, and good enough for a parent
|
|
118
|
+
* with thousands of children where an exact LCS would cost seconds.
|
|
119
|
+
* Failure mode: it never looks backwards, so a reordered list degrades to
|
|
120
|
+
* many removed/added pairs instead of a minimal edit. */
|
|
121
|
+
function greedy(ca, cb, same) {
|
|
122
|
+
const out = [];
|
|
123
|
+
let j = 0;
|
|
124
|
+
for (const i of ca) {
|
|
125
|
+
let k = j;
|
|
126
|
+
while (k < cb.length && !same(i, cb[k]))
|
|
127
|
+
k++;
|
|
128
|
+
if (k < cb.length) {
|
|
129
|
+
out.push([i, cb[k]]);
|
|
130
|
+
j = k + 1;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return out;
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=match.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"match.js","sourceRoot":"","sources":["../../../src/diff/structural/match.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// "Everything below the banner moved down 14px because the banner grew 14px"
|
|
2
|
+
// is one fact, not forty. Collapse a run of ≥3 matched elements that moved by
|
|
3
|
+
// the same (dx, dy), contiguous in document order, into one `shift` anchored
|
|
4
|
+
// on the element that explains it (see `collapse`).
|
|
5
|
+
//
|
|
6
|
+
// The anchor set is wider than the spec's added/removed/resized: a margin or
|
|
7
|
+
// line-height change moves everything after it without resizing anything the
|
|
8
|
+
// LCS can see, and leaving those as 40 `moved` deltas defeats the purpose.
|
|
9
|
+
import { compareCodeUnits, sortFacts } from './deltas.js';
|
|
10
|
+
const MIN_RUN = 3;
|
|
11
|
+
export function synthesizeShifts(facts) {
|
|
12
|
+
const anchors = facts.filter((f) => f.delta.kind !== 'moved' && f.delta.kind !== 'shift');
|
|
13
|
+
const moved = facts.filter((f) => f.delta.kind === 'moved');
|
|
14
|
+
if (moved.length < MIN_RUN)
|
|
15
|
+
return facts;
|
|
16
|
+
// Group by displacement, keeping document order within a group.
|
|
17
|
+
const groups = new Map();
|
|
18
|
+
for (const f of moved) {
|
|
19
|
+
const d = f.delta;
|
|
20
|
+
const key = `${d.dx},${d.dy}`;
|
|
21
|
+
const g = groups.get(key);
|
|
22
|
+
if (g)
|
|
23
|
+
g.push(f);
|
|
24
|
+
else
|
|
25
|
+
groups.set(key, [f]);
|
|
26
|
+
}
|
|
27
|
+
// A fact's position among all moves, in document order: two members of a
|
|
28
|
+
// group are adjacent exactly when no move with another displacement sits
|
|
29
|
+
// between them (unmoved elements in between — sticky headers, absolutes —
|
|
30
|
+
// do not break a run).
|
|
31
|
+
const pos = new Map(moved.map((f, k) => [f, k]));
|
|
32
|
+
const anchorOrders = anchors.map((f) => f.order); // ascending: facts are sorted
|
|
33
|
+
const collapsed = new Set();
|
|
34
|
+
const shifts = [];
|
|
35
|
+
// Deterministic: iterate groups by key, not by insertion.
|
|
36
|
+
for (const key of [...groups.keys()].sort(compareCodeUnits)) {
|
|
37
|
+
const g = groups.get(key);
|
|
38
|
+
// A different move inside a group splits it rather than vetoing it: in
|
|
39
|
+
// `font-size` the logo slides right between the button and the rows, and
|
|
40
|
+
// the six elements below still moved up together. So does another cause
|
|
41
|
+
// between two members: rows below a second removal moved because of that
|
|
42
|
+
// removal, not the first, so "480 elements after the first" would blame
|
|
43
|
+
// the wrong element (measured on the 50-section perf tree).
|
|
44
|
+
let start = 0;
|
|
45
|
+
for (let k = 1; k <= g.length; k++) {
|
|
46
|
+
if (k < g.length &&
|
|
47
|
+
pos.get(g[k]) === pos.get(g[k - 1]) + 1 &&
|
|
48
|
+
!causeBetween(anchorOrders, g[k - 1].order, g[k].order))
|
|
49
|
+
continue;
|
|
50
|
+
const run = g.slice(start, k);
|
|
51
|
+
start = k;
|
|
52
|
+
const shift = run.length >= MIN_RUN ? collapse(run, anchors, moved) : undefined;
|
|
53
|
+
if (!shift)
|
|
54
|
+
continue;
|
|
55
|
+
for (const f of run)
|
|
56
|
+
collapsed.add(f);
|
|
57
|
+
shifts.push(shift);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (shifts.length === 0)
|
|
61
|
+
return facts;
|
|
62
|
+
return sortFacts([...facts.filter((f) => !collapsed.has(f)), ...shifts]);
|
|
63
|
+
}
|
|
64
|
+
/** One contiguous run as a shift on what explains it; nothing means the
|
|
65
|
+
* move stays as moves. In order of preference:
|
|
66
|
+
*
|
|
67
|
+
* 1. A move with another displacement that crossed the run — before it in
|
|
68
|
+
* the baseline, after it now, or the reverse. That is a reorder: moving
|
|
69
|
+
* item 3 to slot 9 of 12 moves items 4–9 up 37px each, and item 3 (the
|
|
70
|
+
* only thing the author touched) sits after them in the current order,
|
|
71
|
+
* so "nearest preceding" can never find it. Measured on the 12-item
|
|
72
|
+
* probe: before this, the six −37px moves stayed six (no anchor) and
|
|
73
|
+
* item 3's `moved down 222px` was the 8th line.
|
|
74
|
+
* 2. The nearest preceding element that changed in another way.
|
|
75
|
+
* 3. The nearest preceding move with another displacement.
|
|
76
|
+
*
|
|
77
|
+
* (1) and (3) take the head of the anchor's own block of equal moves, so the
|
|
78
|
+
* shift names the list item, not the `span` inside it that moved with it.
|
|
79
|
+
* (2) is ahead of (3) because a nearer odd move is usually a consequence of
|
|
80
|
+
* the same cause: in `inserted` the logo drops off the button's line between
|
|
81
|
+
* the banner and the rows, and the rows moved because of the banner. */
|
|
82
|
+
function collapse(run, anchors, moved) {
|
|
83
|
+
const first = run[0];
|
|
84
|
+
const last = run[run.length - 1];
|
|
85
|
+
const d = first.delta;
|
|
86
|
+
const other = moved.filter((f) => {
|
|
87
|
+
const m = f.delta;
|
|
88
|
+
return m.dx !== d.dx || m.dy !== d.dy;
|
|
89
|
+
});
|
|
90
|
+
const base = (f) => f.source.index; // moves are matched: baseline side
|
|
91
|
+
const crossed = other
|
|
92
|
+
.filter((f) => (base(f) < base(first) && f.order > last.order) ||
|
|
93
|
+
(base(f) > base(last) && f.order < first.order))
|
|
94
|
+
.sort((x, y) => base(x) - base(y));
|
|
95
|
+
const preceding = (fs) => fs.filter((f) => f.order < first.order).at(-1);
|
|
96
|
+
const anchor = (crossed[0] && headOf(crossed[0], moved)) ??
|
|
97
|
+
preceding(anchors) ??
|
|
98
|
+
(preceding(other) && headOf(preceding(other), moved));
|
|
99
|
+
if (!anchor)
|
|
100
|
+
return undefined;
|
|
101
|
+
return {
|
|
102
|
+
delta: {
|
|
103
|
+
kind: 'shift',
|
|
104
|
+
after: 'el' in anchor.delta ? anchor.delta.el : anchor.delta.after,
|
|
105
|
+
dx: d.dx,
|
|
106
|
+
dy: d.dy,
|
|
107
|
+
count: run.length,
|
|
108
|
+
},
|
|
109
|
+
rects: run.flatMap((f) => f.rects),
|
|
110
|
+
abstract: anchor.abstract,
|
|
111
|
+
order: anchor.order + 0.25,
|
|
112
|
+
source: anchor.source,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/** The first of the moves contiguous (in document order) with `f` that
|
|
116
|
+
* share its displacement: an element and the descendants that moved with it
|
|
117
|
+
* read as the element. */
|
|
118
|
+
function headOf(f, moved) {
|
|
119
|
+
const m = f.delta;
|
|
120
|
+
let k = moved.indexOf(f);
|
|
121
|
+
while (k > 0) {
|
|
122
|
+
const p = moved[k - 1].delta;
|
|
123
|
+
if (p.dx !== m.dx || p.dy !== m.dy)
|
|
124
|
+
break;
|
|
125
|
+
k--;
|
|
126
|
+
}
|
|
127
|
+
return moved[k];
|
|
128
|
+
}
|
|
129
|
+
/** Some anchor sits strictly between two run members. An anchor at a
|
|
130
|
+
* member's own position (a moved row whose text also changed) is part of
|
|
131
|
+
* the run, not a new cause. */
|
|
132
|
+
function causeBetween(orders, lo, hi) {
|
|
133
|
+
let a = 0;
|
|
134
|
+
let b = orders.length;
|
|
135
|
+
while (a < b) {
|
|
136
|
+
const m = (a + b) >> 1;
|
|
137
|
+
if (orders[m] <= lo)
|
|
138
|
+
a = m + 1;
|
|
139
|
+
else
|
|
140
|
+
b = m;
|
|
141
|
+
}
|
|
142
|
+
return a < orders.length && orders[a] < hi;
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=shift.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shift.js","sourceRoot":"","sources":["../../../src/diff/structural/shift.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ChangedRegion, Classification, Delta, Flag, StructureSnapshot, StyleTag } from '../../types.js';
|
|
2
|
+
export declare const MAX_SUMMARY = 300;
|
|
3
|
+
/** `change`: the summary's lead clauses without the per-story tail. */
|
|
4
|
+
export declare const MAX_CHANGE = 160;
|
|
5
|
+
export declare function describeDelta(d: Delta): string;
|
|
6
|
+
/** The deltas as indented display lines for a human, grouped by element:
|
|
7
|
+
* the element's content changes (four-side longhands collapsed), then its
|
|
8
|
+
* resize and move on one line. Groups run causes first — content, added and
|
|
9
|
+
* removed, and a move a shift is blamed on; then shifts; then other moves;
|
|
10
|
+
* then resizes, so an ancestor's consequential `resized` comes last. Every
|
|
11
|
+
* delta passed in is stated on some line; `total` is the verdict's
|
|
12
|
+
* `deltaCount`, which says whether `deltas` is the whole set. */
|
|
13
|
+
export declare function describeDeltas(deltas: Delta[], total?: number): string[];
|
|
14
|
+
export interface SummaryArgs {
|
|
15
|
+
classification: Classification;
|
|
16
|
+
styleTags: StyleTag[];
|
|
17
|
+
deltas: Delta[];
|
|
18
|
+
deltaCount: number;
|
|
19
|
+
regionCount: number;
|
|
20
|
+
unexplainedRegions: ChangedRegion[];
|
|
21
|
+
flags: Flag[];
|
|
22
|
+
imagesIdentical: boolean;
|
|
23
|
+
baseline?: StructureSnapshot;
|
|
24
|
+
current?: StructureSnapshot;
|
|
25
|
+
}
|
|
26
|
+
/** The change alone, for naming what several stories share: the summary's
|
|
27
|
+
* own clause wording with every element reduced to its role|tag (the
|
|
28
|
+
* caller passes `deltas` already abstracted), no coverage or tolerance
|
|
29
|
+
* tail, at most two clauses. */
|
|
30
|
+
export declare function buildChange(a: SummaryArgs): string;
|
|
31
|
+
export declare function buildSummary(a: SummaryArgs): string;
|