@vizzly-testing/cli 0.35.3-beta.1 → 0.35.3-beta.3
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/README.md +8 -3
- package/dist/api/endpoints.js +1 -46
- package/dist/api/index.js +1 -1
- package/dist/cli.js +5 -5
- package/dist/commands/api.js +3 -3
- package/dist/commands/builds.js +12 -15
- package/dist/commands/comparisons.js +7 -9
- package/dist/commands/context.js +10 -20
- package/dist/commands/init.js +62 -13
- package/dist/commands/review.js +122 -35
- package/dist/commands/run.js +1 -3
- package/dist/commands/status.js +35 -79
- package/dist/commands/upload.js +1 -1
- package/dist/context/local-workspace-provider.js +36 -126
- package/dist/reporter/reporter-bundle.iife.js +49 -81
- package/dist/server/handlers/tdd-handler.js +2 -7
- package/dist/server/routers/dashboard.js +5 -1
- package/dist/tdd/runtime-context.js +0 -2
- package/dist/tdd/services/comparison-service.js +36 -76
- package/dist/tdd/tdd-service.js +7 -205
- package/dist/types/index.d.ts +1 -1
- package/dist/uploader/core.js +7 -8
- package/dist/utils/visual-context-normalizers.js +86 -139
- package/package.json +4 -4
- package/skills/vizzly/SKILL.md +42 -52
- package/skills/vizzly/references/cli-context.md +49 -52
- package/skills/vizzly/references/dynamic-content.md +18 -29
- package/skills/vizzly/references/sdks.md +20 -28
- package/skills/vizzly/references/setup-ci.md +18 -42
- package/dist/tdd/core/hotspot-coverage.js +0 -112
- package/dist/tdd/core/region-coverage.js +0 -203
- package/dist/tdd/metadata/hotspot-metadata.js +0 -93
- package/dist/tdd/metadata/region-metadata.js +0 -56
- package/skills/vizzly/agents/openai.yaml +0 -4
|
@@ -43,7 +43,7 @@ function mapComparisonResult(status) {
|
|
|
43
43
|
}
|
|
44
44
|
return status || 'unknown';
|
|
45
45
|
}
|
|
46
|
-
function
|
|
46
|
+
function mapReviewState(status) {
|
|
47
47
|
if (status === 'failed' || status === 'new') {
|
|
48
48
|
return 'pending';
|
|
49
49
|
}
|
|
@@ -88,50 +88,6 @@ function resolveAssetReference(assetPath, snapshot) {
|
|
|
88
88
|
}
|
|
89
89
|
return assetPath;
|
|
90
90
|
}
|
|
91
|
-
function normalizeConfirmedRegions(regions = []) {
|
|
92
|
-
return regions.map((region, index) => ({
|
|
93
|
-
id: region.id || `local-region-${index}`,
|
|
94
|
-
x1: region.x1 ?? region.x ?? null,
|
|
95
|
-
y1: region.y1 ?? region.y ?? null,
|
|
96
|
-
x2: region.x2 ?? (region.x != null && region.width != null ? region.x + region.width : null),
|
|
97
|
-
y2: region.y2 ?? (region.y != null && region.height != null ? region.y + region.height : null),
|
|
98
|
-
label: region.label || null
|
|
99
|
-
}));
|
|
100
|
-
}
|
|
101
|
-
function mergeConfirmedRegions(snapshot, comparisonName, details = {}) {
|
|
102
|
-
let workspaceRegions = snapshot.regions?.[comparisonName]?.confirmed || [];
|
|
103
|
-
let detailRegions = details.confirmedRegions || [];
|
|
104
|
-
let merged = [...workspaceRegions, ...detailRegions];
|
|
105
|
-
let seen = new Set();
|
|
106
|
-
return normalizeConfirmedRegions(merged).filter(region => {
|
|
107
|
-
let key = `${region.label || ''}:${region.x1}:${region.y1}:${region.x2}:${region.y2}`;
|
|
108
|
-
if (seen.has(key)) {
|
|
109
|
-
return false;
|
|
110
|
-
}
|
|
111
|
-
seen.add(key);
|
|
112
|
-
return true;
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
function buildHotspotAnalysis(snapshot, comparisonName, details = {}) {
|
|
116
|
-
let hotspotMetadata = snapshot.hotspots?.[comparisonName] || null;
|
|
117
|
-
let hotspotAnalysis = details.hotspotAnalysis || null;
|
|
118
|
-
if (!hotspotMetadata && !hotspotAnalysis) {
|
|
119
|
-
return {
|
|
120
|
-
regions: [],
|
|
121
|
-
total_builds_analyzed: 0,
|
|
122
|
-
confidence: 'no_data',
|
|
123
|
-
confidence_score: null,
|
|
124
|
-
data_source: 'local_workspace'
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
return {
|
|
128
|
-
regions: hotspotMetadata?.regions || [],
|
|
129
|
-
total_builds_analyzed: 1,
|
|
130
|
-
confidence: hotspotAnalysis?.confidence || hotspotMetadata?.confidence || 'workspace',
|
|
131
|
-
confidence_score: hotspotAnalysis?.confidenceScore || null,
|
|
132
|
-
data_source: 'local_workspace'
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
91
|
function buildComparisonLinks(snapshot, comparisonId) {
|
|
136
92
|
if (!snapshot.serverInfo?.port) {
|
|
137
93
|
return {};
|
|
@@ -160,7 +116,7 @@ function buildBuildSnapshot(snapshot) {
|
|
|
160
116
|
branch: 'local',
|
|
161
117
|
commit_sha: null,
|
|
162
118
|
commit_message: null,
|
|
163
|
-
|
|
119
|
+
review_state: snapshot.serverInfo ? 'pending' : 'approved',
|
|
164
120
|
status: snapshot.serverInfo ? 'running' : 'completed',
|
|
165
121
|
created_at: null
|
|
166
122
|
};
|
|
@@ -181,7 +137,7 @@ function buildBaselineSnapshot(snapshot) {
|
|
|
181
137
|
branch: metadata.branch || 'local',
|
|
182
138
|
commit_sha: metadata.buildInfo?.commitSha || null,
|
|
183
139
|
commit_message: metadata.buildInfo?.commitMessage || null,
|
|
184
|
-
|
|
140
|
+
review_state: metadata.buildInfo?.visual_review?.state || 'approved',
|
|
185
141
|
status: metadata.buildInfo?.completedAt ? 'completed' : 'local',
|
|
186
142
|
created_at: metadata.createdAt || null,
|
|
187
143
|
completed_at: metadata.buildInfo?.completedAt || null
|
|
@@ -192,7 +148,7 @@ function buildBaselineSnapshot(snapshot) {
|
|
|
192
148
|
}
|
|
193
149
|
function buildReviewState(build, reviewSummary) {
|
|
194
150
|
let reasons = [];
|
|
195
|
-
if (build.
|
|
151
|
+
if (build.review_state === 'pending') {
|
|
196
152
|
reasons.push('build_pending_approval');
|
|
197
153
|
}
|
|
198
154
|
if (reviewSummary.pending > 0) {
|
|
@@ -208,12 +164,10 @@ function buildReviewState(build, reviewSummary) {
|
|
|
208
164
|
function mapLocalComparison(snapshot, comparison) {
|
|
209
165
|
let details = snapshot.comparisonDetails[comparison.id] || {};
|
|
210
166
|
let comparisonName = comparison.originalName || comparison.name;
|
|
211
|
-
let confirmedRegions = mergeConfirmedRegions(snapshot, comparisonName, details);
|
|
212
|
-
let hotspotAnalysis = buildHotspotAnalysis(snapshot, comparisonName, details);
|
|
213
167
|
let properties = comparison.properties || {};
|
|
214
168
|
let buildSnapshot = buildBuildSnapshot(snapshot);
|
|
215
169
|
let result = mapComparisonResult(comparison.status);
|
|
216
|
-
let
|
|
170
|
+
let reviewState = mapReviewState(comparison.status);
|
|
217
171
|
let baselineBuildId = snapshot.baselineMetadata?.buildId || null;
|
|
218
172
|
let diffImageUrl = resolveAssetReference(comparison.diff, snapshot);
|
|
219
173
|
let diffRegions = details.diffClusters || [];
|
|
@@ -223,8 +177,8 @@ function mapLocalComparison(snapshot, comparison) {
|
|
|
223
177
|
screenshot_name: comparisonName,
|
|
224
178
|
status: comparison.status,
|
|
225
179
|
result,
|
|
226
|
-
|
|
227
|
-
needs_review:
|
|
180
|
+
review_state: reviewState,
|
|
181
|
+
needs_review: reviewState === 'pending' && ['changed', 'new'].includes(result),
|
|
228
182
|
build_id: buildSnapshot.id,
|
|
229
183
|
build_name: buildSnapshot.name,
|
|
230
184
|
build_branch: buildSnapshot.branch,
|
|
@@ -275,14 +229,11 @@ function mapLocalComparison(snapshot, comparison) {
|
|
|
275
229
|
} : null,
|
|
276
230
|
diff_lines: null,
|
|
277
231
|
fingerprint_hash: null,
|
|
278
|
-
fingerprint_data: null
|
|
279
|
-
hotspot_analysis: hotspotAnalysis,
|
|
280
|
-
region_analysis: details.regionAnalysis || null,
|
|
281
|
-
confirmed_regions: confirmedRegions
|
|
232
|
+
fingerprint_data: null
|
|
282
233
|
}
|
|
283
234
|
};
|
|
284
235
|
}
|
|
285
|
-
function
|
|
236
|
+
function formatLocalScreenshot(screenshot, baseline) {
|
|
286
237
|
if (!screenshot) return null;
|
|
287
238
|
return {
|
|
288
239
|
id: screenshot.id,
|
|
@@ -306,8 +257,8 @@ function projectLocalScreenshot(screenshot, baseline) {
|
|
|
306
257
|
} : null
|
|
307
258
|
};
|
|
308
259
|
}
|
|
309
|
-
function
|
|
310
|
-
let
|
|
260
|
+
function formatLocalDiff(diff, includeDiffs) {
|
|
261
|
+
let formatted = {
|
|
311
262
|
percentage: diff?.percentage ?? null,
|
|
312
263
|
changed_pixels: diff?.changed_pixels ?? null,
|
|
313
264
|
total_pixels: diff?.total_pixels ?? null,
|
|
@@ -317,32 +268,32 @@ function projectLocalDiff(diff, includeDiffs) {
|
|
|
317
268
|
region_count: Array.isArray(diff?.regions) ? diff.regions.length : null
|
|
318
269
|
};
|
|
319
270
|
if (includeDiffs) {
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
271
|
+
formatted.regions = diff?.regions || [];
|
|
272
|
+
formatted.cluster_metadata = diff?.cluster_metadata ?? null;
|
|
273
|
+
formatted.fingerprint_data = diff?.fingerprint_data ?? null;
|
|
274
|
+
formatted.diff_lines = diff?.diff_lines ?? [];
|
|
324
275
|
}
|
|
325
|
-
return
|
|
276
|
+
return formatted;
|
|
326
277
|
}
|
|
327
|
-
function
|
|
278
|
+
function formatLocalEvidence(comparison, includeDiffs) {
|
|
328
279
|
return {
|
|
329
280
|
type: 'comparison',
|
|
330
281
|
id: comparison.id,
|
|
331
282
|
screenshot_name: comparison.screenshot_name,
|
|
332
283
|
status: comparison.status,
|
|
333
284
|
result: comparison.result,
|
|
334
|
-
|
|
285
|
+
review_state: comparison.review_state,
|
|
335
286
|
needs_review: comparison.needs_review,
|
|
336
287
|
build_id: comparison.build_id,
|
|
337
288
|
build_name: comparison.build_name,
|
|
338
289
|
build_branch: comparison.build_branch,
|
|
339
290
|
build_commit_sha: comparison.build_commit_sha,
|
|
340
291
|
build_created_at: comparison.build_created_at,
|
|
341
|
-
screenshot:
|
|
342
|
-
diff:
|
|
292
|
+
screenshot: formatLocalScreenshot(comparison.screenshot, comparison.baseline),
|
|
293
|
+
diff: formatLocalDiff(comparison.diff, includeDiffs)
|
|
343
294
|
};
|
|
344
295
|
}
|
|
345
|
-
function
|
|
296
|
+
function formatLocalFocusedComparison(comparison, includeDiffs) {
|
|
346
297
|
if (includeDiffs) return comparison;
|
|
347
298
|
let analysis = comparison.analysis || {};
|
|
348
299
|
let {
|
|
@@ -354,11 +305,11 @@ function projectLocalFocusedComparison(comparison, includeDiffs) {
|
|
|
354
305
|
} = analysis;
|
|
355
306
|
return {
|
|
356
307
|
...comparison,
|
|
357
|
-
diff:
|
|
308
|
+
diff: formatLocalDiff(comparison.diff, false),
|
|
358
309
|
analysis: summaryAnalysis
|
|
359
310
|
};
|
|
360
311
|
}
|
|
361
|
-
function
|
|
312
|
+
function formatLocalHistoryItem(comparison) {
|
|
362
313
|
return {
|
|
363
314
|
id: comparison.id,
|
|
364
315
|
screenshot_name: comparison.screenshot_name,
|
|
@@ -369,7 +320,7 @@ function projectLocalHistoryItem(comparison) {
|
|
|
369
320
|
build_name: comparison.build_name,
|
|
370
321
|
build_branch: comparison.build_branch,
|
|
371
322
|
build_created_at: comparison.build_created_at,
|
|
372
|
-
screenshot:
|
|
323
|
+
screenshot: formatLocalScreenshot(comparison.screenshot, comparison.baseline),
|
|
373
324
|
diff: {
|
|
374
325
|
percentage: comparison.diff?.percentage ?? null,
|
|
375
326
|
fingerprint_hash: comparison.diff?.fingerprint_hash ?? null,
|
|
@@ -384,43 +335,19 @@ function compactLocalCollection(items) {
|
|
|
384
335
|
details_available: Array.isArray(items) && items.length > 0
|
|
385
336
|
};
|
|
386
337
|
}
|
|
387
|
-
function createLocalDynamicRegions(comparison = null) {
|
|
388
|
-
let confirmedRegions = comparison?.analysis?.confirmed_regions;
|
|
389
|
-
let hotspotAnalysis = comparison?.analysis?.hotspot_analysis;
|
|
390
|
-
return {
|
|
391
|
-
decision: null,
|
|
392
|
-
patterns: compactLocalCollection([]),
|
|
393
|
-
confirmed_regions: compactLocalCollection(confirmedRegions),
|
|
394
|
-
exclusions: {
|
|
395
|
-
total: null,
|
|
396
|
-
included: false,
|
|
397
|
-
details_available: false
|
|
398
|
-
},
|
|
399
|
-
hotspot_analysis: hotspotAnalysis ? {
|
|
400
|
-
total_builds_analyzed: hotspotAnalysis.total_builds_analyzed ?? null,
|
|
401
|
-
confidence: hotspotAnalysis.confidence ?? null,
|
|
402
|
-
confidence_score: hotspotAnalysis.confidence_score ?? null,
|
|
403
|
-
data_source: hotspotAnalysis.data_source ?? null,
|
|
404
|
-
coverage: hotspotAnalysis.coverage ?? null,
|
|
405
|
-
confirmed_region_coverage: hotspotAnalysis.confirmed_region_coverage ?? null
|
|
406
|
-
} : null
|
|
407
|
-
};
|
|
408
|
-
}
|
|
409
338
|
function createLocalSnapshotRevision(snapshot) {
|
|
410
339
|
let revisionInput = {
|
|
411
340
|
serverInfo: snapshot.serverInfo,
|
|
412
341
|
reportData: snapshot.reportData,
|
|
413
342
|
comparisonDetails: snapshot.comparisonDetails,
|
|
414
|
-
baselineMetadata: snapshot.baselineMetadata
|
|
415
|
-
hotspotFile: snapshot.hotspotFile,
|
|
416
|
-
regionFile: snapshot.regionFile
|
|
343
|
+
baselineMetadata: snapshot.baselineMetadata
|
|
417
344
|
};
|
|
418
345
|
return createHash('sha256').update(JSON.stringify(revisionInput)).digest('base64url');
|
|
419
346
|
}
|
|
420
347
|
function buildReviewSummary(comparisons = []) {
|
|
421
|
-
let approved = comparisons.filter(comparison =>
|
|
422
|
-
let rejected = comparisons.filter(comparison =>
|
|
423
|
-
let pending = comparisons.filter(comparison =>
|
|
348
|
+
let approved = comparisons.filter(comparison => mapReviewState(comparison.status) === 'approved').length;
|
|
349
|
+
let rejected = comparisons.filter(comparison => mapReviewState(comparison.status) === 'rejected').length;
|
|
350
|
+
let pending = comparisons.filter(comparison => mapReviewState(comparison.status) === 'pending').length;
|
|
424
351
|
return {
|
|
425
352
|
total: comparisons.length,
|
|
426
353
|
pending,
|
|
@@ -457,12 +384,8 @@ export function createLocalWorkspaceContextProvider(options = {}, deps = {}) {
|
|
|
457
384
|
serverInfo: readJson(join(vizzlyDir, 'server.json')),
|
|
458
385
|
reportData: normalizeReportData(readJson(join(vizzlyDir, 'report-data.json')) || createEmptyReportData()),
|
|
459
386
|
comparisonDetails: readJson(join(vizzlyDir, 'comparison-details.json')) || {},
|
|
460
|
-
baselineMetadata: readJson(join(vizzlyDir, 'baselines', 'metadata.json'))
|
|
461
|
-
hotspotFile: readJson(join(vizzlyDir, 'hotspots.json')),
|
|
462
|
-
regionFile: readJson(join(vizzlyDir, 'regions.json'))
|
|
387
|
+
baselineMetadata: readJson(join(vizzlyDir, 'baselines', 'metadata.json'))
|
|
463
388
|
};
|
|
464
|
-
snapshotCache.hotspots = snapshotCache.hotspotFile?.hotspots || null;
|
|
465
|
-
snapshotCache.regions = snapshotCache.regionFile?.regions || null;
|
|
466
389
|
return snapshotCache;
|
|
467
390
|
}
|
|
468
391
|
function isAvailable(snapshot = loadSnapshot()) {
|
|
@@ -486,7 +409,7 @@ export function createLocalWorkspaceContextProvider(options = {}, deps = {}) {
|
|
|
486
409
|
return Boolean(findComparison(snapshot, target));
|
|
487
410
|
}
|
|
488
411
|
if (command === 'screenshot') {
|
|
489
|
-
return Boolean(snapshot.reportData.comparisons.some(comparison => (comparison.originalName || comparison.name) === target)
|
|
412
|
+
return Boolean(snapshot.reportData.comparisons.some(comparison => (comparison.originalName || comparison.name) === target));
|
|
490
413
|
}
|
|
491
414
|
if (command === 'review-queue') {
|
|
492
415
|
return snapshot.reportData.comparisons.length > 0;
|
|
@@ -572,7 +495,7 @@ export function createLocalWorkspaceContextProvider(options = {}, deps = {}) {
|
|
|
572
495
|
throw createLocalWorkspaceError(`Local workspace context is only available for the active local build (${resolvedBuild.id})`);
|
|
573
496
|
}
|
|
574
497
|
let mappedComparisons = snapshot.reportData.comparisons.map(comparison => mapLocalComparison(snapshot, comparison));
|
|
575
|
-
let mappedScreenshots = mappedComparisons.map(comparison =>
|
|
498
|
+
let mappedScreenshots = mappedComparisons.map(comparison => formatLocalScreenshot(comparison.screenshot, comparison.baseline));
|
|
576
499
|
let reviewSummary = buildReviewSummary(snapshot.reportData.comparisons);
|
|
577
500
|
let reviewState = buildReviewState(resolvedBuild, reviewSummary);
|
|
578
501
|
let context = {
|
|
@@ -612,7 +535,7 @@ export function createLocalWorkspaceContextProvider(options = {}, deps = {}) {
|
|
|
612
535
|
let cursorQuery = `${query.details}:${limit}`;
|
|
613
536
|
let cursor = readLocalCursor(query.cursor, 'build_context', resolvedBuild.id, cursorQuery, revision, ['evidence']);
|
|
614
537
|
let includeDiffs = query.details === 'diffs';
|
|
615
|
-
let evidence = mappedComparisons.map(comparison =>
|
|
538
|
+
let evidence = mappedComparisons.map(comparison => formatLocalEvidence(comparison, includeDiffs));
|
|
616
539
|
return {
|
|
617
540
|
resource: 'build_context',
|
|
618
541
|
source: LOCAL_CONTEXT_SOURCE,
|
|
@@ -623,14 +546,6 @@ export function createLocalWorkspaceContextProvider(options = {}, deps = {}) {
|
|
|
623
546
|
summary: context.summary,
|
|
624
547
|
preview: null,
|
|
625
548
|
signature_properties: [],
|
|
626
|
-
dynamic_regions: {
|
|
627
|
-
exclusions: {
|
|
628
|
-
total: null,
|
|
629
|
-
included: false,
|
|
630
|
-
details_available: false
|
|
631
|
-
},
|
|
632
|
-
item_details_included: false
|
|
633
|
-
},
|
|
634
549
|
evidence: createLocalPage(evidence, {
|
|
635
550
|
limit,
|
|
636
551
|
offset: cursor?.offset || 0,
|
|
@@ -664,9 +579,7 @@ export function createLocalWorkspaceContextProvider(options = {}, deps = {}) {
|
|
|
664
579
|
comparison: mappedComparison,
|
|
665
580
|
history: {
|
|
666
581
|
similar_by_fingerprint: [],
|
|
667
|
-
recent_by_name: history
|
|
668
|
-
hotspot_analysis: buildHotspotAnalysis(snapshot, comparisonName, snapshot.comparisonDetails[comparison.id] || {}),
|
|
669
|
-
confirmed_regions: mergeConfirmedRegions(snapshot, comparisonName, snapshot.comparisonDetails[comparison.id] || {})
|
|
582
|
+
recent_by_name: history
|
|
670
583
|
},
|
|
671
584
|
review: {
|
|
672
585
|
review_summary: {
|
|
@@ -699,8 +612,7 @@ export function createLocalWorkspaceContextProvider(options = {}, deps = {}) {
|
|
|
699
612
|
scope: context.scope,
|
|
700
613
|
build: context.build,
|
|
701
614
|
signature_properties: [],
|
|
702
|
-
comparison:
|
|
703
|
-
dynamic_regions: createLocalDynamicRegions(context.comparison),
|
|
615
|
+
comparison: formatLocalFocusedComparison(context.comparison, query.details === 'diffs'),
|
|
704
616
|
history: {
|
|
705
617
|
active_stream: activeStream || null,
|
|
706
618
|
similar_by_fingerprint: createLocalPage([], {
|
|
@@ -712,7 +624,7 @@ export function createLocalWorkspaceContextProvider(options = {}, deps = {}) {
|
|
|
712
624
|
stream: 'similar_by_fingerprint',
|
|
713
625
|
revision
|
|
714
626
|
}),
|
|
715
|
-
recent_by_name: createLocalPage(history.map(
|
|
627
|
+
recent_by_name: createLocalPage(history.map(formatLocalHistoryItem), {
|
|
716
628
|
limit,
|
|
717
629
|
offset: recentOffset,
|
|
718
630
|
resource: 'comparison_context',
|
|
@@ -735,7 +647,7 @@ export function createLocalWorkspaceContextProvider(options = {}, deps = {}) {
|
|
|
735
647
|
function getScreenshotContext(screenshotName) {
|
|
736
648
|
let snapshot = loadSnapshot();
|
|
737
649
|
let matches = snapshot.reportData.comparisons.filter(comparison => (comparison.originalName || comparison.name) === screenshotName).map(comparison => mapLocalComparison(snapshot, comparison));
|
|
738
|
-
if (matches.length === 0
|
|
650
|
+
if (matches.length === 0) {
|
|
739
651
|
throw createLocalWorkspaceError(`No local screenshot context found for "${screenshotName}"`);
|
|
740
652
|
}
|
|
741
653
|
return {
|
|
@@ -745,8 +657,6 @@ export function createLocalWorkspaceContextProvider(options = {}, deps = {}) {
|
|
|
745
657
|
screenshot: {
|
|
746
658
|
name: screenshotName
|
|
747
659
|
},
|
|
748
|
-
hotspot_analysis: buildHotspotAnalysis(snapshot, screenshotName),
|
|
749
|
-
confirmed_regions: mergeConfirmedRegions(snapshot, screenshotName),
|
|
750
660
|
history: {
|
|
751
661
|
recent_comparisons: matches
|
|
752
662
|
}
|