@vizzly-testing/cli 0.34.0 → 0.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -3
- package/dist/api/core.js +2 -1
- package/dist/cli.js +19 -6
- package/dist/client/index.js +62 -21
- package/dist/commands/api.js +9 -3
- package/dist/commands/preview.js +13 -11
- package/dist/commands/project.js +1 -1
- package/dist/commands/run.js +18 -6
- package/dist/commands/tdd-daemon.js +8 -3
- package/dist/commands/tdd.js +24 -4
- package/dist/commands/upload.js +31 -13
- package/dist/reporter/reporter-bundle.iife.js +1 -1
- package/dist/sdk/index.js +23 -9
- package/dist/server/handlers/api-handler.js +3 -2
- package/dist/server/handlers/tdd-handler.js +5 -1
- package/dist/server/routers/screenshot.js +11 -4
- package/dist/services/build-manager.js +6 -1
- package/dist/test-runner/core.js +4 -1
- package/dist/test-runner/operations.js +2 -1
- package/dist/types/client.d.ts +32 -5
- package/dist/types/index.d.ts +33 -5
- package/dist/uploader/core.js +41 -9
- package/dist/uploader/operations.js +19 -17
- package/dist/utils/config-loader.js +12 -6
- package/dist/utils/context.js +46 -76
- package/dist/utils/environment-config.js +20 -0
- package/dist/utils/output.js +5 -5
- package/dist/utils/screenshot-options.js +60 -10
- package/package.json +1 -1
package/dist/commands/upload.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createApiClient as defaultCreateApiClient, finalizeBuild as defaultFinalizeBuild, getTokenContext as defaultGetTokenContext } from '../api/index.js';
|
|
2
|
+
import { CONFIG_DEFAULTS } from '../config/core.js';
|
|
2
3
|
import { createUploader as defaultCreateUploader } from '../uploader/index.js';
|
|
3
4
|
import { getAppBaseUrl } from '../utils/api-url.js';
|
|
4
5
|
import { loadConfig as defaultLoadConfig } from '../utils/config-loader.js';
|
|
@@ -73,12 +74,12 @@ export async function uploadCommand(screenshotsPath, options = {}, globalOptions
|
|
|
73
74
|
});
|
|
74
75
|
let buildId = null;
|
|
75
76
|
let config = null;
|
|
76
|
-
|
|
77
|
+
let uploadStartTime = Date.now();
|
|
77
78
|
try {
|
|
78
79
|
output.info('Starting upload process...');
|
|
79
80
|
|
|
80
81
|
// Load configuration with CLI overrides
|
|
81
|
-
|
|
82
|
+
let allOptions = {
|
|
82
83
|
...globalOptions,
|
|
83
84
|
...options
|
|
84
85
|
};
|
|
@@ -95,11 +96,12 @@ export async function uploadCommand(screenshotsPath, options = {}, globalOptions
|
|
|
95
96
|
}
|
|
96
97
|
|
|
97
98
|
// Collect git metadata if not provided
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
99
|
+
let configuredBuildName = config.build.name && config.build.name !== CONFIG_DEFAULTS.build.name ? config.build.name : undefined;
|
|
100
|
+
let branch = await detectBranch(options.branch || config.build.branch);
|
|
101
|
+
let commit = await detectCommit(options.commit || config.build.commit);
|
|
102
|
+
let message = options.message || config.build.message || (await detectCommitMessage());
|
|
103
|
+
let buildName = await generateBuildNameWithGit(options.buildName || configuredBuildName);
|
|
104
|
+
let pullRequestNumber = detectPullRequestNumber();
|
|
103
105
|
output.info(`Uploading screenshots from: ${screenshotsPath}`);
|
|
104
106
|
if (globalOptions.verbose) {
|
|
105
107
|
output.info('Configuration loaded');
|
|
@@ -119,7 +121,7 @@ export async function uploadCommand(screenshotsPath, options = {}, globalOptions
|
|
|
119
121
|
});
|
|
120
122
|
|
|
121
123
|
// Prepare upload options with progress callback
|
|
122
|
-
|
|
124
|
+
let uploadOptions = {
|
|
123
125
|
screenshotsDir: screenshotsPath,
|
|
124
126
|
buildName,
|
|
125
127
|
branch,
|
|
@@ -127,12 +129,13 @@ export async function uploadCommand(screenshotsPath, options = {}, globalOptions
|
|
|
127
129
|
message,
|
|
128
130
|
environment: config.build.environment,
|
|
129
131
|
threshold: config.comparison.threshold,
|
|
132
|
+
minClusterSize: config.comparison.minClusterSize,
|
|
130
133
|
uploadAll: options.uploadAll || false,
|
|
131
134
|
metadata: options.metadata ? JSON.parse(options.metadata) : {},
|
|
132
135
|
pullRequestNumber,
|
|
133
136
|
parallelId: config.parallelId,
|
|
134
137
|
onProgress: progressData => {
|
|
135
|
-
|
|
138
|
+
let {
|
|
136
139
|
message: progressMessage,
|
|
137
140
|
current,
|
|
138
141
|
total,
|
|
@@ -158,7 +161,7 @@ export async function uploadCommand(screenshotsPath, options = {}, globalOptions
|
|
|
158
161
|
|
|
159
162
|
// Start upload
|
|
160
163
|
output.progress('Starting upload...');
|
|
161
|
-
|
|
164
|
+
let result = await uploader.upload(uploadOptions);
|
|
162
165
|
buildId = result.buildId; // Ensure we have the buildId
|
|
163
166
|
|
|
164
167
|
// Write session for subsequent commands (like preview)
|
|
@@ -263,8 +266,10 @@ export async function uploadCommand(screenshotsPath, options = {}, globalOptions
|
|
|
263
266
|
executionTimeMs
|
|
264
267
|
});
|
|
265
268
|
output.cleanup();
|
|
269
|
+
let success = buildResult.failedComparisons === 0;
|
|
266
270
|
return {
|
|
267
|
-
success
|
|
271
|
+
success,
|
|
272
|
+
exitCode: success ? 0 : 1,
|
|
268
273
|
result
|
|
269
274
|
};
|
|
270
275
|
}
|
|
@@ -283,6 +288,14 @@ export async function uploadCommand(screenshotsPath, options = {}, globalOptions
|
|
|
283
288
|
let waitBuildUrl = buildResult.url || (await buildUrlConstructor(result.buildId, config.apiUrl, config.apiKey, deps));
|
|
284
289
|
output.blank();
|
|
285
290
|
output.labelValue('View', output.link('Results', waitBuildUrl));
|
|
291
|
+
if (buildResult.failedComparisons > 0) {
|
|
292
|
+
output.cleanup();
|
|
293
|
+
return {
|
|
294
|
+
success: false,
|
|
295
|
+
exitCode: 1,
|
|
296
|
+
result
|
|
297
|
+
};
|
|
298
|
+
}
|
|
286
299
|
}
|
|
287
300
|
output.cleanup();
|
|
288
301
|
return {
|
|
@@ -300,11 +313,10 @@ export async function uploadCommand(screenshotsPath, options = {}, globalOptions
|
|
|
300
313
|
message: 'Vizzly API unavailable - upload skipped',
|
|
301
314
|
executionTimeMs: Date.now() - uploadStartTime
|
|
302
315
|
});
|
|
303
|
-
output.cleanup();
|
|
304
316
|
} else {
|
|
305
317
|
output.warn('Vizzly API unavailable - upload skipped. Your tests still ran.');
|
|
306
|
-
output.cleanup();
|
|
307
318
|
}
|
|
319
|
+
output.cleanup();
|
|
308
320
|
return {
|
|
309
321
|
success: true,
|
|
310
322
|
result: {
|
|
@@ -381,6 +393,12 @@ export function validateUploadOptions(screenshotsPath, options) {
|
|
|
381
393
|
errors.push('Threshold must be a non-negative number (CIEDE2000 Delta E)');
|
|
382
394
|
}
|
|
383
395
|
}
|
|
396
|
+
if (options.minClusterSize !== undefined) {
|
|
397
|
+
let minClusterSize = Number(options.minClusterSize);
|
|
398
|
+
if (!Number.isInteger(minClusterSize) || minClusterSize < 1) {
|
|
399
|
+
errors.push('Min cluster size must be a positive integer');
|
|
400
|
+
}
|
|
401
|
+
}
|
|
384
402
|
if (options.batchSize !== undefined) {
|
|
385
403
|
let n = Number(options.batchSize);
|
|
386
404
|
if (!Number.isInteger(n) || n <= 0) {
|
|
@@ -194,7 +194,7 @@ Error generating stack: `+e.message+`
|
|
|
194
194
|
linear-gradient(-45deg, var(--vz-checkerboard-tile) 25%, transparent 25%),
|
|
195
195
|
linear-gradient(45deg, transparent 75%, var(--vz-checkerboard-tile) 75%),
|
|
196
196
|
linear-gradient(-45deg, transparent 75%, var(--vz-checkerboard-tile) 75%)
|
|
197
|
-
`,backgroundSize:`16px 16px`,backgroundPosition:`0 0, 0 8px, 8px -8px, -8px 0px`,backgroundColor:`var(--vz-checkerboard-bg)`};function Oo({comparison:e,viewMode:t=`overlay`,showDiffOverlay:n=!0,showBaseline:r=!0,onToggleBaseline:i,onionSkinPosition:a=50,onOnionSkinChange:o,onDiffToggle:s,disableLoadingOverlay:c=!1,zoom:l=`fit`,className:u=``,showRegions:d=!1}){let[f,p]=(0,N.useState)(new Set),[m,h]=(0,N.useState)(new Map),[g,_]=(0,N.useState)(!1),[v,y]=(0,N.useState)(!1),[b,x]=(0,N.useState)(!1),[S,ee]=(0,N.useState)(!1),[C,w]=(0,N.useState)(1),[te,ne]=(0,N.useState)({width:0,height:0}),T=(0,N.useRef)(null),re=(0,N.useRef)(null);(0,N.useEffect)(()=>{let e=()=>{let e=T.current,t=e?.querySelector(`img`);if(!e||!t?.naturalWidth)return;ne({width:t.naturalWidth,height:t.naturalHeight});let n=e.getBoundingClientRect(),r=n.width-40,i=n.height-40,a=r/t.naturalWidth,o=i/t.naturalHeight;w(Math.min(a,o,1))};e();let t=new window.ResizeObserver(e);T.current&&t.observe(T.current);let n=T.current?.querySelectorAll(`img`)||[];for(let t of n)t.addEventListener(`load`,e);return()=>{t.disconnect();for(let t of n)t.removeEventListener(`load`,e)}},[]);let E=(0,N.useMemo)(()=>{let e=l===`fit`,t=e?C:typeof l==`number`?l:1,n=!e&&t>C;return{isFit:e,scale:t,allowsOverflow:n,containerClass:n?`overflow-auto`:`overflow-hidden`}},[l,C]),D=(0,N.useCallback)(e=>{p(t=>new Set([...t,e]))},[]),O=(0,N.useCallback)(e=>{h(t=>new Map(t).set(e,`loaded`))},[]),k=(0,N.useMemo)(()=>e?{current:Eo(e.current,e.timestamp),baseline:Eo(e.baseline,e.timestamp),diff:Eo(e.diff,e.timestamp)}:{},[e]),A=(0,N.useMemo)(()=>e?{id:e.id||e.signature||`unknown`,name:e.name||e.originalName||`Screenshot`}:null,[e]);return(0,N.useEffect)(()=>{if(!A?.id){_(!1),y(!1),x(!1),ee(!1);return}_(!1),y(!1),x(!1),ee(!1)},[A?.id]),!e||e.status===`new`||e.status===`baseline-created`?(0,P.jsx)(`div`,{className:`h-full ${u}`,children:(0,P.jsxs)(`div`,{ref:T,className:`bg-[var(--vz-elevated)] relative h-full ${E.containerClass}`,style:Do,children:[(0,P.jsx)(`div`,{className:`relative ${E.allowsOverflow?`flex justify-center items-start`:`min-w-full min-h-full flex justify-center items-center`}`,style:{...E.allowsOverflow?{minWidth:te.width*E.scale+40,minHeight:te.height*E.scale+40,padding:`20px`}:{zoom:E.scale,padding:`20px`}},children:(0,P.jsx)(`div`,{ref:re,className:`relative inline-block`,style:E.allowsOverflow?{transform:`scale(${E.scale})`,transformOrigin:`top center`}:{},children:e&&(0,P.jsx)(`img`,{src:k.current,alt:e.name||`New screenshot`,className:`block`,onLoad:()=>O(`current-${A?.id}`),onError:()=>D(`current-${A?.id}`)})})}),!c&&e&&!m.has(`current-${A?.id}`)&&(0,P.jsx)(`div`,{className:`absolute inset-0 bg-[var(--vz-raised)] animate-pulse flex items-center justify-center`,children:(0,P.jsx)(`div`,{className:`text-[var(--text-tertiary)]`,children:`Loading...`})}),e&&f.has(`current-${A?.id}`)&&(0,P.jsx)(`div`,{className:`absolute inset-0 bg-[var(--vz-raised)] flex items-center justify-center`,children:(0,P.jsxs)(`div`,{className:`text-center text-[var(--text-tertiary)]`,children:[(0,P.jsx)(hn,{className:`w-8 h-8 mx-auto mb-2`}),(0,P.jsx)(`div`,{className:`text-sm`,children:`Failed to load image`})]})})]})}):(0,P.jsx)(`div`,{className:`h-full ${u}`,children:(0,P.jsxs)(`div`,{ref:T,className:`bg-[var(--vz-elevated)] relative unified-screenshot-container h-full ${E.containerClass}`,style:Do,children:[(0,P.jsx)(`div`,{className:`relative ${E.allowsOverflow?`flex justify-center items-start`:`min-w-full min-h-full flex justify-center items-center`}`,style:{...E.allowsOverflow?{minWidth:te.width*E.scale+40,minHeight:te.height*E.scale+40,padding:`20px`}:{zoom:E.scale,padding:`20px`}},children:(0,P.jsxs)(`div`,{ref:re,className:`relative inline-block`,style:E.allowsOverflow?{transform:`scale(${E.scale})`,transformOrigin:`top center`}:{},children:[t===`toggle`&&k.baseline?(0,P.jsx)(ja,{baselineImageUrl:k.baseline,currentImageUrl:k.current,baselineImageLoaded:g,setBaselineImageLoaded:_,currentImageLoaded:v,setCurrentImageLoaded:y,baselineImageError:b,setBaselineImageError:x,currentImageError:S,setCurrentImageError:ee,screenshot:A,onImageError:D,onImageLoad:O,imageErrors:f,showBaseline:r,onToggle:i}):t===`onion-skin`&&k.baseline?(0,P.jsx)(ka,{baselineImageUrl:k.baseline,currentImageUrl:k.current,sliderPosition:a,onSliderChange:o,screenshot:A,onImageError:D,onImageLoad:O,imageErrors:f}):(0,P.jsx)(Aa,{baselineImageUrl:k.baseline,currentImageUrl:k.current,diffImageUrl:k.diff,showDiffOverlay:n,screenshot:A,onImageError:D,onImageLoad:O,imageErrors:f,onDiffToggle:s}),d&&e?.confirmedRegions?.length>0&&(0,P.jsx)(Ca,{confirmed:e.confirmedRegions,candidates:[],imageWidth:te.width,imageHeight:te.height,showConfirmed:!0,showCandidates:!1,disabled:!0})]})}),!c&&!m.has(`current-${A?.id}`)&&(0,P.jsx)(`div`,{className:`absolute inset-0 bg-[var(--vz-raised)] animate-pulse flex items-center justify-center`,children:(0,P.jsx)(`div`,{className:`text-[var(--text-tertiary)]`,children:`Loading...`})}),f.has(`current-${A?.id}`)&&(0,P.jsx)(`div`,{className:`absolute inset-0 bg-[var(--vz-raised)] flex items-center justify-center`,children:(0,P.jsxs)(`div`,{className:`text-center text-[var(--text-tertiary)]`,children:[(0,P.jsx)(hn,{className:`w-8 h-8 mx-auto mb-2`}),(0,P.jsx)(`div`,{className:`text-sm`,children:`Failed to load image`})]})})]})})}function G(e){return e?.id||e?.signature||e?.name}function ko(e){switch(e){case`failed`:return`changed`;case`new`:case`baseline-created`:return`new`;case`passed`:return`unchanged`;default:return e}}function Ao(e){return(0,P.jsx)(to,{children:(0,P.jsx)(jo,{...e})})}function jo({comparison:e,comparisons:t=[],onClose:n,onAccept:r,onReject:i,onDelete:a,onNavigate:o}){let[s,c]=(0,N.useState)(So.OVERLAY),[l,u]=(0,N.useState)(!0),[d,f]=(0,N.useState)(50),[p,m]=(0,N.useState)(!0),[h,g]=(0,N.useState)(!1),[_,v]=(0,N.useState)(`needs-review`),[y,b]=(0,N.useState)(!0),[x,S]=(0,N.useState)(!1),{zoom:ee,setZoom:C}=bo(`fit`),{isActive:w}=no(),te=(0,N.useRef)(null),ne=(0,N.useCallback)(()=>{g(e=>!e)},[]),T=(0,N.useMemo)(()=>t.map(e=>({...e,name:e.name||e.originalName||`Unknown`,result:ko(e.status),status:e.status,approval_status:e.userAction===`accepted`?`approved`:e.userAction===`rejected`?`rejected`:e.status===`passed`?`approved`:`pending`,diff_percentage:e.diffPercentage??e.diff_percentage,diffClusters:e.diffClusters||[],viewport_width:e.properties?.viewport_width??e.viewport_width,viewport_height:e.properties?.viewport_height??e.viewport_height,browser:e.properties?.browser??e.browser})),[t]),re=(0,N.useMemo)(()=>{if(!e)return null;let t=e.name||e.originalName;return{name:t,comparisons:T.filter(e=>(e.name||e.originalName)===t)}},[e,T]),E=(0,N.useCallback)(e=>{e&&o&&o(e)},[o]),D=(0,N.useMemo)(()=>{let e={all:()=>!0,"needs-review":e=>(e.result===`changed`||e.result===`new`)&&e.approval_status!==`approved`&&e.approval_status!==`rejected`,changes:e=>e.result===`changed`,new:e=>e.result===`new`},t=e[_]||e.all;return T.filter(t)},[T,_]),O=(0,N.useMemo)(()=>{if(!e)return-1;let t=G(e);return D.findIndex(e=>G(e)===t)},[D,e]),k=(0,N.useMemo)(()=>({prev:O>0,next:O<D.length-1&&O!==-1}),[O,D.length]),A=(0,N.useCallback)(()=>{if(k.prev){let e=D[O-1];o(e)}},[k.prev,D,O,o]),ie=(0,N.useCallback)(()=>{if(k.next){let e=D[O+1];o(e)}},[k.next,D,O,o]),ae=(0,N.useCallback)(()=>O<D.length-1?D[O+1]:O>0?D[O-1]:null,[O,D]),oe=(0,N.useCallback)(()=>{if(!r||!e)return;let t=_===`needs-review`?ae():null;r(G(e)),t?o(t):_===`needs-review`&&D.length<=1&&n()},[r,e,_,ae,D.length,o,n]),se=(0,N.useCallback)(()=>{if(!i||!e)return;let t=_===`needs-review`?ae():null;i(G(e)),t?o(t):_===`needs-review`&&D.length<=1&&n()},[i,e,_,ae,D.length,o,n]),ce=(0,N.useCallback)(()=>{!a||!e||a(G(e))},[a,e]),j=(0,N.useCallback)(()=>{c(e=>e===So.OVERLAY?So.TOGGLE:So.OVERLAY)},[]),le=(0,N.useCallback)(()=>{s===So.TOGGLE?b(e=>!e):u(e=>!e)},[s]);if(ro((0,N.useMemo)(()=>({a:r?oe:void 0,r:i?se:void 0,d:le,t:j,1:()=>c(So.OVERLAY),2:()=>c(So.TOGGLE),3:()=>c(So.ONION)}),[oe,se,r,i,j,le])),(0,N.useEffect)(()=>{let e=e=>{if(!(e.target.tagName===`INPUT`||e.target.tagName===`TEXTAREA`))switch(e.key){case`ArrowUp`:case`ArrowLeft`:e.preventDefault(),k.prev&&A();break;case`ArrowDown`:case`ArrowRight`:e.preventDefault(),k.next&&ie();break;case`Escape`:e.preventDefault(),n();break;case`i`:!e.metaKey&&!e.ctrlKey&&(e.preventDefault(),ne());break;case`g`:!e.metaKey&&!e.ctrlKey&&(e.preventDefault(),S(e=>!e));break}};return window.addEventListener(`keydown`,e),()=>window.removeEventListener(`keydown`,e)},[k,A,ie,n,ne]),(0,N.useEffect)(()=>{if(!p||O<0||O>=D.length)return;let e=te.current;if(!e)return;let t=e.closest(`.overflow-y-auto`);if(!t)return;let n=e.getBoundingClientRect(),r=t.getBoundingClientRect();n.top<r.top?t.scrollBy({top:n.top-r.top-8,behavior:`smooth`}):n.bottom>r.bottom&&t.scrollBy({top:n.bottom-r.bottom+8,behavior:`smooth`})},[O,D.length,p]),!e)return(0,P.jsx)(`div`,{className:`min-h-screen bg-[var(--vz-bg)] flex items-center justify-center`,children:(0,P.jsxs)(`div`,{className:`text-center text-[var(--text-tertiary)]`,children:[(0,P.jsx)(`div`,{className:`text-lg mb-2`,children:`Comparison not found`}),(0,P.jsx)(`button`,{type:`button`,onClick:n,className:`text-[var(--accent-info)] hover:text-[var(--text-primary)]`,children:`Return to list`})]})});let ue=ko(e.status),de=e.userAction===`accepted`?`approved`:e.userAction===`rejected`?`rejected`:e.status===`passed`?`approved`:`pending`,M={new:{variant:`info`,label:`New`},changed:{variant:`warning`,label:`Changed`},unchanged:{variant:`default`,label:`Unchanged`}}[ue],fe=e.status===`new`||e.status===`baseline-created`,pe=e.properties||{};return(0,P.jsxs)(`div`,{className:`h-screen bg-[var(--vz-bg)] flex flex-col overflow-hidden`,"data-testid":`fullscreen-viewer`,children:[(0,P.jsxs)(`header`,{className:`flex-shrink-0 bg-[var(--vz-bg)] border-b border-[var(--vz-border-subtle)] z-30`,children:[(0,P.jsxs)(`div`,{className:`px-2 sm:px-4 py-2 flex items-center gap-1 sm:gap-3`,children:[(0,P.jsx)(qa,{onClick:n}),(0,P.jsxs)(`div`,{className:`flex-1 min-w-0 flex items-center gap-2`,children:[(0,P.jsx)(`h1`,{className:`text-sm font-medium text-[var(--text-primary)] truncate`,children:e.name||e.originalName||`Unknown`}),M&&(0,P.jsx)(nr,{variant:M.variant,size:`sm`,className:`hidden xs:flex`,children:M.label}),w&&(0,P.jsx)(nr,{variant:`warning`,size:`sm`,dot:!0,pulseDot:!0,className:`hidden sm:flex`,children:`Review Mode`})]}),(0,P.jsxs)(`div`,{className:`flex items-center`,children:[(0,P.jsx)(`button`,{type:`button`,onClick:A,disabled:!k.prev,className:`p-2 text-[var(--text-tertiary)] hover:text-[var(--text-primary)] disabled:opacity-85 disabled:cursor-not-allowed rounded-md hover:bg-[var(--vz-raised)] transition-colors`,"aria-label":`Previous`,children:(0,P.jsx)(Kt,{className:`w-5 h-5`})}),(0,P.jsxs)(`span`,{className:`hidden sm:block text-xs text-[var(--text-muted)] font-medium tabular-nums px-1 min-w-[3rem] text-center`,children:[O+1,`/`,D.length]}),(0,P.jsx)(`button`,{type:`button`,onClick:ie,disabled:!k.next,className:`p-2 text-[var(--text-tertiary)] hover:text-[var(--text-primary)] disabled:opacity-85 disabled:cursor-not-allowed rounded-md hover:bg-[var(--vz-raised)] transition-colors`,"aria-label":`Next`,children:(0,P.jsx)(Jt,{className:`w-5 h-5`})})]}),(0,P.jsx)(`div`,{className:`hidden sm:block h-5 w-px bg-[var(--vz-border-subtle)]`}),(0,P.jsxs)(`div`,{className:`hidden sm:flex items-center gap-2`,children:[fe&&a?(0,P.jsx)(`button`,{type:`button`,onClick:ce,className:`px-3 py-1.5 text-xs font-medium text-[var(--accent-danger)] hover:text-[var(--text-primary)] hover:bg-[var(--accent-danger-muted)] rounded-md transition-colors`,"data-testid":`btn-delete`,children:`Delete`}):null,(0,P.jsx)(ba,{status:de,onApprove:r?oe:null,onReject:i?se:null,compact:!0})]}),(0,P.jsxs)(`div`,{className:`hidden sm:flex items-center`,children:[(0,P.jsx)(`button`,{type:`button`,onClick:()=>m(!p),className:`flex items-center justify-center p-2 rounded-md transition-colors ${p?`bg-[var(--accent-brand-muted)] text-[var(--accent-brand)]`:`text-[var(--text-tertiary)] hover:text-[var(--text-primary)] hover:bg-[var(--vz-raised)]`}`,title:`Queue`,"aria-label":`Toggle queue`,"data-testid":`toggle-queue-btn`,children:(0,P.jsx)(Tn,{className:`w-5 h-5 pointer-events-none`})}),(e?.confirmedRegions?.length>0||e?.hasConfirmedRegions)&&(0,P.jsx)(`button`,{type:`button`,onClick:()=>S(!x),className:`p-2 rounded-md transition-colors ${x?`bg-[var(--accent-success-muted)] text-[var(--accent-success)]`:`text-[var(--text-tertiary)] hover:text-[var(--text-primary)] hover:bg-[var(--vz-raised)]`}`,title:`Show Regions (G)`,"aria-label":`Toggle regions`,"data-testid":`toggle-regions-btn`,children:(0,P.jsx)(jn,{className:`w-5 h-5 pointer-events-none`})}),(0,P.jsx)(`button`,{type:`button`,onClick:ne,className:`p-2 rounded-md transition-colors ${h?`bg-[var(--accent-info-muted)] text-[var(--accent-info)]`:`text-[var(--text-tertiary)] hover:text-[var(--text-primary)] hover:bg-[var(--vz-raised)]`}`,title:`Details (I)`,"aria-label":`Screenshot details`,"data-testid":`toggle-inspector-btn`,children:(0,P.jsx)(Cn,{className:`w-5 h-5 pointer-events-none`})})]})]}),(0,P.jsxs)(`div`,{className:`px-2 sm:px-4 py-1.5 sm:py-2 border-t border-[var(--vz-border-subtle)] flex items-center gap-2 sm:gap-3`,children:[re&&re.comparisons.length>0&&(0,P.jsx)(go,{variants:re.comparisons,currentVariantId:G(e),onVariantSelect:E}),(0,P.jsx)(`div`,{className:`flex-1`}),e.diff&&(0,P.jsx)(vo,{value:s,onChange:c,compact:!0,className:`hidden sm:flex`}),(0,P.jsx)(xo,{zoom:ee,onZoomChange:C})]})]}),(0,P.jsxs)(`div`,{className:`flex-1 min-h-0 flex overflow-hidden`,children:[(0,P.jsxs)(Va,{isOpen:p,onClose:()=>m(!1),position:`bottom`,desktopPosition:`left`,width:`w-80`,mobileHeight:`30vh`,expandedHeight:`80vh`,alwaysVisibleOnDesktop:!1,showBackdrop:!1,testId:`queue-drawer`,children:[(0,P.jsx)(Ha,{title:`Queue`,subtitle:`${D.length} of ${T.length} items`,onClose:()=>m(!1)}),(0,P.jsx)(Ga,{children:[{id:`needs-review`,label:`To Review`},{id:`changes`,label:`Changes`},{id:`new`,label:`New`},{id:`all`,label:`All`}].map(e=>(0,P.jsx)(Ka,{isActive:_===e.id,onClick:()=>v(e.id),children:e.label},e.id))}),(0,P.jsxs)(Ua,{className:`px-2 py-2 space-y-1`,children:[D.map(t=>{let n=G(t)===G(e);return(0,P.jsx)(`div`,{ref:n?te:null,children:(0,P.jsx)($a,{item:t,isActive:n,thumbnailUrl:Eo(t.current||t.baseline,t.timestamp),onClick:()=>o(t)})},G(t))}),D.length===0&&(0,P.jsx)(`div`,{className:`text-center py-8 text-[var(--text-muted)] text-xs`,children:_===`needs-review`?`All screenshots reviewed!`:`No items match this filter`})]}),(0,P.jsxs)(Wa,{className:`hidden sm:flex flex-col items-center gap-1.5`,children:[(0,P.jsxs)(`p`,{className:`text-xs text-[var(--text-muted)] text-center`,children:[(0,P.jsx)(`span`,{className:`text-[var(--text-tertiary)]`,children:`↑↓`}),` navigate`,` · `,(0,P.jsx)(`span`,{className:`text-[var(--text-tertiary)]`,children:`Space`}),` `,w?`exit`:`review mode`]}),w&&(0,P.jsxs)(`p`,{className:`text-xs text-[var(--text-muted)] text-center`,children:[(0,P.jsx)(`span`,{className:`text-[var(--accent-success)] font-medium`,children:`A`}),` `,`approve`,` · `,(0,P.jsx)(`span`,{className:`text-[var(--accent-danger)] font-medium`,children:`R`}),` `,`reject`,` · `,(0,P.jsx)(`span`,{className:`text-[var(--text-tertiary)] font-medium`,children:`D`}),` `,`diff`,` · `,(0,P.jsx)(`span`,{className:`text-[var(--text-tertiary)] font-medium`,children:`T`}),` `,`view`]})]})]}),(0,P.jsx)(`main`,{className:`flex-1 min-h-0 overflow-hidden relative`,children:(0,P.jsx)(Oo,{comparison:e,viewMode:s===So.ONION?`onion-skin`:s,showDiffOverlay:l,onDiffToggle:()=>u(e=>!e),showBaseline:y,onToggleBaseline:()=>b(e=>!e),onionSkinPosition:d,onOnionSkinChange:f,zoom:ee,disableLoadingOverlay:!0,showRegions:x,className:`w-full h-full`},G(e))}),(0,P.jsxs)(Za,{isOpen:h,onClose:()=>g(!1),title:`Screenshot Details`,subtitle:e.name||e.originalName,className:`order-last`,children:[(0,P.jsx)(Za.Section,{title:`Status`,icon:sn,children:(0,P.jsxs)(`div`,{className:`flex items-center gap-2 flex-wrap`,children:[M&&(0,P.jsx)(nr,{variant:M.variant,size:`sm`,children:M.label}),(0,P.jsx)(nr,{variant:de===`approved`?`success`:de===`rejected`?`danger`:`default`,size:`sm`,children:de})]})}),(0,P.jsxs)(Za.Section,{title:`Screenshot`,icon:an,children:[pe.browser&&(0,P.jsx)(Za.Row,{label:`Browser`,value:pe.browser,icon:()=>(0,P.jsx)(ir,{browser:pe.browser,className:`w-3.5 h-3.5`})}),(0,P.jsx)(Za.Row,{label:`Viewport`,value:pe.viewport_width&&pe.viewport_height?`${pe.viewport_width} × ${pe.viewport_height}`:null,mono:!0}),(0,P.jsx)(Za.Row,{label:`Device`,value:pe.device})]}),ue===`changed`&&e.diffPercentage!=null&&(0,P.jsxs)(Za.Section,{title:`Diff Analysis`,icon:pn,children:[(0,P.jsx)(Za.Row,{label:`Changed`,value:`${Number(e.diffPercentage).toFixed(3)}%`,mono:!0}),e.diffPixels!=null&&(0,P.jsx)(Za.Row,{label:`Pixels`,value:e.diffPixels.toLocaleString(),mono:!0}),e.threshold!=null&&(0,P.jsx)(Za.Row,{label:`Threshold`,value:Number(e.threshold).toFixed(2),mono:!0})]}),(0,P.jsx)(Za.Section,{title:`Identifier`,icon:sn,children:(0,P.jsx)(Za.Row,{label:`Signature`,value:e.signature||e.id,mono:!0,copyable:!0})})]})]}),(0,P.jsxs)(`div`,{className:`sm:hidden flex-shrink-0 bg-[var(--vz-bg)] border-t border-[var(--vz-border-subtle)] safe-area-pb`,children:[(0,P.jsx)(xa,{status:de,onApprove:r?oe:null,onReject:i?se:null}),(0,P.jsxs)(`div`,{className:`px-3 py-2 flex items-center gap-1 border-t border-[var(--vz-border-subtle)]`,children:[(0,P.jsx)(`button`,{type:`button`,onClick:()=>m(!p),className:`flex items-center justify-center p-2.5 rounded-lg transition-colors ${p?`bg-[var(--accent-brand-muted)] text-[var(--accent-brand)]`:`text-[var(--text-tertiary)] hover:text-[var(--text-primary)] hover:bg-[var(--vz-raised)] active:bg-[var(--vz-elevated)]`}`,"aria-label":`Queue`,"data-testid":`mobile-toggle-queue-btn`,children:(0,P.jsx)(Tn,{className:`w-5 h-5 pointer-events-none`})}),(0,P.jsx)(`button`,{type:`button`,onClick:ne,className:`flex items-center justify-center p-2.5 rounded-lg transition-colors ${h?`bg-[var(--accent-info-muted)] text-[var(--accent-info)]`:`text-[var(--text-tertiary)] hover:text-[var(--text-primary)] hover:bg-[var(--vz-raised)] active:bg-[var(--vz-elevated)]`}`,"aria-label":`Screenshot details`,"data-testid":`mobile-toggle-inspector-btn`,children:(0,P.jsx)(Cn,{className:`w-5 h-5 pointer-events-none`})}),(e?.confirmedRegions?.length>0||e?.hasConfirmedRegions)&&(0,P.jsx)(`button`,{type:`button`,onClick:()=>S(!x),className:`flex items-center justify-center p-2.5 rounded-lg transition-colors ${x?`bg-[var(--accent-success-muted)] text-[var(--accent-success)]`:`text-[var(--text-tertiary)] hover:text-[var(--text-primary)] hover:bg-[var(--vz-raised)] active:bg-[var(--vz-elevated)]`}`,"aria-label":`Toggle regions`,"data-testid":`mobile-toggle-regions-btn`,children:(0,P.jsx)(jn,{className:`w-5 h-5 pointer-events-none`})}),fe&&a&&(0,P.jsx)(`button`,{type:`button`,onClick:ce,className:`flex items-center justify-center p-2.5 rounded-lg text-[var(--accent-danger)] hover:text-[var(--text-primary)] hover:bg-[var(--accent-danger-muted)] active:bg-[var(--accent-danger-muted)] transition-colors`,"aria-label":`Delete`,"data-testid":`mobile-delete-btn`,children:(0,P.jsx)(`span`,{className:`text-xs font-medium`,children:`Delete`})}),(0,P.jsx)(`div`,{className:`flex-1`}),(0,P.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,P.jsxs)(`span`,{className:`text-xs text-[var(--text-muted)] tabular-nums`,children:[O+1,` / `,D.length]}),M&&(0,P.jsx)(nr,{variant:M.variant,size:`sm`,children:M.label})]})]})]})]})}function Mo(){let[,e]=gi(),[,t]=vi(`/comparison/:id`),{data:n}=Ui(),r=Wi(),i=qi(),a=Xi(),o=(0,N.useMemo)(()=>n?.comparisons||[],[n?.comparisons]),s=(0,N.useMemo)(()=>{let e=t?.id?decodeURIComponent(t.id):null;return!e||o.length===0?null:o.find(t=>t.id===e||t.signature===e||t.name===e)},[t,o]),{data:c}=Hi(s?.id),l=(0,N.useMemo)(()=>s?c?{...s,diffClusters:c.diffClusters,confirmedRegions:c.confirmedRegions,intensityStats:c.intensityStats,boundingBox:c.boundingBox,regionAnalysis:c.regionAnalysis,hotspotAnalysis:c.hotspotAnalysis}:s:null,[s,c]),u=(0,N.useCallback)(t=>{let n=t.id||t.signature||t.name;n&&e(`/comparison/${encodeURIComponent(n)}`)},[e]),d=(0,N.useCallback)(()=>{e(`/`)},[e]),f=(0,N.useCallback)(e=>{r.mutate(e)},[r]),p=(0,N.useCallback)(e=>{i.mutate(e)},[i]),m=(0,N.useCallback)(t=>{a.mutate(t,{onSuccess:()=>{e(`/`)}})},[a,e]);return l?(0,P.jsx)(Ao,{comparison:l,comparisons:o,onClose:d,onAccept:f,onReject:p,onDelete:m,onNavigate:u}):(0,P.jsx)(`div`,{className:`fixed inset-0 bg-[var(--vz-bg)] z-50 flex items-center justify-center`,children:(0,P.jsxs)(`div`,{className:`text-center`,children:[(0,P.jsx)(`div`,{className:`text-lg text-[var(--text-primary)] font-medium mb-2`,children:`Comparison not found`}),(0,P.jsxs)(`p`,{className:`text-sm text-[var(--text-tertiary)] mb-4`,children:[`ID: `,t?.id||`none`]}),(0,P.jsx)(`button`,{type:`button`,onClick:d,className:`text-[var(--accent-media)] hover:text-[var(--accent-media)] font-medium transition-colors`,children:`Return to list`})]})})}To.NEW,To.BASELINE_CREATED,To.PASSED,To.FAILED,To.ERROR,To.REJECTED;function No(e,t){return[...e].sort((e,n)=>{if(t===`priority`){let t={[To.FAILED]:3,[To.NEW]:2,[To.PASSED]:1},r=e.initialStatus||e.status,i=n.initialStatus||n.status,a=t[r]||0,o=t[i]||0;return a===o?(n.diffPercentage||0)-(e.diffPercentage||0):o-a}return t===`name`?(e.name||``).localeCompare(n.name||``):t===`time`?(n.timestamp||0)-(e.timestamp||0):0})}function Po(e,t){return t===`failed`?e.filter(e=>e.status===To.FAILED):t===`passed`?e.filter(e=>e.status===To.PASSED):t===`new`?e.filter(e=>e.status===To.NEW||e.status===To.BASELINE_CREATED):e}function Fo(e){return e===`new`||e===`baseline-created`}function Io(e){return e===`failed`||Fo(e)}var Lo=()=>{let e=new URLSearchParams(window.location.search);return{filter:e.get(`filter`)||Co.ALL,sortBy:e.get(`sort`)||wo.PRIORITY,searchQuery:e.get(`search`)||``,selectedBrowser:e.get(`browser`)||`all`,selectedViewport:e.get(`viewport`)||`all`}};function Ro(e=[]){let t=Lo(),[n,r]=(0,N.useState)(t.filter),[i,a]=(0,N.useState)(t.sortBy),[o,s]=(0,N.useState)(t.searchQuery),[c,l]=(0,N.useState)(t.selectedBrowser),[u,d]=(0,N.useState)(t.selectedViewport);(0,N.useEffect)(()=>{let e=new URLSearchParams;n!==Co.ALL&&e.set(`filter`,n),i!==wo.PRIORITY&&e.set(`sort`,i),o&&e.set(`search`,o),c!==`all`&&e.set(`browser`,c),u!==`all`&&e.set(`viewport`,u);let t=e.toString()?`?${e.toString()}`:window.location.pathname;window.history.replaceState({},``,t)},[n,i,o,c,u]);let f=(0,N.useMemo)(()=>{let t=new Set,n=new Set;return e.forEach(e=>{if(e.properties?.browser&&t.add(e.properties.browser),e.properties?.viewport_width&&e.properties?.viewport_height){let t=`${e.properties.viewport_width}x${e.properties.viewport_height}`;n.add(t)}else if(e.properties?.viewport){let t=`${e.properties.viewport.width}x${e.properties.viewport.height}`;n.add(t)}}),{browsers:Array.from(t).sort(),viewports:Array.from(n).sort()}},[e]);return{filteredComparisons:(0,N.useMemo)(()=>{let t=Po(e,n);if(o.trim()){let e=o.toLowerCase();t=t.filter(t=>t.name.toLowerCase().includes(e)||t.originalName?.toLowerCase().includes(e))}return c!==`all`&&(t=t.filter(e=>e.properties?.browser===c)),u!==`all`&&(t=t.filter(e=>{let t=null;return e.properties?.viewport_width&&e.properties?.viewport_height?t=`${e.properties.viewport_width}x${e.properties.viewport_height}`:e.properties?.viewport&&(t=`${e.properties.viewport.width}x${e.properties.viewport.height}`),t===u})),No(t,i)},[e,n,i,o,c,u]),filter:n,setFilter:r,sortBy:i,setSortBy:a,searchQuery:o,setSearchQuery:s,selectedBrowser:c,setSelectedBrowser:l,selectedViewport:u,setSelectedViewport:d,availableFilters:f,counts:{all:e.length,failed:e.filter(e=>e.status===`failed`).length,passed:e.filter(e=>e.status===`passed`).length,new:e.filter(e=>Fo(e.status)).length,rejected:e.filter(e=>e.status===`rejected`).length}}}function zo({src:e,alt:t,className:n,style:r,onClick:i}){let[a,o]=(0,N.useState)(e?`loading`:`missing`);if((0,N.useEffect)(()=>{if(!e){o(`missing`);return}o(`loading`)},[e]),a===`missing`)return(0,P.jsx)(`div`,{className:`flex items-center justify-center bg-[var(--vz-raised)] border border-[var(--vz-border)] rounded min-h-[200px]`,style:r,children:(0,P.jsxs)(`div`,{className:`text-[var(--text-tertiary)] text-center`,children:[(0,P.jsx)(Fn,{className:`w-12 h-12 mx-auto mb-3`}),(0,P.jsxs)(`div`,{className:`text-sm`,children:[`No `,t.toLowerCase(),` available`]})]})});if(a===`error`)return(0,P.jsx)(`div`,{className:`flex items-center justify-center bg-[var(--accent-danger-muted)] border border-[color-mix(in_srgb,var(--accent-danger)_28%,transparent)] rounded min-h-[200px]`,style:r,children:(0,P.jsxs)(`div`,{className:`text-[var(--accent-danger)] text-center`,children:[(0,P.jsx)(hn,{className:`w-12 h-12 mx-auto mb-3`}),(0,P.jsxs)(`div`,{className:`text-sm`,children:[`Failed to load `,t.toLowerCase()]}),(0,P.jsx)(`div`,{className:`text-xs text-[var(--text-muted)] mt-1 font-mono break-all px-4`,children:e})]})});let s=null;return i&&(s=e=>{(e.key===`Enter`||e.key===` `)&&(e.preventDefault(),i(e))}),(0,P.jsx)(`img`,{src:e,alt:t,className:n,style:r,loading:`lazy`,decoding:`async`,onLoad:()=>o(`loaded`),onError:()=>o(`error`),onClick:i,onKeyDown:s,role:i?`button`:void 0,tabIndex:i?0:void 0})}function Bo(e){return e.id||e.signature||e.name}function Vo(e){let t=new Map;for(let n of e){let e=n.name||n.originalName||`Unknown`;t.has(e)||t.set(e,{name:e,comparisons:[],browsers:new Set,viewports:[],hasChanges:!1,hasNew:!1,allPassed:!0,maxDiff:0,failedCount:0,newCount:0,passedCount:0,acceptedCount:0,rejectedCount:0});let r=t.get(e);r.comparisons.push(n),n.properties?.browser&&r.browsers.add(n.properties.browser),n.properties?.viewport_width&&n.properties?.viewport_height&&r.viewports.push({width:n.properties.viewport_width,height:n.properties.viewport_height,id:n.id,status:n.status}),n.status===`failed`?(r.hasChanges=!0,r.allPassed=!1,r.failedCount++,r.maxDiff=Math.max(r.maxDiff,n.diffPercentage||0)):n.status===`new`||n.status===`baseline-created`?(r.hasNew=!0,r.allPassed=!1,r.newCount++):n.status===`passed`&&r.passedCount++,n.userAction===`accepted`?r.acceptedCount++:n.userAction===`rejected`&&r.rejectedCount++}return Array.from(t.values()).map(e=>({...e,browsers:Array.from(e.browsers),viewports:e.viewports.sort((e,t)=>t.width*t.height-e.width*e.height),primary:e.comparisons[0]})).sort((e,t)=>e.hasChanges&&!t.hasChanges?-1:!e.hasChanges&&t.hasChanges?1:e.hasNew&&!t.hasNew?-1:!e.hasNew&&t.hasNew?1:e.maxDiff===t.maxDiff?e.name.localeCompare(t.name):t.maxDiff-e.maxDiff)}function Ho({width:e,className:t=`w-3.5 h-3.5`}){return e<=480?(0,P.jsx)(ln,{className:t}):e<=1024?(0,P.jsx)(dn,{className:t}):(0,P.jsx)(an,{className:t})}function Uo({group:e}){return e.acceptedCount===e.comparisons.length?(0,P.jsxs)(nr,{variant:`success`,size:`sm`,children:[(0,P.jsx)(Bt,{className:`w-3 h-3 mr-1`}),`Accepted`]}):e.rejectedCount>0?(0,P.jsxs)(nr,{variant:`warning`,size:`sm`,children:[(0,P.jsx)(Yn,{className:`w-3 h-3 mr-1`}),`Rejected`]}):e.hasChanges?(0,P.jsxs)(nr,{variant:`danger`,size:`sm`,children:[e.failedCount,` changed`]}):e.hasNew?(0,P.jsxs)(nr,{variant:`info`,size:`sm`,children:[e.newCount,` new`]}):(0,P.jsx)(nr,{variant:`success`,size:`sm`,children:`Passed`})}function Wo({group:e,maxVisible:t=4}){let n=e.comparisons.length;if(n<=1){let t=e.viewports[0],n=e.browsers[0];return(0,P.jsxs)(`div`,{className:`flex items-center gap-2 text-xs text-[var(--text-tertiary)]`,children:[t&&(0,P.jsxs)(`span`,{className:`inline-flex items-center gap-1`,children:[(0,P.jsx)(Ho,{width:t.width,className:`w-3 h-3`}),t.width,`×`,t.height]}),n&&(0,P.jsx)(`span`,{children:n})]})}let r=e.viewports.slice(0,t),i=n-t;return(0,P.jsxs)(`div`,{className:`flex items-center gap-1.5 flex-wrap`,children:[r.map((e,t)=>(0,P.jsxs)(`span`,{className:`
|
|
197
|
+
`,backgroundSize:`16px 16px`,backgroundPosition:`0 0, 0 8px, 8px -8px, -8px 0px`,backgroundColor:`var(--vz-checkerboard-bg)`};function Oo({comparison:e,viewMode:t=`overlay`,showDiffOverlay:n=!0,showBaseline:r=!0,onToggleBaseline:i,onionSkinPosition:a=50,onOnionSkinChange:o,onDiffToggle:s,disableLoadingOverlay:c=!1,zoom:l=`fit`,className:u=``,showRegions:d=!1}){let[f,p]=(0,N.useState)(new Set),[m,h]=(0,N.useState)(new Map),[g,_]=(0,N.useState)(!1),[v,y]=(0,N.useState)(!1),[b,x]=(0,N.useState)(!1),[S,ee]=(0,N.useState)(!1),[C,w]=(0,N.useState)(1),[te,ne]=(0,N.useState)({width:0,height:0}),T=(0,N.useRef)(null),re=(0,N.useRef)(null);(0,N.useEffect)(()=>{let e=()=>{let e=T.current,t=e?.querySelector(`img`);if(!e||!t?.naturalWidth)return;ne({width:t.naturalWidth,height:t.naturalHeight});let n=e.getBoundingClientRect(),r=n.width-40,i=n.height-40,a=r/t.naturalWidth,o=i/t.naturalHeight;w(Math.min(a,o,1))};e();let t=new window.ResizeObserver(e);T.current&&t.observe(T.current);let n=T.current?.querySelectorAll(`img`)||[];for(let t of n)t.addEventListener(`load`,e);return()=>{t.disconnect();for(let t of n)t.removeEventListener(`load`,e)}},[]);let E=(0,N.useMemo)(()=>{let e=l===`fit`,t=e?C:typeof l==`number`?l:1,n=!e&&t>C;return{isFit:e,scale:t,allowsOverflow:n,containerClass:n?`overflow-auto`:`overflow-hidden`}},[l,C]),D=(0,N.useCallback)(e=>{p(t=>new Set([...t,e]))},[]),O=(0,N.useCallback)(e=>{h(t=>new Map(t).set(e,`loaded`))},[]),k=(0,N.useMemo)(()=>e?{current:Eo(e.current,e.timestamp),baseline:Eo(e.baseline,e.timestamp),diff:Eo(e.diff,e.timestamp)}:{},[e]),A=(0,N.useMemo)(()=>e?{id:e.id||e.signature||`unknown`,name:e.name||e.originalName||`Screenshot`}:null,[e]);return(0,N.useEffect)(()=>{if(!A?.id){_(!1),y(!1),x(!1),ee(!1);return}_(!1),y(!1),x(!1),ee(!1)},[A?.id]),!e||e.status===`new`||e.status===`baseline-created`?(0,P.jsx)(`div`,{className:`h-full ${u}`,children:(0,P.jsxs)(`div`,{ref:T,className:`bg-[var(--vz-elevated)] relative h-full ${E.containerClass}`,style:Do,children:[(0,P.jsx)(`div`,{className:`relative ${E.allowsOverflow?`flex justify-center items-start`:`min-w-full min-h-full flex justify-center items-center`}`,style:{...E.allowsOverflow?{minWidth:te.width*E.scale+40,minHeight:te.height*E.scale+40,padding:`20px`}:{zoom:E.scale,padding:`20px`}},children:(0,P.jsx)(`div`,{ref:re,className:`relative inline-block`,style:E.allowsOverflow?{transform:`scale(${E.scale})`,transformOrigin:`top center`}:{},children:e&&(0,P.jsx)(`img`,{src:k.current,alt:e.name||`New screenshot`,className:`block`,onLoad:()=>O(`current-${A?.id}`),onError:()=>D(`current-${A?.id}`)})})}),!c&&e&&!m.has(`current-${A?.id}`)&&(0,P.jsx)(`div`,{className:`absolute inset-0 bg-[var(--vz-raised)] animate-pulse flex items-center justify-center`,children:(0,P.jsx)(`div`,{className:`text-[var(--text-tertiary)]`,children:`Loading...`})}),e&&f.has(`current-${A?.id}`)&&(0,P.jsx)(`div`,{className:`absolute inset-0 bg-[var(--vz-raised)] flex items-center justify-center`,children:(0,P.jsxs)(`div`,{className:`text-center text-[var(--text-tertiary)]`,children:[(0,P.jsx)(hn,{className:`w-8 h-8 mx-auto mb-2`}),(0,P.jsx)(`div`,{className:`text-sm`,children:`Failed to load image`})]})})]})}):(0,P.jsx)(`div`,{className:`h-full ${u}`,children:(0,P.jsxs)(`div`,{ref:T,className:`bg-[var(--vz-elevated)] relative unified-screenshot-container h-full ${E.containerClass}`,style:Do,children:[(0,P.jsx)(`div`,{className:`relative ${E.allowsOverflow?`flex justify-center items-start`:`min-w-full min-h-full flex justify-center items-center`}`,style:{...E.allowsOverflow?{minWidth:te.width*E.scale+40,minHeight:te.height*E.scale+40,padding:`20px`}:{zoom:E.scale,padding:`20px`}},children:(0,P.jsxs)(`div`,{ref:re,className:`relative inline-block`,style:E.allowsOverflow?{transform:`scale(${E.scale})`,transformOrigin:`top center`}:{},children:[t===`toggle`&&k.baseline?(0,P.jsx)(ja,{baselineImageUrl:k.baseline,currentImageUrl:k.current,baselineImageLoaded:g,setBaselineImageLoaded:_,currentImageLoaded:v,setCurrentImageLoaded:y,baselineImageError:b,setBaselineImageError:x,currentImageError:S,setCurrentImageError:ee,screenshot:A,onImageError:D,onImageLoad:O,imageErrors:f,showBaseline:r,onToggle:i}):t===`onion-skin`&&k.baseline?(0,P.jsx)(ka,{baselineImageUrl:k.baseline,currentImageUrl:k.current,sliderPosition:a,onSliderChange:o,screenshot:A,onImageError:D,onImageLoad:O,imageErrors:f}):(0,P.jsx)(Aa,{baselineImageUrl:k.baseline,currentImageUrl:k.current,diffImageUrl:k.diff,showDiffOverlay:n,screenshot:A,onImageError:D,onImageLoad:O,imageErrors:f,onDiffToggle:s}),d&&e?.confirmedRegions?.length>0&&(0,P.jsx)(Ca,{confirmed:e.confirmedRegions,candidates:[],imageWidth:te.width,imageHeight:te.height,showConfirmed:!0,showCandidates:!1,disabled:!0})]})}),!c&&!m.has(`current-${A?.id}`)&&(0,P.jsx)(`div`,{className:`absolute inset-0 bg-[var(--vz-raised)] animate-pulse flex items-center justify-center`,children:(0,P.jsx)(`div`,{className:`text-[var(--text-tertiary)]`,children:`Loading...`})}),f.has(`current-${A?.id}`)&&(0,P.jsx)(`div`,{className:`absolute inset-0 bg-[var(--vz-raised)] flex items-center justify-center`,children:(0,P.jsxs)(`div`,{className:`text-center text-[var(--text-tertiary)]`,children:[(0,P.jsx)(hn,{className:`w-8 h-8 mx-auto mb-2`}),(0,P.jsx)(`div`,{className:`text-sm`,children:`Failed to load image`})]})})]})})}function G(e){return e?.id||e?.signature||e?.name}function ko(e){switch(e){case`failed`:return`changed`;case`new`:case`baseline-created`:return`new`;case`passed`:return`unchanged`;default:return e}}function Ao(e){return(0,P.jsx)(to,{children:(0,P.jsx)(jo,{...e})})}function jo({comparison:e,comparisons:t=[],onClose:n,onAccept:r,onReject:i,onDelete:a,onNavigate:o}){let[s,c]=(0,N.useState)(So.OVERLAY),[l,u]=(0,N.useState)(!0),[d,f]=(0,N.useState)(50),[p,m]=(0,N.useState)(!0),[h,g]=(0,N.useState)(!1),[_,v]=(0,N.useState)(`needs-review`),[y,b]=(0,N.useState)(!0),[x,S]=(0,N.useState)(!1),{zoom:ee,setZoom:C}=bo(`fit`),{isActive:w}=no(),te=(0,N.useRef)(null),ne=(0,N.useCallback)(()=>{g(e=>!e)},[]),T=(0,N.useMemo)(()=>t.map(e=>({...e,name:e.name||e.originalName||`Unknown`,result:ko(e.status),status:e.status,approval_status:e.userAction===`accepted`?`approved`:e.userAction===`rejected`?`rejected`:e.status===`passed`?`approved`:`pending`,diff_percentage:e.diffPercentage??e.diff_percentage,diffClusters:e.diffClusters||[],viewport_width:e.properties?.viewport_width??e.viewport_width,viewport_height:e.properties?.viewport_height??e.viewport_height,browser:e.properties?.browser??e.browser})),[t]),re=(0,N.useMemo)(()=>{if(!e)return null;let t=e.name||e.originalName;return{name:t,comparisons:T.filter(e=>(e.name||e.originalName)===t)}},[e,T]),E=(0,N.useCallback)(e=>{e&&o&&o(e)},[o]),D=(0,N.useMemo)(()=>{let e={all:()=>!0,"needs-review":e=>(e.result===`changed`||e.result===`new`)&&e.approval_status!==`approved`&&e.approval_status!==`rejected`,changes:e=>e.result===`changed`,new:e=>e.result===`new`},t=e[_]||e.all;return T.filter(t)},[T,_]),O=(0,N.useMemo)(()=>{if(!e)return-1;let t=G(e);return D.findIndex(e=>G(e)===t)},[D,e]),k=(0,N.useMemo)(()=>({prev:O>0,next:O<D.length-1&&O!==-1}),[O,D.length]),A=(0,N.useCallback)(()=>{if(k.prev){let e=D[O-1];o(e)}},[k.prev,D,O,o]),ie=(0,N.useCallback)(()=>{if(k.next){let e=D[O+1];o(e)}},[k.next,D,O,o]),ae=(0,N.useCallback)(()=>O<D.length-1?D[O+1]:O>0?D[O-1]:null,[O,D]),oe=(0,N.useCallback)(()=>{if(!r||!e)return;let t=_===`needs-review`?ae():null;r(G(e)),t?o(t):_===`needs-review`&&D.length<=1&&n()},[r,e,_,ae,D.length,o,n]),se=(0,N.useCallback)(()=>{if(!i||!e)return;let t=_===`needs-review`?ae():null;i(G(e)),t?o(t):_===`needs-review`&&D.length<=1&&n()},[i,e,_,ae,D.length,o,n]),ce=(0,N.useCallback)(()=>{!a||!e||a(G(e))},[a,e]),j=(0,N.useCallback)(()=>{c(e=>e===So.OVERLAY?So.TOGGLE:So.OVERLAY)},[]),le=(0,N.useCallback)(()=>{s===So.TOGGLE?b(e=>!e):u(e=>!e)},[s]);if(ro((0,N.useMemo)(()=>({a:r?oe:void 0,r:i?se:void 0,d:le,t:j,1:()=>c(So.OVERLAY),2:()=>c(So.TOGGLE),3:()=>c(So.ONION)}),[oe,se,r,i,j,le])),(0,N.useEffect)(()=>{let e=e=>{if(!(e.target.tagName===`INPUT`||e.target.tagName===`TEXTAREA`))switch(e.key){case`ArrowUp`:case`ArrowLeft`:e.preventDefault(),k.prev&&A();break;case`ArrowDown`:case`ArrowRight`:e.preventDefault(),k.next&&ie();break;case`Escape`:e.preventDefault(),n();break;case`i`:!e.metaKey&&!e.ctrlKey&&(e.preventDefault(),ne());break;case`g`:!e.metaKey&&!e.ctrlKey&&(e.preventDefault(),S(e=>!e));break}};return window.addEventListener(`keydown`,e),()=>window.removeEventListener(`keydown`,e)},[k,A,ie,n,ne]),(0,N.useEffect)(()=>{if(!p||O<0||O>=D.length)return;let e=te.current;if(!e)return;let t=e.closest(`.overflow-y-auto`);if(!t)return;let n=e.getBoundingClientRect(),r=t.getBoundingClientRect();n.top<r.top?t.scrollBy({top:n.top-r.top-8,behavior:`smooth`}):n.bottom>r.bottom&&t.scrollBy({top:n.bottom-r.bottom+8,behavior:`smooth`})},[O,D.length,p]),!e)return(0,P.jsx)(`div`,{className:`min-h-screen bg-[var(--vz-bg)] flex items-center justify-center`,children:(0,P.jsxs)(`div`,{className:`text-center text-[var(--text-tertiary)]`,children:[(0,P.jsx)(`div`,{className:`text-lg mb-2`,children:`Comparison not found`}),(0,P.jsx)(`button`,{type:`button`,onClick:n,className:`text-[var(--accent-info)] hover:text-[var(--text-primary)]`,children:`Return to list`})]})});let ue=ko(e.status),de=e.userAction===`accepted`?`approved`:e.userAction===`rejected`?`rejected`:e.status===`passed`?`approved`:`pending`,M={new:{variant:`info`,label:`New`},changed:{variant:`warning`,label:`Changed`},unchanged:{variant:`default`,label:`Unchanged`}}[ue],fe=e.status===`new`||e.status===`baseline-created`,pe=e.properties||{};return(0,P.jsxs)(`div`,{className:`h-screen bg-[var(--vz-bg)] flex flex-col overflow-hidden`,"data-testid":`fullscreen-viewer`,children:[(0,P.jsxs)(`header`,{className:`flex-shrink-0 bg-[var(--vz-bg)] border-b border-[var(--vz-border-subtle)] z-30`,children:[(0,P.jsxs)(`div`,{className:`px-2 sm:px-4 py-2 flex items-center gap-1 sm:gap-3`,children:[(0,P.jsx)(qa,{onClick:n}),(0,P.jsxs)(`div`,{className:`flex-1 min-w-0 flex items-center gap-2`,children:[(0,P.jsx)(`h1`,{className:`text-sm font-medium text-[var(--text-primary)] truncate`,children:e.name||e.originalName||`Unknown`}),M&&(0,P.jsx)(nr,{variant:M.variant,size:`sm`,className:`hidden xs:flex`,children:M.label}),w&&(0,P.jsx)(nr,{variant:`warning`,size:`sm`,dot:!0,pulseDot:!0,className:`hidden sm:flex`,children:`Review Mode`})]}),(0,P.jsxs)(`div`,{className:`flex items-center`,children:[(0,P.jsx)(`button`,{type:`button`,onClick:A,disabled:!k.prev,className:`p-2 text-[var(--text-tertiary)] hover:text-[var(--text-primary)] disabled:opacity-85 disabled:cursor-not-allowed rounded-md hover:bg-[var(--vz-raised)] transition-colors`,"aria-label":`Previous`,children:(0,P.jsx)(Kt,{className:`w-5 h-5`})}),(0,P.jsxs)(`span`,{className:`hidden sm:block text-xs text-[var(--text-muted)] font-medium tabular-nums px-1 min-w-[3rem] text-center`,children:[O+1,`/`,D.length]}),(0,P.jsx)(`button`,{type:`button`,onClick:ie,disabled:!k.next,className:`p-2 text-[var(--text-tertiary)] hover:text-[var(--text-primary)] disabled:opacity-85 disabled:cursor-not-allowed rounded-md hover:bg-[var(--vz-raised)] transition-colors`,"aria-label":`Next`,children:(0,P.jsx)(Jt,{className:`w-5 h-5`})})]}),(0,P.jsx)(`div`,{className:`hidden sm:block h-5 w-px bg-[var(--vz-border-subtle)]`}),(0,P.jsxs)(`div`,{className:`hidden sm:flex items-center gap-2`,children:[fe&&a?(0,P.jsx)(`button`,{type:`button`,onClick:ce,className:`px-3 py-1.5 text-xs font-medium text-[var(--accent-danger)] hover:text-[var(--text-primary)] hover:bg-[var(--accent-danger-muted)] rounded-md transition-colors`,"data-testid":`btn-delete`,children:`Delete`}):null,(0,P.jsx)(ba,{status:de,onApprove:r?oe:null,onReject:i?se:null,compact:!0})]}),(0,P.jsxs)(`div`,{className:`hidden sm:flex items-center`,children:[(0,P.jsx)(`button`,{type:`button`,onClick:()=>m(!p),className:`flex items-center justify-center p-2 rounded-md transition-colors ${p?`bg-[var(--accent-brand-muted)] text-[var(--accent-brand)]`:`text-[var(--text-tertiary)] hover:text-[var(--text-primary)] hover:bg-[var(--vz-raised)]`}`,title:`Queue`,"aria-label":`Toggle queue`,"data-testid":`toggle-queue-btn`,children:(0,P.jsx)(Tn,{className:`w-5 h-5 pointer-events-none`})}),(e?.confirmedRegions?.length>0||e?.hasConfirmedRegions)&&(0,P.jsx)(`button`,{type:`button`,onClick:()=>S(!x),className:`p-2 rounded-md transition-colors ${x?`bg-[var(--accent-success-muted)] text-[var(--accent-success)]`:`text-[var(--text-tertiary)] hover:text-[var(--text-primary)] hover:bg-[var(--vz-raised)]`}`,title:`Show Regions (G)`,"aria-label":`Toggle regions`,"data-testid":`toggle-regions-btn`,children:(0,P.jsx)(jn,{className:`w-5 h-5 pointer-events-none`})}),(0,P.jsx)(`button`,{type:`button`,onClick:ne,className:`p-2 rounded-md transition-colors ${h?`bg-[var(--accent-info-muted)] text-[var(--accent-info)]`:`text-[var(--text-tertiary)] hover:text-[var(--text-primary)] hover:bg-[var(--vz-raised)]`}`,title:`Details (I)`,"aria-label":`Screenshot details`,"data-testid":`toggle-inspector-btn`,children:(0,P.jsx)(Cn,{className:`w-5 h-5 pointer-events-none`})})]})]}),(0,P.jsxs)(`div`,{className:`px-2 sm:px-4 py-1.5 sm:py-2 border-t border-[var(--vz-border-subtle)] flex items-center gap-2 sm:gap-3`,children:[re&&re.comparisons.length>0&&(0,P.jsx)(go,{variants:re.comparisons,currentVariantId:G(e),onVariantSelect:E}),(0,P.jsx)(`div`,{className:`flex-1`}),e.diff&&(0,P.jsx)(vo,{value:s,onChange:c,compact:!0,className:`hidden sm:flex`}),(0,P.jsx)(xo,{zoom:ee,onZoomChange:C})]})]}),(0,P.jsxs)(`div`,{className:`flex-1 min-h-0 flex overflow-hidden`,children:[(0,P.jsxs)(Va,{isOpen:p,onClose:()=>m(!1),position:`bottom`,desktopPosition:`left`,width:`w-80`,mobileHeight:`30vh`,expandedHeight:`80vh`,alwaysVisibleOnDesktop:!1,showBackdrop:!1,testId:`queue-drawer`,children:[(0,P.jsx)(Ha,{title:`Queue`,subtitle:`${D.length} of ${T.length} items`,onClose:()=>m(!1)}),(0,P.jsx)(Ga,{children:[{id:`needs-review`,label:`To Review`},{id:`changes`,label:`Changes`},{id:`new`,label:`New`},{id:`all`,label:`All`}].map(e=>(0,P.jsx)(Ka,{isActive:_===e.id,onClick:()=>v(e.id),children:e.label},e.id))}),(0,P.jsxs)(Ua,{className:`px-2 py-2 space-y-1`,children:[D.map(t=>{let n=G(t)===G(e);return(0,P.jsx)(`div`,{ref:n?te:null,children:(0,P.jsx)($a,{item:t,isActive:n,thumbnailUrl:Eo(t.current||t.baseline,t.timestamp),onClick:()=>o(t)})},G(t))}),D.length===0&&(0,P.jsx)(`div`,{className:`text-center py-8 text-[var(--text-muted)] text-xs`,children:_===`needs-review`?`All screenshots reviewed!`:`No items match this filter`})]}),(0,P.jsxs)(Wa,{className:`hidden sm:flex flex-col items-center gap-1.5`,children:[(0,P.jsxs)(`p`,{className:`text-xs text-[var(--text-muted)] text-center`,children:[(0,P.jsx)(`span`,{className:`text-[var(--text-tertiary)]`,children:`↑↓`}),` navigate`,` · `,(0,P.jsx)(`span`,{className:`text-[var(--text-tertiary)]`,children:`Space`}),` `,w?`exit`:`review mode`]}),w&&(0,P.jsxs)(`p`,{className:`text-xs text-[var(--text-muted)] text-center`,children:[(0,P.jsx)(`span`,{className:`text-[var(--accent-success)] font-medium`,children:`A`}),` `,`approve`,` · `,(0,P.jsx)(`span`,{className:`text-[var(--accent-danger)] font-medium`,children:`R`}),` `,`reject`,` · `,(0,P.jsx)(`span`,{className:`text-[var(--text-tertiary)] font-medium`,children:`D`}),` `,`diff`,` · `,(0,P.jsx)(`span`,{className:`text-[var(--text-tertiary)] font-medium`,children:`T`}),` `,`view`]})]})]}),(0,P.jsx)(`main`,{className:`flex-1 min-h-0 overflow-hidden relative`,children:(0,P.jsx)(Oo,{comparison:e,viewMode:s===So.ONION?`onion-skin`:s,showDiffOverlay:l,onDiffToggle:()=>u(e=>!e),showBaseline:y,onToggleBaseline:()=>b(e=>!e),onionSkinPosition:d,onOnionSkinChange:f,zoom:ee,disableLoadingOverlay:!0,showRegions:x,className:`w-full h-full`},G(e))}),(0,P.jsxs)(Za,{isOpen:h,onClose:()=>g(!1),title:`Screenshot Details`,subtitle:e.name||e.originalName,className:`order-last`,children:[(0,P.jsx)(Za.Section,{title:`Status`,icon:sn,children:(0,P.jsxs)(`div`,{className:`flex items-center gap-2 flex-wrap`,children:[M&&(0,P.jsx)(nr,{variant:M.variant,size:`sm`,children:M.label}),(0,P.jsx)(nr,{variant:de===`approved`?`success`:de===`rejected`?`danger`:`default`,size:`sm`,children:de})]})}),e.warnings?.length>0&&(0,P.jsx)(Za.Section,{title:`Warnings`,icon:hn,children:e.warnings.map(e=>(0,P.jsx)(Za.Row,{label:e.option||`Warning`,value:e.message},`${e.code}-${e.option}`))}),(0,P.jsxs)(Za.Section,{title:`Screenshot`,icon:an,children:[pe.browser&&(0,P.jsx)(Za.Row,{label:`Browser`,value:pe.browser,icon:()=>(0,P.jsx)(ir,{browser:pe.browser,className:`w-3.5 h-3.5`})}),(0,P.jsx)(Za.Row,{label:`Viewport`,value:pe.viewport_width&&pe.viewport_height?`${pe.viewport_width} × ${pe.viewport_height}`:null,mono:!0}),(0,P.jsx)(Za.Row,{label:`Device`,value:pe.device})]}),ue===`changed`&&e.diffPercentage!=null&&(0,P.jsxs)(Za.Section,{title:`Diff Analysis`,icon:pn,children:[(0,P.jsx)(Za.Row,{label:`Changed`,value:`${Number(e.diffPercentage).toFixed(3)}%`,mono:!0}),e.diffPixels!=null&&(0,P.jsx)(Za.Row,{label:`Pixels`,value:e.diffPixels.toLocaleString(),mono:!0}),e.threshold!=null&&(0,P.jsx)(Za.Row,{label:`Threshold`,value:Number(e.threshold).toFixed(2),mono:!0})]}),(0,P.jsx)(Za.Section,{title:`Identifier`,icon:sn,children:(0,P.jsx)(Za.Row,{label:`Signature`,value:e.signature||e.id,mono:!0,copyable:!0})})]})]}),(0,P.jsxs)(`div`,{className:`sm:hidden flex-shrink-0 bg-[var(--vz-bg)] border-t border-[var(--vz-border-subtle)] safe-area-pb`,children:[(0,P.jsx)(xa,{status:de,onApprove:r?oe:null,onReject:i?se:null}),(0,P.jsxs)(`div`,{className:`px-3 py-2 flex items-center gap-1 border-t border-[var(--vz-border-subtle)]`,children:[(0,P.jsx)(`button`,{type:`button`,onClick:()=>m(!p),className:`flex items-center justify-center p-2.5 rounded-lg transition-colors ${p?`bg-[var(--accent-brand-muted)] text-[var(--accent-brand)]`:`text-[var(--text-tertiary)] hover:text-[var(--text-primary)] hover:bg-[var(--vz-raised)] active:bg-[var(--vz-elevated)]`}`,"aria-label":`Queue`,"data-testid":`mobile-toggle-queue-btn`,children:(0,P.jsx)(Tn,{className:`w-5 h-5 pointer-events-none`})}),(0,P.jsx)(`button`,{type:`button`,onClick:ne,className:`flex items-center justify-center p-2.5 rounded-lg transition-colors ${h?`bg-[var(--accent-info-muted)] text-[var(--accent-info)]`:`text-[var(--text-tertiary)] hover:text-[var(--text-primary)] hover:bg-[var(--vz-raised)] active:bg-[var(--vz-elevated)]`}`,"aria-label":`Screenshot details`,"data-testid":`mobile-toggle-inspector-btn`,children:(0,P.jsx)(Cn,{className:`w-5 h-5 pointer-events-none`})}),(e?.confirmedRegions?.length>0||e?.hasConfirmedRegions)&&(0,P.jsx)(`button`,{type:`button`,onClick:()=>S(!x),className:`flex items-center justify-center p-2.5 rounded-lg transition-colors ${x?`bg-[var(--accent-success-muted)] text-[var(--accent-success)]`:`text-[var(--text-tertiary)] hover:text-[var(--text-primary)] hover:bg-[var(--vz-raised)] active:bg-[var(--vz-elevated)]`}`,"aria-label":`Toggle regions`,"data-testid":`mobile-toggle-regions-btn`,children:(0,P.jsx)(jn,{className:`w-5 h-5 pointer-events-none`})}),fe&&a&&(0,P.jsx)(`button`,{type:`button`,onClick:ce,className:`flex items-center justify-center p-2.5 rounded-lg text-[var(--accent-danger)] hover:text-[var(--text-primary)] hover:bg-[var(--accent-danger-muted)] active:bg-[var(--accent-danger-muted)] transition-colors`,"aria-label":`Delete`,"data-testid":`mobile-delete-btn`,children:(0,P.jsx)(`span`,{className:`text-xs font-medium`,children:`Delete`})}),(0,P.jsx)(`div`,{className:`flex-1`}),(0,P.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,P.jsxs)(`span`,{className:`text-xs text-[var(--text-muted)] tabular-nums`,children:[O+1,` / `,D.length]}),M&&(0,P.jsx)(nr,{variant:M.variant,size:`sm`,children:M.label})]})]})]})]})}function Mo(){let[,e]=gi(),[,t]=vi(`/comparison/:id`),{data:n}=Ui(),r=Wi(),i=qi(),a=Xi(),o=(0,N.useMemo)(()=>n?.comparisons||[],[n?.comparisons]),s=(0,N.useMemo)(()=>{let e=t?.id?decodeURIComponent(t.id):null;return!e||o.length===0?null:o.find(t=>t.id===e||t.signature===e||t.name===e)},[t,o]),{data:c}=Hi(s?.id),l=(0,N.useMemo)(()=>s?c?{...s,diffClusters:c.diffClusters,confirmedRegions:c.confirmedRegions,intensityStats:c.intensityStats,boundingBox:c.boundingBox,regionAnalysis:c.regionAnalysis,hotspotAnalysis:c.hotspotAnalysis}:s:null,[s,c]),u=(0,N.useCallback)(t=>{let n=t.id||t.signature||t.name;n&&e(`/comparison/${encodeURIComponent(n)}`)},[e]),d=(0,N.useCallback)(()=>{e(`/`)},[e]),f=(0,N.useCallback)(e=>{r.mutate(e)},[r]),p=(0,N.useCallback)(e=>{i.mutate(e)},[i]),m=(0,N.useCallback)(t=>{a.mutate(t,{onSuccess:()=>{e(`/`)}})},[a,e]);return l?(0,P.jsx)(Ao,{comparison:l,comparisons:o,onClose:d,onAccept:f,onReject:p,onDelete:m,onNavigate:u}):(0,P.jsx)(`div`,{className:`fixed inset-0 bg-[var(--vz-bg)] z-50 flex items-center justify-center`,children:(0,P.jsxs)(`div`,{className:`text-center`,children:[(0,P.jsx)(`div`,{className:`text-lg text-[var(--text-primary)] font-medium mb-2`,children:`Comparison not found`}),(0,P.jsxs)(`p`,{className:`text-sm text-[var(--text-tertiary)] mb-4`,children:[`ID: `,t?.id||`none`]}),(0,P.jsx)(`button`,{type:`button`,onClick:d,className:`text-[var(--accent-media)] hover:text-[var(--accent-media)] font-medium transition-colors`,children:`Return to list`})]})})}To.NEW,To.BASELINE_CREATED,To.PASSED,To.FAILED,To.ERROR,To.REJECTED;function No(e,t){return[...e].sort((e,n)=>{if(t===`priority`){let t={[To.FAILED]:3,[To.NEW]:2,[To.PASSED]:1},r=e.initialStatus||e.status,i=n.initialStatus||n.status,a=t[r]||0,o=t[i]||0;return a===o?(n.diffPercentage||0)-(e.diffPercentage||0):o-a}return t===`name`?(e.name||``).localeCompare(n.name||``):t===`time`?(n.timestamp||0)-(e.timestamp||0):0})}function Po(e,t){return t===`failed`?e.filter(e=>e.status===To.FAILED):t===`passed`?e.filter(e=>e.status===To.PASSED):t===`new`?e.filter(e=>e.status===To.NEW||e.status===To.BASELINE_CREATED):e}function Fo(e){return e===`new`||e===`baseline-created`}function Io(e){return e===`failed`||Fo(e)}var Lo=()=>{let e=new URLSearchParams(window.location.search);return{filter:e.get(`filter`)||Co.ALL,sortBy:e.get(`sort`)||wo.PRIORITY,searchQuery:e.get(`search`)||``,selectedBrowser:e.get(`browser`)||`all`,selectedViewport:e.get(`viewport`)||`all`}};function Ro(e=[]){let t=Lo(),[n,r]=(0,N.useState)(t.filter),[i,a]=(0,N.useState)(t.sortBy),[o,s]=(0,N.useState)(t.searchQuery),[c,l]=(0,N.useState)(t.selectedBrowser),[u,d]=(0,N.useState)(t.selectedViewport);(0,N.useEffect)(()=>{let e=new URLSearchParams;n!==Co.ALL&&e.set(`filter`,n),i!==wo.PRIORITY&&e.set(`sort`,i),o&&e.set(`search`,o),c!==`all`&&e.set(`browser`,c),u!==`all`&&e.set(`viewport`,u);let t=e.toString()?`?${e.toString()}`:window.location.pathname;window.history.replaceState({},``,t)},[n,i,o,c,u]);let f=(0,N.useMemo)(()=>{let t=new Set,n=new Set;return e.forEach(e=>{if(e.properties?.browser&&t.add(e.properties.browser),e.properties?.viewport_width&&e.properties?.viewport_height){let t=`${e.properties.viewport_width}x${e.properties.viewport_height}`;n.add(t)}else if(e.properties?.viewport){let t=`${e.properties.viewport.width}x${e.properties.viewport.height}`;n.add(t)}}),{browsers:Array.from(t).sort(),viewports:Array.from(n).sort()}},[e]);return{filteredComparisons:(0,N.useMemo)(()=>{let t=Po(e,n);if(o.trim()){let e=o.toLowerCase();t=t.filter(t=>t.name.toLowerCase().includes(e)||t.originalName?.toLowerCase().includes(e))}return c!==`all`&&(t=t.filter(e=>e.properties?.browser===c)),u!==`all`&&(t=t.filter(e=>{let t=null;return e.properties?.viewport_width&&e.properties?.viewport_height?t=`${e.properties.viewport_width}x${e.properties.viewport_height}`:e.properties?.viewport&&(t=`${e.properties.viewport.width}x${e.properties.viewport.height}`),t===u})),No(t,i)},[e,n,i,o,c,u]),filter:n,setFilter:r,sortBy:i,setSortBy:a,searchQuery:o,setSearchQuery:s,selectedBrowser:c,setSelectedBrowser:l,selectedViewport:u,setSelectedViewport:d,availableFilters:f,counts:{all:e.length,failed:e.filter(e=>e.status===`failed`).length,passed:e.filter(e=>e.status===`passed`).length,new:e.filter(e=>Fo(e.status)).length,rejected:e.filter(e=>e.status===`rejected`).length}}}function zo({src:e,alt:t,className:n,style:r,onClick:i}){let[a,o]=(0,N.useState)(e?`loading`:`missing`);if((0,N.useEffect)(()=>{if(!e){o(`missing`);return}o(`loading`)},[e]),a===`missing`)return(0,P.jsx)(`div`,{className:`flex items-center justify-center bg-[var(--vz-raised)] border border-[var(--vz-border)] rounded min-h-[200px]`,style:r,children:(0,P.jsxs)(`div`,{className:`text-[var(--text-tertiary)] text-center`,children:[(0,P.jsx)(Fn,{className:`w-12 h-12 mx-auto mb-3`}),(0,P.jsxs)(`div`,{className:`text-sm`,children:[`No `,t.toLowerCase(),` available`]})]})});if(a===`error`)return(0,P.jsx)(`div`,{className:`flex items-center justify-center bg-[var(--accent-danger-muted)] border border-[color-mix(in_srgb,var(--accent-danger)_28%,transparent)] rounded min-h-[200px]`,style:r,children:(0,P.jsxs)(`div`,{className:`text-[var(--accent-danger)] text-center`,children:[(0,P.jsx)(hn,{className:`w-12 h-12 mx-auto mb-3`}),(0,P.jsxs)(`div`,{className:`text-sm`,children:[`Failed to load `,t.toLowerCase()]}),(0,P.jsx)(`div`,{className:`text-xs text-[var(--text-muted)] mt-1 font-mono break-all px-4`,children:e})]})});let s=null;return i&&(s=e=>{(e.key===`Enter`||e.key===` `)&&(e.preventDefault(),i(e))}),(0,P.jsx)(`img`,{src:e,alt:t,className:n,style:r,loading:`lazy`,decoding:`async`,onLoad:()=>o(`loaded`),onError:()=>o(`error`),onClick:i,onKeyDown:s,role:i?`button`:void 0,tabIndex:i?0:void 0})}function Bo(e){return e.id||e.signature||e.name}function Vo(e){let t=new Map;for(let n of e){let e=n.name||n.originalName||`Unknown`;t.has(e)||t.set(e,{name:e,comparisons:[],browsers:new Set,viewports:[],hasChanges:!1,hasNew:!1,allPassed:!0,maxDiff:0,failedCount:0,newCount:0,passedCount:0,acceptedCount:0,rejectedCount:0});let r=t.get(e);r.comparisons.push(n),n.properties?.browser&&r.browsers.add(n.properties.browser),n.properties?.viewport_width&&n.properties?.viewport_height&&r.viewports.push({width:n.properties.viewport_width,height:n.properties.viewport_height,id:n.id,status:n.status}),n.status===`failed`?(r.hasChanges=!0,r.allPassed=!1,r.failedCount++,r.maxDiff=Math.max(r.maxDiff,n.diffPercentage||0)):n.status===`new`||n.status===`baseline-created`?(r.hasNew=!0,r.allPassed=!1,r.newCount++):n.status===`passed`&&r.passedCount++,n.userAction===`accepted`?r.acceptedCount++:n.userAction===`rejected`&&r.rejectedCount++}return Array.from(t.values()).map(e=>({...e,browsers:Array.from(e.browsers),viewports:e.viewports.sort((e,t)=>t.width*t.height-e.width*e.height),primary:e.comparisons[0]})).sort((e,t)=>e.hasChanges&&!t.hasChanges?-1:!e.hasChanges&&t.hasChanges?1:e.hasNew&&!t.hasNew?-1:!e.hasNew&&t.hasNew?1:e.maxDiff===t.maxDiff?e.name.localeCompare(t.name):t.maxDiff-e.maxDiff)}function Ho({width:e,className:t=`w-3.5 h-3.5`}){return e<=480?(0,P.jsx)(ln,{className:t}):e<=1024?(0,P.jsx)(dn,{className:t}):(0,P.jsx)(an,{className:t})}function Uo({group:e}){return e.acceptedCount===e.comparisons.length?(0,P.jsxs)(nr,{variant:`success`,size:`sm`,children:[(0,P.jsx)(Bt,{className:`w-3 h-3 mr-1`}),`Accepted`]}):e.rejectedCount>0?(0,P.jsxs)(nr,{variant:`warning`,size:`sm`,children:[(0,P.jsx)(Yn,{className:`w-3 h-3 mr-1`}),`Rejected`]}):e.hasChanges?(0,P.jsxs)(nr,{variant:`danger`,size:`sm`,children:[e.failedCount,` changed`]}):e.hasNew?(0,P.jsxs)(nr,{variant:`info`,size:`sm`,children:[e.newCount,` new`]}):(0,P.jsx)(nr,{variant:`success`,size:`sm`,children:`Passed`})}function Wo({group:e,maxVisible:t=4}){let n=e.comparisons.length;if(n<=1){let t=e.viewports[0],n=e.browsers[0];return(0,P.jsxs)(`div`,{className:`flex items-center gap-2 text-xs text-[var(--text-tertiary)]`,children:[t&&(0,P.jsxs)(`span`,{className:`inline-flex items-center gap-1`,children:[(0,P.jsx)(Ho,{width:t.width,className:`w-3 h-3`}),t.width,`×`,t.height]}),n&&(0,P.jsx)(`span`,{children:n})]})}let r=e.viewports.slice(0,t),i=n-t;return(0,P.jsxs)(`div`,{className:`flex items-center gap-1.5 flex-wrap`,children:[r.map((e,t)=>(0,P.jsxs)(`span`,{className:`
|
|
198
198
|
inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-[10px] font-medium
|
|
199
199
|
${e.status===`failed`?`bg-[var(--accent-danger-muted)] text-[var(--accent-danger)] border border-[color-mix(in_srgb,var(--accent-danger)_32%,transparent)]`:e.status===`new`||e.status===`baseline-created`?`bg-[var(--accent-media-muted)] text-[var(--accent-media)] border border-[color-mix(in_srgb,var(--accent-media)_30%,transparent)]`:`bg-[var(--vz-raised)] text-[var(--text-tertiary)] border border-[var(--vz-border)]`}
|
|
200
200
|
`,children:[(0,P.jsx)(Ho,{width:e.width,className:`w-2.5 h-2.5`}),e.width]},t)),i>0&&(0,P.jsxs)(`span`,{className:`text-[10px] text-[var(--text-muted)]`,children:[`+`,i,` more`]}),e.browsers.length>0&&(0,P.jsxs)(`span`,{className:`text-[10px] text-[var(--text-muted)] ml-1`,children:[`· `,e.browsers.join(`, `)]})]})}function Go({group:e,onSelect:t,onAcceptGroup:n,onRejectGroup:r,isAccepting:i}){let{primary:a,hasChanges:o,hasNew:s,maxDiff:c}=e,l=o||s,u=Eo(a.current||a.baseline,a.timestamp);return(0,P.jsxs)(`div`,{"data-testid":`screenshot-group-${(a.id||a.signature||e.name).replace(/[^a-zA-Z0-9-]/g,`-`)}`,className:`
|
package/dist/sdk/index.js
CHANGED
|
@@ -19,7 +19,7 @@ import { createUploader } from '../uploader/index.js';
|
|
|
19
19
|
import { loadConfig } from '../utils/config-loader.js';
|
|
20
20
|
import { resolveImageBuffer } from '../utils/file-helpers.js';
|
|
21
21
|
import * as output from '../utils/output.js';
|
|
22
|
-
import {
|
|
22
|
+
import { normalizeScreenshotOptions } from '../utils/screenshot-options.js';
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* Create a new Vizzly instance with custom configuration
|
|
@@ -293,9 +293,16 @@ export class VizzlySDK extends EventEmitter {
|
|
|
293
293
|
|
|
294
294
|
// Resolve Buffer or file path using shared utility
|
|
295
295
|
let buffer = resolveImageBuffer(imageBuffer, 'screenshot');
|
|
296
|
+
let normalizedOptions = normalizeScreenshotOptions(options);
|
|
297
|
+
for (let warning of normalizedOptions.warnings) {
|
|
298
|
+
output.warn(warning.message, {
|
|
299
|
+
code: warning.code,
|
|
300
|
+
option: warning.option
|
|
301
|
+
});
|
|
302
|
+
}
|
|
296
303
|
|
|
297
304
|
// Generate or use provided build ID
|
|
298
|
-
let buildId =
|
|
305
|
+
let buildId = normalizedOptions.buildId || this.currentBuildId || 'default';
|
|
299
306
|
this.currentBuildId = buildId;
|
|
300
307
|
|
|
301
308
|
// Convert Buffer to base64 for JSON transport
|
|
@@ -305,8 +312,11 @@ export class VizzlySDK extends EventEmitter {
|
|
|
305
312
|
name,
|
|
306
313
|
image: imageBase64,
|
|
307
314
|
type: 'base64',
|
|
308
|
-
properties:
|
|
315
|
+
properties: normalizedOptions.properties
|
|
309
316
|
};
|
|
317
|
+
if (normalizedOptions.warnings.length > 0) {
|
|
318
|
+
screenshotData.warnings = normalizedOptions.warnings;
|
|
319
|
+
}
|
|
310
320
|
|
|
311
321
|
// POST to the local screenshot server
|
|
312
322
|
let serverUrl = `http://localhost:${this.config.server?.port || 3000}`;
|
|
@@ -362,12 +372,16 @@ export class VizzlySDK extends EventEmitter {
|
|
|
362
372
|
let screenshotsDir = options.screenshotsDir || this.config?.upload?.screenshotsDir || './screenshots';
|
|
363
373
|
let uploadOptions = {
|
|
364
374
|
screenshotsDir,
|
|
365
|
-
buildName: options.buildName || this.config.
|
|
366
|
-
branch: options.branch || this.config.branch,
|
|
367
|
-
commit: options.commit || this.config.commit,
|
|
368
|
-
message: options.message || this.config.message,
|
|
369
|
-
environment: options.environment || this.config.environment || 'production',
|
|
370
|
-
threshold: options.threshold ?? this.config.threshold,
|
|
375
|
+
buildName: options.buildName || this.config.build?.name,
|
|
376
|
+
branch: options.branch || this.config.build?.branch,
|
|
377
|
+
commit: options.commit || this.config.build?.commit,
|
|
378
|
+
message: options.message || this.config.build?.message,
|
|
379
|
+
environment: options.environment || this.config.build?.environment || 'production',
|
|
380
|
+
threshold: options.threshold ?? this.config.comparison?.threshold,
|
|
381
|
+
minClusterSize: options.minClusterSize ?? this.config.comparison?.minClusterSize,
|
|
382
|
+
metadata: options.metadata || {},
|
|
383
|
+
pullRequestNumber: options.pullRequestNumber,
|
|
384
|
+
parallelId: options.parallelId || this.config.parallelId,
|
|
371
385
|
onProgress: progress => {
|
|
372
386
|
this.emit('upload:progress', progress);
|
|
373
387
|
if (options.onProgress) {
|
|
@@ -47,7 +47,7 @@ export const createApiHandler = (client, {
|
|
|
47
47
|
let vizzlyDisabled = false;
|
|
48
48
|
let screenshotCount = 0;
|
|
49
49
|
let uploadPromises = [];
|
|
50
|
-
const handleScreenshot = async (buildId, name, image, properties = {}, type) => {
|
|
50
|
+
const handleScreenshot = async (buildId, name, image, properties = {}, type, warnings = []) => {
|
|
51
51
|
let handlerStart = Date.now();
|
|
52
52
|
output.debug('upload', `${name} received`, {
|
|
53
53
|
buildId: buildId?.slice(0, 8)
|
|
@@ -177,7 +177,8 @@ export const createApiHandler = (client, {
|
|
|
177
177
|
body: {
|
|
178
178
|
success: true,
|
|
179
179
|
name,
|
|
180
|
-
count: screenshotCount
|
|
180
|
+
count: screenshotCount,
|
|
181
|
+
warnings
|
|
181
182
|
}
|
|
182
183
|
};
|
|
183
184
|
};
|
|
@@ -305,7 +305,7 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
|
|
|
305
305
|
output.debug('tdd', `baseline: ${baseline.buildName}`);
|
|
306
306
|
}
|
|
307
307
|
};
|
|
308
|
-
const handleScreenshot = async (_buildId, name, image, properties = {}, type) => {
|
|
308
|
+
const handleScreenshot = async (_buildId, name, image, properties = {}, type, warnings = []) => {
|
|
309
309
|
let handlerStart = Date.now();
|
|
310
310
|
output.debug('tdd', `${name} received`);
|
|
311
311
|
|
|
@@ -447,6 +447,7 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
|
|
|
447
447
|
aaPercentage: comparison.aaPercentage,
|
|
448
448
|
heightDiff: comparison.heightDiff,
|
|
449
449
|
error: comparison.error,
|
|
450
|
+
warnings,
|
|
450
451
|
originalName: name,
|
|
451
452
|
timestamp: Date.now(),
|
|
452
453
|
// Boolean hints so UI can show toggle buttons without fetching heavy data
|
|
@@ -490,6 +491,7 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
|
|
|
490
491
|
diff: comparison.diff,
|
|
491
492
|
diffPercentage: comparison.diffPercentage,
|
|
492
493
|
threshold: comparison.threshold,
|
|
494
|
+
warnings,
|
|
493
495
|
tddMode: true
|
|
494
496
|
}
|
|
495
497
|
};
|
|
@@ -503,6 +505,7 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
|
|
|
503
505
|
message: `Baseline updated for '${name}'`,
|
|
504
506
|
baseline: comparison.baseline,
|
|
505
507
|
current: comparison.current,
|
|
508
|
+
warnings,
|
|
506
509
|
tddMode: true
|
|
507
510
|
}
|
|
508
511
|
};
|
|
@@ -530,6 +533,7 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
|
|
|
530
533
|
name: comparison.name,
|
|
531
534
|
baseline: comparison.baseline,
|
|
532
535
|
current: comparison.current,
|
|
536
|
+
warnings,
|
|
533
537
|
tddMode: true
|
|
534
538
|
}
|
|
535
539
|
};
|
|
@@ -32,7 +32,8 @@ export function createScreenshotRouter({
|
|
|
32
32
|
name,
|
|
33
33
|
properties,
|
|
34
34
|
image,
|
|
35
|
-
type
|
|
35
|
+
type,
|
|
36
|
+
warnings
|
|
36
37
|
} = body;
|
|
37
38
|
if (!name || !image) {
|
|
38
39
|
sendError(res, 400, 'name and image are required');
|
|
@@ -41,7 +42,7 @@ export function createScreenshotRouter({
|
|
|
41
42
|
|
|
42
43
|
// Use buildId from request body, or fall back to server's buildId
|
|
43
44
|
const effectiveBuildId = buildId || defaultBuildId;
|
|
44
|
-
const result = await screenshotHandler.handleScreenshot(effectiveBuildId, name, image, properties, type);
|
|
45
|
+
const result = await screenshotHandler.handleScreenshot(effectiveBuildId, name, image, properties, type, warnings);
|
|
45
46
|
sendJson(res, result.statusCode, result.body);
|
|
46
47
|
return true;
|
|
47
48
|
} catch (error) {
|
|
@@ -57,9 +58,15 @@ export function createScreenshotRouter({
|
|
|
57
58
|
// Flush endpoint - signals test completion and prints summary
|
|
58
59
|
if (pathname === '/flush') {
|
|
59
60
|
try {
|
|
60
|
-
if (screenshotHandler.
|
|
61
|
+
if (screenshotHandler.flush) {
|
|
62
|
+
let stats = await screenshotHandler.flush();
|
|
63
|
+
sendJson(res, 200, {
|
|
64
|
+
success: true,
|
|
65
|
+
...stats
|
|
66
|
+
});
|
|
67
|
+
} else if (screenshotHandler.getResults) {
|
|
61
68
|
// This triggers printResults() which outputs the summary
|
|
62
|
-
|
|
69
|
+
let results = await screenshotHandler.getResults();
|
|
63
70
|
sendJson(res, 200, {
|
|
64
71
|
success: true,
|
|
65
72
|
summary: {
|
|
@@ -20,9 +20,12 @@ import { randomUUID } from 'node:crypto';
|
|
|
20
20
|
export function createBuildObject(buildOptions, deps = {}) {
|
|
21
21
|
let {
|
|
22
22
|
name,
|
|
23
|
+
buildName,
|
|
23
24
|
branch,
|
|
24
25
|
commit,
|
|
26
|
+
message,
|
|
25
27
|
environment = 'test',
|
|
28
|
+
parallelId,
|
|
26
29
|
metadata = {}
|
|
27
30
|
} = buildOptions;
|
|
28
31
|
let randomId = deps.randomId || randomUUID;
|
|
@@ -30,10 +33,12 @@ export function createBuildObject(buildOptions, deps = {}) {
|
|
|
30
33
|
let timestamp = deps.timestamp || Date.now;
|
|
31
34
|
return {
|
|
32
35
|
id: `build-${randomId()}`,
|
|
33
|
-
name: name || `build-${timestamp()}`,
|
|
36
|
+
name: name || buildName || `build-${timestamp()}`,
|
|
34
37
|
branch,
|
|
35
38
|
commit,
|
|
39
|
+
message,
|
|
36
40
|
environment,
|
|
41
|
+
parallelId,
|
|
37
42
|
metadata,
|
|
38
43
|
status: 'pending',
|
|
39
44
|
createdAt: now(),
|
package/dist/test-runner/core.js
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* @param {number} options.port - Server port
|
|
15
15
|
* @param {string} options.buildId - Build ID
|
|
16
16
|
* @param {boolean} [options.setBaseline] - Whether to set baseline
|
|
17
|
+
* @param {boolean} [options.failOnDiff] - Whether visual diffs should fail tests
|
|
17
18
|
* @param {Object} [options.baseEnv] - Base environment (defaults to process.env)
|
|
18
19
|
* @returns {Object} Environment variables object
|
|
19
20
|
*/
|
|
@@ -21,6 +22,7 @@ export function buildTestEnv({
|
|
|
21
22
|
port,
|
|
22
23
|
buildId,
|
|
23
24
|
setBaseline = false,
|
|
25
|
+
failOnDiff = false,
|
|
24
26
|
baseEnv = process.env
|
|
25
27
|
}) {
|
|
26
28
|
return {
|
|
@@ -28,7 +30,8 @@ export function buildTestEnv({
|
|
|
28
30
|
VIZZLY_SERVER_URL: `http://localhost:${port}`,
|
|
29
31
|
VIZZLY_BUILD_ID: buildId,
|
|
30
32
|
VIZZLY_ENABLED: 'true',
|
|
31
|
-
VIZZLY_SET_BASELINE: setBaseline ? 'true' : 'false'
|
|
33
|
+
VIZZLY_SET_BASELINE: setBaseline ? 'true' : 'false',
|
|
34
|
+
VIZZLY_FAIL_ON_DIFF: failOnDiff ? 'true' : 'false'
|
|
32
35
|
};
|
|
33
36
|
}
|
|
34
37
|
|
package/dist/types/client.d.ts
CHANGED
|
@@ -24,6 +24,7 @@ export function autoDiscoverTddServer(
|
|
|
24
24
|
deps?: {
|
|
25
25
|
exists?: (path: string) => boolean;
|
|
26
26
|
readFile?: (path: string, encoding: BufferEncoding) => string | Buffer;
|
|
27
|
+
env?: Record<string, string | undefined>;
|
|
27
28
|
}
|
|
28
29
|
): string | null;
|
|
29
30
|
|
|
@@ -32,7 +33,14 @@ export function autoDiscoverTddServer(
|
|
|
32
33
|
*/
|
|
33
34
|
export interface ScreenshotResult {
|
|
34
35
|
success: boolean;
|
|
35
|
-
status?:
|
|
36
|
+
status?:
|
|
37
|
+
| 'passed'
|
|
38
|
+
| 'failed'
|
|
39
|
+
| 'new'
|
|
40
|
+
| 'match'
|
|
41
|
+
| 'diff'
|
|
42
|
+
| 'baseline-updated'
|
|
43
|
+
| 'error';
|
|
36
44
|
name?: string;
|
|
37
45
|
diffPercentage?: number;
|
|
38
46
|
[key: string]: unknown;
|
|
@@ -59,10 +67,16 @@ export interface ScreenshotResult {
|
|
|
59
67
|
* @example
|
|
60
68
|
* // With properties and comparison settings
|
|
61
69
|
* await vizzlyScreenshot('checkout-form', screenshot, {
|
|
62
|
-
* properties: { browser: 'chrome', viewport:
|
|
70
|
+
* properties: { browser: 'chrome', viewport: { width: 1920, height: 1080 } },
|
|
63
71
|
* threshold: 5,
|
|
64
|
-
* minClusterSize: 10
|
|
72
|
+
* minClusterSize: 10,
|
|
73
|
+
* fullPage: true,
|
|
74
|
+
* requestTimeout: 5000
|
|
65
75
|
* });
|
|
76
|
+
*
|
|
77
|
+
* `properties` is the user metadata bag. Comparison options are normalized
|
|
78
|
+
* into the server metadata payload, while `requestTimeout` stays on the
|
|
79
|
+
* client request and `buildId` only routes the screenshot to a build.
|
|
66
80
|
*/
|
|
67
81
|
export function vizzlyScreenshot(
|
|
68
82
|
name: string,
|
|
@@ -72,7 +86,10 @@ export function vizzlyScreenshot(
|
|
|
72
86
|
threshold?: number;
|
|
73
87
|
minClusterSize?: number;
|
|
74
88
|
fullPage?: boolean;
|
|
75
|
-
|
|
89
|
+
/** Transport-only build ID used to route the request. */
|
|
90
|
+
buildId?: string;
|
|
91
|
+
/** Client-side HTTP timeout in milliseconds; not stored as metadata. */
|
|
92
|
+
requestTimeout?: number;
|
|
76
93
|
}
|
|
77
94
|
): Promise<ScreenshotResult | null>;
|
|
78
95
|
|
|
@@ -81,7 +98,15 @@ export function vizzlyScreenshot(
|
|
|
81
98
|
*/
|
|
82
99
|
export interface FlushResult {
|
|
83
100
|
success: boolean;
|
|
84
|
-
|
|
101
|
+
uploaded?: number;
|
|
102
|
+
flushed?: boolean;
|
|
103
|
+
total?: number;
|
|
104
|
+
passed?: number;
|
|
105
|
+
failed?: number;
|
|
106
|
+
new?: number;
|
|
107
|
+
errors?: number;
|
|
108
|
+
message?: string;
|
|
109
|
+
summary?: {
|
|
85
110
|
total: number;
|
|
86
111
|
passed: number;
|
|
87
112
|
failed: number;
|
|
@@ -124,6 +149,7 @@ export function isVizzlyReady(): boolean;
|
|
|
124
149
|
export function configure(config?: {
|
|
125
150
|
serverUrl?: string;
|
|
126
151
|
enabled?: boolean;
|
|
152
|
+
failOnDiff?: boolean;
|
|
127
153
|
}): void;
|
|
128
154
|
|
|
129
155
|
/**
|
|
@@ -145,4 +171,5 @@ export function getVizzlyInfo(): {
|
|
|
145
171
|
buildId: string | null;
|
|
146
172
|
tddMode: boolean;
|
|
147
173
|
disabled: boolean;
|
|
174
|
+
failOnDiff: boolean;
|
|
148
175
|
};
|