@vizzly-testing/cli 0.21.2 → 0.22.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.
@@ -0,0 +1,558 @@
1
+ import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
+ import { renderToString } from "react-dom/server";
3
+ const statusConfig = {
4
+ failed: {
5
+ label: "Changed",
6
+ borderClass: "border-l-red-500",
7
+ badgeClass: "bg-red-500/15 text-red-400 ring-red-500/30",
8
+ dotClass: "bg-red-500",
9
+ sectionTitle: "Visual Changes",
10
+ sectionIcon: "◐"
11
+ },
12
+ new: {
13
+ label: "New",
14
+ borderClass: "border-l-blue-500",
15
+ badgeClass: "bg-blue-500/15 text-blue-400 ring-blue-500/30",
16
+ dotClass: "bg-blue-500",
17
+ sectionTitle: "New Screenshots",
18
+ sectionIcon: "+"
19
+ },
20
+ passed: {
21
+ label: "Passed",
22
+ borderClass: "border-l-emerald-500",
23
+ badgeClass: "bg-emerald-500/15 text-emerald-400 ring-emerald-500/30",
24
+ dotClass: "bg-emerald-500",
25
+ sectionTitle: "Passed",
26
+ sectionIcon: "✓"
27
+ },
28
+ error: {
29
+ label: "Error",
30
+ borderClass: "border-l-orange-500",
31
+ badgeClass: "bg-orange-500/15 text-orange-400 ring-orange-500/30",
32
+ dotClass: "bg-orange-500",
33
+ sectionTitle: "Errors",
34
+ sectionIcon: "!"
35
+ }
36
+ };
37
+ function StatusBadge({ status }) {
38
+ let config = statusConfig[status] || statusConfig.error;
39
+ return /* @__PURE__ */ jsxs(
40
+ "span",
41
+ {
42
+ className: `inline-flex items-center gap-1.5 px-2 py-0.5 rounded-full text-xs font-medium ring-1 ring-inset ${config.badgeClass}`,
43
+ children: [
44
+ /* @__PURE__ */ jsx("span", { className: `w-1.5 h-1.5 rounded-full ${config.dotClass}` }),
45
+ config.label
46
+ ]
47
+ }
48
+ );
49
+ }
50
+ function DiffBadge({ percentage }) {
51
+ if (percentage === void 0 || percentage === null || percentage === 0)
52
+ return null;
53
+ return /* @__PURE__ */ jsxs("span", { className: "font-mono text-xs text-red-400/80 tabular-nums", children: [
54
+ percentage.toFixed(2),
55
+ "%"
56
+ ] });
57
+ }
58
+ function MetaInfo({ properties }) {
59
+ if (!properties) return null;
60
+ let { viewport_width, viewport_height, browser } = properties;
61
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-xs text-slate-500", children: [
62
+ viewport_width && viewport_height && /* @__PURE__ */ jsxs("span", { className: "font-mono tabular-nums", children: [
63
+ viewport_width,
64
+ "×",
65
+ viewport_height
66
+ ] }),
67
+ browser && /* @__PURE__ */ jsxs(Fragment, { children: [
68
+ /* @__PURE__ */ jsx("span", { className: "text-slate-600", children: "·" }),
69
+ /* @__PURE__ */ jsx("span", { className: "capitalize", children: browser })
70
+ ] })
71
+ ] });
72
+ }
73
+ function FailedComparison({ comparison, isEven }) {
74
+ let { name, status, current, baseline, diff, diffPercentage, properties } = comparison;
75
+ let config = statusConfig[status] || statusConfig.failed;
76
+ return /* @__PURE__ */ jsxs("details", { className: "group", children: [
77
+ /* @__PURE__ */ jsxs(
78
+ "summary",
79
+ {
80
+ className: `
81
+ flex items-center gap-4 p-4 cursor-pointer
82
+ ${isEven ? "bg-slate-800/30" : "bg-slate-800/50"}
83
+ hover:bg-slate-700/50
84
+ border-l-4 ${config.borderClass}
85
+ rounded-r-lg transition-all duration-150
86
+ list-none [&::-webkit-details-marker]:hidden
87
+ focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-amber-500/50 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-900
88
+ `,
89
+ children: [
90
+ /* @__PURE__ */ jsxs("div", { className: "relative w-16 h-10 flex-shrink-0 rounded overflow-hidden bg-slate-900", children: [
91
+ (current || baseline) && /* @__PURE__ */ jsx(
92
+ "img",
93
+ {
94
+ src: current || baseline,
95
+ alt: "",
96
+ className: "w-full h-full object-cover object-top"
97
+ }
98
+ ),
99
+ diff && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-red-500/20 flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "w-2 h-2 bg-red-500 rounded-full animate-pulse" }) })
100
+ ] }),
101
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
102
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
103
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-white truncate", children: name }),
104
+ /* @__PURE__ */ jsx(DiffBadge, { percentage: diffPercentage })
105
+ ] }),
106
+ /* @__PURE__ */ jsx(MetaInfo, { properties })
107
+ ] }),
108
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
109
+ /* @__PURE__ */ jsx(StatusBadge, { status }),
110
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-slate-500 group-hover:text-slate-400 transition-colors", children: [
111
+ /* @__PURE__ */ jsx("span", { className: "text-xs hidden sm:inline opacity-0 group-hover:opacity-100 transition-opacity", children: "Details" }),
112
+ /* @__PURE__ */ jsx(
113
+ "svg",
114
+ {
115
+ className: "w-5 h-5 transition-transform duration-200 group-open:rotate-180",
116
+ fill: "none",
117
+ viewBox: "0 0 24 24",
118
+ stroke: "currentColor",
119
+ "aria-hidden": "true",
120
+ children: /* @__PURE__ */ jsx(
121
+ "path",
122
+ {
123
+ strokeLinecap: "round",
124
+ strokeLinejoin: "round",
125
+ strokeWidth: 2,
126
+ d: "M19 9l-7 7-7-7"
127
+ }
128
+ )
129
+ }
130
+ )
131
+ ] })
132
+ ] })
133
+ ]
134
+ }
135
+ ),
136
+ /* @__PURE__ */ jsxs("div", { className: "mt-1 p-4 bg-slate-800/20 rounded-lg border border-slate-700/30 animate-[fadeIn_150ms_ease-out]", children: [
137
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 lg:grid-cols-2 gap-4", children: [
138
+ baseline && /* @__PURE__ */ jsxs("div", { children: [
139
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mb-2", children: [
140
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-slate-400 uppercase tracking-wider", children: "Baseline" }),
141
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-slate-600", children: "Expected" })
142
+ ] }),
143
+ /* @__PURE__ */ jsx(
144
+ "a",
145
+ {
146
+ href: baseline,
147
+ target: "_blank",
148
+ rel: "noopener noreferrer",
149
+ className: "block rounded-lg overflow-hidden border border-slate-700/50 hover:border-slate-600 transition-colors",
150
+ children: /* @__PURE__ */ jsx(
151
+ "img",
152
+ {
153
+ src: baseline,
154
+ alt: `${name} baseline`,
155
+ className: "w-full"
156
+ }
157
+ )
158
+ }
159
+ )
160
+ ] }),
161
+ current && /* @__PURE__ */ jsxs("div", { children: [
162
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mb-2", children: [
163
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-slate-400 uppercase tracking-wider", children: "Current" }),
164
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-slate-600", children: "Actual" })
165
+ ] }),
166
+ /* @__PURE__ */ jsx(
167
+ "a",
168
+ {
169
+ href: current,
170
+ target: "_blank",
171
+ rel: "noopener noreferrer",
172
+ className: "block rounded-lg overflow-hidden border border-slate-700/50 hover:border-slate-600 transition-colors",
173
+ children: /* @__PURE__ */ jsx("img", { src: current, alt: `${name} current`, className: "w-full" })
174
+ }
175
+ )
176
+ ] })
177
+ ] }),
178
+ diff && /* @__PURE__ */ jsxs("div", { className: "mt-4 pt-4 border-t border-slate-700/30", children: [
179
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mb-2", children: [
180
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-red-400 uppercase tracking-wider", children: "Difference" }),
181
+ diffPercentage > 0 && /* @__PURE__ */ jsxs("span", { className: "text-xs text-red-400/60 font-mono", children: [
182
+ diffPercentage.toFixed(2),
183
+ "% of pixels changed"
184
+ ] })
185
+ ] }),
186
+ /* @__PURE__ */ jsx(
187
+ "a",
188
+ {
189
+ href: diff,
190
+ target: "_blank",
191
+ rel: "noopener noreferrer",
192
+ className: "block rounded-lg overflow-hidden border border-red-500/30 hover:border-red-500/50 transition-colors max-w-2xl",
193
+ children: /* @__PURE__ */ jsx("img", { src: diff, alt: `${name} diff`, className: "w-full" })
194
+ }
195
+ )
196
+ ] })
197
+ ] })
198
+ ] });
199
+ }
200
+ function NewComparison({ comparison, isEven }) {
201
+ let { name, current, baseline, properties } = comparison;
202
+ let imageSrc = current || baseline;
203
+ return /* @__PURE__ */ jsxs("details", { className: "group", children: [
204
+ /* @__PURE__ */ jsxs(
205
+ "summary",
206
+ {
207
+ className: `
208
+ flex items-center gap-4 p-4 cursor-pointer
209
+ ${isEven ? "bg-slate-800/30" : "bg-slate-800/50"}
210
+ hover:bg-slate-700/50
211
+ border-l-4 border-l-blue-500
212
+ rounded-r-lg transition-all duration-150
213
+ list-none [&::-webkit-details-marker]:hidden
214
+ focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-amber-500/50 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-900
215
+ `,
216
+ children: [
217
+ /* @__PURE__ */ jsx("div", { className: "w-16 h-10 flex-shrink-0 rounded overflow-hidden bg-slate-900", children: imageSrc && /* @__PURE__ */ jsx(
218
+ "img",
219
+ {
220
+ src: imageSrc,
221
+ alt: "",
222
+ className: "w-full h-full object-cover object-top"
223
+ }
224
+ ) }),
225
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
226
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-white truncate block", children: name }),
227
+ /* @__PURE__ */ jsx(MetaInfo, { properties })
228
+ ] }),
229
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
230
+ /* @__PURE__ */ jsx(StatusBadge, { status: "new" }),
231
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-slate-500 group-hover:text-slate-400 transition-colors", children: [
232
+ /* @__PURE__ */ jsx("span", { className: "text-xs hidden sm:inline opacity-0 group-hover:opacity-100 transition-opacity", children: "Preview" }),
233
+ /* @__PURE__ */ jsx(
234
+ "svg",
235
+ {
236
+ className: "w-5 h-5 transition-transform duration-200 group-open:rotate-180",
237
+ fill: "none",
238
+ viewBox: "0 0 24 24",
239
+ stroke: "currentColor",
240
+ "aria-hidden": "true",
241
+ children: /* @__PURE__ */ jsx(
242
+ "path",
243
+ {
244
+ strokeLinecap: "round",
245
+ strokeLinejoin: "round",
246
+ strokeWidth: 2,
247
+ d: "M19 9l-7 7-7-7"
248
+ }
249
+ )
250
+ }
251
+ )
252
+ ] })
253
+ ] })
254
+ ]
255
+ }
256
+ ),
257
+ /* @__PURE__ */ jsxs("div", { className: "mt-1 p-4 bg-slate-800/20 rounded-lg border border-slate-700/30 animate-[fadeIn_150ms_ease-out]", children: [
258
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mb-2", children: [
259
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-blue-400 uppercase tracking-wider", children: "New Screenshot" }),
260
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-slate-600", children: "No baseline to compare" })
261
+ ] }),
262
+ imageSrc && /* @__PURE__ */ jsx(
263
+ "a",
264
+ {
265
+ href: imageSrc,
266
+ target: "_blank",
267
+ rel: "noopener noreferrer",
268
+ className: "block rounded-lg overflow-hidden border border-blue-500/30 hover:border-blue-500/50 transition-colors max-w-2xl",
269
+ children: /* @__PURE__ */ jsx("img", { src: imageSrc, alt: name, className: "w-full" })
270
+ }
271
+ )
272
+ ] })
273
+ ] });
274
+ }
275
+ function PassedComparison({ comparison }) {
276
+ let { name, properties } = comparison;
277
+ return /* @__PURE__ */ jsxs(
278
+ "div",
279
+ {
280
+ className: `
281
+ flex items-center gap-3 px-4 py-2.5
282
+ bg-slate-800/20 hover:bg-slate-800/30
283
+ border-l-4 border-l-emerald-500/50
284
+ rounded-r-lg transition-colors
285
+ `,
286
+ children: [
287
+ /* @__PURE__ */ jsx("div", { className: "w-5 h-5 rounded-full bg-emerald-500/20 flex items-center justify-center flex-shrink-0", children: /* @__PURE__ */ jsx(
288
+ "svg",
289
+ {
290
+ className: "w-3 h-3 text-emerald-400",
291
+ fill: "currentColor",
292
+ viewBox: "0 0 20 20",
293
+ "aria-hidden": "true",
294
+ children: /* @__PURE__ */ jsx(
295
+ "path",
296
+ {
297
+ fillRule: "evenodd",
298
+ d: "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z",
299
+ clipRule: "evenodd"
300
+ }
301
+ )
302
+ }
303
+ ) }),
304
+ /* @__PURE__ */ jsx("span", { className: "flex-1 text-sm text-slate-300 truncate", children: name }),
305
+ /* @__PURE__ */ jsx(MetaInfo, { properties })
306
+ ]
307
+ }
308
+ );
309
+ }
310
+ function SectionHeader({ title, count, icon, colorClass }) {
311
+ if (count === 0) return null;
312
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 mb-3", children: [
313
+ /* @__PURE__ */ jsx("span", { className: `text-lg font-mono ${colorClass}`, children: icon }),
314
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-slate-300 uppercase tracking-wider", children: title }),
315
+ /* @__PURE__ */ jsxs("span", { className: "text-sm text-slate-500 font-mono", children: [
316
+ "(",
317
+ count,
318
+ ")"
319
+ ] })
320
+ ] });
321
+ }
322
+ function SummaryBar({ stats }) {
323
+ let hasIssues = stats.failed > 0 || stats.new > 0;
324
+ return /* @__PURE__ */ jsxs(
325
+ "div",
326
+ {
327
+ className: `
328
+ flex flex-wrap items-center gap-6 p-4 rounded-xl mb-8
329
+ ${hasIssues ? "bg-slate-800/60 border border-slate-700/50" : "bg-emerald-500/10 border border-emerald-500/20"}
330
+ `,
331
+ children: [
332
+ /* @__PURE__ */ jsx("div", { className: "flex items-center gap-3", children: hasIssues ? /* @__PURE__ */ jsxs(Fragment, { children: [
333
+ /* @__PURE__ */ jsx("div", { className: "w-10 h-10 rounded-full bg-amber-500/20 flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "text-amber-400 text-lg", children: "◐" }) }),
334
+ /* @__PURE__ */ jsxs("div", { children: [
335
+ /* @__PURE__ */ jsx("div", { className: "text-white font-semibold", children: "Review Required" }),
336
+ /* @__PURE__ */ jsxs("div", { className: "text-sm text-slate-400", children: [
337
+ stats.failed + (stats.new || 0),
338
+ " items need attention"
339
+ ] })
340
+ ] })
341
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
342
+ /* @__PURE__ */ jsx("div", { className: "w-10 h-10 rounded-full bg-emerald-500/20 flex items-center justify-center", children: /* @__PURE__ */ jsx(
343
+ "svg",
344
+ {
345
+ className: "w-5 h-5 text-emerald-400",
346
+ fill: "currentColor",
347
+ viewBox: "0 0 20 20",
348
+ "aria-hidden": "true",
349
+ children: /* @__PURE__ */ jsx(
350
+ "path",
351
+ {
352
+ fillRule: "evenodd",
353
+ d: "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z",
354
+ clipRule: "evenodd"
355
+ }
356
+ )
357
+ }
358
+ ) }),
359
+ /* @__PURE__ */ jsxs("div", { children: [
360
+ /* @__PURE__ */ jsx("div", { className: "text-white font-semibold", children: "All Tests Passed" }),
361
+ /* @__PURE__ */ jsx("div", { className: "text-sm text-slate-400", children: "No visual changes detected" })
362
+ ] })
363
+ ] }) }),
364
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-6 ml-auto", children: [
365
+ /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
366
+ /* @__PURE__ */ jsx("div", { className: "text-2xl font-bold text-white tabular-nums", children: stats.total }),
367
+ /* @__PURE__ */ jsx("div", { className: "text-xs text-slate-500 uppercase tracking-wider", children: "Total" })
368
+ ] }),
369
+ /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
370
+ /* @__PURE__ */ jsx("div", { className: "text-2xl font-bold text-emerald-400 tabular-nums", children: stats.passed }),
371
+ /* @__PURE__ */ jsx("div", { className: "text-xs text-slate-500 uppercase tracking-wider", children: "Passed" })
372
+ ] }),
373
+ stats.failed > 0 && /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
374
+ /* @__PURE__ */ jsx("div", { className: "text-2xl font-bold text-red-400 tabular-nums", children: stats.failed }),
375
+ /* @__PURE__ */ jsx("div", { className: "text-xs text-slate-500 uppercase tracking-wider", children: "Changed" })
376
+ ] }),
377
+ (stats.new || 0) > 0 && /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
378
+ /* @__PURE__ */ jsx("div", { className: "text-2xl font-bold text-blue-400 tabular-nums", children: stats.new }),
379
+ /* @__PURE__ */ jsx("div", { className: "text-xs text-slate-500 uppercase tracking-wider", children: "New" })
380
+ ] })
381
+ ] })
382
+ ]
383
+ }
384
+ );
385
+ }
386
+ function formatTimestamp(timestamp) {
387
+ if (!timestamp) return null;
388
+ let date = new Date(timestamp);
389
+ return date.toLocaleDateString(void 0, {
390
+ year: "numeric",
391
+ month: "short",
392
+ day: "numeric",
393
+ hour: "2-digit",
394
+ minute: "2-digit"
395
+ });
396
+ }
397
+ function Header({ timestamp }) {
398
+ return /* @__PURE__ */ jsx("header", { className: "bg-slate-950/80 backdrop-blur-sm border-b border-slate-800/60 sticky top-0 z-10", children: /* @__PURE__ */ jsxs("div", { className: "max-w-6xl mx-auto px-6 py-4 flex items-center justify-between", children: [
399
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
400
+ /* @__PURE__ */ jsx("div", { className: "w-9 h-9 bg-gradient-to-br from-amber-400 to-amber-600 rounded-lg flex items-center justify-center shadow-lg shadow-amber-500/20", children: /* @__PURE__ */ jsxs(
401
+ "svg",
402
+ {
403
+ className: "w-5 h-5 text-slate-900",
404
+ viewBox: "0 0 24 24",
405
+ fill: "currentColor",
406
+ "aria-hidden": "true",
407
+ children: [
408
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "3" }),
409
+ /* @__PURE__ */ jsx("path", { d: "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z" })
410
+ ]
411
+ }
412
+ ) }),
413
+ /* @__PURE__ */ jsxs("div", { children: [
414
+ /* @__PURE__ */ jsx("div", { className: "text-lg font-semibold text-white tracking-tight", children: "Vizzly" }),
415
+ /* @__PURE__ */ jsx("div", { className: "text-xs text-slate-500", children: "Visual Test Report" })
416
+ ] })
417
+ ] }),
418
+ timestamp && /* @__PURE__ */ jsx("div", { className: "text-xs text-slate-500 tabular-nums", children: formatTimestamp(timestamp) })
419
+ ] }) });
420
+ }
421
+ function StaticReportView({ reportData }) {
422
+ if (!reportData || !reportData.comparisons) {
423
+ return /* @__PURE__ */ jsx("div", { className: "min-h-screen bg-slate-900 text-white flex items-center justify-center", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
424
+ /* @__PURE__ */ jsx("div", { className: "text-xl font-medium mb-2", children: "No Report Data" }),
425
+ /* @__PURE__ */ jsx("p", { className: "text-slate-400", children: "No comparison data available for this report." })
426
+ ] }) });
427
+ }
428
+ let { comparisons } = reportData;
429
+ let stats = {
430
+ total: comparisons.length,
431
+ passed: comparisons.filter((c) => c.status === "passed").length,
432
+ failed: comparisons.filter((c) => c.status === "failed").length,
433
+ new: comparisons.filter((c) => c.status === "new").length,
434
+ error: comparisons.filter((c) => c.status === "error").length
435
+ };
436
+ let failed = comparisons.filter((c) => c.status === "failed");
437
+ let newItems = comparisons.filter((c) => c.status === "new");
438
+ let passed = comparisons.filter((c) => c.status === "passed");
439
+ let errors = comparisons.filter((c) => c.status === "error");
440
+ return /* @__PURE__ */ jsxs("div", { className: "min-h-screen bg-slate-900 text-white", children: [
441
+ /* @__PURE__ */ jsx(Header, { timestamp: reportData.timestamp }),
442
+ /* @__PURE__ */ jsxs("main", { className: "max-w-6xl mx-auto px-6 py-8", children: [
443
+ /* @__PURE__ */ jsx(SummaryBar, { stats }),
444
+ failed.length > 0 && /* @__PURE__ */ jsxs("section", { className: "mb-8", children: [
445
+ /* @__PURE__ */ jsx(
446
+ SectionHeader,
447
+ {
448
+ title: "Visual Changes",
449
+ count: failed.length,
450
+ icon: "◐",
451
+ colorClass: "text-red-400"
452
+ }
453
+ ),
454
+ /* @__PURE__ */ jsx("div", { className: "space-y-2", children: failed.map((comparison, index) => /* @__PURE__ */ jsx(
455
+ FailedComparison,
456
+ {
457
+ comparison,
458
+ isEven: index % 2 === 0
459
+ },
460
+ comparison.id || comparison.signature || `failed-${index}`
461
+ )) })
462
+ ] }),
463
+ newItems.length > 0 && /* @__PURE__ */ jsxs("section", { className: "mb-8", children: [
464
+ /* @__PURE__ */ jsx(
465
+ SectionHeader,
466
+ {
467
+ title: "New Screenshots",
468
+ count: newItems.length,
469
+ icon: "+",
470
+ colorClass: "text-blue-400"
471
+ }
472
+ ),
473
+ /* @__PURE__ */ jsx("div", { className: "space-y-2", children: newItems.map((comparison, index) => /* @__PURE__ */ jsx(
474
+ NewComparison,
475
+ {
476
+ comparison,
477
+ isEven: index % 2 === 0
478
+ },
479
+ comparison.id || comparison.signature || `new-${index}`
480
+ )) })
481
+ ] }),
482
+ errors.length > 0 && /* @__PURE__ */ jsxs("section", { className: "mb-8", children: [
483
+ /* @__PURE__ */ jsx(
484
+ SectionHeader,
485
+ {
486
+ title: "Errors",
487
+ count: errors.length,
488
+ icon: "!",
489
+ colorClass: "text-orange-400"
490
+ }
491
+ ),
492
+ /* @__PURE__ */ jsx("div", { className: "space-y-2", children: errors.map((comparison, index) => /* @__PURE__ */ jsx(
493
+ FailedComparison,
494
+ {
495
+ comparison,
496
+ isEven: index % 2 === 0
497
+ },
498
+ comparison.id || comparison.signature || `error-${index}`
499
+ )) })
500
+ ] }),
501
+ passed.length > 0 && /* @__PURE__ */ jsx("section", { className: "mb-8", children: /* @__PURE__ */ jsxs("details", { className: "group", children: [
502
+ /* @__PURE__ */ jsx("summary", { className: "cursor-pointer list-none [&::-webkit-details-marker]:hidden", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 mb-3", children: [
503
+ /* @__PURE__ */ jsx("span", { className: "text-lg font-mono text-emerald-400", children: "✓" }),
504
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-slate-300 uppercase tracking-wider", children: "Passed" }),
505
+ /* @__PURE__ */ jsxs("span", { className: "text-sm text-slate-500 font-mono", children: [
506
+ "(",
507
+ passed.length,
508
+ ")"
509
+ ] }),
510
+ /* @__PURE__ */ jsx(
511
+ "svg",
512
+ {
513
+ className: "w-4 h-4 text-slate-500 transition-transform group-open:rotate-180 ml-auto",
514
+ fill: "none",
515
+ viewBox: "0 0 24 24",
516
+ stroke: "currentColor",
517
+ "aria-hidden": "true",
518
+ children: /* @__PURE__ */ jsx(
519
+ "path",
520
+ {
521
+ strokeLinecap: "round",
522
+ strokeLinejoin: "round",
523
+ strokeWidth: 2,
524
+ d: "M19 9l-7 7-7-7"
525
+ }
526
+ )
527
+ }
528
+ )
529
+ ] }) }),
530
+ /* @__PURE__ */ jsx("div", { className: "space-y-1", children: passed.map((comparison, index) => /* @__PURE__ */ jsx(
531
+ PassedComparison,
532
+ {
533
+ comparison
534
+ },
535
+ comparison.id || comparison.signature || `passed-${index}`
536
+ )) })
537
+ ] }) }),
538
+ /* @__PURE__ */ jsx("footer", { className: "mt-12 pt-6 border-t border-slate-800/60 text-center", children: /* @__PURE__ */ jsxs("p", { className: "text-sm text-slate-500", children: [
539
+ "Visual regression report generated by",
540
+ " ",
541
+ /* @__PURE__ */ jsx(
542
+ "a",
543
+ {
544
+ href: "https://vizzly.dev",
545
+ className: "text-amber-400 hover:text-amber-300 transition-colors",
546
+ children: "Vizzly"
547
+ }
548
+ )
549
+ ] }) })
550
+ ] })
551
+ ] });
552
+ }
553
+ function renderStaticReport(reportData) {
554
+ return renderToString(/* @__PURE__ */ jsx(StaticReportView, { reportData }));
555
+ }
556
+ export {
557
+ renderStaticReport
558
+ };
@@ -403,21 +403,21 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
403
403
 
