@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.
Files changed (153) hide show
  1. package/dist/agent/handoff.d.ts +8 -1
  2. package/dist/agent/handoff.js +75 -7
  3. package/dist/agent/handoff.js.map +1 -1
  4. package/dist/analysis/affected.d.ts +37 -0
  5. package/dist/analysis/affected.js +97 -22
  6. package/dist/analysis/affected.js.map +1 -1
  7. package/dist/analysis/cause.d.ts +32 -0
  8. package/dist/analysis/cause.js +187 -0
  9. package/dist/analysis/cause.js.map +1 -0
  10. package/dist/baseline/approve.d.ts +24 -0
  11. package/dist/baseline/approve.js +54 -0
  12. package/dist/baseline/approve.js.map +1 -0
  13. package/dist/baseline/git-store.d.ts +4 -0
  14. package/dist/baseline/git-store.js +22 -0
  15. package/dist/baseline/git-store.js.map +1 -1
  16. package/dist/browser/acquire.d.ts +15 -0
  17. package/dist/browser/acquire.js +26 -0
  18. package/dist/browser/acquire.js.map +1 -0
  19. package/dist/browser/browser.d.ts +53 -0
  20. package/dist/browser/browser.js +122 -0
  21. package/dist/browser/browser.js.map +1 -0
  22. package/dist/browser/cdp.d.ts +81 -0
  23. package/dist/browser/cdp.js +230 -0
  24. package/dist/browser/cdp.js.map +1 -0
  25. package/dist/browser/daemon/client.d.ts +145 -0
  26. package/dist/browser/daemon/client.js +764 -0
  27. package/dist/browser/daemon/client.js.map +1 -0
  28. package/dist/browser/daemon/state.d.ts +99 -0
  29. package/dist/browser/daemon/state.js +297 -0
  30. package/dist/browser/daemon/state.js.map +1 -0
  31. package/dist/browser/daemon/supervisor-main.d.ts +1 -0
  32. package/dist/browser/daemon/supervisor-main.js +54 -0
  33. package/dist/browser/daemon/supervisor-main.js.map +1 -0
  34. package/dist/browser/daemon/supervisor.d.ts +37 -0
  35. package/dist/browser/daemon/supervisor.js +777 -0
  36. package/dist/browser/daemon/supervisor.js.map +1 -0
  37. package/dist/{capture/launch.d.ts → browser/flags.d.ts} +19 -1
  38. package/dist/browser/flags.js +146 -0
  39. package/dist/browser/flags.js.map +1 -0
  40. package/dist/browser/index.d.ts +14 -0
  41. package/dist/browser/index.js +14 -0
  42. package/dist/browser/index.js.map +1 -0
  43. package/dist/browser/install.d.ts +33 -0
  44. package/dist/browser/install.js +167 -0
  45. package/dist/browser/install.js.map +1 -0
  46. package/dist/browser/launch.d.ts +37 -0
  47. package/dist/browser/launch.js +254 -0
  48. package/dist/browser/launch.js.map +1 -0
  49. package/dist/browser/page.d.ts +125 -0
  50. package/dist/browser/page.js +564 -0
  51. package/dist/browser/page.js.map +1 -0
  52. package/dist/browser/pins.d.ts +12 -0
  53. package/dist/browser/pins.js +57 -0
  54. package/dist/browser/pins.js.map +1 -0
  55. package/dist/browser/private-dir.d.ts +7 -0
  56. package/dist/browser/private-dir.js +24 -0
  57. package/dist/browser/private-dir.js.map +1 -0
  58. package/dist/browser/resolve.d.ts +31 -0
  59. package/dist/browser/resolve.js +126 -0
  60. package/dist/browser/resolve.js.map +1 -0
  61. package/dist/browser/selectors.d.ts +10 -0
  62. package/dist/browser/selectors.js +60 -0
  63. package/dist/browser/selectors.js.map +1 -0
  64. package/dist/browser/transport.d.ts +51 -0
  65. package/dist/browser/transport.js +124 -0
  66. package/dist/browser/transport.js.map +1 -0
  67. package/dist/browser/types.d.ts +4 -0
  68. package/dist/browser/types.js +2 -0
  69. package/dist/browser/types.js.map +1 -0
  70. package/dist/browser/zip.d.ts +27 -0
  71. package/dist/browser/zip.js +241 -0
  72. package/dist/browser/zip.js.map +1 -0
  73. package/dist/capture/capture.d.ts +7 -1
  74. package/dist/capture/capture.js +25 -8
  75. package/dist/capture/capture.js.map +1 -1
  76. package/dist/capture/env.d.ts +1 -1
  77. package/dist/capture/pool.d.ts +1 -1
  78. package/dist/capture/pool.js +41 -8
  79. package/dist/capture/pool.js.map +1 -1
  80. package/dist/capture/settle.d.ts +1 -1
  81. package/dist/capture/settle.js +2 -0
  82. package/dist/capture/settle.js.map +1 -1
  83. package/dist/capture/snap.d.ts +43 -0
  84. package/dist/capture/snap.js +103 -0
  85. package/dist/capture/snap.js.map +1 -0
  86. package/dist/capture/structure.d.ts +77 -0
  87. package/dist/capture/structure.js +498 -0
  88. package/dist/capture/structure.js.map +1 -0
  89. package/dist/diff/compare.d.ts +19 -0
  90. package/dist/diff/compare.js +19 -0
  91. package/dist/diff/compare.js.map +1 -0
  92. package/dist/diff/crops.d.ts +26 -0
  93. package/dist/diff/crops.js +92 -0
  94. package/dist/diff/crops.js.map +1 -0
  95. package/dist/diff/structural/classify.d.ts +29 -0
  96. package/dist/diff/structural/classify.js +113 -0
  97. package/dist/diff/structural/classify.js.map +1 -0
  98. package/dist/diff/structural/coverage.d.ts +11 -0
  99. package/dist/diff/structural/coverage.js +37 -0
  100. package/dist/diff/structural/coverage.js.map +1 -0
  101. package/dist/diff/structural/deltas.d.ts +29 -0
  102. package/dist/diff/structural/deltas.js +218 -0
  103. package/dist/diff/structural/deltas.js.map +1 -0
  104. package/dist/diff/structural/disambiguate.d.ts +3 -0
  105. package/dist/diff/structural/disambiguate.js +89 -0
  106. package/dist/diff/structural/disambiguate.js.map +1 -0
  107. package/dist/diff/structural/label.d.ts +15 -0
  108. package/dist/diff/structural/label.js +64 -0
  109. package/dist/diff/structural/label.js.map +1 -0
  110. package/dist/diff/structural/match.d.ts +13 -0
  111. package/dist/diff/structural/match.js +135 -0
  112. package/dist/diff/structural/match.js.map +1 -0
  113. package/dist/diff/structural/shift.d.ts +2 -0
  114. package/dist/diff/structural/shift.js +144 -0
  115. package/dist/diff/structural/shift.js.map +1 -0
  116. package/dist/diff/structural/summary.d.ts +31 -0
  117. package/dist/diff/structural/summary.js +420 -0
  118. package/dist/diff/structural/summary.js.map +1 -0
  119. package/dist/diff/structural.d.ts +40 -0
  120. package/dist/diff/structural.js +219 -0
  121. package/dist/diff/structural.js.map +1 -0
  122. package/dist/index.d.ts +28 -4
  123. package/dist/index.js +32 -3
  124. package/dist/index.js.map +1 -1
  125. package/dist/platform.d.ts +1 -1
  126. package/dist/platforms/urls.d.ts +3 -0
  127. package/dist/platforms/urls.js +89 -0
  128. package/dist/platforms/urls.js.map +1 -0
  129. package/dist/report/comment.js +9 -3
  130. package/dist/report/comment.js.map +1 -1
  131. package/dist/report/format.d.ts +18 -0
  132. package/dist/report/format.js +20 -0
  133. package/dist/report/format.js.map +1 -1
  134. package/dist/report/html.js +2 -2
  135. package/dist/report/html.js.map +1 -1
  136. package/dist/run/groups.d.ts +2 -0
  137. package/dist/run/groups.js +45 -0
  138. package/dist/run/groups.js.map +1 -0
  139. package/dist/run/run.d.ts +6 -2
  140. package/dist/run/run.js +74 -6
  141. package/dist/run/run.js.map +1 -1
  142. package/dist/shell-quote.d.ts +2 -0
  143. package/dist/shell-quote.js +10 -0
  144. package/dist/shell-quote.js.map +1 -0
  145. package/dist/types.d.ts +201 -5
  146. package/dist/types.js +0 -2
  147. package/dist/types.js.map +1 -1
  148. package/dist/url-platform.d.ts +2 -2
  149. package/dist/url-platform.js +2 -2
  150. package/dist/url-platform.js.map +1 -1
  151. package/package.json +6 -10
  152. package/dist/capture/launch.js +0 -56
  153. 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,3 @@
1
+ import type { StructureSnapshot } from '../../types.js';
2
+ import type { Fact } from './deltas.js';
3
+ export declare function disambiguate(facts: Fact[], baseline: StructureSnapshot, current: StructureSnapshot): Fact[];
@@ -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,2 @@
1
+ import { type Fact } from './deltas.js';
2
+ export declare function synthesizeShifts(facts: Fact[]): Fact[];