@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.
Files changed (34) hide show
  1. package/README.md +8 -3
  2. package/dist/api/endpoints.js +1 -46
  3. package/dist/api/index.js +1 -1
  4. package/dist/cli.js +5 -5
  5. package/dist/commands/api.js +3 -3
  6. package/dist/commands/builds.js +12 -15
  7. package/dist/commands/comparisons.js +7 -9
  8. package/dist/commands/context.js +10 -20
  9. package/dist/commands/init.js +62 -13
  10. package/dist/commands/review.js +122 -35
  11. package/dist/commands/run.js +1 -3
  12. package/dist/commands/status.js +35 -79
  13. package/dist/commands/upload.js +1 -1
  14. package/dist/context/local-workspace-provider.js +36 -126
  15. package/dist/reporter/reporter-bundle.iife.js +49 -81
  16. package/dist/server/handlers/tdd-handler.js +2 -7
  17. package/dist/server/routers/dashboard.js +5 -1
  18. package/dist/tdd/runtime-context.js +0 -2
  19. package/dist/tdd/services/comparison-service.js +36 -76
  20. package/dist/tdd/tdd-service.js +7 -205
  21. package/dist/types/index.d.ts +1 -1
  22. package/dist/uploader/core.js +7 -8
  23. package/dist/utils/visual-context-normalizers.js +86 -139
  24. package/package.json +4 -4
  25. package/skills/vizzly/SKILL.md +42 -52
  26. package/skills/vizzly/references/cli-context.md +49 -52
  27. package/skills/vizzly/references/dynamic-content.md +18 -29
  28. package/skills/vizzly/references/sdks.md +20 -28
  29. package/skills/vizzly/references/setup-ci.md +18 -42
  30. package/dist/tdd/core/hotspot-coverage.js +0 -112
  31. package/dist/tdd/core/region-coverage.js +0 -203
  32. package/dist/tdd/metadata/hotspot-metadata.js +0 -93
  33. package/dist/tdd/metadata/region-metadata.js +0 -56
  34. 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 mapApprovalStatus(status) {
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
- approval_status: snapshot.serverInfo ? 'pending' : 'approved',
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
- approval_status: metadata.buildInfo?.approvalStatus || 'approved',
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.approval_status === 'pending') {
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 approvalStatus = mapApprovalStatus(comparison.status);
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
- approval_status: approvalStatus,
227
- needs_review: approvalStatus === 'pending' && ['changed', 'new'].includes(result),
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 projectLocalScreenshot(screenshot, baseline) {
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 projectLocalDiff(diff, includeDiffs) {
310
- let projected = {
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
- projected.regions = diff?.regions || [];
321
- projected.cluster_metadata = diff?.cluster_metadata ?? null;
322
- projected.fingerprint_data = diff?.fingerprint_data ?? null;
323
- projected.diff_lines = diff?.diff_lines ?? [];
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 projected;
276
+ return formatted;
326
277
  }
327
- function projectLocalEvidence(comparison, includeDiffs) {
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
- approval_status: comparison.approval_status,
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: projectLocalScreenshot(comparison.screenshot, comparison.baseline),
342
- diff: projectLocalDiff(comparison.diff, includeDiffs)
292
+ screenshot: formatLocalScreenshot(comparison.screenshot, comparison.baseline),
293
+ diff: formatLocalDiff(comparison.diff, includeDiffs)
343
294
  };
344
295
  }
345
- function projectLocalFocusedComparison(comparison, includeDiffs) {
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: projectLocalDiff(comparison.diff, false),
308
+ diff: formatLocalDiff(comparison.diff, false),
358
309
  analysis: summaryAnalysis
359
310
  };
360
311
  }
361
- function projectLocalHistoryItem(comparison) {
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: projectLocalScreenshot(comparison.screenshot, comparison.baseline),
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 => mapApprovalStatus(comparison.status) === 'approved').length;
422
- let rejected = comparisons.filter(comparison => mapApprovalStatus(comparison.status) === 'rejected').length;
423
- let pending = comparisons.filter(comparison => mapApprovalStatus(comparison.status) === 'pending').length;
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) || snapshot.regions?.[target] || snapshot.hotspots?.[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 => projectLocalScreenshot(comparison.screenshot, comparison.baseline));
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 => projectLocalEvidence(comparison, includeDiffs));
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: projectLocalFocusedComparison(context.comparison, query.details === 'diffs'),
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(projectLocalHistoryItem), {
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 && !snapshot.regions?.[screenshotName] && !snapshot.hotspots?.[screenshotName]) {
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
  }