404
404
  // Update comparison in report data file
405
405
  updateComparison(newComparison);
406
+
407
+ // Visual diffs return 200 with status: 'diff' - they're not errors
408
+ // The SDK/user can decide whether to fail tests based on this
406
409
  if (comparison.status === 'failed') {
407
410
  return {
408
- statusCode: 422,
411
+ statusCode: 200,
409
412
  body: {
410
- error: 'Visual difference detected',
411
- details: `Screenshot '${name}' differs from baseline`,
412
- comparison: {
413
- name: comparison.name,
414
- status: comparison.status,
415
- baseline: comparison.baseline,
416
- current: comparison.current,
417
- diff: comparison.diff,
418
- diffPercentage: comparison.diffPercentage,
419
- threshold: comparison.threshold
420
- },
413
+ status: 'diff',
414
+ name: comparison.name,
415
+ message: `Visual difference detected for '${name}'`,
416
+ baseline: comparison.baseline,
417
+ current: comparison.current,
418
+ diff: comparison.diff,
419
+ diffPercentage: comparison.diffPercentage,
420
+ threshold: comparison.threshold,
421
421
  tddMode: true
422
422
  }
423
423
  };
@@ -426,14 +426,11 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
426
426
  return {
427
427
  statusCode: 200,
428
428
  body: {
429
- status: 'success',
430
- message: `Baseline updated for ${name}`,
431
- comparison: {
432
- name: comparison.name,
433
- status: comparison.status,
434
- baseline: comparison.baseline,
435
- current: comparison.current
436
- },
429
+ status: 'baseline-updated',
430
+ name: comparison.name,
431
+ message: `Baseline updated for '${name}'`,
432
+ baseline: comparison.baseline,
433
+ current: comparison.current,
437
434
  tddMode: true
438
435
  }
439
436
  };
@@ -448,15 +445,14 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
448
445
  };
449
446
  }
450
447
 
451
- // Debug output handled by tdd.js event handler
448
+ // Match or new baseline
452
449
  return {
453
450
  statusCode: 200,
454
451
  body: {
455
- success: true,
456
- comparison: {
457
- name: comparison.name,
458
- status: comparison.status
459
- },
452
+ status: comparison.status === 'new' ? 'new' : 'match',
453
+ name: comparison.name,
454
+ baseline: comparison.baseline,
455
+ current: comparison.current,
460
456
  tddMode: true
461
457
  }
462
458
  };