@vizzly-testing/cli 0.33.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 +53 -3
- package/dist/api/core.js +2 -1
- package/dist/cli.js +21 -8
- package/dist/client/index.js +62 -21
- package/dist/commands/api.js +9 -3
- package/dist/commands/init.js +214 -2
- 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 +2 -1
- package/skills/vizzly/SKILL.md +99 -0
- package/skills/vizzly/agents/openai.yaml +4 -0
- package/skills/vizzly/references/cli-context.md +42 -0
- package/skills/vizzly/references/dynamic-content.md +44 -0
- package/skills/vizzly/references/public-screenshots.md +44 -0
- package/skills/vizzly/references/sdks.md +62 -0
- package/skills/vizzly/references/setup-ci.md +42 -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
|
};
|
package/dist/types/index.d.ts
CHANGED
|
@@ -24,7 +24,9 @@ export interface BuildConfig {
|
|
|
24
24
|
|
|
25
25
|
export interface UploadConfig {
|
|
26
26
|
screenshotsDir?: string | string[];
|
|
27
|
+
/** Batch size used when uploading screenshots. */
|
|
27
28
|
batchSize?: number;
|
|
29
|
+
/** Timeout in milliseconds for waiting on build processing after upload. */
|
|
28
30
|
timeout?: number;
|
|
29
31
|
}
|
|
30
32
|
|
|
@@ -76,13 +78,22 @@ export interface ScreenshotOptions {
|
|
|
76
78
|
threshold?: number;
|
|
77
79
|
minClusterSize?: number;
|
|
78
80
|
fullPage?: boolean;
|
|
81
|
+
/** Transport-only build ID used to route the request. */
|
|
79
82
|
buildId?: string;
|
|
80
|
-
|
|
83
|
+
/** Client-side HTTP timeout in milliseconds; not stored as metadata. */
|
|
84
|
+
requestTimeout?: number;
|
|
81
85
|
}
|
|
82
86
|
|
|
83
87
|
export interface ScreenshotResult {
|
|
84
88
|
success: boolean;
|
|
85
|
-
status?:
|
|
89
|
+
status?:
|
|
90
|
+
| 'passed'
|
|
91
|
+
| 'failed'
|
|
92
|
+
| 'new'
|
|
93
|
+
| 'match'
|
|
94
|
+
| 'diff'
|
|
95
|
+
| 'baseline-updated'
|
|
96
|
+
| 'error';
|
|
86
97
|
name?: string;
|
|
87
98
|
diffPercentage?: number;
|
|
88
99
|
[key: string]: unknown;
|
|
@@ -95,7 +106,13 @@ export interface ScreenshotResult {
|
|
|
95
106
|
export interface ComparisonResult {
|
|
96
107
|
id: string;
|
|
97
108
|
name: string;
|
|
98
|
-
status:
|
|
109
|
+
status:
|
|
110
|
+
| 'passed'
|
|
111
|
+
| 'failed'
|
|
112
|
+
| 'new'
|
|
113
|
+
| 'error'
|
|
114
|
+
| 'baseline-created'
|
|
115
|
+
| 'baseline-updated';
|
|
99
116
|
baseline: string;
|
|
100
117
|
current: string;
|
|
101
118
|
diff: string | null;
|
|
@@ -150,7 +167,9 @@ export interface UploadOptions {
|
|
|
150
167
|
message?: string;
|
|
151
168
|
environment?: string;
|
|
152
169
|
threshold?: number;
|
|
153
|
-
|
|
170
|
+
minClusterSize?: number;
|
|
171
|
+
metadata?: Record<string, unknown>;
|
|
172
|
+
pullRequestNumber?: string | number;
|
|
154
173
|
parallelId?: string;
|
|
155
174
|
onProgress?: (progress: UploadProgress) => void;
|
|
156
175
|
}
|
|
@@ -186,8 +205,10 @@ export interface BuildResult {
|
|
|
186
205
|
status: 'completed' | 'failed' | 'pending';
|
|
187
206
|
build: unknown;
|
|
188
207
|
comparisons?: number;
|
|
208
|
+
totalComparisons: number;
|
|
189
209
|
passedComparisons?: number;
|
|
190
210
|
failedComparisons?: number;
|
|
211
|
+
newComparisons: number;
|
|
191
212
|
url?: string;
|
|
192
213
|
}
|
|
193
214
|
|
|
@@ -487,15 +508,19 @@ export interface BuildOptions {
|
|
|
487
508
|
buildName?: string;
|
|
488
509
|
branch?: string;
|
|
489
510
|
commit?: string;
|
|
511
|
+
commit_sha?: string;
|
|
490
512
|
message?: string;
|
|
513
|
+
commit_message?: string;
|
|
491
514
|
environment?: string;
|
|
492
515
|
threshold?: number;
|
|
493
516
|
eager?: boolean;
|
|
494
517
|
allowNoToken?: boolean;
|
|
495
518
|
wait?: boolean;
|
|
496
519
|
uploadAll?: boolean;
|
|
497
|
-
pullRequestNumber?: string;
|
|
520
|
+
pullRequestNumber?: string | number;
|
|
521
|
+
github_pull_request_number?: string | number;
|
|
498
522
|
parallelId?: string;
|
|
523
|
+
parallel_id?: string;
|
|
499
524
|
}
|
|
500
525
|
|
|
501
526
|
/**
|
|
@@ -565,6 +590,8 @@ export function vizzlyScreenshot(
|
|
|
565
590
|
threshold?: number;
|
|
566
591
|
minClusterSize?: number;
|
|
567
592
|
fullPage?: boolean;
|
|
593
|
+
requestTimeout?: number;
|
|
594
|
+
buildId?: string;
|
|
568
595
|
[key: string]: unknown;
|
|
569
596
|
}
|
|
570
597
|
): Promise<ScreenshotResult | null>;
|
|
@@ -573,6 +600,7 @@ export function vizzlyScreenshot(
|
|
|
573
600
|
export function configure(config?: {
|
|
574
601
|
serverUrl?: string;
|
|
575
602
|
enabled?: boolean;
|
|
603
|
+
failOnDiff?: boolean;
|
|
576
604
|
}): void;
|
|
577
605
|
|
|
578
606
|
/** Enable or disable screenshot capture */
|
package/dist/uploader/core.js
CHANGED
|
@@ -123,6 +123,25 @@ export function extractBrowserFromFilename(filename) {
|
|
|
123
123
|
// Build Info Construction
|
|
124
124
|
// ============================================================================
|
|
125
125
|
|
|
126
|
+
function buildUploadMetadata(options) {
|
|
127
|
+
let metadata = {
|
|
128
|
+
...(options.metadata || {})
|
|
129
|
+
};
|
|
130
|
+
let comparison = {
|
|
131
|
+
...(metadata.comparison || {})
|
|
132
|
+
};
|
|
133
|
+
if (options.threshold !== undefined) {
|
|
134
|
+
comparison.threshold = options.threshold;
|
|
135
|
+
}
|
|
136
|
+
if (options.minClusterSize !== undefined) {
|
|
137
|
+
comparison.minClusterSize = options.minClusterSize;
|
|
138
|
+
}
|
|
139
|
+
if (Object.keys(comparison).length > 0) {
|
|
140
|
+
metadata.comparison = comparison;
|
|
141
|
+
}
|
|
142
|
+
return Object.keys(metadata).length > 0 ? metadata : undefined;
|
|
143
|
+
}
|
|
144
|
+
|
|
126
145
|
/**
|
|
127
146
|
* Build API build info payload
|
|
128
147
|
* @param {Object} options - Upload options
|
|
@@ -130,7 +149,7 @@ export function extractBrowserFromFilename(filename) {
|
|
|
130
149
|
* @returns {Object} Build info payload
|
|
131
150
|
*/
|
|
132
151
|
export function buildBuildInfo(options, defaultBranch = 'main') {
|
|
133
|
-
|
|
152
|
+
let buildInfo = {
|
|
134
153
|
name: options.buildName || `Upload ${new Date().toISOString()}`,
|
|
135
154
|
branch: options.branch || defaultBranch || 'main',
|
|
136
155
|
commit_sha: options.commit,
|
|
@@ -140,6 +159,11 @@ export function buildBuildInfo(options, defaultBranch = 'main') {
|
|
|
140
159
|
github_pull_request_number: options.pullRequestNumber,
|
|
141
160
|
parallel_id: options.parallelId
|
|
142
161
|
};
|
|
162
|
+
let metadata = buildUploadMetadata(options);
|
|
163
|
+
if (metadata) {
|
|
164
|
+
buildInfo.metadata = metadata;
|
|
165
|
+
}
|
|
166
|
+
return buildInfo;
|
|
143
167
|
}
|
|
144
168
|
|
|
145
169
|
// ============================================================================
|
|
@@ -316,17 +340,25 @@ export function buildUploadResult({
|
|
|
316
340
|
* @returns {Object} Wait result
|
|
317
341
|
*/
|
|
318
342
|
export function buildWaitResult(build) {
|
|
343
|
+
let totalComparisons = build.comparisonsTotal ?? build.total_comparisons ?? 0;
|
|
344
|
+
let passedComparisons = build.comparisonsPassed ?? build.passed_comparisons ?? build.identicalComparisons ?? build.identical_comparisons ?? 0;
|
|
345
|
+
let failedComparisons = build.comparisonsFailed ?? build.failed_comparisons ?? build.changedComparisons ?? build.changed_comparisons ?? 0;
|
|
346
|
+
let newComparisons = build.comparisonsNew ?? build.newComparisons ?? build.new_comparisons ?? 0;
|
|
347
|
+
let identicalComparisons = build.identicalComparisons ?? build.identical_comparisons ?? passedComparisons;
|
|
348
|
+
let approvalStatus = build.approvalStatus ?? build.approval_status ?? 'pending';
|
|
349
|
+
let hasComparisonCounts = build.comparisonsTotal !== undefined || build.total_comparisons !== undefined || build.comparisonsPassed !== undefined || build.passed_comparisons !== undefined || build.comparisonsFailed !== undefined || build.failed_comparisons !== undefined || build.comparisonsNew !== undefined || build.new_comparisons !== undefined || build.identicalComparisons !== undefined || build.identical_comparisons !== undefined;
|
|
319
350
|
let result = {
|
|
320
351
|
status: 'completed',
|
|
321
|
-
build
|
|
352
|
+
build,
|
|
353
|
+
passedComparisons,
|
|
354
|
+
failedComparisons,
|
|
355
|
+
newComparisons,
|
|
356
|
+
identicalComparisons,
|
|
357
|
+
approvalStatus,
|
|
358
|
+
totalComparisons
|
|
322
359
|
};
|
|
323
|
-
if (
|
|
324
|
-
result.comparisons =
|
|
325
|
-
result.passedComparisons = build.comparisonsPassed || 0;
|
|
326
|
-
result.failedComparisons = build.comparisonsFailed || 0;
|
|
327
|
-
} else {
|
|
328
|
-
result.passedComparisons = 0;
|
|
329
|
-
result.failedComparisons = 0;
|
|
360
|
+
if (hasComparisonCounts) {
|
|
361
|
+
result.comparisons = totalComparisons;
|
|
330
362
|
}
|
|
331
363
|
if (build.url) {
|
|
332
364
|
result.url = build.url;
|