@juxtapose/core 0.4.0 → 0.6.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 +37 -0
- package/dist/analysis/affected.js +97 -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 +15 -0
- package/dist/browser/acquire.js +26 -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/daemon/client.d.ts +145 -0
- package/dist/browser/daemon/client.js +764 -0
- package/dist/browser/daemon/client.js.map +1 -0
- package/dist/browser/daemon/state.d.ts +99 -0
- package/dist/browser/daemon/state.js +297 -0
- package/dist/browser/daemon/state.js.map +1 -0
- package/dist/browser/daemon/supervisor-main.d.ts +1 -0
- package/dist/browser/daemon/supervisor-main.js +54 -0
- package/dist/browser/daemon/supervisor-main.js.map +1 -0
- package/dist/browser/daemon/supervisor.d.ts +37 -0
- package/dist/browser/daemon/supervisor.js +777 -0
- package/dist/browser/daemon/supervisor.js.map +1 -0
- package/dist/{capture/launch.d.ts → browser/flags.d.ts} +19 -1
- package/dist/browser/flags.js +146 -0
- package/dist/browser/flags.js.map +1 -0
- package/dist/browser/index.d.ts +14 -0
- package/dist/browser/index.js +14 -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 +254 -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/private-dir.d.ts +7 -0
- package/dist/browser/private-dir.js +24 -0
- package/dist/browser/private-dir.js.map +1 -0
- package/dist/browser/resolve.d.ts +31 -0
- package/dist/browser/resolve.js +126 -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 +51 -0
- package/dist/browser/transport.js +124 -0
- package/dist/browser/transport.js.map +1 -0
- package/dist/browser/types.d.ts +4 -0
- package/dist/browser/types.js +2 -0
- package/dist/browser/types.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 +25 -8
- package/dist/capture/capture.js.map +1 -1
- package/dist/capture/env.d.ts +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 +28 -4
- package/dist/index.js +32 -3
- 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.js +9 -3
- package/dist/report/comment.js.map +1 -1
- package/dist/report/format.d.ts +18 -0
- package/dist/report/format.js +20 -0
- package/dist/report/format.js.map +1 -1
- package/dist/report/html.js +2 -2
- package/dist/report/html.js.map +1 -1
- 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/run.d.ts +6 -2
- package/dist/run/run.js +74 -6
- 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 +201 -5
- package/dist/types.js +0 -2
- package/dist/types.js.map +1 -1
- 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 +6 -10
- package/dist/capture/launch.js +0 -56
- package/dist/capture/launch.js.map +0 -1
|
@@ -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,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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"}
|